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

0001 /*
0002  *  alarmtimewidget.cpp  -  alarm date/time entry widget
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 "alarmtimewidget.h"
0010 
0011 #include "preferences.h"
0012 #include "lib/buttongroup.h"
0013 #include "lib/checkbox.h"
0014 #include "lib/messagebox.h"
0015 #include "lib/pushbutton.h"
0016 #include "lib/radiobutton.h"
0017 #include "lib/synchtimer.h"
0018 #include "lib/timeedit.h"
0019 #include "lib/timeperiod.h"
0020 #include "lib/timespinbox.h"
0021 #include "lib/timezonecombo.h"
0022 
0023 #include <KDateComboBox>
0024 #include <KLocalizedString>
0025 
0026 #include <QTimeZone>
0027 #include <QGroupBox>
0028 #include <QGridLayout>
0029 #include <QLabel>
0030 #include <QHBoxLayout>
0031 #include <QStandardItemModel>
0032 
0033 //clazy:excludeall=non-pod-global-static
0034 
0035 static const QTime time_23_59(23, 59);
0036 
0037 
0038 const int AlarmTimeWidget::maxDelayTime = 999*60 + 59;    // < 1000 hours
0039 
0040 QString AlarmTimeWidget::i18n_TimeAfterPeriod()
0041 {
0042     return i18nc("@info", "Enter the length of time (in hours and minutes) after "
0043                 "the current time to schedule the alarm.");
0044 }
0045 
0046 
0047 /******************************************************************************
0048 * Construct a widget with a group box and title.
0049 */
0050 AlarmTimeWidget::AlarmTimeWidget(const QString& groupBoxTitle, Mode mode, QWidget* parent)
0051     : QFrame(parent)
0052 {
0053     init(mode, groupBoxTitle);
0054 }
0055 
0056 /******************************************************************************
0057 * Construct a widget without a group box or title.
0058 */
0059 AlarmTimeWidget::AlarmTimeWidget(Mode mode, QWidget* parent)
0060     : QFrame(parent)
0061 {
0062     init(mode);
0063 }
0064 
0065 void AlarmTimeWidget::init(Mode mode, const QString& title)
0066 {
0067     static const QString recurText = i18nc("@info",
0068                                            "If a recurrence is configured, the start date/time will be adjusted "
0069                                            "to the first recurrence on or after the entered date/time.");
0070     static const QString tzText = i18nc("@info",
0071                                         "This uses KAlarm's default time zone, set in the Configuration dialog.");
0072 
0073     QWidget* topWidget;
0074     if (title.isEmpty())
0075         topWidget = this;
0076     else
0077     {
0078         QBoxLayout* layout = new QVBoxLayout(this);
0079         layout->setContentsMargins(0, 0, 0, 0);
0080         layout->setSpacing(0);
0081         topWidget = new QGroupBox(title, this);
0082         layout->addWidget(topWidget);
0083     }
0084     mDeferring = mode & DEFER_TIME;
0085     mButtonGroup = new ButtonGroup(this);
0086     connect(mButtonGroup, &ButtonGroup::buttonSet, this, &AlarmTimeWidget::slotButtonSet);
0087     auto topLayout = new QVBoxLayout(topWidget);
0088     if (title.isEmpty())
0089         topLayout->setContentsMargins(0, 0, 0, 0);
0090 
0091     // At time radio button/label
0092     mAtTimeRadio = new RadioButton((mDeferring ? i18nc("@option:radio", "Defer until:") : i18nc("@option:radio", "At date/time:")), topWidget);
0093     mAtTimeRadio->setWhatsThis(mDeferring ? i18nc("@info:whatsthis", "Reschedule the alarm to the specified date and time.")
0094                                           : i18nc("@info:whatsthis", "Specify the date, or date and time, to schedule the alarm."));
0095     mButtonGroup->addButton(mAtTimeRadio);
0096 
0097     // Date edit box
0098     mDateEdit = new KDateComboBox(topWidget);
0099     mDateEdit->setOptions(KDateComboBox::EditDate | KDateComboBox::SelectDate | KDateComboBox::DatePicker);
0100     connect(mDateEdit, &KDateComboBox::dateChanged, this, &AlarmTimeWidget::dateTimeChanged);
0101     connect(mDateEdit, &KDateComboBox::dateEdited, this, &AlarmTimeWidget::dateTimeChanged);
0102     mDateEdit->setWhatsThis(xi18nc("@info:whatsthis",
0103                                    "<para>Enter the date to schedule the alarm.</para>"
0104                                    "<para>%1</para>", (mDeferring ? tzText : recurText)));
0105     mAtTimeRadio->setFocusWidget(mDateEdit);
0106 
0107     // Time edit box and Any time checkbox
0108     QWidget* timeBox = new QWidget(topWidget);
0109     auto timeBoxHLayout = new QHBoxLayout(timeBox);
0110     timeBoxHLayout->setContentsMargins(0, 0, 0, 0);
0111     mTimeEdit = new TimeEdit(timeBox);
0112     timeBoxHLayout->addWidget(mTimeEdit);
0113     connect(mTimeEdit, &TimeEdit::valueChanged, this, &AlarmTimeWidget::dateTimeChanged);
0114     mTimeEdit->setWhatsThis(xi18nc("@info:whatsthis",
0115                                    "<para>Enter the time to schedule the alarm.</para>"
0116                                    "<para>%1</para>"
0117                                    "<para>%2</para>", (mDeferring ? tzText : recurText), TimeSpinBox::shiftWhatsThis()));
0118 
0119     mAnyTime = -1;    // current status is uninitialised
0120     if (mode == DEFER_TIME)
0121     {
0122         mAnyTimeAllowed = false;
0123         mAnyTimeCheckBox = nullptr;
0124     }
0125     else
0126     {
0127         mAnyTimeAllowed = true;
0128         mAnyTimeCheckBox = new CheckBox(i18nc("@option:check", "Any time"), timeBox);
0129         timeBoxHLayout->addWidget(mAnyTimeCheckBox);
0130         connect(mAnyTimeCheckBox, &CheckBox::toggled, this, &AlarmTimeWidget::slotAnyTimeToggled);
0131         mAnyTimeCheckBox->setToolTip(i18nc("@info:tooltip", "Set only a date (without a time) for the alarm"));
0132         mAnyTimeCheckBox->setWhatsThis(i18nc("@info:whatsthis",
0133               "Check to specify only a date (without a time) for the alarm. The alarm will trigger at the first opportunity on the selected date."));
0134     }
0135 
0136     // 'Time from now' radio button/label
0137     mAfterTimeRadio = new RadioButton((mDeferring ? i18nc("@option:radio Defer for time interval", "Defer for:") : i18nc("@option:radio", "Time from now:")), topWidget);
0138     mAfterTimeRadio->setWhatsThis(mDeferring ? i18nc("@info:whatsthis", "Reschedule the alarm for the specified time interval after now.")
0139                                              : i18nc("@info:whatsthis", "Schedule the alarm after the specified time interval from now."));
0140     mButtonGroup->addButton(mAfterTimeRadio);
0141 
0142     if (mDeferring)
0143     {
0144         // Delay time period
0145         mDelayTimePeriod = new TimePeriod(TimePeriod::ShowMinutes, topWidget);
0146         mDelayTimePeriod->setPeriod(0, false, TimePeriod::HoursMinutes);
0147         connect(mDelayTimePeriod, &TimePeriod::valueChanged,
0148                 this, [this](const KCalendarCore::Duration& period) { delayTimeChanged(period.asSeconds()/60); });
0149         mDelayTimePeriod->setWhatsThis(xi18nc("@info:whatsthis", "<para>%1</para><para>%2</para>", i18n_TimeAfterPeriod(), TimeSpinBox::shiftWhatsThis()));
0150         mAfterTimeRadio->setFocusWidget(mDelayTimePeriod);
0151 
0152         // Delay presets
0153         mPresetsCombo = new ComboBox(topWidget);
0154         mPresetsCombo->setEditable(false);
0155         mPresetsCombo->setPlaceholderText(i18nc("@item:inlistbox", "Preset"));
0156         const KLocalizedString minutesText = ki18ncp("@item:inlistbox", "1 minute", "%1 minutes");
0157         const KLocalizedString hoursText   = ki18ncp("@item:inlistbox", "1 hour", "%1 hours");
0158         mPresetsCombo->addItem(minutesText.subs(5).toString(),   5);
0159         mPresetsCombo->addItem(minutesText.subs(10).toString(), 10);
0160         mPresetsCombo->addItem(minutesText.subs(15).toString(), 15);
0161         mPresetsCombo->addItem(minutesText.subs(30).toString(), 30);
0162         mPresetsCombo->addItem(minutesText.subs(45).toString(), 45);
0163         mPresetsCombo->addItem(hoursText.subs(1).toString(),    60);
0164         mPresetsCombo->addItem(hoursText.subs(3).toString(),   180);
0165         mPresetsCombo->addItem(i18nc("@item:inlistbox", "1 day"), 1440);
0166         mPresetsCombo->addItem(i18nc("@item:inlistbox", "1 week"), 7*1440);
0167         mPresetsCombo->setCurrentIndex(-1);
0168         connect(mPresetsCombo, &ComboBox::activated, this, &AlarmTimeWidget::slotPresetSelected);
0169         mPresetsCombo->setToolTip(i18nc("@info:tooltip", "Select time delay from a list of preset values"));
0170     }
0171     else
0172     {
0173         // Delay time spin box
0174         mDelayTimeEdit = new TimeSpinBox(1, maxDelayTime, topWidget);
0175         mDelayTimeEdit->setValue(1439);
0176         connect(mDelayTimeEdit, &TimeSpinBox::valueChanged, this, &AlarmTimeWidget::delayTimeChanged);
0177         mDelayTimeEdit->setWhatsThis(xi18nc("@info:whatsthis", "<para>%1</para><para>%2</para><para>%3</para>", i18n_TimeAfterPeriod(), recurText, TimeSpinBox::shiftWhatsThis()));
0178         mAfterTimeRadio->setFocusWidget(mDelayTimeEdit);
0179     }
0180 
0181     // Set up the layout
0182     auto grid = new QGridLayout();
0183     grid->setContentsMargins(0, 0, 0, 0);
0184     topLayout->addLayout(grid);
0185     const int atRow    = mDeferring ? 2 : 0;
0186     const int afterRow = mDeferring ? 0 : 2;
0187     grid->addWidget(mAtTimeRadio, atRow, 0, Qt::AlignLeft);
0188     auto hLayout = new QHBoxLayout;
0189     hLayout->addWidget(mDateEdit);
0190     hLayout->addSpacing(style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
0191     hLayout->addWidget(timeBox);
0192     grid->addLayout(hLayout, atRow, 1, Qt::AlignLeft);
0193     grid->setRowStretch(1, 1);
0194     grid->addWidget(mAfterTimeRadio, afterRow, 0, Qt::AlignLeft);
0195     if (mDelayTimeEdit)
0196         grid->addWidget(mDelayTimeEdit, 2, 1, Qt::AlignLeft);
0197     else
0198     {
0199         hLayout = new QHBoxLayout;
0200         hLayout->addWidget(mDelayTimePeriod);
0201         hLayout->addSpacing(2 * style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
0202         hLayout->addWidget(mPresetsCombo);
0203         grid->addLayout(hLayout, 0, 1, Qt::AlignLeft);
0204     }
0205 
0206     if (!mDeferring)
0207     {
0208         // Time zone selection push button
0209         mTimeZoneButton = new PushButton(i18nc("@action:button", "Time Zone..."), topWidget);
0210         connect(mTimeZoneButton, &PushButton::clicked, this, &AlarmTimeWidget::showTimeZoneSelector);
0211         mTimeZoneButton->setToolTip(i18nc("@info:tooltip", "Choose a time zone for this alarm"));
0212         mTimeZoneButton->setWhatsThis(i18nc("@info:whatsthis",
0213               "Choose a time zone for this alarm which is different from the default time zone set in KAlarm's configuration dialog."));
0214         grid->addWidget(mTimeZoneButton, 2, 2, 1, 2, Qt::AlignRight);
0215 
0216         grid->setColumnStretch(2, 1);
0217         topLayout->addStretch();
0218 
0219         auto layout = new QHBoxLayout();
0220         topLayout->addLayout(layout);
0221         layout->setSpacing(2 * style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
0222 
0223         // Time zone selector
0224         mTimeZoneBox = new QWidget(topWidget);   // this is to control the QWhatsThis text display area
0225         auto hlayout = new QHBoxLayout(mTimeZoneBox);
0226         hlayout->setContentsMargins(0, 0, 0, 0);
0227         QLabel* label = new QLabel(i18nc("@label:listbox", "Time zone:"), mTimeZoneBox);
0228         hlayout->addWidget(label);
0229         mTimeZone = new TimeZoneCombo(mTimeZoneBox);
0230         hlayout->addWidget(mTimeZone);
0231         mTimeZone->setMaxVisibleItems(15);
0232         connect(mTimeZone, &TimeZoneCombo::activated, this, &AlarmTimeWidget::slotTimeZoneChanged);
0233         mTimeZoneBox->setWhatsThis(i18nc("@info:whatsthis", "Select the time zone to use for this alarm."));
0234         label->setBuddy(mTimeZone);
0235         layout->addWidget(mTimeZoneBox);
0236         layout->addStretch();
0237 
0238         // Initially show only the time zone button, not time zone selector
0239         mTimeZoneBox->hide();
0240     }
0241 
0242     // Initialise the radio button statuses
0243     mAtTimeRadio->setChecked(true);
0244     slotButtonSet(mAtTimeRadio);
0245 
0246     // Timeout every minute to update alarm time fields.
0247     MinuteTimer::connect(this, SLOT(updateTimes()));
0248 }
0249 
0250 /******************************************************************************
0251 * Set or clear read-only status for the controls
0252 */
0253 void AlarmTimeWidget::setReadOnly(bool ro)
0254 {
0255     mAtTimeRadio->setReadOnly(ro);
0256     mDateEdit->setOptions(ro ? KDateComboBox::Options{} : KDateComboBox::EditDate | KDateComboBox::SelectDate | KDateComboBox::DatePicker);
0257     mTimeEdit->setReadOnly(ro);
0258     if (mAnyTimeCheckBox)
0259         mAnyTimeCheckBox->setReadOnly(ro);
0260     mAfterTimeRadio->setReadOnly(ro);
0261     if (!mDeferring)
0262         mTimeZone->setReadOnly(ro);
0263     if (mDelayTimeEdit)
0264         mDelayTimeEdit->setReadOnly(ro);
0265     else
0266         mDelayTimePeriod->setReadOnly(ro);
0267 }
0268 
0269 /******************************************************************************
0270 * Select the "Time from now" radio button, or if minutes < 0, select
0271 * 'At date/time'.
0272 */
0273 void AlarmTimeWidget::selectTimeFromNow(int minutes)
0274 {
0275     if (minutes >= 0)
0276     {
0277         mAfterTimeRadio->setChecked(true);
0278         if (minutes > 0)
0279         {
0280             if (mDelayTimeEdit)
0281                 mDelayTimeEdit->setValue(minutes);
0282             else
0283                 mDelayTimePeriod->setMinutes(minutes);
0284         }
0285     }
0286     else
0287         mAtTimeRadio->setChecked(true);
0288 }
0289 
0290 /******************************************************************************
0291 * Fetch the entered date/time.
0292 * If 'checkExpired' is true and the entered value <= current time, an error occurs.
0293 * If 'minsFromNow' is non-null, it is set to the number of minutes' delay selected,
0294 * or to zero if a date/time was entered.
0295 * In this case, if 'showErrorMessage' is true, output an error message.
0296 * 'errorWidget' if non-null, is set to point to the widget containing the error.
0297 * Reply = invalid date/time if error.
0298 */
0299 KADateTime AlarmTimeWidget::getDateTime(int* minsFromNow, bool checkExpired, bool showErrorMessage, QWidget** errorWidget) const
0300 {
0301     if (minsFromNow)
0302         *minsFromNow = 0;
0303     if (errorWidget)
0304         *errorWidget = nullptr;
0305     KADateTime now = KADateTime::currentUtcDateTime();
0306     now.setTime(QTime(now.time().hour(), now.time().minute(), 0));
0307     if (!mAtTimeRadio->isChecked())
0308     {
0309         // A relative time has been entered.
0310         if ((mDelayTimeEdit  &&  !mDelayTimeEdit->isValid())
0311         ||  (mDelayTimePeriod  &&  !mDelayTimePeriod->isValid()))
0312         {
0313             if (showErrorMessage)
0314                 KAMessageBox::error(const_cast<AlarmTimeWidget*>(this), i18nc("@info", "Invalid time"));
0315             if (errorWidget)
0316                 *errorWidget = mDelayTimeEdit ? (QWidget*)mDelayTimeEdit : (QWidget*)mDelayTimePeriod;
0317             return {};
0318         }
0319         const int delayMins = mDelayTimeEdit ? mDelayTimeEdit->value() : mDelayTimePeriod->minutes();
0320         if (minsFromNow)
0321             *minsFromNow = delayMins;
0322         return now.addSecs(delayMins * 60).toTimeSpec(mTimeSpec);
0323     }
0324     else
0325     {
0326         // An absolute time has been entered.
0327         const bool dateOnly = mAnyTimeAllowed && mAnyTimeCheckBox && mAnyTimeCheckBox->isChecked();
0328         if (!mDateEdit->date().isValid()  ||  !mTimeEdit->isValid())
0329         {
0330             // The date and/or time is invalid
0331             if (!mDateEdit->date().isValid())
0332             {
0333                 if (showErrorMessage)
0334                     KAMessageBox::error(const_cast<AlarmTimeWidget*>(this), i18nc("@info", "Invalid date"));
0335                 if (errorWidget)
0336                     *errorWidget = mDateEdit;
0337             }
0338             else
0339             {
0340                 if (showErrorMessage)
0341                     KAMessageBox::error(const_cast<AlarmTimeWidget*>(this), i18nc("@info", "Invalid time"));
0342                 if (errorWidget)
0343                     *errorWidget = mTimeEdit;
0344             }
0345             return {};
0346         }
0347 
0348         KADateTime result;
0349         if (dateOnly)
0350         {
0351             result = KADateTime(mDateEdit->date(), mTimeSpec);
0352             if (checkExpired  &&  result.date() < now.date())
0353             {
0354                 if (showErrorMessage)
0355                     KAMessageBox::error(const_cast<AlarmTimeWidget*>(this), i18nc("@info", "Alarm date has already expired"));
0356                 if (errorWidget)
0357                     *errorWidget = mDateEdit;
0358                 return {};
0359             }
0360         }
0361         else
0362         {
0363             result = KADateTime(mDateEdit->date(), mTimeEdit->time(), mTimeSpec);
0364             if (checkExpired  &&  result <= now.addSecs(1))
0365             {
0366                 if (showErrorMessage)
0367                     KAMessageBox::error(const_cast<AlarmTimeWidget*>(this), i18nc("@info", "Alarm time has already expired"));
0368                 if (errorWidget)
0369                     *errorWidget = mTimeEdit;
0370                 return {};
0371             }
0372         }
0373         return result;
0374     }
0375 }
0376 
0377 /******************************************************************************
0378 * Set the date/time.
0379 */
0380 void AlarmTimeWidget::setDateTime(const DateTime& dt)
0381 {
0382     // Set the time zone first so that the call to dateTimeChanged() works correctly.
0383     if (mDeferring)
0384         mTimeSpec = dt.timeSpec().isValid() ? dt.timeSpec() : KADateTime::LocalZone;
0385     else
0386     {
0387         const QTimeZone tz = (dt.timeSpec() == KADateTime::LocalZone) ? QTimeZone() : dt.timeZone();
0388         mTimeZone->setTimeZone(tz);
0389         slotTimeZoneChanged();
0390     }
0391 
0392     if (dt.date().isValid())
0393     {
0394         mTimeEdit->setValue(dt.effectiveTime());
0395         mDateEdit->setDate(dt.date());
0396         dateTimeChanged();     // update the delay time edit box
0397     }
0398     else
0399     {
0400         mTimeEdit->setValid(false);
0401         mDateEdit->setDate(QDate());
0402         if (mDelayTimeEdit)
0403             mDelayTimeEdit->setValid(false);
0404         else
0405             mDelayTimePeriod->setValid(false);
0406     }
0407     if (mAnyTimeCheckBox)
0408     {
0409         const bool dateOnly = dt.isDateOnly();
0410         if (dateOnly)
0411             mAnyTimeAllowed = true;
0412         mAnyTimeCheckBox->setChecked(dateOnly);
0413         setAnyTime();
0414     }
0415 }
0416 
0417 /******************************************************************************
0418 * Set the minimum date/time to track the current time.
0419 */
0420 void AlarmTimeWidget::setMinDateTimeIsCurrent()
0421 {
0422     mMinDateTimeIsNow = true;
0423     mMinDateTime = KADateTime();
0424     const KADateTime now = KADateTime::currentDateTime(mTimeSpec);
0425     mDateEdit->setMinimumDate(now.date());
0426     setMaxMinTimeIf(now);
0427 }
0428 
0429 /******************************************************************************
0430 * Set the minimum date/time, adjusting the entered date/time if necessary.
0431 * If 'dt' is invalid, any current minimum date/time is cleared.
0432 */
0433 void AlarmTimeWidget::setMinDateTime(const KADateTime& dt)
0434 {
0435     mMinDateTimeIsNow = false;
0436     mMinDateTime = dt.toTimeSpec(mTimeSpec);
0437     mDateEdit->setMinimumDate(mMinDateTime.date());
0438     setMaxMinTimeIf(KADateTime::currentDateTime(mTimeSpec));
0439 }
0440 
0441 /******************************************************************************
0442 * Set the maximum date/time, adjusting the entered date/time if necessary.
0443 * If 'dt' is invalid, any current maximum date/time is cleared.
0444 */
0445 void AlarmTimeWidget::setMaxDateTime(const DateTime& dt)
0446 {
0447     mPastMax = false;
0448     if (dt.isValid()  &&  dt.isDateOnly())
0449         mMaxDateTime = dt.effectiveKDateTime().addSecs(24*3600 - 60).toTimeSpec(mTimeSpec);
0450     else
0451         mMaxDateTime = dt.kDateTime().toTimeSpec(mTimeSpec);
0452     mDateEdit->setMaximumDate(mMaxDateTime.date());
0453     const KADateTime now = KADateTime::currentDateTime(mTimeSpec);
0454     setMaxMinTimeIf(now);
0455     setMaxDelayTime(now);
0456 }
0457 
0458 /******************************************************************************
0459 * If the minimum and maximum date/times fall on the same date, set the minimum
0460 * and maximum times in the time edit box.
0461 */
0462 void AlarmTimeWidget::setMaxMinTimeIf(const KADateTime& now)
0463 {
0464     int   mint = 0;
0465     QTime maxt = time_23_59;
0466     mMinMaxTimeSet = false;
0467     if (mMaxDateTime.isValid())
0468     {
0469         bool set = true;
0470         KADateTime minDT;
0471         if (mMinDateTimeIsNow)
0472             minDT = now.addSecs(60);
0473         else if (mMinDateTime.isValid())
0474             minDT = mMinDateTime;
0475         else
0476             set = false;
0477         if (set  &&  mMaxDateTime.date() == minDT.date())
0478         {
0479             // The minimum and maximum times are on the same date, so
0480             // constrain the time value.
0481             mint = minDT.time().hour()*60 + minDT.time().minute();
0482             maxt = mMaxDateTime.time();
0483             mMinMaxTimeSet = true;
0484         }
0485     }
0486     mTimeEdit->setMinimum(mint);
0487     mTimeEdit->setMaximum(maxt);
0488     mTimeEdit->setWrapping(!mint  &&  maxt == time_23_59);
0489 }
0490 
0491 /******************************************************************************
0492 * Set the maximum value for the delay time edit box, depending on the maximum
0493 * value for the date/time.
0494 */
0495 void AlarmTimeWidget::setMaxDelayTime(const KADateTime& now)
0496 {
0497     int maxVal = maxDelayTime;
0498     if (mMaxDateTime.isValid())
0499     {
0500         if (now.date().daysTo(mMaxDateTime.date()) < 100)    // avoid possible 32-bit overflow on secsTo()
0501         {
0502             KADateTime dt(now);
0503             dt.setTime(QTime(now.time().hour(), now.time().minute(), 0));   // round down to nearest minute
0504             maxVal = dt.secsTo(mMaxDateTime) / 60;
0505             if (maxVal > maxDelayTime)
0506                 maxVal = maxDelayTime;
0507         }
0508     }
0509     if (mDelayTimeEdit)
0510         mDelayTimeEdit->setMaximum(maxVal);
0511     else
0512     {
0513         mDelayTimePeriod->setMaxMinutes(maxVal);
0514         // Disable all presets greater than the maximum delay minutes
0515         auto* model = qobject_cast<QStandardItemModel*>(mPresetsCombo->model());
0516         if (model)
0517         {
0518             for (int i = 0, count = mPresetsCombo->count();  i < count;  ++i)
0519             {
0520                 const int minutes = mPresetsCombo->itemData(i).toInt();
0521                 model->item(i)->setEnabled(minutes <= maxVal);
0522             }
0523         }
0524     }
0525 }
0526 
0527 /******************************************************************************
0528 * Set the status for whether a time is specified, or just a date.
0529 */
0530 void AlarmTimeWidget::setAnyTime()
0531 {
0532     const int old = mAnyTime;
0533     mAnyTime = (mAtTimeRadio->isChecked() && mAnyTimeAllowed && mAnyTimeCheckBox && mAnyTimeCheckBox->isChecked()) ? 1 : 0;
0534     if (mAnyTime != old)
0535         Q_EMIT dateOnlyToggled(mAnyTime);
0536 }
0537 
0538 /******************************************************************************
0539 * Return whether the "date only" checkbox is selected.
0540 */
0541 bool AlarmTimeWidget::anyTimeSelected() const
0542 {
0543     return mAnyTimeCheckBox && mAnyTimeCheckBox->isChecked();
0544 }
0545 
0546 /******************************************************************************
0547 * Enable/disable the "date only" checkbox.
0548 */
0549 void AlarmTimeWidget::enableAnyTime(bool enable)
0550 {
0551     if (mAnyTimeCheckBox)
0552     {
0553         mAnyTimeAllowed = enable;
0554         const bool at = mAtTimeRadio->isChecked();
0555         mAnyTimeCheckBox->setEnabled(enable && at);
0556         if (at)
0557             mTimeEdit->setEnabled(!enable || !mAnyTimeCheckBox->isChecked());
0558         setAnyTime();
0559     }
0560 }
0561 
0562 /******************************************************************************
0563 * Set the keyboard focus to the time from now field.
0564 */
0565 void AlarmTimeWidget::focusTimeFromNow()
0566 {
0567     if (!mAtTimeRadio->isChecked())
0568     {
0569         if (mDelayTimeEdit)
0570             mDelayTimeEdit->setFocus();
0571         else
0572             mDelayTimePeriod->setFocus();
0573     }
0574 }
0575 
0576 /******************************************************************************
0577 * Called every minute to update the alarm time data entry fields.
0578 * If the maximum date/time has been reached, a 'pastMax()' signal is emitted.
0579 */
0580 void AlarmTimeWidget::updateTimes()
0581 {
0582     KADateTime now;
0583     if (mMinDateTimeIsNow)
0584     {
0585         // Make sure that the minimum date is updated when the day changes
0586         now = KADateTime::currentDateTime(mTimeSpec);
0587         mDateEdit->setMinimumDate(now.date());
0588     }
0589     if (mMaxDateTime.isValid())
0590     {
0591         if (!now.isValid())
0592             now = KADateTime::currentDateTime(mTimeSpec);
0593         if (!mPastMax)
0594         {
0595             // Check whether the maximum date/time has now been reached
0596             if (now.date() >= mMaxDateTime.date())
0597             {
0598                 // The current date has reached or has passed the maximum date
0599                 if (now.date() > mMaxDateTime.date()
0600                 ||  (!mAnyTime && now.time() > mTimeEdit->maxTime()))
0601                 {
0602                     mPastMax = true;
0603                     Q_EMIT pastMax();
0604                 }
0605                 else if (mMinDateTimeIsNow  &&  !mMinMaxTimeSet)
0606                 {
0607                     // The minimum date/time tracks the clock, so set the minimum
0608                     // and maximum times
0609                     setMaxMinTimeIf(now);
0610                 }
0611             }
0612         }
0613         setMaxDelayTime(now);
0614     }
0615 
0616     if (mAtTimeRadio->isChecked())
0617         dateTimeChanged();
0618     else
0619     {
0620         if (mDelayTimeEdit)
0621             delayTimeChanged(mDelayTimeEdit->value());
0622         else
0623             delayTimeChanged(mDelayTimePeriod->minutes());
0624     }
0625 }
0626 
0627 
0628 /******************************************************************************
0629 * Called when the radio button states have been changed.
0630 * Updates the appropriate edit box.
0631 */
0632 void AlarmTimeWidget::slotButtonSet(QAbstractButton*)
0633 {
0634     const bool at = mAtTimeRadio->isChecked();
0635     mDateEdit->setEnabled(at);
0636     mTimeEdit->setEnabled(at && (!mAnyTimeAllowed || !mAnyTimeCheckBox || !mAnyTimeCheckBox->isChecked()));
0637     if (mAnyTimeCheckBox)
0638         mAnyTimeCheckBox->setEnabled(at && mAnyTimeAllowed);
0639     // Ensure that the value of the delay edit box is > 0.
0640     const KADateTime att(mDateEdit->date(), mTimeEdit->time(), mTimeSpec);
0641     const int minutes = (KADateTime::currentUtcDateTime().secsTo(att) + 59) / 60;
0642     if (mDelayTimeEdit)
0643     {
0644         if (minutes <= 0)
0645             mDelayTimeEdit->setValid(true);
0646         mDelayTimeEdit->setEnabled(!at);
0647     }
0648     else
0649     {
0650         if (minutes <= 0)
0651             mDelayTimePeriod->setValid(true);
0652         mDelayTimePeriod->setEnabled(!at);
0653         mPresetsCombo->setEnabled(!at);
0654         QPalette pal = mPresetsCombo->palette();
0655         pal.setColor(QPalette::PlaceholderText, pal.color(at ? QPalette::Disabled : QPalette::Active, QPalette::Text));
0656         mPresetsCombo->setPalette(pal);
0657     }
0658     setAnyTime();
0659 }
0660 
0661 /******************************************************************************
0662 * Called after the mAnyTimeCheckBox checkbox has been toggled.
0663 */
0664 void AlarmTimeWidget::slotAnyTimeToggled(bool on)
0665 {
0666     on = (on && mAnyTimeAllowed);
0667     mTimeEdit->setEnabled(!on && mAtTimeRadio->isChecked());
0668     setAnyTime();
0669     if (on)
0670         Q_EMIT changed(KADateTime(mDateEdit->date(), mTimeSpec));
0671     else
0672         Q_EMIT changed(KADateTime(mDateEdit->date(), mTimeEdit->time(), mTimeSpec));
0673 }
0674 
0675 /******************************************************************************
0676 * Called after a new selection has been made in the time zone combo box.
0677 * Re-evaluates the time specification to use.
0678 */
0679 void AlarmTimeWidget::slotTimeZoneChanged()
0680 {
0681     const QTimeZone tz = mTimeZone->timeZone();
0682     mTimeSpec = tz.isValid() ? KADateTime::Spec(tz) : KADateTime::LocalZone;
0683     if (!mTimeZoneBox->isVisible()  &&  mTimeSpec != Preferences::timeSpec())
0684     {
0685         // The current time zone is not the default one, so
0686         // show the time zone selection controls
0687         showTimeZoneSelector();
0688     }
0689     mMinDateTime = mMinDateTime.toTimeSpec(mTimeSpec);
0690     mMaxDateTime = mMaxDateTime.toTimeSpec(mTimeSpec);
0691     updateTimes();
0692 }
0693 
0694 /******************************************************************************
0695 * Called after the mTimeZoneButton button has been clicked.
0696 * Show the time zone selection controls, and hide the button.
0697 */
0698 void AlarmTimeWidget::showTimeZoneSelector()
0699 {
0700     mTimeZoneButton->hide();
0701     mTimeZoneBox->show();
0702 }
0703 
0704 /******************************************************************************
0705 * Show or hide the time zone button.
0706 */
0707 void AlarmTimeWidget::showMoreOptions(bool more)
0708 {
0709     if (more)
0710     {
0711         if (!mTimeZoneBox->isVisible())
0712             mTimeZoneButton->show();
0713     }
0714     else
0715         mTimeZoneButton->hide();
0716 }
0717 
0718 /******************************************************************************
0719 * Called when the date or time edit box values have changed.
0720 * Updates the time delay edit box accordingly.
0721 */
0722 void AlarmTimeWidget::dateTimeChanged()
0723 {
0724     const KADateTime dt(mDateEdit->date(), mTimeEdit->time(), mTimeSpec);
0725     const int minutes = (KADateTime::currentUtcDateTime().secsTo(dt) + 59) / 60;
0726     if (mDelayTimeEdit)
0727     {
0728         const bool blocked = mDelayTimeEdit->signalsBlocked();
0729         mDelayTimeEdit->blockSignals(true);     // prevent infinite recursion between here and delayTimeChanged()
0730         if (minutes <= 0  ||  minutes > mDelayTimeEdit->maximum())
0731             mDelayTimeEdit->setValid(false);
0732         else
0733             mDelayTimeEdit->setValue(minutes);
0734         mDelayTimeEdit->blockSignals(blocked);
0735     }
0736     else
0737     {
0738         const bool blocked = mDelayTimePeriod->signalsBlocked();
0739         mDelayTimePeriod->blockSignals(true);     // prevent infinite recursion between here and delayTimeChanged()
0740         if (minutes <= 0  ||  minutes > mDelayTimePeriod->maxMinutes())
0741             mDelayTimePeriod->setValid(false);
0742         else
0743             mDelayTimePeriod->setMinutes(minutes);
0744         mDelayTimePeriod->blockSignals(blocked);
0745     }
0746     if (mAnyTimeAllowed && mAnyTimeCheckBox && mAnyTimeCheckBox->isChecked())
0747         Q_EMIT changed(KADateTime(dt.date(), mTimeSpec));
0748     else
0749         Q_EMIT changed(dt);
0750 }
0751 
0752 /******************************************************************************
0753 * Called when the delay time edit box value has changed.
0754 * Updates the Date and Time edit boxes accordingly.
0755 */
0756 void AlarmTimeWidget::delayTimeChanged(int minutes)
0757 {
0758     if ((mDelayTimeEdit  &&  mDelayTimeEdit->isValid())
0759     ||  (mDelayTimePeriod  &&  mDelayTimePeriod->isValid()))
0760     {
0761         QDateTime dt = KADateTime::currentUtcDateTime().addSecs(minutes * 60).toTimeSpec(mTimeSpec).qDateTime();
0762         const bool blockedT = mTimeEdit->signalsBlocked();
0763         const bool blockedD = mDateEdit->signalsBlocked();
0764         mTimeEdit->blockSignals(true);     // prevent infinite recursion between here and dateTimeChanged()
0765         mDateEdit->blockSignals(true);
0766         mTimeEdit->setValue(dt.time());
0767         mDateEdit->setDate(dt.date());
0768         mTimeEdit->blockSignals(blockedT);
0769         mDateEdit->blockSignals(blockedD);
0770         Q_EMIT changed(KADateTime(dt.date(), dt.time(), mTimeSpec));
0771     }
0772 }
0773 
0774 /******************************************************************************
0775 * Called when a new item is selected in the presets combo box.
0776 */
0777 void AlarmTimeWidget::slotPresetSelected(int index)
0778 {
0779     const int minutes = mPresetsCombo->itemData(index).toInt();
0780     if (minutes > 0)
0781     {
0782         switch (mDelayTimePeriod->units())
0783         {
0784             case TimePeriod::Minutes:
0785             case TimePeriod::HoursMinutes:
0786                 if (minutes < 1440)
0787                     mDelayTimePeriod->setMinutes(minutes);
0788                 else
0789                     mDelayTimePeriod->setMinutes(minutes, TimePeriod::Days);
0790                 break;
0791             case TimePeriod::Days:
0792             case TimePeriod::Weeks:
0793                 if (minutes >= 1440)
0794                     mDelayTimePeriod->setMinutes(minutes);
0795                 else
0796                     mDelayTimePeriod->setMinutes(minutes, TimePeriod::HoursMinutes);
0797                 break;
0798         }
0799     }
0800     mPresetsCombo->blockSignals(true);
0801     mPresetsCombo->setCurrentIndex(-1);
0802     mPresetsCombo->blockSignals(false);
0803 }
0804 
0805 #include "moc_alarmtimewidget.cpp"
0806 
0807 // vim: et sw=4: