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

0001 /*
0002  *  recurrenceedit.cpp  -  widget to edit the event's recurrence definition
0003  *  Program:  kalarm
0004  *  SPDX-FileCopyrightText: 2002-2022 David Jarvie <djarvie@kde.org>
0005  *
0006  *  Based originally on KOrganizer module koeditorrecurrence.cpp,
0007  *  SPDX-FileCopyrightText: 2000, 2001 Cornelius Schumacher <schumacher@kde.org>
0008  *
0009  *  SPDX-License-Identifier: GPL-2.0-or-later
0010  */
0011 
0012 #include "recurrenceedit.h"
0013 #include "recurrenceedit_p.h"
0014 
0015 #include "preferences.h"
0016 #include "repetitionbutton.h"
0017 #include "lib/buttongroup.h"
0018 #include "lib/checkbox.h"
0019 #include "lib/combobox.h"
0020 #include "lib/locale.h"
0021 #include "lib/radiobutton.h"
0022 #include "lib/spinbox.h"
0023 #include "lib/timeedit.h"
0024 #include "lib/timespinbox.h"
0025 #include "kalarmcalendar/kaevent.h"
0026 #include "kalarmcalendar/karecurrence.h"
0027 #include "kalarm_debug.h"
0028 
0029 #include <KCalendarCore/Event>
0030 
0031 #include <KLocalizedString>
0032 #include <KDateComboBox>
0033 
0034 #include <QHBoxLayout>
0035 #include <QPushButton>
0036 #include <QLabel>
0037 #include <QStackedWidget>
0038 #include <QListWidget>
0039 #include <QGroupBox>
0040 #include <QGridLayout>
0041 #include <QVBoxLayout>
0042 #include <QtAlgorithms>
0043 #include <QLocale>
0044 
0045 using namespace KCalendarCore;
0046 
0047 class ListWidget : public QListWidget
0048 {
0049     public:
0050         explicit ListWidget(QWidget* parent) : QListWidget(parent) {}
0051         QSize sizeHint() const override  { return minimumSizeHint(); }
0052 };
0053 
0054 // Collect these widget labels together to ensure consistent wording and
0055 // translations across different modules.
0056 QString RecurrenceEdit::i18n_combo_NoRecur()        { return i18nc("@item:inlistbox Recurrence type", "No Recurrence"); }
0057 QString RecurrenceEdit::i18n_combo_AtLogin()        { return i18nc("@item:inlistbox Recurrence type", "At Login"); }
0058 QString RecurrenceEdit::i18n_combo_HourlyMinutely() { return i18nc("@item:inlistbox Recurrence type", "Hourly/Minutely"); }
0059 QString RecurrenceEdit::i18n_combo_Daily()          { return i18nc("@item:inlistbox Recurrence type", "Daily"); }
0060 QString RecurrenceEdit::i18n_combo_Weekly()         { return i18nc("@item:inlistbox Recurrence type", "Weekly"); }
0061 QString RecurrenceEdit::i18n_combo_Monthly()        { return i18nc("@item:inlistbox Recurrence type", "Monthly"); }
0062 QString RecurrenceEdit::i18n_combo_Yearly()         { return i18nc("@item:inlistbox Recurrence type", "Yearly"); }
0063 
0064 
0065 RecurrenceEdit::RecurrenceEdit(bool readOnly, QWidget* parent)
0066     : QFrame(parent)
0067     , mReadOnly(readOnly)
0068 {
0069     qCDebug(KALARM_LOG) << "RecurrenceEdit:";
0070     auto topLayout = new QVBoxLayout(this);
0071     topLayout->setContentsMargins(0, 0, 0, 0);
0072 
0073     /* Create the recurrence rule Group box which holds the recurrence period
0074      * selection buttons, and the weekly, monthly and yearly recurrence rule
0075      * frames which specify options individual to each of these distinct
0076      * sections of the recurrence rule. Each frame is made visible by the
0077      * selection of its corresponding radio button.
0078      */
0079 
0080     QGroupBox* recurGroup = new QGroupBox(i18nc("@title:group", "Recurrence Rule"), this);
0081     topLayout->addWidget(recurGroup);
0082     auto hlayout = new QHBoxLayout(recurGroup);
0083     const int marLeft  = style()->pixelMetric(QStyle::PM_LayoutLeftMargin);
0084     const int marRight = style()->pixelMetric(QStyle::PM_LayoutRightMargin);
0085     hlayout->setSpacing((marLeft + marRight)/2);   // use margin spacing due to vertical divider line
0086 
0087     // Recurrence period radio buttons
0088     auto vlayout = new QVBoxLayout();
0089     vlayout->setSpacing(0);
0090     vlayout->setContentsMargins(0, 0, 0, 0);
0091     hlayout->addLayout(vlayout);
0092     mRuleButtonGroup = new ButtonGroup(recurGroup);
0093     connect(mRuleButtonGroup, &ButtonGroup::buttonSet, this, &RecurrenceEdit::periodClicked);
0094     connect(mRuleButtonGroup, &ButtonGroup::buttonSet, this, &RecurrenceEdit::contentsChanged);
0095 
0096     mNoneButton = new RadioButton(i18n_combo_NoRecur(), recurGroup);
0097     mNoneButton->setReadOnly(mReadOnly);
0098     mNoneButton->setWhatsThis(i18nc("@info:whatsthis", "Do not repeat the alarm"));
0099     mRuleButtonGroup->addButton(mNoneButton);
0100     vlayout->addWidget(mNoneButton);
0101 
0102     mAtLoginButton = new RadioButton(i18n_combo_AtLogin(), recurGroup);
0103     mAtLoginButton->setReadOnly(mReadOnly);
0104     mAtLoginButton->setWhatsThis(xi18nc("@info:whatsthis",
0105                                       "<para>Trigger the alarm at the specified date/time and at every login until then.</para>"
0106                                       "<para>Note that it will also be triggered any time <application>KAlarm</application> is restarted.</para>"));
0107     mRuleButtonGroup->addButton(mAtLoginButton);
0108     vlayout->addWidget(mAtLoginButton);
0109 
0110     mSubDailyButton = new RadioButton(i18n_combo_HourlyMinutely(), recurGroup);
0111     mSubDailyButton->setReadOnly(mReadOnly);
0112     mSubDailyButton->setWhatsThis(i18nc("@info:whatsthis", "Repeat the alarm at hourly/minutely intervals"));
0113     mRuleButtonGroup->addButton(mSubDailyButton);
0114     vlayout->addWidget(mSubDailyButton);
0115 
0116     mDailyButton = new RadioButton(i18n_combo_Daily(), recurGroup);
0117     mDailyButton->setReadOnly(mReadOnly);
0118     mDailyButton->setWhatsThis(i18nc("@info:whatsthis", "Repeat the alarm at daily intervals"));
0119     mRuleButtonGroup->addButton(mDailyButton);
0120     vlayout->addWidget(mDailyButton);
0121 
0122     mWeeklyButton = new RadioButton(i18n_combo_Weekly(), recurGroup);
0123     mWeeklyButton->setReadOnly(mReadOnly);
0124     mWeeklyButton->setWhatsThis(i18nc("@info:whatsthis", "Repeat the alarm at weekly intervals"));
0125     mRuleButtonGroup->addButton(mWeeklyButton);
0126     vlayout->addWidget(mWeeklyButton);
0127 
0128     mMonthlyButton = new RadioButton(i18n_combo_Monthly(), recurGroup);
0129     mMonthlyButton->setReadOnly(mReadOnly);
0130     mMonthlyButton->setWhatsThis(i18nc("@info:whatsthis", "Repeat the alarm at monthly intervals"));
0131     mRuleButtonGroup->addButton(mMonthlyButton);
0132     vlayout->addWidget(mMonthlyButton);
0133 
0134     mYearlyButton = new RadioButton(i18n_combo_Yearly(), recurGroup);
0135     mYearlyButton->setReadOnly(mReadOnly);
0136     mYearlyButton->setWhatsThis(i18nc("@info:whatsthis", "Repeat the alarm at annual intervals"));
0137     mRuleButtonGroup->addButton(mYearlyButton);
0138     vlayout->addWidget(mYearlyButton);
0139     vlayout->addStretch();    // top-adjust the interval radio buttons
0140 
0141     // Sub-repetition button
0142     mSubRepetition = new RepetitionButton(i18nc("@action:button", "Sub-Repetition"), true, recurGroup);
0143     mSubRepetition->setReadOnly(mReadOnly);
0144     mSubRepetition->setWhatsThis(i18nc("@info:whatsthis",
0145                                        "Set up a repetition within the recurrence, to trigger the alarm multiple times each time the recurrence is due."));
0146     connect(mSubRepetition, &RepetitionButton::needsInitialisation, this, &RecurrenceEdit::repeatNeedsInitialisation);
0147     connect(mSubRepetition, &RepetitionButton::changed, this, &RecurrenceEdit::frequencyChanged);
0148     connect(mSubRepetition, &RepetitionButton::changed, this, &RecurrenceEdit::contentsChanged);
0149     vlayout->addSpacing(style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing));
0150     vlayout->addWidget(mSubRepetition);
0151 
0152     // Vertical divider line
0153     vlayout = new QVBoxLayout();
0154     vlayout->setContentsMargins(0, 0, 0, 0);
0155     hlayout->addLayout(vlayout);
0156     QFrame* divider = new QFrame(recurGroup);
0157     divider->setFrameStyle(QFrame::VLine | QFrame::Sunken);
0158     vlayout->addWidget(divider, 1);
0159 
0160     // Rule definition stack
0161     mRuleStack = new QStackedWidget(recurGroup);
0162     hlayout->addWidget(mRuleStack);
0163     hlayout->addStretch(1);
0164     mNoRule       = new NoRule(mRuleStack);
0165     mSubDailyRule = new SubDailyRule(mReadOnly, mRuleStack);
0166     mDailyRule    = new DailyRule(mReadOnly, mRuleStack);
0167     mWeeklyRule   = new WeeklyRule(mReadOnly, mRuleStack);
0168     mMonthlyRule  = new MonthlyRule(mReadOnly, mRuleStack);
0169     mYearlyRule   = new YearlyRule(mReadOnly, mRuleStack);
0170 
0171     connect(mSubDailyRule, &SubDailyRule::frequencyChanged, this, &RecurrenceEdit::frequencyChanged);
0172     connect(mDailyRule, &DailyRule::frequencyChanged, this, &RecurrenceEdit::frequencyChanged);
0173     connect(mWeeklyRule, &WeeklyRule::frequencyChanged, this, &RecurrenceEdit::frequencyChanged);
0174     connect(mMonthlyRule, &MonthlyRule::frequencyChanged, this, &RecurrenceEdit::frequencyChanged);
0175     connect(mYearlyRule, &YearlyRule::frequencyChanged, this, &RecurrenceEdit::frequencyChanged);
0176     connect(mSubDailyRule, &SubDailyRule::changed, this, &RecurrenceEdit::contentsChanged);
0177     connect(mDailyRule, &DailyRule::changed, this, &RecurrenceEdit::contentsChanged);
0178     connect(mWeeklyRule, &WeeklyRule::changed, this, &RecurrenceEdit::contentsChanged);
0179     connect(mMonthlyRule, &MonthlyRule::changed, this, &RecurrenceEdit::contentsChanged);
0180     connect(mYearlyRule, &YearlyRule::changed, this, &RecurrenceEdit::contentsChanged);
0181 
0182     mRuleStack->addWidget(mNoRule);
0183     mRuleStack->addWidget(mSubDailyRule);
0184     mRuleStack->addWidget(mDailyRule);
0185     mRuleStack->addWidget(mWeeklyRule);
0186     mRuleStack->addWidget(mMonthlyRule);
0187     mRuleStack->addWidget(mYearlyRule);
0188     hlayout->addSpacing((marLeft + marRight)/2);
0189 
0190     // Create the recurrence range group which contains the controls
0191     // which specify how long the recurrence is to last.
0192 
0193     mRangeButtonBox = new QGroupBox(i18nc("@title:group", "Recurrence End"), this);
0194     topLayout->addWidget(mRangeButtonBox);
0195     mRangeButtonGroup = new ButtonGroup(mRangeButtonBox);
0196     connect(mRangeButtonGroup, &ButtonGroup::buttonSet, this, &RecurrenceEdit::rangeTypeClicked);
0197     connect(mRangeButtonGroup, &ButtonGroup::buttonSet, this, &RecurrenceEdit::contentsChanged);
0198 
0199     vlayout = new QVBoxLayout(mRangeButtonBox);
0200     mNoEndDateButton = new RadioButton(i18nc("@option:radio", "No end"), mRangeButtonBox);
0201     mNoEndDateButton->setReadOnly(mReadOnly);
0202     mNoEndDateButton->setWhatsThis(i18nc("@info:whatsthis", "Repeat the alarm indefinitely"));
0203     mRangeButtonGroup->addButton(mNoEndDateButton);
0204     vlayout->addWidget(mNoEndDateButton, 1, Qt::AlignLeft);
0205     QSize size = mNoEndDateButton->size();
0206 
0207     hlayout = new QHBoxLayout();
0208     hlayout->setContentsMargins(0, 0, 0, 0);
0209     vlayout->addLayout(hlayout);
0210     mRepeatCountButton = new RadioButton(i18nc("@option:radio", "End after:"), mRangeButtonBox);
0211     mRepeatCountButton->setReadOnly(mReadOnly);
0212     mRepeatCountButton->setWhatsThis(i18nc("@info:whatsthis", "Repeat the alarm for the number of times specified"));
0213     mRangeButtonGroup->addButton(mRepeatCountButton);
0214     mRepeatCountEntry = new SpinBox(1, 9999, mRangeButtonBox);
0215     mRepeatCountEntry->setSingleShiftStep(10);
0216     mRepeatCountEntry->setSelectOnStep(false);
0217     mRepeatCountEntry->setReadOnly(mReadOnly);
0218     mRepeatCountEntry->setWhatsThis(i18nc("@info:whatsthis", "Enter the total number of times to trigger the alarm"));
0219     connect(mRepeatCountEntry, &SpinBox::valueChanged, this, &RecurrenceEdit::repeatCountChanged);
0220     connect(mRepeatCountEntry, &SpinBox::valueChanged, this, &RecurrenceEdit::contentsChanged);
0221     mRepeatCountButton->setFocusWidget(mRepeatCountEntry);
0222     mRepeatCountLabel = new QLabel(i18nc("@label", "occurrence(s)"), mRangeButtonBox);
0223     hlayout->addWidget(mRepeatCountButton);
0224     hlayout->addSpacing(style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
0225     hlayout->addWidget(mRepeatCountEntry);
0226     hlayout->addWidget(mRepeatCountLabel);
0227     hlayout->addStretch();
0228     size = size.expandedTo(mRepeatCountButton->sizeHint());
0229 
0230     hlayout = new QHBoxLayout();
0231     hlayout->setContentsMargins(0, 0, 0, 0);
0232     vlayout->addLayout(hlayout);
0233     mEndDateButton = new RadioButton(i18nc("@option:radio", "End by:"), mRangeButtonBox);
0234     mEndDateButton->setReadOnly(mReadOnly);
0235     mEndDateButton->setWhatsThis(
0236           xi18nc("@info:whatsthis", "<para>Repeat the alarm until the date/time specified.</para>"
0237                 "<para><note>This applies to the main recurrence only. It does not limit any sub-repetition which will occur regardless after the last main recurrence.</note></para>"));
0238     mRangeButtonGroup->addButton(mEndDateButton);
0239     mEndDateEdit = new KDateComboBox(mRangeButtonBox);
0240     mEndDateEdit->setOptions(mReadOnly ? KDateComboBox::Options{} : KDateComboBox::EditDate | KDateComboBox::SelectDate | KDateComboBox::DatePicker);
0241     static const QString tzText = i18nc("@info", "This uses the same time zone as the start time.");
0242     mEndDateEdit->setWhatsThis(xi18nc("@info:whatsthis",
0243           "<para>Enter the last date to repeat the alarm.</para><para>%1</para>", tzText));
0244     connect(mEndDateEdit, &KDateComboBox::dateEdited, this, &RecurrenceEdit::contentsChanged);
0245     mEndDateButton->setFocusWidget(mEndDateEdit);
0246     mEndTimeEdit = new TimeEdit(mRangeButtonBox);
0247     mEndTimeEdit->setReadOnly(mReadOnly);
0248     mEndTimeEdit->setWhatsThis(xi18nc("@info:whatsthis",
0249           "<para>Enter the last time to repeat the alarm.</para><para>%1</para><para>%2</para>", tzText, TimeSpinBox::shiftWhatsThis()));
0250     connect(mEndTimeEdit, &TimeEdit::valueChanged, this, &RecurrenceEdit::contentsChanged);
0251     mEndAnyTimeCheckBox = new CheckBox(i18nc("@option:check", "Any time"), mRangeButtonBox);
0252     mEndAnyTimeCheckBox->setReadOnly(mReadOnly);
0253     mEndAnyTimeCheckBox->setWhatsThis(i18nc("@info:whatsthis", "Stop repeating the alarm after your first login on or after the specified end date"));
0254     connect(mEndAnyTimeCheckBox, &CheckBox::toggled, this, &RecurrenceEdit::slotAnyTimeToggled);
0255     connect(mEndAnyTimeCheckBox, &CheckBox::toggled, this, &RecurrenceEdit::contentsChanged);
0256     hlayout->addWidget(mEndDateButton);
0257     hlayout->addSpacing(style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
0258     hlayout->addWidget(mEndDateEdit);
0259     hlayout->addSpacing(2 * style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
0260     hlayout->addWidget(mEndTimeEdit);
0261     hlayout->addWidget(mEndAnyTimeCheckBox);
0262     hlayout->addStretch();
0263     size = size.expandedTo(mEndDateButton->sizeHint());
0264 
0265     // Line up the widgets to the right of the radio buttons
0266     mRepeatCountButton->setFixedSize(size);
0267     mEndDateButton->setFixedSize(size);
0268 
0269     // Create the exceptions group which specifies dates to be excluded
0270     // from the recurrence.
0271 
0272     mExceptionGroup = new QGroupBox(i18nc("@title:group", "Exceptions"), this);
0273     topLayout->addWidget(mExceptionGroup);
0274     topLayout->setStretchFactor(mExceptionGroup, 2);
0275     hlayout = new QHBoxLayout(mExceptionGroup);
0276     vlayout = new QVBoxLayout();
0277     vlayout->setContentsMargins(0, 0, 0, 0);
0278     hlayout->addLayout(vlayout);
0279 
0280     mExceptionDateList = new ListWidget(mExceptionGroup);
0281     mExceptionDateList->setWhatsThis(i18nc("@info:whatsthis", "The list of exceptions, i.e. dates/times excluded from the recurrence"));
0282     connect(mExceptionDateList, &QListWidget::currentRowChanged, this, &RecurrenceEdit::enableExceptionButtons);
0283     vlayout->addWidget(mExceptionDateList);
0284 
0285     if (mReadOnly)
0286     {
0287         mExceptionDateEdit     = nullptr;
0288         mChangeExceptionButton = nullptr;
0289         mDeleteExceptionButton = nullptr;
0290     }
0291     else
0292     {
0293         vlayout = new QVBoxLayout();
0294         vlayout->setContentsMargins(0, 0, 0, 0);
0295         hlayout->addLayout(vlayout);
0296         mExceptionDateEdit = new KDateComboBox(mExceptionGroup);
0297         mExceptionDateEdit->setOptions(mReadOnly ? KDateComboBox::Options{} : KDateComboBox::EditDate | KDateComboBox::SelectDate | KDateComboBox::DatePicker);
0298         mExceptionDateEdit->setDate(KADateTime::currentLocalDate());
0299         mExceptionDateEdit->setWhatsThis(i18nc("@info:whatsthis",
0300               "Enter a date to insert in the exceptions list. "
0301               "Use in conjunction with the Add or Change button below."));
0302         vlayout->addWidget(mExceptionDateEdit, 0, Qt::AlignLeft);
0303 
0304         hlayout = new QHBoxLayout();
0305         hlayout->setContentsMargins(0, 0, 0, 0);
0306         vlayout->addLayout(hlayout);
0307         QPushButton* button = new QPushButton(i18nc("@action:button", "Add"), mExceptionGroup);
0308         button->setWhatsThis(i18nc("@info:whatsthis", "Add the date entered above to the exceptions list"));
0309         connect(button, &QPushButton::clicked, this, &RecurrenceEdit::addException);
0310         hlayout->addWidget(button);
0311 
0312         mChangeExceptionButton = new QPushButton(i18nc("@action:button", "Change"), mExceptionGroup);
0313         mChangeExceptionButton->setWhatsThis(i18nc("@info:whatsthis",
0314               "Replace the currently highlighted item in the exceptions list with the date entered above"));
0315         connect(mChangeExceptionButton, &QPushButton::clicked, this, &RecurrenceEdit::changeException);
0316         hlayout->addWidget(mChangeExceptionButton);
0317 
0318         mDeleteExceptionButton = new QPushButton(i18nc("@action:button", "Delete"), mExceptionGroup);
0319         mDeleteExceptionButton->setWhatsThis(i18nc("@info:whatsthis", "Remove the currently highlighted item from the exceptions list"));
0320         connect(mDeleteExceptionButton, &QPushButton::clicked, this, &RecurrenceEdit::deleteException);
0321         hlayout->addWidget(mDeleteExceptionButton);
0322     }
0323 
0324     vlayout->addStretch();
0325 
0326     mExcludeHolidays = new CheckBox(i18nc("@option:check", "Exclude holidays"), mExceptionGroup);
0327     mExcludeHolidays->setReadOnly(mReadOnly);
0328     mExcludeHolidays->setWhatsThis(xi18nc("@info:whatsthis",
0329           "<para>Do not trigger the alarm on holidays.</para>"
0330           "<para>You can specify your holiday region in the Configuration dialog.</para>"));
0331     connect(mExcludeHolidays, &CheckBox::toggled, this, &RecurrenceEdit::contentsChanged);
0332     vlayout->addWidget(mExcludeHolidays);
0333 
0334     mWorkTimeOnly = new CheckBox(i18nc("@option:check", "Only during working time"), mExceptionGroup);
0335     mWorkTimeOnly->setReadOnly(mReadOnly);
0336     mWorkTimeOnly->setWhatsThis(xi18nc("@info:whatsthis",
0337           "<para>Only execute the alarm during working hours, on working days.</para>"
0338           "<para>You can specify working days and hours in the Configuration dialog.</para>"));
0339     connect(mWorkTimeOnly, &CheckBox::toggled, this, &RecurrenceEdit::contentsChanged);
0340     vlayout->addWidget(mWorkTimeOnly);
0341 
0342     topLayout->addStretch();
0343     mNoEmitTypeChanged = false;
0344 }
0345 
0346 /******************************************************************************
0347 * Show or hide the exception controls.
0348 */
0349 void RecurrenceEdit::showMoreOptions(bool more)
0350 {
0351     if (more)
0352         mExceptionGroup->show();
0353     else
0354         mExceptionGroup->hide();
0355     updateGeometry();
0356 }
0357 
0358 /******************************************************************************
0359 * Verify the consistency of the entered data.
0360 * Reply = widget to receive focus on error, or 0 if no error.
0361 */
0362 QWidget* RecurrenceEdit::checkData(const KADateTime& startDateTime, QString& errorMessage) const
0363 {
0364     if (mAtLoginButton->isChecked())
0365         return nullptr;
0366     const_cast<RecurrenceEdit*>(this)->mCurrStartDateTime = startDateTime;
0367     if (mEndDateButton->isChecked())
0368     {
0369         // N.B. End date/time takes the same time spec as start date/time
0370         QWidget* errWidget = nullptr;
0371         bool noTime = !mEndTimeEdit->isEnabled();
0372         QDate endDate = mEndDateEdit->date();
0373         if (endDate < startDateTime.date())
0374             errWidget = mEndDateEdit;
0375         else if (!noTime  &&  KADateTime(endDate, mEndTimeEdit->time(), startDateTime.timeSpec()) < startDateTime)
0376             errWidget = mEndTimeEdit;
0377         if (errWidget)
0378         {
0379             errorMessage = noTime
0380                          ? i18nc("@info", "End date is earlier than start date")
0381                          : i18nc("@info", "End date/time is earlier than start date/time");
0382             return errWidget;
0383         }
0384     }
0385     if (!mRule)
0386         return nullptr;
0387     return mRule->validate(errorMessage);
0388 }
0389 
0390 /******************************************************************************
0391 * Called when a recurrence period radio button is clicked.
0392 */
0393 void RecurrenceEdit::periodClicked(QAbstractButton* button)
0394 {
0395     const RepeatType oldType = mRuleButtonType;
0396     const bool none     = (button == mNoneButton);
0397     const bool atLogin  = (button == mAtLoginButton);
0398     const bool subdaily = (button == mSubDailyButton);
0399     if (none)
0400     {
0401         mRule = nullptr;
0402         mRuleButtonType = NO_RECUR;
0403     }
0404     else if (atLogin)
0405     {
0406         mRule = nullptr;
0407         mRuleButtonType = AT_LOGIN;
0408         mEndDateButton->setChecked(true);
0409     }
0410     else if (subdaily)
0411     {
0412         mRule = mSubDailyRule;
0413         mRuleButtonType = SUBDAILY;
0414     }
0415     else if (button == mDailyButton)
0416     {
0417         mRule = mDailyRule;
0418         mRuleButtonType = DAILY;
0419         mDailyShown = true;
0420     }
0421     else if (button == mWeeklyButton)
0422     {
0423         mRule = mWeeklyRule;
0424         mRuleButtonType = WEEKLY;
0425         mWeeklyShown = true;
0426     }
0427     else if (button == mMonthlyButton)
0428     {
0429         mRule = mMonthlyRule;
0430         mRuleButtonType = MONTHLY;
0431         mMonthlyShown = true;
0432     }
0433     else if (button == mYearlyButton)
0434     {
0435         mRule = mYearlyRule;
0436         mRuleButtonType = ANNUAL;
0437         mYearlyShown = true;
0438     }
0439     else
0440         return;
0441 
0442     if (mRuleButtonType != oldType)
0443     {
0444         mRuleStack->setCurrentWidget(mRule ? mRule : mNoRule);
0445         if (oldType == NO_RECUR  ||  none)
0446             mRangeButtonBox->setEnabled(!none);
0447         mExceptionGroup->setEnabled(!(none || atLogin));
0448         mEndAnyTimeCheckBox->setEnabled(atLogin);
0449         if (!none)
0450         {
0451             mNoEndDateButton->setEnabled(!atLogin);
0452             mRepeatCountButton->setEnabled(!atLogin);
0453         }
0454         rangeTypeClicked();
0455         mSubRepetition->setEnabled(!(none || atLogin));
0456         if (!mNoEmitTypeChanged)
0457             Q_EMIT typeChanged(mRuleButtonType);
0458     }
0459 }
0460 
0461 void RecurrenceEdit::slotAnyTimeToggled(bool on)
0462 {
0463     QAbstractButton* button = mRuleButtonGroup->checkedButton();
0464     mEndTimeEdit->setEnabled((button == mAtLoginButton && !on)
0465                          ||  (button == mSubDailyButton && mEndDateButton->isChecked()));
0466 }
0467 
0468 /******************************************************************************
0469 * Called when a recurrence range type radio button is clicked.
0470 */
0471 void RecurrenceEdit::rangeTypeClicked()
0472 {
0473     const bool endDate = mEndDateButton->isChecked();
0474     mEndDateEdit->setEnabled(endDate);
0475     mEndTimeEdit->setEnabled(endDate
0476                              &&  ((mAtLoginButton->isChecked() && !mEndAnyTimeCheckBox->isChecked())
0477                                   ||  mSubDailyButton->isChecked()));
0478     const bool repeatCount = mRepeatCountButton->isChecked();
0479     mRepeatCountEntry->setEnabled(repeatCount);
0480     mRepeatCountLabel->setEnabled(repeatCount);
0481 }
0482 
0483 void RecurrenceEdit::showEvent(QShowEvent*)
0484 {
0485     if (mRule)
0486         mRule->setFrequencyFocus();
0487     else
0488         mRuleButtonGroup->checkedButton()->setFocus();
0489     Q_EMIT shown();
0490 }
0491 
0492 /******************************************************************************
0493 * Return the sub-repetition interval and count within the recurrence, i.e. the
0494 * number of repetitions after the main recurrence.
0495 */
0496 Repetition RecurrenceEdit::subRepetition() const
0497 {
0498     return (mRuleButtonType >= SUBDAILY) ? mSubRepetition->repetition() : Repetition();
0499 }
0500 
0501 /******************************************************************************
0502 * Called when the Sub-Repetition button has been pressed to display the
0503 * sub-repetition dialog.
0504 * Alarm repetition has the following restrictions:
0505 * 1) Not allowed for a repeat-at-login alarm
0506 * 2) For a date-only alarm, the repeat interval must be a whole number of days.
0507 * 3) The overall repeat duration must be less than the recurrence interval.
0508 */
0509 void RecurrenceEdit::setSubRepetition(int reminderMinutes, bool dateOnly)
0510 {
0511     int maxDuration;
0512     switch (mRuleButtonType)
0513     {
0514         case RecurrenceEdit::NO_RECUR:
0515         case RecurrenceEdit::AT_LOGIN:   // alarm repeat not allowed
0516             maxDuration = 0;
0517             break;
0518         default:          // repeat duration must be less than recurrence interval
0519         {
0520             KAEvent event;
0521             updateEvent(event, false);
0522             maxDuration = event.longestRecurrenceInterval().asSeconds()/60 - reminderMinutes - 1;
0523             break;
0524         }
0525     }
0526     mSubRepetition->initialise(mSubRepetition->repetition(), dateOnly, maxDuration);
0527     mSubRepetition->setEnabled(mRuleButtonType >= SUBDAILY && maxDuration);
0528 }
0529 
0530 /******************************************************************************
0531 * Activate the sub-repetition dialog.
0532 */
0533 void RecurrenceEdit::activateSubRepetition()
0534 {
0535     mSubRepetition->activate();
0536 }
0537 
0538 /******************************************************************************
0539 * Called when the value of the repeat count field changes, to reset the
0540 * minimum value to 1 if the value was 0.
0541 */
0542 void RecurrenceEdit::repeatCountChanged(int value)
0543 {
0544     if (value > 0  &&  mRepeatCountEntry->minimum() == 0)
0545         mRepeatCountEntry->setMinimum(1);
0546 }
0547 
0548 /******************************************************************************
0549 * Add the date entered in the exception date edit control to the list of
0550 * exception dates.
0551 */
0552 void RecurrenceEdit::addException()
0553 {
0554     if (!mExceptionDateEdit  ||  !mExceptionDateEdit->date().isValid())
0555         return;
0556     const QDate date = mExceptionDateEdit->date();
0557     DateList::Iterator it;
0558     int index = 0;
0559     bool insert = true;
0560     for (it = mExceptionDates.begin();  it != mExceptionDates.end();  ++index, ++it)
0561     {
0562         if (date <= *it)
0563         {
0564             insert = (date != *it);
0565             break;
0566         }
0567     }
0568     if (insert)
0569     {
0570         mExceptionDates.insert(it, date);
0571         mExceptionDateList->insertItem(index, new QListWidgetItem(QLocale().toString(date, QLocale::LongFormat)));
0572         Q_EMIT contentsChanged();
0573     }
0574     mExceptionDateList->setCurrentItem(mExceptionDateList->item(index));
0575     enableExceptionButtons();
0576 }
0577 
0578 /******************************************************************************
0579 * Change the currently highlighted exception date to that entered in the
0580 * exception date edit control.
0581 */
0582 void RecurrenceEdit::changeException()
0583 {
0584     if (!mExceptionDateEdit  ||  !mExceptionDateEdit->date().isValid())
0585         return;
0586     QListWidgetItem* item = mExceptionDateList->currentItem();
0587     if (item  &&  item->isSelected())
0588     {
0589         const int index = mExceptionDateList->row(item);
0590         const QDate olddate = mExceptionDates.at(index);
0591         const QDate newdate = mExceptionDateEdit->date();
0592         if (newdate != olddate)
0593         {
0594             mExceptionDates.removeAt(index);
0595             mExceptionDateList->takeItem(index);
0596             Q_EMIT contentsChanged();
0597             addException();
0598         }
0599     }
0600 }
0601 
0602 /******************************************************************************
0603 * Delete the currently highlighted exception date.
0604 */
0605 void RecurrenceEdit::deleteException()
0606 {
0607     QListWidgetItem* item = mExceptionDateList->currentItem();
0608     if (item  &&  item->isSelected())
0609     {
0610         const int index = mExceptionDateList->row(item);
0611         mExceptionDates.removeAt(index);
0612         mExceptionDateList->takeItem(index);
0613         Q_EMIT contentsChanged();
0614         enableExceptionButtons();
0615     }
0616 }
0617 
0618 /******************************************************************************
0619 * Enable/disable the exception group buttons according to whether any item is
0620 * selected in the exceptions listbox.
0621 */
0622 void RecurrenceEdit::enableExceptionButtons()
0623 {
0624     QListWidgetItem* item = mExceptionDateList->currentItem();
0625     const bool enable = item;
0626     if (mDeleteExceptionButton)
0627         mDeleteExceptionButton->setEnabled(enable);
0628     if (mChangeExceptionButton)
0629         mChangeExceptionButton->setEnabled(enable);
0630 
0631     // Prevent the exceptions list box receiving keyboard focus is it's empty
0632     mExceptionDateList->setFocusPolicy(mExceptionDateList->count() ? Qt::WheelFocus : Qt::NoFocus);
0633 }
0634 
0635 /******************************************************************************
0636 * Notify this instance of a change in the alarm start date.
0637 */
0638 void RecurrenceEdit::setStartDate(const QDate& start, const QDate& today)
0639 {
0640     if (!mReadOnly)
0641     {
0642         setRuleDefaults(start);
0643         if (start < today)
0644         {
0645             mEndDateEdit->setMinimumDate(today);
0646             if (mExceptionDateEdit)
0647                 mExceptionDateEdit->setMinimumDate(today);
0648         }
0649         else
0650         {
0651             const QString startString = i18nc("@info", "Date cannot be earlier than start date");
0652             mEndDateEdit->setMinimumDate(start, startString);
0653             if (mExceptionDateEdit)
0654                 mExceptionDateEdit->setMinimumDate(start, startString);
0655         }
0656     }
0657 }
0658 
0659 /******************************************************************************
0660 * Specify the default recurrence end date.
0661 */
0662 void RecurrenceEdit::setDefaultEndDate(const QDate& end)
0663 {
0664     if (!mEndDateButton->isChecked())
0665         mEndDateEdit->setDate(end);
0666 }
0667 
0668 void RecurrenceEdit::setEndDateTime(const KADateTime& end)
0669 {
0670     const KADateTime edt = end.toTimeSpec(mCurrStartDateTime.timeSpec());
0671     mEndDateEdit->setDate(edt.date());
0672     mEndTimeEdit->setValue(edt.time());
0673     mEndTimeEdit->setEnabled(!end.isDateOnly());
0674     mEndAnyTimeCheckBox->setChecked(end.isDateOnly());
0675 }
0676 
0677 KADateTime RecurrenceEdit::endDateTime() const
0678 {
0679     if (mRuleButtonGroup->checkedButton() == mAtLoginButton  &&  mEndAnyTimeCheckBox->isChecked())
0680         return KADateTime(mEndDateEdit->date(), mCurrStartDateTime.timeSpec());
0681     return KADateTime(mEndDateEdit->date(), mEndTimeEdit->time(), mCurrStartDateTime.timeSpec());
0682 }
0683 
0684 /******************************************************************************
0685 * Set all controls to their default values.
0686 */
0687 void RecurrenceEdit::setDefaults(const KADateTime& from)
0688 {
0689     mCurrStartDateTime = from;
0690     const QDate fromDate = from.date();
0691     mNoEndDateButton->setChecked(true);
0692 
0693     mSubDailyRule->setFrequency(1);
0694     mDailyRule->setFrequency(1);
0695     mWeeklyRule->setFrequency(1);
0696     mMonthlyRule->setFrequency(1);
0697     mYearlyRule->setFrequency(1);
0698 
0699     setRuleDefaults(fromDate);
0700     mMonthlyRule->setType(MonthYearRule::DATE);   // date in month
0701     mYearlyRule->setType(MonthYearRule::DATE);    // date in year
0702 
0703     mEndDateEdit->setDate(fromDate);
0704 
0705     mNoEmitTypeChanged = true;
0706     RadioButton* button;
0707     switch (Preferences::defaultRecurPeriod())
0708     {
0709         case Preferences::Recur_Login:    button = mAtLoginButton;  break;
0710         case Preferences::Recur_Yearly:   button = mYearlyButton;   break;
0711         case Preferences::Recur_Monthly:  button = mMonthlyButton;  break;
0712         case Preferences::Recur_Weekly:   button = mWeeklyButton;   break;
0713         case Preferences::Recur_Daily:    button = mDailyButton;    break;
0714         case Preferences::Recur_SubDaily: button = mSubDailyButton; break;
0715         case Preferences::Recur_None:
0716         default:                          button = mNoneButton;     break;
0717     }
0718     button->setChecked(true);
0719     mNoEmitTypeChanged = false;
0720     rangeTypeClicked();
0721     enableExceptionButtons();
0722 
0723     saveState();
0724 }
0725 
0726 /******************************************************************************
0727 * Set the controls for weekly, monthly and yearly rules which have not so far
0728 * been shown, to their default values, depending on the recurrence start date.
0729 */
0730 void RecurrenceEdit::setRuleDefaults(const QDate& fromDate)
0731 {
0732     const int day       = fromDate.day();
0733     const int dayOfWeek = fromDate.dayOfWeek();
0734     const int month     = fromDate.month();
0735     if (!mDailyShown)
0736         mDailyRule->setDays(true);
0737     if (!mWeeklyShown)
0738         mWeeklyRule->setDay(dayOfWeek);
0739     if (!mMonthlyShown)
0740         mMonthlyRule->setDefaultValues(day, dayOfWeek);
0741     if (!mYearlyShown)
0742         mYearlyRule->setDefaultValues(day, dayOfWeek, month);
0743 }
0744 
0745 /******************************************************************************
0746 * Initialise the recurrence to select repeat-at-login.
0747 * This function and set() are mutually exclusive: call one or the other, not both.
0748 */
0749 void RecurrenceEdit::setRepeatAtLogin()
0750 {
0751     mAtLoginButton->setChecked(true);
0752     mEndDateButton->setChecked(true);
0753 }
0754 
0755 /******************************************************************************
0756 * Set the state of all controls to reflect the data in the specified event.
0757 */
0758 void RecurrenceEdit::set(const KAEvent& event)
0759 {
0760     setDefaults(event.mainDateTime().kDateTime());
0761     if (event.repeatAtLogin())
0762     {
0763         mAtLoginButton->setChecked(true);
0764         mEndDateButton->setChecked(true);
0765         return;
0766     }
0767     mNoneButton->setChecked(true);
0768     KARecurrence* recurrence = event.recurrence();
0769     if (!recurrence)
0770         return;
0771     const KARecurrence::Type rtype = recurrence->type();
0772     switch (rtype)
0773     {
0774         case KARecurrence::MINUTELY:
0775             mSubDailyButton->setChecked(true);
0776             break;
0777 
0778         case KARecurrence::DAILY:
0779         {
0780             mDailyButton->setChecked(true);
0781             const QBitArray rDays = recurrence->days();
0782             if (rDays.count(true))
0783                 mDailyRule->setDays(rDays);
0784             else
0785                 mDailyRule->setDays(true);
0786             break;
0787         }
0788         case KARecurrence::WEEKLY:
0789         {
0790             mWeeklyButton->setChecked(true);
0791             const QBitArray rDays = recurrence->days();
0792             mWeeklyRule->setDays(rDays);
0793             break;
0794         }
0795         case KARecurrence::MONTHLY_POS:    // on nth (Tuesday) of the month
0796         {
0797             const QList<RecurrenceRule::WDayPos> posns = recurrence->monthPositions();
0798             int i = posns.first().pos();
0799             if (!i)
0800             {
0801                 // It's every (Tuesday) of the month. Convert to a weekly recurrence
0802                 // (but ignoring any non-every xxxDay positions).
0803                 mWeeklyButton->setChecked(true);
0804                 mWeeklyRule->setFrequency(recurrence->frequency());
0805                 QBitArray rDays(7);
0806                 for (const RecurrenceRule::WDayPos& posn : posns)
0807                 {
0808                     if (!posn.pos())
0809                         rDays.setBit(posn.day() - 1, true);
0810                 }
0811                 mWeeklyRule->setDays(rDays);
0812                 break;
0813             }
0814             mMonthlyButton->setChecked(true);
0815             mMonthlyRule->setPosition(i, posns.first().day());
0816             break;
0817         }
0818         case KARecurrence::MONTHLY_DAY:     // on nth day of the month
0819         {
0820             mMonthlyButton->setChecked(true);
0821             const QList<int> rmd = recurrence->monthDays();
0822             const int day = (rmd.isEmpty()) ? event.mainDateTime().date().day() : rmd.first();
0823             mMonthlyRule->setDate(day);
0824             break;
0825         }
0826         case KARecurrence::ANNUAL_DATE:   // on the nth day of (months...) in the year
0827         case KARecurrence::ANNUAL_POS:     // on the nth (Tuesday) of (months...) in the year
0828         {
0829             if (rtype == KARecurrence::ANNUAL_DATE)
0830             {
0831                 mYearlyButton->setChecked(true);
0832                 const QList<int> rmd = recurrence->monthDays();
0833                 const int day = (rmd.isEmpty()) ? event.mainDateTime().date().day() : rmd.first();
0834                 mYearlyRule->setDate(day);
0835                 mYearlyRule->setFeb29Type(recurrence->feb29Type());
0836             }
0837             else if (rtype == KARecurrence::ANNUAL_POS)
0838             {
0839                 mYearlyButton->setChecked(true);
0840                 const QList<RecurrenceRule::WDayPos> posns = recurrence->yearPositions();
0841                 mYearlyRule->setPosition(posns.first().pos(), posns.first().day());
0842             }
0843             mYearlyRule->setMonths(recurrence->yearMonths());
0844             break;
0845         }
0846         default:
0847             return;
0848     }
0849 
0850     mRule->setFrequency(recurrence->frequency());
0851 
0852     // Get range information
0853     KADateTime endtime = mCurrStartDateTime;
0854     const int duration = recurrence->duration();
0855     if (duration == -1)
0856         mNoEndDateButton->setChecked(true);
0857     else if (duration)
0858     {
0859         mRepeatCountButton->setChecked(true);
0860         mRepeatCountEntry->setValue(duration);
0861     }
0862     else
0863     {
0864         mEndDateButton->setChecked(true);
0865         endtime = recurrence->endDateTime();
0866         mEndTimeEdit->setValue(endtime.time());
0867     }
0868     mEndDateEdit->setDate(endtime.date());
0869 
0870     // Get exception information
0871     mExceptionDates = event.recurrence()->exDates();
0872     std::sort(mExceptionDates.begin(), mExceptionDates.end());
0873     mExceptionDateList->clear();
0874     for (const QDate& exceptionDate : std::as_const(mExceptionDates))
0875         new QListWidgetItem(QLocale().toString(exceptionDate, QLocale::LongFormat), mExceptionDateList);
0876     enableExceptionButtons();
0877     mExcludeHolidays->setChecked(event.holidaysExcluded());
0878     mWorkTimeOnly->setChecked(event.workTimeOnly());
0879 
0880     // Get repetition within recurrence
0881     mSubRepetition->set(event.repetition());
0882 
0883     rangeTypeClicked();
0884 
0885     saveState();
0886 }
0887 
0888 /******************************************************************************
0889 * Update the specified KAEvent with the entered recurrence data.
0890 * If 'adjustStart' is true, the start date/time will be adjusted if necessary
0891 * to be the first date/time which recurs on or after the original start.
0892 */
0893 void RecurrenceEdit::updateEvent(KAEvent& event, bool adjustStart)
0894 {
0895     // Get end date and repeat count, common to all types of recurring events
0896     QDate  endDate;
0897     QTime  endTime;
0898     int    repeatCount;
0899     if (mNoEndDateButton->isChecked())
0900         repeatCount = -1;
0901     else if (mRepeatCountButton->isChecked())
0902         repeatCount = mRepeatCountEntry->value();
0903     else
0904     {
0905         repeatCount = 0;
0906         endDate = mEndDateEdit->date();
0907         endTime = mEndTimeEdit->time();
0908     }
0909 
0910     // Set up the recurrence according to the type selected
0911     event.startChanges();
0912     QAbstractButton* button = mRuleButtonGroup->checkedButton();
0913     event.setRepeatAtLogin(button == mAtLoginButton);
0914     const int frequency = mRule ? mRule->frequency() : 0;
0915     if (button == mSubDailyButton)
0916     {
0917         const KADateTime endDateTime(endDate, endTime, mCurrStartDateTime.timeSpec());
0918         event.setRecurMinutely(frequency, repeatCount, endDateTime);
0919     }
0920     else if (button == mDailyButton)
0921     {
0922         event.setRecurDaily(frequency, mDailyRule->days(), repeatCount, endDate);
0923     }
0924     else if (button == mWeeklyButton)
0925     {
0926         event.setRecurWeekly(frequency, mWeeklyRule->days(), repeatCount, endDate);
0927     }
0928     else if (button == mMonthlyButton)
0929     {
0930         if (mMonthlyRule->type() == MonthlyRule::POS)
0931         {
0932             // It's by position
0933             KAEvent::MonthPos pos;
0934             pos.days.fill(false);
0935             pos.days.setBit(mMonthlyRule->dayOfWeek() - 1);
0936             pos.weeknum = mMonthlyRule->week();
0937             QList<KAEvent::MonthPos> poses(1, pos);
0938             event.setRecurMonthlyByPos(frequency, poses, repeatCount, endDate);
0939         }
0940         else
0941         {
0942             // It's by day
0943             const int daynum = mMonthlyRule->date();
0944             QList<int> daynums(1, daynum);
0945             event.setRecurMonthlyByDate(frequency, daynums, repeatCount, endDate);
0946         }
0947     }
0948     else if (button == mYearlyButton)
0949     {
0950         const QList<int> months = mYearlyRule->months();
0951         if (mYearlyRule->type() == YearlyRule::POS)
0952         {
0953             // It's by position
0954             KAEvent::MonthPos pos;
0955             pos.days.fill(false);
0956             pos.days.setBit(mYearlyRule->dayOfWeek() - 1);
0957             pos.weeknum = mYearlyRule->week();
0958             QList<KAEvent::MonthPos> poses(1, pos);
0959             event.setRecurAnnualByPos(frequency, poses, months, repeatCount, endDate);
0960         }
0961         else
0962         {
0963             // It's by date in month
0964             event.setRecurAnnualByDate(frequency, months, mYearlyRule->date(),
0965                                        mYearlyRule->feb29Type(), repeatCount, endDate);
0966         }
0967     }
0968     else
0969     {
0970         event.setNoRecur();
0971         event.endChanges();
0972         return;
0973     }
0974     if (!event.recurs())
0975     {
0976         event.endChanges();
0977         return;    // an error occurred setting up the recurrence
0978     }
0979     if (adjustStart)
0980         event.setFirstRecurrence();
0981 
0982     // Set up repetition within the recurrence
0983     // N.B. This requires the main recurrence to be set up first.
0984     event.setRepetition((mRuleButtonType < SUBDAILY) ? Repetition() : mSubRepetition->repetition());
0985 
0986     // Set up exceptions
0987     event.recurrence()->setExDates(mExceptionDates);
0988     event.setWorkTimeOnly(mWorkTimeOnly->isChecked());
0989     event.setExcludeHolidays(mExcludeHolidays->isChecked());
0990 
0991     event.endChanges();
0992 }
0993 
0994 /******************************************************************************
0995 * Save the state of all controls.
0996 */
0997 void RecurrenceEdit::saveState()
0998 {
0999     mSavedRuleButton = mRuleButtonGroup->checkedButton();
1000     if (mRule)
1001         mRule->saveState();
1002     mSavedRangeButton = mRangeButtonGroup->checkedButton();
1003     if (mSavedRangeButton == mRepeatCountButton)
1004         mSavedRecurCount = mRepeatCountEntry->value();
1005     else if (mSavedRangeButton == mEndDateButton)
1006     {
1007         mSavedEndDateTime = KADateTime(mEndDateEdit->date(), mEndTimeEdit->time(), mCurrStartDateTime.timeSpec());
1008         mSavedEndDateTime.setDateOnly(mEndAnyTimeCheckBox->isChecked());
1009     }
1010     mSavedExceptionDates = mExceptionDates;
1011     mSavedWorkTimeOnly   = mWorkTimeOnly->isChecked();
1012     mSavedExclHolidays   = mExcludeHolidays->isChecked();
1013     mSavedRepetition     = mSubRepetition->repetition();
1014 }
1015 
1016 /******************************************************************************
1017 * Check whether any of the controls have changed state since initialisation.
1018 */
1019 bool RecurrenceEdit::stateChanged() const
1020 {
1021     if (mSavedRuleButton  != mRuleButtonGroup->checkedButton()
1022     ||  mSavedRangeButton != mRangeButtonGroup->checkedButton()
1023     ||  (mRule  &&  mRule->stateChanged()))
1024         return true;
1025     if (mSavedRangeButton == mRepeatCountButton
1026     &&  mSavedRecurCount  != mRepeatCountEntry->value())
1027         return true;
1028     if (mSavedRangeButton == mEndDateButton)
1029     {
1030         KADateTime edt(mEndDateEdit->date(), mEndTimeEdit->time(), mCurrStartDateTime.timeSpec());
1031         edt.setDateOnly(mEndAnyTimeCheckBox->isChecked());
1032         if (mSavedEndDateTime != edt)
1033             return true;
1034     }
1035     if (mSavedExceptionDates != mExceptionDates
1036     ||  mSavedWorkTimeOnly   != mWorkTimeOnly->isChecked()
1037     ||  mSavedExclHolidays   != mExcludeHolidays->isChecked()
1038     ||  mSavedRepetition     != mSubRepetition->repetition())
1039         return true;
1040     return false;
1041 }
1042 
1043 
1044 
1045 /*=============================================================================
1046 = Class Rule
1047 = Base class for rule widgets, including recurrence frequency.
1048 =============================================================================*/
1049 
1050 Rule::Rule(const QString& freqText, const QString& freqWhatsThis, bool time, bool readOnly, QWidget* parent)
1051     : NoRule(parent)
1052 {
1053     mLayout = new QVBoxLayout(this);
1054     mLayout->setContentsMargins(0, 0, 0, 0);
1055 
1056     auto freqLayout = new QHBoxLayout();
1057     freqLayout->setContentsMargins(0, 0, 0, 0);
1058     mLayout->addLayout(freqLayout);
1059     QWidget* box = new QWidget(this);    // this is to control the QWhatsThis text display area
1060     freqLayout->addWidget(box, 0, Qt::AlignLeft);
1061     auto boxLayout = new QHBoxLayout(box);
1062     boxLayout->setContentsMargins(0, 0, 0, 0);
1063 
1064     QLabel* label = new QLabel(i18nc("@label:spinbox", "Recur every"), box);
1065     boxLayout->addWidget(label, 0, Qt::AlignLeft);
1066     if (time)
1067     {
1068         mIntSpinBox = nullptr;
1069         mSpinBox = mTimeSpinBox = new TimeSpinBox(1, 5999, box);
1070         mTimeSpinBox->setReadOnly(readOnly);
1071         boxLayout->addWidget(mSpinBox, 0, Qt::AlignLeft);
1072         connect(mTimeSpinBox, &TimeSpinBox::valueChanged, this, &Rule::frequencyChanged);
1073         connect(mTimeSpinBox, &TimeSpinBox::valueChanged, this, &Rule::changed);
1074     }
1075     else
1076     {
1077         mTimeSpinBox = nullptr;
1078         mSpinBox = mIntSpinBox = new SpinBox(1, 999, box);
1079         mIntSpinBox->setReadOnly(readOnly);
1080         boxLayout->addWidget(mSpinBox, 0, Qt::AlignLeft);
1081         connect(mIntSpinBox, &QSpinBox::valueChanged, this, &Rule::frequencyChanged);
1082         connect(mIntSpinBox, &QSpinBox::valueChanged, this, &Rule::changed);
1083     }
1084     label->setBuddy(mSpinBox);
1085     label = new QLabel(freqText, box);
1086     boxLayout->addWidget(label, 0, Qt::AlignLeft);
1087     box->setFixedSize(sizeHint());
1088     box->setWhatsThis(freqWhatsThis);
1089 }
1090 
1091 int Rule::frequency() const
1092 {
1093     if (mIntSpinBox)
1094         return mIntSpinBox->value();
1095     if (mTimeSpinBox)
1096         return mTimeSpinBox->value();
1097     return 0;
1098 }
1099 
1100 void Rule::setFrequency(int n)
1101 {
1102     if (mIntSpinBox)
1103         mIntSpinBox->setValue(n);
1104     if (mTimeSpinBox)
1105         mTimeSpinBox->setValue(n);
1106 }
1107 
1108 /******************************************************************************
1109 * Save the state of all controls.
1110 */
1111 void Rule::saveState()
1112 {
1113     mSavedFrequency = frequency();
1114 }
1115 
1116 /******************************************************************************
1117 * Check whether any of the controls have changed state since initialisation.
1118 */
1119 bool Rule::stateChanged() const
1120 {
1121     return (mSavedFrequency != frequency());
1122 }
1123 
1124 
1125 /*=============================================================================
1126 = Class SubDailyRule
1127 = Sub-daily rule widget.
1128 =============================================================================*/
1129 
1130 SubDailyRule::SubDailyRule(bool readOnly, QWidget* parent)
1131     : Rule(i18nc("@label Time units for user-entered numbers", "hours:minutes"),
1132            i18nc("@info:whatsthis", "Enter the number of hours and minutes between repetitions of the alarm"),
1133            true, readOnly, parent)
1134 { }
1135 
1136 
1137 /*=============================================================================
1138 = Class DayWeekRule
1139 = Daily/weekly rule widget base class.
1140 =============================================================================*/
1141 
1142 DayWeekRule::DayWeekRule(const QString& freqText, const QString& freqWhatsThis, const QString& daysWhatsThis,
1143                          bool readOnly, QWidget* parent)
1144     : Rule(freqText, freqWhatsThis, false, readOnly, parent)
1145     , mSavedDays(7)
1146 {
1147     auto grid = new QGridLayout();
1148     grid->setContentsMargins(0, 0, 0, 0);
1149     grid->setRowStretch(0, 1);
1150     layout()->addLayout(grid);
1151 
1152     QLabel* label = new QLabel(i18nc("@label On: Tuesday", "On:"), this);
1153     grid->addWidget(label, 0, 0, Qt::AlignRight | Qt::AlignTop);
1154     grid->setColumnMinimumWidth(1, style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
1155 
1156     // List the days of the week starting at the user's start day of the week.
1157     // Save the first day of the week, just in case it changes while the dialog is open.
1158     QWidget* box = new QWidget(this);   // this is to control the QWhatsThis text display area
1159     auto dgrid = new QGridLayout(box);
1160     dgrid->setContentsMargins(0, 0, 0, 0);
1161     QLocale locale;
1162     for (int i = 0;  i < 7;  ++i)
1163     {
1164         const int day = Locale::localeDayInWeek_to_weekDay(i);
1165         mDayBox[i] = new CheckBox(locale.dayName(day), box);
1166         mDayBox[i]->setReadOnly(readOnly);
1167         connect(mDayBox[i], &QAbstractButton::toggled, this, &Rule::changed);
1168         dgrid->addWidget(mDayBox[i], i%4, i/4, Qt::AlignLeft);
1169     }
1170     box->setFixedSize(box->sizeHint());
1171     box->setWhatsThis(daysWhatsThis);
1172     grid->addWidget(box, 0, 2, Qt::AlignLeft);
1173     label->setBuddy(mDayBox[0]);
1174     grid->setColumnStretch(3, 1);
1175 }
1176 
1177 /******************************************************************************
1178 * Fetch which days of the week have been ticked.
1179 */
1180 QBitArray DayWeekRule::days() const
1181 {
1182     QBitArray ds(7);
1183     ds.fill(false);
1184     for (int i = 0;  i < 7;  ++i)
1185         if (mDayBox[i]->isChecked())
1186             ds.setBit(Locale::localeDayInWeek_to_weekDay(i) - 1, true);
1187     return ds;
1188 }
1189 
1190 /******************************************************************************
1191 * Tick/untick every day of the week.
1192 */
1193 void DayWeekRule::setDays(bool tick)
1194 {
1195     for (int i = 0;  i < 7;  ++i)
1196         mDayBox[i]->setChecked(tick);
1197 }
1198 
1199 /******************************************************************************
1200 * Tick/untick each day of the week according to the specified bits.
1201 */
1202 void DayWeekRule::setDays(const QBitArray& days)
1203 {
1204     if (days.size() != 7)
1205     {
1206         qCWarning(KALARM_LOG) << "DayWeekRule::setDays: Error! 'days' parameter must have 7 elements: actual size" << days.size();
1207         return;
1208     }
1209     for (int i = 0;  i < 7;  ++i)
1210     {
1211         bool x = days.testBit(Locale::localeDayInWeek_to_weekDay(i) - 1);
1212         mDayBox[i]->setChecked(x);
1213     }
1214 }
1215 
1216 /******************************************************************************
1217 * Tick the specified day of the week, and untick all other days.
1218 */
1219 void DayWeekRule::setDay(int dayOfWeek)
1220 {
1221     for (int i = 0;  i < 7;  ++i)
1222         mDayBox[i]->setChecked(false);
1223     if (dayOfWeek > 0  &&  dayOfWeek <= 7)
1224         mDayBox[Locale::weekDay_to_localeDayInWeek(dayOfWeek)]->setChecked(true);
1225 }
1226 
1227 /******************************************************************************
1228 * Validate: check that at least one day is selected.
1229 */
1230 QWidget* DayWeekRule::validate(QString& errorMessage)
1231 {
1232     for (int i = 0;  i < 7;  ++i)
1233         if (mDayBox[i]->isChecked())
1234             return nullptr;
1235     errorMessage = i18nc("@info", "No day selected");
1236     return mDayBox[0];
1237 }
1238 
1239 /******************************************************************************
1240 * Save the state of all controls.
1241 */
1242 void DayWeekRule::saveState()
1243 {
1244     Rule::saveState();
1245     mSavedDays = days();
1246 }
1247 
1248 /******************************************************************************
1249 * Check whether any of the controls have changed state since initialisation.
1250 */
1251 bool DayWeekRule::stateChanged() const
1252 {
1253     return (Rule::stateChanged()
1254         ||  mSavedDays != days());
1255 }
1256 
1257 
1258 /*=============================================================================
1259 = Class DailyRule
1260 = Daily rule widget.
1261 =============================================================================*/
1262 
1263 DailyRule::DailyRule(bool readOnly, QWidget* parent)
1264     : DayWeekRule(i18nc("@label Time unit for user-entered number", "day(s)"),
1265                   i18nc("@info:whatsthis", "Enter the number of days between repetitions of the alarm"),
1266                   i18nc("@info:whatsthis", "Select the days of the week on which the alarm is allowed to occur"),
1267                   readOnly, parent)
1268 { }
1269 
1270 
1271 /*=============================================================================
1272 = Class WeeklyRule
1273 = Weekly rule widget.
1274 =============================================================================*/
1275 
1276 WeeklyRule::WeeklyRule(bool readOnly, QWidget* parent)
1277     : DayWeekRule(i18nc("@label Time unit for user-entered number", "week(s)"),
1278                   i18nc("@info:whatsthis", "Enter the number of weeks between repetitions of the alarm"),
1279                   i18nc("@info:whatsthis", "Select the days of the week on which to repeat the alarm"),
1280                   readOnly, parent)
1281 { }
1282 
1283 
1284 /*=============================================================================
1285 = Class MonthYearRule
1286 = Monthly/yearly rule widget base class.
1287 =============================================================================*/
1288 
1289 MonthYearRule::MonthYearRule(const QString& freqText, const QString& freqWhatsThis, bool allowEveryWeek,
1290                              bool readOnly, QWidget* parent)
1291     : Rule(freqText, freqWhatsThis, false, readOnly, parent)
1292     , mEveryWeek(allowEveryWeek)
1293 {
1294     mButtonGroup = new ButtonGroup(this);
1295 
1296     // Month day selector
1297     auto boxLayout = new QGridLayout();
1298     boxLayout->setContentsMargins(0, 0, 0, 0);
1299     layout()->addLayout(boxLayout);
1300 
1301     mDayButton = new RadioButton(i18nc("@option:radio On day number in the month", "On day"), this);
1302     mDayButton->setFixedSize(mDayButton->sizeHint());
1303     mDayButton->setReadOnly(readOnly);
1304     mButtonGroup->addButton(mDayButton);
1305     mDayButton->setWhatsThis(i18nc("@info:whatsthis", "Repeat the alarm on the selected day of the month"));
1306     boxLayout->addWidget(mDayButton, 0, 0);
1307 
1308     QLocale locale;
1309     mDayCombo = new ComboBox(this);
1310     mDayCombo->setEditable(false);
1311     mDayCombo->setMaxVisibleItems(11);
1312     for (int i = 0;  i < 31;  ++i)
1313         mDayCombo->addItem(locale.toString(i + 1));
1314     mDayCombo->addItem(i18nc("@item:inlistbox Last day of month", "Last"));
1315     mDayCombo->setReadOnly(readOnly);
1316     mDayCombo->setWhatsThis(i18nc("@info:whatsthis", "Select the day of the month on which to repeat the alarm"));
1317     mDayButton->setFocusWidget(mDayCombo);
1318     connect(mDayCombo, &ComboBox::activated, this, &MonthYearRule::slotDaySelected);
1319     connect(mDayCombo, static_cast<void (ComboBox::*)(int)>(&ComboBox::currentIndexChanged), this, &MonthYearRule::changed);
1320     boxLayout->addWidget(mDayCombo, 0, 1, 1, 2, Qt::AlignLeft);
1321 
1322     // Month position selector
1323     mPosButton = new RadioButton(i18nc("@option:radio On the 1st Tuesday", "On the"), this);
1324     mPosButton->setFixedSize(mPosButton->sizeHint());
1325     mPosButton->setReadOnly(readOnly);
1326     mButtonGroup->addButton(mPosButton);
1327     mPosButton->setWhatsThis(i18nc("@info:whatsthis", "Repeat the alarm on one day of the week, in the selected week of the month"));
1328     boxLayout->addWidget(mPosButton, 1, 0);
1329 
1330     mWeekCombo = new ComboBox(this);
1331     mWeekCombo->setEditable(false);
1332     mWeekCombo->addItem(i18nc("@item:inlistbox", "1st"));
1333     mWeekCombo->addItem(i18nc("@item:inlistbox", "2nd"));
1334     mWeekCombo->addItem(i18nc("@item:inlistbox", "3rd"));
1335     mWeekCombo->addItem(i18nc("@item:inlistbox", "4th"));
1336     mWeekCombo->addItem(i18nc("@item:inlistbox", "5th"));
1337     mWeekCombo->addItem(i18nc("@item:inlistbox Last Monday in March", "Last"));
1338     mWeekCombo->addItem(i18nc("@item:inlistbox", "2nd Last"));
1339     mWeekCombo->addItem(i18nc("@item:inlistbox", "3rd Last"));
1340     mWeekCombo->addItem(i18nc("@item:inlistbox", "4th Last"));
1341     mWeekCombo->addItem(i18nc("@item:inlistbox", "5th Last"));
1342     if (mEveryWeek)
1343     {
1344         mWeekCombo->addItem(i18nc("@item:inlistbox Every (Monday...) in month", "Every"));
1345         mWeekCombo->setMaxVisibleItems(11);
1346     }
1347     mWeekCombo->setWhatsThis(i18nc("@info:whatsthis", "Select the week of the month in which to repeat the alarm"));
1348     mWeekCombo->setReadOnly(readOnly);
1349     mPosButton->setFocusWidget(mWeekCombo);
1350     connect(mWeekCombo, static_cast<void (ComboBox::*)(int)>(&ComboBox::currentIndexChanged), this, &MonthYearRule::changed);
1351     boxLayout->addWidget(mWeekCombo, 1, 1);
1352 
1353     mDayOfWeekCombo = new ComboBox(this);
1354     mDayOfWeekCombo->setEditable(false);
1355     for (int i = 0;  i < 7;  ++i)
1356     {
1357         int day = Locale::localeDayInWeek_to_weekDay(i);
1358         mDayOfWeekCombo->addItem(locale.dayName(day));
1359     }
1360     mDayOfWeekCombo->setReadOnly(readOnly);
1361     mDayOfWeekCombo->setWhatsThis(i18nc("@info:whatsthis", "Select the day of the week on which to repeat the alarm"));
1362     connect(mDayOfWeekCombo, static_cast<void (ComboBox::*)(int)>(&ComboBox::currentIndexChanged), this, &MonthYearRule::changed);
1363     boxLayout->addWidget(mDayOfWeekCombo, 1, 2, Qt::AlignLeft);
1364 
1365     connect(mButtonGroup, &ButtonGroup::buttonSet, this, &MonthYearRule::clicked);
1366     connect(mButtonGroup, &ButtonGroup::buttonSet, this, &MonthYearRule::changed);
1367 }
1368 
1369 MonthYearRule::DayPosType MonthYearRule::type() const
1370 {
1371     return (mButtonGroup->checkedButton() == mDayButton) ? DATE : POS;
1372 }
1373 
1374 void MonthYearRule::setType(MonthYearRule::DayPosType type)
1375 {
1376     if (type == DATE)
1377         mDayButton->setChecked(true);
1378     else
1379         mPosButton->setChecked(true);
1380 }
1381 
1382 void MonthYearRule::setDefaultValues(int dayOfMonth, int dayOfWeek)
1383 {
1384     --dayOfMonth;
1385     mDayCombo->setCurrentIndex(dayOfMonth);
1386     mWeekCombo->setCurrentIndex(dayOfMonth / 7);
1387     mDayOfWeekCombo->setCurrentIndex(Locale::weekDay_to_localeDayInWeek(dayOfWeek));
1388 }
1389 
1390 int MonthYearRule::date() const
1391 {
1392     const int daynum  = mDayCombo->currentIndex() + 1;
1393     return (daynum <= 31) ? daynum : 31 - daynum;
1394 }
1395 
1396 int MonthYearRule::week() const
1397 {
1398     int weeknum = mWeekCombo->currentIndex() + 1;
1399     return (weeknum <= 5) ? weeknum : (weeknum == 11) ? 0 : 5 - weeknum;
1400 }
1401 
1402 int MonthYearRule::dayOfWeek() const
1403 {
1404     return Locale::localeDayInWeek_to_weekDay(mDayOfWeekCombo->currentIndex());
1405 }
1406 
1407 void MonthYearRule::setDate(int dayOfMonth)
1408 {
1409     mDayButton->setChecked(true);;
1410     mDayCombo->setCurrentIndex(dayOfMonth > 0 ? dayOfMonth - 1 : dayOfMonth < 0 ? 30 - dayOfMonth : 0);   // day 0 shouldn't ever occur
1411 }
1412 
1413 void MonthYearRule::setPosition(int week, int dayOfWeek)
1414 {
1415     mPosButton->setChecked(true);
1416     mWeekCombo->setCurrentIndex((week > 0) ? week - 1 : (week < 0) ? 4 - week : mEveryWeek ? 10 : 0);
1417     mDayOfWeekCombo->setCurrentIndex(Locale::weekDay_to_localeDayInWeek(dayOfWeek));
1418 }
1419 
1420 void MonthYearRule::enableSelection(DayPosType type)
1421 {
1422     const bool date = (type == DATE);
1423     mDayCombo->setEnabled(date);
1424     mWeekCombo->setEnabled(!date);
1425     mDayOfWeekCombo->setEnabled(!date);
1426 }
1427 
1428 void MonthYearRule::clicked(QAbstractButton* button)
1429 {
1430     enableSelection(button == mDayButton ? DATE : POS);
1431 }
1432 
1433 void MonthYearRule::slotDaySelected(int index)
1434 {
1435     daySelected(index <= 30 ? index + 1 : 30 - index);
1436 }
1437 
1438 /******************************************************************************
1439 * Save the state of all controls.
1440 */
1441 void MonthYearRule::saveState()
1442 {
1443     Rule::saveState();
1444     mSavedType = type();
1445     if (mSavedType == DATE)
1446         mSavedDay = date();
1447     else
1448     {
1449         mSavedWeek    = week();
1450         mSavedWeekDay = dayOfWeek();
1451     }
1452 }
1453 
1454 /******************************************************************************
1455 * Check whether any of the controls have changed state since initialisation.
1456 */
1457 bool MonthYearRule::stateChanged() const
1458 {
1459     if (Rule::stateChanged()
1460     ||  mSavedType != type())
1461         return true;
1462     if (mSavedType == DATE)
1463     {
1464         if (mSavedDay != date())
1465             return true;
1466     }
1467     else
1468     {
1469         if (mSavedWeek    != week()
1470         ||  mSavedWeekDay != dayOfWeek())
1471             return true;
1472     }
1473     return false;
1474 }
1475 
1476 
1477 /*=============================================================================
1478 = Class MonthlyRule
1479 = Monthly rule widget.
1480 =============================================================================*/
1481 
1482 MonthlyRule::MonthlyRule(bool readOnly, QWidget* parent)
1483     : MonthYearRule(i18nc("@label Time unit for user-entered number", "month(s)"),
1484            i18nc("@info:whatsthis", "Enter the number of months between repetitions of the alarm"),
1485            false, readOnly, parent)
1486 { }
1487 
1488 
1489 /*=============================================================================
1490 = Class YearlyRule
1491 = Yearly rule widget.
1492 =============================================================================*/
1493 
1494 YearlyRule::YearlyRule(bool readOnly, QWidget* parent)
1495     : MonthYearRule(i18nc("@label Time unit for user-entered number", "year(s)"),
1496            i18nc("@info:whatsthis", "Enter the number of years between repetitions of the alarm"),
1497            true, readOnly, parent)
1498 {
1499     // Set up the month selection widgets
1500     auto hlayout = new QHBoxLayout();
1501     hlayout->setContentsMargins(0, 0, 0, 0);
1502     layout()->addLayout(hlayout);
1503     QLabel* label = new QLabel(i18nc("@label List of months to select", "Months:"), this);
1504     hlayout->addWidget(label, 0, Qt::AlignLeft | Qt::AlignTop);
1505 
1506     // List the months of the year.
1507     QWidget* w = new QWidget(this);   // this is to control the QWhatsThis text display area
1508     hlayout->addWidget(w, 1, Qt::AlignLeft);
1509     auto grid = new QGridLayout(w);
1510     grid->setContentsMargins(0, 0, 0, 0);
1511     QLocale locale;
1512     for (int i = 0;  i < 12;  ++i)
1513     {
1514         mMonthBox[i] = new CheckBox(locale.monthName(i + 1, QLocale::ShortFormat), w);
1515         mMonthBox[i]->setReadOnly(readOnly);
1516         connect(mMonthBox[i], &QAbstractButton::toggled, this, &Rule::changed);
1517         grid->addWidget(mMonthBox[i], i%3, i/3, Qt::AlignLeft);
1518     }
1519     connect(mMonthBox[1], &QAbstractButton::toggled, this, &YearlyRule::enableFeb29);
1520     w->setFixedHeight(w->sizeHint().height());
1521     w->setWhatsThis(i18nc("@info:whatsthis", "Select the months of the year in which to repeat the alarm"));
1522 
1523     // February 29th handling option
1524     auto f29box = new QHBoxLayout;
1525     layout()->addLayout(f29box);
1526     w = new QWidget(this);     // this is to control the QWhatsThis text display area
1527     f29box->addWidget(w, 0, Qt::AlignLeft);
1528     auto boxLayout = new QHBoxLayout(w);
1529     boxLayout->setContentsMargins(0, 0, 0, 0);
1530     mFeb29Label = new QLabel(i18nc("@label:listbox", "February 29th alarm in non-leap years:"));
1531     boxLayout->addWidget(mFeb29Label);
1532     mFeb29Combo = new ComboBox();
1533     mFeb29Combo->setEditable(false);
1534     mFeb29Combo->addItem(i18nc("@item:inlistbox No date", "None"));
1535     mFeb29Combo->addItem(i18nc("@item:inlistbox 1st March (short form)", "1 Mar"));
1536     mFeb29Combo->addItem(i18nc("@item:inlistbox 28th February (short form)", "28 Feb"));
1537     mFeb29Combo->setReadOnly(readOnly);
1538     connect(mFeb29Combo, static_cast<void (ComboBox::*)(int)>(&ComboBox::currentIndexChanged), this, &YearlyRule::changed);
1539     mFeb29Label->setBuddy(mFeb29Combo);
1540     boxLayout->addWidget(mFeb29Combo);
1541     w->setFixedSize(w->sizeHint());
1542     w->setWhatsThis(i18nc("@info:whatsthis", "Select which date, if any, the February 29th alarm should trigger in non-leap years"));
1543 }
1544 
1545 void YearlyRule::setDefaultValues(int dayOfMonth, int dayOfWeek, int month)
1546 {
1547     MonthYearRule::setDefaultValues(dayOfMonth, dayOfWeek);
1548     --month;
1549     for (int i = 0;  i < 12;  ++i)
1550         mMonthBox[i]->setChecked(i == month);
1551     setFeb29Type(KARecurrence::defaultFeb29Type());
1552     daySelected(dayOfMonth);     // enable/disable month checkboxes as appropriate
1553 }
1554 
1555 /******************************************************************************
1556 * Fetch which months have been checked (1 - 12).
1557 * Reply = true if February has been checked.
1558 */
1559 QList<int> YearlyRule::months() const
1560 {
1561     QList<int> mnths;
1562     for (int i = 0;  i < 12;  ++i)
1563         if (mMonthBox[i]->isChecked()  &&  mMonthBox[i]->isEnabled())
1564             mnths.append(i + 1);
1565     return mnths;
1566 }
1567 
1568 /******************************************************************************
1569 * Check/uncheck each month of the year according to the specified list.
1570 */
1571 void YearlyRule::setMonths(const QList<int>& mnths)
1572 {
1573     bool checked[12];
1574     for (int i = 0;  i < 12;  ++i)
1575         checked[i] = false;
1576     for (int i = 0, end = mnths.count();  i < end;  ++i)
1577         checked[mnths[i] - 1] = true;
1578     for (int i = 0;  i < 12;  ++i)
1579         mMonthBox[i]->setChecked(checked[i]);
1580     enableFeb29();
1581 }
1582 
1583 /******************************************************************************
1584 * Return the date for February 29th alarms in non-leap years.
1585 */
1586 KARecurrence::Feb29Type YearlyRule::feb29Type() const
1587 {
1588     if (mFeb29Combo->isEnabled())
1589     {
1590         switch (mFeb29Combo->currentIndex())
1591         {
1592             case 1:   return KARecurrence::Feb29_Mar1;
1593             case 2:   return KARecurrence::Feb29_Feb28;
1594             default:  break;
1595         }
1596     }
1597     return KARecurrence::Feb29_None;
1598 }
1599 
1600 /******************************************************************************
1601 * Set the date for February 29th alarms to trigger in non-leap years.
1602 */
1603 void YearlyRule::setFeb29Type(KARecurrence::Feb29Type type)
1604 {
1605     int index;
1606     switch (type)
1607     {
1608         default:
1609         case KARecurrence::Feb29_None:  index = 0;  break;
1610         case KARecurrence::Feb29_Mar1:  index = 1;  break;
1611         case KARecurrence::Feb29_Feb28: index = 2;  break;
1612     }
1613     mFeb29Combo->setCurrentIndex(index);
1614 }
1615 
1616 /******************************************************************************
1617 * Validate: check that at least one month is selected.
1618 */
1619 QWidget* YearlyRule::validate(QString& errorMessage)
1620 {
1621     for (int i = 0;  i < 12;  ++i)
1622         if (mMonthBox[i]->isChecked()  &&  mMonthBox[i]->isEnabled())
1623             return nullptr;
1624     errorMessage = i18nc("@info", "No month selected");
1625     return mMonthBox[0];
1626 }
1627 
1628 /******************************************************************************
1629 * Called when a yearly recurrence type radio button is clicked,
1630 * to enable/disable month checkboxes as appropriate for the date selected.
1631 */
1632 void YearlyRule::clicked(QAbstractButton* button)
1633 {
1634     MonthYearRule::clicked(button);
1635     daySelected(buttonType(button) == DATE ? date() : 1);
1636 }
1637 
1638 /******************************************************************************
1639 * Called when a day of the month is selected in a yearly recurrence, to
1640 * disable months for which the day is out of range.
1641 */
1642 void YearlyRule::daySelected(int day)
1643 {
1644     mMonthBox[1]->setEnabled(day <= 29);  // February
1645     const bool enable = (day != 31);
1646     mMonthBox[3]->setEnabled(enable);     // April
1647     mMonthBox[5]->setEnabled(enable);     // June
1648     mMonthBox[8]->setEnabled(enable);     // September
1649     mMonthBox[10]->setEnabled(enable);    // November
1650     enableFeb29();
1651 }
1652 
1653 /******************************************************************************
1654 * Enable/disable the February 29th combo box depending on whether February
1655 * 29th is selected.
1656 */
1657 void YearlyRule::enableFeb29()
1658 {
1659     const bool enable = (type() == DATE  &&  date() == 29  &&  mMonthBox[1]->isChecked()  &&  mMonthBox[1]->isEnabled());
1660     mFeb29Label->setEnabled(enable);
1661     mFeb29Combo->setEnabled(enable);
1662 }
1663 
1664 /******************************************************************************
1665 * Save the state of all controls.
1666 */
1667 void YearlyRule::saveState()
1668 {
1669     MonthYearRule::saveState();
1670     mSavedMonths    = months();
1671     mSavedFeb29Type = feb29Type();
1672 }
1673 
1674 /******************************************************************************
1675 * Check whether any of the controls have changed state since initialisation.
1676 */
1677 bool YearlyRule::stateChanged() const
1678 {
1679     return (MonthYearRule::stateChanged()
1680         ||  mSavedMonths    != months()
1681         ||  mSavedFeb29Type != feb29Type());
1682 }
1683 
1684 #include "moc_recurrenceedit_p.cpp"
1685 #include "moc_recurrenceedit.cpp"
1686 
1687 // vim: et sw=4: