kalarm

recurrenceedit.cpp

00001 /*
00002  *  recurrenceedit.cpp  -  widget to edit the event's recurrence definition
00003  *  Program:  kalarm
00004  *  Copyright © 2002-2008 by David Jarvie <djarvie@kde.org>
00005  *
00006  *  Based originally on KOrganizer module koeditorrecurrence.cpp,
00007  *  Copyright (c) 2000,2001 Cornelius Schumacher <schumacher@kde.org>
00008  *
00009  *  This program is free software; you can redistribute it and/or modify
00010  *  it under the terms of the GNU General Public License as published by
00011  *  the Free Software Foundation; either version 2 of the License, or
00012  *  (at your option) any later version.
00013  *
00014  *  This program is distributed in the hope that it will be useful,
00015  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00016  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00017  *  GNU General Public License for more details.
00018  *
00019  *  You should have received a copy of the GNU General Public License along
00020  *  with this program; if not, write to the Free Software Foundation, Inc.,
00021  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00022  */
00023 
00024 #include "kalarm.h"
00025 
00026 #include <qtooltip.h>
00027 #include <qlayout.h>
00028 #include <qvbox.h>
00029 #include <qwidgetstack.h>
00030 #include <qlistbox.h>
00031 #include <qframe.h>
00032 #include <qlabel.h>
00033 #include <qpushbutton.h>
00034 #include <qlineedit.h>
00035 #include <qwhatsthis.h>
00036 
00037 #include <kglobal.h>
00038 #include <klocale.h>
00039 #include <kcalendarsystem.h>
00040 #include <kiconloader.h>
00041 #include <kdialog.h>
00042 #include <kmessagebox.h>
00043 #include <kdebug.h>
00044 
00045 #include <libkcal/event.h>
00046 
00047 #include "alarmevent.h"
00048 #include "alarmtimewidget.h"
00049 #include "checkbox.h"
00050 #include "combobox.h"
00051 #include "dateedit.h"
00052 #include "functions.h"
00053 #include "kalarmapp.h"
00054 #include "karecurrence.h"
00055 #include "preferences.h"
00056 #include "radiobutton.h"
00057 #include "repetition.h"
00058 #include "spinbox.h"
00059 #include "timeedit.h"
00060 #include "timespinbox.h"
00061 #include "buttongroup.h"
00062 using namespace KCal;
00063 
00064 #include "recurrenceedit.moc"
00065 #include "recurrenceeditprivate.moc"
00066 
00067 // Collect these widget labels together to ensure consistent wording and
00068 // translations across different modules.
00069 QString RecurrenceEdit::i18n_Norecur()           { return i18n("No recurrence"); }
00070 QString RecurrenceEdit::i18n_NoRecur()           { return i18n("No Recurrence"); }
00071 QString RecurrenceEdit::i18n_AtLogin()           { return i18n("At Login"); }
00072 QString RecurrenceEdit::i18n_l_Atlogin()         { return i18n("At &login"); }
00073 QString RecurrenceEdit::i18n_HourlyMinutely()    { return i18n("Hourly/Minutely"); }
00074 QString RecurrenceEdit::i18n_u_HourlyMinutely()  { return i18n("Ho&urly/Minutely"); }
00075 QString RecurrenceEdit::i18n_Daily()             { return i18n("Daily"); }
00076 QString RecurrenceEdit::i18n_d_Daily()           { return i18n("&Daily"); }
00077 QString RecurrenceEdit::i18n_Weekly()            { return i18n("Weekly"); }
00078 QString RecurrenceEdit::i18n_w_Weekly()          { return i18n("&Weekly"); }
00079 QString RecurrenceEdit::i18n_Monthly()           { return i18n("Monthly"); }
00080 QString RecurrenceEdit::i18n_m_Monthly()         { return i18n("&Monthly"); }
00081 QString RecurrenceEdit::i18n_Yearly()            { return i18n("Yearly"); }
00082 QString RecurrenceEdit::i18n_y_Yearly()          { return i18n("&Yearly"); }
00083 
00084 
00085 RecurrenceEdit::RecurrenceEdit(bool readOnly, QWidget* parent, const char* name)
00086     : QFrame(parent, name),
00087       mRule(0),
00088       mRuleButtonType(INVALID_RECUR),
00089       mDailyShown(false),
00090       mWeeklyShown(false),
00091       mMonthlyShown(false),
00092       mYearlyShown(false),
00093       mNoEmitTypeChanged(true),
00094       mReadOnly(readOnly)
00095 {
00096     QBoxLayout* layout;
00097     QVBoxLayout* topLayout = new QVBoxLayout(this, 0, KDialog::spacingHint());
00098 
00099     /* Create the recurrence rule Group box which holds the recurrence period
00100      * selection buttons, and the weekly, monthly and yearly recurrence rule
00101      * frames which specify options individual to each of these distinct
00102      * sections of the recurrence rule. Each frame is made visible by the
00103      * selection of its corresponding radio button.
00104      */
00105 
00106     QGroupBox* recurGroup = new QGroupBox(1, Qt::Vertical, i18n("Recurrence Rule"), this, "recurGroup");
00107     topLayout->addWidget(recurGroup);
00108     QFrame* ruleFrame = new QFrame(recurGroup, "ruleFrame");
00109     layout = new QVBoxLayout(ruleFrame, 0);
00110     layout->addSpacing(KDialog::spacingHint()/2);
00111 
00112     layout = new QHBoxLayout(layout, 0);
00113     QBoxLayout* lay = new QVBoxLayout(layout, 0);
00114     mRuleButtonGroup = new ButtonGroup(1, Qt::Horizontal, ruleFrame);
00115     mRuleButtonGroup->setInsideMargin(0);
00116     mRuleButtonGroup->setFrameStyle(QFrame::NoFrame);
00117     lay->addWidget(mRuleButtonGroup);
00118     lay->addStretch();    // top-adjust the interval radio buttons
00119     connect(mRuleButtonGroup, SIGNAL(buttonSet(int)), SLOT(periodClicked(int)));
00120 
00121     mNoneButton = new RadioButton(i18n_Norecur(), mRuleButtonGroup);
00122     mNoneButton->setFixedSize(mNoneButton->sizeHint());
00123     mNoneButton->setReadOnly(mReadOnly);
00124     QWhatsThis::add(mNoneButton, i18n("Do not repeat the alarm"));
00125 
00126     mAtLoginButton = new RadioButton(i18n_l_Atlogin(), mRuleButtonGroup);
00127     mAtLoginButton->setFixedSize(mAtLoginButton->sizeHint());
00128     mAtLoginButton->setReadOnly(mReadOnly);
00129     QWhatsThis::add(mAtLoginButton,
00130           i18n("Trigger the alarm at the specified date/time and at every login until then.\n"
00131                "Note that it will also be triggered any time the alarm daemon is restarted."));
00132 
00133     mSubDailyButton = new RadioButton(i18n_u_HourlyMinutely(), mRuleButtonGroup);
00134     mSubDailyButton->setFixedSize(mSubDailyButton->sizeHint());
00135     mSubDailyButton->setReadOnly(mReadOnly);
00136     QWhatsThis::add(mSubDailyButton,
00137           i18n("Repeat the alarm at hourly/minutely intervals"));
00138 
00139     mDailyButton = new RadioButton(i18n_d_Daily(), mRuleButtonGroup);
00140     mDailyButton->setFixedSize(mDailyButton->sizeHint());
00141     mDailyButton->setReadOnly(mReadOnly);
00142     QWhatsThis::add(mDailyButton,
00143           i18n("Repeat the alarm at daily intervals"));
00144 
00145     mWeeklyButton = new RadioButton(i18n_w_Weekly(), mRuleButtonGroup);
00146     mWeeklyButton->setFixedSize(mWeeklyButton->sizeHint());
00147     mWeeklyButton->setReadOnly(mReadOnly);
00148     QWhatsThis::add(mWeeklyButton,
00149           i18n("Repeat the alarm at weekly intervals"));
00150 
00151     mMonthlyButton = new RadioButton(i18n_m_Monthly(), mRuleButtonGroup);
00152     mMonthlyButton->setFixedSize(mMonthlyButton->sizeHint());
00153     mMonthlyButton->setReadOnly(mReadOnly);
00154     QWhatsThis::add(mMonthlyButton,
00155           i18n("Repeat the alarm at monthly intervals"));
00156 
00157     mYearlyButton = new RadioButton(i18n_y_Yearly(), mRuleButtonGroup);
00158     mYearlyButton->setFixedSize(mYearlyButton->sizeHint());
00159     mYearlyButton->setReadOnly(mReadOnly);
00160     QWhatsThis::add(mYearlyButton,
00161           i18n("Repeat the alarm at annual intervals"));
00162 
00163     mNoneButtonId     = mRuleButtonGroup->id(mNoneButton);
00164     mAtLoginButtonId  = mRuleButtonGroup->id(mAtLoginButton);
00165     mSubDailyButtonId = mRuleButtonGroup->id(mSubDailyButton);
00166     mDailyButtonId    = mRuleButtonGroup->id(mDailyButton);
00167     mWeeklyButtonId   = mRuleButtonGroup->id(mWeeklyButton);
00168     mMonthlyButtonId  = mRuleButtonGroup->id(mMonthlyButton);
00169     mYearlyButtonId   = mRuleButtonGroup->id(mYearlyButton);
00170 
00171     // Sub-repetition button
00172     mSubRepetition = new RepetitionButton(i18n("Sub-Repetition"), true, ruleFrame);
00173     mSubRepetition->setFixedSize(mSubRepetition->sizeHint());
00174     mSubRepetition->setReadOnly(mReadOnly);
00175     connect(mSubRepetition, SIGNAL(needsInitialisation()), SIGNAL(repeatNeedsInitialisation()));
00176     connect(mSubRepetition, SIGNAL(changed()), SIGNAL(frequencyChanged()));
00177     QWhatsThis::add(mSubRepetition, i18n("Set up a repetition within the recurrence, to trigger the alarm multiple times each time the recurrence is due."));
00178     lay->addSpacing(KDialog::spacingHint());
00179     lay->addWidget(mSubRepetition);
00180 
00181     lay = new QVBoxLayout(layout);
00182 
00183     lay->addStretch();
00184     layout = new QHBoxLayout(lay);
00185 
00186     layout->addSpacing(KDialog::marginHint());
00187     QFrame* divider = new QFrame(ruleFrame);
00188     divider->setFrameStyle(QFrame::VLine | QFrame::Sunken);
00189     layout->addWidget(divider);
00190     layout->addSpacing(KDialog::marginHint());
00191 
00192     mNoRule       = new NoRule(ruleFrame, "noFrame");
00193     mSubDailyRule = new SubDailyRule(mReadOnly, ruleFrame, "subdayFrame");
00194     mDailyRule    = new DailyRule(mReadOnly, ruleFrame, "dayFrame");
00195     mWeeklyRule   = new WeeklyRule(mReadOnly, ruleFrame, "weekFrame");
00196     mMonthlyRule  = new MonthlyRule(mReadOnly, ruleFrame, "monthFrame");
00197     mYearlyRule   = new YearlyRule(mReadOnly, ruleFrame, "yearFrame");
00198 
00199     connect(mSubDailyRule, SIGNAL(frequencyChanged()), this, SIGNAL(frequencyChanged()));
00200     connect(mDailyRule, SIGNAL(frequencyChanged()), this, SIGNAL(frequencyChanged()));
00201     connect(mWeeklyRule, SIGNAL(frequencyChanged()), this, SIGNAL(frequencyChanged()));
00202     connect(mMonthlyRule, SIGNAL(frequencyChanged()), this, SIGNAL(frequencyChanged()));
00203     connect(mYearlyRule, SIGNAL(frequencyChanged()), this, SIGNAL(frequencyChanged()));
00204 
00205     mRuleStack = new QWidgetStack(ruleFrame);
00206     layout->addWidget(mRuleStack);
00207     layout->addStretch(1);
00208     mRuleStack->addWidget(mNoRule, 0);
00209     mRuleStack->addWidget(mSubDailyRule, 1);
00210     mRuleStack->addWidget(mDailyRule, 2);
00211     mRuleStack->addWidget(mWeeklyRule, 3);
00212     mRuleStack->addWidget(mMonthlyRule, 4);
00213     mRuleStack->addWidget(mYearlyRule, 5);
00214     layout->addSpacing(KDialog::marginHint());
00215 
00216     // Create the recurrence range group which contains the controls
00217     // which specify how long the recurrence is to last.
00218 
00219     mRangeButtonGroup = new ButtonGroup(i18n("Recurrence End"), this, "mRangeButtonGroup");
00220     connect(mRangeButtonGroup, SIGNAL(buttonSet(int)), SLOT(rangeTypeClicked()));
00221     topLayout->addWidget(mRangeButtonGroup);
00222 
00223     QVBoxLayout* vlayout = new QVBoxLayout(mRangeButtonGroup, KDialog::marginHint(), KDialog::spacingHint());
00224     vlayout->addSpacing(fontMetrics().lineSpacing()/2);
00225     mNoEndDateButton = new RadioButton(i18n("No &end"), mRangeButtonGroup);
00226     mNoEndDateButton->setFixedSize(mNoEndDateButton->sizeHint());
00227     mNoEndDateButton->setReadOnly(mReadOnly);
00228     QWhatsThis::add(mNoEndDateButton, i18n("Repeat the alarm indefinitely"));
00229     vlayout->addWidget(mNoEndDateButton, 1, Qt::AlignAuto);
00230     QSize size = mNoEndDateButton->size();
00231 
00232     layout = new QHBoxLayout(vlayout, KDialog::spacingHint());
00233     mRepeatCountButton = new RadioButton(i18n("End a&fter:"), mRangeButtonGroup);
00234     mRepeatCountButton->setReadOnly(mReadOnly);
00235     QWhatsThis::add(mRepeatCountButton,
00236           i18n("Repeat the alarm for the number of times specified"));
00237     mRepeatCountEntry = new SpinBox(1, 9999, 1, mRangeButtonGroup);
00238     mRepeatCountEntry->setFixedSize(mRepeatCountEntry->sizeHint());
00239     mRepeatCountEntry->setLineShiftStep(10);
00240     mRepeatCountEntry->setSelectOnStep(false);
00241     mRepeatCountEntry->setReadOnly(mReadOnly);
00242     connect(mRepeatCountEntry, SIGNAL(valueChanged(int)), SLOT(repeatCountChanged(int)));
00243     QWhatsThis::add(mRepeatCountEntry,
00244           i18n("Enter the total number of times to trigger the alarm"));
00245     mRepeatCountButton->setFocusWidget(mRepeatCountEntry);
00246     mRepeatCountLabel = new QLabel(i18n("occurrence(s)"), mRangeButtonGroup);
00247     mRepeatCountLabel->setFixedSize(mRepeatCountLabel->sizeHint());
00248     layout->addWidget(mRepeatCountButton);
00249     layout->addSpacing(KDialog::spacingHint());
00250     layout->addWidget(mRepeatCountEntry);
00251     layout->addWidget(mRepeatCountLabel);
00252     layout->addStretch();
00253     size = size.expandedTo(mRepeatCountButton->sizeHint());
00254 
00255     layout = new QHBoxLayout(vlayout, KDialog::spacingHint());
00256     mEndDateButton = new RadioButton(i18n("End &by:"), mRangeButtonGroup);
00257     mEndDateButton->setReadOnly(mReadOnly);
00258     QWhatsThis::add(mEndDateButton,
00259           i18n("Repeat the alarm until the date/time specified.\n\n"
00260                "Note: This applies to the main recurrence only. It does not limit any sub-repetition which will occur regardless after the last main recurrence."));
00261     mEndDateEdit = new DateEdit(mRangeButtonGroup);
00262     mEndDateEdit->setFixedSize(mEndDateEdit->sizeHint());
00263     mEndDateEdit->setReadOnly(mReadOnly);
00264     QWhatsThis::add(mEndDateEdit,
00265           i18n("Enter the last date to repeat the alarm"));
00266     mEndDateButton->setFocusWidget(mEndDateEdit);
00267     mEndTimeEdit = new TimeEdit(mRangeButtonGroup);
00268     mEndTimeEdit->setFixedSize(mEndTimeEdit->sizeHint());
00269     mEndTimeEdit->setReadOnly(mReadOnly);
00270     static const QString lastTimeText = i18n("Enter the last time to repeat the alarm.");
00271     QWhatsThis::add(mEndTimeEdit, QString("%1\n\n%2").arg(lastTimeText).arg(TimeSpinBox::shiftWhatsThis()));
00272     mEndAnyTimeCheckBox = new CheckBox(i18n("Any time"), mRangeButtonGroup);
00273     mEndAnyTimeCheckBox->setFixedSize(mEndAnyTimeCheckBox->sizeHint());
00274     mEndAnyTimeCheckBox->setReadOnly(mReadOnly);
00275     connect(mEndAnyTimeCheckBox, SIGNAL(toggled(bool)), SLOT(slotAnyTimeToggled(bool)));
00276     QWhatsThis::add(mEndAnyTimeCheckBox,
00277           i18n("Stop repeating the alarm after your first login on or after the specified end date"));
00278     layout->addWidget(mEndDateButton);
00279     layout->addSpacing(KDialog::spacingHint());
00280     layout->addWidget(mEndDateEdit);
00281     layout->addWidget(mEndTimeEdit);
00282     layout->addWidget(mEndAnyTimeCheckBox);
00283     layout->addStretch();
00284     size = size.expandedTo(mEndDateButton->sizeHint());
00285 
00286     // Line up the widgets to the right of the radio buttons
00287     mRepeatCountButton->setFixedSize(size);
00288     mEndDateButton->setFixedSize(size);
00289 
00290     // Create the exceptions group which specifies dates to be excluded
00291     // from the recurrence.
00292 
00293     mExceptionGroup = new QGroupBox(i18n("E&xceptions"), this, "mExceptionGroup");
00294     topLayout->addWidget(mExceptionGroup);
00295     topLayout->setStretchFactor(mExceptionGroup, 2);
00296     vlayout = new QVBoxLayout(mExceptionGroup, KDialog::marginHint(), KDialog::spacingHint());
00297     vlayout->addSpacing(fontMetrics().lineSpacing()/2);
00298     layout = new QHBoxLayout(vlayout, KDialog::spacingHint());
00299     vlayout = new QVBoxLayout(layout);
00300 
00301     mExceptionDateList = new QListBox(mExceptionGroup);
00302     mExceptionDateList->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
00303     connect(mExceptionDateList, SIGNAL(selectionChanged()), SLOT(enableExceptionButtons()));
00304     QWhatsThis::add(mExceptionDateList,
00305           i18n("The list of exceptions, i.e. dates/times excluded from the recurrence"));
00306     vlayout->addWidget(mExceptionDateList);
00307 
00308     if (mReadOnly)
00309     {
00310         mExceptionDateEdit     = 0;
00311         mChangeExceptionButton = 0;
00312         mDeleteExceptionButton = 0;
00313     }
00314     else
00315     {
00316         vlayout = new QVBoxLayout(layout);
00317         mExceptionDateEdit = new DateEdit(mExceptionGroup);
00318         mExceptionDateEdit->setFixedSize(mExceptionDateEdit->sizeHint());
00319         mExceptionDateEdit->setDate(QDate::currentDate());
00320         QWhatsThis::add(mExceptionDateEdit,
00321               i18n("Enter a date to insert in the exceptions list. "
00322                    "Use in conjunction with the Add or Change button below."));
00323         vlayout->addWidget(mExceptionDateEdit);
00324 
00325         layout = new QHBoxLayout(vlayout, KDialog::spacingHint());
00326         QPushButton* button = new QPushButton(i18n("Add"), mExceptionGroup);
00327         button->setFixedSize(button->sizeHint());
00328         connect(button, SIGNAL(clicked()), SLOT(addException()));
00329         QWhatsThis::add(button,
00330               i18n("Add the date entered above to the exceptions list"));
00331         layout->addWidget(button);
00332 
00333         mChangeExceptionButton = new QPushButton(i18n("Change"), mExceptionGroup);
00334         mChangeExceptionButton->setFixedSize(mChangeExceptionButton->sizeHint());
00335         connect(mChangeExceptionButton, SIGNAL(clicked()), SLOT(changeException()));
00336         QWhatsThis::add(mChangeExceptionButton,
00337               i18n("Replace the currently highlighted item in the exceptions list with the date entered above"));
00338         layout->addWidget(mChangeExceptionButton);
00339 
00340         mDeleteExceptionButton = new QPushButton(i18n("Delete"), mExceptionGroup);
00341         mDeleteExceptionButton->setFixedSize(mDeleteExceptionButton->sizeHint());
00342         connect(mDeleteExceptionButton, SIGNAL(clicked()), SLOT(deleteException()));
00343         QWhatsThis::add(mDeleteExceptionButton,
00344               i18n("Remove the currently highlighted item from the exceptions list"));
00345         layout->addWidget(mDeleteExceptionButton);
00346     }
00347 
00348     mNoEmitTypeChanged = false;
00349 }
00350 
00351 /******************************************************************************
00352  * Verify the consistency of the entered data.
00353  * Reply = widget to receive focus on error, or 0 if no error.
00354  */
00355 QWidget* RecurrenceEdit::checkData(const QDateTime& startDateTime, QString& errorMessage) const
00356 {
00357     if (mAtLoginButton->isOn())
00358         return 0;
00359     const_cast<RecurrenceEdit*>(this)->mCurrStartDateTime = startDateTime;
00360     if (mEndDateButton->isChecked())
00361     {
00362         QWidget* errWidget = 0;
00363         bool noTime = !mEndTimeEdit->isEnabled();
00364         QDate endDate = mEndDateEdit->date();
00365         if (endDate < startDateTime.date())
00366             errWidget = mEndDateEdit;
00367         else if (!noTime  &&  QDateTime(endDate, mEndTimeEdit->time()) < startDateTime)
00368             errWidget = mEndTimeEdit;
00369         if (errWidget)
00370         {
00371             errorMessage = noTime
00372                          ? i18n("End date is earlier than start date")
00373                          : i18n("End date/time is earlier than start date/time");
00374             return errWidget;
00375         }
00376     }
00377     if (!mRule)
00378         return 0;
00379     return mRule->validate(errorMessage);
00380 }
00381 
00382 /******************************************************************************
00383  * Called when a recurrence period radio button is clicked.
00384  */
00385 void RecurrenceEdit::periodClicked(int id)
00386 {
00387     RepeatType oldType = mRuleButtonType;
00388     bool none     = (id == mNoneButtonId);
00389     bool atLogin  = (id == mAtLoginButtonId);
00390     bool subdaily = (id == mSubDailyButtonId);
00391     if (none)
00392     {
00393         mRule = 0;
00394         mRuleButtonType = NO_RECUR;
00395     }
00396     else if (atLogin)
00397     {
00398         mRule = 0;
00399         mRuleButtonType = AT_LOGIN;
00400         mRangeButtonGroup->setButton(mRangeButtonGroup->id(mEndDateButton));
00401     }
00402     else if (subdaily)
00403     {
00404         mRule = mSubDailyRule;
00405         mRuleButtonType = SUBDAILY;
00406     }
00407     else if (id == mDailyButtonId)
00408     {
00409         mRule = mDailyRule;
00410         mRuleButtonType = DAILY;
00411         mDailyShown = true;
00412     }
00413     else if (id == mWeeklyButtonId)
00414     {
00415         mRule = mWeeklyRule;
00416         mRuleButtonType = WEEKLY;
00417         mWeeklyShown = true;
00418     }
00419     else if (id == mMonthlyButtonId)
00420     {
00421         mRule = mMonthlyRule;
00422         mRuleButtonType = MONTHLY;
00423         mMonthlyShown = true;
00424     }
00425     else if (id == mYearlyButtonId)
00426     {
00427         mRule = mYearlyRule;
00428         mRuleButtonType = ANNUAL;
00429         mYearlyShown = true;
00430     }
00431     else
00432         return;
00433 
00434     if (mRuleButtonType != oldType)
00435     {
00436         mRuleStack->raiseWidget(mRule ? mRule : mNoRule);
00437         if (oldType == NO_RECUR  ||  none)
00438             mRangeButtonGroup->setEnabled(!none);
00439         mExceptionGroup->setEnabled(!(none || atLogin));
00440         mEndAnyTimeCheckBox->setEnabled(atLogin);
00441         if (!none)
00442         {
00443             mNoEndDateButton->setEnabled(!atLogin);
00444             mRepeatCountButton->setEnabled(!atLogin);
00445         }
00446         rangeTypeClicked();
00447         mSubRepetition->setEnabled(!(none || atLogin));
00448         if (!mNoEmitTypeChanged)
00449             emit typeChanged(mRuleButtonType);
00450     }
00451 }
00452 
00453 void RecurrenceEdit::slotAnyTimeToggled(bool on)
00454 {
00455     QButton* button = mRuleButtonGroup->selected();
00456     mEndTimeEdit->setEnabled(button == mAtLoginButton && !on
00457                          ||  button == mSubDailyButton && mEndDateButton->isChecked());
00458 }
00459 
00460 /******************************************************************************
00461  * Called when a recurrence range type radio button is clicked.
00462  */
00463 void RecurrenceEdit::rangeTypeClicked()
00464 {
00465     bool endDate = mEndDateButton->isOn();
00466     mEndDateEdit->setEnabled(endDate);
00467     mEndTimeEdit->setEnabled(endDate
00468                              &&  (mAtLoginButton->isOn() && !mEndAnyTimeCheckBox->isChecked()
00469                                   ||  mSubDailyButton->isOn()));
00470     bool repeatCount = mRepeatCountButton->isOn();
00471     mRepeatCountEntry->setEnabled(repeatCount);
00472     mRepeatCountLabel->setEnabled(repeatCount);
00473 }
00474 
00475 void RecurrenceEdit::showEvent(QShowEvent*)
00476 {
00477     if (mRule)
00478         mRule->setFrequencyFocus();
00479     else
00480         mRuleButtonGroup->selected()->setFocus();
00481     emit shown();
00482 }
00483  
00484  /******************************************************************************
00485 * Return the sub-repetition count within the recurrence, i.e. the number of
00486 * repetitions after the main recurrence.
00487 */
00488 int RecurrenceEdit::subRepeatCount(int* subRepeatInterval) const
00489 {
00490     int count = (mRuleButtonType >= SUBDAILY) ? mSubRepetition->count() : 0;
00491     if (subRepeatInterval)
00492         *subRepeatInterval = count ? mSubRepetition->interval() : 0;
00493     return count;
00494 }
00495 
00496 /******************************************************************************
00497 *  Called when the Sub-Repetition button has been pressed to display the
00498 *  sub-repetition dialog.
00499 *  Alarm repetition has the following restrictions:
00500 *  1) Not allowed for a repeat-at-login alarm
00501 *  2) For a date-only alarm, the repeat interval must be a whole number of days.
00502 *  3) The overall repeat duration must be less than the recurrence interval.
00503 */
00504 void RecurrenceEdit::setSubRepetition(int reminderMinutes, bool dateOnly)
00505 {
00506     int maxDuration;
00507     switch (mRuleButtonType)
00508     {
00509         case RecurrenceEdit::NO_RECUR:
00510         case RecurrenceEdit::AT_LOGIN:   // alarm repeat not allowed
00511             maxDuration = 0;
00512             break;
00513         default:          // repeat duration must be less than recurrence interval
00514         {
00515             KAEvent event;
00516             updateEvent(event, false);
00517             maxDuration = event.longestRecurrenceInterval() - reminderMinutes - 1;
00518             break;
00519         }
00520     }
00521     mSubRepetition->initialise(mSubRepetition->interval(), mSubRepetition->count(), dateOnly, maxDuration);
00522     mSubRepetition->setEnabled(mRuleButtonType >= SUBDAILY && maxDuration);
00523 }
00524 
00525 /******************************************************************************
00526 * Activate the sub-repetition dialog.
00527 */
00528 void RecurrenceEdit::activateSubRepetition()
00529 {
00530     mSubRepetition->activate();
00531 }
00532 
00533 /******************************************************************************
00534  * Called when the value of the repeat count field changes, to reset the
00535  * minimum value to 1 if the value was 0.
00536  */
00537 void RecurrenceEdit::repeatCountChanged(int value)
00538 {
00539     if (value > 0  &&  mRepeatCountEntry->minValue() == 0)
00540         mRepeatCountEntry->setMinValue(1);
00541 }
00542 
00543 /******************************************************************************
00544  * Add the date entered in the exception date edit control to the list of
00545  * exception dates.
00546  */
00547 void RecurrenceEdit::addException()
00548 {
00549     if (!mExceptionDateEdit  ||  !mExceptionDateEdit->isValid())
00550         return;
00551     QDate date = mExceptionDateEdit->date();
00552     QValueList<QDate>::Iterator it;
00553     int index = 0;
00554     bool insert = true;
00555     for (it = mExceptionDates.begin();  it != mExceptionDates.end();  ++index, ++it)
00556     {
00557         if (date <= *it)
00558         {
00559             insert = (date != *it);
00560             break;
00561         }
00562     }
00563     if (insert)
00564     {
00565         mExceptionDates.insert(it, date);
00566         mExceptionDateList->insertItem(KGlobal::locale()->formatDate(date), index);
00567     }
00568     mExceptionDateList->setCurrentItem(index);
00569     enableExceptionButtons();
00570 }
00571 
00572 /******************************************************************************
00573  * Change the currently highlighted exception date to that entered in the
00574  * exception date edit control.
00575  */
00576 void RecurrenceEdit::changeException()
00577 {
00578     if (!mExceptionDateEdit  ||  !mExceptionDateEdit->isValid())
00579         return;
00580     int index = mExceptionDateList->currentItem();
00581     if (index >= 0  &&  mExceptionDateList->isSelected(index))
00582     {
00583         QDate olddate = mExceptionDates[index];
00584         QDate newdate = mExceptionDateEdit->date();
00585         if (newdate != olddate)
00586         {
00587             mExceptionDates.remove(mExceptionDates.at(index));
00588             mExceptionDateList->removeItem(index);
00589             addException();
00590         }
00591     }
00592 }
00593 
00594 /******************************************************************************
00595  * Delete the currently highlighted exception date.
00596  */
00597 void RecurrenceEdit::deleteException()
00598 {
00599     int index = mExceptionDateList->currentItem();
00600     if (index >= 0  &&  mExceptionDateList->isSelected(index))
00601     {
00602         mExceptionDates.remove(mExceptionDates.at(index));
00603         mExceptionDateList->removeItem(index);
00604         enableExceptionButtons();
00605     }
00606 }
00607 
00608 /******************************************************************************
00609  * Enable/disable the exception group buttons according to whether any item is
00610  * selected in the exceptions listbox.
00611  */
00612 void RecurrenceEdit::enableExceptionButtons()
00613 {
00614     int index = mExceptionDateList->currentItem();
00615     bool enable = (index >= 0  &&  mExceptionDateList->isSelected(index));
00616     if (mDeleteExceptionButton)
00617         mDeleteExceptionButton->setEnabled(enable);
00618     if (mChangeExceptionButton)
00619         mChangeExceptionButton->setEnabled(enable);
00620 
00621     // Prevent the exceptions list box receiving keyboard focus is it's empty
00622     mExceptionDateList->setFocusPolicy(mExceptionDateList->count() ? QWidget::WheelFocus : QWidget::NoFocus);
00623 }
00624 
00625 /******************************************************************************
00626  * Notify this instance of a change in the alarm start date.
00627  */
00628 void RecurrenceEdit::setStartDate(const QDate& start, const QDate& today)
00629 {
00630     if (!mReadOnly)
00631     {
00632         setRuleDefaults(start);
00633         if (start < today)
00634         {
00635             mEndDateEdit->setMinDate(today);
00636             if (mExceptionDateEdit)
00637                 mExceptionDateEdit->setMinDate(today);
00638         }
00639         else
00640         {
00641             const QString startString = i18n("Date cannot be earlier than start date", "start date");
00642             mEndDateEdit->setMinDate(start, startString);
00643             if (mExceptionDateEdit)
00644                 mExceptionDateEdit->setMinDate(start, startString);
00645         }
00646     }
00647 }
00648 
00649 /******************************************************************************
00650  * Specify the default recurrence end date.
00651  */
00652 void RecurrenceEdit::setDefaultEndDate(const QDate& end)
00653 {
00654     if (!mEndDateButton->isOn())
00655         mEndDateEdit->setDate(end);
00656 }
00657 
00658 void RecurrenceEdit::setEndDateTime(const DateTime& end)
00659 {
00660     mEndDateEdit->setDate(end.date());
00661     mEndTimeEdit->setValue(end.time());
00662     mEndTimeEdit->setEnabled(!end.isDateOnly());
00663     mEndAnyTimeCheckBox->setChecked(end.isDateOnly());
00664 }
00665 
00666 DateTime RecurrenceEdit::endDateTime() const
00667 {
00668     if (mRuleButtonGroup->selected() == mAtLoginButton  &&  mEndAnyTimeCheckBox->isChecked())
00669         return DateTime(mEndDateEdit->date());
00670     return DateTime(mEndDateEdit->date(), mEndTimeEdit->time());
00671 }
00672 
00673 /******************************************************************************
00674  * Set all controls to their default values.
00675  */
00676 void RecurrenceEdit::setDefaults(const QDateTime& from)
00677 {
00678     mCurrStartDateTime = from;
00679     QDate fromDate = from.date();
00680     mNoEndDateButton->setChecked(true);
00681 
00682     mSubDailyRule->setFrequency(1);
00683     mDailyRule->setFrequency(1);
00684     mWeeklyRule->setFrequency(1);
00685     mMonthlyRule->setFrequency(1);
00686     mYearlyRule->setFrequency(1);
00687 
00688     setRuleDefaults(fromDate);
00689     mMonthlyRule->setType(MonthYearRule::DATE);   // date in month
00690     mYearlyRule->setType(MonthYearRule::DATE);    // date in year
00691 
00692     mEndDateEdit->setDate(fromDate);
00693 
00694     mNoEmitTypeChanged = true;
00695     int button;
00696     switch (Preferences::defaultRecurPeriod())
00697     {
00698         case AT_LOGIN: button = mAtLoginButtonId;  break;
00699         case ANNUAL:   button = mYearlyButtonId;   break;
00700         case MONTHLY:  button = mMonthlyButtonId;  break;
00701         case WEEKLY:   button = mWeeklyButtonId;   break;
00702         case DAILY:    button = mDailyButtonId;    break;
00703         case SUBDAILY: button = mSubDailyButtonId; break;
00704         case NO_RECUR:
00705         default:       button = mNoneButtonId;     break;
00706     }
00707     mRuleButtonGroup->setButton(button);
00708     mNoEmitTypeChanged = false;
00709     rangeTypeClicked();
00710     enableExceptionButtons();
00711 
00712     saveState();
00713 }
00714 
00715 /******************************************************************************
00716  * Set the controls for weekly, monthly and yearly rules which have not so far
00717  * been shown, to their default values, depending on the recurrence start date.
00718  */
00719 void RecurrenceEdit::setRuleDefaults(const QDate& fromDate)
00720 {
00721     int day       = fromDate.day();
00722     int dayOfWeek = fromDate.dayOfWeek();
00723     int month     = fromDate.month();
00724     if (!mDailyShown)
00725         mDailyRule->setDays(true);
00726     if (!mWeeklyShown)
00727         mWeeklyRule->setDay(dayOfWeek);
00728     if (!mMonthlyShown)
00729         mMonthlyRule->setDefaultValues(day, dayOfWeek);
00730     if (!mYearlyShown)
00731         mYearlyRule->setDefaultValues(day, dayOfWeek, month);
00732 }
00733 
00734 /******************************************************************************
00735  * Set the state of all controls to reflect the data in the specified event.
00736  */
00737 void RecurrenceEdit::set(const KAEvent& event)
00738 {
00739     setDefaults(event.mainDateTime().dateTime());
00740     if (event.repeatAtLogin())
00741     {
00742         mRuleButtonGroup->setButton(mAtLoginButtonId);
00743         mEndDateButton->setChecked(true);
00744         return;
00745     }
00746     mRuleButtonGroup->setButton(mNoneButtonId);
00747     KARecurrence* recurrence = event.recurrence();
00748     if (!recurrence)
00749         return;
00750     KARecurrence::Type rtype = recurrence->type();
00751     switch (rtype)
00752     {
00753         case KARecurrence::MINUTELY:
00754             mRuleButtonGroup->setButton(mSubDailyButtonId);
00755             break;
00756 
00757         case KARecurrence::DAILY:
00758         {
00759             mRuleButtonGroup->setButton(mDailyButtonId);
00760             QBitArray rDays = recurrence->days();
00761             bool set = false;
00762             for (int i = 0;  i < 7 && !set;  ++i)
00763                 set = rDays.testBit(i);
00764             if (set)
00765                 mDailyRule->setDays(rDays);
00766             else
00767                 mDailyRule->setDays(true);
00768             break;
00769         }
00770         case KARecurrence::WEEKLY:
00771         {
00772             mRuleButtonGroup->setButton(mWeeklyButtonId);
00773             QBitArray rDays = recurrence->days();
00774             mWeeklyRule->setDays(rDays);
00775             break;
00776         }
00777         case KARecurrence::MONTHLY_POS:    // on nth (Tuesday) of the month
00778         {
00779             QValueList<RecurrenceRule::WDayPos> posns = recurrence->monthPositions();
00780             int i = posns.first().pos();
00781             if (!i)
00782             {
00783                 // It's every (Tuesday) of the month. Convert to a weekly recurrence
00784                 // (but ignoring any non-every xxxDay positions).
00785                 mRuleButtonGroup->setButton(mWeeklyButtonId);
00786                 mWeeklyRule->setFrequency(recurrence->frequency());
00787                 QBitArray rDays(7);
00788                 for (QValueList<RecurrenceRule::WDayPos>::ConstIterator it = posns.begin();  it != posns.end();  ++it)
00789                 {
00790                     if (!(*it).pos())
00791                         rDays.setBit((*it).day() - 1, 1);
00792                 }
00793                 mWeeklyRule->setDays(rDays);
00794                 break;
00795             }
00796             mRuleButtonGroup->setButton(mMonthlyButtonId);
00797             mMonthlyRule->setPosition(i, posns.first().day());
00798             break;
00799         }
00800         case KARecurrence::MONTHLY_DAY:     // on nth day of the month
00801         {
00802             mRuleButtonGroup->setButton(mMonthlyButtonId);
00803             QValueList<int> rmd = recurrence->monthDays();
00804             int day = (rmd.isEmpty()) ? event.mainDate().day() : rmd.first();
00805             mMonthlyRule->setDate(day);
00806             break;
00807         }
00808         case KARecurrence::ANNUAL_DATE:   // on the nth day of (months...) in the year
00809         case KARecurrence::ANNUAL_POS:     // on the nth (Tuesday) of (months...) in the year
00810         {
00811             if (rtype == KARecurrence::ANNUAL_DATE)
00812             {
00813                 mRuleButtonGroup->setButton(mYearlyButtonId);
00814                 const QValueList<int> rmd = recurrence->monthDays();
00815                 int day = (rmd.isEmpty()) ? event.mainDate().day() : rmd.first();
00816                 mYearlyRule->setDate(day);
00817                 mYearlyRule->setFeb29Type(recurrence->feb29Type());
00818             }
00819             else if (rtype == KARecurrence::ANNUAL_POS)
00820             {
00821                 mRuleButtonGroup->setButton(mYearlyButtonId);
00822                 QValueList<RecurrenceRule::WDayPos> posns = recurrence->yearPositions();
00823                 mYearlyRule->setPosition(posns.first().pos(), posns.first().day());
00824             }
00825             mYearlyRule->setMonths(recurrence->yearMonths());
00826             break;
00827         }
00828         default:
00829             return;
00830     }
00831 
00832     mRule->setFrequency(recurrence->frequency());
00833 
00834     // Get range information
00835     QDateTime endtime = mCurrStartDateTime;
00836     int duration = recurrence->duration();
00837     if (duration == -1)
00838         mNoEndDateButton->setChecked(true);
00839     else if (duration)
00840     {
00841         mRepeatCountButton->setChecked(true);
00842         int repeatCount;
00843         if (event.expired())
00844             repeatCount = duration;
00845         else if (event.mainExpired())
00846         {
00847             mRepeatCountEntry->setMinValue(0);
00848             repeatCount = 0;
00849         }
00850         else
00851             repeatCount = duration - recurrence->durationTo(QDateTime::currentDateTime());
00852         mRepeatCountEntry->setValue(repeatCount);
00853     }
00854     else
00855     {
00856         mEndDateButton->setChecked(true);
00857         endtime = recurrence->endDateTime();
00858         mEndTimeEdit->setValue(endtime.time());
00859     }
00860     mEndDateEdit->setDate(endtime.date());
00861 
00862     // Get exception information
00863     mExceptionDates = event.recurrence()->exDates();
00864     qHeapSort(mExceptionDates);
00865     mExceptionDateList->clear();
00866     for (DateList::ConstIterator it = mExceptionDates.begin();  it != mExceptionDates.end();  ++it)
00867         mExceptionDateList->insertItem(KGlobal::locale()->formatDate(*it));
00868     enableExceptionButtons();
00869 
00870     // Get repetition within recurrence
00871     mSubRepetition->set(event.repeatInterval(), event.repeatCount());
00872 
00873     rangeTypeClicked();
00874 
00875     saveState();
00876 }
00877 
00878 /******************************************************************************
00879  * Update the specified KAEvent with the entered recurrence data.
00880  * If 'adjustStart' is true, the start date/time will be adjusted if necessary
00881  * to be the first date/time which recurs on or after the original start.
00882  */
00883 void RecurrenceEdit::updateEvent(KAEvent& event, bool adjustStart)
00884 {
00885     // Get end date and repeat count, common to all types of recurring events
00886     QDate  endDate;
00887     QTime  endTime;
00888     int    repeatCount;
00889     if (mNoEndDateButton->isChecked())
00890         repeatCount = -1;
00891     else if (mRepeatCountButton->isChecked())
00892         repeatCount = mRepeatCountEntry->value();
00893     else
00894     {
00895         repeatCount = 0;
00896         endDate = mEndDateEdit->date();
00897         endTime = mEndTimeEdit->time();
00898     }
00899 
00900     // Set up the recurrence according to the type selected
00901     QButton* button = mRuleButtonGroup->selected();
00902     event.setRepeatAtLogin(button == mAtLoginButton);
00903     int frequency = mRule ? mRule->frequency() : 0;
00904     if (button == mSubDailyButton)
00905     {
00906         QDateTime endDateTime(endDate, endTime);
00907         event.setRecurMinutely(frequency, repeatCount, endDateTime);
00908     }
00909     else if (button == mDailyButton)
00910     {
00911         event.setRecurDaily(frequency, mDailyRule->days(), repeatCount, endDate);
00912     }
00913     else if (button == mWeeklyButton)
00914     {
00915         event.setRecurWeekly(frequency, mWeeklyRule->days(), repeatCount, endDate);
00916     }
00917     else if (button == mMonthlyButton)
00918     {
00919         if (mMonthlyRule->type() == MonthlyRule::POS)
00920         {
00921             // It's by position
00922             KAEvent::MonthPos pos;
00923             pos.days.fill(false);
00924             pos.days.setBit(mMonthlyRule->dayOfWeek() - 1);
00925             pos.weeknum = mMonthlyRule->week();
00926             QValueList<KAEvent::MonthPos> poses;
00927             poses.append(pos);
00928             event.setRecurMonthlyByPos(frequency, poses, repeatCount, endDate);
00929         }
00930         else
00931         {
00932             // It's by day
00933             int daynum = mMonthlyRule->date();
00934             QValueList<int> daynums;
00935             daynums.append(daynum);
00936             event.setRecurMonthlyByDate(frequency, daynums, repeatCount, endDate);
00937         }
00938     }
00939     else if (button == mYearlyButton)
00940     {
00941         QValueList<int> months = mYearlyRule->months();
00942         if (mYearlyRule->type() == YearlyRule::POS)
00943         {
00944             // It's by position
00945             KAEvent::MonthPos pos;
00946             pos.days.fill(false);
00947             pos.days.setBit(mYearlyRule->dayOfWeek() - 1);
00948             pos.weeknum = mYearlyRule->week();
00949             QValueList<KAEvent::MonthPos> poses;
00950             poses.append(pos);
00951             event.setRecurAnnualByPos(frequency, poses, months, repeatCount, endDate);
00952         }
00953         else
00954         {
00955             // It's by date in month
00956             event.setRecurAnnualByDate(frequency, months, mYearlyRule->date(),
00957                                        mYearlyRule->feb29Type(), repeatCount, endDate);
00958         }
00959     }
00960     else
00961     {
00962         event.setNoRecur();
00963         return;
00964     }
00965     if (!event.recurs())
00966         return;    // an error occurred setting up the recurrence
00967     if (adjustStart)
00968         event.setFirstRecurrence();
00969 
00970     // Set up repetition within the recurrence.
00971     // N.B. This requires the main recurrence to be set up first.
00972     int count = mSubRepetition->count();
00973     if (mRuleButtonType < SUBDAILY)
00974         count = 0;
00975     event.setRepetition(mSubRepetition->interval(), count);
00976 
00977     // Set up exceptions
00978     event.recurrence()->setExDates(mExceptionDates);
00979 
00980     event.setUpdated();
00981 }
00982 
00983 /******************************************************************************
00984  * Save the state of all controls.
00985  */
00986 void RecurrenceEdit::saveState()
00987 {
00988     mSavedRuleButton = mRuleButtonGroup->selected();
00989     if (mRule)
00990         mRule->saveState();
00991     mSavedRangeButton = mRangeButtonGroup->selected();
00992     if (mSavedRangeButton == mRepeatCountButton)
00993         mSavedRecurCount = mRepeatCountEntry->value();
00994     else if (mSavedRangeButton == mEndDateButton)
00995         mSavedEndDateTime.set(QDateTime(mEndDateEdit->date(), mEndTimeEdit->time()), mEndAnyTimeCheckBox->isChecked());
00996     mSavedExceptionDates = mExceptionDates;
00997     mSavedRepeatInterval = mSubRepetition->interval();
00998     mSavedRepeatCount    = mSubRepetition->count();
00999 }
01000 
01001 /******************************************************************************
01002  * Check whether any of the controls have changed state since initialisation.
01003  */
01004 bool RecurrenceEdit::stateChanged() const
01005 {
01006     if (mSavedRuleButton  != mRuleButtonGroup->selected()
01007     ||  mSavedRangeButton != mRangeButtonGroup->selected()
01008     ||  mRule  &&  mRule->stateChanged())
01009         return true;
01010     if (mSavedRangeButton == mRepeatCountButton
01011     &&  mSavedRecurCount  != mRepeatCountEntry->value())
01012         return true;
01013     if (mSavedRangeButton == mEndDateButton
01014     &&  mSavedEndDateTime != DateTime(QDateTime(mEndDateEdit->date(), mEndTimeEdit->time()), mEndAnyTimeCheckBox->isChecked()))
01015         return true;
01016     if (mSavedExceptionDates != mExceptionDates
01017     ||  mSavedRepeatInterval != mSubRepetition->interval()
01018     ||  mSavedRepeatCount    != mSubRepetition->count())
01019         return true;
01020     return false;
01021 }
01022 
01023 
01024 
01025 /*=============================================================================
01026 = Class Rule
01027 = Base class for rule widgets, including recurrence frequency.
01028 =============================================================================*/
01029 
01030 Rule::Rule(const QString& freqText, const QString& freqWhatsThis, bool time, bool readOnly, QWidget* parent, const char* name)
01031     : NoRule(parent, name)
01032 {
01033     mLayout = new QVBoxLayout(this, 0, KDialog::spacingHint());
01034     QHBox* freqBox = new QHBox(this);
01035     mLayout->addWidget(freqBox);
01036     QHBox* box = new QHBox(freqBox);    // this is to control the QWhatsThis text display area
01037     box->setSpacing(KDialog::spacingHint());
01038 
01039     QLabel* label = new QLabel(i18n("Recur e&very"), box);
01040     label->setFixedSize(label->sizeHint());
01041     if (time)
01042     {
01043         mIntSpinBox = 0;
01044         mSpinBox = mTimeSpinBox = new TimeSpinBox(1, 5999, box);
01045         mTimeSpinBox->setFixedSize(mTimeSpinBox->sizeHint());
01046         mTimeSpinBox->setReadOnly(readOnly);
01047     }
01048     else
01049     {
01050         mTimeSpinBox = 0;
01051         mSpinBox = mIntSpinBox = new SpinBox(1, 999, 1, box);
01052         mIntSpinBox->setFixedSize(mIntSpinBox->sizeHint());
01053         mIntSpinBox->setReadOnly(readOnly);
01054     }
01055     connect(mSpinBox, SIGNAL(valueChanged(int)), SIGNAL(frequencyChanged()));
01056     label->setBuddy(mSpinBox);
01057     label = new QLabel(freqText, box);
01058     label->setFixedSize(label->sizeHint());
01059     box->setFixedSize(sizeHint());
01060     QWhatsThis::add(box, freqWhatsThis);
01061 
01062     new QWidget(freqBox);     // left adjust the visible widgets
01063     freqBox->setFixedHeight(freqBox->sizeHint().height());
01064     freqBox->setFocusProxy(mSpinBox);
01065 }
01066 
01067 int Rule::frequency() const
01068 {
01069     if (mIntSpinBox)
01070         return mIntSpinBox->value();
01071     if (mTimeSpinBox)
01072         return mTimeSpinBox->value();
01073     return 0;
01074 }
01075 
01076 void Rule::setFrequency(int n)
01077 {
01078     if (mIntSpinBox)
01079         mIntSpinBox->setValue(n);
01080     if (mTimeSpinBox)
01081         mTimeSpinBox->setValue(n);
01082 }
01083 
01084 /******************************************************************************
01085  * Save the state of all controls.
01086  */
01087 void Rule::saveState()
01088 {
01089     mSavedFrequency = frequency();
01090 }
01091 
01092 /******************************************************************************
01093  * Check whether any of the controls have changed state since initialisation.
01094  */
01095 bool Rule::stateChanged() const
01096 {
01097     return (mSavedFrequency != frequency());
01098 }
01099 
01100 
01101 /*=============================================================================
01102 = Class SubDailyRule
01103 = Sub-daily rule widget.
01104 =============================================================================*/
01105 
01106 SubDailyRule::SubDailyRule(bool readOnly, QWidget* parent, const char* name)
01107     : Rule(i18n("hours:minutes"),
01108            i18n("Enter the number of hours and minutes between repetitions of the alarm"),
01109            true, readOnly, parent, name)
01110 { }
01111 
01112 
01113 /*=============================================================================
01114 = Class DayWeekRule
01115 = Daily/weekly rule widget base class.
01116 =============================================================================*/
01117 
01118 DayWeekRule::DayWeekRule(const QString& freqText, const QString& freqWhatsThis, const QString& daysWhatsThis,
01119                          bool readOnly, QWidget* parent, const char* name)
01120     : Rule(freqText, freqWhatsThis, false, readOnly, parent, name),
01121       mSavedDays(7)
01122 {
01123     QGridLayout* grid = new QGridLayout(layout(), 1, 4, KDialog::spacingHint());
01124     grid->setRowStretch(0, 1);
01125 
01126     QLabel* label = new QLabel(i18n("On: Tuesday", "O&n:"), this);
01127     label->setFixedSize(label->sizeHint());
01128     grid->addWidget(label, 0, 0, Qt::AlignRight | Qt::AlignTop);
01129     grid->addColSpacing(1, KDialog::spacingHint());
01130 
01131     // List the days of the week starting at the user's start day of the week.
01132     // Save the first day of the week, just in case it changes while the dialog is open.
01133     QWidget* box = new QWidget(this);   // this is to control the QWhatsThis text display area
01134     QGridLayout* dgrid = new QGridLayout(box, 4, 2, 0, KDialog::spacingHint());
01135     const KCalendarSystem* calendar = KGlobal::locale()->calendar();
01136     for (int i = 0;  i < 7;  ++i)
01137     {
01138         int day = KAlarm::localeDayInWeek_to_weekDay(i);
01139         mDayBox[i] = new CheckBox(calendar->weekDayName(day), box);
01140         mDayBox[i]->setFixedSize(mDayBox[i]->sizeHint());
01141         mDayBox[i]->setReadOnly(readOnly);
01142         dgrid->addWidget(mDayBox[i], i%4, i/4, Qt::AlignAuto);
01143     }
01144     box->setFixedSize(box->sizeHint());
01145     QWhatsThis::add(box, daysWhatsThis);
01146     grid->addWidget(box, 0, 2, Qt::AlignAuto);
01147     label->setBuddy(mDayBox[0]);
01148     grid->setColStretch(3, 1);
01149 }
01150 
01151 /******************************************************************************
01152  * Fetch which days of the week have been ticked.
01153  */
01154 QBitArray DayWeekRule::days() const
01155 {
01156     QBitArray ds(7);
01157     ds.fill(false);
01158     for (int i = 0;  i < 7;  ++i)
01159         if (mDayBox[i]->isChecked())
01160             ds.setBit(KAlarm::localeDayInWeek_to_weekDay(i) - 1, 1);
01161     return ds;
01162 }
01163 
01164 /******************************************************************************
01165  * Tick/untick every day of the week.
01166  */
01167 void DayWeekRule::setDays(bool tick)
01168 {
01169     for (int i = 0;  i < 7;  ++i)
01170         mDayBox[i]->setChecked(tick);
01171 }
01172 
01173 /******************************************************************************
01174  * Tick/untick each day of the week according to the specified bits.
01175  */
01176 void DayWeekRule::setDays(const QBitArray& days)
01177 {
01178     for (int i = 0;  i < 7;  ++i)
01179     {
01180         bool x = days.testBit(KAlarm::localeDayInWeek_to_weekDay(i) - 1);
01181         mDayBox[i]->setChecked(x);
01182     }
01183 }
01184 
01185 /******************************************************************************
01186  * Tick the specified day of the week, and untick all other days.
01187  */
01188 void DayWeekRule::setDay(int dayOfWeek)
01189 {
01190     for (int i = 0;  i < 7;  ++i)
01191         mDayBox[i]->setChecked(false);
01192     if (dayOfWeek > 0  &&  dayOfWeek <= 7)
01193         mDayBox[KAlarm::weekDay_to_localeDayInWeek(dayOfWeek)]->setChecked(true);
01194 }
01195 
01196 /******************************************************************************
01197  * Validate: check that at least one day is selected.
01198  */
01199 QWidget* DayWeekRule::validate(QString& errorMessage)
01200 {
01201     for (int i = 0;  i < 7;  ++i)
01202         if (mDayBox[i]->isChecked())
01203             return 0;
01204     errorMessage = i18n("No day selected");
01205     return mDayBox[0];
01206 }
01207 
01208 /******************************************************************************
01209  * Save the state of all controls.
01210  */
01211 void DayWeekRule::saveState()
01212 {
01213     Rule::saveState();
01214     mSavedDays = days();
01215 }
01216 
01217 /******************************************************************************
01218  * Check whether any of the controls have changed state since initialisation.
01219  */
01220 bool DayWeekRule::stateChanged() const
01221 {
01222     return (Rule::stateChanged()
01223         ||  mSavedDays != days());
01224 }
01225 
01226 
01227 /*=============================================================================
01228 = Class DailyRule
01229 = Daily rule widget.
01230 =============================================================================*/
01231 
01232 DailyRule::DailyRule(bool readOnly, QWidget* parent, const char* name)
01233     : DayWeekRule(i18n("day(s)"),
01234                   i18n("Enter the number of days between repetitions of the alarm"),
01235 #if KDE_IS_VERSION(3,9,0)
01236                   i18n("Select the days of the week on which the alarm is allowed to occur"),
01237 #else
01238                   i18n("Select the days of the week on which to repeat the alarm"),
01239 #endif
01240                   readOnly, parent, name)
01241 { }
01242 
01243 
01244 /*=============================================================================
01245 = Class WeeklyRule
01246 = Weekly rule widget.
01247 =============================================================================*/
01248 
01249 WeeklyRule::WeeklyRule(bool readOnly, QWidget* parent, const char* name)
01250     : DayWeekRule(i18n("week(s)"),
01251                   i18n("Enter the number of weeks between repetitions of the alarm"),
01252                   i18n("Select the days of the week on which to repeat the alarm"),
01253                   readOnly, parent, name)
01254 { }
01255 
01256 
01257 /*=============================================================================
01258 = Class MonthYearRule
01259 = Monthly/yearly rule widget base class.
01260 =============================================================================*/
01261 
01262 MonthYearRule::MonthYearRule(const QString& freqText, const QString& freqWhatsThis, bool allowEveryWeek,
01263                              bool readOnly, QWidget* parent, const char* name)
01264     : Rule(freqText, freqWhatsThis, false, readOnly, parent, name),
01265       mEveryWeek(allowEveryWeek)
01266 {
01267     mButtonGroup = new ButtonGroup(this);
01268     mButtonGroup->hide();
01269 
01270     // Month day selector
01271     QHBox* box = new QHBox(this);
01272     box->setSpacing(KDialog::spacingHint());
01273     layout()->addWidget(box);
01274 
01275     mDayButton = new RadioButton(i18n("On day number in the month", "O&n day"), box);
01276     mDayButton->setFixedSize(mDayButton->sizeHint());
01277     mDayButton->setReadOnly(readOnly);
01278     mDayButtonId = mButtonGroup->insert(mDayButton);
01279     QWhatsThis::add(mDayButton, i18n("Repeat the alarm on the selected day of the month"));
01280 
01281     mDayCombo = new ComboBox(false, box);
01282     mDayCombo->setSizeLimit(11);
01283     for (int i = 0;  i < 31;  ++i)
01284         mDayCombo->insertItem(QString::number(i + 1));
01285     mDayCombo->insertItem(i18n("Last day of month", "Last"));
01286     mDayCombo->setFixedSize(mDayCombo->sizeHint());
01287     mDayCombo->setReadOnly(readOnly);
01288     QWhatsThis::add(mDayCombo, i18n("Select the day of the month on which to repeat the alarm"));
01289     mDayButton->setFocusWidget(mDayCombo);
01290     connect(mDayCombo, SIGNAL(activated(int)), SLOT(slotDaySelected(int)));
01291 
01292     box->setStretchFactor(new QWidget(box), 1);    // left adjust the controls
01293     box->setFixedHeight(box->sizeHint().height());
01294 
01295     // Month position selector
01296     box = new QHBox(this);
01297     box->setSpacing(KDialog::spacingHint());
01298     layout()->addWidget(box);
01299 
01300     mPosButton = new RadioButton(i18n("On the 1st Tuesday", "On t&he"), box);
01301     mPosButton->setFixedSize(mPosButton->sizeHint());
01302     mPosButton->setReadOnly(readOnly);
01303     mPosButtonId = mButtonGroup->insert(mPosButton);
01304     QWhatsThis::add(mPosButton,
01305           i18n("Repeat the alarm on one day of the week, in the selected week of the month"));
01306 
01307     mWeekCombo = new ComboBox(false, box);
01308     mWeekCombo->insertItem(i18n("1st"));
01309     mWeekCombo->insertItem(i18n("2nd"));
01310     mWeekCombo->insertItem(i18n("3rd"));
01311     mWeekCombo->insertItem(i18n("4th"));
01312     mWeekCombo->insertItem(i18n("5th"));
01313     mWeekCombo->insertItem(i18n("Last Monday in March", "Last"));
01314     mWeekCombo->insertItem(i18n("2nd Last"));
01315     mWeekCombo->insertItem(i18n("3rd Last"));
01316     mWeekCombo->insertItem(i18n("4th Last"));
01317     mWeekCombo->insertItem(i18n("5th Last"));
01318     if (mEveryWeek)
01319     {
01320         mWeekCombo->insertItem(i18n("Every (Monday...) in month", "Every"));
01321         mWeekCombo->setSizeLimit(11);
01322     }
01323     QWhatsThis::add(mWeekCombo, i18n("Select the week of the month in which to repeat the alarm"));
01324     mWeekCombo->setFixedSize(mWeekCombo->sizeHint());
01325     mWeekCombo->setReadOnly(readOnly);
01326     mPosButton->setFocusWidget(mWeekCombo);
01327 
01328     mDayOfWeekCombo = new ComboBox(false, box);
01329     const KCalendarSystem* calendar = KGlobal::locale()->calendar();
01330     for (int i = 0;  i < 7;  ++i)
01331     {
01332         int day = KAlarm::localeDayInWeek_to_weekDay(i);
01333         mDayOfWeekCombo->insertItem(calendar->weekDayName(day));
01334     }
01335     mDayOfWeekCombo->setReadOnly(readOnly);
01336     QWhatsThis::add(mDayOfWeekCombo, i18n("Select the day of the week on which to repeat the alarm"));
01337 
01338     box->setStretchFactor(new QWidget(box), 1);    // left adjust the controls
01339     box->setFixedHeight(box->sizeHint().height());
01340     connect(mButtonGroup, SIGNAL(buttonSet(int)), SLOT(clicked(int)));
01341 }
01342 
01343 MonthYearRule::DayPosType MonthYearRule::type() const
01344 {
01345     return (mButtonGroup->selectedId() == mDayButtonId) ? DATE : POS;
01346 }
01347 
01348 void MonthYearRule::setType(MonthYearRule::DayPosType type)
01349 {
01350     mButtonGroup->setButton(type == DATE ? mDayButtonId : mPosButtonId);
01351 }
01352 
01353 void MonthYearRule::setDefaultValues(int dayOfMonth, int dayOfWeek)
01354 {
01355     --dayOfMonth;
01356     mDayCombo->setCurrentItem(dayOfMonth);
01357     mWeekCombo->setCurrentItem(dayOfMonth / 7);
01358     mDayOfWeekCombo->setCurrentItem(KAlarm::weekDay_to_localeDayInWeek(dayOfWeek));
01359 }
01360 
01361 int MonthYearRule::date() const
01362 {
01363     int daynum  = mDayCombo->currentItem() + 1;
01364     return (daynum <= 31) ? daynum : 31 - daynum;
01365 }
01366 
01367 int MonthYearRule::week() const
01368 {
01369     int weeknum = mWeekCombo->currentItem() + 1;
01370     return (weeknum <= 5) ? weeknum : (weeknum == 11) ? 0 : 5 - weeknum;
01371 }
01372 
01373 int MonthYearRule::dayOfWeek() const
01374 {
01375     return KAlarm::localeDayInWeek_to_weekDay(mDayOfWeekCombo->currentItem());
01376 }
01377 
01378 void MonthYearRule::setDate(int dayOfMonth)
01379 {
01380     mButtonGroup->setButton(mDayButtonId);
01381     mDayCombo->setCurrentItem(dayOfMonth > 0 ? dayOfMonth - 1 : dayOfMonth < 0 ? 30 - dayOfMonth : 0);   // day 0 shouldn't ever occur
01382 }
01383 
01384 void MonthYearRule::setPosition(int week, int dayOfWeek)
01385 {
01386     mButtonGroup->setButton(mPosButtonId);
01387     mWeekCombo->setCurrentItem((week > 0) ? week - 1 : (week < 0) ? 4 - week : mEveryWeek ? 10 : 0);
01388     mDayOfWeekCombo->setCurrentItem(KAlarm::weekDay_to_localeDayInWeek(dayOfWeek));
01389 }
01390 
01391 void MonthYearRule::enableSelection(DayPosType type)
01392 {
01393     bool date = (type == DATE);
01394     mDayCombo->setEnabled(date);
01395     mWeekCombo->setEnabled(!date);
01396     mDayOfWeekCombo->setEnabled(!date);
01397 }
01398 
01399 void MonthYearRule::clicked(int id)
01400 {
01401     enableSelection(id == mDayButtonId ? DATE : POS);
01402 }
01403 
01404 void MonthYearRule::slotDaySelected(int index)
01405 {
01406     daySelected(index <= 30 ? index + 1 : 30 - index);
01407 }
01408 
01409 /******************************************************************************
01410  * Save the state of all controls.
01411  */
01412 void MonthYearRule::saveState()
01413 {
01414     Rule::saveState();
01415     mSavedType = type();
01416     if (mSavedType == DATE)
01417         mSavedDay = date();
01418     else
01419     {
01420         mSavedWeek    = week();
01421         mSavedWeekDay = dayOfWeek();
01422     }
01423 }
01424 
01425 /******************************************************************************
01426  * Check whether any of the controls have changed state since initialisation.
01427  */
01428 bool MonthYearRule::stateChanged() const
01429 {
01430     if (Rule::stateChanged()
01431     ||  mSavedType != type())
01432         return true;
01433     if (mSavedType == DATE)
01434     {
01435         if (mSavedDay != date())
01436             return true;
01437     }
01438     else
01439     {
01440         if (mSavedWeek    != week()
01441         ||  mSavedWeekDay != dayOfWeek())
01442             return true;
01443     }
01444     return false;
01445 }
01446 
01447 
01448 /*=============================================================================
01449 = Class MonthlyRule
01450 = Monthly rule widget.
01451 =============================================================================*/
01452 
01453 MonthlyRule::MonthlyRule(bool readOnly, QWidget* parent, const char* name)
01454     : MonthYearRule(i18n("month(s)"),
01455            i18n("Enter the number of months between repetitions of the alarm"),
01456            false, readOnly, parent, name)
01457 { }
01458 
01459 
01460 /*=============================================================================
01461 = Class YearlyRule
01462 = Yearly rule widget.
01463 =============================================================================*/
01464 
01465 YearlyRule::YearlyRule(bool readOnly, QWidget* parent, const char* name)
01466     : MonthYearRule(i18n("year(s)"),
01467            i18n("Enter the number of years between repetitions of the alarm"),
01468            true, readOnly, parent, name)
01469 {
01470     // Set up the month selection widgets
01471     QBoxLayout* hlayout = new QHBoxLayout(layout(), KDialog::spacingHint());
01472     QLabel* label = new QLabel(i18n("List of months to select", "Months:"), this);
01473     label->setFixedSize(label->sizeHint());
01474     hlayout->addWidget(label, 0, Qt::AlignAuto | Qt::AlignTop);
01475 
01476     // List the months of the year.
01477     QWidget* w = new QWidget(this);   // this is to control the QWhatsThis text display area
01478     hlayout->addWidget(w, 1, Qt::AlignAuto);
01479     QGridLayout* grid = new QGridLayout(w, 4, 3, 0, KDialog::spacingHint());
01480     const KCalendarSystem* calendar = KGlobal::locale()->calendar();
01481     int year = QDate::currentDate().year();
01482     for (int i = 0;  i < 12;  ++i)
01483     {
01484         mMonthBox[i] = new CheckBox(calendar->monthName(i + 1, year, true), w);
01485         mMonthBox[i]->setFixedSize(mMonthBox[i]->sizeHint());
01486         mMonthBox[i]->setReadOnly(readOnly);
01487         grid->addWidget(mMonthBox[i], i%3, i/3, Qt::AlignAuto);
01488     }
01489     connect(mMonthBox[1], SIGNAL(toggled(bool)), SLOT(enableFeb29()));
01490     w->setFixedHeight(w->sizeHint().height());
01491     QWhatsThis::add(w, i18n("Select the months of the year in which to repeat the alarm"));
01492 
01493     // February 29th handling option
01494     QHBox* f29box = new QHBox(this);
01495     layout()->addWidget(f29box);
01496     QHBox* box = new QHBox(f29box);    // this is to control the QWhatsThis text display area
01497     box->setSpacing(KDialog::spacingHint());
01498     mFeb29Label = new QLabel(i18n("February 2&9th alarm in non-leap years:"), box);
01499     mFeb29Label->setFixedSize(mFeb29Label->sizeHint());
01500     mFeb29Combo = new ComboBox(false, box);
01501     mFeb29Combo->insertItem(i18n("No date", "None"));
01502     mFeb29Combo->insertItem(i18n("1st March (short form)", "1 Mar"));
01503     mFeb29Combo->insertItem(i18n("28th February (short form)", "28 Feb"));
01504     mFeb29Combo->setFixedSize(mFeb29Combo->sizeHint());
01505     mFeb29Combo->setReadOnly(readOnly);
01506     mFeb29Label->setBuddy(mFeb29Combo);
01507     box->setFixedSize(box->sizeHint());
01508     QWhatsThis::add(box,
01509           i18n("Select which date, if any, the February 29th alarm should trigger in non-leap years"));
01510     new QWidget(f29box);     // left adjust the visible widgets
01511     f29box->setFixedHeight(f29box->sizeHint().height());
01512 }
01513 
01514 void YearlyRule::setDefaultValues(int dayOfMonth, int dayOfWeek, int month)
01515 {
01516     MonthYearRule::setDefaultValues(dayOfMonth, dayOfWeek);
01517     --month;
01518     for (int i = 0;  i < 12;  ++i)
01519         mMonthBox[i]->setChecked(i == month);
01520     setFeb29Type(Preferences::defaultFeb29Type());
01521     daySelected(dayOfMonth);     // enable/disable month checkboxes as appropriate
01522 }
01523 
01524 /******************************************************************************
01525  * Fetch which months have been checked (1 - 12).
01526  * Reply = true if February has been checked.
01527  */
01528 QValueList<int> YearlyRule::months() const
01529 {
01530     QValueList<int> mnths;
01531     for (int i = 0;  i < 12;  ++i)
01532         if (mMonthBox[i]->isChecked()  &&  mMonthBox[i]->isEnabled())
01533             mnths.append(i + 1);
01534     return mnths;
01535 }
01536 
01537 /******************************************************************************
01538  * Check/uncheck each month of the year according to the specified list.
01539  */
01540 void YearlyRule::setMonths(const QValueList<int>& mnths)
01541 {
01542     bool checked[12];
01543     for (int i = 0;  i < 12;  ++i)
01544         checked[i] = false;
01545     for (QValueListConstIterator<int> it = mnths.begin();  it != mnths.end();  ++it)
01546         checked[(*it) - 1] = true;
01547     for (int i = 0;  i < 12;  ++i)
01548         mMonthBox[i]->setChecked(checked[i]);
01549     enableFeb29();
01550 }
01551 
01552 /******************************************************************************
01553  * Return the date for February 29th alarms in non-leap years.
01554  */
01555 KARecurrence::Feb29Type YearlyRule::feb29Type() const
01556 {
01557     if (mFeb29Combo->isEnabled())
01558     {
01559         switch (mFeb29Combo->currentItem())
01560         {
01561             case 1:   return KARecurrence::FEB29_MAR1;
01562             case 2:   return KARecurrence::FEB29_FEB28;
01563             default:  break;
01564         }
01565     }
01566     return KARecurrence::FEB29_FEB29;
01567 }
01568 
01569 /******************************************************************************
01570  * Set the date for February 29th alarms to trigger in non-leap years.
01571  */
01572 void YearlyRule::setFeb29Type(KARecurrence::Feb29Type type)
01573 {
01574     int index;
01575     switch (type)
01576     {
01577         default:
01578         case KARecurrence::FEB29_FEB29:  index = 0;  break;
01579         case KARecurrence::FEB29_MAR1:   index = 1;  break;
01580         case KARecurrence::FEB29_FEB28:  index = 2;  break;
01581     }
01582     mFeb29Combo->setCurrentItem(index);
01583 }
01584 
01585 /******************************************************************************
01586  * Validate: check that at least one month is selected.
01587  */
01588 QWidget* YearlyRule::validate(QString& errorMessage)
01589 {
01590     for (int i = 0;  i < 12;  ++i)
01591         if (mMonthBox[i]->isChecked()  &&  mMonthBox[i]->isEnabled())
01592             return 0;
01593     errorMessage = i18n("No month selected");
01594     return mMonthBox[0];
01595 }
01596 
01597 /******************************************************************************
01598  * Called when a yearly recurrence type radio button is clicked,
01599  * to enable/disable month checkboxes as appropriate for the date selected.
01600  */
01601 void YearlyRule::clicked(int id)
01602 {
01603     MonthYearRule::clicked(id);
01604     daySelected(buttonType(id) == DATE ? date() : 1);
01605 }
01606 
01607 /******************************************************************************
01608  * Called when a day of the month is selected in a yearly recurrence, to
01609  * disable months for which the day is out of range.
01610  */
01611 void YearlyRule::daySelected(int day)
01612 {
01613     mMonthBox[1]->setEnabled(day <= 29);  // February
01614     bool enable = (day != 31);
01615     mMonthBox[3]->setEnabled(enable);     // April
01616     mMonthBox[5]->setEnabled(enable);     // June
01617     mMonthBox[8]->setEnabled(enable);     // September
01618     mMonthBox[10]->setEnabled(enable);    // November
01619     enableFeb29();
01620 }
01621 
01622 /******************************************************************************
01623  * Enable/disable the February 29th combo box depending on whether February
01624  * 29th is selected.
01625  */
01626 void YearlyRule::enableFeb29()
01627 {
01628     bool enable = (type() == DATE  &&  date() == 29  &&  mMonthBox[1]->isChecked()  &&  mMonthBox[1]->isEnabled());
01629     mFeb29Label->setEnabled(enable);
01630     mFeb29Combo->setEnabled(enable);
01631 }
01632 
01633 /******************************************************************************
01634  * Save the state of all controls.
01635  */
01636 void YearlyRule::saveState()
01637 {
01638     MonthYearRule::saveState();
01639     mSavedMonths    = months();
01640     mSavedFeb29Type = feb29Type();
01641 }
01642 
01643 /******************************************************************************
01644  * Check whether any of the controls have changed state since initialisation.
01645  */
01646 bool YearlyRule::stateChanged() const
01647 {
01648     return (MonthYearRule::stateChanged()
01649         ||  mSavedMonths    != months()
01650         ||  mSavedFeb29Type != feb29Type());
01651 }
KDE Home | KDE Accessibility Home | Description of Access Keys