1 /*
2  *  editdlgtypes.cpp  -  dialogs to create or edit alarm or alarm template types
3  *  Program:  kalarm
4  *  SPDX-FileCopyrightText: 2001-2021 David Jarvie <djarvie@kde.org>
5  *
6  *  SPDX-License-Identifier: GPL-2.0-or-later
7  */
8 
9 #include "editdlgtypes.h"
10 #include "editdlg_p.h"
11 
12 #include "emailidcombo.h"
13 #include "fontcolourbutton.h"
14 #include "functions.h"
15 #include "kalarmapp.h"
16 #include "kamail.h"
17 #include "latecancel.h"
18 #include "mainwindow.h"
19 #include "messagewindow.h"
20 #include "pickfileradio.h"
21 #include "reminder.h"
22 #include "soundpicker.h"
23 #include "sounddlg.h"
24 #include "specialactions.h"
25 #include "templatepickdlg.h"
26 #include "lib/autoqpointer.h"
27 #include "lib/buttongroup.h"
28 #include "lib/checkbox.h"
29 #include "lib/colourbutton.h"
30 #include "lib/dragdrop.h"
31 #include "lib/file.h"
32 #include "lib/lineedit.h"
33 #include "lib/messagebox.h"
34 #include "lib/radiobutton.h"
35 #include "lib/shellprocess.h"
36 #include "lib/timespinbox.h"
37 #include "kalarm_debug.h"
38 
39 #include <KAlarmCal/Identities>
40 
41 #include <Akonadi/Contact/EmailAddressSelectionDialog>
42 #include <KCalUtils/ICalDrag>
43 #include <KCalendarCore/Person>
44 
45 #include <KLocalizedString>
46 #include <KFileItem>
47 
48 #include <QComboBox>
49 #include <QLabel>
50 #include <QDir>
51 #include <QStyle>
52 #include <QGroupBox>
53 #include <QGridLayout>
54 #include <QHBoxLayout>
55 #include <QVBoxLayout>
56 #include <QDragEnterEvent>
57 #include <QStandardItemModel>
58 
59 using namespace KAlarmCal;
60 using namespace KCalendarCore;
61 
62 enum { tTEXT, tFILE, tCOMMAND };  // order of mTypeCombo items
63 enum { dWINDOW, dNOTIFY };        // order of mDisplayMethodCombo items
64 
65 
66 /*=============================================================================
67 = Class PickLogFileRadio
68 =============================================================================*/
69 class PickLogFileRadio : public PickFileRadio
70 {
71     public:
PickLogFileRadio(QPushButton * b,LineEdit * e,const QString & text,ButtonGroup * group,QWidget * parent)72         PickLogFileRadio(QPushButton* b, LineEdit* e, const QString& text, ButtonGroup* group, QWidget* parent)
73             : PickFileRadio(b, e, text, group, parent) { }
pickFile(QString & file)74         bool pickFile(QString& file) override    // called when browse button is pressed to select a log file
75         {
76             return File::browseFile(file, i18nc("@title:window", "Choose Log File"), mDefaultDir, fileEdit()->text(),
77                                     false, parentWidget());
78         }
79     private:
80         QString mDefaultDir;   // default directory for log file browse button
81 };
82 
83 
84 /*=============================================================================
85 = Class EditDisplayAlarmDlg
86 = Dialog to edit display alarms.
87 =============================================================================*/
88 
i18n_lbl_DisplayMethod()89 QString EditDisplayAlarmDlg::i18n_lbl_DisplayMethod() { return i18nc("@label:listbox How to display alarms", "Display method:"); }
i18n_combo_Window()90 QString EditDisplayAlarmDlg::i18n_combo_Window()      { return i18nc("@item:inlistbox", "Window"); }
i18n_combo_Notify()91 QString EditDisplayAlarmDlg::i18n_combo_Notify()      { return i18nc("@item:inlistbox", "Notification"); }
i18n_chk_ConfirmAck()92 QString EditDisplayAlarmDlg::i18n_chk_ConfirmAck()    { return i18nc("@option:check", "Confirm acknowledgment"); }
93 
94 /******************************************************************************
95 * Constructor.
96 * Parameters:
97 *   Template = true to edit/create an alarm template
98 *            = false to edit/create an alarm.
99 *   event   != to initialise the dialog to show the specified event's data.
100 */
EditDisplayAlarmDlg(bool Template,QWidget * parent,GetResourceType getResource)101 EditDisplayAlarmDlg::EditDisplayAlarmDlg(bool Template, QWidget* parent, GetResourceType getResource)
102     : EditAlarmDlg(Template, KAEvent::MESSAGE, parent, getResource)
103 {
104     qCDebug(KALARM_LOG) << "EditDisplayAlarmDlg: New";
105     init(KAEvent());
106 }
107 
EditDisplayAlarmDlg(bool Template,const KAEvent & event,bool newAlarm,QWidget * parent,GetResourceType getResource,bool readOnly)108 EditDisplayAlarmDlg::EditDisplayAlarmDlg(bool Template, const KAEvent& event, bool newAlarm, QWidget* parent,
109                                          GetResourceType getResource, bool readOnly)
110     : EditAlarmDlg(Template, event, newAlarm, parent, getResource, readOnly)
111 {
112     qCDebug(KALARM_LOG) << "EditDisplayAlarmDlg: Event.id()";
113     init(event);
114 }
115 
116 /******************************************************************************
117 * Return the window caption.
118 */
type_caption() const119 QString EditDisplayAlarmDlg::type_caption() const
120 {
121     return isTemplate() ? (isNewAlarm() ? i18nc("@title:window", "New Display Alarm Template") : i18nc("@title:window", "Edit Display Alarm Template"))
122                         : (isNewAlarm() ? i18nc("@title:window", "New Display Alarm") : i18nc("@title:window", "Edit Display Alarm"));
123 }
124 
125 /******************************************************************************
126 * Set up the dialog controls common to display alarms.
127 */
type_init(QWidget * parent,QVBoxLayout * frameLayout)128 void EditDisplayAlarmDlg::type_init(QWidget* parent, QVBoxLayout* frameLayout)
129 {
130     // Display type combo box
131     QWidget* box = new QWidget(parent);    // to group widgets for QWhatsThis text
132 
133     auto boxHLayout = new QHBoxLayout(box);
134     boxHLayout->setContentsMargins(0, 0, 0, 0);
135     QLabel* label = new QLabel(i18nc("@label:listbox", "Display type:"), box);
136     boxHLayout->addWidget(label);
137     mTypeCombo = new ComboBox(box);
138     boxHLayout->addWidget(mTypeCombo);
139     const QString textItem    = i18nc("@item:inlistbox", "Text message");
140     const QString fileItem    = i18nc("@item:inlistbox", "File contents");
141     const QString commandItem = i18nc("@item:inlistbox", "Command output");
142     mTypeCombo->addItem(textItem);     // index = tTEXT
143     mTypeCombo->addItem(fileItem);     // index = tFILE
144     mTypeCombo->addItem(commandItem);  // index = tCOMMAND
145     mTypeCombo->setCurrentIndex(-1);    // ensure slotAlarmTypeChanged() is called when index is set
146     if (!ShellProcess::authorised())
147     {
148         // User not authorised to issue shell commands - disable Command Output option
149         auto model = qobject_cast<QStandardItemModel*>(mTypeCombo->model());
150         if (model)
151         {
152             QModelIndex index = model->index(2, mTypeCombo->modelColumn(), mTypeCombo->rootModelIndex());
153             QStandardItem* item = model->itemFromIndex(index);
154             if (item)
155                 item->setEnabled(false);
156         }
157     }
158     connect(mTypeCombo, static_cast<void (ComboBox::*)(int)>(&ComboBox::currentIndexChanged), this, &EditDisplayAlarmDlg::slotAlarmTypeChanged);
159     connect(mTypeCombo, static_cast<void (ComboBox::*)(int)>(&ComboBox::currentIndexChanged), this, &EditDisplayAlarmDlg::contentsChanged);
160     label->setBuddy(mTypeCombo);
161     box->setWhatsThis(xi18nc("@info:whatsthis", "<para>Select what the alarm should display:"
162                              "<list><item><interface>%1</interface>: the alarm will display the text message you type in.</item>"
163                              "<item><interface>%2</interface>: the alarm will display the contents of a text or image file.</item>"
164                              "<item><interface>%3</interface>: the alarm will display the output from a command.</item></list></para>",
165                              textItem, fileItem, commandItem));
166     auto hlayout = new QHBoxLayout();
167     hlayout->setContentsMargins(0, 0, 0, 0);
168     frameLayout->addLayout(hlayout);
169     hlayout->addWidget(box);
170     hlayout->addStretch();    // left adjust the control
171 
172     // Text message edit box
173     mTextMessageEdit = new TextEdit(parent);
174     mTextMessageEdit->setLineWrapMode(KTextEdit::NoWrap);
175     mTextMessageEdit->enableEmailDrop();         // allow drag-and-drop of emails onto this widget
176     mTextMessageEdit->setWhatsThis(i18nc("@info:whatsthis", "Enter the text of the alarm message. It may be multi-line."));
177     connect(mTextMessageEdit, &TextEdit::textChanged, this, &EditDisplayAlarmDlg::contentsChanged);
178     frameLayout->addWidget(mTextMessageEdit);
179 
180     // File name edit box
181     mFileBox = new QWidget(parent);
182     frameLayout->addWidget(mFileBox);
183     auto fileBoxHLayout = new QHBoxLayout(mFileBox);
184     fileBoxHLayout->setContentsMargins(0, 0, 0, 0);
185     fileBoxHLayout->setSpacing(0);
186     mFileMessageEdit = new LineEdit(LineEdit::Url, mFileBox);
187     fileBoxHLayout->addWidget(mFileMessageEdit);
188     mFileMessageEdit->setAcceptDrops(true);
189     mFileMessageEdit->setWhatsThis(i18nc("@info:whatsthis", "Enter the name or URL of a text or image file to display."));
190     connect(mFileMessageEdit, &LineEdit::textChanged, this, &EditDisplayAlarmDlg::contentsChanged);
191 
192     // File browse button
193     mFileBrowseButton = new QPushButton(mFileBox);
194     fileBoxHLayout->addWidget(mFileBrowseButton);
195     mFileBrowseButton->setIcon(QIcon::fromTheme(QStringLiteral("document-open")));
196     mFileBrowseButton->setToolTip(i18nc("@info:tooltip", "Choose a file"));
197     mFileBrowseButton->setWhatsThis(i18nc("@info:whatsthis", "Select a text or image file to display."));
198     connect(mFileBrowseButton, &QPushButton::clicked, this, &EditDisplayAlarmDlg::slotPickFile);
199 
200     // Command type checkbox and edit box
201     mCmdEdit = new CommandEdit(parent);
202     connect(mCmdEdit, &CommandEdit::scriptToggled, this, &EditDisplayAlarmDlg::slotCmdScriptToggled);
203     connect(mCmdEdit, &CommandEdit::changed, this, &EditDisplayAlarmDlg::contentsChanged);
204     frameLayout->addWidget(mCmdEdit);
205 
206     // Sound checkbox and file selector
207     hlayout = new QHBoxLayout();
208     hlayout->setContentsMargins(0, 0, 0, 0);
209     frameLayout->addLayout(hlayout);
210     mSoundPicker = new SoundPicker(parent);
211     connect(mSoundPicker, &SoundPicker::changed, this, &EditDisplayAlarmDlg::contentsChanged);
212     hlayout->addWidget(mSoundPicker);
213     hlayout->addSpacing(2 * style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
214     hlayout->addStretch();
215 
216     // Font and colour choice button and sample text
217     mFontColourButton = new FontColourButton(parent);
218     mFontColourButton->setMaximumHeight(mFontColourButton->sizeHint().height() * 3/2);
219     hlayout->addWidget(mFontColourButton);
220     connect(mFontColourButton, &FontColourButton::selected, this, &EditDisplayAlarmDlg::setColours);
221     connect(mFontColourButton, &FontColourButton::selected, this, &EditDisplayAlarmDlg::contentsChanged);
222 
223     // Display method selector
224     hlayout = new QHBoxLayout();
225     hlayout->setContentsMargins(0, 0, 0, 0);
226     frameLayout->addLayout(hlayout);
227     mDisplayMethodBox = new QWidget(parent);    // to group widgets for QWhatsThis text
228     boxHLayout = new QHBoxLayout(mDisplayMethodBox);
229     boxHLayout->setContentsMargins(0, 0, 0, 0);
230     label = new QLabel(i18n_lbl_DisplayMethod(), mDisplayMethodBox);
231     boxHLayout->addWidget(label);
232     mDisplayMethodCombo = new ComboBox(mDisplayMethodBox);
233     boxHLayout->addWidget(mDisplayMethodCombo);
234     const QString windowItem = i18n_combo_Window();
235     const QString notifyItem = i18n_combo_Notify();
236     mDisplayMethodCombo->addItem(windowItem);     // index = dWINDOW
237     mDisplayMethodCombo->addItem(notifyItem);     // index = dNOTIFY
238     connect(mDisplayMethodCombo, static_cast<void (ComboBox::*)(int)>(&ComboBox::currentIndexChanged), this, &EditDisplayAlarmDlg::slotDisplayMethodChanged);
239     connect(mDisplayMethodCombo, static_cast<void (ComboBox::*)(int)>(&ComboBox::currentIndexChanged), this, &EditDisplayAlarmDlg::contentsChanged);
240     label->setBuddy(mDisplayMethodCombo);
241     mDisplayMethodBox->setWhatsThis(i18nc("@info:whatsthis", "Select whether to display the alarm in a window or by the notification system."));
242     hlayout->addWidget(mDisplayMethodBox);
243     hlayout->addSpacing(2 * style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
244     hlayout->addStretch();
245 
246     if (ShellProcess::authorised())    // don't display if shell commands not allowed (e.g. kiosk mode)
247     {
248         // Special actions button
249         mSpecialActionsButton = new SpecialActionsButton(false, parent);
250         connect(mSpecialActionsButton, &SpecialActionsButton::selected, this, &EditDisplayAlarmDlg::contentsChanged);
251         hlayout->addWidget(mSpecialActionsButton);
252     }
253 
254     // Top-adjust the controls
255     mFilePadding = new QWidget(parent);
256     hlayout = new QHBoxLayout(mFilePadding);
257     hlayout->setContentsMargins(0, 0, 0, 0);
258     hlayout->setSpacing(0);
259     frameLayout->addWidget(mFilePadding);
260     frameLayout->setStretchFactor(mFilePadding, 1);
261 }
262 
263 /******************************************************************************
264 * Create a reminder control.
265 */
createReminder(QWidget * parent)266 Reminder* EditDisplayAlarmDlg::createReminder(QWidget* parent)
267 {
268     return new Reminder(i18nc("@info:whatsthis", "Check to additionally display a reminder in advance of or after the main alarm time(s)."),
269                         xi18nc("@info:whatsthis", "<para>Enter how long in advance of or after the main alarm to display a reminder alarm.</para><para>%1</para>", TimeSpinBox::shiftWhatsThis()),
270                         i18nc("@info:whatsthis", "Select whether the reminder should be triggered before or after the main alarm"),
271                         true, true, parent);
272 }
273 
274 /******************************************************************************
275 * Create an "acknowledgement confirmation required" checkbox.
276 */
createConfirmAckCheckbox(QWidget * parent)277 CheckBox* EditDisplayAlarmDlg::createConfirmAckCheckbox(QWidget* parent)
278 {
279     CheckBox* confirmAck = new CheckBox(i18n_chk_ConfirmAck(), parent);
280     confirmAck->setWhatsThis(i18nc("@info:whatsthis", "Check to be prompted for confirmation when you acknowledge the alarm."));
281     return confirmAck;
282 }
283 
284 /******************************************************************************
285 * Initialise the dialog controls from the specified event.
286 */
type_initValues(const KAEvent & event)287 void EditDisplayAlarmDlg::type_initValues(const KAEvent& event)
288 {
289     mTryButton->setToolTip(i18nc("@info:tooltip", "Display the alarm now"));
290     mAkonadiItemId = -1;
291     lateCancel()->showAutoClose(true);
292     if (event.isValid())
293     {
294         if (mAlarmType == KAEvent::MESSAGE  &&  event.akonadiItemId()
295         &&  AlarmText::checkIfEmail(event.cleanText()))
296             mAkonadiItemId = event.akonadiItemId();
297         lateCancel()->setAutoClose(event.autoClose());
298         if (event.useDefaultFont())
299             mFontColourButton->setDefaultFont();
300         else
301             mFontColourButton->setFont(event.font());
302         mFontColourButton->setBgColour(event.bgColour());
303         mFontColourButton->setFgColour(event.fgColour());
304         setColours(event.fgColour(), event.bgColour());
305         mDisplayMethodCombo->setCurrentIndex(event.notify() ? dNOTIFY : dWINDOW);
306         mConfirmAck->setChecked(event.confirmAck());
307         const bool recurs = event.recurs();
308         int reminderMins = event.reminderMinutes();
309         if (reminderMins > 0  &&  !event.reminderActive())
310             reminderMins = 0;   // don't show advance reminder which has already passed
311         if (!reminderMins)
312         {
313             if (event.reminderDeferral()  &&  !recurs)
314             {
315                 reminderMins = event.deferDateTime().minsTo(event.mainDateTime());
316                 mReminderDeferral = true;
317             }
318             else if (event.reminderMinutes()  &&  recurs)
319             {
320                 reminderMins = event.reminderMinutes();
321                 mReminderArchived = true;
322             }
323         }
324         reminder()->setMinutes(reminderMins, dateOnly());
325         reminder()->setOnceOnly(event.reminderOnceOnly());
326         reminder()->enableOnceOnly(recurs);
327         if (mSpecialActionsButton)
328             mSpecialActionsButton->setActions(event.preAction(), event.postAction(), event.extraActionOptions());
329         Preferences::SoundType soundType = event.speak()                ? Preferences::Sound_Speak
330                                          : event.beep()                 ? Preferences::Sound_Beep
331                                          : !event.audioFile().isEmpty() ? Preferences::Sound_File
332                                          :                                Preferences::Sound_None;
333         mSoundPicker->set(soundType, event.audioFile(), event.soundVolume(),
334                           event.fadeVolume(), event.fadeSeconds(), event.repeatSoundPause());
335     }
336     else
337     {
338         // Set the values to their defaults
339         if (!ShellProcess::authorised())
340         {
341             // Don't allow shell commands in kiosk mode
342             if (mSpecialActionsButton)
343                 mSpecialActionsButton->setEnabled(false);
344         }
345         lateCancel()->setAutoClose(Preferences::defaultAutoClose());
346         mTypeCombo->setCurrentIndex(0);
347         mFontColourButton->setDefaultFont();
348         mFontColourButton->setBgColour(Preferences::defaultBgColour());
349         mFontColourButton->setFgColour(Preferences::defaultFgColour());
350         setColours(Preferences::defaultFgColour(), Preferences::defaultBgColour());
351         mDisplayMethodCombo->setCurrentIndex(Preferences::defaultDisplayMethod() == Preferences::Display_Window ? dWINDOW : dNOTIFY);
352         mConfirmAck->setChecked(Preferences::defaultConfirmAck());
353         reminder()->setMinutes(0, false);
354         reminder()->enableOnceOnly(isTimedRecurrence());   // must be called after mRecurrenceEdit is set up
355         if (mSpecialActionsButton)
356         {
357             KAEvent::ExtraActionOptions opts({});
358             if (Preferences::defaultExecPreActionOnDeferral())
359                 opts |= KAEvent::ExecPreActOnDeferral;
360             if (Preferences::defaultCancelOnPreActionError())
361                 opts |= KAEvent::CancelOnPreActError;
362             if (Preferences::defaultDontShowPreActionError())
363                 opts |= KAEvent::DontShowPreActError;
364             mSpecialActionsButton->setActions(Preferences::defaultPreAction(), Preferences::defaultPostAction(), opts);
365         }
366         mSoundPicker->set(Preferences::defaultSoundType(), Preferences::defaultSoundFile(),
367                           Preferences::defaultSoundVolume(), -1, 0, (Preferences::defaultSoundRepeat() ? 0 : -1));
368     }
369     slotDisplayMethodChanged(mDisplayMethodCombo->currentIndex());
370 }
371 
372 /******************************************************************************
373 * Called when the More/Less Options button is clicked.
374 * Show/hide the optional options.
375 */
type_showOptions(bool more)376 void EditDisplayAlarmDlg::type_showOptions(bool more)
377 {
378     if (mSpecialActionsButton)
379     {
380         if (more)
381         {
382             mDisplayMethodBox->show();
383             mSpecialActionsButton->show();
384         }
385         else
386         {
387             mDisplayMethodBox->hide();
388             mSpecialActionsButton->hide();
389         }
390     }
391 }
392 
393 /******************************************************************************
394 * Called when the font/color button has been clicked.
395 * Set the colors in the message text entry control.
396 */
setColours(const QColor & fgColour,const QColor & bgColour)397 void EditDisplayAlarmDlg::setColours(const QColor& fgColour, const QColor& bgColour)
398 {
399     QColor fg(fgColour);
400     if (mDisplayMethodCombo->currentIndex() == dNOTIFY)
401     {
402         const QPalette pal = mFileMessageEdit->palette();
403         mTextMessageEdit->setPalette(pal);
404         mTextMessageEdit->viewport()->setPalette(pal);
405         fg = pal.color(QPalette::Text);
406     }
407     else
408     {
409         QPalette pal = mTextMessageEdit->palette();
410         pal.setColor(mTextMessageEdit->backgroundRole(), bgColour);
411         pal.setColor(QPalette::Text, fgColour);
412         mTextMessageEdit->setPalette(pal);
413         pal = mTextMessageEdit->viewport()->palette();
414         pal.setColor(mTextMessageEdit->viewport()->backgroundRole(), bgColour);
415         pal.setColor(QPalette::Text, fgColour);
416         mTextMessageEdit->viewport()->setPalette(pal);
417     }
418     // Change the color of existing text
419     const QTextCursor cursor = mTextMessageEdit->textCursor();
420     mTextMessageEdit->selectAll();
421     mTextMessageEdit->setTextColor(fg);
422     mTextMessageEdit->setTextCursor(cursor);
423 }
424 
425 /******************************************************************************
426 * Set the dialog's action and the action's text.
427 */
setAction(KAEvent::SubAction action,const AlarmText & alarmText)428 void EditDisplayAlarmDlg::setAction(KAEvent::SubAction action, const AlarmText& alarmText)
429 {
430     const QString text = alarmText.displayText();
431     switch (action)
432     {
433         case KAEvent::MESSAGE:
434             mTypeCombo->setCurrentIndex(tTEXT);
435             mTextMessageEdit->setPlainText(text);
436             mAkonadiItemId = alarmText.isEmail() ? alarmText.akonadiItemId() : -1;
437             break;
438         case KAEvent::FILE:
439             mTypeCombo->setCurrentIndex(tFILE);
440             mFileMessageEdit->setText(text);
441             break;
442         case KAEvent::COMMAND:
443             mTypeCombo->setCurrentIndex(tCOMMAND);
444             mCmdEdit->setText(alarmText);
445             break;
446         default:
447             Q_ASSERT(0);
448             break;
449     }
450 }
451 
452 /******************************************************************************
453 * Initialise various values in the New Alarm dialogue.
454 */
setBgColour(const QColor & colour)455 void EditDisplayAlarmDlg::setBgColour(const QColor& colour)
456 {
457     mFontColourButton->setBgColour(colour);
458     setColours(mFontColourButton->fgColour(), colour);
459 }
setFgColour(const QColor & colour)460 void EditDisplayAlarmDlg::setFgColour(const QColor& colour)
461 {
462     mFontColourButton->setFgColour(colour);
463     setColours(colour, mFontColourButton->bgColour());
464 }
setNotify(bool notify)465 void EditDisplayAlarmDlg::setNotify(bool notify)
466 {
467     mDisplayMethodCombo->setCurrentIndex(notify ? dNOTIFY : dWINDOW);
468 }
setConfirmAck(bool confirm)469 void EditDisplayAlarmDlg::setConfirmAck(bool confirm)
470 {
471     mConfirmAck->setChecked(confirm);
472 }
setAutoClose(bool close)473 void EditDisplayAlarmDlg::setAutoClose(bool close)
474 {
475     lateCancel()->setAutoClose(close);
476 }
setAudio(Preferences::SoundType type,const QString & file,float volume,int repeatPause)477 void EditDisplayAlarmDlg::setAudio(Preferences::SoundType type, const QString& file, float volume, int repeatPause)
478 {
479     mSoundPicker->set(type, file, volume, -1, 0, repeatPause);
480 }
setReminder(int minutes,bool onceOnly)481 void EditDisplayAlarmDlg::setReminder(int minutes, bool onceOnly)
482 {
483     reminder()->setMinutes(minutes, dateOnly());
484     reminder()->setOnceOnly(onceOnly);
485     reminder()->enableOnceOnly(isTimedRecurrence());
486 }
487 
488 /******************************************************************************
489 * Set the read-only status of all non-template controls.
490 */
setReadOnly(bool readOnly)491 void EditDisplayAlarmDlg::setReadOnly(bool readOnly)
492 {
493     mTypeCombo->setReadOnly(readOnly);
494     mTextMessageEdit->setReadOnly(readOnly);
495     mFileMessageEdit->setReadOnly(readOnly);
496     mCmdEdit->setReadOnly(readOnly);
497     mFontColourButton->setReadOnly(readOnly);
498     mSoundPicker->setReadOnly(readOnly);
499     mDisplayMethodCombo->setReadOnly(readOnly);
500     mConfirmAck->setReadOnly(readOnly);
501     reminder()->setReadOnly(readOnly);
502     if (mSpecialActionsButton)
503         mSpecialActionsButton->setReadOnly(readOnly);
504     if (readOnly)
505         mFileBrowseButton->hide();
506     else
507         mFileBrowseButton->show();
508     EditAlarmDlg::setReadOnly(readOnly);
509 }
510 
511 /******************************************************************************
512 * Save the state of all controls.
513 */
saveState(const KAEvent * event)514 void EditDisplayAlarmDlg::saveState(const KAEvent* event)
515 {
516     EditAlarmDlg::saveState(event);
517     mSavedType          = mTypeCombo->currentIndex();
518     mSavedCmdScript     = mCmdEdit->isScript();
519     mSavedSoundType     = mSoundPicker->sound();
520     mSavedSoundFile     = mSoundPicker->file();
521     mSavedSoundVolume   = mSoundPicker->volume(mSavedSoundFadeVolume, mSavedSoundFadeSeconds);
522     mSavedRepeatPause   = mSoundPicker->repeatPause();
523     mSavedDisplayMethod = mDisplayMethodCombo->currentIndex();
524     mSavedConfirmAck    = mConfirmAck->isChecked();
525     mSavedFont          = mFontColourButton->font();
526     mSavedFgColour      = mFontColourButton->fgColour();
527     mSavedBgColour      = mFontColourButton->bgColour();
528     mSavedReminder      = reminder()->minutes();
529     mSavedOnceOnly      = reminder()->isOnceOnly();
530     mSavedAutoClose     = lateCancel()->isAutoClose();
531     if (mSpecialActionsButton)
532     {
533         mSavedPreAction        = mSpecialActionsButton->preAction();
534         mSavedPostAction       = mSpecialActionsButton->postAction();
535         mSavedPreActionOptions = mSpecialActionsButton->options();
536     }
537 }
538 
539 /******************************************************************************
540 * Check whether any of the controls has changed state since the dialog was
541 * first displayed.
542 * Reply = true if any controls have changed, or if it's a new event.
543 *       = false if no controls have changed.
544 */
type_stateChanged() const545 bool EditDisplayAlarmDlg::type_stateChanged() const
546 {
547     if (mSavedType          != mTypeCombo->currentIndex()
548     ||  mSavedCmdScript     != mCmdEdit->isScript()
549     ||  mSavedSoundType     != mSoundPicker->sound()
550     ||  mSavedDisplayMethod != mDisplayMethodCombo->currentIndex()
551     ||  mSavedReminder      != reminder()->minutes()
552     ||  mSavedOnceOnly      != reminder()->isOnceOnly())
553         return true;
554     if (mDisplayMethodCombo->currentIndex() == dWINDOW)
555     {
556         if (mSavedConfirmAck != mConfirmAck->isChecked()
557         ||  mSavedFont       != mFontColourButton->font()
558         ||  mSavedFgColour   != mFontColourButton->fgColour()
559         ||  mSavedBgColour   != mFontColourButton->bgColour()
560         ||  mSavedAutoClose  != lateCancel()->isAutoClose())
561             return true;
562     }
563     if (mSpecialActionsButton)
564     {
565         if (mSavedPreAction        != mSpecialActionsButton->preAction()
566         ||  mSavedPostAction       != mSpecialActionsButton->postAction()
567         ||  mSavedPreActionOptions != mSpecialActionsButton->options())
568             return true;
569     }
570     if (mSavedSoundType == Preferences::Sound_File)
571     {
572         if (mSavedSoundFile != mSoundPicker->file())
573             return true;
574         if (!mSavedSoundFile.isEmpty())
575         {
576             float fadeVolume;
577             int   fadeSecs;
578             if (mSavedRepeatPause != mSoundPicker->repeatPause()
579             ||  mSavedSoundVolume != mSoundPicker->volume(fadeVolume, fadeSecs)
580             ||  mSavedSoundFadeVolume != fadeVolume
581             ||  mSavedSoundFadeSeconds != fadeSecs)
582                 return true;
583         }
584     }
585     return false;
586 }
587 
588 /******************************************************************************
589 * Extract the data in the dialog specific to the alarm type and set up a
590 * KAEvent from it.
591 */
type_setEvent(KAEvent & event,const KADateTime & dt,const QString & name,const QString & text,int lateCancel,bool trial)592 void EditDisplayAlarmDlg::type_setEvent(KAEvent& event, const KADateTime& dt, const QString& name, const QString& text, int lateCancel, bool trial)
593 {
594     KAEvent::SubAction type;
595     switch (mTypeCombo->currentIndex())
596     {
597         case tFILE:     type = KAEvent::FILE; break;
598         case tCOMMAND:  type = KAEvent::COMMAND; break;
599         default:
600         case tTEXT:     type = KAEvent::MESSAGE; break;
601     }
602     QColor fgColour, bgColour;
603     QFont font;
604     if (mDisplayMethodCombo->currentIndex() == dNOTIFY)
605     {
606         bgColour = Preferences::defaultBgColour();
607         fgColour = Preferences::defaultFgColour();
608     }
609     else
610     {
611         bgColour = mFontColourButton->bgColour();
612         fgColour = mFontColourButton->fgColour();
613         font     = mFontColourButton->font();
614     }
615     event = KAEvent(dt, name, text, bgColour, fgColour, font, type, lateCancel, getAlarmFlags());
616     if (type == KAEvent::MESSAGE)
617     {
618         if (AlarmText::checkIfEmail(text))
619             event.setAkonadiItemId(mAkonadiItemId);
620     }
621     float fadeVolume;
622     int   fadeSecs;
623     const float volume = mSoundPicker->volume(fadeVolume, fadeSecs);
624     const int   repeatPause = mSoundPicker->repeatPause();
625     event.setAudioFile(mSoundPicker->file().toDisplayString(), volume, fadeVolume, fadeSecs, repeatPause);
626     if (!trial  &&  reminder()->isEnabled())
627         event.setReminder(reminder()->minutes(), reminder()->isOnceOnly());
628     if (mSpecialActionsButton  &&  mSpecialActionsButton->isEnabled())
629         event.setActions(mSpecialActionsButton->preAction(), mSpecialActionsButton->postAction(),
630                          mSpecialActionsButton->options());
631 }
632 
633 /******************************************************************************
634 * Get the currently specified alarm flag bits.
635 */
getAlarmFlags() const636 KAEvent::Flags EditDisplayAlarmDlg::getAlarmFlags() const
637 {
638     const bool cmd = (mTypeCombo->currentIndex() == tCOMMAND);
639     KAEvent::Flags flags = EditAlarmDlg::getAlarmFlags();
640     if (mSoundPicker->sound() == Preferences::Sound_Beep)  flags |= KAEvent::BEEP;
641     if (mSoundPicker->sound() == Preferences::Sound_Speak) flags |= KAEvent::SPEAK;
642     if (mSoundPicker->repeatPause() >= 0)                  flags |= KAEvent::REPEAT_SOUND;
643     if (cmd)                                               flags |= KAEvent::DISPLAY_COMMAND;
644     if (cmd && mCmdEdit->isScript())                       flags |= KAEvent::SCRIPT;
645     if (mDisplayMethodCombo->currentIndex() == dNOTIFY)
646     {
647                                                            flags |= KAEvent::NOTIFY;
648                                                            flags |= KAEvent::DEFAULT_FONT;
649     }
650     else
651     {
652         if (mFontColourButton->defaultFont())              flags |= KAEvent::DEFAULT_FONT;
653         if (mConfirmAck->isChecked())                      flags |= KAEvent::CONFIRM_ACK;
654         if (lateCancel()->isAutoClose())                   flags |= KAEvent::AUTO_CLOSE;
655     }
656     return flags;
657 }
658 
659 /******************************************************************************
660 * Called when the alarm display type combo box is changed, to display the
661 * appropriate set of controls for that action type.
662 */
slotAlarmTypeChanged(int index)663 void EditDisplayAlarmDlg::slotAlarmTypeChanged(int index)
664 {
665     QWidget* focus = nullptr;
666     switch (index)
667     {
668         case tTEXT:    // text message
669             mFileBox->hide();
670             mFilePadding->hide();
671             mCmdEdit->hide();
672             mTextMessageEdit->show();
673             mSoundPicker->showSpeak(true);
674             mTryButton->setWhatsThis(i18nc("@info:whatsthis", "Display the alarm message now"));
675             focus = mTextMessageEdit;
676             break;
677         case tFILE:    // file contents
678             mTextMessageEdit->hide();
679             mFileBox->show();
680             mFilePadding->show();
681             mCmdEdit->hide();
682             mSoundPicker->showSpeak(false);
683             mTryButton->setWhatsThis(i18nc("@info:whatsthis", "Display the file now"));
684             mFileMessageEdit->setNoSelect();
685             focus = mFileMessageEdit;
686             break;
687         case tCOMMAND:    // command output
688             mTextMessageEdit->hide();
689             mFileBox->hide();
690             slotCmdScriptToggled(mCmdEdit->isScript());  // show/hide mFilePadding
691             mCmdEdit->show();
692             mSoundPicker->showSpeak(true);
693             mTryButton->setWhatsThis(i18nc("@info:whatsthis", "Display the command output now"));
694             focus = mCmdEdit;
695             break;
696     }
697     if (focus)
698         focus->setFocus();
699 }
700 
701 /******************************************************************************
702 * Called when the display method combo box is changed, to enable/disable the
703 * appropriate set of controls for that display method.
704 */
slotDisplayMethodChanged(int index)705 void EditDisplayAlarmDlg::slotDisplayMethodChanged(int index)
706 {
707     const bool enable = (index == dWINDOW);
708     mConfirmAck->setVisible(enable);
709     mFontColourButton->setVisible(enable);
710     mSoundPicker->showFile(enable);
711     // Because notifications automatically time out after 10 seconds,
712     // auto-close would always occur after a notification closes.
713     lateCancel()->showAutoClose(enable);
714     // Set the text message edit box colours according to the display metho according to the display methodd.
715     setColours(mFontColourButton->fgColour(), mFontColourButton->bgColour());
716 }
717 
718 /******************************************************************************
719 * Called when the file browse button is pressed to select a file to display.
720 */
slotPickFile()721 void EditDisplayAlarmDlg::slotPickFile()
722 {
723     static QString defaultDir;   // default directory for file browse button
724     QString file;
725     if (File::browseFile(file, i18nc("@title:window", "Choose Text or Image File to Display"),
726                          defaultDir, mFileMessageEdit->text(), true, this))
727     {
728         if (!file.isEmpty())
729         {
730             mFileMessageEdit->setText(File::pathOrUrl(file));
731             contentsChanged();
732         }
733     }
734 }
735 
736 /******************************************************************************
737 * Called when one of the command type radio buttons is clicked,
738 * to display the appropriate edit field.
739 */
slotCmdScriptToggled(bool on)740 void EditDisplayAlarmDlg::slotCmdScriptToggled(bool on)
741 {
742     if (on)
743         mFilePadding->hide();
744     else
745         mFilePadding->show();
746 }
747 
748 /******************************************************************************
749 * Clean up the alarm text, and if it's a file, check whether it's valid.
750 */
checkText(QString & result,bool showErrorMessage) const751 bool EditDisplayAlarmDlg::checkText(QString& result, bool showErrorMessage) const
752 {
753     switch (mTypeCombo->currentIndex())
754     {
755         case tTEXT:
756             result = mTextMessageEdit->toPlainText();
757             break;
758 
759         case tFILE:
760         {
761             QString fileName = mFileMessageEdit->text().trimmed();
762             QUrl url;
763             File::FileErr err = File::checkFileExists(fileName, url, MainWindow::mainMainWindow());
764             if (err == File::FileErr::None)
765             {
766                 KFileItem fi(url);
767                 switch (File::fileType(fi.currentMimeType()))
768                 {
769                     case File::TextFormatted:
770                     case File::TextPlain:
771                     case File::TextApplication:
772                     case File::Image:
773                         break;
774                     default:
775                         err = File::FileErr::NotTextImage;
776                         break;
777                 }
778             }
779             if (err != File::FileErr::None  &&  showErrorMessage)
780             {
781                 mFileMessageEdit->setFocus();
782                 if (!File::showFileErrMessage(fileName, err, File::FileErr::BlankDisplay, const_cast<EditDisplayAlarmDlg*>(this)))
783                     return false;
784             }
785             result = fileName;
786             break;
787         }
788         case tCOMMAND:
789             result = mCmdEdit->text(const_cast<EditDisplayAlarmDlg*>(this), showErrorMessage);
790             if (result.isEmpty())
791                 return false;
792             break;
793     }
794     return true;
795 }
796 
797 
798 /*=============================================================================
799 = Class EditCommandAlarmDlg
800 = Dialog to edit command alarms.
801 =============================================================================*/
802 
i18n_chk_EnterScript()803 QString EditCommandAlarmDlg::i18n_chk_EnterScript()        { return i18nc("@option:check", "Enter a script"); }
i18n_radio_ExecInTermWindow()804 QString EditCommandAlarmDlg::i18n_radio_ExecInTermWindow() { return i18nc("@option:radio", "Execute in terminal window"); }
i18n_chk_ExecInTermWindow()805 QString EditCommandAlarmDlg::i18n_chk_ExecInTermWindow()   { return i18nc("@option:check", "Execute in terminal window"); }
806 
807 
808 /******************************************************************************
809 * Constructor.
810 * Parameters:
811 *   Template = true to edit/create an alarm template
812 *            = false to edit/create an alarm.
813 *   event   != to initialise the dialog to show the specified event's data.
814 */
EditCommandAlarmDlg(bool Template,QWidget * parent,GetResourceType getResource)815 EditCommandAlarmDlg::EditCommandAlarmDlg(bool Template, QWidget* parent, GetResourceType getResource)
816     : EditAlarmDlg(Template, KAEvent::COMMAND, parent, getResource)
817 {
818     qCDebug(KALARM_LOG) << "EditCommandAlarmDlg: New";
819     init(KAEvent());
820 }
821 
EditCommandAlarmDlg(bool Template,const KAEvent & event,bool newAlarm,QWidget * parent,GetResourceType getResource,bool readOnly)822 EditCommandAlarmDlg::EditCommandAlarmDlg(bool Template, const KAEvent& event, bool newAlarm, QWidget* parent,
823                                          GetResourceType getResource, bool readOnly)
824     : EditAlarmDlg(Template, event, newAlarm, parent, getResource, readOnly)
825 {
826     qCDebug(KALARM_LOG) << "EditCommandAlarmDlg: Event.id()";
827     init(event);
828 }
829 
830 /******************************************************************************
831 * Return the window caption.
832 */
type_caption() const833 QString EditCommandAlarmDlg::type_caption() const
834 {
835     return isTemplate() ? (isNewAlarm() ? i18nc("@title:window", "New Command Alarm Template") : i18nc("@title:window", "Edit Command Alarm Template"))
836                         : (isNewAlarm() ? i18nc("@title:window", "New Command Alarm") : i18nc("@title:window", "Edit Command Alarm"));
837 }
838 
839 /******************************************************************************
840 * Set up the command alarm dialog controls.
841 */
type_init(QWidget * parent,QVBoxLayout * frameLayout)842 void EditCommandAlarmDlg::type_init(QWidget* parent, QVBoxLayout* frameLayout)
843 {
844     mTryButton->setWhatsThis(i18nc("@info:whatsthis", "Execute the specified command now"));
845     mTryButton->setToolTip(i18nc("@info:tooltip", "Execute the specified command now"));
846 
847     mCmdEdit = new CommandEdit(parent);
848     connect(mCmdEdit, &CommandEdit::scriptToggled, this, &EditCommandAlarmDlg::slotCmdScriptToggled);
849     connect(mCmdEdit, &CommandEdit::changed, this, &EditCommandAlarmDlg::contentsChanged);
850     frameLayout->addWidget(mCmdEdit);
851 
852     mCmdDontShowError = new CheckBox(i18nc("@option:check", "Do not notify errors"), parent);
853     mCmdDontShowError->setWhatsThis(i18nc("@info:whatsthis", "Do not show error message if the command fails."));
854     frameLayout->addWidget(mCmdDontShowError, 0, Qt::AlignLeft);
855     connect(mCmdDontShowError, &CheckBox::toggled, this, &EditCommandAlarmDlg::contentsChanged);
856 
857     // What to do with command output
858 
859     mCmdOutputBox = new QGroupBox(i18nc("@title:group", "Command Output"), parent);
860     frameLayout->addWidget(mCmdOutputBox);
861     auto vlayout = new QVBoxLayout(mCmdOutputBox);
862     mCmdOutputGroup = new ButtonGroup(mCmdOutputBox);
863     connect(mCmdOutputGroup, &ButtonGroup::buttonSet, this, &EditCommandAlarmDlg::contentsChanged);
864 
865     // Execute in terminal window
866     mCmdExecInTerm = new RadioButton(i18n_radio_ExecInTermWindow(), mCmdOutputBox);
867     mCmdExecInTerm->setWhatsThis(i18nc("@info:whatsthis", "Check to execute the command in a terminal window"));
868     mCmdOutputGroup->addButton(mCmdExecInTerm, Preferences::Log_Terminal);
869     vlayout->addWidget(mCmdExecInTerm, 0, Qt::AlignLeft);
870 
871     // Log file name edit box
872     QWidget* box = new QWidget(mCmdOutputBox);
873     auto boxHLayout = new QHBoxLayout(box);
874     boxHLayout->setContentsMargins(0, 0, 0, 0);
875     boxHLayout->setSpacing(0);
876     (new QWidget(box))->setFixedWidth(mCmdExecInTerm->style()->pixelMetric(QStyle::PM_ExclusiveIndicatorWidth));   // indent the edit box
877     mCmdLogFileEdit = new LineEdit(LineEdit::Url, box);
878     boxHLayout->addWidget(mCmdLogFileEdit);
879     mCmdLogFileEdit->setAcceptDrops(true);
880     mCmdLogFileEdit->setWhatsThis(i18nc("@info:whatsthis", "Enter the name or path of the log file."));
881     connect(mCmdLogFileEdit, &LineEdit::textChanged, this, &EditCommandAlarmDlg::contentsChanged);
882 
883     // Log file browse button.
884     // The file browser dialog is activated by the PickLogFileRadio class.
885     auto browseButton = new QPushButton(box);
886     boxHLayout->addWidget(browseButton);
887     browseButton->setIcon(QIcon::fromTheme(QStringLiteral("document-open")));
888     browseButton->setToolTip(i18nc("@info:tooltip", "Choose a file"));
889     browseButton->setWhatsThis(i18nc("@info:whatsthis", "Select a log file."));
890 
891     // Log output to file
892     mCmdLogToFile = new PickLogFileRadio(browseButton, mCmdLogFileEdit, i18nc("@option:radio", "Log to file"), mCmdOutputGroup, mCmdOutputBox);
893     mCmdLogToFile->setWhatsThis(i18nc("@info:whatsthis", "Check to log the command output to a local file. The output will be appended to any existing contents of the file."));
894     connect(mCmdLogToFile, &PickLogFileRadio::fileChanged, this, &EditCommandAlarmDlg::contentsChanged);
895     mCmdOutputGroup->addButton(mCmdLogToFile, Preferences::Log_File);
896     vlayout->addWidget(mCmdLogToFile, 0, Qt::AlignLeft);
897     vlayout->addWidget(box);
898 
899     // Discard output
900     mCmdDiscardOutput = new RadioButton(i18nc("@option:radio", "Discard"), mCmdOutputBox);
901     mCmdDiscardOutput->setWhatsThis(i18nc("@info:whatsthis", "Check to discard command output."));
902     mCmdOutputGroup->addButton(mCmdDiscardOutput, Preferences::Log_Discard);
903     vlayout->addWidget(mCmdDiscardOutput, 0, Qt::AlignLeft);
904 
905     // Top-adjust the controls
906     mCmdPadding = new QWidget(parent);
907     auto hlayout = new QHBoxLayout(mCmdPadding);
908     hlayout->setContentsMargins(0, 0, 0, 0);
909     hlayout->setSpacing(0);
910     frameLayout->addWidget(mCmdPadding);
911     frameLayout->setStretchFactor(mCmdPadding, 1);
912 }
913 
914 /******************************************************************************
915 * Initialise the dialog controls from the specified event.
916 */
type_initValues(const KAEvent & event)917 void EditCommandAlarmDlg::type_initValues(const KAEvent& event)
918 {
919     if (event.isValid())
920     {
921         // Set the values to those for the specified event
922         RadioButton* logType = event.commandXterm()       ? mCmdExecInTerm
923                              : !event.logFile().isEmpty() ? mCmdLogToFile
924                              :                               mCmdDiscardOutput;
925         if (logType == mCmdLogToFile)
926             mCmdLogFileEdit->setText(event.logFile());    // set file name before setting radio button
927         logType->setChecked(true);
928         mCmdDontShowError->setChecked(event.commandHideError());
929     }
930     else
931     {
932         // Set the values to their defaults
933         mCmdEdit->setScript(Preferences::defaultCmdScript());
934         mCmdLogFileEdit->setText(Preferences::defaultCmdLogFile());    // set file name before setting radio button
935         mCmdOutputGroup->setButton(Preferences::defaultCmdLogType());
936         mCmdDontShowError->setChecked(false);
937     }
938     slotCmdScriptToggled(mCmdEdit->isScript());
939 }
940 
941 /******************************************************************************
942 * Called when the More/Less Options button is clicked.
943 * Show/hide the optional options.
944 */
type_showOptions(bool more)945 void EditCommandAlarmDlg::type_showOptions(bool more)
946 {
947     if (more)
948         mCmdOutputBox->show();
949     else
950         mCmdOutputBox->hide();
951 }
952 
953 /******************************************************************************
954 * Set the dialog's action and the action's text.
955 */
setAction(KAEvent::SubAction action,const AlarmText & alarmText)956 void EditCommandAlarmDlg::setAction(KAEvent::SubAction action, const AlarmText& alarmText)
957 {
958     Q_UNUSED(action);
959     Q_ASSERT(action == KAEvent::COMMAND);
960     mCmdEdit->setText(alarmText);
961 }
962 
963 /******************************************************************************
964 * Set the read-only status of all non-template controls.
965 */
setReadOnly(bool readOnly)966 void EditCommandAlarmDlg::setReadOnly(bool readOnly)
967 {
968     if (!isTemplate()  &&  !ShellProcess::authorised())
969         readOnly = true;     // don't allow editing of existing command alarms in kiosk mode
970     mCmdEdit->setReadOnly(readOnly);
971     mCmdDontShowError->setReadOnly(readOnly);
972     mCmdExecInTerm->setReadOnly(readOnly);
973     mCmdLogToFile->setReadOnly(readOnly);
974     mCmdDiscardOutput->setReadOnly(readOnly);
975     EditAlarmDlg::setReadOnly(readOnly);
976 }
977 
978 /******************************************************************************
979 * Save the state of all controls.
980 */
saveState(const KAEvent * event)981 void EditCommandAlarmDlg::saveState(const KAEvent* event)
982 {
983     EditAlarmDlg::saveState(event);
984     mSavedCmdScript        = mCmdEdit->isScript();
985     mSavedCmdDontShowError = mCmdDontShowError->isChecked();
986     mSavedCmdOutputRadio   = mCmdOutputGroup->checkedButton();
987     mSavedCmdLogFile       = mCmdLogFileEdit->text();
988 }
989 
990 /******************************************************************************
991 * Check whether any of the controls has changed state since the dialog was
992 * first displayed.
993 * Reply = true if any controls have changed, or if it's a new event.
994 *       = false if no controls have changed.
995 */
type_stateChanged() const996 bool EditCommandAlarmDlg::type_stateChanged() const
997 {
998     if (mSavedCmdScript        != mCmdEdit->isScript()
999     ||  mSavedCmdOutputRadio   != mCmdOutputGroup->checkedButton()
1000     ||  mSavedCmdDontShowError != mCmdDontShowError->isChecked())
1001         return true;
1002     if (mCmdOutputGroup->checkedButton() == mCmdLogToFile)
1003     {
1004         if (mSavedCmdLogFile != mCmdLogFileEdit->text())
1005             return true;
1006     }
1007     return false;
1008 }
1009 
1010 /******************************************************************************
1011 * Extract the data in the dialog specific to the alarm type and set up a
1012 * KAEvent from it.
1013 */
type_setEvent(KAEvent & event,const KADateTime & dt,const QString & name,const QString & text,int lateCancel,bool trial)1014 void EditCommandAlarmDlg::type_setEvent(KAEvent& event, const KADateTime& dt, const QString& name, const QString& text, int lateCancel, bool trial)
1015 {
1016     Q_UNUSED(trial);
1017     event = KAEvent(dt, name, text, QColor(), QColor(), QFont(), KAEvent::COMMAND, lateCancel, getAlarmFlags());
1018     if (mCmdOutputGroup->checkedButton() == mCmdLogToFile)
1019         event.setLogFile(mCmdLogFileEdit->text());
1020 }
1021 
1022 /******************************************************************************
1023 * Get the currently specified alarm flag bits.
1024 */
getAlarmFlags() const1025 KAEvent::Flags EditCommandAlarmDlg::getAlarmFlags() const
1026 {
1027     KAEvent::Flags flags = EditAlarmDlg::getAlarmFlags();
1028     if (mCmdEdit->isScript())                               flags |= KAEvent::SCRIPT;
1029     if (mCmdOutputGroup->checkedButton() == mCmdExecInTerm) flags |= KAEvent::EXEC_IN_XTERM;
1030     if (mCmdDontShowError->isChecked())                     flags |= KAEvent::DONT_SHOW_ERROR;
1031     return flags;
1032 }
1033 
1034 /******************************************************************************
1035 * Validate and convert command alarm data.
1036 */
type_validate(bool trial)1037 bool EditCommandAlarmDlg::type_validate(bool trial)
1038 {
1039     Q_UNUSED(trial);
1040     if (mCmdOutputGroup->checkedButton() == mCmdLogToFile)
1041     {
1042         // Validate the log file name
1043         const QString file = mCmdLogFileEdit->text();
1044         const QFileInfo info(file);
1045         QDir::setCurrent(QDir::homePath());
1046         bool err = file.isEmpty()  ||  info.isDir();
1047         if (!err)
1048         {
1049             if (info.exists())
1050             {
1051                 err = !info.isWritable();
1052             }
1053             else
1054             {
1055                 const QFileInfo dirinfo(info.absolutePath());    // get absolute directory path
1056                 err = (!dirinfo.isDir()  ||  !dirinfo.isWritable());
1057             }
1058         }
1059         if (err)
1060         {
1061             showMainPage();
1062             mCmdLogFileEdit->setFocus();
1063             KAMessageBox::sorry(this, i18nc("@info", "Log file must be the name or path of a local file, with write permission."));
1064             return false;
1065         }
1066         // Convert the log file to an absolute path
1067         mCmdLogFileEdit->setText(info.absoluteFilePath());
1068     }
1069     else if (mCmdOutputGroup->checkedButton() == mCmdExecInTerm)
1070     {
1071         if (Preferences::cmdXTermCommand().isEmpty())
1072         {
1073             if (KAMessageBox::warningContinueCancel(this, xi18nc("@info", "<para>No terminal is selected for command alarms.</para>"
1074                                                                  "<para>Please set it in the <application>KAlarm</application> Configuration dialog.</para>"))
1075                     != KMessageBox::Continue)
1076                 return false;
1077         }
1078     }
1079     return true;
1080 }
1081 
1082 /******************************************************************************
1083 * Called when the Try action has been executed.
1084 * Tell the user the result of the Try action.
1085 */
type_executedTry(const QString & text,void * result)1086 void EditCommandAlarmDlg::type_executedTry(const QString& text, void* result)
1087 {
1088     auto* proc = (ShellProcess*)result;
1089     if (proc  &&  proc != (void*)-1
1090     &&  mCmdOutputGroup->checkedButton() != mCmdExecInTerm)
1091     {
1092         theApp()->commandMessage(proc, this);
1093         KAMessageBox::information(this, xi18nc("@info", "Command executed: <icode>%1</icode>", text));
1094         theApp()->commandMessage(proc, nullptr);
1095     }
1096 }
1097 
1098 /******************************************************************************
1099 * Called when one of the command type radio buttons is clicked,
1100 * to display the appropriate edit field.
1101 */
slotCmdScriptToggled(bool on)1102 void EditCommandAlarmDlg::slotCmdScriptToggled(bool on)
1103 {
1104     if (on)
1105         mCmdPadding->hide();
1106     else
1107         mCmdPadding->show();
1108 }
1109 
1110 /******************************************************************************
1111 * Clean up the alarm text.
1112 */
checkText(QString & result,bool showErrorMessage) const1113 bool EditCommandAlarmDlg::checkText(QString& result, bool showErrorMessage) const
1114 {
1115     result = mCmdEdit->text(const_cast<EditCommandAlarmDlg*>(this), showErrorMessage);
1116     if (result.isEmpty())
1117         return false;
1118     return true;
1119 }
1120 
1121 
1122 /*=============================================================================
1123 = Class EditEmailAlarmDlg
1124 = Dialog to edit email alarms.
1125 =============================================================================*/
1126 
i18n_chk_CopyEmailToSelf()1127 QString EditEmailAlarmDlg::i18n_chk_CopyEmailToSelf()    { return i18nc("@option:check", "Copy email to self"); }
1128 
1129 
1130 /******************************************************************************
1131 * Constructor.
1132 * Parameters:
1133 *   Template = true to edit/create an alarm template
1134 *            = false to edit/create an alarm.
1135 *   event   != to initialise the dialog to show the specified event's data.
1136 */
EditEmailAlarmDlg(bool Template,QWidget * parent,GetResourceType getResource)1137 EditEmailAlarmDlg::EditEmailAlarmDlg(bool Template, QWidget* parent, GetResourceType getResource)
1138     : EditAlarmDlg(Template, KAEvent::EMAIL, parent, getResource)
1139 {
1140     qCDebug(KALARM_LOG) << "EditEmailAlarmDlg: New";
1141     init(KAEvent());
1142 }
1143 
EditEmailAlarmDlg(bool Template,const KAEvent & event,bool newAlarm,QWidget * parent,GetResourceType getResource,bool readOnly)1144 EditEmailAlarmDlg::EditEmailAlarmDlg(bool Template, const KAEvent& event, bool newAlarm, QWidget* parent,
1145                                      GetResourceType getResource, bool readOnly)
1146     : EditAlarmDlg(Template, event, newAlarm, parent, getResource, readOnly)
1147 {
1148     qCDebug(KALARM_LOG) << "EditEmailAlarmDlg: Event.id()";
1149     init(event);
1150 }
1151 
1152 /******************************************************************************
1153 * Return the window caption.
1154 */
type_caption() const1155 QString EditEmailAlarmDlg::type_caption() const
1156 {
1157     return isTemplate() ? (isNewAlarm() ? i18nc("@title:window", "New Email Alarm Template") : i18nc("@title:window", "Edit Email Alarm Template"))
1158                         : (isNewAlarm() ? i18nc("@title:window", "New Email Alarm") : i18nc("@title:window", "Edit Email Alarm"));
1159 }
1160 
1161 /******************************************************************************
1162 * Set up the email alarm dialog controls.
1163 */
type_init(QWidget * parent,QVBoxLayout * frameLayout)1164 void EditEmailAlarmDlg::type_init(QWidget* parent, QVBoxLayout* frameLayout)
1165 {
1166     mTryButton->setWhatsThis(i18nc("@info:whatsthis", "Send the email to the specified addressees now"));
1167     mTryButton->setToolTip(i18nc("@info:tooltip", "Send the email now"));
1168 
1169     auto grid = new QGridLayout();
1170     grid->setContentsMargins(0, 0, 0, 0);
1171     grid->setColumnStretch(1, 1);
1172     frameLayout->addLayout(grid);
1173 
1174     mEmailFromList = nullptr;
1175     if (Preferences::emailFrom() == Preferences::MAIL_FROM_KMAIL)
1176     {
1177         // Email sender identity
1178         QLabel* label = new QLabel(i18nc("@label:listbox 'From' email address", "From:"), parent);
1179         grid->addWidget(label, 0, 0);
1180 
1181         mEmailFromList = new EmailIdCombo(Identities::identityManager(), parent);
1182         mEmailFromList->setMinimumSize(mEmailFromList->sizeHint());
1183         label->setBuddy(mEmailFromList);
1184         mEmailFromList->setWhatsThis(i18nc("@info:whatsthis", "Your email identity, used to identify you as the sender when sending email alarms."));
1185         connect(mEmailFromList, &EmailIdCombo::identityChanged, this, &EditEmailAlarmDlg::contentsChanged);
1186         grid->addWidget(mEmailFromList, 0, 1, 1, 2);
1187     }
1188 
1189     // Email recipients
1190     QLabel* label = new QLabel(i18nc("@label:textbox Email addressee", "To:"), parent);
1191     grid->addWidget(label, 1, 0);
1192 
1193     mEmailToEdit = new LineEdit(LineEdit::Emails, parent);
1194     mEmailToEdit->setMinimumSize(mEmailToEdit->sizeHint());
1195     mEmailToEdit->setWhatsThis(i18nc("@info:whatsthis", "Enter the addresses of the email recipients. Separate multiple addresses by "
1196                                     "commas or semicolons."));
1197     connect(mEmailToEdit, &LineEdit::textChanged, this, &EditEmailAlarmDlg::contentsChanged);
1198     grid->addWidget(mEmailToEdit, 1, 1);
1199 
1200     mEmailAddressButton = new QPushButton(parent);
1201     mEmailAddressButton->setIcon(QIcon::fromTheme(QStringLiteral("help-contents")));
1202     connect(mEmailAddressButton, &QPushButton::clicked, this, &EditEmailAlarmDlg::openAddressBook);
1203     mEmailAddressButton->setToolTip(i18nc("@info:tooltip", "Open address book"));
1204     mEmailAddressButton->setWhatsThis(i18nc("@info:whatsthis", "Select email addresses from your address book."));
1205     grid->addWidget(mEmailAddressButton, 1, 2);
1206 
1207     // Email subject
1208     label = new QLabel(i18nc("@label:textbox Email subject", "Subject:"), parent);
1209     grid->addWidget(label, 2, 0);
1210 
1211     mEmailSubjectEdit = new LineEdit(parent);
1212     mEmailSubjectEdit->setMinimumSize(mEmailSubjectEdit->sizeHint());
1213     label->setBuddy(mEmailSubjectEdit);
1214     mEmailSubjectEdit->setWhatsThis(i18nc("@info:whatsthis", "Enter the email subject."));
1215     connect(mEmailSubjectEdit, &LineEdit::textChanged, this, &EditEmailAlarmDlg::contentsChanged);
1216     grid->addWidget(mEmailSubjectEdit, 2, 1, 1, 2);
1217 
1218     // Email body
1219     mEmailMessageEdit = new TextEdit(parent);
1220     mEmailMessageEdit->setWhatsThis(i18nc("@info:whatsthis", "Enter the email message."));
1221     connect(mEmailMessageEdit, &TextEdit::textChanged, this, &EditEmailAlarmDlg::contentsChanged);
1222     frameLayout->addWidget(mEmailMessageEdit);
1223 
1224     // Email attachments
1225     grid = new QGridLayout();
1226     grid->setContentsMargins(0, 0, 0, 0);
1227     frameLayout->addLayout(grid);
1228     label = new QLabel(i18nc("@label:listbox", "Attachments:"), parent);
1229     grid->addWidget(label, 0, 0);
1230 
1231     mEmailAttachList = new QComboBox(parent);
1232     mEmailAttachList->setEditable(true);
1233     mEmailAttachList->setMinimumSize(mEmailAttachList->sizeHint());
1234     if (mEmailAttachList->lineEdit())
1235         mEmailAttachList->lineEdit()->setReadOnly(true);
1236     label->setBuddy(mEmailAttachList);
1237     mEmailAttachList->setWhatsThis(i18nc("@info:whatsthis", "Files to send as attachments to the email."));
1238     grid->addWidget(mEmailAttachList, 0, 1);
1239     grid->setColumnStretch(1, 1);
1240 
1241     mEmailAddAttachButton = new QPushButton(i18nc("@action:button", "Add..."), parent);
1242     connect(mEmailAddAttachButton, &QPushButton::clicked, this, &EditEmailAlarmDlg::slotAddAttachment);
1243     mEmailAddAttachButton->setWhatsThis(i18nc("@info:whatsthis", "Add an attachment to the email."));
1244     grid->addWidget(mEmailAddAttachButton, 0, 2);
1245 
1246     mEmailRemoveButton = new QPushButton(i18nc("@action:button", "Remove"), parent);
1247     connect(mEmailRemoveButton, &QPushButton::clicked, this, &EditEmailAlarmDlg::slotRemoveAttachment);
1248     mEmailRemoveButton->setWhatsThis(i18nc("@info:whatsthis", "Remove the highlighted attachment from the email."));
1249     grid->addWidget(mEmailRemoveButton, 1, 2);
1250 
1251     // BCC email to sender
1252     mEmailBcc = new CheckBox(i18n_chk_CopyEmailToSelf(), parent);
1253     mEmailBcc->setWhatsThis(i18nc("@info:whatsthis", "If checked, the email will be blind copied to you."));
1254     connect(mEmailBcc, &CheckBox::toggled, this, &EditEmailAlarmDlg::contentsChanged);
1255     grid->addWidget(mEmailBcc, 1, 0, 1, 2, Qt::AlignLeft);
1256 }
1257 
1258 /******************************************************************************
1259 * Initialise the dialog controls from the specified event.
1260 */
type_initValues(const KAEvent & event)1261 void EditEmailAlarmDlg::type_initValues(const KAEvent& event)
1262 {
1263     if (event.isValid())
1264     {
1265         // Set the values to those for the specified event
1266         mEmailAttachList->addItems(event.emailAttachments());
1267         mEmailToEdit->setText(event.emailAddresses(QStringLiteral(", ")));
1268         mEmailSubjectEdit->setText(event.emailSubject());
1269         mEmailBcc->setChecked(event.emailBcc());
1270         if (mEmailFromList)
1271             mEmailFromList->setCurrentIdentity(event.emailFromId());
1272     }
1273     else
1274     {
1275         // Set the values to their defaults
1276         mEmailBcc->setChecked(Preferences::defaultEmailBcc());
1277     }
1278     attachmentEnable();
1279 }
1280 
1281 /******************************************************************************
1282 * Enable/disable controls depending on whether any attachments are entered.
1283 */
attachmentEnable()1284 void EditEmailAlarmDlg::attachmentEnable()
1285 {
1286     const bool enable = mEmailAttachList->count();
1287     mEmailAttachList->setEnabled(enable);
1288     if (mEmailRemoveButton)
1289         mEmailRemoveButton->setEnabled(enable);
1290 }
1291 
1292 /******************************************************************************
1293 * Set the dialog's action and the action's text.
1294 */
setAction(KAEvent::SubAction action,const AlarmText & alarmText)1295 void EditEmailAlarmDlg::setAction(KAEvent::SubAction action, const AlarmText& alarmText)
1296 {
1297     Q_UNUSED(action);
1298     Q_ASSERT(action == KAEvent::EMAIL);
1299     if (alarmText.isEmail())
1300     {
1301         mEmailToEdit->setText(alarmText.to());
1302         mEmailSubjectEdit->setText(alarmText.subject());
1303         mEmailMessageEdit->setPlainText(alarmText.body());
1304     }
1305     else
1306         mEmailMessageEdit->setPlainText(alarmText.displayText());
1307 }
1308 
1309 /******************************************************************************
1310 * Initialise various values in the New Alarm dialogue.
1311 */
setEmailFields(uint fromID,const KCalendarCore::Person::List & addresses,const QString & subject,const QStringList & attachments)1312 void EditEmailAlarmDlg::setEmailFields(uint fromID, const KCalendarCore::Person::List& addresses,
1313                                        const QString& subject, const QStringList& attachments)
1314 {
1315     if (fromID && mEmailFromList)
1316         mEmailFromList->setCurrentIdentity(fromID);
1317     if (!addresses.isEmpty())
1318         mEmailToEdit->setText(KAEvent::joinEmailAddresses(addresses, QStringLiteral(", ")));
1319     if (!subject.isEmpty())
1320         mEmailSubjectEdit->setText(subject);
1321     if (!attachments.isEmpty())
1322     {
1323         mEmailAttachList->addItems(attachments);
1324         attachmentEnable();
1325     }
1326 }
setBcc(bool bcc)1327 void EditEmailAlarmDlg::setBcc(bool bcc)
1328 {
1329     mEmailBcc->setChecked(bcc);
1330 }
1331 
1332 /******************************************************************************
1333 * Set the read-only status of all non-template controls.
1334 */
setReadOnly(bool readOnly)1335 void EditEmailAlarmDlg::setReadOnly(bool readOnly)
1336 {
1337     mEmailToEdit->setReadOnly(readOnly);
1338     mEmailSubjectEdit->setReadOnly(readOnly);
1339     mEmailMessageEdit->setReadOnly(readOnly);
1340     mEmailBcc->setReadOnly(readOnly);
1341     if (mEmailFromList)
1342         mEmailFromList->setReadOnly(readOnly);
1343     if (readOnly)
1344     {
1345         mEmailAddressButton->hide();
1346         mEmailAddAttachButton->hide();
1347         mEmailRemoveButton->hide();
1348     }
1349     else
1350     {
1351         mEmailAddressButton->show();
1352         mEmailAddAttachButton->show();
1353         mEmailRemoveButton->show();
1354     }
1355     EditAlarmDlg::setReadOnly(readOnly);
1356 }
1357 
1358 /******************************************************************************
1359 * Save the state of all controls.
1360 */
saveState(const KAEvent * event)1361 void EditEmailAlarmDlg::saveState(const KAEvent* event)
1362 {
1363     EditAlarmDlg::saveState(event);
1364     if (mEmailFromList)
1365         mSavedEmailFrom = mEmailFromList->currentIdentityName();
1366     mSavedEmailTo      = mEmailToEdit->text();
1367     mSavedEmailSubject = mEmailSubjectEdit->text();
1368     mSavedEmailAttach.clear();
1369     for (int i = 0, end = mEmailAttachList->count();  i < end;  ++i)
1370         mSavedEmailAttach += mEmailAttachList->itemText(i);
1371     mSavedEmailBcc     = mEmailBcc->isChecked();
1372 }
1373 
1374 /******************************************************************************
1375 * Check whether any of the controls has changed state since the dialog was
1376 * first displayed.
1377 * Reply = true if any controls have changed, or if it's a new event.
1378 *       = false if no controls have changed.
1379 */
type_stateChanged() const1380 bool EditEmailAlarmDlg::type_stateChanged() const
1381 {
1382     int count = mEmailAttachList->count();
1383     QStringList emailAttach;
1384     emailAttach.reserve(count);
1385     for (int i = 0;  i < count;  ++i)
1386         emailAttach += mEmailAttachList->itemText(i);
1387     if ((mEmailFromList  &&  mSavedEmailFrom != mEmailFromList->currentIdentityName())
1388     ||  mSavedEmailTo      != mEmailToEdit->text()
1389     ||  mSavedEmailSubject != mEmailSubjectEdit->text()
1390     ||  mSavedEmailAttach  != emailAttach
1391     ||  mSavedEmailBcc     != mEmailBcc->isChecked())
1392         return true;
1393     return false;
1394 }
1395 
1396 /******************************************************************************
1397 * Extract the data in the dialog specific to the alarm type and set up a
1398 * KAEvent from it.
1399 */
type_setEvent(KAEvent & event,const KADateTime & dt,const QString & name,const QString & text,int lateCancel,bool trial)1400 void EditEmailAlarmDlg::type_setEvent(KAEvent& event, const KADateTime& dt, const QString& name, const QString& text, int lateCancel, bool trial)
1401 {
1402     Q_UNUSED(trial);
1403     event = KAEvent(dt, name, text, QColor(), QColor(), QFont(), KAEvent::EMAIL, lateCancel, getAlarmFlags());
1404     const uint from = mEmailFromList ? mEmailFromList->currentIdentity() : 0;
1405     event.setEmail(from, mEmailAddresses, mEmailSubjectEdit->text(), mEmailAttachments);
1406 }
1407 
1408 /******************************************************************************
1409 * Get the currently specified alarm flag bits.
1410 */
getAlarmFlags() const1411 KAEvent::Flags EditEmailAlarmDlg::getAlarmFlags() const
1412 {
1413     KAEvent::Flags flags = EditAlarmDlg::getAlarmFlags();
1414     if (mEmailBcc->isChecked()) flags |= KAEvent::EMAIL_BCC;
1415     return flags;
1416 }
1417 
1418 /******************************************************************************
1419 * Convert the email addresses to a list, and validate them. Convert the email
1420 * attachments to a list.
1421 */
type_validate(bool trial)1422 bool EditEmailAlarmDlg::type_validate(bool trial)
1423 {
1424     const QString addrs = mEmailToEdit->text();
1425     if (addrs.isEmpty())
1426         mEmailAddresses.clear();
1427     else
1428     {
1429         const QString bad = KAMail::convertAddresses(addrs, mEmailAddresses);
1430         if (!bad.isEmpty())
1431         {
1432             mEmailToEdit->setFocus();
1433             KAMessageBox::error(this, xi18nc("@info", "Invalid email address: <email>%1</email>", bad));
1434             return false;
1435         }
1436     }
1437     if (mEmailAddresses.isEmpty())
1438     {
1439         mEmailToEdit->setFocus();
1440         KAMessageBox::error(this, i18nc("@info", "No email address specified"));
1441         return false;
1442     }
1443 
1444     mEmailAttachments.clear();
1445     for (int i = 0, end = mEmailAttachList->count();  i < end;  ++i)
1446     {
1447         QString att = mEmailAttachList->itemText(i);
1448         switch (KAMail::checkAttachment(att))
1449         {
1450             case 1:
1451                 mEmailAttachments.append(att);
1452                 break;
1453             case 0:
1454                 break;      // empty
1455             case -1:
1456                 mEmailAttachList->setFocus();
1457                 KAMessageBox::error(this, xi18nc("@info", "Invalid email attachment: <filename>%1</filename>", att));
1458                 return false;
1459         }
1460     }
1461     if (trial  &&  KAMessageBox::warningContinueCancel(this, i18nc("@info", "Do you really want to send the email now to the specified recipient(s)?"),
1462                                                        i18nc("@action:button", "Confirm Email"), KGuiItem(i18nc("@action:button", "Send"))) != KMessageBox::Continue)
1463         return false;
1464     return true;
1465 }
1466 
1467 /******************************************************************************
1468 * Called when the Try action is about to be executed.
1469 */
type_aboutToTry()1470 void EditEmailAlarmDlg::type_aboutToTry()
1471 {
1472     // Disconnect any previous connections, to prevent multiple messages being output
1473     disconnect(theApp(), &KAlarmApp::execAlarmSuccess, this, &EditEmailAlarmDlg::slotTrySuccess);
1474     connect(theApp(), &KAlarmApp::execAlarmSuccess, this, &EditEmailAlarmDlg::slotTrySuccess);
1475 }
1476 
1477 /******************************************************************************
1478 * Tell the user the result of the Try action.
1479 */
slotTrySuccess()1480 void EditEmailAlarmDlg::slotTrySuccess()
1481 {
1482     disconnect(theApp(), &KAlarmApp::execAlarmSuccess, this, &EditEmailAlarmDlg::slotTrySuccess);
1483     QString msg;
1484     QString to = KAEvent::joinEmailAddresses(mEmailAddresses, QStringLiteral("<nl/>"));
1485     to.replace(QLatin1Char('<'), QStringLiteral("&lt;"));
1486     to.replace(QLatin1Char('>'), QStringLiteral("&gt;"));
1487     if (mEmailBcc->isChecked())
1488         msg = QLatin1String("<qt>") + xi18nc("@info", "Email sent to:<nl/>%1<nl/>Bcc: <email>%2</email>",
1489                     to, Preferences::emailBccAddress()) + QLatin1String("</qt>");
1490     else
1491         msg = QLatin1String("<qt>") + xi18nc("@info", "Email sent to:<nl/>%1", to) + QLatin1String("</qt>");
1492     KAMessageBox::information(this, msg);
1493 }
1494 
1495 /******************************************************************************
1496 * Get a selection from the Address Book.
1497 */
openAddressBook()1498 void EditEmailAlarmDlg::openAddressBook()
1499 {
1500     // Use AutoQPointer to guard against crash on application exit while
1501     // the dialogue is still open. It prevents double deletion (both on
1502     // deletion of MainWindow, and on return from this function).
1503     AutoQPointer<Akonadi::EmailAddressSelectionDialog> dlg = new Akonadi::EmailAddressSelectionDialog(this);
1504     if (dlg->exec() != QDialog::Accepted)
1505         return;
1506 
1507     Akonadi::EmailAddressSelection::List selections = dlg->selectedAddresses();
1508     if (selections.isEmpty())
1509         return;
1510     const Person person(selections.first().name(), selections.first().email());
1511     QString addrs = mEmailToEdit->text().trimmed();
1512     if (!addrs.isEmpty())
1513         addrs += QLatin1String(", ");
1514     addrs += person.fullName();
1515     mEmailToEdit->setText(addrs);
1516 }
1517 
1518 /******************************************************************************
1519 * Select a file to attach to the email.
1520 */
slotAddAttachment()1521 void EditEmailAlarmDlg::slotAddAttachment()
1522 {
1523     QString file;
1524     if (File::browseFile(file, i18nc("@title:window", "Choose File to Attach"),
1525                          mAttachDefaultDir, QString(), true, this))
1526     {
1527         if (!file.isEmpty())
1528         {
1529             mEmailAttachList->addItem(file);
1530             mEmailAttachList->setCurrentIndex(mEmailAttachList->count() - 1);   // select the new item
1531             mEmailRemoveButton->setEnabled(true);
1532             mEmailAttachList->setEnabled(true);
1533             contentsChanged();
1534         }
1535     }
1536 }
1537 
1538 /******************************************************************************
1539 * Remove the currently selected attachment from the email.
1540 */
slotRemoveAttachment()1541 void EditEmailAlarmDlg::slotRemoveAttachment()
1542 {
1543     const int item = mEmailAttachList->currentIndex();
1544     mEmailAttachList->removeItem(item);
1545     const int count = mEmailAttachList->count();
1546     if (item >= count)
1547         mEmailAttachList->setCurrentIndex(count - 1);
1548     if (!count)
1549     {
1550         mEmailRemoveButton->setEnabled(false);
1551         mEmailAttachList->setEnabled(false);
1552     }
1553     contentsChanged();
1554 }
1555 
1556 /******************************************************************************
1557 * Clean up the alarm text.
1558 */
checkText(QString & result,bool showErrorMessage) const1559 bool EditEmailAlarmDlg::checkText(QString& result, bool showErrorMessage) const
1560 {
1561     Q_UNUSED(showErrorMessage);
1562     result = mEmailMessageEdit->toPlainText();
1563     return true;
1564 }
1565 
1566 
1567 /*=============================================================================
1568 = Class EditAudioAlarmDlg
1569 = Dialog to edit audio alarms with no display window.
1570 =============================================================================*/
1571 
1572 /******************************************************************************
1573 * Constructor.
1574 * Parameters:
1575 *   Template = true to edit/create an alarm template
1576 *            = false to edit/create an alarm.
1577 *   event   != to initialise the dialog to show the specified event's data.
1578 */
EditAudioAlarmDlg(bool Template,QWidget * parent,GetResourceType getResource)1579 EditAudioAlarmDlg::EditAudioAlarmDlg(bool Template, QWidget* parent, GetResourceType getResource)
1580     : EditAlarmDlg(Template, KAEvent::AUDIO, parent, getResource)
1581 {
1582     qCDebug(KALARM_LOG) << "EditAudioAlarmDlg: New";
1583     init(KAEvent());
1584 }
1585 
EditAudioAlarmDlg(bool Template,const KAEvent & event,bool newAlarm,QWidget * parent,GetResourceType getResource,bool readOnly)1586 EditAudioAlarmDlg::EditAudioAlarmDlg(bool Template, const KAEvent& event, bool newAlarm, QWidget* parent,
1587                                      GetResourceType getResource, bool readOnly)
1588     : EditAlarmDlg(Template, event, newAlarm, parent, getResource, readOnly)
1589 {
1590     qCDebug(KALARM_LOG) << "EditAudioAlarmDlg: Event.id()";
1591     init(event);
1592     mTryButton->setEnabled(!MessageDisplay::isAudioPlaying());
1593     connect(theApp(), &KAlarmApp::audioPlaying, this, &EditAudioAlarmDlg::slotAudioPlaying);
1594 }
1595 
1596 /******************************************************************************
1597 * Return the window caption.
1598 */
type_caption() const1599 QString EditAudioAlarmDlg::type_caption() const
1600 {
1601     return isTemplate() ? (isNewAlarm() ? i18nc("@title:window", "New Audio Alarm Template") : i18nc("@title:window", "Edit Audio Alarm Template"))
1602                         : (isNewAlarm() ? i18nc("@title:window", "New Audio Alarm") : i18nc("@title:window", "Edit Audio Alarm"));
1603 }
1604 
1605 /******************************************************************************
1606 * Set up the dialog controls common to display alarms.
1607 */
type_init(QWidget * parent,QVBoxLayout * frameLayout)1608 void EditAudioAlarmDlg::type_init(QWidget* parent, QVBoxLayout* frameLayout)
1609 {
1610     mTryButton->setWhatsThis(i18nc("@info:whatsthis", "Play the audio file now"));
1611     mTryButton->setToolTip(i18nc("@info:tooltip", "Play the audio file now"));
1612     // File name edit box
1613     const QString repWhatsThis = i18nc("@info:whatsthis", "If checked, the sound file will be played repeatedly until %1 is clicked.", KAlarm::i18n_act_StopPlay());
1614     mSoundConfig = new SoundWidget(false, repWhatsThis, parent);
1615     if (isTemplate())
1616         mSoundConfig->setAllowEmptyFile();
1617     connect(mSoundConfig, &SoundWidget::changed, this, &EditAudioAlarmDlg::contentsChanged);
1618     frameLayout->addWidget(mSoundConfig);
1619 
1620     // Top-adjust the controls
1621     mPadding = new QWidget(parent);
1622     auto hlayout = new QHBoxLayout(mPadding);
1623     hlayout->setContentsMargins(0, 0, 0, 0);
1624     hlayout->setSpacing(0);
1625     frameLayout->addWidget(mPadding);
1626     frameLayout->setStretchFactor(mPadding, 1);
1627 }
1628 
1629 /******************************************************************************
1630 * Initialise the dialog controls from the specified event.
1631 */
type_initValues(const KAEvent & event)1632 void EditAudioAlarmDlg::type_initValues(const KAEvent& event)
1633 {
1634     if (event.isValid())
1635     {
1636         mSoundConfig->set(event.audioFile(), event.soundVolume(), event.fadeVolume(), event.fadeSeconds(),
1637                           (event.flags() & KAEvent::REPEAT_SOUND) ? event.repeatSoundPause() : -1);
1638     }
1639     else
1640     {
1641         // Set the values to their defaults
1642         mSoundConfig->set(Preferences::defaultSoundFile(), Preferences::defaultSoundVolume(),
1643                           -1, 0, (Preferences::defaultSoundRepeat() ? 0 : -1));
1644     }
1645 }
1646 
1647 /******************************************************************************
1648 * Initialise various values in the New Alarm dialogue.
1649 */
setAudio(const QString & file,float volume)1650 void EditAudioAlarmDlg::setAudio(const QString& file, float volume)
1651 {
1652     mSoundConfig->set(file, volume);
1653 }
1654 
1655 /******************************************************************************
1656 * Set the dialog's action and the action's text.
1657 */
setAction(KAEvent::SubAction action,const AlarmText & alarmText)1658 void EditAudioAlarmDlg::setAction(KAEvent::SubAction action, const AlarmText& alarmText)
1659 {
1660     Q_UNUSED(action);
1661     Q_ASSERT(action == KAEvent::AUDIO);
1662     mSoundConfig->set(alarmText.displayText(), Preferences::defaultSoundVolume());
1663 }
1664 
1665 /******************************************************************************
1666 * Set the read-only status of all non-template controls.
1667 */
setReadOnly(bool readOnly)1668 void EditAudioAlarmDlg::setReadOnly(bool readOnly)
1669 {
1670     mSoundConfig->setReadOnly(readOnly);
1671     EditAlarmDlg::setReadOnly(readOnly);
1672 }
1673 
1674 /******************************************************************************
1675 * Save the state of all controls.
1676 */
saveState(const KAEvent * event)1677 void EditAudioAlarmDlg::saveState(const KAEvent* event)
1678 {
1679     EditAlarmDlg::saveState(event);
1680     mSavedFile   = mSoundConfig->fileName();
1681     mSoundConfig->getVolume(mSavedVolume, mSavedFadeVolume, mSavedFadeSeconds);
1682     mSavedRepeatPause = mSoundConfig->repeatPause();
1683 }
1684 
1685 /******************************************************************************
1686 * Check whether any of the controls has changed state since the dialog was
1687 * first displayed.
1688 * Reply = true if any controls have changed, or if it's a new event.
1689 *       = false if no controls have changed.
1690 */
type_stateChanged() const1691 bool EditAudioAlarmDlg::type_stateChanged() const
1692 {
1693     if (mSavedFile != mSoundConfig->fileName())
1694         return true;
1695     if (!mSavedFile.isEmpty()  ||  isTemplate())
1696     {
1697         float volume, fadeVolume;
1698         int   fadeSecs;
1699         mSoundConfig->getVolume(volume, fadeVolume, fadeSecs);
1700         if (mSavedRepeatPause != mSoundConfig->repeatPause()
1701         ||  mSavedVolume      != volume
1702         ||  mSavedFadeVolume  != fadeVolume
1703         ||  mSavedFadeSeconds != fadeSecs)
1704             return true;
1705     }
1706     return false;
1707 }
1708 
1709 /******************************************************************************
1710 * Extract the data in the dialog specific to the alarm type and set up a
1711 * KAEvent from it.
1712 */
type_setEvent(KAEvent & event,const KADateTime & dt,const QString & name,const QString & text,int lateCancel,bool trial)1713 void EditAudioAlarmDlg::type_setEvent(KAEvent& event, const KADateTime& dt, const QString& name, const QString& text, int lateCancel, bool trial)
1714 {
1715     Q_UNUSED(text);
1716     Q_UNUSED(trial);
1717     event = KAEvent(dt, name, QString(), QColor(), QColor(), QFont(), KAEvent::AUDIO, lateCancel, getAlarmFlags());
1718     float volume, fadeVolume;
1719     int   fadeSecs;
1720     mSoundConfig->getVolume(volume, fadeVolume, fadeSecs);
1721     const int repeatPause = mSoundConfig->repeatPause();
1722     QUrl url;
1723     mSoundConfig->file(url, false);
1724     event.setAudioFile(url.toString(), volume, fadeVolume, fadeSecs, repeatPause, isTemplate());
1725 }
1726 
1727 /******************************************************************************
1728 * Get the currently specified alarm flag bits.
1729 */
getAlarmFlags() const1730 KAEvent::Flags EditAudioAlarmDlg::getAlarmFlags() const
1731 {
1732     KAEvent::Flags flags = EditAlarmDlg::getAlarmFlags();
1733     if (mSoundConfig->repeatPause() >= 0) flags |= KAEvent::REPEAT_SOUND;
1734     return flags;
1735 }
1736 
1737 /******************************************************************************
1738 * Check whether the file name is valid.
1739 */
checkText(QString & result,bool showErrorMessage) const1740 bool EditAudioAlarmDlg::checkText(QString& result, bool showErrorMessage) const
1741 {
1742     QUrl url;
1743     if (!mSoundConfig->file(url, showErrorMessage))
1744     {
1745         result.clear();
1746         return false;
1747     }
1748     result = url.isLocalFile() ? url.toLocalFile() : url.toString();
1749     return true;
1750 }
1751 
1752 /******************************************************************************
1753 * Called when the Try button is clicked.
1754 * If the audio file is currently playing (as a result of previously clicking
1755 * the Try button), cancel playback. Otherwise, play the audio file.
1756 */
slotTry()1757 void EditAudioAlarmDlg::slotTry()
1758 {
1759     if (!MessageDisplay::isAudioPlaying())
1760         EditAlarmDlg::slotTry();   // play the audio file
1761     else if (mMessageWindow)
1762     {
1763         MessageDisplay::stopAudio();
1764         mMessageWindow = nullptr;
1765     }
1766 }
1767 
1768 /******************************************************************************
1769 * Called when the Try action has been executed.
1770 */
type_executedTry(const QString &,void * result)1771 void EditAudioAlarmDlg::type_executedTry(const QString&, void* result)
1772 {
1773     mMessageWindow = (MessageWindow*)result;    // note which MessageWindow controls the audio playback
1774     if (mMessageWindow)
1775     {
1776         slotAudioPlaying(true);
1777         connect(mMessageWindow, &QObject::destroyed, this, &EditAudioAlarmDlg::audioWinDestroyed);
1778     }
1779 }
1780 
1781 /******************************************************************************
1782 * Called when audio playing starts or stops.
1783 * Enable/disable/toggle the Try button.
1784 */
slotAudioPlaying(bool playing)1785 void EditAudioAlarmDlg::slotAudioPlaying(bool playing)
1786 {
1787     if (!playing)
1788     {
1789         // Nothing is playing, so enable the Try button
1790         mTryButton->setEnabled(true);
1791         mTryButton->setCheckable(false);
1792         mTryButton->setChecked(false);
1793         mMessageWindow = nullptr;
1794     }
1795     else if (mMessageWindow)
1796     {
1797         // The test sound file is playing, so enable the Try button and depress it
1798         mTryButton->setEnabled(true);
1799         mTryButton->setCheckable(true);
1800         mTryButton->setChecked(true);
1801     }
1802     else
1803     {
1804         // An alarm is playing, so disable the Try button
1805         mTryButton->setEnabled(false);
1806         mTryButton->setCheckable(false);
1807         mTryButton->setChecked(false);
1808     }
1809 }
1810 
1811 
1812 /*=============================================================================
1813 = Class CommandEdit
1814 = A widget to allow entry of a command or a command script.
1815 =============================================================================*/
CommandEdit(QWidget * parent)1816 CommandEdit::CommandEdit(QWidget* parent)
1817     : QWidget(parent)
1818 {
1819     auto vlayout = new QVBoxLayout(this);
1820     vlayout->setContentsMargins(0, 0, 0, 0);
1821     mTypeScript = new CheckBox(EditCommandAlarmDlg::i18n_chk_EnterScript(), this);
1822     mTypeScript->setWhatsThis(i18nc("@info:whatsthis", "Check to enter the contents of a script instead of a shell command line"));
1823     connect(mTypeScript, &CheckBox::toggled, this, &CommandEdit::slotCmdScriptToggled);
1824     connect(mTypeScript, &CheckBox::toggled, this, &CommandEdit::changed);
1825     vlayout->addWidget(mTypeScript, 0, Qt::AlignLeft);
1826 
1827     mCommandEdit = new LineEdit(LineEdit::Url, this);
1828     mCommandEdit->setWhatsThis(i18nc("@info:whatsthis", "Enter a shell command to execute."));
1829     connect(mCommandEdit, &LineEdit::textChanged, this, &CommandEdit::changed);
1830     vlayout->addWidget(mCommandEdit);
1831 
1832     mScriptEdit = new TextEdit(this);
1833     mScriptEdit->setWhatsThis(i18nc("@info:whatsthis", "Enter the contents of a script to execute"));
1834     connect(mScriptEdit, &TextEdit::textChanged, this, &CommandEdit::changed);
1835     vlayout->addWidget(mScriptEdit);
1836 
1837     slotCmdScriptToggled(mTypeScript->isChecked());
1838 }
1839 
1840 /******************************************************************************
1841 * Initialise the widget controls from the specified event.
1842 */
setScript(bool script)1843 void CommandEdit::setScript(bool script)
1844 {
1845     mTypeScript->setChecked(script);
1846 }
1847 
isScript() const1848 bool CommandEdit::isScript() const
1849 {
1850     return mTypeScript->isChecked();
1851 }
1852 
1853 /******************************************************************************
1854 * Set the widget's text.
1855 */
setText(const AlarmText & alarmText)1856 void CommandEdit::setText(const AlarmText& alarmText)
1857 {
1858     const QString text = alarmText.displayText();
1859     const bool script = alarmText.isScript();
1860     mTypeScript->setChecked(script);
1861     if (script)
1862         mScriptEdit->setPlainText(text);
1863     else
1864         mCommandEdit->setText(File::pathOrUrl(text));
1865 }
1866 
1867 /******************************************************************************
1868 * Return the widget's text.
1869 */
text() const1870 QString CommandEdit::text() const
1871 {
1872     QString result;
1873     if (mTypeScript->isChecked())
1874         result = mScriptEdit->toPlainText();
1875     else
1876         result = mCommandEdit->text();
1877     return result.trimmed();
1878 }
1879 
1880 /******************************************************************************
1881 * Return the alarm text.
1882 * If 'showErrorMessage' is true and the text is empty, an error message is
1883 * displayed.
1884 */
text(EditAlarmDlg * dlg,bool showErrorMessage) const1885 QString CommandEdit::text(EditAlarmDlg* dlg, bool showErrorMessage) const
1886 {
1887     const QString result = text();
1888     if (showErrorMessage  &&  result.isEmpty())
1889         KAMessageBox::sorry(dlg, i18nc("@info", "Please enter a command or script to execute"));
1890     return result;
1891 }
1892 
1893 /******************************************************************************
1894 * Set the read-only status of all controls.
1895 */
setReadOnly(bool readOnly)1896 void CommandEdit::setReadOnly(bool readOnly)
1897 {
1898     mTypeScript->setReadOnly(readOnly);
1899     mCommandEdit->setReadOnly(readOnly);
1900     mScriptEdit->setReadOnly(readOnly);
1901 }
1902 
1903 /******************************************************************************
1904 * Called when one of the command type radio buttons is clicked,
1905 * to display the appropriate edit field.
1906 */
slotCmdScriptToggled(bool on)1907 void CommandEdit::slotCmdScriptToggled(bool on)
1908 {
1909     if (on)
1910     {
1911         mCommandEdit->hide();
1912         mScriptEdit->show();
1913         mScriptEdit->setFocus();
1914     }
1915     else
1916     {
1917         mScriptEdit->hide();
1918         mCommandEdit->show();
1919         mCommandEdit->setFocus();
1920     }
1921     Q_EMIT scriptToggled(on);
1922 }
1923 
1924 /******************************************************************************
1925 * Returns the minimum size of the widget.
1926 */
minimumSizeHint() const1927 QSize CommandEdit::minimumSizeHint() const
1928 {
1929     const QSize t(mTypeScript->minimumSizeHint());
1930     QSize s(mCommandEdit->minimumSizeHint().expandedTo(mScriptEdit->minimumSizeHint()));
1931     s.setHeight(s.height() + style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing) + t.height());
1932     if (s.width() < t.width())
1933         s.setWidth(t.width());
1934     return s;
1935 }
1936 
1937 
1938 
1939 /*=============================================================================
1940 = Class TextEdit
1941 = A text edit field with a minimum height of 3 text lines.
1942 =============================================================================*/
TextEdit(QWidget * parent)1943 TextEdit::TextEdit(QWidget* parent)
1944     : KTextEdit(parent)
1945 {
1946     QSize tsize = TextEdit::minimumSizeHint();   // avoid calling virtual method from constructor
1947     tsize.setHeight(fontMetrics().lineSpacing()*13/4 + 2*frameWidth());
1948     setMinimumSize(tsize);
1949 }
1950 
enableEmailDrop()1951 void TextEdit::enableEmailDrop()
1952 {
1953     mEmailDrop = true;
1954     setAcceptDrops(true);   // allow drag-and-drop onto this widget
1955 }
1956 
dragEnterEvent(QDragEnterEvent * e)1957 void TextEdit::dragEnterEvent(QDragEnterEvent* e)
1958 {
1959     if (KCalUtils::ICalDrag::canDecode(e->mimeData()))
1960     {
1961         e->ignore();   // don't accept "text/calendar" objects
1962         return;
1963     }
1964     if (mEmailDrop  &&  DragDrop::mayHaveRFC822(e->mimeData()))
1965     {
1966         e->acceptProposedAction();
1967         return;
1968     }
1969     KTextEdit::dragEnterEvent(e);
1970 }
1971 
dragMoveEvent(QDragMoveEvent * e)1972 void TextEdit::dragMoveEvent(QDragMoveEvent* e)
1973 {
1974     if (mEmailDrop  &&  DragDrop::mayHaveRFC822(e->mimeData()))
1975     {
1976         e->acceptProposedAction();
1977         return;
1978     }
1979     KTextEdit::dragMoveEvent(e);
1980 }
1981 
1982 /******************************************************************************
1983 * Called when an object is dropped on the widget.
1984 */
dropEvent(QDropEvent * e)1985 void TextEdit::dropEvent(QDropEvent* e)
1986 {
1987     const QMimeData* data = e->mimeData();
1988     if (mEmailDrop)
1989     {
1990         AlarmText alarmText;
1991         bool haveEmail = false;
1992         if (DragDrop::dropRFC822(data, alarmText))
1993         {
1994             // Email message(s). Ignore all but the first.
1995             qCDebug(KALARM_LOG) << "TextEdit::dropEvent: email";
1996             haveEmail = true;
1997         }
1998         else
1999         {
2000             QUrl url;
2001             Akonadi::Item item;
2002             if (DragDrop::dropAkonadiEmail(data, url, item, alarmText))
2003             {
2004                 // It's an email held in Akonadi
2005                 qCDebug(KALARM_LOG) << "TextEdit::dropEvent: Akonadi email";
2006                 haveEmail = true;
2007             }
2008         }
2009         if (haveEmail)
2010         {
2011             if (!alarmText.isEmpty())
2012                 setPlainText(alarmText.displayText());
2013             return;
2014         }
2015     }
2016     QString text;
2017     if (DragDrop::dropPlainText(data, text))
2018     {
2019         setPlainText(text);
2020         return;
2021     }
2022     KTextEdit::dropEvent(e);
2023 }
2024 
2025 // vim: et sw=4:
2026