• Skip to content
  • Skip to link menu
KDE 4.2 API Reference
  • KDE API Reference
  • kdepim
  • Sitemap
  • Contact Us
 

kalarm

recurrenceedit.cpp

Go to the documentation of this file.
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 <QPushButton>
00027 #include <QLabel>
00028 #include <QStackedWidget>
00029 #include <QListWidget>
00030 #include <QGroupBox>
00031 #include <QGridLayout>
00032 #include <QHBoxLayout>
00033 #include <QVBoxLayout>
00034 #include <QtAlgorithms>
00035 
00036 #include <kglobal.h>
00037 #include <klocale.h>
00038 #include <kcalendarsystem.h>
00039 #include <kiconloader.h>
00040 #include <kdialog.h>
00041 #include <kmessagebox.h>
00042 #include <kdebug.h>
00043 
00044 #include <kcal/event.h>
00045 
00046 #include "alarmevent.h"
00047 #include "alarmtimewidget.h"
00048 #include "checkbox.h"
00049 #include "combobox.h"
00050 #include "dateedit.h"
00051 #include "functions.h"
00052 #include "kalarmapp.h"
00053 #include "karecurrence.h"
00054 #include "preferences.h"
00055 #include "radiobutton.h"
00056 #include "repetition.h"
00057 #include "spinbox.h"
00058 #include "timeedit.h"
00059 #include "timespinbox.h"
00060 #include "buttongroup.h"
00061 using namespace KCal;
00062 
00063 #include "recurrenceedit.moc"
00064 #include "recurrenceeditprivate.moc"
00065 
00066 
00067 class ListWidget : public QListWidget
00068 {
00069     public:
00070         explicit ListWidget(QWidget* parent) : QListWidget(parent) {}
00071         virtual QSize sizeHint() const  { return minimumSizeHint(); }
00072 };
00073 
00074 // Collect these widget labels together to ensure consistent wording and
00075 // translations across different modules.
00076 QString RecurrenceEdit::i18n_combo_NoRecur()        { return i18nc("@item:inlistbox Recurrence type", "No Recurrence"); }
00077 QString RecurrenceEdit::i18n_combo_AtLogin()        { return i18nc("@item:inlistbox Recurrence type", "At Login"); }
00078 QString RecurrenceEdit::i18n_combo_HourlyMinutely() { return i18nc("@item:inlistbox Recurrence type", "Hourly/Minutely"); }
00079 QString RecurrenceEdit::i18n_combo_Daily()          { return i18nc("@item:inlistbox Recurrence type", "Daily"); }
00080 QString RecurrenceEdit::i18n_combo_Weekly()         { return i18nc("@item:inlistbox Recurrence type", "Weekly"); }
00081 QString RecurrenceEdit::i18n_combo_Monthly()        { return i18nc("@item:inlistbox Recurrence type", "Monthly"); }
00082 QString RecurrenceEdit::i18n_combo_Yearly()         { return i18nc("@item:inlistbox Recurrence type", "Yearly"); }
00083 
00084 
00085 RecurrenceEdit::RecurrenceEdit(bool readOnly, QWidget* parent)
00086     : QFrame(parent),
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     kDebug();
00097     QVBoxLayout* topLayout = new QVBoxLayout(this);
00098     topLayout->setMargin(0);
00099     topLayout->setSpacing(KDialog::spacingHint());
00100 
00101     /* Create the recurrence rule Group box which holds the recurrence period
00102      * selection buttons, and the weekly, monthly and yearly recurrence rule
00103      * frames which specify options individual to each of these distinct
00104      * sections of the recurrence rule. Each frame is made visible by the
00105      * selection of its corresponding radio button.
00106      */
00107 
00108     QGroupBox* recurGroup = new QGroupBox(i18nc("@title:group", "Recurrence Rule"), this);
00109     topLayout->addWidget(recurGroup);
00110     QHBoxLayout* hlayout = new QHBoxLayout(recurGroup);
00111     hlayout->setMargin(KDialog::marginHint());
00112     hlayout->setSpacing(KDialog::marginHint());   // use margin spacing due to vertical divider line
00113 
00114     // Recurrence period radio buttons
00115     QVBoxLayout* vlayout = new QVBoxLayout();
00116     vlayout->setSpacing(0);
00117     vlayout->setMargin(0);
00118     hlayout->addLayout(vlayout);
00119     mRuleButtonGroup = new ButtonGroup(recurGroup);
00120     connect(mRuleButtonGroup, SIGNAL(buttonSet(QAbstractButton*)), SLOT(periodClicked(QAbstractButton*)));
00121 
00122     mNoneButton = new RadioButton(i18n_combo_NoRecur(), recurGroup);
00123     mNoneButton->setFixedSize(mNoneButton->sizeHint());
00124     mNoneButton->setReadOnly(mReadOnly);
00125     mNoneButton->setWhatsThis(i18nc("@info:whatsthis", "Do not repeat the alarm"));
00126     mRuleButtonGroup->addButton(mNoneButton);
00127     vlayout->addWidget(mNoneButton);
00128 
00129     mAtLoginButton = new RadioButton(i18n_combo_AtLogin(), recurGroup);
00130     mAtLoginButton->setFixedSize(mAtLoginButton->sizeHint());
00131     mAtLoginButton->setReadOnly(mReadOnly);
00132     mAtLoginButton->setWhatsThis(i18nc("@info:whatsthis",
00133                                       "<para>Trigger the alarm at the specified date/time and at every login until then.</para>"
00134                                       "<para>Note that it will also be triggered any time <application>KAlarm</application> is restarted.</para>"));
00135     mRuleButtonGroup->addButton(mAtLoginButton);
00136     vlayout->addWidget(mAtLoginButton);
00137 
00138     mSubDailyButton = new RadioButton(i18n_combo_HourlyMinutely(), recurGroup);
00139     mSubDailyButton->setFixedSize(mSubDailyButton->sizeHint());
00140     mSubDailyButton->setReadOnly(mReadOnly);
00141     mSubDailyButton->setWhatsThis(i18nc("@info:whatsthis", "Repeat the alarm at hourly/minutely intervals"));
00142     mRuleButtonGroup->addButton(mSubDailyButton);
00143     vlayout->addWidget(mSubDailyButton);
00144 
00145     mDailyButton = new RadioButton(i18n_combo_Daily(), recurGroup);
00146     mDailyButton->setFixedSize(mDailyButton->sizeHint());
00147     mDailyButton->setReadOnly(mReadOnly);
00148     mDailyButton->setWhatsThis(i18nc("@info:whatsthis", "Repeat the alarm at daily intervals"));
00149     mRuleButtonGroup->addButton(mDailyButton);
00150     vlayout->addWidget(mDailyButton);
00151 
00152     mWeeklyButton = new RadioButton(i18n_combo_Weekly(), recurGroup);
00153     mWeeklyButton->setFixedSize(mWeeklyButton->sizeHint());
00154     mWeeklyButton->setReadOnly(mReadOnly);
00155     mWeeklyButton->setWhatsThis(i18nc("@info:whatsthis", "Repeat the alarm at weekly intervals"));
00156     mRuleButtonGroup->addButton(mWeeklyButton);
00157     vlayout->addWidget(mWeeklyButton);
00158 
00159     mMonthlyButton = new RadioButton(i18n_combo_Monthly(), recurGroup);
00160     mMonthlyButton->setFixedSize(mMonthlyButton->sizeHint());
00161     mMonthlyButton->setReadOnly(mReadOnly);
00162     mMonthlyButton->setWhatsThis(i18nc("@info:whatsthis", "Repeat the alarm at monthly intervals"));
00163     mRuleButtonGroup->addButton(mMonthlyButton);
00164     vlayout->addWidget(mMonthlyButton);
00165 
00166     mYearlyButton = new RadioButton(i18n_combo_Yearly(), recurGroup);
00167     mYearlyButton->setFixedSize(mYearlyButton->sizeHint());
00168     mYearlyButton->setReadOnly(mReadOnly);
00169     mYearlyButton->setWhatsThis(i18nc("@info:whatsthis", "Repeat the alarm at annual intervals"));
00170     mRuleButtonGroup->addButton(mYearlyButton);
00171     vlayout->addWidget(mYearlyButton);
00172     vlayout->addStretch();    // top-adjust the interval radio buttons
00173 
00174     // Sub-repetition button
00175     mSubRepetition = new RepetitionButton(i18nc("@action:button", "Sub-Repetition"), true, recurGroup);
00176     mSubRepetition->setFixedSize(mSubRepetition->sizeHint());
00177     mSubRepetition->setReadOnly(mReadOnly);
00178     connect(mSubRepetition, SIGNAL(needsInitialisation()), SIGNAL(repeatNeedsInitialisation()));
00179     connect(mSubRepetition, SIGNAL(changed()), SIGNAL(frequencyChanged()));
00180     mSubRepetition->setWhatsThis(i18nc("@info:whatsthis",
00181                                        "Set up a repetition within the recurrence, to trigger the alarm multiple times each time the recurrence is due."));
00182     vlayout->addSpacing(KDialog::spacingHint());
00183     vlayout->addWidget(mSubRepetition);
00184 
00185     // Vertical divider line
00186     vlayout = new QVBoxLayout();
00187     vlayout->setMargin(0);
00188     hlayout->addLayout(vlayout);
00189     QFrame* divider = new QFrame(recurGroup);
00190     divider->setFrameStyle(QFrame::VLine | QFrame::Sunken);
00191     vlayout->addWidget(divider, 1);
00192 
00193     // Rule definition stack
00194     mRuleStack = new QStackedWidget(recurGroup);
00195     hlayout->addWidget(mRuleStack);
00196     hlayout->addStretch(1);
00197     mNoRule       = new NoRule(mRuleStack);
00198     mSubDailyRule = new SubDailyRule(mReadOnly, mRuleStack);
00199     mDailyRule    = new DailyRule(mReadOnly, mRuleStack);
00200     mWeeklyRule   = new WeeklyRule(mReadOnly, mRuleStack);
00201     mMonthlyRule  = new MonthlyRule(mReadOnly, mRuleStack);
00202     mYearlyRule   = new YearlyRule(mReadOnly, mRuleStack);
00203 
00204     connect(mSubDailyRule, SIGNAL(frequencyChanged()), this, SIGNAL(frequencyChanged()));
00205     connect(mDailyRule, SIGNAL(frequencyChanged()), this, SIGNAL(frequencyChanged()));
00206     connect(mWeeklyRule, SIGNAL(frequencyChanged()), this, SIGNAL(frequencyChanged()));
00207     connect(mMonthlyRule, SIGNAL(frequencyChanged()), this, SIGNAL(frequencyChanged()));
00208     connect(mYearlyRule, SIGNAL(frequencyChanged()), this, SIGNAL(frequencyChanged()));
00209 
00210     mRuleStack->addWidget(mNoRule);
00211     mRuleStack->addWidget(mSubDailyRule);
00212     mRuleStack->addWidget(mDailyRule);
00213     mRuleStack->addWidget(mWeeklyRule);
00214     mRuleStack->addWidget(mMonthlyRule);
00215     mRuleStack->addWidget(mYearlyRule);
00216     hlayout->addSpacing(KDialog::marginHint());
00217 
00218     // Create the recurrence range group which contains the controls
00219     // which specify how long the recurrence is to last.
00220 
00221     mRangeButtonBox = new QGroupBox(i18nc("@title:group", "Recurrence End"), this);
00222     topLayout->addWidget(mRangeButtonBox);
00223     mRangeButtonGroup = new ButtonGroup(mRangeButtonBox);
00224     connect(mRangeButtonGroup, SIGNAL(buttonSet(QAbstractButton*)), SLOT(rangeTypeClicked()));
00225 
00226     vlayout = new QVBoxLayout(mRangeButtonBox);
00227     vlayout->setMargin(KDialog::marginHint());
00228     vlayout->setSpacing(KDialog::spacingHint());
00229     mNoEndDateButton = new RadioButton(i18nc("@option:radio", "No end"), mRangeButtonBox);
00230     mNoEndDateButton->setFixedSize(mNoEndDateButton->sizeHint());
00231     mNoEndDateButton->setReadOnly(mReadOnly);
00232     mNoEndDateButton->setWhatsThis(i18nc("@info:whatsthis", "Repeat the alarm indefinitely"));
00233     mRangeButtonGroup->addButton(mNoEndDateButton);
00234     vlayout->addWidget(mNoEndDateButton, 1, Qt::AlignLeft);
00235     QSize size = mNoEndDateButton->size();
00236 
00237     hlayout = new QHBoxLayout();
00238     hlayout->setMargin(0);
00239     vlayout->addLayout(hlayout);
00240     mRepeatCountButton = new RadioButton(i18nc("@option:radio", "End after:"), mRangeButtonBox);
00241     mRepeatCountButton->setReadOnly(mReadOnly);
00242     mRepeatCountButton->setWhatsThis(i18nc("@info:whatsthis", "Repeat the alarm for the number of times specified"));
00243     mRangeButtonGroup->addButton(mRepeatCountButton);
00244     mRepeatCountEntry = new SpinBox(1, 9999, mRangeButtonBox);
00245     mRepeatCountEntry->setFixedSize(mRepeatCountEntry->sizeHint());
00246     mRepeatCountEntry->setSingleShiftStep(10);
00247     mRepeatCountEntry->setSelectOnStep(false);
00248     mRepeatCountEntry->setReadOnly(mReadOnly);
00249     connect(mRepeatCountEntry, SIGNAL(valueChanged(int)), SLOT(repeatCountChanged(int)));
00250     mRepeatCountEntry->setWhatsThis(i18nc("@info:whatsthis", "Enter the total number of times to trigger the alarm"));
00251     mRepeatCountButton->setFocusWidget(mRepeatCountEntry);
00252     mRepeatCountLabel = new QLabel(i18nc("@label", "occurrence(s)"), mRangeButtonBox);
00253     mRepeatCountLabel->setFixedSize(mRepeatCountLabel->sizeHint());
00254     hlayout->addWidget(mRepeatCountButton);
00255     hlayout->addSpacing(KDialog::spacingHint());
00256     hlayout->addWidget(mRepeatCountEntry);
00257     hlayout->addWidget(mRepeatCountLabel);
00258     hlayout->addStretch();
00259     size = size.expandedTo(mRepeatCountButton->sizeHint());
00260 
00261     hlayout = new QHBoxLayout();
00262     hlayout->setMargin(0);
00263     vlayout->addLayout(hlayout);
00264     mEndDateButton = new RadioButton(i18nc("@option:radio", "End by:"), mRangeButtonBox);
00265     mEndDateButton->setReadOnly(mReadOnly);
00266     mEndDateButton->setWhatsThis(
00267           i18nc("@info:whatsthis", "<para>Repeat the alarm until the date/time specified.</para>"
00268                 "<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>"));
00269     mRangeButtonGroup->addButton(mEndDateButton);
00270     mEndDateEdit = new DateEdit(mRangeButtonBox);
00271     mEndDateEdit->setReadOnly(mReadOnly);
00272     static const QString tzText = i18nc("@info/plain", "This uses the same time zone as the start time.");
00273     mEndDateEdit->setWhatsThis(i18nc("@info:whatsthis",
00274           "<para>Enter the last date to repeat the alarm.</para><para>%1</para>", tzText));
00275     mEndDateButton->setFocusWidget(mEndDateEdit);
00276     mEndTimeEdit = new TimeEdit(mRangeButtonBox);
00277     mEndTimeEdit->setFixedSize(mEndTimeEdit->sizeHint());
00278     mEndTimeEdit->setReadOnly(mReadOnly);
00279     mEndTimeEdit->setWhatsThis(i18nc("@info:whatsthis",
00280           "<para>Enter the last time to repeat the alarm.</para><para>%1</para><para>%2</para>", tzText, TimeSpinBox::shiftWhatsThis()));
00281     mEndAnyTimeCheckBox = new CheckBox(i18nc("@option:check", "Any time"), mRangeButtonBox);
00282     mEndAnyTimeCheckBox->setFixedSize(mEndAnyTimeCheckBox->sizeHint());
00283     mEndAnyTimeCheckBox->setReadOnly(mReadOnly);
00284     connect(mEndAnyTimeCheckBox, SIGNAL(toggled(bool)), SLOT(slotAnyTimeToggled(bool)));
00285     mEndAnyTimeCheckBox->setWhatsThis(i18nc("@info:whatsthis", "Stop repeating the alarm after your first login on or after the specified end date"));
00286     hlayout->addWidget(mEndDateButton);
00287     hlayout->addSpacing(KDialog::spacingHint());
00288     hlayout->addWidget(mEndDateEdit);
00289     hlayout->addWidget(mEndTimeEdit);
00290     hlayout->addWidget(mEndAnyTimeCheckBox);
00291     hlayout->addStretch();
00292     size = size.expandedTo(mEndDateButton->sizeHint());
00293 
00294     // Line up the widgets to the right of the radio buttons
00295     mRepeatCountButton->setFixedSize(size);
00296     mEndDateButton->setFixedSize(size);
00297 
00298     // Create the exceptions group which specifies dates to be excluded
00299     // from the recurrence.
00300 
00301     mExceptionGroup = new QGroupBox(i18nc("@title:group", "Exceptions"), this);
00302     topLayout->addWidget(mExceptionGroup);
00303     topLayout->setStretchFactor(mExceptionGroup, 2);
00304     hlayout = new QHBoxLayout(mExceptionGroup);
00305     hlayout->setMargin(KDialog::marginHint());
00306     hlayout->setSpacing(KDialog::spacingHint());
00307     vlayout = new QVBoxLayout();
00308     vlayout->setMargin(0);
00309     hlayout->addLayout(vlayout);
00310 
00311     mExceptionDateList = new ListWidget(mExceptionGroup);
00312     connect(mExceptionDateList, SIGNAL(currentRowChanged(int)), SLOT(enableExceptionButtons()));
00313     mExceptionDateList->setWhatsThis(i18nc("@info:whatsthis", "The list of exceptions, i.e. dates/times excluded from the recurrence"));
00314     vlayout->addWidget(mExceptionDateList);
00315 
00316     if (mReadOnly)
00317     {
00318         mExceptionDateEdit     = 0;
00319         mChangeExceptionButton = 0;
00320         mDeleteExceptionButton = 0;
00321     }
00322     else
00323     {
00324         vlayout = new QVBoxLayout();
00325         vlayout->setMargin(0);
00326         hlayout->addLayout(vlayout);
00327         mExceptionDateEdit = new DateEdit(mExceptionGroup);
00328         mExceptionDateEdit->setDate(QDate::currentDate());
00329         mExceptionDateEdit->setWhatsThis(i18nc("@info:whatsthis",
00330               "Enter a date to insert in the exceptions list. "
00331               "Use in conjunction with the Add or Change button below."));
00332         vlayout->addWidget(mExceptionDateEdit, 0, Qt::AlignLeft);
00333 
00334         hlayout = new QHBoxLayout();
00335         hlayout->setMargin(0);
00336         vlayout->addLayout(hlayout);
00337         QPushButton* button = new QPushButton(i18nc("@action:button", "Add"), mExceptionGroup);
00338         button->setFixedSize(button->sizeHint());
00339         connect(button, SIGNAL(clicked()), SLOT(addException()));
00340         button->setWhatsThis(i18nc("@info:whatsthis", "Add the date entered above to the exceptions list"));
00341         hlayout->addWidget(button);
00342 
00343         mChangeExceptionButton = new QPushButton(i18nc("@action:button", "Change"), mExceptionGroup);
00344         mChangeExceptionButton->setFixedSize(mChangeExceptionButton->sizeHint());
00345         connect(mChangeExceptionButton, SIGNAL(clicked()), SLOT(changeException()));
00346         mChangeExceptionButton->setWhatsThis(i18nc("@info:whatsthis",
00347               "Replace the currently highlighted item in the exceptions list with the date entered above"));
00348         hlayout->addWidget(mChangeExceptionButton);
00349 
00350         mDeleteExceptionButton = new QPushButton(i18nc("@action:button", "Delete"), mExceptionGroup);
00351         mDeleteExceptionButton->setFixedSize(mDeleteExceptionButton->sizeHint());
00352         connect(mDeleteExceptionButton, SIGNAL(clicked()), SLOT(deleteException()));
00353         mDeleteExceptionButton->setWhatsThis(i18nc("@info:whatsthis", "Remove the currently highlighted item from the exceptions list"));
00354         hlayout->addWidget(mDeleteExceptionButton);
00355     }
00356 
00357     mWorkTimeOnly = new CheckBox(i18nc("@option:check", "Only during working time"), mExceptionGroup);
00358     mWorkTimeOnly->setFixedSize(mWorkTimeOnly->sizeHint());
00359     mWorkTimeOnly->setReadOnly(mReadOnly);
00360     mWorkTimeOnly->setWhatsThis(i18nc("@info:whatsthis",
00361           "<para>Only execute the alarm during working hours, on working days.</para>"
00362           "<para>You can specify working days and hours in the Configuration dialog.</para>"));
00363     vlayout->addWidget(mWorkTimeOnly);
00364 
00365     mNoEmitTypeChanged = false;
00366 }
00367 
00368 /******************************************************************************
00369  * Verify the consistency of the entered data.
00370  * Reply = widget to receive focus on error, or 0 if no error.
00371  */
00372 QWidget* RecurrenceEdit::checkData(const KDateTime& startDateTime, QString& errorMessage) const
00373 {
00374     if (mAtLoginButton->isChecked())
00375         return 0;
00376     const_cast<RecurrenceEdit*>(this)->mCurrStartDateTime = startDateTime;
00377     if (mEndDateButton->isChecked())
00378     {
00379         // N.B. End date/time takes the same time spec as start date/time
00380         QWidget* errWidget = 0;
00381         bool noTime = !mEndTimeEdit->isEnabled();
00382         QDate endDate = mEndDateEdit->date();
00383         if (endDate < startDateTime.date())
00384             errWidget = mEndDateEdit;
00385         else if (!noTime  &&  QDateTime(endDate, mEndTimeEdit->time()) < startDateTime.dateTime())
00386             errWidget = mEndTimeEdit;
00387         if (errWidget)
00388         {
00389             errorMessage = noTime
00390                          ? i18nc("@info", "End date is earlier than start date")
00391                          : i18nc("@info", "End date/time is earlier than start date/time");
00392             return errWidget;
00393         }
00394     }
00395     if (!mRule)
00396         return 0;
00397     return mRule->validate(errorMessage);
00398 }
00399 
00400 /******************************************************************************
00401  * Called when a recurrence period radio button is clicked.
00402  */
00403 void RecurrenceEdit::periodClicked(QAbstractButton* button)
00404 {
00405     RepeatType oldType = mRuleButtonType;
00406     bool none     = (button == mNoneButton);
00407     bool atLogin  = (button == mAtLoginButton);
00408     bool subdaily = (button == mSubDailyButton);
00409     if (none)
00410     {
00411         mRule = 0;
00412         mRuleButtonType = NO_RECUR;
00413     }
00414     else if (atLogin)
00415     {
00416         mRule = 0;
00417         mRuleButtonType = AT_LOGIN;
00418         mEndDateButton->setChecked(true);
00419     }
00420     else if (subdaily)
00421     {
00422         mRule = mSubDailyRule;
00423         mRuleButtonType = SUBDAILY;
00424     }
00425     else if (button == mDailyButton)
00426     {
00427         mRule = mDailyRule;
00428         mRuleButtonType = DAILY;
00429         mDailyShown = true;
00430     }
00431     else if (button == mWeeklyButton)
00432     {
00433         mRule = mWeeklyRule;
00434         mRuleButtonType = WEEKLY;
00435         mWeeklyShown = true;
00436     }
00437     else if (button == mMonthlyButton)
00438     {
00439         mRule = mMonthlyRule;
00440         mRuleButtonType = MONTHLY;
00441         mMonthlyShown = true;
00442     }
00443     else if (button == mYearlyButton)
00444     {
00445         mRule = mYearlyRule;
00446         mRuleButtonType = ANNUAL;
00447         mYearlyShown = true;
00448     }
00449     else
00450         return;
00451 
00452     if (mRuleButtonType != oldType)
00453     {
00454         mRuleStack->setCurrentWidget(mRule ? mRule : mNoRule);
00455         if (oldType == NO_RECUR  ||  none)
00456             mRangeButtonBox->setEnabled(!none);
00457         mExceptionGroup->setEnabled(!(none || atLogin));
00458         mEndAnyTimeCheckBox->setEnabled(atLogin);
00459         if (!none)
00460         {
00461             mNoEndDateButton->setEnabled(!atLogin);
00462             mRepeatCountButton->setEnabled(!atLogin);
00463         }
00464         rangeTypeClicked();
00465         mSubRepetition->setEnabled(!(none || atLogin));
00466         if (!mNoEmitTypeChanged)
00467             emit typeChanged(mRuleButtonType);
00468     }
00469 }
00470 
00471 void RecurrenceEdit::slotAnyTimeToggled(bool on)
00472 {
00473     QAbstractButton* button = mRuleButtonGroup->checkedButton();
00474     mEndTimeEdit->setEnabled((button == mAtLoginButton && !on)
00475                          ||  (button == mSubDailyButton && mEndDateButton->isChecked()));
00476 }
00477 
00478 /******************************************************************************
00479  * Called when a recurrence range type radio button is clicked.
00480  */
00481 void RecurrenceEdit::rangeTypeClicked()
00482 {
00483     bool endDate = mEndDateButton->isChecked();
00484     mEndDateEdit->setEnabled(endDate);
00485     mEndTimeEdit->setEnabled(endDate
00486                              &&  ((mAtLoginButton->isChecked() && !mEndAnyTimeCheckBox->isChecked())
00487                                   ||  mSubDailyButton->isChecked()));
00488     bool repeatCount = mRepeatCountButton->isChecked();
00489     mRepeatCountEntry->setEnabled(repeatCount);
00490     mRepeatCountLabel->setEnabled(repeatCount);
00491 }
00492 
00493 void RecurrenceEdit::showEvent(QShowEvent*)
00494 {
00495     if (mRule)
00496         mRule->setFrequencyFocus();
00497     else
00498         mRuleButtonGroup->checkedButton()->setFocus();
00499     emit shown();
00500 }
00501 
00502 /******************************************************************************
00503 * Return the sub-repetition count within the recurrence, i.e. the number of
00504 * repetitions after the main recurrence.
00505 */
00506 int RecurrenceEdit::subRepeatCount(Duration* subRepeatInterval) const
00507 {
00508     int count = (mRuleButtonType >= SUBDAILY) ? mSubRepetition->count() : 0;
00509     if (subRepeatInterval)
00510         *subRepeatInterval = count ? mSubRepetition->interval() : Duration(0);
00511     return count;
00512 }
00513 
00514 /******************************************************************************
00515 *  Called when the Sub-Repetition button has been pressed to display the
00516 *  sub-repetition dialog.
00517 *  Alarm repetition has the following restrictions:
00518 *  1) Not allowed for a repeat-at-login alarm
00519 *  2) For a date-only alarm, the repeat interval must be a whole number of days.
00520 *  3) The overall repeat duration must be less than the recurrence interval.
00521 */
00522 void RecurrenceEdit::setSubRepetition(int reminderMinutes, bool dateOnly)
00523 {
00524     int maxDuration;
00525     switch (mRuleButtonType)
00526     {
00527         case RecurrenceEdit::NO_RECUR:
00528         case RecurrenceEdit::AT_LOGIN:   // alarm repeat not allowed
00529             maxDuration = 0;
00530             break;
00531         default:          // repeat duration must be less than recurrence interval
00532         {
00533             KAEvent event;
00534             updateEvent(event, false);
00535             maxDuration = event.longestRecurrenceInterval().asSeconds()/60 - reminderMinutes - 1;
00536             break;
00537         }
00538     }
00539     mSubRepetition->initialise(mSubRepetition->interval(), mSubRepetition->count(), dateOnly, maxDuration);
00540     mSubRepetition->setEnabled(mRuleButtonType >= SUBDAILY && maxDuration);
00541 }
00542 
00543 /******************************************************************************
00544 * Activate the sub-repetition dialog.
00545 */
00546 void RecurrenceEdit::activateSubRepetition()
00547 {
00548     mSubRepetition->activate();
00549 }
00550 
00551 /******************************************************************************
00552  * Called when the value of the repeat count field changes, to reset the
00553  * minimum value to 1 if the value was 0.
00554  */
00555 void RecurrenceEdit::repeatCountChanged(int value)
00556 {
00557     if (value > 0  &&  mRepeatCountEntry->minimum() == 0)
00558         mRepeatCountEntry->setMinimum(1);
00559 }
00560 
00561 /******************************************************************************
00562  * Add the date entered in the exception date edit control to the list of
00563  * exception dates.
00564  */
00565 void RecurrenceEdit::addException()
00566 {
00567     if (!mExceptionDateEdit  ||  !mExceptionDateEdit->isValid())
00568         return;
00569     QDate date = mExceptionDateEdit->date();
00570     DateList::Iterator it;
00571     int index = 0;
00572     bool insert = true;
00573     for (it = mExceptionDates.begin();  it != mExceptionDates.end();  ++index, ++it)
00574     {
00575         if (date <= *it)
00576         {
00577             insert = (date != *it);
00578             break;
00579         }
00580     }
00581     if (insert)
00582     {
00583         mExceptionDates.insert(it, date);
00584         mExceptionDateList->insertItem(index, new QListWidgetItem(KGlobal::locale()->formatDate(date)));
00585     }
00586     mExceptionDateList->setCurrentItem(mExceptionDateList->item(index));
00587     enableExceptionButtons();
00588 }
00589 
00590 /******************************************************************************
00591  * Change the currently highlighted exception date to that entered in the
00592  * exception date edit control.
00593  */
00594 void RecurrenceEdit::changeException()
00595 {
00596     if (!mExceptionDateEdit  ||  !mExceptionDateEdit->isValid())
00597         return;
00598     QListWidgetItem* item = mExceptionDateList->currentItem();
00599     if (item  &&  mExceptionDateList->isItemSelected(item))
00600     {
00601         int index = mExceptionDateList->row(item);
00602         QDate olddate = mExceptionDates[index];
00603         QDate newdate = mExceptionDateEdit->date();
00604         if (newdate != olddate)
00605         {
00606             mExceptionDates.removeAt(index);
00607             mExceptionDateList->takeItem(index);
00608             addException();
00609         }
00610     }
00611 }
00612 
00613 /******************************************************************************
00614  * Delete the currently highlighted exception date.
00615  */
00616 void RecurrenceEdit::deleteException()
00617 {
00618     QListWidgetItem* item = mExceptionDateList->currentItem();
00619     if (item  &&  mExceptionDateList->isItemSelected(item))
00620     {
00621         int index = mExceptionDateList->row(item);
00622         mExceptionDates.removeAt(index);
00623         mExceptionDateList->takeItem(index);
00624         enableExceptionButtons();
00625     }
00626 }
00627 
00628 /******************************************************************************
00629  * Enable/disable the exception group buttons according to whether any item is
00630  * selected in the exceptions listbox.
00631  */
00632 void RecurrenceEdit::enableExceptionButtons()
00633 {
00634     QListWidgetItem* item = mExceptionDateList->currentItem();
00635     bool enable = item;
00636     if (mDeleteExceptionButton)
00637         mDeleteExceptionButton->setEnabled(enable);
00638     if (mChangeExceptionButton)
00639         mChangeExceptionButton->setEnabled(enable);
00640 
00641     // Prevent the exceptions list box receiving keyboard focus is it's empty
00642     mExceptionDateList->setFocusPolicy(mExceptionDateList->count() ? Qt::WheelFocus : Qt::NoFocus);
00643 }
00644 
00645 /******************************************************************************
00646  * Notify this instance of a change in the alarm start date.
00647  */
00648 void RecurrenceEdit::setStartDate(const QDate& start, const QDate& today)
00649 {
00650     if (!mReadOnly)
00651     {
00652         setRuleDefaults(start);
00653         if (start < today)
00654         {
00655             mEndDateEdit->setMinDate(today);
00656             if (mExceptionDateEdit)
00657                 mExceptionDateEdit->setMinDate(today);
00658         }
00659         else
00660         {
00661             const QString startString = i18nc("@info Date cannot be earlier than start date", "start date");
00662             mEndDateEdit->setMinDate(start, startString);
00663             if (mExceptionDateEdit)
00664                 mExceptionDateEdit->setMinDate(start, startString);
00665         }
00666     }
00667 }
00668 
00669 /******************************************************************************
00670  * Specify the default recurrence end date.
00671  */
00672 void RecurrenceEdit::setDefaultEndDate(const QDate& end)
00673 {
00674     if (!mEndDateButton->isChecked())
00675         mEndDateEdit->setDate(end);
00676 }
00677 
00678 void RecurrenceEdit::setEndDateTime(const KDateTime& end)
00679 {
00680     KDateTime edt = end.toTimeSpec(mCurrStartDateTime.timeSpec());
00681     mEndDateEdit->setDate(edt.date());
00682     mEndTimeEdit->setValue(edt.time());
00683     mEndTimeEdit->setEnabled(!end.isDateOnly());
00684     mEndAnyTimeCheckBox->setChecked(end.isDateOnly());
00685 }
00686 
00687 KDateTime RecurrenceEdit::endDateTime() const
00688 {
00689     if (mRuleButtonGroup->checkedButton() == mAtLoginButton  &&  mEndAnyTimeCheckBox->isChecked())
00690         return KDateTime(mEndDateEdit->date(), mCurrStartDateTime.timeSpec());
00691     return KDateTime(mEndDateEdit->date(), mEndTimeEdit->time(), mCurrStartDateTime.timeSpec());
00692 }
00693 
00694 /******************************************************************************
00695  * Set all controls to their default values.
00696  */
00697 void RecurrenceEdit::setDefaults(const KDateTime& from)
00698 {
00699     mCurrStartDateTime = from;
00700     QDate fromDate = from.date();
00701     mNoEndDateButton->setChecked(true);
00702 
00703     mSubDailyRule->setFrequency(1);
00704     mDailyRule->setFrequency(1);
00705     mWeeklyRule->setFrequency(1);
00706     mMonthlyRule->setFrequency(1);
00707     mYearlyRule->setFrequency(1);
00708 
00709     setRuleDefaults(fromDate);
00710     mMonthlyRule->setType(MonthYearRule::DATE);   // date in month
00711     mYearlyRule->setType(MonthYearRule::DATE);    // date in year
00712 
00713     mEndDateEdit->setDate(fromDate);
00714 
00715     mNoEmitTypeChanged = true;
00716     RadioButton* button;
00717     switch (Preferences::defaultRecurPeriod())
00718     {
00719         case AT_LOGIN: button = mAtLoginButton;  break;
00720         case ANNUAL:   button = mYearlyButton;   break;
00721         case MONTHLY:  button = mMonthlyButton;  break;
00722         case WEEKLY:   button = mWeeklyButton;   break;
00723         case DAILY:    button = mDailyButton;    break;
00724         case SUBDAILY: button = mSubDailyButton; break;
00725         case NO_RECUR:
00726         default:       button = mNoneButton;     break;
00727     }
00728     button->setChecked(true);
00729     mNoEmitTypeChanged = false;
00730     rangeTypeClicked();
00731     enableExceptionButtons();
00732 
00733     saveState();
00734 }
00735 
00736 /******************************************************************************
00737  * Set the controls for weekly, monthly and yearly rules which have not so far
00738  * been shown, to their default values, depending on the recurrence start date.
00739  */
00740 void RecurrenceEdit::setRuleDefaults(const QDate& fromDate)
00741 {
00742     int day       = fromDate.day();
00743     int dayOfWeek = fromDate.dayOfWeek();
00744     int month     = fromDate.month();
00745     if (!mDailyShown)
00746         mDailyRule->setDays(true);
00747     if (!mWeeklyShown)
00748         mWeeklyRule->setDay(dayOfWeek);
00749     if (!mMonthlyShown)
00750         mMonthlyRule->setDefaultValues(day, dayOfWeek);
00751     if (!mYearlyShown)
00752         mYearlyRule->setDefaultValues(day, dayOfWeek, month);
00753 }
00754 
00755 /******************************************************************************
00756 * Set the state of all controls to reflect the data in the specified event.
00757 * Set 'keepDuration' true to prevent the recurrence count being adjusted to the
00758 * remaining number of recurrences.
00759 */
00760 void RecurrenceEdit::set(const KAEvent& event, bool keepDuration)
00761 {
00762     setDefaults(event.mainDateTime().kDateTime());
00763     if (event.repeatAtLogin())
00764     {
00765         mAtLoginButton->setChecked(true);
00766         mEndDateButton->setChecked(true);
00767         return;
00768     }
00769     mNoneButton->setChecked(true);
00770     KARecurrence* recurrence = event.recurrence();
00771     if (!recurrence)
00772         return;
00773     KARecurrence::Type rtype = recurrence->type();
00774     switch (rtype)
00775     {
00776         case KARecurrence::MINUTELY:
00777             mSubDailyButton->setChecked(true);
00778             break;
00779 
00780         case KARecurrence::DAILY:
00781         {
00782             mDailyButton->setChecked(true);
00783             QBitArray rDays = recurrence->days();
00784             bool set = false;
00785             for (int i = 0;  i < 7 && !set;  ++i)
00786                 set = rDays.testBit(i);
00787             if (set)
00788                 mDailyRule->setDays(rDays);
00789             else
00790                 mDailyRule->setDays(true);
00791             break;
00792         }
00793         case KARecurrence::WEEKLY:
00794         {
00795             mWeeklyButton->setChecked(true);
00796             QBitArray rDays = recurrence->days();
00797             mWeeklyRule->setDays(rDays);
00798             break;
00799         }
00800         case KARecurrence::MONTHLY_POS:    // on nth (Tuesday) of the month
00801         {
00802             QList<RecurrenceRule::WDayPos> posns = recurrence->monthPositions();
00803             int i = posns.first().pos();
00804             if (!i)
00805             {
00806                 // It's every (Tuesday) of the month. Convert to a weekly recurrence
00807                 // (but ignoring any non-every xxxDay positions).
00808                 mWeeklyButton->setChecked(true);
00809                 mWeeklyRule->setFrequency(recurrence->frequency());
00810                 QBitArray rDays(7);
00811                 for (int i = 0, end = posns.count();  i < end;  ++i)
00812                 {
00813                     if (!posns[i].pos())
00814                         rDays.setBit(posns[i].day() - 1, 1);
00815                 }
00816                 mWeeklyRule->setDays(rDays);
00817                 break;
00818             }
00819             mMonthlyButton->setChecked(true);
00820             mMonthlyRule->setPosition(i, posns.first().day());
00821             break;
00822         }
00823         case KARecurrence::MONTHLY_DAY:     // on nth day of the month
00824         {
00825             mMonthlyButton->setChecked(true);
00826             QList<int> rmd = recurrence->monthDays();
00827             int day = (rmd.isEmpty()) ? event.mainDate().day() : rmd.first();
00828             mMonthlyRule->setDate(day);
00829             break;
00830         }
00831         case KARecurrence::ANNUAL_DATE:   // on the nth day of (months...) in the year
00832         case KARecurrence::ANNUAL_POS:     // on the nth (Tuesday) of (months...) in the year
00833         {
00834             if (rtype == KARecurrence::ANNUAL_DATE)
00835             {
00836                 mYearlyButton->setChecked(true);
00837                 const QList<int> rmd = recurrence->monthDays();
00838                 int day = (rmd.isEmpty()) ? event.mainDate().day() : rmd.first();
00839                 mYearlyRule->setDate(day);
00840                 mYearlyRule->setFeb29Type(recurrence->feb29Type());
00841             }
00842             else if (rtype == KARecurrence::ANNUAL_POS)
00843             {
00844                 mYearlyButton->setChecked(true);
00845                 QList<RecurrenceRule::WDayPos> posns = recurrence->yearPositions();
00846                 mYearlyRule->setPosition(posns.first().pos(), posns.first().day());
00847             }
00848             mYearlyRule->setMonths(recurrence->yearMonths());
00849             break;
00850         }
00851         default:
00852             return;
00853     }
00854 
00855     mRule->setFrequency(recurrence->frequency());
00856 
00857     // Get range information
00858     KDateTime endtime = mCurrStartDateTime;
00859     int duration = recurrence->duration();
00860     if (duration == -1)
00861         mNoEndDateButton->setChecked(true);
00862     else if (duration)
00863     {
00864         mRepeatCountButton->setChecked(true);
00865         mRepeatCountEntry->setValue(duration);
00866     }
00867     else
00868     {
00869         mEndDateButton->setChecked(true);
00870         endtime = recurrence->endDateTime();
00871         mEndTimeEdit->setValue(endtime.time());
00872     }
00873     mEndDateEdit->setDate(endtime.date());
00874 
00875     // Get exception information
00876     mExceptionDates = event.recurrence()->exDates();
00877     qSort(mExceptionDates);
00878     mExceptionDateList->clear();
00879     for (DateList::ConstIterator it = mExceptionDates.begin();  it != mExceptionDates.end();  ++it)
00880         new QListWidgetItem(KGlobal::locale()->formatDate(*it), mExceptionDateList);
00881     enableExceptionButtons();
00882     mWorkTimeOnly->setChecked(event.workTimeOnly());
00883 
00884     // Get repetition within recurrence
00885     mSubRepetition->set(event.repeatInterval(), event.repeatCount());
00886 
00887     rangeTypeClicked();
00888 
00889     saveState();
00890 }
00891 
00892 /******************************************************************************
00893  * Update the specified KAEvent with the entered recurrence data.
00894  * If 'adjustStart' is true, the start date/time will be adjusted if necessary
00895  * to be the first date/time which recurs on or after the original start.
00896  */
00897 void RecurrenceEdit::updateEvent(KAEvent& event, bool adjustStart)
00898 {
00899     // Get end date and repeat count, common to all types of recurring events
00900     QDate  endDate;
00901     QTime  endTime;
00902     int    repeatCount;
00903     if (mNoEndDateButton->isChecked())
00904         repeatCount = -1;
00905     else if (mRepeatCountButton->isChecked())
00906         repeatCount = mRepeatCountEntry->value();
00907     else
00908     {
00909         repeatCount = 0;
00910         endDate = mEndDateEdit->date();
00911         endTime = mEndTimeEdit->time();
00912     }
00913 
00914     // Set up the recurrence according to the type selected
00915     event.startChanges();
00916     QAbstractButton* button = mRuleButtonGroup->checkedButton();
00917     event.setRepeatAtLogin(button == mAtLoginButton);
00918     int frequency = mRule ? mRule->frequency() : 0;
00919     if (button == mSubDailyButton)
00920     {
00921         KDateTime endDateTime(endDate, endTime, mCurrStartDateTime.timeSpec());
00922         event.setRecurMinutely(frequency, repeatCount, endDateTime);
00923     }
00924     else if (button == mDailyButton)
00925     {
00926         event.setRecurDaily(frequency, mDailyRule->days(), repeatCount, endDate);
00927     }
00928     else if (button == mWeeklyButton)
00929     {
00930         event.setRecurWeekly(frequency, mWeeklyRule->days(), repeatCount, endDate);
00931     }
00932     else if (button == mMonthlyButton)
00933     {
00934         if (mMonthlyRule->type() == MonthlyRule::POS)
00935         {
00936             // It's by position
00937             KAEvent::MonthPos pos;
00938             pos.days.fill(false);
00939             pos.days.setBit(mMonthlyRule->dayOfWeek() - 1);
00940             pos.weeknum = mMonthlyRule->week();
00941             QList<KAEvent::MonthPos> poses;
00942             poses.append(pos);
00943             event.setRecurMonthlyByPos(frequency, poses, repeatCount, endDate);
00944         }
00945         else
00946         {
00947             // It's by day
00948             int daynum = mMonthlyRule->date();
00949             QList<int> daynums;
00950             daynums.append(daynum);
00951             event.setRecurMonthlyByDate(frequency, daynums, repeatCount, endDate);
00952         }
00953     }
00954     else if (button == mYearlyButton)
00955     {
00956         QList<int> months = mYearlyRule->months();
00957         if (mYearlyRule->type() == YearlyRule::POS)
00958         {
00959             // It's by position
00960             KAEvent::MonthPos pos;
00961             pos.days.fill(false);