Make it possible to use a distinct selection model in the foldertreewidget.
[kdepim.git] / kalarm / recurrenceedit.cpp
blob60c37a96ce7fcc8591c3ad083fbf2282d8dd6d89
1 /*
2 * recurrenceedit.cpp - widget to edit the event's recurrence definition
3 * Program: kalarm
4 * Copyright © 2002-2010 by David Jarvie <djarvie@kde.org>
6 * Based originally on KOrganizer module koeditorrecurrence.cpp,
7 * Copyright (c) 2000,2001 Cornelius Schumacher <schumacher@kde.org>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License along
20 * with this program; if not, write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24 #include "kalarm.h"
25 #include "recurrenceedit.moc"
26 #include "recurrenceeditprivate.moc"
28 #include "alarmtimewidget.h"
29 #include "checkbox.h"
30 #include "combobox.h"
31 #include "dateedit.h"
32 #include "kaevent.h"
33 #include "kalarmapp.h"
34 #include "kalocale.h"
35 #include "karecurrence.h"
36 #include "preferences.h"
37 #include "radiobutton.h"
38 #include "repetitionbutton.h"
39 #include "spinbox.h"
40 #include "timeedit.h"
41 #include "timespinbox.h"
42 #include "buttongroup.h"
44 #include <kcal/event.h>
46 #include <kglobal.h>
47 #include <klocale.h>
48 #include <kcalendarsystem.h>
49 #include <kiconloader.h>
50 #include <kdialog.h>
51 #include <kmessagebox.h>
52 #include <kdebug.h>
54 #include <QPushButton>
55 #include <QLabel>
56 #include <QStackedWidget>
57 #include <QListWidget>
58 #include <QGroupBox>
59 #include <QGridLayout>
60 #include <QHBoxLayout>
61 #include <QVBoxLayout>
62 #include <QtAlgorithms>
64 using namespace KCal;
67 class ListWidget : public QListWidget
69 public:
70 explicit ListWidget(QWidget* parent) : QListWidget(parent) {}
71 virtual QSize sizeHint() const { return minimumSizeHint(); }
74 // Collect these widget labels together to ensure consistent wording and
75 // translations across different modules.
76 QString RecurrenceEdit::i18n_combo_NoRecur() { return i18nc("@item:inlistbox Recurrence type", "No Recurrence"); }
77 QString RecurrenceEdit::i18n_combo_AtLogin() { return i18nc("@item:inlistbox Recurrence type", "At Login"); }
78 QString RecurrenceEdit::i18n_combo_HourlyMinutely() { return i18nc("@item:inlistbox Recurrence type", "Hourly/Minutely"); }
79 QString RecurrenceEdit::i18n_combo_Daily() { return i18nc("@item:inlistbox Recurrence type", "Daily"); }
80 QString RecurrenceEdit::i18n_combo_Weekly() { return i18nc("@item:inlistbox Recurrence type", "Weekly"); }
81 QString RecurrenceEdit::i18n_combo_Monthly() { return i18nc("@item:inlistbox Recurrence type", "Monthly"); }
82 QString RecurrenceEdit::i18n_combo_Yearly() { return i18nc("@item:inlistbox Recurrence type", "Yearly"); }
85 RecurrenceEdit::RecurrenceEdit(bool readOnly, QWidget* parent)
86 : QFrame(parent),
87 mRule(0),
88 mRuleButtonType(INVALID_RECUR),
89 mDailyShown(false),
90 mWeeklyShown(false),
91 mMonthlyShown(false),
92 mYearlyShown(false),
93 mNoEmitTypeChanged(true),
94 mReadOnly(readOnly)
96 kDebug();
97 QVBoxLayout* topLayout = new QVBoxLayout(this);
98 topLayout->setMargin(0);
99 topLayout->setSpacing(KDialog::spacingHint());
101 /* Create the recurrence rule Group box which holds the recurrence period
102 * selection buttons, and the weekly, monthly and yearly recurrence rule
103 * frames which specify options individual to each of these distinct
104 * sections of the recurrence rule. Each frame is made visible by the
105 * selection of its corresponding radio button.
108 QGroupBox* recurGroup = new QGroupBox(i18nc("@title:group", "Recurrence Rule"), this);
109 topLayout->addWidget(recurGroup);
110 QHBoxLayout* hlayout = new QHBoxLayout(recurGroup);
111 hlayout->setMargin(KDialog::marginHint());
112 hlayout->setSpacing(KDialog::marginHint()); // use margin spacing due to vertical divider line
114 // Recurrence period radio buttons
115 QVBoxLayout* vlayout = new QVBoxLayout();
116 vlayout->setSpacing(0);
117 vlayout->setMargin(0);
118 hlayout->addLayout(vlayout);
119 mRuleButtonGroup = new ButtonGroup(recurGroup);
120 connect(mRuleButtonGroup, SIGNAL(buttonSet(QAbstractButton*)), SLOT(periodClicked(QAbstractButton*)));
121 connect(mRuleButtonGroup, SIGNAL(buttonSet(QAbstractButton*)), SIGNAL(contentsChanged()));
123 mNoneButton = new RadioButton(i18n_combo_NoRecur(), recurGroup);
124 mNoneButton->setFixedSize(mNoneButton->sizeHint());
125 mNoneButton->setReadOnly(mReadOnly);
126 mNoneButton->setWhatsThis(i18nc("@info:whatsthis", "Do not repeat the alarm"));
127 mRuleButtonGroup->addButton(mNoneButton);
128 vlayout->addWidget(mNoneButton);
130 mAtLoginButton = new RadioButton(i18n_combo_AtLogin(), recurGroup);
131 mAtLoginButton->setFixedSize(mAtLoginButton->sizeHint());
132 mAtLoginButton->setReadOnly(mReadOnly);
133 mAtLoginButton->setWhatsThis(i18nc("@info:whatsthis",
134 "<para>Trigger the alarm at the specified date/time and at every login until then.</para>"
135 "<para>Note that it will also be triggered any time <application>KAlarm</application> is restarted.</para>"));
136 mRuleButtonGroup->addButton(mAtLoginButton);
137 vlayout->addWidget(mAtLoginButton);
139 mSubDailyButton = new RadioButton(i18n_combo_HourlyMinutely(), recurGroup);
140 mSubDailyButton->setFixedSize(mSubDailyButton->sizeHint());
141 mSubDailyButton->setReadOnly(mReadOnly);
142 mSubDailyButton->setWhatsThis(i18nc("@info:whatsthis", "Repeat the alarm at hourly/minutely intervals"));
143 mRuleButtonGroup->addButton(mSubDailyButton);
144 vlayout->addWidget(mSubDailyButton);
146 mDailyButton = new RadioButton(i18n_combo_Daily(), recurGroup);
147 mDailyButton->setFixedSize(mDailyButton->sizeHint());
148 mDailyButton->setReadOnly(mReadOnly);
149 mDailyButton->setWhatsThis(i18nc("@info:whatsthis", "Repeat the alarm at daily intervals"));
150 mRuleButtonGroup->addButton(mDailyButton);
151 vlayout->addWidget(mDailyButton);
153 mWeeklyButton = new RadioButton(i18n_combo_Weekly(), recurGroup);
154 mWeeklyButton->setFixedSize(mWeeklyButton->sizeHint());
155 mWeeklyButton->setReadOnly(mReadOnly);
156 mWeeklyButton->setWhatsThis(i18nc("@info:whatsthis", "Repeat the alarm at weekly intervals"));
157 mRuleButtonGroup->addButton(mWeeklyButton);
158 vlayout->addWidget(mWeeklyButton);
160 mMonthlyButton = new RadioButton(i18n_combo_Monthly(), recurGroup);
161 mMonthlyButton->setFixedSize(mMonthlyButton->sizeHint());
162 mMonthlyButton->setReadOnly(mReadOnly);
163 mMonthlyButton->setWhatsThis(i18nc("@info:whatsthis", "Repeat the alarm at monthly intervals"));
164 mRuleButtonGroup->addButton(mMonthlyButton);
165 vlayout->addWidget(mMonthlyButton);
167 mYearlyButton = new RadioButton(i18n_combo_Yearly(), recurGroup);
168 mYearlyButton->setFixedSize(mYearlyButton->sizeHint());
169 mYearlyButton->setReadOnly(mReadOnly);
170 mYearlyButton->setWhatsThis(i18nc("@info:whatsthis", "Repeat the alarm at annual intervals"));
171 mRuleButtonGroup->addButton(mYearlyButton);
172 vlayout->addWidget(mYearlyButton);
173 vlayout->addStretch(); // top-adjust the interval radio buttons
175 // Sub-repetition button
176 mSubRepetition = new RepetitionButton(i18nc("@action:button", "Sub-Repetition"), true, recurGroup);
177 mSubRepetition->setFixedSize(mSubRepetition->sizeHint());
178 mSubRepetition->setReadOnly(mReadOnly);
179 mSubRepetition->setWhatsThis(i18nc("@info:whatsthis",
180 "Set up a repetition within the recurrence, to trigger the alarm multiple times each time the recurrence is due."));
181 connect(mSubRepetition, SIGNAL(needsInitialisation()), SIGNAL(repeatNeedsInitialisation()));
182 connect(mSubRepetition, SIGNAL(changed()), SIGNAL(frequencyChanged()));
183 connect(mSubRepetition, SIGNAL(changed()), SIGNAL(contentsChanged()));
184 vlayout->addSpacing(KDialog::spacingHint());
185 vlayout->addWidget(mSubRepetition);
187 // Vertical divider line
188 vlayout = new QVBoxLayout();
189 vlayout->setMargin(0);
190 hlayout->addLayout(vlayout);
191 QFrame* divider = new QFrame(recurGroup);
192 divider->setFrameStyle(QFrame::VLine | QFrame::Sunken);
193 vlayout->addWidget(divider, 1);
195 // Rule definition stack
196 mRuleStack = new QStackedWidget(recurGroup);
197 hlayout->addWidget(mRuleStack);
198 hlayout->addStretch(1);
199 mNoRule = new NoRule(mRuleStack);
200 mSubDailyRule = new SubDailyRule(mReadOnly, mRuleStack);
201 mDailyRule = new DailyRule(mReadOnly, mRuleStack);
202 mWeeklyRule = new WeeklyRule(mReadOnly, mRuleStack);
203 mMonthlyRule = new MonthlyRule(mReadOnly, mRuleStack);
204 mYearlyRule = new YearlyRule(mReadOnly, mRuleStack);
206 connect(mSubDailyRule, SIGNAL(frequencyChanged()), SIGNAL(frequencyChanged()));
207 connect(mDailyRule, SIGNAL(frequencyChanged()), SIGNAL(frequencyChanged()));
208 connect(mWeeklyRule, SIGNAL(frequencyChanged()), SIGNAL(frequencyChanged()));
209 connect(mMonthlyRule, SIGNAL(frequencyChanged()), SIGNAL(frequencyChanged()));
210 connect(mYearlyRule, SIGNAL(frequencyChanged()), SIGNAL(frequencyChanged()));
211 connect(mSubDailyRule, SIGNAL(changed()), SIGNAL(contentsChanged()));
212 connect(mDailyRule, SIGNAL(changed()), SIGNAL(contentsChanged()));
213 connect(mWeeklyRule, SIGNAL(changed()), SIGNAL(contentsChanged()));
214 connect(mMonthlyRule, SIGNAL(changed()), SIGNAL(contentsChanged()));
215 connect(mYearlyRule, SIGNAL(changed()), SIGNAL(contentsChanged()));
217 mRuleStack->addWidget(mNoRule);
218 mRuleStack->addWidget(mSubDailyRule);
219 mRuleStack->addWidget(mDailyRule);
220 mRuleStack->addWidget(mWeeklyRule);
221 mRuleStack->addWidget(mMonthlyRule);
222 mRuleStack->addWidget(mYearlyRule);
223 hlayout->addSpacing(KDialog::marginHint());
225 // Create the recurrence range group which contains the controls
226 // which specify how long the recurrence is to last.
228 mRangeButtonBox = new QGroupBox(i18nc("@title:group", "Recurrence End"), this);
229 topLayout->addWidget(mRangeButtonBox);
230 mRangeButtonGroup = new ButtonGroup(mRangeButtonBox);
231 connect(mRangeButtonGroup, SIGNAL(buttonSet(QAbstractButton*)), SLOT(rangeTypeClicked()));
232 connect(mRangeButtonGroup, SIGNAL(buttonSet(QAbstractButton*)), SIGNAL(contentsChanged()));
234 vlayout = new QVBoxLayout(mRangeButtonBox);
235 vlayout->setMargin(KDialog::marginHint());
236 vlayout->setSpacing(KDialog::spacingHint());
237 mNoEndDateButton = new RadioButton(i18nc("@option:radio", "No end"), mRangeButtonBox);
238 mNoEndDateButton->setFixedSize(mNoEndDateButton->sizeHint());
239 mNoEndDateButton->setReadOnly(mReadOnly);
240 mNoEndDateButton->setWhatsThis(i18nc("@info:whatsthis", "Repeat the alarm indefinitely"));
241 mRangeButtonGroup->addButton(mNoEndDateButton);
242 vlayout->addWidget(mNoEndDateButton, 1, Qt::AlignLeft);
243 QSize size = mNoEndDateButton->size();
245 hlayout = new QHBoxLayout();
246 hlayout->setMargin(0);
247 vlayout->addLayout(hlayout);
248 mRepeatCountButton = new RadioButton(i18nc("@option:radio", "End after:"), mRangeButtonBox);
249 mRepeatCountButton->setReadOnly(mReadOnly);
250 mRepeatCountButton->setWhatsThis(i18nc("@info:whatsthis", "Repeat the alarm for the number of times specified"));
251 mRangeButtonGroup->addButton(mRepeatCountButton);
252 mRepeatCountEntry = new SpinBox(1, 9999, mRangeButtonBox);
253 mRepeatCountEntry->setFixedSize(mRepeatCountEntry->sizeHint());
254 mRepeatCountEntry->setSingleShiftStep(10);
255 mRepeatCountEntry->setSelectOnStep(false);
256 mRepeatCountEntry->setReadOnly(mReadOnly);
257 mRepeatCountEntry->setWhatsThis(i18nc("@info:whatsthis", "Enter the total number of times to trigger the alarm"));
258 connect(mRepeatCountEntry, SIGNAL(valueChanged(int)), SLOT(repeatCountChanged(int)));
259 connect(mRepeatCountEntry, SIGNAL(valueChanged(int)), SIGNAL(contentsChanged()));
260 mRepeatCountButton->setFocusWidget(mRepeatCountEntry);
261 mRepeatCountLabel = new QLabel(i18nc("@label", "occurrence(s)"), mRangeButtonBox);
262 mRepeatCountLabel->setFixedSize(mRepeatCountLabel->sizeHint());
263 hlayout->addWidget(mRepeatCountButton);
264 hlayout->addSpacing(KDialog::spacingHint());
265 hlayout->addWidget(mRepeatCountEntry);
266 hlayout->addWidget(mRepeatCountLabel);
267 hlayout->addStretch();
268 size = size.expandedTo(mRepeatCountButton->sizeHint());
270 hlayout = new QHBoxLayout();
271 hlayout->setMargin(0);
272 vlayout->addLayout(hlayout);
273 mEndDateButton = new RadioButton(i18nc("@option:radio", "End by:"), mRangeButtonBox);
274 mEndDateButton->setReadOnly(mReadOnly);
275 mEndDateButton->setWhatsThis(
276 i18nc("@info:whatsthis", "<para>Repeat the alarm until the date/time specified.</para>"
277 "<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>"));
278 mRangeButtonGroup->addButton(mEndDateButton);
279 mEndDateEdit = new DateEdit(mRangeButtonBox);
280 mEndDateEdit->setReadOnly(mReadOnly);
281 static const QString tzText = i18nc("@info/plain", "This uses the same time zone as the start time.");
282 mEndDateEdit->setWhatsThis(i18nc("@info:whatsthis",
283 "<para>Enter the last date to repeat the alarm.</para><para>%1</para>", tzText));
284 connect(mEndDateEdit, SIGNAL(dateChanged(const QDate&)), SIGNAL(contentsChanged()));
285 mEndDateButton->setFocusWidget(mEndDateEdit);
286 mEndTimeEdit = new TimeEdit(mRangeButtonBox);
287 mEndTimeEdit->setFixedSize(mEndTimeEdit->sizeHint());
288 mEndTimeEdit->setReadOnly(mReadOnly);
289 mEndTimeEdit->setWhatsThis(i18nc("@info:whatsthis",
290 "<para>Enter the last time to repeat the alarm.</para><para>%1</para><para>%2</para>", tzText, TimeSpinBox::shiftWhatsThis()));
291 connect(mEndTimeEdit, SIGNAL(valueChanged(int)), SIGNAL(contentsChanged()));
292 mEndAnyTimeCheckBox = new CheckBox(i18nc("@option:check", "Any time"), mRangeButtonBox);
293 mEndAnyTimeCheckBox->setFixedSize(mEndAnyTimeCheckBox->sizeHint());
294 mEndAnyTimeCheckBox->setReadOnly(mReadOnly);
295 mEndAnyTimeCheckBox->setWhatsThis(i18nc("@info:whatsthis", "Stop repeating the alarm after your first login on or after the specified end date"));
296 connect(mEndAnyTimeCheckBox, SIGNAL(toggled(bool)), SLOT(slotAnyTimeToggled(bool)));
297 connect(mEndAnyTimeCheckBox, SIGNAL(toggled(bool)), SIGNAL(contentsChanged()));
298 hlayout->addWidget(mEndDateButton);
299 hlayout->addSpacing(KDialog::spacingHint());
300 hlayout->addWidget(mEndDateEdit);
301 hlayout->addWidget(mEndTimeEdit);
302 hlayout->addWidget(mEndAnyTimeCheckBox);
303 hlayout->addStretch();
304 size = size.expandedTo(mEndDateButton->sizeHint());
306 // Line up the widgets to the right of the radio buttons
307 mRepeatCountButton->setFixedSize(size);
308 mEndDateButton->setFixedSize(size);
310 // Create the exceptions group which specifies dates to be excluded
311 // from the recurrence.
313 mExceptionGroup = new QGroupBox(i18nc("@title:group", "Exceptions"), this);
314 topLayout->addWidget(mExceptionGroup);
315 topLayout->setStretchFactor(mExceptionGroup, 2);
316 hlayout = new QHBoxLayout(mExceptionGroup);
317 hlayout->setMargin(KDialog::marginHint());
318 hlayout->setSpacing(KDialog::spacingHint());
319 vlayout = new QVBoxLayout();
320 vlayout->setMargin(0);
321 hlayout->addLayout(vlayout);
323 mExceptionDateList = new ListWidget(mExceptionGroup);
324 mExceptionDateList->setWhatsThis(i18nc("@info:whatsthis", "The list of exceptions, i.e. dates/times excluded from the recurrence"));
325 connect(mExceptionDateList, SIGNAL(currentRowChanged(int)), SLOT(enableExceptionButtons()));
326 vlayout->addWidget(mExceptionDateList);
328 if (mReadOnly)
330 mExceptionDateEdit = 0;
331 mChangeExceptionButton = 0;
332 mDeleteExceptionButton = 0;
334 else
336 vlayout = new QVBoxLayout();
337 vlayout->setMargin(0);
338 hlayout->addLayout(vlayout);
339 mExceptionDateEdit = new DateEdit(mExceptionGroup);
340 mExceptionDateEdit->setDate(KDateTime::currentLocalDate());
341 mExceptionDateEdit->setWhatsThis(i18nc("@info:whatsthis",
342 "Enter a date to insert in the exceptions list. "
343 "Use in conjunction with the Add or Change button below."));
344 vlayout->addWidget(mExceptionDateEdit, 0, Qt::AlignLeft);
346 hlayout = new QHBoxLayout();
347 hlayout->setMargin(0);
348 vlayout->addLayout(hlayout);
349 QPushButton* button = new QPushButton(i18nc("@action:button", "Add"), mExceptionGroup);
350 button->setWhatsThis(i18nc("@info:whatsthis", "Add the date entered above to the exceptions list"));
351 connect(button, SIGNAL(clicked()), SLOT(addException()));
352 hlayout->addWidget(button);
354 mChangeExceptionButton = new QPushButton(i18nc("@action:button", "Change"), mExceptionGroup);
355 mChangeExceptionButton->setWhatsThis(i18nc("@info:whatsthis",
356 "Replace the currently highlighted item in the exceptions list with the date entered above"));
357 connect(mChangeExceptionButton, SIGNAL(clicked()), SLOT(changeException()));
358 hlayout->addWidget(mChangeExceptionButton);
360 mDeleteExceptionButton = new QPushButton(i18nc("@action:button", "Delete"), mExceptionGroup);
361 mDeleteExceptionButton->setWhatsThis(i18nc("@info:whatsthis", "Remove the currently highlighted item from the exceptions list"));
362 connect(mDeleteExceptionButton, SIGNAL(clicked()), SLOT(deleteException()));
363 hlayout->addWidget(mDeleteExceptionButton);
366 vlayout->addStretch();
368 mExcludeHolidays = new CheckBox(i18nc("@option:check", "Exclude holidays"), mExceptionGroup);
369 mExcludeHolidays->setReadOnly(mReadOnly);
370 mExcludeHolidays->setWhatsThis(i18nc("@info:whatsthis",
371 "<para>Do not trigger the alarm on holidays.</para>"
372 "<para>You can specify your holiday region in the Configuration dialog.</para>"));
373 connect(mExcludeHolidays, SIGNAL(toggled(bool)), SIGNAL(contentsChanged()));
374 vlayout->addWidget(mExcludeHolidays);
376 mWorkTimeOnly = new CheckBox(i18nc("@option:check", "Only during working time"), mExceptionGroup);
377 mWorkTimeOnly->setReadOnly(mReadOnly);
378 mWorkTimeOnly->setWhatsThis(i18nc("@info:whatsthis",
379 "<para>Only execute the alarm during working hours, on working days.</para>"
380 "<para>You can specify working days and hours in the Configuration dialog.</para>"));
381 connect(mWorkTimeOnly, SIGNAL(toggled(bool)), SIGNAL(contentsChanged()));
382 vlayout->addWidget(mWorkTimeOnly);
384 topLayout->addStretch();
385 mNoEmitTypeChanged = false;
388 /******************************************************************************
389 * Show or hide the exception controls.
391 void RecurrenceEdit::showMoreOptions(bool more)
393 if (more)
394 mExceptionGroup->show();
395 else
396 mExceptionGroup->hide();
397 updateGeometry();
400 /******************************************************************************
401 * Verify the consistency of the entered data.
402 * Reply = widget to receive focus on error, or 0 if no error.
404 QWidget* RecurrenceEdit::checkData(const KDateTime& startDateTime, QString& errorMessage) const
406 if (mAtLoginButton->isChecked())
407 return 0;
408 const_cast<RecurrenceEdit*>(this)->mCurrStartDateTime = startDateTime;
409 if (mEndDateButton->isChecked())
411 // N.B. End date/time takes the same time spec as start date/time
412 QWidget* errWidget = 0;
413 bool noTime = !mEndTimeEdit->isEnabled();
414 QDate endDate = mEndDateEdit->date();
415 if (endDate < startDateTime.date())
416 errWidget = mEndDateEdit;
417 else if (!noTime && QDateTime(endDate, mEndTimeEdit->time()) < startDateTime.dateTime())
418 errWidget = mEndTimeEdit;
419 if (errWidget)
421 errorMessage = noTime
422 ? i18nc("@info", "End date is earlier than start date")
423 : i18nc("@info", "End date/time is earlier than start date/time");
424 return errWidget;
427 if (!mRule)
428 return 0;
429 return mRule->validate(errorMessage);
432 /******************************************************************************
433 * Called when a recurrence period radio button is clicked.
435 void RecurrenceEdit::periodClicked(QAbstractButton* button)
437 RepeatType oldType = mRuleButtonType;
438 bool none = (button == mNoneButton);
439 bool atLogin = (button == mAtLoginButton);
440 bool subdaily = (button == mSubDailyButton);
441 if (none)
443 mRule = 0;
444 mRuleButtonType = NO_RECUR;
446 else if (atLogin)
448 mRule = 0;
449 mRuleButtonType = AT_LOGIN;
450 mEndDateButton->setChecked(true);
452 else if (subdaily)
454 mRule = mSubDailyRule;
455 mRuleButtonType = SUBDAILY;
457 else if (button == mDailyButton)
459 mRule = mDailyRule;
460 mRuleButtonType = DAILY;
461 mDailyShown = true;
463 else if (button == mWeeklyButton)
465 mRule = mWeeklyRule;
466 mRuleButtonType = WEEKLY;
467 mWeeklyShown = true;
469 else if (button == mMonthlyButton)
471 mRule = mMonthlyRule;
472 mRuleButtonType = MONTHLY;
473 mMonthlyShown = true;
475 else if (button == mYearlyButton)
477 mRule = mYearlyRule;
478 mRuleButtonType = ANNUAL;
479 mYearlyShown = true;
481 else
482 return;
484 if (mRuleButtonType != oldType)
486 mRuleStack->setCurrentWidget(mRule ? mRule : mNoRule);
487 if (oldType == NO_RECUR || none)
488 mRangeButtonBox->setEnabled(!none);
489 mExceptionGroup->setEnabled(!(none || atLogin));
490 mEndAnyTimeCheckBox->setEnabled(atLogin);
491 if (!none)
493 mNoEndDateButton->setEnabled(!atLogin);
494 mRepeatCountButton->setEnabled(!atLogin);
496 rangeTypeClicked();
497 mSubRepetition->setEnabled(!(none || atLogin));
498 if (!mNoEmitTypeChanged)
499 emit typeChanged(mRuleButtonType);
503 void RecurrenceEdit::slotAnyTimeToggled(bool on)
505 QAbstractButton* button = mRuleButtonGroup->checkedButton();
506 mEndTimeEdit->setEnabled((button == mAtLoginButton && !on)
507 || (button == mSubDailyButton && mEndDateButton->isChecked()));
510 /******************************************************************************
511 * Called when a recurrence range type radio button is clicked.
513 void RecurrenceEdit::rangeTypeClicked()
515 bool endDate = mEndDateButton->isChecked();
516 mEndDateEdit->setEnabled(endDate);
517 mEndTimeEdit->setEnabled(endDate
518 && ((mAtLoginButton->isChecked() && !mEndAnyTimeCheckBox->isChecked())
519 || mSubDailyButton->isChecked()));
520 bool repeatCount = mRepeatCountButton->isChecked();
521 mRepeatCountEntry->setEnabled(repeatCount);
522 mRepeatCountLabel->setEnabled(repeatCount);
525 void RecurrenceEdit::showEvent(QShowEvent*)
527 if (mRule)
528 mRule->setFrequencyFocus();
529 else
530 mRuleButtonGroup->checkedButton()->setFocus();
531 emit shown();
534 /******************************************************************************
535 * Return the sub-repetition interval and count within the recurrence, i.e. the
536 * number of repetitions after the main recurrence.
538 Repetition RecurrenceEdit::subRepetition() const
540 return (mRuleButtonType >= SUBDAILY) ? mSubRepetition->repetition() : Repetition();
543 /******************************************************************************
544 * Called when the Sub-Repetition button has been pressed to display the
545 * sub-repetition dialog.
546 * Alarm repetition has the following restrictions:
547 * 1) Not allowed for a repeat-at-login alarm
548 * 2) For a date-only alarm, the repeat interval must be a whole number of days.
549 * 3) The overall repeat duration must be less than the recurrence interval.
551 void RecurrenceEdit::setSubRepetition(int reminderMinutes, bool dateOnly)
553 int maxDuration;
554 switch (mRuleButtonType)
556 case RecurrenceEdit::NO_RECUR:
557 case RecurrenceEdit::AT_LOGIN: // alarm repeat not allowed
558 maxDuration = 0;
559 break;
560 default: // repeat duration must be less than recurrence interval
562 KAEvent event;
563 updateEvent(event, false);
564 maxDuration = event.longestRecurrenceInterval().asSeconds()/60 - reminderMinutes - 1;
565 break;
568 mSubRepetition->initialise(mSubRepetition->repetition(), dateOnly, maxDuration);
569 mSubRepetition->setEnabled(mRuleButtonType >= SUBDAILY && maxDuration);
572 /******************************************************************************
573 * Activate the sub-repetition dialog.
575 void RecurrenceEdit::activateSubRepetition()
577 mSubRepetition->activate();
580 /******************************************************************************
581 * Called when the value of the repeat count field changes, to reset the
582 * minimum value to 1 if the value was 0.
584 void RecurrenceEdit::repeatCountChanged(int value)
586 if (value > 0 && mRepeatCountEntry->minimum() == 0)
587 mRepeatCountEntry->setMinimum(1);
590 /******************************************************************************
591 * Add the date entered in the exception date edit control to the list of
592 * exception dates.
594 void RecurrenceEdit::addException()
596 if (!mExceptionDateEdit || !mExceptionDateEdit->isValid())
597 return;
598 QDate date = mExceptionDateEdit->date();
599 DateList::Iterator it;
600 int index = 0;
601 bool insert = true;
602 for (it = mExceptionDates.begin(); it != mExceptionDates.end(); ++index, ++it)
604 if (date <= *it)
606 insert = (date != *it);
607 break;
610 if (insert)
612 mExceptionDates.insert(it, date);
613 mExceptionDateList->insertItem(index, new QListWidgetItem(KGlobal::locale()->formatDate(date)));
614 emit contentsChanged();
616 mExceptionDateList->setCurrentItem(mExceptionDateList->item(index));
617 enableExceptionButtons();
620 /******************************************************************************
621 * Change the currently highlighted exception date to that entered in the
622 * exception date edit control.
624 void RecurrenceEdit::changeException()
626 if (!mExceptionDateEdit || !mExceptionDateEdit->isValid())
627 return;
628 QListWidgetItem* item = mExceptionDateList->currentItem();
629 if (item && mExceptionDateList->isItemSelected(item))
631 int index = mExceptionDateList->row(item);
632 QDate olddate = mExceptionDates[index];
633 QDate newdate = mExceptionDateEdit->date();
634 if (newdate != olddate)
636 mExceptionDates.removeAt(index);
637 mExceptionDateList->takeItem(index);
638 emit contentsChanged();
639 addException();
644 /******************************************************************************
645 * Delete the currently highlighted exception date.
647 void RecurrenceEdit::deleteException()
649 QListWidgetItem* item = mExceptionDateList->currentItem();
650 if (item && mExceptionDateList->isItemSelected(item))
652 int index = mExceptionDateList->row(item);
653 mExceptionDates.removeAt(index);
654 mExceptionDateList->takeItem(index);
655 emit contentsChanged();
656 enableExceptionButtons();
660 /******************************************************************************
661 * Enable/disable the exception group buttons according to whether any item is
662 * selected in the exceptions listbox.
664 void RecurrenceEdit::enableExceptionButtons()
666 QListWidgetItem* item = mExceptionDateList->currentItem();
667 bool enable = item;
668 if (mDeleteExceptionButton)
669 mDeleteExceptionButton->setEnabled(enable);
670 if (mChangeExceptionButton)
671 mChangeExceptionButton->setEnabled(enable);
673 // Prevent the exceptions list box receiving keyboard focus is it's empty
674 mExceptionDateList->setFocusPolicy(mExceptionDateList->count() ? Qt::WheelFocus : Qt::NoFocus);
677 /******************************************************************************
678 * Notify this instance of a change in the alarm start date.
680 void RecurrenceEdit::setStartDate(const QDate& start, const QDate& today)
682 if (!mReadOnly)
684 setRuleDefaults(start);
685 if (start < today)
687 mEndDateEdit->setMinDate(today);
688 if (mExceptionDateEdit)
689 mExceptionDateEdit->setMinDate(today);
691 else
693 const QString startString = i18nc("@info Date cannot be earlier than start date", "start date");
694 mEndDateEdit->setMinDate(start, startString);
695 if (mExceptionDateEdit)
696 mExceptionDateEdit->setMinDate(start, startString);
701 /******************************************************************************
702 * Specify the default recurrence end date.
704 void RecurrenceEdit::setDefaultEndDate(const QDate& end)
706 if (!mEndDateButton->isChecked())
707 mEndDateEdit->setDate(end);
710 void RecurrenceEdit::setEndDateTime(const KDateTime& end)
712 KDateTime edt = end.toTimeSpec(mCurrStartDateTime.timeSpec());
713 mEndDateEdit->setDate(edt.date());
714 mEndTimeEdit->setValue(edt.time());
715 mEndTimeEdit->setEnabled(!end.isDateOnly());
716 mEndAnyTimeCheckBox->setChecked(end.isDateOnly());
719 KDateTime RecurrenceEdit::endDateTime() const
721 if (mRuleButtonGroup->checkedButton() == mAtLoginButton && mEndAnyTimeCheckBox->isChecked())
722 return KDateTime(mEndDateEdit->date(), mCurrStartDateTime.timeSpec());
723 return KDateTime(mEndDateEdit->date(), mEndTimeEdit->time(), mCurrStartDateTime.timeSpec());
726 /******************************************************************************
727 * Set all controls to their default values.
729 void RecurrenceEdit::setDefaults(const KDateTime& from)
731 mCurrStartDateTime = from;
732 QDate fromDate = from.date();
733 mNoEndDateButton->setChecked(true);
735 mSubDailyRule->setFrequency(1);
736 mDailyRule->setFrequency(1);
737 mWeeklyRule->setFrequency(1);
738 mMonthlyRule->setFrequency(1);
739 mYearlyRule->setFrequency(1);
741 setRuleDefaults(fromDate);
742 mMonthlyRule->setType(MonthYearRule::DATE); // date in month
743 mYearlyRule->setType(MonthYearRule::DATE); // date in year
745 mEndDateEdit->setDate(fromDate);
747 mNoEmitTypeChanged = true;
748 RadioButton* button;
749 switch (Preferences::defaultRecurPeriod())
751 case AT_LOGIN: button = mAtLoginButton; break;
752 case ANNUAL: button = mYearlyButton; break;
753 case MONTHLY: button = mMonthlyButton; break;
754 case WEEKLY: button = mWeeklyButton; break;
755 case DAILY: button = mDailyButton; break;
756 case SUBDAILY: button = mSubDailyButton; break;
757 case NO_RECUR:
758 default: button = mNoneButton; break;
760 button->setChecked(true);
761 mNoEmitTypeChanged = false;
762 rangeTypeClicked();
763 enableExceptionButtons();
765 saveState();
768 /******************************************************************************
769 * Set the controls for weekly, monthly and yearly rules which have not so far
770 * been shown, to their default values, depending on the recurrence start date.
772 void RecurrenceEdit::setRuleDefaults(const QDate& fromDate)
774 int day = fromDate.day();
775 int dayOfWeek = fromDate.dayOfWeek();
776 int month = fromDate.month();
777 if (!mDailyShown)
778 mDailyRule->setDays(true);
779 if (!mWeeklyShown)
780 mWeeklyRule->setDay(dayOfWeek);
781 if (!mMonthlyShown)
782 mMonthlyRule->setDefaultValues(day, dayOfWeek);
783 if (!mYearlyShown)
784 mYearlyRule->setDefaultValues(day, dayOfWeek, month);
787 /******************************************************************************
788 * Initialise the recurrence to select repeat-at-login.
789 * This function and set() are mutually exclusive: call one or the other, not both.
791 void RecurrenceEdit::setRepeatAtLogin()
793 mAtLoginButton->setChecked(true);
794 mEndDateButton->setChecked(true);
797 /******************************************************************************
798 * Set the state of all controls to reflect the data in the specified event.
800 void RecurrenceEdit::set(const KAEvent& event)
802 setDefaults(event.mainDateTime().kDateTime());
803 if (event.repeatAtLogin())
805 mAtLoginButton->setChecked(true);
806 mEndDateButton->setChecked(true);
807 return;
809 mNoneButton->setChecked(true);
810 KARecurrence* recurrence = event.recurrence();
811 if (!recurrence)
812 return;
813 KARecurrence::Type rtype = recurrence->type();
814 switch (rtype)
816 case KARecurrence::MINUTELY:
817 mSubDailyButton->setChecked(true);
818 break;
820 case KARecurrence::DAILY:
822 mDailyButton->setChecked(true);
823 QBitArray rDays = recurrence->days();
824 bool set = false;
825 for (int i = 0; i < 7 && !set; ++i)
826 set = rDays.testBit(i);
827 if (set)
828 mDailyRule->setDays(rDays);
829 else
830 mDailyRule->setDays(true);
831 break;
833 case KARecurrence::WEEKLY:
835 mWeeklyButton->setChecked(true);
836 QBitArray rDays = recurrence->days();
837 mWeeklyRule->setDays(rDays);
838 break;
840 case KARecurrence::MONTHLY_POS: // on nth (Tuesday) of the month
842 QList<RecurrenceRule::WDayPos> posns = recurrence->monthPositions();
843 int i = posns.first().pos();
844 if (!i)
846 // It's every (Tuesday) of the month. Convert to a weekly recurrence
847 // (but ignoring any non-every xxxDay positions).
848 mWeeklyButton->setChecked(true);
849 mWeeklyRule->setFrequency(recurrence->frequency());
850 QBitArray rDays(7);
851 for (int i = 0, end = posns.count(); i < end; ++i)
853 if (!posns[i].pos())
854 rDays.setBit(posns[i].day() - 1, 1);
856 mWeeklyRule->setDays(rDays);
857 break;
859 mMonthlyButton->setChecked(true);
860 mMonthlyRule->setPosition(i, posns.first().day());
861 break;
863 case KARecurrence::MONTHLY_DAY: // on nth day of the month
865 mMonthlyButton->setChecked(true);
866 QList<int> rmd = recurrence->monthDays();
867 int day = (rmd.isEmpty()) ? event.mainDate().day() : rmd.first();
868 mMonthlyRule->setDate(day);
869 break;
871 case KARecurrence::ANNUAL_DATE: // on the nth day of (months...) in the year
872 case KARecurrence::ANNUAL_POS: // on the nth (Tuesday) of (months...) in the year
874 if (rtype == KARecurrence::ANNUAL_DATE)
876 mYearlyButton->setChecked(true);
877 const QList<int> rmd = recurrence->monthDays();
878 int day = (rmd.isEmpty()) ? event.mainDate().day() : rmd.first();
879 mYearlyRule->setDate(day);
880 mYearlyRule->setFeb29Type(recurrence->feb29Type());
882 else if (rtype == KARecurrence::ANNUAL_POS)
884 mYearlyButton->setChecked(true);
885 QList<RecurrenceRule::WDayPos> posns = recurrence->yearPositions();
886 mYearlyRule->setPosition(posns.first().pos(), posns.first().day());
888 mYearlyRule->setMonths(recurrence->yearMonths());
889 break;
891 default:
892 return;
895 mRule->setFrequency(recurrence->frequency());
897 // Get range information
898 KDateTime endtime = mCurrStartDateTime;
899 int duration = recurrence->duration();
900 if (duration == -1)
901 mNoEndDateButton->setChecked(true);
902 else if (duration)
904 mRepeatCountButton->setChecked(true);
905 mRepeatCountEntry->setValue(duration);
907 else
909 mEndDateButton->setChecked(true);
910 endtime = recurrence->endDateTime();
911 mEndTimeEdit->setValue(endtime.time());
913 mEndDateEdit->setDate(endtime.date());
915 // Get exception information
916 mExceptionDates = event.recurrence()->exDates();
917 qSort(mExceptionDates);
918 mExceptionDateList->clear();
919 for (int i = 0, iend = mExceptionDates.count(); i < iend; ++i)
920 new QListWidgetItem(KGlobal::locale()->formatDate(mExceptionDates[i]), mExceptionDateList);
921 enableExceptionButtons();
922 mExcludeHolidays->setChecked(event.holidaysExcluded());
923 mWorkTimeOnly->setChecked(event.workTimeOnly());
925 // Get repetition within recurrence
926 mSubRepetition->set(event.repetition());
928 rangeTypeClicked();
930 saveState();
933 /******************************************************************************
934 * Update the specified KAEvent with the entered recurrence data.
935 * If 'adjustStart' is true, the start date/time will be adjusted if necessary
936 * to be the first date/time which recurs on or after the original start.
938 void RecurrenceEdit::updateEvent(KAEvent& event, bool adjustStart)
940 // Get end date and repeat count, common to all types of recurring events
941 QDate endDate;
942 QTime endTime;
943 int repeatCount;
944 if (mNoEndDateButton->isChecked())
945 repeatCount = -1;
946 else if (mRepeatCountButton->isChecked())
947 repeatCount = mRepeatCountEntry->value();
948 else
950 repeatCount = 0;
951 endDate = mEndDateEdit->date();
952 endTime = mEndTimeEdit->time();
955 // Set up the recurrence according to the type selected
956 event.startChanges();
957 QAbstractButton* button = mRuleButtonGroup->checkedButton();
958 event.setRepeatAtLogin(button == mAtLoginButton);
959 int frequency = mRule ? mRule->frequency() : 0;
960 if (button == mSubDailyButton)
962 KDateTime endDateTime(endDate, endTime, mCurrStartDateTime.timeSpec());
963 event.setRecurMinutely(frequency, repeatCount, endDateTime);
965 else if (button == mDailyButton)
967 event.setRecurDaily(frequency, mDailyRule->days(), repeatCount, endDate);
969 else if (button == mWeeklyButton)
971 event.setRecurWeekly(frequency, mWeeklyRule->days(), repeatCount, endDate);
973 else if (button == mMonthlyButton)
975 if (mMonthlyRule->type() == MonthlyRule::POS)
977 // It's by position
978 KAEvent::MonthPos pos;
979 pos.days.fill(false);
980 pos.days.setBit(mMonthlyRule->dayOfWeek() - 1);
981 pos.weeknum = mMonthlyRule->week();
982 QList<KAEvent::MonthPos> poses;
983 poses.append(pos);
984 event.setRecurMonthlyByPos(frequency, poses, repeatCount, endDate);
986 else
988 // It's by day
989 int daynum = mMonthlyRule->date();
990 QList<int> daynums;
991 daynums.append(daynum);
992 event.setRecurMonthlyByDate(frequency, daynums, repeatCount, endDate);
995 else if (button == mYearlyButton)
997 QList<int> months = mYearlyRule->months();
998 if (mYearlyRule->type() == YearlyRule::POS)
1000 // It's by position
1001 KAEvent::MonthPos pos;
1002 pos.days.fill(false);
1003 pos.days.setBit(mYearlyRule->dayOfWeek() - 1);
1004 pos.weeknum = mYearlyRule->week();
1005 QList<KAEvent::MonthPos> poses;
1006 poses.append(pos);
1007 event.setRecurAnnualByPos(frequency, poses, months, repeatCount, endDate);
1009 else
1011 // It's by date in month
1012 event.setRecurAnnualByDate(frequency, months, mYearlyRule->date(),
1013 mYearlyRule->feb29Type(), repeatCount, endDate);
1016 else
1018 event.setNoRecur();
1019 event.endChanges();
1020 return;
1022 if (!event.recurs())
1024 event.endChanges();
1025 return; // an error occurred setting up the recurrence
1027 if (adjustStart)
1028 event.setFirstRecurrence();
1030 // Set up repetition within the recurrence
1031 // N.B. This requires the main recurrence to be set up first.
1032 event.setRepetition((mRuleButtonType < SUBDAILY) ? Repetition() : mSubRepetition->repetition());
1034 // Set up exceptions
1035 event.recurrence()->setExDates(mExceptionDates);
1036 event.setWorkTimeOnly(mWorkTimeOnly->isChecked());
1037 event.setExcludeHolidays(mExcludeHolidays->isChecked());
1039 event.setUpdated();
1040 event.endChanges();
1043 /******************************************************************************
1044 * Save the state of all controls.
1046 void RecurrenceEdit::saveState()
1048 mSavedRuleButton = mRuleButtonGroup->checkedButton();
1049 if (mRule)
1050 mRule->saveState();
1051 mSavedRangeButton = mRangeButtonGroup->checkedButton();
1052 if (mSavedRangeButton == mRepeatCountButton)
1053 mSavedRecurCount = mRepeatCountEntry->value();
1054 else if (mSavedRangeButton == mEndDateButton)
1056 mSavedEndDateTime = KDateTime(QDateTime(mEndDateEdit->date(), mEndTimeEdit->time()), mCurrStartDateTime.timeSpec());
1057 mSavedEndDateTime.setDateOnly(mEndAnyTimeCheckBox->isChecked());
1059 mSavedExceptionDates = mExceptionDates;
1060 mSavedWorkTimeOnly = mWorkTimeOnly->isChecked();
1061 mSavedExclHolidays = mExcludeHolidays->isChecked();
1062 mSavedRepetition = mSubRepetition->repetition();
1065 /******************************************************************************
1066 * Check whether any of the controls have changed state since initialisation.
1068 bool RecurrenceEdit::stateChanged() const
1070 if (mSavedRuleButton != mRuleButtonGroup->checkedButton()
1071 || mSavedRangeButton != mRangeButtonGroup->checkedButton()
1072 || (mRule && mRule->stateChanged()))
1073 return true;
1074 if (mSavedRangeButton == mRepeatCountButton
1075 && mSavedRecurCount != mRepeatCountEntry->value())
1076 return true;
1077 if (mSavedRangeButton == mEndDateButton)
1079 KDateTime edt(QDateTime(mEndDateEdit->date(), mEndTimeEdit->time()), mCurrStartDateTime.timeSpec());
1080 edt.setDateOnly(mEndAnyTimeCheckBox->isChecked());
1081 if (mSavedEndDateTime != edt)
1082 return true;
1084 if (mSavedExceptionDates != mExceptionDates
1085 || mSavedWorkTimeOnly != mWorkTimeOnly->isChecked()
1086 || mSavedExclHolidays != mExcludeHolidays->isChecked()
1087 || mSavedRepetition != mSubRepetition->repetition())
1088 return true;
1089 return false;
1094 /*=============================================================================
1095 = Class Rule
1096 = Base class for rule widgets, including recurrence frequency.
1097 =============================================================================*/
1099 Rule::Rule(const QString& freqText, const QString& freqWhatsThis, bool time, bool readOnly, QWidget* parent)
1100 : NoRule(parent)
1102 mLayout = new QVBoxLayout(this);
1103 mLayout->setMargin(0);
1104 mLayout->setSpacing(KDialog::spacingHint());
1105 KHBox* freqBox = new KHBox(this);
1106 freqBox->setMargin(0);
1107 mLayout->addWidget(freqBox, 0, Qt::AlignLeft);
1108 KHBox* box = new KHBox(freqBox); // this is to control the QWhatsThis text display area
1109 box->setMargin(0);
1110 box->setSpacing(KDialog::spacingHint());
1112 QLabel* label = new QLabel(i18nc("@label:spinbox", "Recur e&very"), box);
1113 label->setFixedSize(label->sizeHint());
1114 if (time)
1116 mIntSpinBox = 0;
1117 mSpinBox = mTimeSpinBox = new TimeSpinBox(1, 5999, box);
1118 mTimeSpinBox->setFixedSize(mTimeSpinBox->sizeHint());
1119 mTimeSpinBox->setReadOnly(readOnly);
1121 else
1123 mTimeSpinBox = 0;
1124 mSpinBox = mIntSpinBox = new SpinBox(1, 999, box);
1125 mIntSpinBox->setFixedSize(mIntSpinBox->sizeHint());
1126 mIntSpinBox->setReadOnly(readOnly);
1128 connect(mSpinBox, SIGNAL(valueChanged(int)), SIGNAL(frequencyChanged()));
1129 connect(mSpinBox, SIGNAL(valueChanged(int)), SIGNAL(changed()));
1130 label->setBuddy(mSpinBox);
1131 label = new QLabel(freqText, box);
1132 label->setFixedSize(label->sizeHint());
1133 box->setFixedSize(sizeHint());
1134 box->setWhatsThis(freqWhatsThis);
1136 new QWidget(freqBox); // left adjust the visible widgets
1137 freqBox->setFixedHeight(freqBox->sizeHint().height());
1138 freqBox->setFocusProxy(mSpinBox);
1141 int Rule::frequency() const
1143 if (mIntSpinBox)
1144 return mIntSpinBox->value();
1145 if (mTimeSpinBox)
1146 return mTimeSpinBox->value();
1147 return 0;
1150 void Rule::setFrequency(int n)
1152 if (mIntSpinBox)
1153 mIntSpinBox->setValue(n);
1154 if (mTimeSpinBox)
1155 mTimeSpinBox->setValue(n);
1158 /******************************************************************************
1159 * Save the state of all controls.
1161 void Rule::saveState()
1163 mSavedFrequency = frequency();
1166 /******************************************************************************
1167 * Check whether any of the controls have changed state since initialisation.
1169 bool Rule::stateChanged() const
1171 return (mSavedFrequency != frequency());
1175 /*=============================================================================
1176 = Class SubDailyRule
1177 = Sub-daily rule widget.
1178 =============================================================================*/
1180 SubDailyRule::SubDailyRule(bool readOnly, QWidget* parent)
1181 : Rule(i18nc("@label Time units for user-entered numbers", "hours:minutes"),
1182 i18nc("@info:whatsthis", "Enter the number of hours and minutes between repetitions of the alarm"),
1183 true, readOnly, parent)
1187 /*=============================================================================
1188 = Class DayWeekRule
1189 = Daily/weekly rule widget base class.
1190 =============================================================================*/
1192 DayWeekRule::DayWeekRule(const QString& freqText, const QString& freqWhatsThis, const QString& daysWhatsThis,
1193 bool readOnly, QWidget* parent)
1194 : Rule(freqText, freqWhatsThis, false, readOnly, parent),
1195 mSavedDays(7)
1197 QGridLayout* grid = new QGridLayout();
1198 grid->setMargin(0);
1199 grid->setRowStretch(0, 1);
1200 layout()->addLayout(grid);
1202 QLabel* label = new QLabel(i18nc("@label On: Tuesday", "O&n:"), this);
1203 label->setFixedSize(label->sizeHint());
1204 grid->addWidget(label, 0, 0, Qt::AlignRight | Qt::AlignTop);
1205 grid->setColumnMinimumWidth(1, KDialog::spacingHint());
1207 // List the days of the week starting at the user's start day of the week.
1208 // Save the first day of the week, just in case it changes while the dialog is open.
1209 QWidget* box = new QWidget(this); // this is to control the QWhatsThis text display area
1210 QGridLayout* dgrid = new QGridLayout(box);
1211 dgrid->setMargin(0);
1212 dgrid->setSpacing(KDialog::spacingHint());
1213 const KCalendarSystem* calendar = KGlobal::locale()->calendar();
1214 for (int i = 0; i < 7; ++i)
1216 int day = KAlarm::localeDayInWeek_to_weekDay(i);
1217 mDayBox[i] = new CheckBox(calendar->weekDayName(day), box);
1218 mDayBox[i]->setFixedSize(mDayBox[i]->sizeHint());
1219 mDayBox[i]->setReadOnly(readOnly);
1220 connect(mDayBox[i], SIGNAL(toggled(bool)), SIGNAL(changed()));
1221 dgrid->addWidget(mDayBox[i], i%4, i/4, Qt::AlignLeft);
1223 box->setFixedSize(box->sizeHint());
1224 box->setWhatsThis(daysWhatsThis);
1225 grid->addWidget(box, 0, 2, Qt::AlignLeft);
1226 label->setBuddy(mDayBox[0]);
1227 grid->setColumnStretch(3, 1);
1230 /******************************************************************************
1231 * Fetch which days of the week have been ticked.
1233 QBitArray DayWeekRule::days() const
1235 QBitArray ds(7);
1236 ds.fill(false);
1237 for (int i = 0; i < 7; ++i)
1238 if (mDayBox[i]->isChecked())
1239 ds.setBit(KAlarm::localeDayInWeek_to_weekDay(i) - 1, 1);
1240 return ds;
1243 /******************************************************************************
1244 * Tick/untick every day of the week.
1246 void DayWeekRule::setDays(bool tick)
1248 for (int i = 0; i < 7; ++i)
1249 mDayBox[i]->setChecked(tick);
1252 /******************************************************************************
1253 * Tick/untick each day of the week according to the specified bits.
1255 void DayWeekRule::setDays(const QBitArray& days)
1257 for (int i = 0; i < 7; ++i)
1259 bool x = days.testBit(KAlarm::localeDayInWeek_to_weekDay(i) - 1);
1260 mDayBox[i]->setChecked(x);
1264 /******************************************************************************
1265 * Tick the specified day of the week, and untick all other days.
1267 void DayWeekRule::setDay(int dayOfWeek)
1269 for (int i = 0; i < 7; ++i)
1270 mDayBox[i]->setChecked(false);
1271 if (dayOfWeek > 0 && dayOfWeek <= 7)
1272 mDayBox[KAlarm::weekDay_to_localeDayInWeek(dayOfWeek)]->setChecked(true);
1275 /******************************************************************************
1276 * Validate: check that at least one day is selected.
1278 QWidget* DayWeekRule::validate(QString& errorMessage)
1280 for (int i = 0; i < 7; ++i)
1281 if (mDayBox[i]->isChecked())
1282 return 0;
1283 errorMessage = i18nc("@info", "No day selected");
1284 return mDayBox[0];
1287 /******************************************************************************
1288 * Save the state of all controls.
1290 void DayWeekRule::saveState()
1292 Rule::saveState();
1293 mSavedDays = days();
1296 /******************************************************************************
1297 * Check whether any of the controls have changed state since initialisation.
1299 bool DayWeekRule::stateChanged() const
1301 return (Rule::stateChanged()
1302 || mSavedDays != days());
1306 /*=============================================================================
1307 = Class DailyRule
1308 = Daily rule widget.
1309 =============================================================================*/
1311 DailyRule::DailyRule(bool readOnly, QWidget* parent)
1312 : DayWeekRule(i18nc("@label Time unit for user-entered number", "day(s)"),
1313 i18nc("@info:whatsthis", "Enter the number of days between repetitions of the alarm"),
1314 i18nc("@info:whatsthis", "Select the days of the week on which the alarm is allowed to occur"),
1315 readOnly, parent)
1319 /*=============================================================================
1320 = Class WeeklyRule
1321 = Weekly rule widget.
1322 =============================================================================*/
1324 WeeklyRule::WeeklyRule(bool readOnly, QWidget* parent)
1325 : DayWeekRule(i18nc("@label Time unit for user-entered number", "week(s)"),
1326 i18nc("@info:whatsthis", "Enter the number of weeks between repetitions of the alarm"),
1327 i18nc("@info:whatsthis", "Select the days of the week on which to repeat the alarm"),
1328 readOnly, parent)
1332 /*=============================================================================
1333 = Class MonthYearRule
1334 = Monthly/yearly rule widget base class.
1335 =============================================================================*/
1337 MonthYearRule::MonthYearRule(const QString& freqText, const QString& freqWhatsThis, bool allowEveryWeek,
1338 bool readOnly, QWidget* parent)
1339 : Rule(freqText, freqWhatsThis, false, readOnly, parent),
1340 mEveryWeek(allowEveryWeek)
1342 mButtonGroup = new ButtonGroup(this);
1344 // Month day selector
1345 KHBox* box = new KHBox(this);
1346 box->setMargin(0);
1347 box->setSpacing(KDialog::spacingHint());
1348 layout()->addWidget(box);
1350 mDayButton = new RadioButton(i18nc("@option:radio On day number in the month", "O&n day"), box);
1351 mDayButton->setFixedSize(mDayButton->sizeHint());
1352 mDayButton->setReadOnly(readOnly);
1353 mButtonGroup->addButton(mDayButton);
1354 mDayButton->setWhatsThis(i18nc("@info:whatsthis", "Repeat the alarm on the selected day of the month"));
1356 mDayCombo = new ComboBox(box);
1357 mDayCombo->setEditable(false);
1358 mDayCombo->setMaxVisibleItems(11);
1359 for (int i = 0; i < 31; ++i)
1360 mDayCombo->addItem(QString::number(i + 1));
1361 mDayCombo->addItem(i18nc("@item:inlistbox Last day of month", "Last"));
1362 mDayCombo->setFixedSize(mDayCombo->sizeHint());
1363 mDayCombo->setReadOnly(readOnly);
1364 mDayCombo->setWhatsThis(i18nc("@info:whatsthis", "Select the day of the month on which to repeat the alarm"));
1365 mDayButton->setFocusWidget(mDayCombo);
1366 connect(mDayCombo, SIGNAL(activated(int)), SLOT(slotDaySelected(int)));
1367 connect(mDayCombo, SIGNAL(currentIndexChanged(int)), SIGNAL(changed()));
1369 box->setStretchFactor(new QWidget(box), 1); // left adjust the controls
1370 box->setFixedHeight(box->sizeHint().height());
1372 // Month position selector
1373 box = new KHBox(this);
1374 box->setMargin(0);
1375 box->setSpacing(KDialog::spacingHint());
1376 layout()->addWidget(box);
1378 mPosButton = new RadioButton(i18nc("@option:radio On the 1st Tuesday", "On t&he"), box);
1379 mPosButton->setFixedSize(mPosButton->sizeHint());
1380 mPosButton->setReadOnly(readOnly);
1381 mButtonGroup->addButton(mPosButton);
1382 mPosButton->setWhatsThis(i18nc("@info:whatsthis", "Repeat the alarm on one day of the week, in the selected week of the month"));
1384 mWeekCombo = new ComboBox(box);
1385 mWeekCombo->setEditable(false);
1386 mWeekCombo->addItem(i18nc("@item:inlistbox", "1st"));
1387 mWeekCombo->addItem(i18nc("@item:inlistbox", "2nd"));
1388 mWeekCombo->addItem(i18nc("@item:inlistbox", "3rd"));
1389 mWeekCombo->addItem(i18nc("@item:inlistbox", "4th"));
1390 mWeekCombo->addItem(i18nc("@item:inlistbox", "5th"));
1391 mWeekCombo->addItem(i18nc("@item:inlistbox Last Monday in March", "Last"));
1392 mWeekCombo->addItem(i18nc("@item:inlistbox", "2nd Last"));
1393 mWeekCombo->addItem(i18nc("@item:inlistbox", "3rd Last"));
1394 mWeekCombo->addItem(i18nc("@item:inlistbox", "4th Last"));
1395 mWeekCombo->addItem(i18nc("@item:inlistbox", "5th Last"));
1396 if (mEveryWeek)
1398 mWeekCombo->addItem(i18nc("@item:inlistbox Every (Monday...) in month", "Every"));
1399 mWeekCombo->setMaxVisibleItems(11);
1401 mWeekCombo->setWhatsThis(i18nc("@info:whatsthis", "Select the week of the month in which to repeat the alarm"));
1402 mWeekCombo->setFixedSize(mWeekCombo->sizeHint());
1403 mWeekCombo->setReadOnly(readOnly);
1404 mPosButton->setFocusWidget(mWeekCombo);
1405 connect(mWeekCombo, SIGNAL(currentIndexChanged(int)), SIGNAL(changed()));
1407 mDayOfWeekCombo = new ComboBox(box);
1408 mDayOfWeekCombo->setEditable(false);
1409 const KCalendarSystem* calendar = KGlobal::locale()->calendar();
1410 for (int i = 0; i < 7; ++i)
1412 int day = KAlarm::localeDayInWeek_to_weekDay(i);
1413 mDayOfWeekCombo->addItem(calendar->weekDayName(day));
1415 mDayOfWeekCombo->setReadOnly(readOnly);
1416 mDayOfWeekCombo->setWhatsThis(i18nc("@info:whatsthis", "Select the day of the week on which to repeat the alarm"));
1417 connect(mDayOfWeekCombo, SIGNAL(currentIndexChanged(int)), SIGNAL(changed()));
1419 box->setStretchFactor(new QWidget(box), 1); // left adjust the controls
1420 box->setFixedHeight(box->sizeHint().height());
1421 connect(mButtonGroup, SIGNAL(buttonSet(QAbstractButton*)), SLOT(clicked(QAbstractButton*)));
1422 connect(mButtonGroup, SIGNAL(buttonSet(QAbstractButton*)), SIGNAL(changed()));
1425 MonthYearRule::DayPosType MonthYearRule::type() const
1427 return (mButtonGroup->checkedButton() == mDayButton) ? DATE : POS;
1430 void MonthYearRule::setType(MonthYearRule::DayPosType type)
1432 if (type == DATE)
1433 mDayButton->setChecked(true);
1434 else
1435 mPosButton->setChecked(true);
1438 void MonthYearRule::setDefaultValues(int dayOfMonth, int dayOfWeek)
1440 --dayOfMonth;
1441 mDayCombo->setCurrentIndex(dayOfMonth);
1442 mWeekCombo->setCurrentIndex(dayOfMonth / 7);
1443 mDayOfWeekCombo->setCurrentIndex(KAlarm::weekDay_to_localeDayInWeek(dayOfWeek));
1446 int MonthYearRule::date() const
1448 int daynum = mDayCombo->currentIndex() + 1;
1449 return (daynum <= 31) ? daynum : 31 - daynum;
1452 int MonthYearRule::week() const
1454 int weeknum = mWeekCombo->currentIndex() + 1;
1455 return (weeknum <= 5) ? weeknum : (weeknum == 11) ? 0 : 5 - weeknum;
1458 int MonthYearRule::dayOfWeek() const
1460 return KAlarm::localeDayInWeek_to_weekDay(mDayOfWeekCombo->currentIndex());
1463 void MonthYearRule::setDate(int dayOfMonth)
1465 mDayButton->setChecked(true);;
1466 mDayCombo->setCurrentIndex(dayOfMonth > 0 ? dayOfMonth - 1 : dayOfMonth < 0 ? 30 - dayOfMonth : 0); // day 0 shouldn't ever occur
1469 void MonthYearRule::setPosition(int week, int dayOfWeek)
1471 mPosButton->setChecked(true);
1472 mWeekCombo->setCurrentIndex((week > 0) ? week - 1 : (week < 0) ? 4 - week : mEveryWeek ? 10 : 0);
1473 mDayOfWeekCombo->setCurrentIndex(KAlarm::weekDay_to_localeDayInWeek(dayOfWeek));
1476 void MonthYearRule::enableSelection(DayPosType type)
1478 bool date = (type == DATE);
1479 mDayCombo->setEnabled(date);
1480 mWeekCombo->setEnabled(!date);
1481 mDayOfWeekCombo->setEnabled(!date);
1484 void MonthYearRule::clicked(QAbstractButton* button)
1486 enableSelection(button == mDayButton ? DATE : POS);
1489 void MonthYearRule::slotDaySelected(int index)
1491 daySelected(index <= 30 ? index + 1 : 30 - index);
1494 /******************************************************************************
1495 * Save the state of all controls.
1497 void MonthYearRule::saveState()
1499 Rule::saveState();
1500 mSavedType = type();
1501 if (mSavedType == DATE)
1502 mSavedDay = date();
1503 else
1505 mSavedWeek = week();
1506 mSavedWeekDay = dayOfWeek();
1510 /******************************************************************************
1511 * Check whether any of the controls have changed state since initialisation.
1513 bool MonthYearRule::stateChanged() const
1515 if (Rule::stateChanged()
1516 || mSavedType != type())
1517 return true;
1518 if (mSavedType == DATE)
1520 if (mSavedDay != date())
1521 return true;
1523 else
1525 if (mSavedWeek != week()
1526 || mSavedWeekDay != dayOfWeek())
1527 return true;
1529 return false;
1533 /*=============================================================================
1534 = Class MonthlyRule
1535 = Monthly rule widget.
1536 =============================================================================*/
1538 MonthlyRule::MonthlyRule(bool readOnly, QWidget* parent)
1539 : MonthYearRule(i18nc("@label Time unit for user-entered number", "month(s)"),
1540 i18nc("@info:whatsthis", "Enter the number of months between repetitions of the alarm"),
1541 false, readOnly, parent)
1545 /*=============================================================================
1546 = Class YearlyRule
1547 = Yearly rule widget.
1548 =============================================================================*/
1550 YearlyRule::YearlyRule(bool readOnly, QWidget* parent)
1551 : MonthYearRule(i18nc("@label Time unit for user-entered number", "year(s)"),
1552 i18nc("@info:whatsthis", "Enter the number of years between repetitions of the alarm"),
1553 true, readOnly, parent)
1555 // Set up the month selection widgets
1556 QHBoxLayout* hlayout = new QHBoxLayout();
1557 hlayout->setMargin(0);
1558 layout()->addLayout(hlayout);
1559 QLabel* label = new QLabel(i18nc("@label List of months to select", "Months:"), this);
1560 label->setFixedSize(label->sizeHint());
1561 hlayout->addWidget(label, 0, Qt::AlignLeft | Qt::AlignTop);
1563 // List the months of the year.
1564 QWidget* w = new QWidget(this); // this is to control the QWhatsThis text display area
1565 hlayout->addWidget(w, 1, Qt::AlignLeft);
1566 QGridLayout* grid = new QGridLayout(w);
1567 grid->setMargin(0);
1568 grid->setSpacing(KDialog::spacingHint());
1569 const KCalendarSystem* calendar = KGlobal::locale()->calendar();
1570 int year = KDateTime::currentLocalDate().year();
1571 for (int i = 0; i < 12; ++i)
1573 mMonthBox[i] = new CheckBox(calendar->monthName(i + 1, year, KCalendarSystem::ShortName), w);
1574 mMonthBox[i]->setFixedSize(mMonthBox[i]->sizeHint());
1575 mMonthBox[i]->setReadOnly(readOnly);
1576 connect(mMonthBox[i], SIGNAL(toggled(bool)), SIGNAL(changed()));
1577 grid->addWidget(mMonthBox[i], i%3, i/3, Qt::AlignLeft);
1579 connect(mMonthBox[1], SIGNAL(toggled(bool)), SLOT(enableFeb29()));
1580 w->setFixedHeight(w->sizeHint().height());
1581 w->setWhatsThis(i18nc("@info:whatsthis", "Select the months of the year in which to repeat the alarm"));
1583 // February 29th handling option
1584 KHBox* f29box = new KHBox(this);
1585 f29box->setMargin(0);
1586 layout()->addWidget(f29box);
1587 KHBox* box = new KHBox(f29box); // this is to control the QWhatsThis text display area
1588 box->setMargin(0);
1589 box->setSpacing(KDialog::spacingHint());
1590 mFeb29Label = new QLabel(i18nc("@label:listbox", "February 2&9th alarm in non-leap years:"), box);
1591 mFeb29Label->setFixedSize(mFeb29Label->sizeHint());
1592 mFeb29Combo = new ComboBox(box);
1593 mFeb29Combo->setEditable(false);
1594 mFeb29Combo->addItem(i18nc("@item:inlistbox No date", "None"));
1595 mFeb29Combo->addItem(i18nc("@item:inlistbox 1st March (short form)", "1 Mar"));
1596 mFeb29Combo->addItem(i18nc("@item:inlistbox 28th February (short form)", "28 Feb"));
1597 mFeb29Combo->setFixedSize(mFeb29Combo->sizeHint());
1598 mFeb29Combo->setReadOnly(readOnly);
1599 connect(mFeb29Combo, SIGNAL(currentIndexChanged(int)), SIGNAL(changed()));
1600 mFeb29Label->setBuddy(mFeb29Combo);
1601 box->setFixedSize(box->sizeHint());
1602 box->setWhatsThis(i18nc("@info:whatsthis", "Select which date, if any, the February 29th alarm should trigger in non-leap years"));
1603 new QWidget(f29box); // left adjust the visible widgets
1604 f29box->setFixedHeight(f29box->sizeHint().height());
1607 void YearlyRule::setDefaultValues(int dayOfMonth, int dayOfWeek, int month)
1609 MonthYearRule::setDefaultValues(dayOfMonth, dayOfWeek);
1610 --month;
1611 for (int i = 0; i < 12; ++i)
1612 mMonthBox[i]->setChecked(i == month);
1613 setFeb29Type(KARecurrence::defaultFeb29Type());
1614 daySelected(dayOfMonth); // enable/disable month checkboxes as appropriate
1617 /******************************************************************************
1618 * Fetch which months have been checked (1 - 12).
1619 * Reply = true if February has been checked.
1621 QList<int> YearlyRule::months() const
1623 QList<int> mnths;
1624 for (int i = 0; i < 12; ++i)
1625 if (mMonthBox[i]->isChecked() && mMonthBox[i]->isEnabled())
1626 mnths.append(i + 1);
1627 return mnths;
1630 /******************************************************************************
1631 * Check/uncheck each month of the year according to the specified list.
1633 void YearlyRule::setMonths(const QList<int>& mnths)
1635 bool checked[12];
1636 for (int i = 0; i < 12; ++i)
1637 checked[i] = false;
1638 for (int i = 0, end = mnths.count(); i < end; ++i)
1639 checked[mnths[i] - 1] = true;
1640 for (int i = 0; i < 12; ++i)
1641 mMonthBox[i]->setChecked(checked[i]);
1642 enableFeb29();
1645 /******************************************************************************
1646 * Return the date for February 29th alarms in non-leap years.
1648 KARecurrence::Feb29Type YearlyRule::feb29Type() const
1650 if (mFeb29Combo->isEnabled())
1652 switch (mFeb29Combo->currentIndex())
1654 case 1: return KARecurrence::Feb29_Mar1;
1655 case 2: return KARecurrence::Feb29_Feb28;
1656 default: break;
1659 return KARecurrence::Feb29_None;
1662 /******************************************************************************
1663 * Set the date for February 29th alarms to trigger in non-leap years.
1665 void YearlyRule::setFeb29Type(KARecurrence::Feb29Type type)
1667 int index;
1668 switch (type)
1670 default:
1671 case KARecurrence::Feb29_None: index = 0; break;
1672 case KARecurrence::Feb29_Mar1: index = 1; break;
1673 case KARecurrence::Feb29_Feb28: index = 2; break;
1675 mFeb29Combo->setCurrentIndex(index);
1678 /******************************************************************************
1679 * Validate: check that at least one month is selected.
1681 QWidget* YearlyRule::validate(QString& errorMessage)
1683 for (int i = 0; i < 12; ++i)
1684 if (mMonthBox[i]->isChecked() && mMonthBox[i]->isEnabled())
1685 return 0;
1686 errorMessage = i18nc("@info", "No month selected");
1687 return mMonthBox[0];
1690 /******************************************************************************
1691 * Called when a yearly recurrence type radio button is clicked,
1692 * to enable/disable month checkboxes as appropriate for the date selected.
1694 void YearlyRule::clicked(QAbstractButton* button)
1696 MonthYearRule::clicked(button);
1697 daySelected(buttonType(button) == DATE ? date() : 1);
1700 /******************************************************************************
1701 * Called when a day of the month is selected in a yearly recurrence, to
1702 * disable months for which the day is out of range.
1704 void YearlyRule::daySelected(int day)
1706 mMonthBox[1]->setEnabled(day <= 29); // February
1707 bool enable = (day != 31);
1708 mMonthBox[3]->setEnabled(enable); // April
1709 mMonthBox[5]->setEnabled(enable); // June
1710 mMonthBox[8]->setEnabled(enable); // September
1711 mMonthBox[10]->setEnabled(enable); // November
1712 enableFeb29();
1715 /******************************************************************************
1716 * Enable/disable the February 29th combo box depending on whether February
1717 * 29th is selected.
1719 void YearlyRule::enableFeb29()
1721 bool enable = (type() == DATE && date() == 29 && mMonthBox[1]->isChecked() && mMonthBox[1]->isEnabled());
1722 mFeb29Label->setEnabled(enable);
1723 mFeb29Combo->setEnabled(enable);
1726 /******************************************************************************
1727 * Save the state of all controls.
1729 void YearlyRule::saveState()
1731 MonthYearRule::saveState();
1732 mSavedMonths = months();
1733 mSavedFeb29Type = feb29Type();
1736 /******************************************************************************
1737 * Check whether any of the controls have changed state since initialisation.
1739 bool YearlyRule::stateChanged() const
1741 return (MonthYearRule::stateChanged()
1742 || mSavedMonths != months()
1743 || mSavedFeb29Type != feb29Type());