Warning, file /pim/kalarm/src/editdlg.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002  *  editdlg.cpp  -  dialog to create or modify an alarm or alarm template
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 "editdlg.h"
0010 #include "editdlg_p.h"
0011 #include "editdlgtypes.h"
0012 
0013 #include "alarmtimewidget.h"
0014 #include "deferdlg.h"
0015 #include "kalarmapp.h"
0016 #include "latecancel.h"
0017 #include "preferences.h"
0018 #include "recurrenceedit.h"
0019 #include "reminder.h"
0020 #include "resourcescalendar.h"
0021 #include "resources/resources.h"
0022 #include "lib/autoqpointer.h"
0023 #include "lib/buttongroup.h"
0024 #include "lib/checkbox.h"
0025 #include "lib/config.h"
0026 #include "lib/lineedit.h"
0027 #include "lib/messagebox.h"
0028 #include "lib/packedlayout.h"
0029 #include "lib/radiobutton.h"
0030 #include "lib/stackedwidgets.h"
0031 #include "templatepickdlg.h"
0032 #include "lib/timeedit.h"
0033 #include "lib/timespinbox.h"
0034 #include "config-kalarm.h"
0035 #include "kalarm_debug.h"
0036 
0037 #include <KLocalizedString>
0038 #include <KConfig>
0039 #include <KSharedConfig>
0040 #if ENABLE_X11
0041 #include <KX11Extras>
0042 #endif
0043 
0044 #include <QLabel>
0045 #include <QGroupBox>
0046 #include <QPushButton>
0047 #include <QGridLayout>
0048 #include <QHBoxLayout>
0049 #include <QVBoxLayout>
0050 #include <QResizeEvent>
0051 #include <QShowEvent>
0052 #include <QScrollBar>
0053 #include <QTimer>
0054 #include <QDialogButtonBox>
0055 #include <QLocale>
0056 #include <QStyle>
0057 
0058 using namespace KCal;
0059 using namespace KAlarmCal;
0060 
0061 namespace
0062 {
0063 const char EDIT_DIALOG_NAME[] = "EditDialog";
0064 const char TEMPLATE_DIALOG_NAME[] = "EditTemplateDialog";
0065 const char EDIT_MORE_KEY[]   = "EditMore";
0066 const QLatin1String EDIT_MORE_GROUP("ShowOpts");
0067 const int  maxDelayTime = 99*60 + 59;    // < 100 hours
0068 }
0069 
0070 inline QString recurText(const KAEvent& event)
0071 {
0072     QString r;
0073     if (event.repetition())
0074         r = QStringLiteral("%1 / %2").arg(event.recurrenceText(), event.repetitionText());
0075     else
0076         r = event.recurrenceText();
0077     return i18nc("@title:tab", "Recurrence - [%1]", r);
0078 }
0079 
0080 QList<EditAlarmDlg*> EditAlarmDlg::mWindowList;
0081 
0082 // Collect these widget labels together to ensure consistent wording and
0083 // translations across different modules.
0084 QString EditAlarmDlg::i18n_chk_ShowInKOrganizer()   { return i18nc("@option:check", "Show in KOrganizer"); }
0085 
0086 
0087 EditAlarmDlg* EditAlarmDlg::create(bool Template, Type type, QWidget* parent, GetResourceType getResource)
0088 {
0089     qCDebug(KALARM_LOG) << "EditAlarmDlg::create";
0090     switch (type)
0091     {
0092         case DISPLAY:  return new EditDisplayAlarmDlg(Template, parent, getResource);
0093         case COMMAND:  return new EditCommandAlarmDlg(Template, parent, getResource);
0094         case EMAIL:    return new EditEmailAlarmDlg(Template, parent, getResource);
0095         case AUDIO:    return new EditAudioAlarmDlg(Template, parent, getResource);
0096         default:  break;
0097     }
0098     return nullptr;
0099 }
0100 
0101 EditAlarmDlg* EditAlarmDlg::create(bool Template, const KAEvent& event, bool newAlarm, QWidget* parent,
0102                                    GetResourceType getResource, bool readOnly)
0103 {
0104     switch (event.actionTypes())
0105     {
0106         case KAEvent::Action::Command:  return new EditCommandAlarmDlg(Template, event, newAlarm, parent, getResource, readOnly);
0107         case KAEvent::Action::DisplayCommand:
0108         case KAEvent::Action::Display:  return new EditDisplayAlarmDlg(Template, event, newAlarm, parent, getResource, readOnly);
0109         case KAEvent::Action::Email:    return new EditEmailAlarmDlg(Template, event, newAlarm, parent, getResource, readOnly);
0110         case KAEvent::Action::Audio:    return new EditAudioAlarmDlg(Template, event, newAlarm, parent, getResource, readOnly);
0111         default:
0112             break;
0113     }
0114     return nullptr;
0115 }
0116 
0117 
0118 /******************************************************************************
0119 * Constructor.
0120 * Parameters:
0121 *   Template = true to edit/create an alarm template
0122 *            = false to edit/create an alarm.
0123 *   event    to initialise the dialog to show the specified event's data.
0124 */
0125 EditAlarmDlg::EditAlarmDlg(bool Template, KAEvent::SubAction action, QWidget* parent, GetResourceType getResource)
0126     : QDialog(parent)
0127     , mAlarmType(action)
0128     , mTemplate(Template)
0129     , mNewAlarm(true)
0130     , mDesiredReadOnly(false)
0131     , mReadOnly(false)
0132 {
0133     init(KAEvent(), getResource);
0134     mWindowList.append(this);
0135 }
0136 
0137 EditAlarmDlg::EditAlarmDlg(bool Template, const KAEvent& event, bool newAlarm, QWidget* parent,
0138                            GetResourceType getResource, bool readOnly)
0139     : QDialog(parent)
0140     , mAlarmType(event.actionSubType())
0141     , mEventId(newAlarm ? QString() : event.id())
0142     , mTemplate(Template)
0143     , mNewAlarm(newAlarm)
0144     , mDesiredReadOnly(readOnly)
0145     , mReadOnly(readOnly)
0146 {
0147     init(event, getResource);
0148     mWindowList.append(this);
0149 }
0150 
0151 void EditAlarmDlg::init(const KAEvent& event, GetResourceType getResource)
0152 {
0153     switch (getResource)
0154     {
0155         case RES_USE_EVENT_ID:
0156             if (event.isValid())
0157             {
0158                 mResourceEventId = event.id();
0159                 mUseResourceEventId = true;
0160                 break;
0161             }
0162             [[fallthrough]];   // fall through to RES_PROMPT
0163         case RES_PROMPT:
0164             mResourceEventId.clear();
0165             mUseResourceEventId = true;
0166             break;
0167         case RES_IGNORE:
0168         default:
0169             mResourceEventId.clear();
0170             mUseResourceEventId = false;
0171             break;
0172     }
0173 }
0174 
0175 void EditAlarmDlg::init(const KAEvent& event)
0176 {
0177     setObjectName(mTemplate ? QStringLiteral("TemplEditDlg") : QStringLiteral("EditDlg"));    // used by LikeBack
0178     QString caption;
0179     if (mReadOnly)
0180         caption = mTemplate ? i18nc("@title:window", "Alarm Template [read-only]")
0181                 : event.expired() ? i18nc("@title:window", "Archived Alarm [read-only]")
0182                                   : i18nc("@title:window", "Alarm [read-only]");
0183     else
0184         caption = type_caption();
0185     setWindowTitle(caption);
0186 
0187     // Create button box now so that types can work with it, but don't insert
0188     // it into layout just yet
0189     mButtonBox = new QDialogButtonBox(this);
0190     if (!mReadOnly)
0191     {
0192         QPushButton* b = mButtonBox->addButton(QDialogButtonBox::Ok);
0193         b->setWhatsThis(i18nc("@info:whatsthis", "Schedule the alarm at the specified time."));
0194         if (!mTemplate)
0195         {
0196             mLoadTemplateButton = mButtonBox->addButton(i18nc("@action:button", "Load Template..."),
0197                                                         QDialogButtonBox::HelpRole);
0198             mLoadTemplateButton->setToolTip(i18nc("@info:tooltip", "Select an alarm template to preset the alarm"));
0199         }
0200     }
0201     mButtonBox->addButton(QDialogButtonBox::Cancel);
0202     mTryButton = mButtonBox->addButton(i18nc("@action:button", "Try"), QDialogButtonBox::ActionRole);
0203     mMoreLessButton = mButtonBox->addButton(QString(), QDialogButtonBox::ResetRole);
0204     connect(mButtonBox, &QDialogButtonBox::clicked, this, &EditAlarmDlg::slotButtonClicked);
0205 
0206     auto mainLayout = new QVBoxLayout(this);
0207     mTabs = new QTabWidget(this);
0208     mainLayout->addWidget(mTabs);
0209     mTabScrollGroup = new StackedScrollGroup(this, mTabs);
0210 
0211     auto mainScroll = new StackedScrollWidget(mTabScrollGroup);
0212     mTabs->addTab(mainScroll, i18nc("@title:tab", "Alarm"));
0213     mMainPageIndex = 0;
0214     auto mainPage = new PageFrame(mainScroll);
0215     mainScroll->setWidget(mainPage);   // mainPage becomes the child of mainScroll
0216     connect(mainPage, &PageFrame::shown, this, &EditAlarmDlg::slotShowMainPage);
0217     auto topLayout = new QVBoxLayout(mainPage);
0218 
0219     // Recurrence tab
0220     auto recurScroll = new StackedScrollWidget(mTabScrollGroup);
0221     mTabs->addTab(recurScroll, QString());
0222     mRecurPageIndex = 1;
0223     QFrame* recurTab = new QFrame;
0224     auto recurTabLayout = new QVBoxLayout();
0225     recurTab->setLayout(recurTabLayout);
0226     recurScroll->setWidget(recurTab);   // recurTab becomes the child of recurScroll
0227     mRecurrenceEdit = new RecurrenceEdit(mReadOnly);
0228     recurTabLayout->addWidget(mRecurrenceEdit);
0229     connect(mRecurrenceEdit, &RecurrenceEdit::shown, this, &EditAlarmDlg::slotShowRecurrenceEdit);
0230     connect(mRecurrenceEdit, &RecurrenceEdit::typeChanged, this, &EditAlarmDlg::slotRecurTypeChange);
0231     connect(mRecurrenceEdit, &RecurrenceEdit::frequencyChanged, this, &EditAlarmDlg::slotRecurFrequencyChange);
0232     connect(mRecurrenceEdit, &RecurrenceEdit::repeatNeedsInitialisation, this, &EditAlarmDlg::slotSetSubRepetition);
0233     connect(mRecurrenceEdit, &RecurrenceEdit::contentsChanged, this, &EditAlarmDlg::contentsChanged);
0234 
0235     if (mTemplate  ||  Preferences::useAlarmName())
0236     {
0237         // Alarm/template name
0238         QFrame* frame = new QFrame;
0239         auto box = new QHBoxLayout();
0240         frame->setLayout(box);
0241         box->setContentsMargins(0, 0, 0, 0);
0242         QLabel* label = new QLabel(mTemplate ? i18nc("@label:textbox", "Template name:") : i18nc("@label:textbox", "Alarm name:"));
0243         box->addWidget(label);
0244         mName = new QLineEdit();
0245         mName->setReadOnly(mReadOnly);
0246         connect(mName, &QLineEdit::textEdited, this, &EditAlarmDlg::contentsChanged);
0247         label->setBuddy(mName);
0248         box->addWidget(mName);
0249         frame->setWhatsThis(mTemplate ? i18nc("@info:whatsthis", "Enter the name of the alarm template")
0250                                       : i18nc("@info:whatsthis", "Enter a name to help you identify this alarm. This is optional and need not be unique."));
0251         topLayout->addWidget(frame);
0252     }
0253 
0254     // Controls specific to the alarm type
0255     QGroupBox* actionBox = new QGroupBox(i18nc("@title:group", "Action"), mainPage);
0256     topLayout->addWidget(actionBox, 1);
0257     auto vlayout = new QVBoxLayout(actionBox);
0258 
0259     type_init(actionBox, vlayout);
0260 
0261     if (!mTemplate)
0262     {
0263         // Deferred date/time: visible only for a deferred recurring event.
0264         mDeferGroup = new QGroupBox(i18nc("@title:group", "Deferred Alarm"), mainPage);
0265         topLayout->addWidget(mDeferGroup);
0266         auto hlayout = new QHBoxLayout(mDeferGroup);
0267         QLabel* label = new QLabel(i18nc("@label", "Deferred to:"), mDeferGroup);
0268         hlayout->addWidget(label);
0269         mDeferTimeLabel = new QLabel(mDeferGroup);
0270         hlayout->addWidget(mDeferTimeLabel);
0271 
0272         mDeferChangeButton = new QPushButton(i18nc("@action:button", "Change..."), mDeferGroup);
0273         connect(mDeferChangeButton, &QPushButton::clicked, this, &EditAlarmDlg::slotEditDeferral);
0274         mDeferChangeButton->setWhatsThis(i18nc("@info:whatsthis", "Change the alarm's deferred time, or cancel the deferral"));
0275         hlayout->addWidget(mDeferChangeButton);
0276     }
0277 
0278     auto hlayout = new QHBoxLayout();
0279     hlayout->setContentsMargins(0, 0, 0, 0);
0280     topLayout->addLayout(hlayout);
0281 
0282     // Date and time entry
0283     if (mTemplate)
0284     {
0285         QGroupBox* templateTimeBox = new QGroupBox(i18nc("@title:group", "Time"), mainPage);
0286         topLayout->addWidget(templateTimeBox);
0287         auto grid = new QGridLayout(templateTimeBox);
0288         mTemplateTimeGroup = new ButtonGroup(templateTimeBox);
0289         connect(mTemplateTimeGroup, &ButtonGroup::buttonSet, this, &EditAlarmDlg::slotTemplateTimeType);
0290         connect(mTemplateTimeGroup, &ButtonGroup::buttonSet, this, &EditAlarmDlg::contentsChanged);
0291 
0292         mTemplateDefaultTime = new RadioButton(i18nc("@option:radio", "Default time"), templateTimeBox);
0293         mTemplateDefaultTime->setReadOnly(mReadOnly);
0294         mTemplateDefaultTime->setWhatsThis(i18nc("@info:whatsthis", "Do not specify a start time for alarms based on this template. "
0295                                                 "The normal default start time will be used."));
0296         mTemplateTimeGroup->addButton(mTemplateDefaultTime);
0297         grid->addWidget(mTemplateDefaultTime, 0, 0, Qt::AlignLeft);
0298 
0299         QWidget* box = new QWidget(templateTimeBox);
0300         auto layout = new QHBoxLayout(box);
0301         layout->setContentsMargins(0, 0, 0, 0);
0302         mTemplateUseTime = new RadioButton(i18nc("@option:radio", "Time:"), box);
0303         mTemplateUseTime->setReadOnly(mReadOnly);
0304         mTemplateUseTime->setWhatsThis(i18nc("@info:whatsthis", "Specify a start time for alarms based on this template."));
0305         layout->addWidget(mTemplateUseTime);
0306         mTemplateTimeGroup->addButton(mTemplateUseTime);
0307         mTemplateTime = new TimeEdit();
0308         mTemplateTime->setReadOnly(mReadOnly);
0309         mTemplateTime->setWhatsThis(xi18nc("@info:whatsthis",
0310               "<para>Enter the start time for alarms based on this template.</para><para>%1</para>",
0311               TimeSpinBox::shiftWhatsThis()));
0312         connect(mTemplateTime, &TimeEdit::valueChanged, this, &EditAlarmDlg::contentsChanged);
0313         layout->addWidget(mTemplateTime);
0314         layout->addStretch(1);
0315         grid->addWidget(box, 0, 1, Qt::AlignLeft);
0316 
0317         mTemplateAnyTime = new RadioButton(i18nc("@option:radio", "Date only"), templateTimeBox);
0318         mTemplateAnyTime->setReadOnly(mReadOnly);
0319         mTemplateAnyTime->setWhatsThis(xi18nc("@info:whatsthis", "Set the <interface>Any time</interface> option for alarms based on this template."));
0320         mTemplateTimeGroup->addButton(mTemplateAnyTime);
0321         grid->addWidget(mTemplateAnyTime, 1, 0, Qt::AlignLeft);
0322 
0323         box = new QWidget(templateTimeBox);
0324         layout = new QHBoxLayout(box);
0325         layout->setContentsMargins(0, 0, 0, 0);
0326         mTemplateUseTimeAfter = new RadioButton(i18nc("@option:radio", "Time from now:"), box);
0327         mTemplateUseTimeAfter->setReadOnly(mReadOnly);
0328         mTemplateUseTimeAfter->setWhatsThis(i18nc("@info:whatsthis",
0329                                                   "Set alarms based on this template to start after the specified time "
0330                                                  "interval from when the alarm is created."));
0331         layout->addWidget(mTemplateUseTimeAfter);
0332         mTemplateTimeGroup->addButton(mTemplateUseTimeAfter);
0333         mTemplateTimeAfter = new TimeSpinBox(1, maxDelayTime);
0334         mTemplateTimeAfter->setValue(1439);
0335         mTemplateTimeAfter->setReadOnly(mReadOnly);
0336         connect(mTemplateTimeAfter, &TimeSpinBox::valueChanged, this, &EditAlarmDlg::contentsChanged);
0337         mTemplateTimeAfter->setWhatsThis(xi18nc("@info:whatsthis", "<para>%1</para><para>%2</para>",
0338                                                AlarmTimeWidget::i18n_TimeAfterPeriod(), TimeSpinBox::shiftWhatsThis()));
0339         layout->addWidget(mTemplateTimeAfter);
0340         grid->addWidget(box, 1, 1, Qt::AlignLeft);
0341 
0342         hlayout->addStretch();
0343     }
0344     else
0345     {
0346         mTimeWidget = new AlarmTimeWidget(i18nc("@title:group", "Time"), AlarmTimeWidget::AT_TIME, mainPage);
0347         connect(mTimeWidget, &AlarmTimeWidget::dateOnlyToggled, this, &EditAlarmDlg::slotAnyTimeToggled);
0348         connect(mTimeWidget, &AlarmTimeWidget::changed, this, &EditAlarmDlg::contentsChanged);
0349         topLayout->addWidget(mTimeWidget);
0350     }
0351 
0352     // Optional controls depending on More/Fewer Options button
0353     mMoreOptions = new QFrame(mainPage);
0354     mMoreOptions->setFrameStyle(QFrame::NoFrame);
0355     topLayout->addWidget(mMoreOptions);
0356     auto moreLayout = new QVBoxLayout(mMoreOptions);
0357     moreLayout->setContentsMargins(0, 0, 0, 0);
0358 
0359     // Reminder
0360     mReminder = createReminder(mMoreOptions);
0361     if (mReminder)
0362     {
0363         connect(mReminder, &Reminder::changed, this, &EditAlarmDlg::contentsChanged);
0364         moreLayout->addWidget(mReminder, 0, Qt::AlignLeft);
0365         if (mTimeWidget)
0366             connect(mTimeWidget, &AlarmTimeWidget::changed, mReminder, &Reminder::setDefaultUnits);
0367     }
0368 
0369     // Late cancel selector - default = allow late display
0370     mLateCancel = new LateCancelSelector(true, mMoreOptions);
0371     connect(mLateCancel, &LateCancelSelector::changed, this, &EditAlarmDlg::contentsChanged);
0372     auto layout = new QHBoxLayout;
0373     layout->setContentsMargins(0, 0, 0, 0);
0374     layout->addWidget(mLateCancel, 1);
0375     moreLayout->addLayout(layout);
0376 
0377     PackedLayout* playout = new PackedLayout(Qt::AlignJustify);
0378     playout->setHorizontalSpacing(2 * style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
0379     playout->setVerticalSpacing(2 * style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing));
0380     moreLayout->addLayout(playout);
0381 
0382     if (KernelWakeAlarm::isAvailable())
0383     {
0384         // Wake from suspend checkbox
0385         mWakeFromSuspend = new CheckBox(i18nc("@option:check", "Wake from suspend"), mMoreOptions);
0386         connect(mWakeFromSuspend, &CheckBox::toggled, this, &EditAlarmDlg::contentsChanged);
0387         mWakeFromSuspend->setWhatsThis(i18nc("@info:whatsthis", "Check to wake the system if the alarm triggers while the system is suspended"));
0388         playout->addWidget(mWakeFromSuspend);
0389     }
0390 
0391     // Acknowledgement confirmation required - default = no confirmation
0392     CheckBox* confirmAck = type_createConfirmAckCheckbox(mMoreOptions);
0393     if (confirmAck)
0394     {
0395         connect(confirmAck, &CheckBox::toggled, this, &EditAlarmDlg::contentsChanged);
0396         playout->addWidget(confirmAck);
0397     }
0398 
0399     if (theApp()->korganizerEnabled())
0400     {
0401         // Show in KOrganizer checkbox
0402         mShowInKorganizer = new CheckBox(i18n_chk_ShowInKOrganizer(), mMoreOptions);
0403         connect(mShowInKorganizer, &CheckBox::toggled, this, &EditAlarmDlg::contentsChanged);
0404         mShowInKorganizer->setWhatsThis(i18nc("@info:whatsthis", "Check to copy the alarm into KOrganizer's calendar"));
0405         if (!confirmAck  ||  !mWakeFromSuspend)
0406             playout->addWidget(mShowInKorganizer);
0407         else
0408             mLateCancel->addWidget(mShowInKorganizer);
0409     }
0410 
0411     mainLayout->addWidget(mButtonBox);
0412 
0413     // Hide optional controls
0414     KConfigGroup config(KSharedConfig::openConfig(), EDIT_MORE_GROUP);
0415     showOptions(config.readEntry(EDIT_MORE_KEY, false));
0416 
0417     // Initialise the state of all controls according to the specified event, if any
0418     initValues(event);
0419     if (mName)
0420         mName->setFocus();
0421 
0422     if (!mNewAlarm)
0423     {
0424         // Save the initial state of all controls so that we can later tell if they have changed
0425         saveState((event.isValid() && (mTemplate || !event.isTemplate())) ? &event : nullptr);
0426         contentsChanged();    // enable/disable OK button
0427     }
0428 
0429     // Note the current virtual desktop so that the dialog can be shown on it.
0430     // If a main window is visible, the dialog will by KDE default always appear on its
0431     // desktop. If the user invokes the dialog via the system tray on a different desktop,
0432     // that can cause confusion.
0433 #if ENABLE_X11
0434     mDesktop = KX11Extras::currentDesktop();
0435 #endif
0436 
0437     if (theApp()->windowFocusBroken())
0438     {
0439         const QList<QWidget*> children = findChildren<QWidget*>();
0440         for (QWidget* w : children)
0441             w->installEventFilter(this);
0442     }
0443 }
0444 
0445 EditAlarmDlg::~EditAlarmDlg()
0446 {
0447     delete mButtonBox;
0448     mButtonBox = nullptr;    // prevent text edit contentsChanged() signal triggering a crash
0449     delete mSavedEvent;
0450     mWindowList.removeAll(this);
0451 }
0452 
0453 /******************************************************************************
0454 * Return the number of instances.
0455 */
0456 int EditAlarmDlg::instanceCount()
0457 {
0458     return mWindowList.count();
0459 }
0460 
0461 /******************************************************************************
0462 * Initialise the dialog controls from the specified event.
0463 */
0464 void EditAlarmDlg::initValues(const KAEvent& event)
0465 {
0466     setReadOnly(mDesiredReadOnly);
0467 
0468     mChanged           = false;
0469     mOnlyDeferred      = false;
0470     mExpiredRecurrence = false;
0471     mLateCancel->showAutoClose(false);
0472     bool deferGroupVisible = false;
0473     if (event.isValid())
0474     {
0475         // Set the values to those for the specified event
0476         if (mName)
0477             mName->setText(event.name());
0478         bool recurs = event.recurs();
0479         if ((recurs || event.repetition())  &&  !mTemplate  &&  event.deferred())
0480         {
0481             deferGroupVisible = true;
0482             mDeferDateTime = event.deferDateTime();
0483             mDeferTimeLabel->setText(mDeferDateTime.formatLocale());
0484             mDeferGroup->show();
0485         }
0486         if (mTemplate)
0487         {
0488             // Editing a template
0489             int afterTime = event.isTemplate() ? event.templateAfterTime() : -1;
0490             bool noTime   = !afterTime;
0491             bool useTime  = !event.mainDateTime().isDateOnly();
0492             RadioButton* button = noTime          ? mTemplateDefaultTime :
0493                                   (afterTime > 0) ? mTemplateUseTimeAfter :
0494                                   useTime         ? mTemplateUseTime : mTemplateAnyTime;
0495             button->setChecked(true);
0496             mTemplateTimeAfter->setValue(afterTime > 0 ? afterTime : 1);
0497             if (!noTime && useTime)
0498                 mTemplateTime->setValue(event.mainDateTime().kDateTime().time());
0499             else
0500                 mTemplateTime->setValue(0);
0501         }
0502         else
0503         {
0504             if (event.isTemplate())
0505             {
0506                 // Initialising from an alarm template: use current date
0507                 const KADateTime now = KADateTime::currentDateTime(Preferences::timeSpec());
0508                 int afterTime = event.templateAfterTime();
0509                 if (afterTime >= 0)
0510                 {
0511                     mTimeWidget->setDateTime(now.addSecs(afterTime * 60));
0512                     mTimeWidget->selectTimeFromNow();
0513                 }
0514                 else
0515                 {
0516                     KADateTime dt = event.startDateTime().kDateTime();
0517                     dt.setTimeSpec(Preferences::timeSpec());
0518                     QDate d = now.date();
0519                     if (!dt.isDateOnly()  &&  now.time() >= dt.time())
0520                         d = d.addDays(1);     // alarm time has already passed, so use tomorrow
0521                     dt.setDate(d);
0522                     mTimeWidget->setDateTime(dt);
0523                 }
0524             }
0525             else
0526             {
0527                 mExpiredRecurrence = recurs && event.mainExpired();
0528                 mTimeWidget->setDateTime(recurs || event.category() == CalEvent::ARCHIVED ? event.startDateTime()
0529                                          : event.mainExpired() ? event.deferDateTime() : event.mainDateTime());
0530             }
0531         }
0532 
0533         KAEvent::SubAction action = event.actionSubType();
0534         AlarmText altext;
0535         if (event.commandScript())
0536             altext.setScript(event.cleanText());
0537         else
0538             altext.setText(event.cleanText());
0539         setAction(action, altext);
0540 
0541         mLateCancel->setMinutes(event.lateCancel(), event.startDateTime().isDateOnly(),
0542                                 TimePeriod::HoursMinutes);
0543         if (mWakeFromSuspend)
0544             mWakeFromSuspend->setChecked(event.wakeFromSuspend());
0545         if (mShowInKorganizer)
0546             mShowInKorganizer->setChecked(event.copyToKOrganizer());
0547         type_initValues(event);
0548         mRecurrenceEdit->set(event);   // must be called after mTimeWidget is set up, to ensure correct date-only enabling
0549         mTabs->setTabText(mRecurPageIndex, recurText(event));
0550     }
0551     else
0552     {
0553         // Set the values to their defaults
0554         const KADateTime defaultTime = KADateTime::currentUtcDateTime().addSecs(60).toTimeSpec(Preferences::timeSpec());
0555         if (mTemplate)
0556         {
0557             mTemplateDefaultTime->setChecked(true);
0558             mTemplateTime->setValue(0);
0559             mTemplateTimeAfter->setValue(1);
0560         }
0561         else
0562             mTimeWidget->setDateTime(defaultTime);
0563         mLateCancel->setMinutes((Preferences::defaultLateCancel() ? 1 : 0), false, TimePeriod::HoursMinutes);
0564         if (mWakeFromSuspend)
0565             mWakeFromSuspend->setChecked(false);
0566         if (mShowInKorganizer)
0567             mShowInKorganizer->setChecked(Preferences::defaultCopyToKOrganizer());
0568         type_initValues(KAEvent());
0569         mRecurrenceEdit->setDefaults(defaultTime);   // must be called after mTimeWidget is set up, to ensure correct date-only enabling
0570         slotRecurFrequencyChange();      // update the Recurrence text
0571     }
0572     if (mReminder  &&  mTimeWidget)
0573         mReminder->setDefaultUnits(mTimeWidget->getDateTime(false, false));
0574 
0575     if (!deferGroupVisible  &&  mDeferGroup)
0576         mDeferGroup->hide();
0577 
0578     bool empty = ResourcesCalendar::events(CalEvent::TEMPLATE).isEmpty();
0579     if (mLoadTemplateButton)
0580         mLoadTemplateButton->setEnabled(!empty);
0581 }
0582 
0583 /******************************************************************************
0584 * Initialise various values in the New Alarm dialogue.
0585 */
0586 void EditAlarmDlg::setName(const QString& name)
0587 {
0588     if (mName)
0589         mName->setText(name);
0590 }
0591 void EditAlarmDlg::setTime(const DateTime& start)
0592 {
0593     mTimeWidget->setDateTime(start);
0594     mTimeWidget->selectTimeFromNow(-1);    // select 'At date/time' option
0595 }
0596 KADateTime EditAlarmDlg::time() const
0597 {
0598     return mTimeWidget->getDateTime(false, false);
0599 }
0600 void EditAlarmDlg::setRecurrence(const KARecurrence& recur, const KCalendarCore::Duration& subRepeatInterval, int subRepeatCount)
0601 {
0602     KAEvent event;
0603     event.setTime(mTimeWidget->getDateTime(false, false));
0604     event.setRecurrence(recur);
0605     event.setRepetition(Repetition(subRepeatInterval, subRepeatCount - 1));
0606     mRecurrenceEdit->set(event);
0607 }
0608 void EditAlarmDlg::setRepeatAtLogin()
0609 {
0610     mRecurrenceEdit->setRepeatAtLogin();
0611 }
0612 void EditAlarmDlg::setLateCancel(int minutes)
0613 {
0614     mLateCancel->setMinutes(minutes, mTimeWidget->getDateTime(false, false).isDateOnly(),
0615                             TimePeriod::HoursMinutes);
0616 }
0617 void EditAlarmDlg::setWakeFromSuspend(bool wake)
0618 {
0619     if (mWakeFromSuspend)
0620         mWakeFromSuspend->setChecked(wake);
0621 }
0622 void EditAlarmDlg::setShowInKOrganizer(bool show)
0623 {
0624     if (mShowInKorganizer)
0625         mShowInKorganizer->setChecked(show);
0626 }
0627 
0628 /******************************************************************************
0629 * Set the read-only status of all non-template controls.
0630 */
0631 void EditAlarmDlg::setReadOnly(bool readOnly)
0632 {
0633     mReadOnly = readOnly;
0634 
0635     if (mTimeWidget)
0636         mTimeWidget->setReadOnly(readOnly);
0637     mLateCancel->setReadOnly(readOnly);
0638     if (mDeferChangeButton)
0639     {
0640         if (readOnly)
0641             mDeferChangeButton->hide();
0642         else
0643             mDeferChangeButton->show();
0644     }
0645     if (mWakeFromSuspend)
0646         mWakeFromSuspend->setReadOnly(readOnly);
0647     if (mShowInKorganizer)
0648         mShowInKorganizer->setReadOnly(readOnly);
0649 }
0650 
0651 /******************************************************************************
0652 * Save the state of all controls.
0653 */
0654 void EditAlarmDlg::saveState(const KAEvent* event)
0655 {
0656     delete mSavedEvent;
0657     mSavedEvent = nullptr;
0658     if (event)
0659         mSavedEvent = new KAEvent(*event);
0660     if (mName)
0661         mSavedName = mName->text();
0662     if (mTemplate)
0663     {
0664         mSavedTemplateTimeType  = mTemplateTimeGroup->checkedButton();
0665         mSavedTemplateTime      = mTemplateTime->time();
0666         mSavedTemplateAfterTime = mTemplateTimeAfter->value();
0667     }
0668     checkText(mSavedTextFileCommandMessage, false);
0669     if (mTimeWidget)
0670         mSavedDateTime = mTimeWidget->getDateTime(false, false);
0671     mSavedLateCancel       = mLateCancel->minutes();
0672     if (mWakeFromSuspend)
0673         mSavedWakeFromSuspend = mWakeFromSuspend->isChecked();
0674     if (mShowInKorganizer)
0675         mSavedShowInKorganizer = mShowInKorganizer->isChecked();
0676     mSavedRecurrenceType   = mRecurrenceEdit->repeatType();
0677     mSavedDeferTime        = mDeferDateTime.kDateTime();
0678 }
0679 
0680 /******************************************************************************
0681 * Check whether any of the controls has changed state since the dialog was
0682 * first displayed.
0683 * Reply = true if any non-deferral controls have changed, or if it's a new event.
0684 *       = false if no non-deferral controls have changed. In this case,
0685 *         mOnlyDeferred indicates whether deferral controls may have changed.
0686 */
0687 bool EditAlarmDlg::stateChanged() const
0688 {
0689     mChanged      = true;
0690     mOnlyDeferred = false;
0691     if (!mSavedEvent)
0692         return true;
0693     if (mName  &&  mSavedName != mName->text())
0694         return true;
0695     QString textFileCommandMessage;
0696     checkText(textFileCommandMessage, false);
0697     if (mTemplate)
0698     {
0699         if (mSavedTemplateTimeType != mTemplateTimeGroup->checkedButton()
0700         ||  (mTemplateUseTime->isChecked()  &&  mSavedTemplateTime != mTemplateTime->time())
0701         ||  (mTemplateUseTimeAfter->isChecked()  &&  mSavedTemplateAfterTime != mTemplateTimeAfter->value()))
0702             return true;
0703     }
0704     else
0705     {
0706         const KADateTime dt = mTimeWidget->getDateTime(false, false);
0707         if (mSavedDateTime.timeSpec() != dt.timeSpec()  ||  mSavedDateTime != dt)
0708             return true;
0709     }
0710     if (mSavedLateCancel       != mLateCancel->minutes()
0711     ||  (mWakeFromSuspend && mSavedWakeFromSuspend != mWakeFromSuspend->isChecked())
0712     ||  (mShowInKorganizer && mSavedShowInKorganizer != mShowInKorganizer->isChecked())
0713     ||  textFileCommandMessage != mSavedTextFileCommandMessage
0714     ||  mSavedRecurrenceType   != mRecurrenceEdit->repeatType())
0715         return true;
0716     if (type_stateChanged())
0717         return true;
0718     if (mRecurrenceEdit->stateChanged())
0719         return true;
0720     if (mSavedEvent  &&  mSavedEvent->deferred())
0721         mOnlyDeferred = true;
0722     mChanged = false;
0723     return false;
0724 }
0725 
0726 /******************************************************************************
0727 * Called whenever any of the controls changes state.
0728 * Enable or disable the OK button depending on whether any controls have a
0729 * different state from their initial state.
0730 */
0731 void EditAlarmDlg::contentsChanged()
0732 {
0733     // Don't do anything if it's a new alarm or we're still initialising
0734     // (i.e. mSavedEvent null).
0735     if (mSavedEvent  &&  mButtonBox  &&  mButtonBox->button(QDialogButtonBox::Ok))
0736         mButtonBox->button(QDialogButtonBox::Ok)->setEnabled(stateChanged() || mDeferDateTime.kDateTime() != mSavedDeferTime);
0737 }
0738 
0739 /******************************************************************************
0740 * Get the currently entered dialog data.
0741 * The data is returned in the supplied KAEvent instance.
0742 * Reply = false if the only change has been to an existing deferral.
0743 */
0744 bool EditAlarmDlg::getEvent(KAEvent& event, Resource& resource)
0745 {
0746     resource = mResource;
0747     if (mChanged)
0748     {
0749         // It's a new event, or the edit controls have changed
0750         setEvent(event, mAlarmMessage, false);
0751         return true;
0752     }
0753 
0754     // Only the deferral time may have changed
0755     event = *mSavedEvent;
0756     if (mOnlyDeferred)
0757     {
0758         // Just modify the original event, to avoid expired recurring events
0759         // being returned as rubbish.
0760         if (mDeferDateTime.isValid())
0761             event.defer(mDeferDateTime, event.reminderDeferral(), false);
0762         else
0763             event.cancelDefer();
0764     }
0765     return false;
0766 }
0767 
0768 /******************************************************************************
0769 * Extract the data in the dialog and set up a KAEvent from it.
0770 * If 'trial' is true, the event is set up for a simple one-off test, ignoring
0771 * recurrence, reminder, template etc. data.
0772 */
0773 void EditAlarmDlg::setEvent(KAEvent& event, const QString& text, bool trial)
0774 {
0775     KADateTime dt;
0776     if (!trial)
0777     {
0778         if (!mTemplate)
0779             dt = mAlarmDateTime.effectiveKDateTime();
0780         else if (mTemplateUseTime->isChecked())
0781             dt = KADateTime(QDate(2000,1,1), mTemplateTime->time());
0782     }
0783 
0784     const int lateCancel = (trial || !mLateCancel->isEnabled()) ? 0 : mLateCancel->minutes();
0785     type_setEvent(event, dt, (mName ? mName->text() : QString()), text, lateCancel, trial);
0786 
0787     if (!trial)
0788     {
0789         if (mRecurrenceEdit->repeatType() != RecurrenceEdit::NO_RECUR)
0790         {
0791             mRecurrenceEdit->updateEvent(event, !mTemplate);
0792             const KADateTime now = KADateTime::currentDateTime(mAlarmDateTime.timeSpec());
0793             const bool dateOnly = mAlarmDateTime.isDateOnly();
0794             if ((dateOnly  &&  mAlarmDateTime.date() < now.date())
0795             ||  (!dateOnly  &&  mAlarmDateTime.kDateTime() < now))
0796             {
0797                 // A timed recurrence has an entered start date which has
0798                 // already expired, so we must adjust the next repetition.
0799                 event.setNextOccurrence(now);
0800             }
0801             mAlarmDateTime = event.startDateTime();
0802             if (mDeferDateTime.isValid()  &&  mDeferDateTime < mAlarmDateTime)
0803             {
0804                 bool deferral = true;
0805                 bool deferReminder = false;
0806                 const int reminder = mReminder ? mReminder->minutes() : 0;
0807                 if (reminder)
0808                 {
0809                     DateTime remindTime = mAlarmDateTime.addMins(-reminder);
0810                     if (mDeferDateTime >= remindTime)
0811                     {
0812                         if (remindTime > KADateTime::currentUtcDateTime())
0813                             deferral = false;    // ignore deferral if it's after next reminder
0814                         else if (mDeferDateTime > remindTime)
0815                             deferReminder = true;    // it's the reminder which is being deferred
0816                     }
0817                 }
0818                 if (deferral)
0819                     event.defer(mDeferDateTime, deferReminder, false);
0820             }
0821         }
0822         if (mTemplate)
0823         {
0824             int afterTime = mTemplateDefaultTime->isChecked() ? 0
0825                           : mTemplateUseTimeAfter->isChecked() ? mTemplateTimeAfter->value() : -1;
0826             event.setTemplate(mName->text(), afterTime);
0827         }
0828     }
0829 }
0830 
0831 /******************************************************************************
0832 * Get the currently specified alarm flag bits.
0833 */
0834 KAEvent::Flags EditAlarmDlg::getAlarmFlags() const
0835 {
0836     KAEvent::Flags flags{};
0837     if (mWakeFromSuspend && mWakeFromSuspend->isEnabled() && mWakeFromSuspend->isChecked())
0838         flags |= KAEvent::WAKE_SUSPEND;
0839     if (mShowInKorganizer && mShowInKorganizer->isEnabled() && mShowInKorganizer->isChecked())
0840         flags |= KAEvent::COPY_KORGANIZER;
0841     if (mRecurrenceEdit->repeatType() == RecurrenceEdit::AT_LOGIN)
0842         flags |= KAEvent::REPEAT_AT_LOGIN;
0843     if (mTemplate ? mTemplateAnyTime->isChecked() : mAlarmDateTime.isDateOnly())
0844         flags |= KAEvent::ANY_TIME;
0845     return flags;
0846 }
0847 
0848 /******************************************************************************
0849 * Called when the dialog is displayed.
0850 * The first time through, sets the size to the same as the last time it was
0851 * displayed.
0852 */
0853 void EditAlarmDlg::showEvent(QShowEvent* se)
0854 {
0855     QDialog::showEvent(se);
0856     if (!mDeferGroupHeight)
0857     {
0858         if (mDeferGroup)
0859             mDeferGroupHeight = mDeferGroup->height() + style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing);
0860         QSize s;
0861         if (Config::readWindowSize(mTemplate ? TEMPLATE_DIALOG_NAME : EDIT_DIALOG_NAME, s))
0862         {
0863             const bool defer = mDeferGroup && !mDeferGroup->isHidden();
0864             s.setHeight(s.height() + (defer ? mDeferGroupHeight : 0));
0865             if (!defer)
0866                 mTabScrollGroup->setSized();
0867             resize(s);
0868         }
0869     }
0870     slotResize();
0871 #if ENABLE_X11
0872     KX11Extras::setOnDesktop(winId(), mDesktop);    // ensure it displays on the virtual desktop expected by the user
0873 #endif
0874 
0875     if (theApp()->needWindowFocusFix())
0876     {
0877         QApplication::setActiveWindow(this);
0878         QTimer::singleShot(0, this, &EditAlarmDlg::focusFixTimer);   //NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
0879     }
0880 }
0881 
0882 /******************************************************************************
0883 * Called when the window is first shown, to ensure that it initially becomes
0884 * the active window.
0885 * This is only required on Ubuntu's Unity desktop, which doesn't transfer
0886 * keyboard focus properly between Edit Alarm Dialog windows and MessageWindow
0887 * windows.
0888 */
0889 void EditAlarmDlg::focusFixTimer()
0890 {
0891     if (theApp()->needWindowFocusFix()
0892     &&  QApplication::focusWidget()->window() != this)
0893     {
0894         QApplication::setActiveWindow(this);
0895         QTimer::singleShot(0, this, &EditAlarmDlg::focusFixTimer);   //NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
0896     }
0897 }
0898 
0899 /******************************************************************************
0900 * Called to detect when the mouse is pressed anywhere inside the window.
0901 * Activates this window if a MessageWindow window is also active.
0902 * This is only required on Ubuntu's Unity desktop, which doesn't transfer
0903 * keyboard focus properly between Edit Alarm Dialog windows and MessageWindow
0904 * windows.
0905 */
0906 bool EditAlarmDlg::eventFilter(QObject*, QEvent* e)
0907 {
0908     if (theApp()->needWindowFocusFix())
0909     {
0910         if (e->type() == QEvent::MouseButtonPress)
0911             QApplication::setActiveWindow(this);
0912     }
0913     return false;
0914 }
0915 
0916 /******************************************************************************
0917 * Called when the dialog is closed.
0918 */
0919 void EditAlarmDlg::closeEvent(QCloseEvent* ce)
0920 {
0921     Q_EMIT rejected();
0922     QDialog::closeEvent(ce);
0923 }
0924 
0925 /******************************************************************************
0926 * Update the tab sizes (again) and if the resized dialog height is greater
0927 * than the minimum, resize it again. This is necessary because (a) resizing
0928 * tabs doesn't always work properly the first time, and (b) resizing to the
0929 * minimum size hint doesn't always work either.
0930 */
0931 void EditAlarmDlg::slotResize()
0932 {
0933     mTabScrollGroup->adjustSize(true);
0934     const QSize s = minimumSizeHint();
0935     if (height() > s.height())
0936     {
0937         // Resize to slightly greater than the minimum height.
0938         // This is for some unknown reason necessary, since
0939         // sometimes resizing to the minimum height fails.
0940         resize(s.width(), s.height() + 2);
0941     }
0942 }
0943 
0944 /******************************************************************************
0945 * Called when the dialog's size has changed.
0946 * Records the new size (adjusted to ignore the optional height of the deferred
0947 * time edit widget) in the config file.
0948 */
0949 void EditAlarmDlg::resizeEvent(QResizeEvent* re)
0950 {
0951     if (isVisible() && mDeferGroupHeight)
0952     {
0953         QSize s = re->size();
0954         s.setHeight(s.height() - (!mDeferGroup || mDeferGroup->isHidden() ? 0 : mDeferGroupHeight));
0955         Config::writeWindowSize(mTemplate ? TEMPLATE_DIALOG_NAME : EDIT_DIALOG_NAME, s);
0956     }
0957     QDialog::resizeEvent(re);
0958 }
0959 
0960 /******************************************************************************
0961 * Called when any button is clicked.
0962 */
0963 void EditAlarmDlg::slotButtonClicked(QAbstractButton* button)
0964 {
0965     if (button == mTryButton)
0966         slotTry();
0967     else if (button == mLoadTemplateButton)
0968         slotHelp();
0969     else if (button == mMoreLessButton)
0970         slotDefault();
0971     else if (button == mButtonBox->button(QDialogButtonBox::Ok))
0972     {
0973         if (validate())
0974             accept();
0975     }
0976     else
0977         reject();
0978 }
0979 
0980 /******************************************************************************
0981 * Called when the OK button is clicked.
0982 * Validate the input data.
0983 */
0984 bool EditAlarmDlg::validate()
0985 {
0986     if (!stateChanged())
0987     {
0988         // No changes have been made except possibly to an existing deferral
0989         if (!mOnlyDeferred)
0990             reject();
0991         return mOnlyDeferred;
0992     }
0993     RecurrenceEdit::RepeatType recurType = mRecurrenceEdit->repeatType();
0994     if (mTimeWidget
0995     &&  mTabs->currentIndex() == mRecurPageIndex  &&  recurType == RecurrenceEdit::AT_LOGIN)
0996         mTimeWidget->setDateTime(mRecurrenceEdit->endDateTime());
0997     const bool timedRecurrence = mRecurrenceEdit->isTimedRepeatType();    // does it recur other than at login?
0998     if (mTemplate)
0999     {
1000         // Check that the template name is not blank and is unique
1001         QString errmsg;
1002         const QString name = mName->text();
1003         if (name.isEmpty())
1004             errmsg = i18nc("@info", "You must enter a name for the alarm template");
1005         else if (name != mSavedName)
1006         {
1007             if (ResourcesCalendar::templateEvent(name).isValid())
1008                 errmsg = i18nc("@info", "Template name is already in use");
1009         }
1010         if (!errmsg.isEmpty())
1011         {
1012             mName->setFocus();
1013             KAMessageBox::error(this, errmsg);
1014             return false;
1015         }
1016     }
1017     else if (mTimeWidget)
1018     {
1019         QWidget* errWidget;
1020         mAlarmDateTime = mTimeWidget->getDateTime(!timedRecurrence, false, &errWidget);
1021         if (errWidget)
1022         {
1023             // It's more than just an existing deferral being changed, so the time matters
1024             mTabs->setCurrentIndex(mMainPageIndex);
1025             errWidget->setFocus();
1026             mTimeWidget->getDateTime();   // display the error message now
1027             return false;
1028         }
1029         if (!mAlarmDateTime.isDateOnly()  &&  KADateTime::currentUtcDateTime().secsTo(mAlarmDateTime.kDateTime()) / 60 < 10)
1030         {
1031             if (KAMessageBox::warningContinueCancel(this, xi18nc("@info", "<para>KAlarm does not provide high accuracy alarms.</para><para> The alarm will trigger at the minute boundary before the specified time from now.</para>"), QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("HighAccuracy"))
1032                     != KMessageBox::Continue)
1033             {
1034                 mTimeWidget->setFocus();
1035                 return false;
1036             }
1037         }
1038     }
1039     if (!type_validate(false))
1040         return false;
1041 
1042     if (!mTemplate)
1043     {
1044         if (mChanged  &&  mRecurrenceEdit->repeatType() != RecurrenceEdit::NO_RECUR)
1045         {
1046             // Check whether the start date/time must be adjusted
1047             // to match the recurrence specification.
1048             DateTime dt = mAlarmDateTime;   // setEvent() changes mAlarmDateTime
1049             KAEvent event;
1050             setEvent(event, mAlarmMessage, false);
1051             mAlarmDateTime = dt;   // restore
1052             KADateTime pre = dt.effectiveKDateTime();
1053             bool dateOnly = dt.isDateOnly();
1054             if (dateOnly)
1055                 pre = pre.addDays(-1);
1056             else
1057                 pre = pre.addSecs(-1);
1058             DateTime next;
1059             event.nextOccurrence(pre, next, KAEvent::Repeats::Ignore);
1060             if (next != dt)
1061             {
1062                 QString prompt = dateOnly ? i18nc("@info The parameter is a date value",
1063                                                   "The start date does not match the alarm's recurrence pattern, "
1064                                                   "so it will be adjusted to the date of the next recurrence (%1).",
1065                                                   QLocale().toString(next.date(), QLocale::ShortFormat))
1066                                           : i18nc("@info The parameter is a date/time value",
1067                                                   "The start date/time does not match the alarm's recurrence pattern, "
1068                                                   "so it will be adjusted to the date/time of the next recurrence (%1).",
1069                                                   QLocale().toString(next.effectiveDateTime(), QLocale::ShortFormat));
1070                 if (KAMessageBox::warningContinueCancel(this, prompt) != KMessageBox::Continue)
1071                     return false;
1072             }
1073         }
1074 
1075         if (timedRecurrence)
1076         {
1077             KAEvent event;
1078             Resource res;
1079             getEvent(event, res);     // this may adjust mAlarmDateTime
1080             const KADateTime now = KADateTime::currentDateTime(mAlarmDateTime.timeSpec());
1081             bool dateOnly = mAlarmDateTime.isDateOnly();
1082             if ((dateOnly  &&  mAlarmDateTime.date() < now.date())
1083             ||  (!dateOnly  &&  mAlarmDateTime.kDateTime() < now))
1084             {
1085                 // A timed recurrence has an entered start date which
1086                 // has already expired, so we must adjust it.
1087                 if (event.nextOccurrence(now, mAlarmDateTime, KAEvent::Repeats::RecurBefore) == KAEvent::OccurType::None)
1088                 {
1089                     KAMessageBox::error(this, i18nc("@info", "Recurrence has already expired"));
1090                     return false;
1091                 }
1092                 if (event.workTimeOnly()  &&  !event.nextTrigger(KAEvent::Trigger::Display).isValid())
1093                 {
1094                     if (KAMessageBox::warningContinueCancel(this, i18nc("@info", "The alarm will never occur during working hours"))
1095                         != KMessageBox::Continue)
1096                         return false;
1097                 }
1098             }
1099         }
1100         QString errmsg;
1101         QWidget* errWidget = mRecurrenceEdit->checkData(mAlarmDateTime.effectiveKDateTime(), errmsg);
1102         if (errWidget)
1103         {
1104             mTabs->setCurrentIndex(mRecurPageIndex);
1105             errWidget->setFocus();
1106             KAMessageBox::error(this, errmsg);
1107             return false;
1108         }
1109     }
1110     if (recurType != RecurrenceEdit::NO_RECUR)
1111     {
1112         KAEvent recurEvent;
1113         int longestRecurMinutes = -1;
1114         int reminder = mReminder ? mReminder->minutes() : 0;
1115         if (reminder  &&  !mReminder->isOnceOnly())
1116         {
1117             mRecurrenceEdit->updateEvent(recurEvent, false);
1118             longestRecurMinutes = recurEvent.longestRecurrenceInterval().asSeconds() / 60;
1119             if (longestRecurMinutes  &&  reminder >= longestRecurMinutes)
1120             {
1121                 mTabs->setCurrentIndex(mMainPageIndex);
1122                 mReminder->setFocusOnCount();
1123                 KAMessageBox::error(this, xi18nc("@info", "Reminder period must be less than the recurrence interval, unless <interface>%1</interface> is checked.",
1124                                                 Reminder::i18n_chk_FirstRecurrenceOnly()));
1125                 return false;
1126             }
1127         }
1128         if (mRecurrenceEdit->subRepetition())
1129         {
1130             if (longestRecurMinutes < 0)
1131             {
1132                 mRecurrenceEdit->updateEvent(recurEvent, false);
1133                 longestRecurMinutes = recurEvent.longestRecurrenceInterval().asSeconds() / 60;
1134             }
1135             if (longestRecurMinutes > 0
1136             &&  recurEvent.repetition().intervalMinutes() * recurEvent.repetition().count() >= longestRecurMinutes - reminder)
1137             {
1138                 KAMessageBox::error(this, i18nc("@info", "The duration of a repetition within the recurrence must be less than the recurrence interval minus any reminder period"));
1139                 mRecurrenceEdit->activateSubRepetition();   // display the alarm repetition dialog again
1140                 return false;
1141             }
1142             if (!recurEvent.repetition().isDaily()
1143             &&  ((mTemplate && mTemplateAnyTime->isChecked())  ||  (!mTemplate && mAlarmDateTime.isDateOnly())))
1144             {
1145                 KAMessageBox::error(this, i18nc("@info", "For a repetition within the recurrence, its period must be in units of days or weeks for a date-only alarm"));
1146                 mRecurrenceEdit->activateSubRepetition();   // display the alarm repetition dialog again
1147                 return false;
1148             }
1149         }
1150     }
1151     if (!checkText(mAlarmMessage))
1152         return false;
1153 
1154     mResource = Resource();
1155     // mUseResourceEventId = false indicates that the caller already knows
1156     // which resource to use.
1157     if (mUseResourceEventId)
1158     {
1159         if (!mResourceEventId.isEmpty())
1160         {
1161             mResource = Resources::resourceForEvent(mResourceEventId);
1162             if (mResource.isValid())
1163             {
1164                 CalEvent::Type type = mTemplate ? CalEvent::TEMPLATE : CalEvent::ACTIVE;
1165                 if (!(mResource.alarmTypes() & type))
1166                     mResource = Resource();   // event may have expired while dialog was open
1167             }
1168         }
1169         bool cancelled = false;
1170         CalEvent::Type type = mTemplate ? CalEvent::TEMPLATE : CalEvent::ACTIVE;
1171         if (!mResource.isWritable(type))
1172             mResource = Resources::destination(type, this, Resources::NoDestOption, &cancelled);
1173         if (!mResource.isValid())
1174         {
1175             if (!cancelled)
1176                 KAMessageBox::error(this, i18nc("@info", "You must select a calendar to save the alarm in"));
1177             return false;
1178         }
1179     }
1180     return true;
1181 }
1182 
1183 /******************************************************************************
1184 * Called when the Try button is clicked.
1185 * Display/execute the alarm immediately for the user to check its configuration.
1186 */
1187 void EditAlarmDlg::slotTry()
1188 {
1189     QString text;
1190     if (checkText(text))
1191     {
1192         if (!type_validate(true))
1193             return;
1194         KAEvent event;
1195         setEvent(event, text, true);
1196         if (!mNewAlarm  &&  !stateChanged())
1197         {
1198             // It's an existing alarm which hasn't been changed yet:
1199             // enable KALARM_UID environment variable to be set.
1200             event.setEventId(mEventId);
1201         }
1202         type_aboutToTry();
1203         void* result = theApp()->execAlarm(event, event.firstAlarm(), KAlarmApp::NoRecordCmdError | KAlarmApp::NoNotifyInhibit);
1204         type_executedTry(text, result);
1205     }
1206 }
1207 
1208 /******************************************************************************
1209 * Called when the Load Template button is clicked.
1210 * Prompt to select a template and initialise the dialog with its contents.
1211 */
1212 void EditAlarmDlg::slotHelp()
1213 {
1214     KAEvent::Action type;
1215     switch (mAlarmType)
1216     {
1217         case KAEvent::SubAction::File:
1218         case KAEvent::SubAction::Message:  type = KAEvent::Action::Display;  break;
1219         case KAEvent::SubAction::Command:  type = KAEvent::Action::Command;  break;
1220         case KAEvent::SubAction::Email:    type = KAEvent::Action::Email;  break;
1221         case KAEvent::SubAction::Audio:    type = KAEvent::Action::Audio;  break;
1222         default:
1223             return;
1224     }
1225     // Use AutoQPointer to guard against crash on application exit while
1226     // the dialogue is still open. It prevents double deletion (both on
1227     // deletion of EditAlarmDlg, and on return from this function).
1228     AutoQPointer<TemplatePickDlg> dlg = new TemplatePickDlg(type, this);
1229     if (dlg->exec() == QDialog::Accepted)
1230     {
1231         KAEvent event = dlg->selectedTemplate();
1232         initValues(event);
1233     }
1234 }
1235 
1236 /******************************************************************************
1237 * Called when the More Options or Fewer Options buttons are clicked.
1238 * Show/hide the optional options and swap the More/Less buttons, and save the
1239 * new setting as the default from now on.
1240 */
1241 void EditAlarmDlg::slotDefault()
1242 {
1243     showOptions(!mShowingMore);
1244     KConfigGroup config(KSharedConfig::openConfig(), EDIT_MORE_GROUP);
1245     config.writeEntry(EDIT_MORE_KEY, mShowingMore);
1246 }
1247 
1248 /******************************************************************************
1249 * Show/hide the optional options and swap the More/Less buttons.
1250 */
1251 void EditAlarmDlg::showOptions(bool more)
1252 {
1253     qCDebug(KALARM_LOG) << "EditAlarmDlg::showOptions:" << (more ? "More" : "Less");
1254     if (more)
1255     {
1256         mMoreOptions->show();
1257         mMoreLessButton->setText(i18nc("@action:Button", "Fewer Options <<"));
1258         mMoreLessButton->setToolTip(i18nc("@info:tooltip", "Show fewer options"));
1259     }
1260     else
1261     {
1262         mMoreOptions->hide();
1263         mMoreLessButton->setText(i18nc("@action:button", "More Options >>"));
1264         mMoreLessButton->setToolTip(i18nc("@info:tooltip", "Show more options"));
1265     }
1266     if (mTimeWidget)
1267         mTimeWidget->showMoreOptions(more);
1268     type_showOptions(more);
1269     mRecurrenceEdit->showMoreOptions(more);
1270     mShowingMore = more;
1271     QTimer::singleShot(0, this, &EditAlarmDlg::slotResize);   //NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
1272 }
1273 
1274 /******************************************************************************
1275 * Called when the Change deferral button is clicked.
1276 */
1277 void EditAlarmDlg::slotEditDeferral()
1278 {
1279     if (!mTimeWidget)
1280         return;
1281     bool limit = true;
1282     const Repetition repetition = mRecurrenceEdit->subRepetition();
1283     DateTime start = mSavedEvent->recurs() ? (mExpiredRecurrence ? DateTime() : mSavedEvent->mainDateTime())
1284                    : mTimeWidget->getDateTime(!repetition, !mExpiredRecurrence);
1285     if (!start.isValid())
1286     {
1287         if (!mExpiredRecurrence)
1288             return;
1289         limit = false;
1290     }
1291     const KADateTime now = KADateTime::currentUtcDateTime();
1292     if (limit)
1293     {
1294         if (repetition  &&  start < now)
1295         {
1296             // Sub-repetition - find the time of the next one
1297             int repeatNum = repetition.isDaily()
1298                            ? (start.daysTo(now) + repetition.intervalDays() - 1) / repetition.intervalDays()
1299                            : (start.secsTo(now) + repetition.intervalSeconds() - 1) / repetition.intervalSeconds();
1300             if (repeatNum > repetition.count())
1301             {
1302                 mTimeWidget->getDateTime();    // output the appropriate error message
1303                 return;
1304             }
1305             start = KADateTime(repetition.duration(repeatNum).end(start.qDateTime()));
1306         }
1307     }
1308 
1309     bool deferred = mDeferDateTime.isValid();
1310     // Use AutoQPointer to guard against crash on application exit while
1311     // the dialogue is still open. It prevents double deletion (both on
1312     // deletion of EditAlarmDlg, and on return from this function).
1313     AutoQPointer<DeferAlarmDlg> deferDlg = new DeferAlarmDlg((deferred ? mDeferDateTime : DateTime(now.addSecs(60).toTimeSpec(start.timeSpec()))),
1314                                                              start.isDateOnly(), deferred, this);
1315     deferDlg->setObjectName(QLatin1StringView("EditDeferDlg"));    // used by LikeBack
1316     if (limit)
1317     {
1318         // Don't allow deferral past the next recurrence
1319         int reminder = mReminder ? mReminder->minutes() : 0;
1320         if (reminder)
1321         {
1322             DateTime remindTime = start.addMins(-reminder);
1323             if (KADateTime::currentUtcDateTime() < remindTime)
1324                 start = remindTime;
1325         }
1326         deferDlg->setLimit(start.addSecs(-60));
1327     }
1328     if (deferDlg->exec() == QDialog::Accepted)
1329     {
1330         mDeferDateTime = deferDlg->getDateTime();
1331         mDeferTimeLabel->setText(mDeferDateTime.isValid() ? mDeferDateTime.formatLocale() : QString());
1332         contentsChanged();
1333     }
1334 }
1335 
1336 /******************************************************************************
1337 * Called when the main page is shown.
1338 * Sets the focus widget to the first edit field.
1339 */
1340 void EditAlarmDlg::slotShowMainPage()
1341 {
1342     if (!mMainPageShown)
1343     {
1344         if (mName)
1345             mName->setFocus();
1346         mMainPageShown = true;
1347     }
1348     else
1349     {
1350         // Set scroll position to top, since it otherwise jumps randomly
1351         auto main = static_cast<StackedScrollWidget*>(mTabs->widget(0));
1352         main->verticalScrollBar()->setValue(0);
1353     }
1354     if (mTimeWidget)
1355     {
1356         if (!mReadOnly  &&  mRecurPageShown  &&  mRecurrenceEdit->repeatType() == RecurrenceEdit::AT_LOGIN)
1357             mTimeWidget->setDateTime(mRecurrenceEdit->endDateTime());
1358         if (mReadOnly  ||  mRecurrenceEdit->isTimedRepeatType())
1359             mTimeWidget->setMinDateTime();             // don't set a minimum date/time
1360         else
1361             mTimeWidget->setMinDateTimeIsCurrent();    // set the minimum date/time to track the clock
1362     }
1363 }
1364 
1365 /******************************************************************************
1366 * Called when the recurrence edit page is shown.
1367 * The recurrence defaults are set to correspond to the start date.
1368 * The first time, for a new alarm, the recurrence end date is set according to
1369 * the alarm start time.
1370 */
1371 void EditAlarmDlg::slotShowRecurrenceEdit()
1372 {
1373     mRecurPageIndex = mTabs->currentIndex();
1374     if (!mReadOnly  &&  !mTemplate)
1375     {
1376         mAlarmDateTime = mTimeWidget->getDateTime(false, false);
1377         const KADateTime now = KADateTime::currentDateTime(mAlarmDateTime.timeSpec());
1378         bool expired = (mAlarmDateTime.effectiveKDateTime() < now);
1379         if (mRecurSetDefaultEndDate)
1380         {
1381             mRecurrenceEdit->setDefaultEndDate(expired ? now.date() : mAlarmDateTime.date());
1382             mRecurSetDefaultEndDate = false;
1383         }
1384         mRecurrenceEdit->setStartDate(mAlarmDateTime.date(), now.date());
1385         if (mRecurrenceEdit->repeatType() == RecurrenceEdit::AT_LOGIN)
1386             mRecurrenceEdit->setEndDateTime(expired ? now : mAlarmDateTime.kDateTime());
1387     }
1388     mRecurPageShown = true;
1389 }
1390 
1391 /******************************************************************************
1392 * Called when the recurrence type selection changes.
1393 * Enables/disables date-only alarms as appropriate.
1394 * Enables/disables controls depending on at-login setting.
1395 */
1396 void EditAlarmDlg::slotRecurTypeChange(int repeatType)
1397 {
1398     bool atLogin = (mRecurrenceEdit->repeatType() == RecurrenceEdit::AT_LOGIN);
1399     if (!mTemplate)
1400     {
1401         bool recurs = (mRecurrenceEdit->repeatType() != RecurrenceEdit::NO_RECUR);
1402         if (mDeferGroup)
1403             mDeferGroup->setEnabled(recurs);
1404         mTimeWidget->enableAnyTime(!recurs || repeatType != RecurrenceEdit::SUBDAILY);
1405         if (atLogin)
1406         {
1407             mAlarmDateTime = mTimeWidget->getDateTime(false, false);
1408             mRecurrenceEdit->setEndDateTime(mAlarmDateTime.kDateTime());
1409         }
1410         if (mReminder)
1411             mReminder->enableOnceOnly(recurs && !atLogin);
1412     }
1413     if (mReminder)
1414         mReminder->setAfterOnly(atLogin);
1415     mLateCancel->setEnabled(!atLogin);
1416     setWakeFromSuspendEnabledStatus();
1417     if (mShowInKorganizer)
1418         mShowInKorganizer->setEnabled(!atLogin);
1419     slotRecurFrequencyChange();
1420 }
1421 
1422 /******************************************************************************
1423 * Called when the recurrence frequency selection changes, or the sub-
1424 * repetition interval changes.
1425 * Updates the recurrence frequency text.
1426 */
1427 void EditAlarmDlg::slotRecurFrequencyChange()
1428 {
1429     slotSetSubRepetition();
1430     KAEvent event;
1431     mRecurrenceEdit->updateEvent(event, false);
1432     mTabs->setTabText(mRecurPageIndex, recurText(event));
1433 }
1434 
1435 /******************************************************************************
1436 * Called when the Repetition within Recurrence button has been pressed to
1437 * display the sub-repetition dialog.
1438 * Alarm repetition has the following restrictions:
1439 * 1) Not allowed for a repeat-at-login alarm
1440 * 2) For a date-only alarm, the repeat interval must be a whole number of days.
1441 * 3) The overall repeat duration must be less than the recurrence interval.
1442 */
1443 void EditAlarmDlg::slotSetSubRepetition()
1444 {
1445     bool dateOnly = mTemplate ? mTemplateAnyTime->isChecked() : mTimeWidget->anyTime();
1446     mRecurrenceEdit->setSubRepetition((mReminder ? mReminder->minutes() : 0), dateOnly);
1447 }
1448 
1449 /******************************************************************************
1450 * Called when one of the template time radio buttons is clicked,
1451 * to enable or disable the template time entry spin boxes.
1452 */
1453 void EditAlarmDlg::slotTemplateTimeType(QAbstractButton*)
1454 {
1455     mTemplateTime->setEnabled(mTemplateUseTime->isChecked());
1456     mTemplateTimeAfter->setEnabled(mTemplateUseTimeAfter->isChecked());
1457     slotAnyTimeToggled(mTemplateAnyTime && mTemplateAnyTime->isChecked());
1458 }
1459 
1460 /******************************************************************************
1461 * Called when the "Any time" checkbox is toggled in the date/time widget.
1462 * Sets the advance reminder and late cancel units to days if any time is checked.
1463 */
1464 void EditAlarmDlg::slotAnyTimeToggled(bool anyTime)
1465 {
1466     if (mReminder)
1467         mReminder->setDateOnly(anyTime);
1468     mLateCancel->setDateOnly(anyTime);
1469     setWakeFromSuspendEnabledStatus();
1470 }
1471 
1472 /******************************************************************************
1473 * Enable or disable the wake-from-suspend checkbox as appropriate.
1474 */
1475 void EditAlarmDlg::setWakeFromSuspendEnabledStatus()
1476 {
1477     if (mWakeFromSuspend)
1478     {
1479         bool atLogin = (mRecurrenceEdit->repeatType() == RecurrenceEdit::AT_LOGIN);
1480         bool anyTime = (mTimeWidget && mTimeWidget->anyTimeSelected())
1481                    ||  (mTemplateAnyTime && mTemplateAnyTime->isChecked());
1482         mWakeFromSuspend->setEnabled(!atLogin && !anyTime);
1483     }
1484 }
1485 
1486 bool EditAlarmDlg::dateOnly() const
1487 {
1488     return mTimeWidget ? mTimeWidget->anyTime() : mTemplateAnyTime->isChecked();
1489 }
1490 
1491 bool EditAlarmDlg::isTimedRecurrence() const
1492 {
1493     return mRecurrenceEdit->isTimedRepeatType();
1494 }
1495 
1496 void EditAlarmDlg::showMainPage()
1497 {
1498     mTabs->setCurrentIndex(mMainPageIndex);
1499 }
1500 
1501 #include "moc_editdlg.cpp"
1502 #include "moc_editdlg_p.cpp"
1503 
1504 // vim: et sw=4: