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("<"));
1486 to.replace(QLatin1Char('>'), QStringLiteral(">"));
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