File indexing completed on 2024-05-12 05:14:44

0001 /*
0002  *  editdlgtypes.cpp  -  dialogs to create or edit alarm or alarm template types
0003  *  Program:  kalarm
0004  *  SPDX-FileCopyrightText: 2001-2023 David Jarvie <djarvie@kde.org>
0005  *
0006  *  SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "editdlgtypes.h"
0010 #include "editdlg_p.h"
0011 
0012 #include "emailidcombo.h"
0013 #include "fontcolourbutton.h"
0014 #include "functions.h"
0015 #include "kalarmapp.h"
0016 #include "kamail.h"
0017 #include "latecancel.h"
0018 #include "mainwindow.h"
0019 #include "messagewindow.h"
0020 #include "pickfileradio.h"
0021 #include "reminder.h"
0022 #include "soundpicker.h"
0023 #include "sounddlg.h"
0024 #include "specialactions.h"
0025 #include "lib/buttongroup.h"
0026 #include "lib/checkbox.h"
0027 #include "lib/dragdrop.h"
0028 #include "lib/file.h"
0029 #include "lib/lineedit.h"
0030 #include "lib/messagebox.h"
0031 #include "lib/radiobutton.h"
0032 #include "lib/shellprocess.h"
0033 #include "lib/timespinbox.h"
0034 #include "kalarmcalendar/identities.h"
0035 #include "akonadiplugin/akonadiplugin.h"
0036 #include "kalarm_debug.h"
0037 
0038 #include <KCalUtils/ICalDrag>
0039 #include <KCalendarCore/Person>
0040 
0041 #include <KLocalizedString>
0042 #include <KFileItem>
0043 
0044 #include <QComboBox>
0045 #include <QLabel>
0046 #include <QDir>
0047 #include <QStyle>
0048 #include <QGroupBox>
0049 #include <QGridLayout>
0050 #include <QHBoxLayout>
0051 #include <QVBoxLayout>
0052 #include <QDragEnterEvent>
0053 #include <QMimeData>
0054 #include <QStandardItemModel>
0055 
0056 using namespace KAlarmCal;
0057 using namespace KCalendarCore;
0058 
0059 enum { tTEXT, tFILE, tCOMMAND };  // order of mTypeCombo items
0060 enum { dWINDOW, dNOTIFY };        // order of mDisplayMethodCombo items
0061 
0062 
0063 /*=============================================================================
0064 = Class PickLogFileRadio
0065 =============================================================================*/
0066 class PickLogFileRadio : public PickFileRadio
0067 {
0068     public:
0069         PickLogFileRadio(QPushButton* b, LineEdit* e, const QString& text, ButtonGroup* group, QWidget* parent)
0070             : PickFileRadio(b, e, text, group, parent) { }
0071         bool pickFile(QString& file) override    // called when browse button is pressed to select a log file
0072         {
0073             return File::browseFile(file, i18nc("@title:window", "Choose Log File"), mDefaultDir, fileEdit()->text(),
0074                                     false, parentWidget());
0075         }
0076     private:
0077         QString mDefaultDir;   // default directory for log file browse button
0078 };
0079 
0080 
0081 /*=============================================================================
0082 = Class EditDisplayAlarmDlg
0083 = Dialog to edit display alarms.
0084 =============================================================================*/
0085 
0086 QString EditDisplayAlarmDlg::i18n_lbl_DisplayMethod() { return i18nc("@label:listbox How to display alarms", "Display method:"); }
0087 QString EditDisplayAlarmDlg::i18n_combo_Window()      { return i18nc("@item:inlistbox", "Window"); }
0088 QString EditDisplayAlarmDlg::i18n_combo_Notify()      { return i18nc("@item:inlistbox", "Notification"); }
0089 QString EditDisplayAlarmDlg::i18n_chk_ConfirmAck()    { return i18nc("@option:check", "Confirm acknowledgment"); }
0090 
0091 /******************************************************************************
0092 * Constructor.
0093 * Parameters:
0094 *   Template = true to edit/create an alarm template
0095 *            = false to edit/create an alarm.
0096 *   event   != to initialise the dialog to show the specified event's data.
0097 */
0098 EditDisplayAlarmDlg::EditDisplayAlarmDlg(bool Template, QWidget* parent, GetResourceType getResource)
0099     : EditAlarmDlg(Template, KAEvent::SubAction::Message, parent, getResource)
0100 {
0101     qCDebug(KALARM_LOG) << "EditDisplayAlarmDlg: New";
0102     init(KAEvent());
0103 }
0104 
0105 EditDisplayAlarmDlg::EditDisplayAlarmDlg(bool Template, const KAEvent& event, bool newAlarm, QWidget* parent,
0106                                          GetResourceType getResource, bool readOnly)
0107     : EditAlarmDlg(Template, event, newAlarm, parent, getResource, readOnly)
0108 {
0109     qCDebug(KALARM_LOG) << "EditDisplayAlarmDlg: Event.id()";
0110     init(event);
0111 }
0112 
0113 /******************************************************************************
0114 * Return the window caption.
0115 */
0116 QString EditDisplayAlarmDlg::type_caption() const
0117 {
0118     return isTemplate() ? (isNewAlarm() ? i18nc("@title:window", "New Display Alarm Template") : i18nc("@title:window", "Edit Display Alarm Template"))
0119                         : (isNewAlarm() ? i18nc("@title:window", "New Display Alarm") : i18nc("@title:window", "Edit Display Alarm"));
0120 }
0121 
0122 /******************************************************************************
0123 * Set up the dialog controls common to display alarms.
0124 */
0125 void EditDisplayAlarmDlg::type_init(QWidget* parent, QVBoxLayout* frameLayout)
0126 {
0127     // Display type combo box
0128     QWidget* box = new QWidget(parent);    // to group widgets for QWhatsThis text
0129 
0130     auto boxHLayout = new QHBoxLayout(box);
0131     boxHLayout->setContentsMargins(0, 0, 0, 0);
0132     QLabel* label = new QLabel(i18nc("@label:listbox", "Display type:"), box);
0133     boxHLayout->addWidget(label);
0134     mTypeCombo = new ComboBox(box);
0135     boxHLayout->addWidget(mTypeCombo);
0136     const QString textItem    = i18nc("@item:inlistbox", "Text message");
0137     const QString fileItem    = i18nc("@item:inlistbox", "File contents");
0138     const QString commandItem = i18nc("@item:inlistbox", "Command output");
0139     mTypeCombo->addItem(textItem);     // index = tTEXT
0140     mTypeCombo->addItem(fileItem);     // index = tFILE
0141     mTypeCombo->addItem(commandItem);  // index = tCOMMAND
0142     mTypeCombo->setCurrentIndex(-1);    // ensure slotAlarmTypeChanged() is called when index is set
0143     if (!ShellProcess::authorised())
0144     {
0145         // User not authorised to issue shell commands - disable Command Output option
0146         auto model = qobject_cast<QStandardItemModel*>(mTypeCombo->model());
0147         if (model)
0148         {
0149             QModelIndex index = model->index(2, mTypeCombo->modelColumn(), mTypeCombo->rootModelIndex());
0150             QStandardItem* item = model->itemFromIndex(index);
0151             if (item)
0152                 item->setEnabled(false);
0153         }
0154     }
0155     connect(mTypeCombo, static_cast<void (ComboBox::*)(int)>(&ComboBox::currentIndexChanged), this, &EditDisplayAlarmDlg::slotAlarmTypeChanged);
0156     connect(mTypeCombo, static_cast<void (ComboBox::*)(int)>(&ComboBox::currentIndexChanged), this, &EditDisplayAlarmDlg::contentsChanged);
0157     label->setBuddy(mTypeCombo);
0158     box->setWhatsThis(xi18nc("@info:whatsthis", "<para>Select what the alarm should display:"
0159                              "<list><item><interface>%1</interface>: the alarm will display the text message you type in.</item>"
0160                              "<item><interface>%2</interface>: the alarm will display the contents of a text or image file.</item>"
0161                              "<item><interface>%3</interface>: the alarm will display the output from a command.</item></list></para>",
0162                              textItem, fileItem, commandItem));
0163     auto hlayout = new QHBoxLayout();
0164     hlayout->setContentsMargins(0, 0, 0, 0);
0165     frameLayout->addLayout(hlayout);
0166     hlayout->addWidget(box);
0167     hlayout->addStretch();    // left adjust the control
0168 
0169     // Text message edit box
0170     mTextMessageEdit = new TextEdit(parent);
0171     mTextMessageEdit->setLineWrapMode(KTextEdit::NoWrap);
0172     mTextMessageEdit->enableEmailDrop();         // allow drag-and-drop of emails onto this widget
0173     mTextMessageEdit->setWhatsThis(i18nc("@info:whatsthis", "Enter the text of the alarm message. It may be multi-line."));
0174     connect(mTextMessageEdit, &TextEdit::textChanged, this, &EditDisplayAlarmDlg::contentsChanged);
0175     frameLayout->addWidget(mTextMessageEdit);
0176 
0177     // File name edit box
0178     mFileBox = new QWidget(parent);
0179     frameLayout->addWidget(mFileBox);
0180     auto fileBoxHLayout = new QHBoxLayout(mFileBox);
0181     fileBoxHLayout->setContentsMargins(0, 0, 0, 0);
0182     fileBoxHLayout->setSpacing(0);
0183     mFileMessageEdit = new LineEdit(LineEdit::Type::Url, mFileBox);
0184     fileBoxHLayout->addWidget(mFileMessageEdit);
0185     mFileMessageEdit->setAcceptDrops(true);
0186     mFileMessageEdit->setWhatsThis(i18nc("@info:whatsthis", "Enter the name or URL of a text or image file to display."));
0187     connect(mFileMessageEdit, &LineEdit::textChanged, this, &EditDisplayAlarmDlg::contentsChanged);
0188 
0189     // File browse button
0190     mFileBrowseButton = new QPushButton(mFileBox);
0191     fileBoxHLayout->addWidget(mFileBrowseButton);
0192     mFileBrowseButton->setIcon(QIcon::fromTheme(QStringLiteral("document-open")));
0193     mFileBrowseButton->setToolTip(i18nc("@info:tooltip", "Choose a file"));
0194     mFileBrowseButton->setWhatsThis(i18nc("@info:whatsthis", "Select a text or image file to display."));
0195     connect(mFileBrowseButton, &QPushButton::clicked, this, &EditDisplayAlarmDlg::slotPickFile);
0196 
0197     // Command type checkbox and edit box
0198     mCmdEdit = new CommandEdit(parent);
0199     connect(mCmdEdit, &CommandEdit::scriptToggled, this, &EditDisplayAlarmDlg::slotCmdScriptToggled);
0200     connect(mCmdEdit, &CommandEdit::changed, this, &EditDisplayAlarmDlg::contentsChanged);
0201     frameLayout->addWidget(mCmdEdit);
0202 
0203     // Sound checkbox and file selector
0204     hlayout = new QHBoxLayout();
0205     hlayout->setContentsMargins(0, 0, 0, 0);
0206     frameLayout->addLayout(hlayout);
0207     mSoundPicker = new SoundPicker(parent);
0208     connect(mSoundPicker, &SoundPicker::changed, this, &EditDisplayAlarmDlg::contentsChanged);
0209     hlayout->addWidget(mSoundPicker);
0210     hlayout->addSpacing(2 * style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
0211     hlayout->addStretch();
0212 
0213     // Font and colour choice button and sample text
0214     mFontColourButton = new FontColourButton(parent);
0215     mFontColourButton->setMaximumHeight(mFontColourButton->sizeHint().height() * 3/2);
0216     hlayout->addWidget(mFontColourButton);
0217     connect(mFontColourButton, &FontColourButton::selected, this, &EditDisplayAlarmDlg::setColours);
0218     connect(mFontColourButton, &FontColourButton::selected, this, &EditDisplayAlarmDlg::contentsChanged);
0219 
0220     // Display method selector
0221     hlayout = new QHBoxLayout();
0222     hlayout->setContentsMargins(0, 0, 0, 0);
0223     frameLayout->addLayout(hlayout);
0224     mDisplayMethodBox = new QWidget(parent);    // to group widgets for QWhatsThis text
0225     boxHLayout = new QHBoxLayout(mDisplayMethodBox);
0226     boxHLayout->setContentsMargins(0, 0, 0, 0);
0227     label = new QLabel(i18n_lbl_DisplayMethod(), mDisplayMethodBox);
0228     boxHLayout->addWidget(label);
0229     mDisplayMethodCombo = new ComboBox(mDisplayMethodBox);
0230     boxHLayout->addWidget(mDisplayMethodCombo);
0231     const QString windowItem = i18n_combo_Window();
0232     const QString notifyItem = i18n_combo_Notify();
0233     mDisplayMethodCombo->addItem(windowItem);     // index = dWINDOW
0234     mDisplayMethodCombo->addItem(notifyItem);     // index = dNOTIFY
0235     connect(mDisplayMethodCombo, static_cast<void (ComboBox::*)(int)>(&ComboBox::currentIndexChanged), this, &EditDisplayAlarmDlg::slotDisplayMethodChanged);
0236     connect(mDisplayMethodCombo, static_cast<void (ComboBox::*)(int)>(&ComboBox::currentIndexChanged), this, &EditDisplayAlarmDlg::contentsChanged);
0237     label->setBuddy(mDisplayMethodCombo);
0238     mDisplayMethodBox->setWhatsThis(i18nc("@info:whatsthis", "Select whether to display the alarm in a window or by the notification system."));
0239     hlayout->addWidget(mDisplayMethodBox);
0240     hlayout->addSpacing(2 * style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
0241     hlayout->addStretch();
0242 
0243     if (ShellProcess::authorised())    // don't display if shell commands not allowed (e.g. kiosk mode)
0244     {
0245         // Special actions button
0246         mSpecialActionsButton = new SpecialActionsButton(false, parent);
0247         connect(mSpecialActionsButton, &SpecialActionsButton::selected, this, &EditDisplayAlarmDlg::contentsChanged);
0248         hlayout->addWidget(mSpecialActionsButton);
0249     }
0250 
0251     // Top-adjust the controls
0252     mFilePadding = new QWidget(parent);
0253     hlayout = new QHBoxLayout(mFilePadding);
0254     hlayout->setContentsMargins(0, 0, 0, 0);
0255     hlayout->setSpacing(0);
0256     frameLayout->addWidget(mFilePadding);
0257     frameLayout->setStretchFactor(mFilePadding, 1);
0258 }
0259 
0260 /******************************************************************************
0261 * Create a reminder control.
0262 */
0263 Reminder* EditDisplayAlarmDlg::createReminder(QWidget* parent)
0264 {
0265     return new Reminder(i18nc("@info:whatsthis", "Check to additionally display a reminder in advance of or after the main alarm time(s)."),
0266                         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()),
0267                         i18nc("@info:whatsthis", "Select whether the reminder should be triggered before or after the main alarm"),
0268                         true, true, parent);
0269 }
0270 
0271 /******************************************************************************
0272 * Create an "acknowledgement confirmation required" checkbox.
0273 */
0274 CheckBox* EditDisplayAlarmDlg::createConfirmAckCheckbox(QWidget* parent)
0275 {
0276     CheckBox* confirmAck = new CheckBox(i18n_chk_ConfirmAck(), parent);
0277     confirmAck->setWhatsThis(i18nc("@info:whatsthis", "Check to be prompted for confirmation when you acknowledge the alarm."));
0278     return confirmAck;
0279 }
0280 
0281 /******************************************************************************
0282 * Initialise the dialog controls from the specified event.
0283 */
0284 void EditDisplayAlarmDlg::type_initValues(const KAEvent& event)
0285 {
0286     mTryButton->setToolTip(i18nc("@info:tooltip", "Display the alarm now"));
0287     mEmailId = -1;
0288     lateCancel()->showAutoClose(true);
0289     if (event.isValid())
0290     {
0291         if (mAlarmType == KAEvent::SubAction::Message  &&  event.emailId()
0292         &&  AlarmText::checkIfEmail(event.cleanText()))
0293             mEmailId = event.emailId();
0294         lateCancel()->setAutoClose(event.autoClose());
0295         mFontColourButton->setFont(event.font(), event.useDefaultFont());
0296         mFontColourButton->setBgColour(event.bgColour());
0297         mFontColourButton->setFgColour(event.fgColour());
0298         setColours(event.fgColour(), event.bgColour());
0299         mDisplayMethodCombo->setCurrentIndex(event.notify() ? dNOTIFY : dWINDOW);
0300         mConfirmAck->setChecked(event.confirmAck());
0301         const bool recurs = event.recurs();
0302         int reminderMins = event.reminderMinutes();
0303         if (reminderMins > 0  &&  !event.reminderActive())
0304             reminderMins = 0;   // don't show advance reminder which has already passed
0305         if (!reminderMins)
0306         {
0307             if (event.reminderDeferral()  &&  !recurs)
0308             {
0309                 reminderMins = event.deferDateTime().minsTo(event.mainDateTime());
0310                 mReminderDeferral = true;
0311             }
0312             else if (event.reminderMinutes()  &&  recurs)
0313             {
0314                 reminderMins = event.reminderMinutes();
0315                 mReminderArchived = true;
0316             }
0317         }
0318         reminder()->setMinutes(reminderMins, dateOnly());
0319         reminder()->setOnceOnly(event.reminderOnceOnly());
0320         reminder()->enableOnceOnly(recurs);
0321         if (mSpecialActionsButton)
0322             mSpecialActionsButton->setActions(event.preAction(), event.postAction(), event.extraActionOptions());
0323         Preferences::SoundType soundType = event.speak()                ? Preferences::Sound_Speak
0324                                          : event.beep()                 ? Preferences::Sound_Beep
0325                                          : !event.audioFile().isEmpty() ? Preferences::Sound_File
0326                                          :                                Preferences::Sound_None;
0327         mSoundPicker->set(soundType, event.audioFile(), event.soundVolume(),
0328                           event.fadeVolume(), event.fadeSeconds(), event.repeatSoundPause());
0329     }
0330     else
0331     {
0332         // Set the values to their defaults
0333         if (!ShellProcess::authorised())
0334         {
0335             // Don't allow shell commands in kiosk mode
0336             if (mSpecialActionsButton)
0337                 mSpecialActionsButton->setEnabled(false);
0338         }
0339         lateCancel()->setAutoClose(Preferences::defaultAutoClose());
0340         mTypeCombo->setCurrentIndex(0);
0341         mFontColourButton->setFont(Preferences::messageFont(), true);
0342         mFontColourButton->setBgColour(Preferences::defaultBgColour());
0343         mFontColourButton->setFgColour(Preferences::defaultFgColour());
0344         setColours(Preferences::defaultFgColour(), Preferences::defaultBgColour());
0345         mDisplayMethodCombo->setCurrentIndex(Preferences::defaultDisplayMethod() == Preferences::Display_Window ? dWINDOW : dNOTIFY);
0346         mConfirmAck->setChecked(Preferences::defaultConfirmAck());
0347         reminder()->setMinutes(0, false);
0348         reminder()->enableOnceOnly(isTimedRecurrence());   // must be called after mRecurrenceEdit is set up
0349         if (mSpecialActionsButton)
0350         {
0351             KAEvent::ExtraActionOptions opts({});
0352             if (Preferences::defaultExecPreActionOnDeferral())
0353                 opts |= KAEvent::ExecPreActOnDeferral;
0354             if (Preferences::defaultCancelOnPreActionError())
0355                 opts |= KAEvent::CancelOnPreActError;
0356             if (Preferences::defaultDontShowPreActionError())
0357                 opts |= KAEvent::DontShowPreActError;
0358             mSpecialActionsButton->setActions(Preferences::defaultPreAction(), Preferences::defaultPostAction(), opts);
0359         }
0360         mSoundPicker->set(Preferences::defaultSoundType(), Preferences::defaultSoundFile(),
0361                           Preferences::defaultSoundVolume(), -1, 0, (Preferences::defaultSoundRepeat() ? 0 : -1));
0362     }
0363     slotDisplayMethodChanged(mDisplayMethodCombo->currentIndex());
0364 }
0365 
0366 /******************************************************************************
0367 * Called when the More/Less Options button is clicked.
0368 * Show/hide the optional options.
0369 */
0370 void EditDisplayAlarmDlg::type_showOptions(bool more)
0371 {
0372     if (mSpecialActionsButton)
0373     {
0374         if (more)
0375         {
0376             mDisplayMethodBox->show();
0377             mSpecialActionsButton->show();
0378         }
0379         else
0380         {
0381             mDisplayMethodBox->hide();
0382             mSpecialActionsButton->hide();
0383         }
0384     }
0385 }
0386 
0387 /******************************************************************************
0388 * Called when the font/color button has been clicked.
0389 * Set the colors in the message text entry control.
0390 */
0391 void EditDisplayAlarmDlg::setColours(const QColor& fgColour, const QColor& bgColour)
0392 {
0393     QColor fg(fgColour);
0394     if (mDisplayMethodCombo->currentIndex() == dNOTIFY)
0395     {
0396         const QPalette pal = mFileMessageEdit->palette();
0397         mTextMessageEdit->setPalette(pal);
0398         mTextMessageEdit->viewport()->setPalette(pal);
0399         fg = pal.color(QPalette::Text);
0400     }
0401     else
0402     {
0403         QPalette pal = mTextMessageEdit->palette();
0404         pal.setColor(mTextMessageEdit->backgroundRole(), bgColour);
0405         pal.setColor(QPalette::Text, fgColour);
0406         mTextMessageEdit->setPalette(pal);
0407         pal = mTextMessageEdit->viewport()->palette();
0408         pal.setColor(mTextMessageEdit->viewport()->backgroundRole(), bgColour);
0409         pal.setColor(QPalette::Text, fgColour);
0410         mTextMessageEdit->viewport()->setPalette(pal);
0411     }
0412     // Change the color of existing text
0413     const QTextCursor cursor = mTextMessageEdit->textCursor();
0414     mTextMessageEdit->selectAll();
0415     mTextMessageEdit->setTextColor(fg);
0416     mTextMessageEdit->setTextCursor(cursor);
0417 }
0418 
0419 /******************************************************************************
0420 * Set the dialog's action and the action's text.
0421 */
0422 void EditDisplayAlarmDlg::setAction(KAEvent::SubAction action, const AlarmText& alarmText)
0423 {
0424     const QString text = alarmText.displayText();
0425     switch (action)
0426     {
0427         case KAEvent::SubAction::Message:
0428             mTypeCombo->setCurrentIndex(tTEXT);
0429             mTextMessageEdit->setPlainText(text);
0430             mEmailId = alarmText.isEmail() ? alarmText.emailId() : -1;
0431             break;
0432         case KAEvent::SubAction::File:
0433             mTypeCombo->setCurrentIndex(tFILE);
0434             mFileMessageEdit->setText(text);
0435             break;
0436         case KAEvent::SubAction::Command:
0437             mTypeCombo->setCurrentIndex(tCOMMAND);
0438             mCmdEdit->setText(alarmText);
0439             break;
0440         default:
0441             Q_ASSERT(0);
0442             break;
0443     }
0444 }
0445 
0446 /******************************************************************************
0447 * Initialise various values in the New Alarm dialogue.
0448 */
0449 void EditDisplayAlarmDlg::setBgColour(const QColor& colour)
0450 {
0451     mFontColourButton->setBgColour(colour);
0452     setColours(mFontColourButton->fgColour(), colour);
0453 }
0454 void EditDisplayAlarmDlg::setFgColour(const QColor& colour)
0455 {
0456     mFontColourButton->setFgColour(colour);
0457     setColours(colour, mFontColourButton->bgColour());
0458 }
0459 void EditDisplayAlarmDlg::setNotify(bool notify)
0460 {
0461     mDisplayMethodCombo->setCurrentIndex(notify ? dNOTIFY : dWINDOW);
0462 }
0463 void EditDisplayAlarmDlg::setConfirmAck(bool confirm)
0464 {
0465     mConfirmAck->setChecked(confirm);
0466 }
0467 void EditDisplayAlarmDlg::setAutoClose(bool close)
0468 {
0469     lateCancel()->setAutoClose(close);
0470 }
0471 void EditDisplayAlarmDlg::setAudio(Preferences::SoundType type, const QString& file, float volume, int repeatPause)
0472 {
0473     mSoundPicker->set(type, file, volume, -1, 0, repeatPause);
0474 }
0475 void EditDisplayAlarmDlg::setReminder(int minutes, bool onceOnly)
0476 {
0477     reminder()->setMinutes(minutes, dateOnly());
0478     reminder()->setOnceOnly(onceOnly);
0479     reminder()->enableOnceOnly(isTimedRecurrence());
0480 }
0481 
0482 /******************************************************************************
0483 * Set the read-only status of all non-template controls.
0484 */
0485 void EditDisplayAlarmDlg::setReadOnly(bool readOnly)
0486 {
0487     mTypeCombo->setReadOnly(readOnly);
0488     mTextMessageEdit->setReadOnly(readOnly);
0489     mFileMessageEdit->setReadOnly(readOnly);
0490     mCmdEdit->setReadOnly(readOnly);
0491     mFontColourButton->setReadOnly(readOnly);
0492     mSoundPicker->setReadOnly(readOnly);
0493     mDisplayMethodCombo->setReadOnly(readOnly);
0494     mConfirmAck->setReadOnly(readOnly);
0495     reminder()->setReadOnly(readOnly);
0496     if (mSpecialActionsButton)
0497         mSpecialActionsButton->setReadOnly(readOnly);
0498     if (readOnly)
0499         mFileBrowseButton->hide();
0500     else
0501         mFileBrowseButton->show();
0502     EditAlarmDlg::setReadOnly(readOnly);
0503 }
0504 
0505 /******************************************************************************
0506 * Save the state of all controls.
0507 */
0508 void EditDisplayAlarmDlg::saveState(const KAEvent* event)
0509 {
0510     EditAlarmDlg::saveState(event);
0511     mSavedType          = mTypeCombo->currentIndex();
0512     mSavedCmdScript     = mCmdEdit->isScript();
0513     mSavedSoundType     = mSoundPicker->sound();
0514     mSavedSoundFile     = mSoundPicker->file();
0515     mSavedSoundVolume   = mSoundPicker->volume(mSavedSoundFadeVolume, mSavedSoundFadeSeconds);
0516     mSavedRepeatPause   = mSoundPicker->repeatPause();
0517     mSavedDisplayMethod = mDisplayMethodCombo->currentIndex();
0518     mSavedConfirmAck    = mConfirmAck->isChecked();
0519     mSavedFont          = mFontColourButton->font();
0520     mSavedFgColour      = mFontColourButton->fgColour();
0521     mSavedBgColour      = mFontColourButton->bgColour();
0522     mSavedReminder      = reminder()->minutes();
0523     mSavedOnceOnly      = reminder()->isOnceOnly();
0524     mSavedAutoClose     = lateCancel()->isAutoClose();
0525     if (mSpecialActionsButton)
0526     {
0527         mSavedPreAction        = mSpecialActionsButton->preAction();
0528         mSavedPostAction       = mSpecialActionsButton->postAction();
0529         mSavedPreActionOptions = mSpecialActionsButton->options();
0530     }
0531 }
0532 
0533 /******************************************************************************
0534 * Check whether any of the controls has changed state since the dialog was
0535 * first displayed.
0536 * Reply = true if any controls have changed, or if it's a new event.
0537 *       = false if no controls have changed.
0538 */
0539 bool EditDisplayAlarmDlg::type_stateChanged() const
0540 {
0541     if (mSavedType          != mTypeCombo->currentIndex()
0542     ||  mSavedCmdScript     != mCmdEdit->isScript()
0543     ||  mSavedSoundType     != mSoundPicker->sound()
0544     ||  mSavedDisplayMethod != mDisplayMethodCombo->currentIndex()
0545     ||  mSavedReminder      != reminder()->minutes()
0546     ||  mSavedOnceOnly      != reminder()->isOnceOnly())
0547         return true;
0548     if (mDisplayMethodCombo->currentIndex() == dWINDOW)
0549     {
0550         if (mSavedConfirmAck != mConfirmAck->isChecked()
0551         ||  mSavedFont       != mFontColourButton->font()
0552         ||  mSavedFgColour   != mFontColourButton->fgColour()
0553         ||  mSavedBgColour   != mFontColourButton->bgColour()
0554         ||  mSavedAutoClose  != lateCancel()->isAutoClose())
0555             return true;
0556     }
0557     if (mSpecialActionsButton)
0558     {
0559         if (mSavedPreAction        != mSpecialActionsButton->preAction()
0560         ||  mSavedPostAction       != mSpecialActionsButton->postAction()
0561         ||  mSavedPreActionOptions != mSpecialActionsButton->options())
0562             return true;
0563     }
0564     if (mSavedSoundType == Preferences::Sound_File)
0565     {
0566         if (mSavedSoundFile != mSoundPicker->file())
0567             return true;
0568         if (!mSavedSoundFile.isEmpty())
0569         {
0570             float fadeVolume;
0571             int   fadeSecs;
0572             if (mSavedRepeatPause != mSoundPicker->repeatPause()
0573             ||  mSavedSoundVolume != mSoundPicker->volume(fadeVolume, fadeSecs)
0574             ||  mSavedSoundFadeVolume != fadeVolume
0575             ||  mSavedSoundFadeSeconds != fadeSecs)
0576                 return true;
0577         }
0578     }
0579     return false;
0580 }
0581 
0582 /******************************************************************************
0583 * Extract the data in the dialog specific to the alarm type and set up a
0584 * KAEvent from it.
0585 */
0586 void EditDisplayAlarmDlg::type_setEvent(KAEvent& event, const KADateTime& dt, const QString& name, const QString& text, int lateCancel, bool trial)
0587 {
0588     KAEvent::SubAction type;
0589     switch (mTypeCombo->currentIndex())
0590     {
0591         case tFILE:     type = KAEvent::SubAction::File; break;
0592         case tCOMMAND:  type = KAEvent::SubAction::Command; break;
0593         default:
0594         case tTEXT:     type = KAEvent::SubAction::Message; break;
0595     }
0596     QColor fgColour, bgColour;
0597     QFont font;
0598     if (mDisplayMethodCombo->currentIndex() == dNOTIFY)
0599     {
0600         bgColour = Preferences::defaultBgColour();
0601         fgColour = Preferences::defaultFgColour();
0602     }
0603     else
0604     {
0605         bgColour = mFontColourButton->bgColour();
0606         fgColour = mFontColourButton->fgColour();
0607         font     = mFontColourButton->font();
0608     }
0609     event = KAEvent(dt, name, text, bgColour, fgColour, font, type, lateCancel, getAlarmFlags());
0610     if (type == KAEvent::SubAction::Message)
0611     {
0612         if (AlarmText::checkIfEmail(text))
0613             event.setEmailId(mEmailId);
0614     }
0615     float fadeVolume;
0616     int   fadeSecs;
0617     const float volume = mSoundPicker->volume(fadeVolume, fadeSecs);
0618     const int   repeatPause = mSoundPicker->repeatPause();
0619     event.setAudioFile(mSoundPicker->file().toDisplayString(), volume, fadeVolume, fadeSecs, repeatPause);
0620     if (!trial  &&  reminder()->isEnabled())
0621         event.setReminder(reminder()->minutes(), reminder()->isOnceOnly());
0622     if (mSpecialActionsButton  &&  mSpecialActionsButton->isEnabled())
0623         event.setActions(mSpecialActionsButton->preAction(), mSpecialActionsButton->postAction(),
0624                          mSpecialActionsButton->options());
0625 }
0626 
0627 /******************************************************************************
0628 * Get the currently specified alarm flag bits.
0629 */
0630 KAEvent::Flags EditDisplayAlarmDlg::getAlarmFlags() const
0631 {
0632     const bool cmd = (mTypeCombo->currentIndex() == tCOMMAND);
0633     KAEvent::Flags flags = EditAlarmDlg::getAlarmFlags();
0634     if (mSoundPicker->sound() == Preferences::Sound_Beep)  flags |= KAEvent::BEEP;
0635     if (mSoundPicker->sound() == Preferences::Sound_Speak) flags |= KAEvent::SPEAK;
0636     if (mSoundPicker->repeatPause() >= 0)                  flags |= KAEvent::REPEAT_SOUND;
0637     if (cmd)                                               flags |= KAEvent::DISPLAY_COMMAND;
0638     if (cmd && mCmdEdit->isScript())                       flags |= KAEvent::SCRIPT;
0639     if (mDisplayMethodCombo->currentIndex() == dNOTIFY)
0640     {
0641                                                            flags |= KAEvent::NOTIFY;
0642                                                            flags |= KAEvent::DEFAULT_FONT;
0643     }
0644     else
0645     {
0646         if (mFontColourButton->defaultFont())              flags |= KAEvent::DEFAULT_FONT;
0647         if (mConfirmAck->isChecked())                      flags |= KAEvent::CONFIRM_ACK;
0648         if (lateCancel()->isAutoClose())                   flags |= KAEvent::AUTO_CLOSE;
0649     }
0650     return flags;
0651 }
0652 
0653 /******************************************************************************
0654 * Called when the alarm display type combo box is changed, to display the
0655 * appropriate set of controls for that action type.
0656 */
0657 void EditDisplayAlarmDlg::slotAlarmTypeChanged(int index)
0658 {
0659     QWidget* focus = nullptr;
0660     switch (index)
0661     {
0662         case tTEXT:    // text message
0663             mFileBox->hide();
0664             mFilePadding->hide();
0665             mCmdEdit->hide();
0666             mTextMessageEdit->show();
0667             mSoundPicker->showSpeak(true);
0668             mTryButton->setWhatsThis(i18nc("@info:whatsthis", "Display the alarm message now"));
0669             focus = mTextMessageEdit;
0670             break;
0671         case tFILE:    // file contents
0672             mTextMessageEdit->hide();
0673             mFileBox->show();
0674             mFilePadding->show();
0675             mCmdEdit->hide();
0676             mSoundPicker->showSpeak(false);
0677             mTryButton->setWhatsThis(i18nc("@info:whatsthis", "Display the file now"));
0678             mFileMessageEdit->setNoSelect();
0679             focus = mFileMessageEdit;
0680             break;
0681         case tCOMMAND:    // command output
0682             mTextMessageEdit->hide();
0683             mFileBox->hide();
0684             slotCmdScriptToggled(mCmdEdit->isScript());  // show/hide mFilePadding
0685             mCmdEdit->show();
0686             mSoundPicker->showSpeak(true);
0687             mTryButton->setWhatsThis(i18nc("@info:whatsthis", "Display the command output now"));
0688             focus = mCmdEdit;
0689             break;
0690     }
0691     if (focus)
0692         focus->setFocus();
0693 }
0694 
0695 /******************************************************************************
0696 * Called when the display method combo box is changed, to enable/disable the
0697 * appropriate set of controls for that display method.
0698 */
0699 void EditDisplayAlarmDlg::slotDisplayMethodChanged(int index)
0700 {
0701     const bool enable = (index == dWINDOW);
0702     mConfirmAck->setVisible(enable);
0703     mFontColourButton->setVisible(enable);
0704     mSoundPicker->showFile(enable);
0705     // Because notifications automatically time out after 10 seconds,
0706     // auto-close would always occur after a notification closes.
0707     lateCancel()->showAutoClose(enable);
0708     // Set the text message edit box colours according to the display method.
0709     setColours(mFontColourButton->fgColour(), mFontColourButton->bgColour());
0710 }
0711 
0712 /******************************************************************************
0713 * Called when the file browse button is pressed to select a file to display.
0714 */
0715 void EditDisplayAlarmDlg::slotPickFile()
0716 {
0717     static QString defaultDir;   // default directory for file browse button
0718     QString file;
0719     if (File::browseFile(file, i18nc("@title:window", "Choose Text or Image File to Display"),
0720                          defaultDir, mFileMessageEdit->text(), true, this))
0721     {
0722         if (!file.isEmpty())
0723         {
0724             mFileMessageEdit->setText(File::pathOrUrl(file));
0725             contentsChanged();
0726         }
0727     }
0728 }
0729 
0730 /******************************************************************************
0731 * Called when one of the command type radio buttons is clicked,
0732 * to display the appropriate edit field.
0733 */
0734 void EditDisplayAlarmDlg::slotCmdScriptToggled(bool on)
0735 {
0736     if (on)
0737         mFilePadding->hide();
0738     else
0739         mFilePadding->show();
0740 }
0741 
0742 /******************************************************************************
0743 * Clean up the alarm text, and if it's a file, check whether it's valid.
0744 */
0745 bool EditDisplayAlarmDlg::checkText(QString& result, bool showErrorMessage) const
0746 {
0747     switch (mTypeCombo->currentIndex())
0748     {
0749         case tTEXT:
0750             result = mTextMessageEdit->toPlainText();
0751             break;
0752 
0753         case tFILE:
0754         {
0755             QString fileName = mFileMessageEdit->text().trimmed();
0756             QUrl url;
0757             File::Error err = File::checkFileExists(fileName, url, MainWindow::mainMainWindow());
0758             if (err == File::Error::None)
0759             {
0760                 KFileItem fi(url);
0761                 switch (File::fileType(fi.currentMimeType()))
0762                 {
0763                     case File::Type::TextFormatted:
0764                     case File::Type::TextPlain:
0765                     case File::Type::TextApplication:
0766                     case File::Type::Image:
0767                         break;
0768                     default:
0769                         err = File::Error::NotTextImage;
0770                         break;
0771                 }
0772             }
0773             if (err != File::Error::None  &&  showErrorMessage)
0774             {
0775                 mFileMessageEdit->setFocus();
0776                 if (!File::showFileErrMessage(fileName, err, File::Error::BlankDisplay, const_cast<EditDisplayAlarmDlg*>(this)))
0777                     return false;
0778             }
0779             result = fileName;
0780             break;
0781         }
0782         case tCOMMAND:
0783             result = mCmdEdit->text(const_cast<EditDisplayAlarmDlg*>(this), showErrorMessage);
0784             if (result.isEmpty())
0785                 return false;
0786             break;
0787     }
0788     return true;
0789 }
0790 
0791 
0792 /*=============================================================================
0793 = Class EditCommandAlarmDlg
0794 = Dialog to edit command alarms.
0795 =============================================================================*/
0796 
0797 QString EditCommandAlarmDlg::i18n_chk_EnterScript()        { return i18nc("@option:check", "Enter a script"); }
0798 QString EditCommandAlarmDlg::i18n_radio_ExecInTermWindow() { return i18nc("@option:radio", "Execute in terminal window"); }
0799 QString EditCommandAlarmDlg::i18n_chk_ExecInTermWindow()   { return i18nc("@option:check", "Execute in terminal window"); }
0800 
0801 
0802 /******************************************************************************
0803 * Constructor.
0804 * Parameters:
0805 *   Template = true to edit/create an alarm template
0806 *            = false to edit/create an alarm.
0807 *   event   != to initialise the dialog to show the specified event's data.
0808 */
0809 EditCommandAlarmDlg::EditCommandAlarmDlg(bool Template, QWidget* parent, GetResourceType getResource)
0810     : EditAlarmDlg(Template, KAEvent::SubAction::Command, parent, getResource)
0811 {
0812     qCDebug(KALARM_LOG) << "EditCommandAlarmDlg: New";
0813     init(KAEvent());
0814 }
0815 
0816 EditCommandAlarmDlg::EditCommandAlarmDlg(bool Template, const KAEvent& event, bool newAlarm, QWidget* parent,
0817                                          GetResourceType getResource, bool readOnly)
0818     : EditAlarmDlg(Template, event, newAlarm, parent, getResource, readOnly)
0819 {
0820     qCDebug(KALARM_LOG) << "EditCommandAlarmDlg: Event.id()";
0821     init(event);
0822 }
0823 
0824 /******************************************************************************
0825 * Return the window caption.
0826 */
0827 QString EditCommandAlarmDlg::type_caption() const
0828 {
0829     return isTemplate() ? (isNewAlarm() ? i18nc("@title:window", "New Command Alarm Template") : i18nc("@title:window", "Edit Command Alarm Template"))
0830                         : (isNewAlarm() ? i18nc("@title:window", "New Command Alarm") : i18nc("@title:window", "Edit Command Alarm"));
0831 }
0832 
0833 /******************************************************************************
0834 * Set up the command alarm dialog controls.
0835 */
0836 void EditCommandAlarmDlg::type_init(QWidget* parent, QVBoxLayout* frameLayout)
0837 {
0838     mTryButton->setWhatsThis(i18nc("@info:whatsthis", "Execute the specified command now"));
0839     mTryButton->setToolTip(i18nc("@info:tooltip", "Execute the specified command now"));
0840 
0841     mCmdEdit = new CommandEdit(parent);
0842     connect(mCmdEdit, &CommandEdit::scriptToggled, this, &EditCommandAlarmDlg::slotCmdScriptToggled);
0843     connect(mCmdEdit, &CommandEdit::changed, this, &EditCommandAlarmDlg::contentsChanged);
0844     frameLayout->addWidget(mCmdEdit);
0845 
0846     mCmdDontShowError = new CheckBox(i18nc("@option:check", "Do not notify errors"), parent);
0847     mCmdDontShowError->setWhatsThis(i18nc("@info:whatsthis", "Do not show error message if the command fails."));
0848     frameLayout->addWidget(mCmdDontShowError, 0, Qt::AlignLeft);
0849     connect(mCmdDontShowError, &CheckBox::toggled, this, &EditCommandAlarmDlg::contentsChanged);
0850 
0851     // What to do with command output
0852 
0853     mCmdOutputBox = new QGroupBox(i18nc("@title:group", "Command Output"), parent);
0854     frameLayout->addWidget(mCmdOutputBox);
0855     auto vlayout = new QVBoxLayout(mCmdOutputBox);
0856     mCmdOutputGroup = new ButtonGroup(mCmdOutputBox);
0857     connect(mCmdOutputGroup, &ButtonGroup::buttonSet, this, &EditCommandAlarmDlg::contentsChanged);
0858 
0859     // Execute in terminal window
0860     mCmdExecInTerm = new RadioButton(i18n_radio_ExecInTermWindow(), mCmdOutputBox);
0861     mCmdExecInTerm->setWhatsThis(i18nc("@info:whatsthis", "Check to execute the command in a terminal window"));
0862     mCmdOutputGroup->addButton(mCmdExecInTerm, Preferences::Log_Terminal);
0863     vlayout->addWidget(mCmdExecInTerm, 0, Qt::AlignLeft);
0864 
0865     // Log file name edit box
0866     QWidget* box = new QWidget(mCmdOutputBox);
0867     auto boxHLayout = new QHBoxLayout(box);
0868     boxHLayout->setContentsMargins(0, 0, 0, 0);
0869     boxHLayout->setSpacing(0);
0870     (new QWidget(box))->setFixedWidth(mCmdExecInTerm->style()->pixelMetric(QStyle::PM_ExclusiveIndicatorWidth));   // indent the edit box
0871     mCmdLogFileEdit = new LineEdit(LineEdit::Type::Url, box);
0872     boxHLayout->addWidget(mCmdLogFileEdit);
0873     mCmdLogFileEdit->setAcceptDrops(true);
0874     mCmdLogFileEdit->setWhatsThis(i18nc("@info:whatsthis", "Enter the name or path of the log file."));
0875     connect(mCmdLogFileEdit, &LineEdit::textChanged, this, &EditCommandAlarmDlg::contentsChanged);
0876 
0877     // Log file browse button.
0878     // The file browser dialog is activated by the PickLogFileRadio class.
0879     auto browseButton = new QPushButton(box);
0880     boxHLayout->addWidget(browseButton);
0881     browseButton->setIcon(QIcon::fromTheme(QStringLiteral("document-open")));
0882     browseButton->setToolTip(i18nc("@info:tooltip", "Choose a file"));
0883     browseButton->setWhatsThis(i18nc("@info:whatsthis", "Select a log file."));
0884 
0885     // Log output to file
0886     mCmdLogToFile = new PickLogFileRadio(browseButton, mCmdLogFileEdit, i18nc("@option:radio", "Log to file"), mCmdOutputGroup, mCmdOutputBox);
0887     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."));
0888     connect(mCmdLogToFile, &PickLogFileRadio::fileChanged, this, &EditCommandAlarmDlg::contentsChanged);
0889     mCmdOutputGroup->addButton(mCmdLogToFile, Preferences::Log_File);
0890     vlayout->addWidget(mCmdLogToFile, 0, Qt::AlignLeft);
0891     vlayout->addWidget(box);
0892 
0893     // Discard output
0894     mCmdDiscardOutput = new RadioButton(i18nc("@option:radio", "Discard"), mCmdOutputBox);
0895     mCmdDiscardOutput->setWhatsThis(i18nc("@info:whatsthis", "Check to discard command output."));
0896     mCmdOutputGroup->addButton(mCmdDiscardOutput, Preferences::Log_Discard);
0897     vlayout->addWidget(mCmdDiscardOutput, 0, Qt::AlignLeft);
0898 
0899     // Top-adjust the controls
0900     mCmdPadding = new QWidget(parent);
0901     auto hlayout = new QHBoxLayout(mCmdPadding);
0902     hlayout->setContentsMargins(0, 0, 0, 0);
0903     hlayout->setSpacing(0);
0904     frameLayout->addWidget(mCmdPadding);
0905     frameLayout->setStretchFactor(mCmdPadding, 1);
0906 }
0907 
0908 /******************************************************************************
0909 * Initialise the dialog controls from the specified event.
0910 */
0911 void EditCommandAlarmDlg::type_initValues(const KAEvent& event)
0912 {
0913     if (event.isValid())
0914     {
0915         // Set the values to those for the specified event
0916         RadioButton* logType = event.commandXterm()       ? mCmdExecInTerm
0917                              : !event.logFile().isEmpty() ? mCmdLogToFile
0918                              :                               mCmdDiscardOutput;
0919         if (logType == mCmdLogToFile)
0920             mCmdLogFileEdit->setText(event.logFile());    // set file name before setting radio button
0921         logType->setChecked(true);
0922         mCmdDontShowError->setChecked(event.commandHideError());
0923     }
0924     else
0925     {
0926         // Set the values to their defaults
0927         mCmdEdit->setScript(Preferences::defaultCmdScript());
0928         mCmdLogFileEdit->setText(Preferences::defaultCmdLogFile());    // set file name before setting radio button
0929         mCmdOutputGroup->setButton(Preferences::defaultCmdLogType());
0930         mCmdDontShowError->setChecked(false);
0931     }
0932     slotCmdScriptToggled(mCmdEdit->isScript());
0933 }
0934 
0935 /******************************************************************************
0936 * Called when the More/Less Options button is clicked.
0937 * Show/hide the optional options.
0938 */
0939 void EditCommandAlarmDlg::type_showOptions(bool more)
0940 {
0941     if (more)
0942         mCmdOutputBox->show();
0943     else
0944         mCmdOutputBox->hide();
0945 }
0946 
0947 /******************************************************************************
0948 * Set the dialog's action and the action's text.
0949 */
0950 void EditCommandAlarmDlg::setAction(KAEvent::SubAction action, const AlarmText& alarmText)
0951 {
0952     Q_UNUSED(action);
0953     Q_ASSERT(action == KAEvent::SubAction::Command);
0954     mCmdEdit->setText(alarmText);
0955 }
0956 
0957 /******************************************************************************
0958 * Set the read-only status of all non-template controls.
0959 */
0960 void EditCommandAlarmDlg::setReadOnly(bool readOnly)
0961 {
0962     if (!isTemplate()  &&  !ShellProcess::authorised())
0963         readOnly = true;     // don't allow editing of existing command alarms in kiosk mode
0964     mCmdEdit->setReadOnly(readOnly);
0965     mCmdDontShowError->setReadOnly(readOnly);
0966     mCmdExecInTerm->setReadOnly(readOnly);
0967     mCmdLogToFile->setReadOnly(readOnly);
0968     mCmdDiscardOutput->setReadOnly(readOnly);
0969     EditAlarmDlg::setReadOnly(readOnly);
0970 }
0971 
0972 /******************************************************************************
0973 * Save the state of all controls.
0974 */
0975 void EditCommandAlarmDlg::saveState(const KAEvent* event)
0976 {
0977     EditAlarmDlg::saveState(event);
0978     mSavedCmdScript        = mCmdEdit->isScript();
0979     mSavedCmdDontShowError = mCmdDontShowError->isChecked();
0980     mSavedCmdOutputRadio   = mCmdOutputGroup->checkedButton();
0981     mSavedCmdLogFile       = mCmdLogFileEdit->text();
0982 }
0983 
0984 /******************************************************************************
0985 * Check whether any of the controls has changed state since the dialog was
0986 * first displayed.
0987 * Reply = true if any controls have changed, or if it's a new event.
0988 *       = false if no controls have changed.
0989 */
0990 bool EditCommandAlarmDlg::type_stateChanged() const
0991 {
0992     if (mSavedCmdScript        != mCmdEdit->isScript()
0993     ||  mSavedCmdOutputRadio   != mCmdOutputGroup->checkedButton()
0994     ||  mSavedCmdDontShowError != mCmdDontShowError->isChecked())
0995         return true;
0996     if (mCmdOutputGroup->checkedButton() == mCmdLogToFile)
0997     {
0998         if (mSavedCmdLogFile != mCmdLogFileEdit->text())
0999             return true;
1000     }
1001     return false;
1002 }
1003 
1004 /******************************************************************************
1005 * Extract the data in the dialog specific to the alarm type and set up a
1006 * KAEvent from it.
1007 */
1008 void EditCommandAlarmDlg::type_setEvent(KAEvent& event, const KADateTime& dt, const QString& name, const QString& text, int lateCancel, bool trial)
1009 {
1010     Q_UNUSED(trial);
1011     event = KAEvent(dt, name, text, QColor(), QColor(), QFont(), KAEvent::SubAction::Command, lateCancel, getAlarmFlags());
1012     if (mCmdOutputGroup->checkedButton() == mCmdLogToFile)
1013         event.setLogFile(mCmdLogFileEdit->text());
1014 }
1015 
1016 /******************************************************************************
1017 * Get the currently specified alarm flag bits.
1018 */
1019 KAEvent::Flags EditCommandAlarmDlg::getAlarmFlags() const
1020 {
1021     KAEvent::Flags flags = EditAlarmDlg::getAlarmFlags();
1022     if (mCmdEdit->isScript())                               flags |= KAEvent::SCRIPT;
1023     if (mCmdOutputGroup->checkedButton() == mCmdExecInTerm) flags |= KAEvent::EXEC_IN_XTERM;
1024     if (mCmdDontShowError->isChecked())                     flags |= KAEvent::DONT_SHOW_ERROR;
1025     return flags;
1026 }
1027 
1028 /******************************************************************************
1029 * Validate and convert command alarm data.
1030 */
1031 bool EditCommandAlarmDlg::type_validate(bool trial)
1032 {
1033     Q_UNUSED(trial);
1034     if (mCmdOutputGroup->checkedButton() == mCmdLogToFile)
1035     {
1036         // Validate the log file name
1037         const QString file = mCmdLogFileEdit->text();
1038         const QFileInfo info(file);
1039         QDir::setCurrent(QDir::homePath());
1040         bool err = file.isEmpty()  ||  info.isDir();
1041         if (!err)
1042         {
1043             if (info.exists())
1044             {
1045                 err = !info.isWritable();
1046             }
1047             else
1048             {
1049                 const QFileInfo dirinfo(info.absolutePath());    // get absolute directory path
1050                 err = (!dirinfo.isDir()  ||  !dirinfo.isWritable());
1051             }
1052         }
1053         if (err)
1054         {
1055             showMainPage();
1056             mCmdLogFileEdit->setFocus();
1057             KAMessageBox::error(this, i18nc("@info", "Log file must be the name or path of a local file, with write permission."));
1058             return false;
1059         }
1060         // Convert the log file to an absolute path
1061         mCmdLogFileEdit->setText(info.absoluteFilePath());
1062     }
1063     else if (mCmdOutputGroup->checkedButton() == mCmdExecInTerm)
1064     {
1065         if (Preferences::cmdXTermCommand().isEmpty())
1066         {
1067             if (KAMessageBox::warningContinueCancel(this, xi18nc("@info", "<para>No terminal is selected for command alarms.</para>"
1068                                                                  "<para>Please set it in the <application>KAlarm</application> Configuration dialog.</para>"))
1069                     != KMessageBox::Continue)
1070                 return false;
1071         }
1072     }
1073     return true;
1074 }
1075 
1076 /******************************************************************************
1077 * Called when the Try action has been executed.
1078 * Tell the user the result of the Try action.
1079 */
1080 void EditCommandAlarmDlg::type_executedTry(const QString& text, void* result)
1081 {
1082     auto* proc = (ShellProcess*)result;
1083     if (proc  &&  proc != (void*)-1
1084     &&  mCmdOutputGroup->checkedButton() != mCmdExecInTerm)
1085     {
1086         theApp()->commandMessage(proc, this);
1087         KAMessageBox::information(this, xi18nc("@info", "Command executed: <icode>%1</icode>", text));
1088         theApp()->commandMessage(proc, nullptr);
1089     }
1090 }
1091 
1092 /******************************************************************************
1093 * Called when one of the command type radio buttons is clicked,
1094 * to display the appropriate edit field.
1095 */
1096 void EditCommandAlarmDlg::slotCmdScriptToggled(bool on)
1097 {
1098     if (on)
1099         mCmdPadding->hide();
1100     else
1101         mCmdPadding->show();
1102 }
1103 
1104 /******************************************************************************
1105 * Clean up the alarm text.
1106 */
1107 bool EditCommandAlarmDlg::checkText(QString& result, bool showErrorMessage) const
1108 {
1109     result = mCmdEdit->text(const_cast<EditCommandAlarmDlg*>(this), showErrorMessage);
1110     if (result.isEmpty())
1111         return false;
1112     return true;
1113 }
1114 
1115 
1116 /*=============================================================================
1117 = Class EditEmailAlarmDlg
1118 = Dialog to edit email alarms.
1119 =============================================================================*/
1120 
1121 QString EditEmailAlarmDlg::i18n_chk_CopyEmailToSelf()    { return i18nc("@option:check", "Copy email to self"); }
1122 
1123 
1124 /******************************************************************************
1125 * Constructor.
1126 * Parameters:
1127 *   Template = true to edit/create an alarm template
1128 *            = false to edit/create an alarm.
1129 *   event   != to initialise the dialog to show the specified event's data.
1130 */
1131 EditEmailAlarmDlg::EditEmailAlarmDlg(bool Template, QWidget* parent, GetResourceType getResource)
1132     : EditAlarmDlg(Template, KAEvent::SubAction::Email, parent, getResource)
1133 {
1134     qCDebug(KALARM_LOG) << "EditEmailAlarmDlg: New";
1135     init(KAEvent());
1136 }
1137 
1138 EditEmailAlarmDlg::EditEmailAlarmDlg(bool Template, const KAEvent& event, bool newAlarm, QWidget* parent,
1139                                      GetResourceType getResource, bool readOnly)
1140     : EditAlarmDlg(Template, event, newAlarm, parent, getResource, readOnly)
1141 {
1142     qCDebug(KALARM_LOG) << "EditEmailAlarmDlg: Event.id()";
1143     init(event);
1144 }
1145 
1146 /******************************************************************************
1147 * Return the window caption.
1148 */
1149 QString EditEmailAlarmDlg::type_caption() const
1150 {
1151     return isTemplate() ? (isNewAlarm() ? i18nc("@title:window", "New Email Alarm Template") : i18nc("@title:window", "Edit Email Alarm Template"))
1152                         : (isNewAlarm() ? i18nc("@title:window", "New Email Alarm") : i18nc("@title:window", "Edit Email Alarm"));
1153 }
1154 
1155 /******************************************************************************
1156 * Set up the email alarm dialog controls.
1157 */
1158 void EditEmailAlarmDlg::type_init(QWidget* parent, QVBoxLayout* frameLayout)
1159 {
1160     mTryButton->setWhatsThis(i18nc("@info:whatsthis", "Send the email to the specified addressees now"));
1161     mTryButton->setToolTip(i18nc("@info:tooltip", "Send the email now"));
1162 
1163     auto grid = new QGridLayout();
1164     grid->setContentsMargins(0, 0, 0, 0);
1165     grid->setColumnStretch(1, 1);
1166     frameLayout->addLayout(grid);
1167 
1168     mEmailFromList = nullptr;
1169     if (Preferences::emailFrom() == Preferences::MAIL_FROM_KMAIL)
1170     {
1171         // Email sender identity
1172         QLabel* label = new QLabel(i18nc("@label:listbox 'From' email address", "From:"), parent);
1173         grid->addWidget(label, 0, 0);
1174 
1175         mEmailFromList = new EmailIdCombo(Identities::identityManager(), parent);
1176         mEmailFromList->setMinimumSize(mEmailFromList->sizeHint());
1177         label->setBuddy(mEmailFromList);
1178         mEmailFromList->setWhatsThis(i18nc("@info:whatsthis", "Your email identity, used to identify you as the sender when sending email alarms."));
1179         connect(mEmailFromList, &EmailIdCombo::identityChanged, this, &EditEmailAlarmDlg::contentsChanged);
1180         grid->addWidget(mEmailFromList, 0, 1, 1, 2);
1181     }
1182 
1183     // Email recipients
1184     QLabel* label = new QLabel(i18nc("@label:textbox Email addressee", "To:"), parent);
1185     grid->addWidget(label, 1, 0);
1186 
1187     mEmailToEdit = new LineEdit(LineEdit::Type::Emails, parent);
1188     mEmailToEdit->setMinimumSize(mEmailToEdit->sizeHint());
1189     mEmailToEdit->setWhatsThis(i18nc("@info:whatsthis", "Enter the addresses of the email recipients. Separate multiple addresses by "
1190                                     "commas or semicolons."));
1191     connect(mEmailToEdit, &LineEdit::textChanged, this, &EditEmailAlarmDlg::contentsChanged);
1192 
1193     if (Preferences::useAkonadi())
1194     {
1195         grid->addWidget(mEmailToEdit, 1, 1);
1196 
1197         mEmailAddressButton = new QPushButton(parent);
1198         mEmailAddressButton->setIcon(QIcon::fromTheme(QStringLiteral("help-contents")));
1199         connect(mEmailAddressButton, &QPushButton::clicked, this, &EditEmailAlarmDlg::openAddressBook);
1200         mEmailAddressButton->setToolTip(i18nc("@info:tooltip", "Open address book"));
1201         mEmailAddressButton->setWhatsThis(i18nc("@info:whatsthis", "Select email addresses from your address book."));
1202         grid->addWidget(mEmailAddressButton, 1, 2);
1203     }
1204     else
1205         grid->addWidget(mEmailToEdit, 1, 1, 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 */
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 */
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 */
1295 void EditEmailAlarmDlg::setAction(KAEvent::SubAction action, const AlarmText& alarmText)
1296 {
1297     Q_UNUSED(action);
1298     Q_ASSERT(action == KAEvent::SubAction::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 */
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 }
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 */
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         if (mEmailAddressButton)
1346             mEmailAddressButton->hide();
1347         mEmailAddAttachButton->hide();
1348         mEmailRemoveButton->hide();
1349     }
1350     else
1351     {
1352         if (mEmailAddressButton)
1353             mEmailAddressButton->show();
1354         mEmailAddAttachButton->show();
1355         mEmailRemoveButton->show();
1356     }
1357     EditAlarmDlg::setReadOnly(readOnly);
1358 }
1359 
1360 /******************************************************************************
1361 * Save the state of all controls.
1362 */
1363 void EditEmailAlarmDlg::saveState(const KAEvent* event)
1364 {
1365     EditAlarmDlg::saveState(event);
1366     if (mEmailFromList)
1367         mSavedEmailFrom = mEmailFromList->currentIdentityName();
1368     mSavedEmailTo      = mEmailToEdit->text();
1369     mSavedEmailSubject = mEmailSubjectEdit->text();
1370     mSavedEmailAttach.clear();
1371     for (int i = 0, end = mEmailAttachList->count();  i < end;  ++i)
1372         mSavedEmailAttach += mEmailAttachList->itemText(i);
1373     mSavedEmailBcc     = mEmailBcc->isChecked();
1374 }
1375 
1376 /******************************************************************************
1377 * Check whether any of the controls has changed state since the dialog was
1378 * first displayed.
1379 * Reply = true if any controls have changed, or if it's a new event.
1380 *       = false if no controls have changed.
1381 */
1382 bool EditEmailAlarmDlg::type_stateChanged() const
1383 {
1384     int count = mEmailAttachList->count();
1385     QStringList emailAttach;
1386     emailAttach.reserve(count);
1387     for (int i = 0;  i < count;  ++i)
1388         emailAttach += mEmailAttachList->itemText(i);
1389     if ((mEmailFromList  &&  mSavedEmailFrom != mEmailFromList->currentIdentityName())
1390     ||  mSavedEmailTo      != mEmailToEdit->text()
1391     ||  mSavedEmailSubject != mEmailSubjectEdit->text()
1392     ||  mSavedEmailAttach  != emailAttach
1393     ||  mSavedEmailBcc     != mEmailBcc->isChecked())
1394         return true;
1395     return false;
1396 }
1397 
1398 /******************************************************************************
1399 * Extract the data in the dialog specific to the alarm type and set up a
1400 * KAEvent from it.
1401 */
1402 void EditEmailAlarmDlg::type_setEvent(KAEvent& event, const KADateTime& dt, const QString& name, const QString& text, int lateCancel, bool trial)
1403 {
1404     Q_UNUSED(trial);
1405     event = KAEvent(dt, name, text, QColor(), QColor(), QFont(), KAEvent::SubAction::Email, lateCancel, getAlarmFlags());
1406     const uint from = mEmailFromList ? mEmailFromList->currentIdentity() : 0;
1407     event.setEmail(from, mEmailAddresses, mEmailSubjectEdit->text(), mEmailAttachments);
1408 }
1409 
1410 /******************************************************************************
1411 * Get the currently specified alarm flag bits.
1412 */
1413 KAEvent::Flags EditEmailAlarmDlg::getAlarmFlags() const
1414 {
1415     KAEvent::Flags flags = EditAlarmDlg::getAlarmFlags();
1416     if (mEmailBcc->isChecked()) flags |= KAEvent::EMAIL_BCC;
1417     return flags;
1418 }
1419 
1420 /******************************************************************************
1421 * Convert the email addresses to a list, and validate them. Convert the email
1422 * attachments to a list.
1423 */
1424 bool EditEmailAlarmDlg::type_validate(bool trial)
1425 {
1426     const QString addrs = mEmailToEdit->text();
1427     if (addrs.isEmpty())
1428         mEmailAddresses.clear();
1429     else
1430     {
1431         const QString bad = KAMail::convertAddresses(addrs, mEmailAddresses);
1432         if (!bad.isEmpty())
1433         {
1434             mEmailToEdit->setFocus();
1435             KAMessageBox::error(this, xi18nc("@info", "Invalid email address: <email>%1</email>", bad));
1436             return false;
1437         }
1438     }
1439     if (mEmailAddresses.isEmpty())
1440     {
1441         mEmailToEdit->setFocus();
1442         KAMessageBox::error(this, i18nc("@info", "No email address specified"));
1443         return false;
1444     }
1445 
1446     mEmailAttachments.clear();
1447     for (int i = 0, end = mEmailAttachList->count();  i < end;  ++i)
1448     {
1449         QString att = mEmailAttachList->itemText(i);
1450         switch (KAMail::checkAttachment(att))
1451         {
1452             case 1:
1453                 mEmailAttachments.append(att);
1454                 break;
1455             case 0:
1456                 break;      // empty
1457             case -1:
1458                 mEmailAttachList->setFocus();
1459                 KAMessageBox::error(this, xi18nc("@info", "Invalid email attachment: <filename>%1</filename>", att));
1460                 return false;
1461         }
1462     }
1463     if (trial  &&  KAMessageBox::warningContinueCancel(this, i18nc("@info", "Do you really want to send the email now to the specified recipient(s)?"),
1464                                                        i18nc("@action:button", "Confirm Email"), KGuiItem(i18nc("@action:button", "Send"))) != KMessageBox::Continue)
1465         return false;
1466     return true;
1467 }
1468 
1469 /******************************************************************************
1470 * Called when the Try action is about to be executed.
1471 */
1472 void EditEmailAlarmDlg::type_aboutToTry()
1473 {
1474     // Disconnect any previous connections, to prevent multiple messages being output
1475     disconnect(theApp(), &KAlarmApp::execAlarmSuccess, this, &EditEmailAlarmDlg::slotTrySuccess);
1476     connect(theApp(), &KAlarmApp::execAlarmSuccess, this, &EditEmailAlarmDlg::slotTrySuccess);
1477 }
1478 
1479 /******************************************************************************
1480 * Tell the user the result of the Try action.
1481 */
1482 void EditEmailAlarmDlg::slotTrySuccess()
1483 {
1484     disconnect(theApp(), &KAlarmApp::execAlarmSuccess, this, &EditEmailAlarmDlg::slotTrySuccess);
1485     QString msg;
1486     QString to = KAEvent::joinEmailAddresses(mEmailAddresses, QStringLiteral("<nl/>"));
1487     to.replace(QLatin1Char('<'), QStringLiteral("&lt;"));
1488     to.replace(QLatin1Char('>'), QStringLiteral("&gt;"));
1489     if (mEmailBcc->isChecked())
1490         msg = QLatin1String("<qt>") + xi18nc("@info", "Email sent to:<nl/>%1<nl/>Bcc: <email>%2</email>",
1491                     to, Preferences::emailBccAddress()) + QLatin1String("</qt>");
1492     else
1493         msg = QLatin1String("<qt>") + xi18nc("@info", "Email sent to:<nl/>%1", to) + QLatin1String("</qt>");
1494     KAMessageBox::information(this, msg);
1495 }
1496 
1497 /******************************************************************************
1498 * Get a selection from the Address Book.
1499 */
1500 void EditEmailAlarmDlg::openAddressBook()
1501 {
1502     AkonadiPlugin* akonadiPlugin = Preferences::akonadiPlugin();
1503     if (akonadiPlugin)
1504     {
1505         Person person;
1506         if (!akonadiPlugin->getAddressBookSelection(person, this))
1507             return;
1508         QString addrs = mEmailToEdit->text().trimmed();
1509         if (!addrs.isEmpty())
1510             addrs += QLatin1String(", ");
1511         addrs += person.fullName();
1512         mEmailToEdit->setText(addrs);
1513     }
1514 }
1515 
1516 /******************************************************************************
1517 * Select a file to attach to the email.
1518 */
1519 void EditEmailAlarmDlg::slotAddAttachment()
1520 {
1521     QString file;
1522     if (File::browseFile(file, i18nc("@title:window", "Choose File to Attach"),
1523                          mAttachDefaultDir, QString(), true, this))
1524     {
1525         if (!file.isEmpty())
1526         {
1527             mEmailAttachList->addItem(file);
1528             mEmailAttachList->setCurrentIndex(mEmailAttachList->count() - 1);   // select the new item
1529             mEmailRemoveButton->setEnabled(true);
1530             mEmailAttachList->setEnabled(true);
1531             contentsChanged();
1532         }
1533     }
1534 }
1535 
1536 /******************************************************************************
1537 * Remove the currently selected attachment from the email.
1538 */
1539 void EditEmailAlarmDlg::slotRemoveAttachment()
1540 {
1541     const int item = mEmailAttachList->currentIndex();
1542     mEmailAttachList->removeItem(item);
1543     const int count = mEmailAttachList->count();
1544     if (item >= count)
1545         mEmailAttachList->setCurrentIndex(count - 1);
1546     if (!count)
1547     {
1548         mEmailRemoveButton->setEnabled(false);
1549         mEmailAttachList->setEnabled(false);
1550     }
1551     contentsChanged();
1552 }
1553 
1554 /******************************************************************************
1555 * Clean up the alarm text.
1556 */
1557 bool EditEmailAlarmDlg::checkText(QString& result, bool showErrorMessage) const
1558 {
1559     Q_UNUSED(showErrorMessage);
1560     result = mEmailMessageEdit->toPlainText();
1561     return true;
1562 }
1563 
1564 
1565 /*=============================================================================
1566 = Class EditAudioAlarmDlg
1567 = Dialog to edit audio alarms with no display window.
1568 =============================================================================*/
1569 
1570 /******************************************************************************
1571 * Constructor.
1572 * Parameters:
1573 *   Template = true to edit/create an alarm template
1574 *            = false to edit/create an alarm.
1575 *   event   != to initialise the dialog to show the specified event's data.
1576 */
1577 EditAudioAlarmDlg::EditAudioAlarmDlg(bool Template, QWidget* parent, GetResourceType getResource)
1578     : EditAlarmDlg(Template, KAEvent::SubAction::Audio, parent, getResource)
1579 {
1580     qCDebug(KALARM_LOG) << "EditAudioAlarmDlg: New";
1581     init(KAEvent());
1582 }
1583 
1584 EditAudioAlarmDlg::EditAudioAlarmDlg(bool Template, const KAEvent& event, bool newAlarm, QWidget* parent,
1585                                      GetResourceType getResource, bool readOnly)
1586     : EditAlarmDlg(Template, event, newAlarm, parent, getResource, readOnly)
1587 {
1588     qCDebug(KALARM_LOG) << "EditAudioAlarmDlg: Event.id()";
1589     init(event);
1590     mTryButton->setEnabled(!MessageDisplay::isAudioPlaying());
1591     connect(theApp(), &KAlarmApp::audioPlaying, this, &EditAudioAlarmDlg::slotAudioPlaying);
1592 }
1593 
1594 EditAudioAlarmDlg::~EditAudioAlarmDlg()
1595 {
1596     if (mMessageWindow)
1597         MessageDisplay::stopAudio();
1598 }
1599 
1600 /******************************************************************************
1601 * Return the window caption.
1602 */
1603 QString EditAudioAlarmDlg::type_caption() const
1604 {
1605     return isTemplate() ? (isNewAlarm() ? i18nc("@title:window", "New Audio Alarm Template") : i18nc("@title:window", "Edit Audio Alarm Template"))
1606                         : (isNewAlarm() ? i18nc("@title:window", "New Audio Alarm") : i18nc("@title:window", "Edit Audio Alarm"));
1607 }
1608 
1609 /******************************************************************************
1610 * Set up the dialog controls common to display alarms.
1611 */
1612 void EditAudioAlarmDlg::type_init(QWidget* parent, QVBoxLayout* frameLayout)
1613 {
1614     mTryButton->setWhatsThis(i18nc("@info:whatsthis", "Play the audio file now"));
1615     mTryButton->setToolTip(i18nc("@info:tooltip", "Play the audio file now"));
1616     // File name edit box
1617     const QString repWhatsThis = i18nc("@info:whatsthis", "If checked, the sound file will be played repeatedly until %1 is clicked.", KAlarm::i18n_act_StopPlay());
1618     mSoundConfig = new SoundWidget(false, repWhatsThis, parent);
1619     if (isTemplate())
1620         mSoundConfig->setAllowEmptyFile();
1621     connect(mSoundConfig, &SoundWidget::changed, this, &EditAudioAlarmDlg::contentsChanged);
1622     frameLayout->addWidget(mSoundConfig);
1623 
1624     // Top-adjust the controls
1625     mPadding = new QWidget(parent);
1626     auto hlayout = new QHBoxLayout(mPadding);
1627     hlayout->setContentsMargins(0, 0, 0, 0);
1628     hlayout->setSpacing(0);
1629     frameLayout->addWidget(mPadding);
1630     frameLayout->setStretchFactor(mPadding, 1);
1631 }
1632 
1633 /******************************************************************************
1634 * Initialise the dialog controls from the specified event.
1635 */
1636 void EditAudioAlarmDlg::type_initValues(const KAEvent& event)
1637 {
1638     if (event.isValid())
1639     {
1640         mSoundConfig->set(event.audioFile(), event.soundVolume(), event.fadeVolume(), event.fadeSeconds(),
1641                           (event.flags() & KAEvent::REPEAT_SOUND) ? event.repeatSoundPause() : -1);
1642     }
1643     else
1644     {
1645         // Set the values to their defaults
1646         mSoundConfig->set(Preferences::defaultSoundFile(), Preferences::defaultSoundVolume(),
1647                           -1, 0, (Preferences::defaultSoundRepeat() ? 0 : -1));
1648     }
1649 }
1650 
1651 /******************************************************************************
1652 * Initialise various values in the New Alarm dialogue.
1653 */
1654 void EditAudioAlarmDlg::setAudio(const QString& file, float volume)
1655 {
1656     mSoundConfig->set(file, volume);
1657 }
1658 
1659 /******************************************************************************
1660 * Set the dialog's action and the action's text.
1661 */
1662 void EditAudioAlarmDlg::setAction(KAEvent::SubAction action, const AlarmText& alarmText)
1663 {
1664     Q_UNUSED(action);
1665     Q_ASSERT(action == KAEvent::SubAction::Audio);
1666     mSoundConfig->set(alarmText.displayText(), Preferences::defaultSoundVolume());
1667 }
1668 
1669 /******************************************************************************
1670 * Set the read-only status of all non-template controls.
1671 */
1672 void EditAudioAlarmDlg::setReadOnly(bool readOnly)
1673 {
1674     mSoundConfig->setReadOnly(readOnly);
1675     EditAlarmDlg::setReadOnly(readOnly);
1676 }
1677 
1678 /******************************************************************************
1679 * Save the state of all controls.
1680 */
1681 void EditAudioAlarmDlg::saveState(const KAEvent* event)
1682 {
1683     EditAlarmDlg::saveState(event);
1684     mSavedFile   = mSoundConfig->fileName();
1685     mSoundConfig->getVolume(mSavedVolume, mSavedFadeVolume, mSavedFadeSeconds);
1686     mSavedRepeatPause = mSoundConfig->repeatPause();
1687 }
1688 
1689 /******************************************************************************
1690 * Check whether any of the controls has changed state since the dialog was
1691 * first displayed.
1692 * Reply = true if any controls have changed, or if it's a new event.
1693 *       = false if no controls have changed.
1694 */
1695 bool EditAudioAlarmDlg::type_stateChanged() const
1696 {
1697     if (mSavedFile != mSoundConfig->fileName())
1698         return true;
1699     if (!mSavedFile.isEmpty()  ||  isTemplate())
1700     {
1701         float volume, fadeVolume;
1702         int   fadeSecs;
1703         mSoundConfig->getVolume(volume, fadeVolume, fadeSecs);
1704         if (mSavedRepeatPause != mSoundConfig->repeatPause()
1705         ||  mSavedVolume      != volume
1706         ||  mSavedFadeVolume  != fadeVolume
1707         ||  mSavedFadeSeconds != fadeSecs)
1708             return true;
1709     }
1710     return false;
1711 }
1712 
1713 /******************************************************************************
1714 * Extract the data in the dialog specific to the alarm type and set up a
1715 * KAEvent from it.
1716 */
1717 void EditAudioAlarmDlg::type_setEvent(KAEvent& event, const KADateTime& dt, const QString& name, const QString& text, int lateCancel, bool trial)
1718 {
1719     Q_UNUSED(text);
1720     Q_UNUSED(trial);
1721     event = KAEvent(dt, name, QString(), QColor(), QColor(), QFont(), KAEvent::SubAction::Audio, lateCancel, getAlarmFlags());
1722     float volume, fadeVolume;
1723     int   fadeSecs;
1724     mSoundConfig->getVolume(volume, fadeVolume, fadeSecs);
1725     const int repeatPause = mSoundConfig->repeatPause();
1726     QUrl url;
1727     mSoundConfig->file(url, false);
1728     event.setAudioFile(url.toString(), volume, fadeVolume, fadeSecs, repeatPause, isTemplate());
1729 }
1730 
1731 /******************************************************************************
1732 * Get the currently specified alarm flag bits.
1733 */
1734 KAEvent::Flags EditAudioAlarmDlg::getAlarmFlags() const
1735 {
1736     KAEvent::Flags flags = EditAlarmDlg::getAlarmFlags();
1737     if (mSoundConfig->repeatPause() >= 0) flags |= KAEvent::REPEAT_SOUND;
1738     return flags;
1739 }
1740 
1741 /******************************************************************************
1742 * Check whether the file name is valid.
1743 */
1744 bool EditAudioAlarmDlg::checkText(QString& result, bool showErrorMessage) const
1745 {
1746     QUrl url;
1747     if (!mSoundConfig->file(url, showErrorMessage))
1748     {
1749         result.clear();
1750         return false;
1751     }
1752     result = url.isLocalFile() ? url.toLocalFile() : url.toString();
1753     return true;
1754 }
1755 
1756 /******************************************************************************
1757 * Called when the Try button is clicked.
1758 * If the audio file is currently playing (as a result of previously clicking
1759 * the Try button), cancel playback. Otherwise, play the audio file.
1760 */
1761 void EditAudioAlarmDlg::slotTry()
1762 {
1763     if (!MessageDisplay::isAudioPlaying())
1764         EditAlarmDlg::slotTry();   // play the audio file
1765     else if (mMessageWindow)
1766     {
1767         MessageDisplay::stopAudio();
1768         mMessageWindow = nullptr;
1769     }
1770 }
1771 
1772 /******************************************************************************
1773 * Called when the Try action has been executed.
1774 */
1775 void EditAudioAlarmDlg::type_executedTry(const QString&, void* result)
1776 {
1777     mMessageWindow = (MessageWindow*)result;    // note which MessageWindow controls the audio playback
1778     if (mMessageWindow)
1779     {
1780         slotAudioPlaying(true);
1781         connect(mMessageWindow, &QObject::destroyed, this, &EditAudioAlarmDlg::audioWinDestroyed);
1782     }
1783 }
1784 
1785 /******************************************************************************
1786 * Called when audio playing starts or stops.
1787 * Enable/disable/toggle the Try button.
1788 */
1789 void EditAudioAlarmDlg::slotAudioPlaying(bool playing)
1790 {
1791     if (!playing)
1792     {
1793         // Nothing is playing, so enable the Try button
1794         mTryButton->setEnabled(true);
1795         mTryButton->setCheckable(false);
1796         mTryButton->setChecked(false);
1797         mMessageWindow = nullptr;
1798     }
1799     else if (mMessageWindow)
1800     {
1801         // The test sound file is playing, so enable the Try button and depress it
1802         mTryButton->setEnabled(true);
1803         mTryButton->setCheckable(true);
1804         mTryButton->setChecked(true);
1805     }
1806     else
1807     {
1808         // An alarm is playing, so disable the Try button
1809         mTryButton->setEnabled(false);
1810         mTryButton->setCheckable(false);
1811         mTryButton->setChecked(false);
1812     }
1813 }
1814 
1815 
1816 /*=============================================================================
1817 = Class CommandEdit
1818 = A widget to allow entry of a command or a command script.
1819 =============================================================================*/
1820 CommandEdit::CommandEdit(QWidget* parent)
1821     : QWidget(parent)
1822 {
1823     auto vlayout = new QVBoxLayout(this);
1824     vlayout->setContentsMargins(0, 0, 0, 0);
1825     mTypeScript = new CheckBox(EditCommandAlarmDlg::i18n_chk_EnterScript(), this);
1826     mTypeScript->setWhatsThis(i18nc("@info:whatsthis", "Check to enter the contents of a script instead of a shell command line"));
1827     connect(mTypeScript, &CheckBox::toggled, this, &CommandEdit::slotCmdScriptToggled);
1828     connect(mTypeScript, &CheckBox::toggled, this, &CommandEdit::changed);
1829     vlayout->addWidget(mTypeScript, 0, Qt::AlignLeft);
1830 
1831     mCommandEdit = new LineEdit(LineEdit::Type::Url, this);
1832     mCommandEdit->setWhatsThis(i18nc("@info:whatsthis", "Enter a shell command to execute."));
1833     connect(mCommandEdit, &LineEdit::textChanged, this, &CommandEdit::changed);
1834     vlayout->addWidget(mCommandEdit);
1835 
1836     mScriptEdit = new TextEdit(this);
1837     mScriptEdit->setWhatsThis(i18nc("@info:whatsthis", "Enter the contents of a script to execute"));
1838     connect(mScriptEdit, &TextEdit::textChanged, this, &CommandEdit::changed);
1839     vlayout->addWidget(mScriptEdit);
1840 
1841     slotCmdScriptToggled(mTypeScript->isChecked());
1842 }
1843 
1844 /******************************************************************************
1845 * Initialise the widget controls from the specified event.
1846 */
1847 void CommandEdit::setScript(bool script)
1848 {
1849     mTypeScript->setChecked(script);
1850 }
1851 
1852 bool CommandEdit::isScript() const
1853 {
1854     return mTypeScript->isChecked();
1855 }
1856 
1857 /******************************************************************************
1858 * Set the widget's text.
1859 */
1860 void CommandEdit::setText(const AlarmText& alarmText)
1861 {
1862     const QString text = alarmText.displayText();
1863     const bool script = alarmText.isScript();
1864     mTypeScript->setChecked(script);
1865     if (script)
1866         mScriptEdit->setPlainText(text);
1867     else
1868         mCommandEdit->setText(File::pathOrUrl(text));
1869 }
1870 
1871 /******************************************************************************
1872 * Return the widget's text.
1873 * If it's a command line, it must not start with environment variable
1874 * specifications.
1875 * If 'showErrorMessage' is true and the text is empty, an error message is
1876 * displayed.
1877 * Reply = text, or empty if a command line starting with environment vars.
1878 */
1879 QString CommandEdit::text(EditAlarmDlg* dlg, bool showErrorMessage) const
1880 {
1881     QString result;
1882     if (mTypeScript->isChecked())
1883         result = mScriptEdit->toPlainText().trimmed();
1884     else
1885     {
1886         result = mCommandEdit->text().trimmed();
1887         QString params = result;
1888         const QString cmd = ShellProcess::splitCommandLine(params);
1889         if (cmd.contains(QLatin1Char('=')))
1890         {
1891             if (showErrorMessage)
1892                 KAMessageBox::error(dlg, xi18nc("@info", "<para>The command cannot set environment variables:</para><para><icode>%1</icode></para>", cmd));
1893             return {};
1894         }
1895     }
1896     if (showErrorMessage  &&  result.isEmpty())
1897         KAMessageBox::error(dlg, i18nc("@info", "Please enter a command or script to execute"));
1898     return result;
1899 }
1900 
1901 /******************************************************************************
1902 * Set the read-only status of all controls.
1903 */
1904 void CommandEdit::setReadOnly(bool readOnly)
1905 {
1906     mTypeScript->setReadOnly(readOnly);
1907     mCommandEdit->setReadOnly(readOnly);
1908     mScriptEdit->setReadOnly(readOnly);
1909 }
1910 
1911 /******************************************************************************
1912 * Called when one of the command type radio buttons is clicked,
1913 * to display the appropriate edit field.
1914 */
1915 void CommandEdit::slotCmdScriptToggled(bool on)
1916 {
1917     if (on)
1918     {
1919         mCommandEdit->hide();
1920         mScriptEdit->show();
1921         mScriptEdit->setFocus();
1922     }
1923     else
1924     {
1925         mScriptEdit->hide();
1926         mCommandEdit->show();
1927         mCommandEdit->setFocus();
1928     }
1929     Q_EMIT scriptToggled(on);
1930 }
1931 
1932 /******************************************************************************
1933 * Returns the minimum size of the widget.
1934 */
1935 QSize CommandEdit::minimumSizeHint() const
1936 {
1937     const QSize t(mTypeScript->minimumSizeHint());
1938     QSize s(mCommandEdit->minimumSizeHint().expandedTo(mScriptEdit->minimumSizeHint()));
1939     s.setHeight(s.height() + style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing) + t.height());
1940     if (s.width() < t.width())
1941         s.setWidth(t.width());
1942     return s;
1943 }
1944 
1945 
1946 
1947 /*=============================================================================
1948 = Class TextEdit
1949 = A text edit field with a minimum height of 3 text lines.
1950 =============================================================================*/
1951 TextEdit::TextEdit(QWidget* parent)
1952     : KTextEdit(parent)
1953 {
1954     QSize tsize = TextEdit::minimumSizeHint();   // avoid calling virtual method from constructor
1955     tsize.setHeight(fontMetrics().lineSpacing()*13/4 + 2*frameWidth());
1956     setMinimumSize(tsize);
1957 }
1958 
1959 void TextEdit::enableEmailDrop()
1960 {
1961     mEmailDrop = true;
1962     setAcceptDrops(true);   // allow drag-and-drop onto this widget
1963 }
1964 
1965 void TextEdit::dragEnterEvent(QDragEnterEvent* e)
1966 {
1967     if (KCalUtils::ICalDrag::canDecode(e->mimeData()))
1968     {
1969         e->ignore();   // don't accept "text/calendar" objects
1970         return;
1971     }
1972     if (mEmailDrop  &&  DragDrop::mayHaveRFC822(e->mimeData()))
1973     {
1974         e->acceptProposedAction();
1975         return;
1976     }
1977     KTextEdit::dragEnterEvent(e);
1978 }
1979 
1980 void TextEdit::dragMoveEvent(QDragMoveEvent* e)
1981 {
1982     if (mEmailDrop  &&  DragDrop::mayHaveRFC822(e->mimeData()))
1983     {
1984         e->acceptProposedAction();
1985         return;
1986     }
1987     KTextEdit::dragMoveEvent(e);
1988 }
1989 
1990 /******************************************************************************
1991 * Called when an object is dropped on the widget.
1992 */
1993 void TextEdit::dropEvent(QDropEvent* e)
1994 {
1995     const QMimeData* data = e->mimeData();
1996     if (mEmailDrop)
1997     {
1998         AlarmText alarmText;
1999         bool haveEmail = false;
2000         if (DragDrop::dropRFC822(data, alarmText))
2001         {
2002             // Email message(s). Ignore all but the first.
2003             qCDebug(KALARM_LOG) << "TextEdit::dropEvent: email";
2004             haveEmail = true;
2005         }
2006         else
2007         {
2008             QUrl url;
2009             if (KAlarm::dropAkonadiEmail(data, url, alarmText))
2010             {
2011                 // It's an email held in Akonadi
2012                 qCDebug(KALARM_LOG) << "TextEdit::dropEvent: Akonadi email";
2013                 haveEmail = true;
2014             }
2015         }
2016         if (haveEmail)
2017         {
2018             if (!alarmText.isEmpty())
2019                 setPlainText(alarmText.displayText());
2020             return;
2021         }
2022     }
2023     QString text;
2024     if (DragDrop::dropPlainText(data, text))
2025     {
2026         setPlainText(text);
2027         return;
2028     }
2029     KTextEdit::dropEvent(e);
2030 }
2031 
2032 #include "moc_editdlgtypes.cpp"
2033 
2034 // vim: et sw=4: