kalarm

alarmtimewidget.cpp

00001 /*
00002  *  alarmtimewidget.cpp  -  alarm date/time entry widget
00003  *  Program:  kalarm
00004  *  Copyright © 2001-2006 by David Jarvie <software@astrojar.org.uk>
00005  *
00006  *  This program is free software; you can redistribute it and/or modify
00007  *  it under the terms of the GNU General Public License as published by
00008  *  the Free Software Foundation; either version 2 of the License, or
00009  *  (at your option) any later version.
00010  *
00011  *  This program is distributed in the hope that it will be useful,
00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00014  *  GNU General Public License for more details.
00015  *
00016  *  You should have received a copy of the GNU General Public License along
00017  *  with this program; if not, write to the Free Software Foundation, Inc.,
00018  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00019  */
00020 
00021 #include "kalarm.h"
00022 
00023 #include <qlayout.h>
00024 #include <qgroupbox.h>
00025 #include <qhbox.h>
00026 #include <qpushbutton.h>
00027 #include <qwhatsthis.h>
00028 
00029 #include <kdialog.h>
00030 #include <kmessagebox.h>
00031 #include <klocale.h>
00032 
00033 #include "checkbox.h"
00034 #include "dateedit.h"
00035 #include "datetime.h"
00036 #include "radiobutton.h"
00037 #include "synchtimer.h"
00038 #include "timeedit.h"
00039 #include "timespinbox.h"
00040 #include "alarmtimewidget.moc"
00041 
00042 static const QTime time_23_59(23, 59);
00043 
00044 
00045 const int AlarmTimeWidget::maxDelayTime = 99*60 + 59;    // < 100 hours
00046 
00047 QString AlarmTimeWidget::i18n_w_TimeFromNow()     { return i18n("Time from no&w:"); }
00048 QString AlarmTimeWidget::i18n_TimeAfterPeriod()
00049 {
00050     return i18n("Enter the length of time (in hours and minutes) after "
00051                 "the current time to schedule the alarm.");
00052 }
00053 
00054 
00055 /******************************************************************************
00056 * Construct a widget with a group box and title.
00057 */
00058 AlarmTimeWidget::AlarmTimeWidget(const QString& groupBoxTitle, int mode, QWidget* parent, const char* name)
00059     : ButtonGroup(groupBoxTitle, parent, name),
00060       mMinDateTimeIsNow(false),
00061       mPastMax(false),
00062       mMinMaxTimeSet(false)
00063 {
00064     init(mode);
00065 }
00066 
00067 /******************************************************************************
00068 * Construct a widget without a group box or title.
00069 */
00070 AlarmTimeWidget::AlarmTimeWidget(int mode, QWidget* parent, const char* name)
00071     : ButtonGroup(parent, name),
00072       mMinDateTimeIsNow(false),
00073       mPastMax(false),
00074       mMinMaxTimeSet(false)
00075 {
00076     setFrameStyle(QFrame::NoFrame);
00077     init(mode);
00078 }
00079 
00080 void AlarmTimeWidget::init(int mode)
00081 {
00082     static const QString recurText = i18n("For a simple repetition, enter the date/time of the first occurrence.\n"
00083                                           "If a recurrence is configured, the start date/time will be adjusted "
00084                                           "to the first recurrence on or after the entered date/time."); 
00085 
00086     connect(this, SIGNAL(buttonSet(int)), SLOT(slotButtonSet(int)));
00087     QBoxLayout* topLayout = new QVBoxLayout(this, 0, KDialog::spacingHint());
00088     if (!title().isEmpty())
00089     {
00090         topLayout->setMargin(KDialog::marginHint());
00091         topLayout->addSpacing(fontMetrics().lineSpacing()/2);
00092     }
00093 
00094     // At time radio button/label
00095     mAtTimeRadio = new RadioButton(((mode & DEFER_TIME) ? i18n("&Defer to date/time:") : i18n("At &date/time:")), this, "atTimeRadio");
00096     mAtTimeRadio->setFixedSize(mAtTimeRadio->sizeHint());
00097     QWhatsThis::add(mAtTimeRadio,
00098                     ((mode & DEFER_TIME) ? i18n("Reschedule the alarm to the specified date and time.")
00099                                          : i18n("Schedule the alarm at the specified date and time.")));
00100 
00101     // Date edit box
00102     mDateEdit = new DateEdit(this);
00103     mDateEdit->setFixedSize(mDateEdit->sizeHint());
00104     connect(mDateEdit, SIGNAL(dateChanged(const QDate&)), SLOT(dateTimeChanged()));
00105     static const QString enterDateText = i18n("Enter the date to schedule the alarm.");
00106     QWhatsThis::add(mDateEdit, ((mode & DEFER_TIME) ? enterDateText
00107                                 : QString("%1\n%2").arg(enterDateText).arg(recurText)));
00108     mAtTimeRadio->setFocusWidget(mDateEdit);
00109 
00110     // Time edit box and Any time checkbox
00111     QHBox* timeBox = new QHBox(this);
00112     timeBox->setSpacing(2*KDialog::spacingHint());
00113     mTimeEdit = new TimeEdit(timeBox);
00114     mTimeEdit->setFixedSize(mTimeEdit->sizeHint());
00115     connect(mTimeEdit, SIGNAL(valueChanged(int)), SLOT(dateTimeChanged()));
00116     static const QString enterTimeText = i18n("Enter the time to schedule the alarm.");
00117     QWhatsThis::add(mTimeEdit,
00118                     ((mode & DEFER_TIME) ? QString("%1\n\n%2").arg(enterTimeText).arg(TimeSpinBox::shiftWhatsThis())
00119                        : QString("%1\n%2\n\n%3").arg(enterTimeText).arg(recurText).arg(TimeSpinBox::shiftWhatsThis())));
00120 
00121     mAnyTime = -1;    // current status is uninitialised
00122     if (mode & DEFER_TIME)
00123     {
00124         mAnyTimeAllowed = false;
00125         mAnyTimeCheckBox = 0;
00126     }
00127     else
00128     {
00129         mAnyTimeAllowed = true;
00130         mAnyTimeCheckBox = new CheckBox(i18n("An&y time"), timeBox);
00131         mAnyTimeCheckBox->setFixedSize(mAnyTimeCheckBox->sizeHint());
00132         connect(mAnyTimeCheckBox, SIGNAL(toggled(bool)), SLOT(slotAnyTimeToggled(bool)));
00133         QWhatsThis::add(mAnyTimeCheckBox, i18n("Schedule the alarm for any time during the day"));
00134     }
00135 
00136     // 'Time from now' radio button/label
00137     mAfterTimeRadio = new RadioButton(((mode & DEFER_TIME) ? i18n("Defer for time &interval:") : i18n_w_TimeFromNow()),
00138                                       this, "afterTimeRadio");
00139     mAfterTimeRadio->setFixedSize(mAfterTimeRadio->sizeHint());
00140     QWhatsThis::add(mAfterTimeRadio,
00141                     ((mode & DEFER_TIME) ? i18n("Reschedule the alarm for the specified time interval after now.")
00142                                          : i18n("Schedule the alarm after the specified time interval from now.")));
00143 
00144     // Delay time spin box
00145     mDelayTimeEdit = new TimeSpinBox(1, maxDelayTime, this);
00146     mDelayTimeEdit->setValue(1439);
00147     mDelayTimeEdit->setFixedSize(mDelayTimeEdit->sizeHint());
00148     connect(mDelayTimeEdit, SIGNAL(valueChanged(int)), SLOT(delayTimeChanged(int)));
00149     QWhatsThis::add(mDelayTimeEdit,
00150                     ((mode & DEFER_TIME) ? QString("%1\n\n%2").arg(i18n_TimeAfterPeriod()).arg(TimeSpinBox::shiftWhatsThis())
00151                                          : QString("%1\n%2\n\n%3").arg(i18n_TimeAfterPeriod()).arg(recurText).arg(TimeSpinBox::shiftWhatsThis())));
00152     mAfterTimeRadio->setFocusWidget(mDelayTimeEdit);
00153 
00154     // Set up the layout, either narrow or wide
00155     if (mode & NARROW)
00156     {
00157         QGridLayout* grid = new QGridLayout(topLayout, 2, 2, KDialog::spacingHint());
00158         grid->addWidget(mAtTimeRadio, 0, 0);
00159         grid->addWidget(mDateEdit, 0, 1, Qt::AlignAuto);
00160         grid->addWidget(timeBox, 1, 1, Qt::AlignAuto);
00161         grid->setColStretch(2, 1);
00162         topLayout->addStretch();
00163         QBoxLayout* layout = new QHBoxLayout(topLayout, KDialog::spacingHint());
00164         layout->addWidget(mAfterTimeRadio);
00165         layout->addWidget(mDelayTimeEdit);
00166         layout->addStretch();
00167     }
00168     else
00169     {
00170         QGridLayout* grid = new QGridLayout(topLayout, 2, 3, KDialog::spacingHint());
00171         grid->addWidget(mAtTimeRadio, 0, 0, Qt::AlignAuto);
00172         grid->addWidget(mDateEdit, 0, 1, Qt::AlignAuto);
00173         grid->addWidget(timeBox, 0, 2, Qt::AlignAuto);
00174         grid->setRowStretch(0, 1);
00175         grid->addWidget(mAfterTimeRadio, 1, 0, Qt::AlignAuto);
00176         grid->addWidget(mDelayTimeEdit, 1, 1, Qt::AlignAuto);
00177         grid->setColStretch(3, 1);
00178         topLayout->addStretch();
00179     }
00180 
00181     // Initialise the radio button statuses
00182     setButton(id(mAtTimeRadio));
00183 
00184     // Timeout every minute to update alarm time fields.
00185     MinuteTimer::connect(this, SLOT(slotTimer()));
00186 }
00187 
00188 /******************************************************************************
00189 * Set or clear read-only status for the controls
00190 */
00191 void AlarmTimeWidget::setReadOnly(bool ro)
00192 {
00193     mAtTimeRadio->setReadOnly(ro);
00194     mDateEdit->setReadOnly(ro);
00195     mTimeEdit->setReadOnly(ro);
00196     if (mAnyTimeCheckBox)
00197         mAnyTimeCheckBox->setReadOnly(ro);
00198     mAfterTimeRadio->setReadOnly(ro);
00199     mDelayTimeEdit->setReadOnly(ro);
00200 }
00201 
00202 /******************************************************************************
00203 * Select the "Time from now" radio button.
00204 */
00205 void AlarmTimeWidget::selectTimeFromNow(int minutes)
00206 {
00207     mAfterTimeRadio->setChecked(true);
00208     slotButtonSet(1);
00209     if (minutes > 0)
00210         mDelayTimeEdit->setValue(minutes);
00211 }
00212 
00213 /******************************************************************************
00214 * Fetch the entered date/time.
00215 * If 'checkExpired' is true and the entered value <= current time, an error occurs.
00216 * If 'minsFromNow' is non-null, it is set to the number of minutes' delay selected,
00217 * or to zero if a date/time was entered.
00218 * In this case, if 'showErrorMessage' is true, output an error message.
00219 * 'errorWidget' if non-null, is set to point to the widget containing the error.
00220 * Reply = invalid date/time if error.
00221 */
00222 DateTime AlarmTimeWidget::getDateTime(int* minsFromNow, bool checkExpired, bool showErrorMessage, QWidget** errorWidget) const
00223 {
00224     if (minsFromNow)
00225         *minsFromNow = 0;
00226     if (errorWidget)
00227         *errorWidget = 0;
00228     QTime nowt = QTime::currentTime();
00229     QDateTime now(QDate::currentDate(), QTime(nowt.hour(), nowt.minute()));
00230     if (mAtTimeRadio->isOn())
00231     {
00232         bool anyTime = mAnyTimeAllowed && mAnyTimeCheckBox && mAnyTimeCheckBox->isChecked();
00233         if (!mDateEdit->isValid()  ||  !mTimeEdit->isValid())
00234         {
00235             // The date and/or time is invalid
00236             if (!mDateEdit->isValid())
00237             {
00238                 if (showErrorMessage)
00239                     KMessageBox::sorry(const_cast<AlarmTimeWidget*>(this), i18n("Invalid date"));
00240                 if (errorWidget)
00241                     *errorWidget = mDateEdit;
00242             }
00243             else
00244             {
00245                 if (showErrorMessage)
00246                     KMessageBox::sorry(const_cast<AlarmTimeWidget*>(this), i18n("Invalid time"));
00247                 if (errorWidget)
00248                     *errorWidget = mTimeEdit;
00249             }
00250             return DateTime();
00251         }
00252 
00253         DateTime result;
00254         if (anyTime)
00255         {
00256             result = mDateEdit->date();
00257             if (checkExpired  &&  result.date() < now.date())
00258             {
00259                 if (showErrorMessage)
00260                     KMessageBox::sorry(const_cast<AlarmTimeWidget*>(this), i18n("Alarm date has already expired"));
00261                 if (errorWidget)
00262                     *errorWidget = mDateEdit;
00263                 return DateTime();
00264             }
00265         }
00266         else
00267         {
00268             result.set(mDateEdit->date(), mTimeEdit->time());
00269             if (checkExpired  &&  result <= now.addSecs(1))
00270             {
00271                 if (showErrorMessage)
00272                     KMessageBox::sorry(const_cast<AlarmTimeWidget*>(this), i18n("Alarm time has already expired"));
00273                 if (errorWidget)
00274                     *errorWidget = mTimeEdit;
00275                 return DateTime();
00276             }
00277         }
00278         return result;
00279     }
00280     else
00281     {
00282         if (!mDelayTimeEdit->isValid())
00283         {
00284             if (showErrorMessage)
00285                 KMessageBox::sorry(const_cast<AlarmTimeWidget*>(this), i18n("Invalid time"));
00286             if (errorWidget)
00287                 *errorWidget = mDelayTimeEdit;
00288             return DateTime();
00289         }
00290         int delayMins = mDelayTimeEdit->value();
00291         if (minsFromNow)
00292             *minsFromNow = delayMins;
00293         return now.addSecs(delayMins * 60);
00294     }
00295 }
00296 
00297 /******************************************************************************
00298 * Set the date/time.
00299 */
00300 void AlarmTimeWidget::setDateTime(const DateTime& dt)
00301 {
00302     if (dt.date().isValid())
00303     {
00304         mTimeEdit->setValue(dt.time());
00305         mDateEdit->setDate(dt.date());
00306         dateTimeChanged();     // update the delay time edit box
00307     }
00308     else
00309     {
00310         mTimeEdit->setValid(false);
00311         mDateEdit->setInvalid();
00312         mDelayTimeEdit->setValid(false);
00313     }
00314     if (mAnyTimeCheckBox)
00315     {
00316         bool anyTime = dt.isDateOnly();
00317         if (anyTime)
00318             mAnyTimeAllowed = true;
00319         mAnyTimeCheckBox->setChecked(anyTime);
00320         setAnyTime();
00321     }
00322 }
00323 
00324 /******************************************************************************
00325 * Set the minimum date/time to track the current time.
00326 */
00327 void AlarmTimeWidget::setMinDateTimeIsCurrent()
00328 {
00329     mMinDateTimeIsNow = true;
00330     mMinDateTime = QDateTime();
00331     QDateTime now = QDateTime::currentDateTime();
00332     mDateEdit->setMinDate(now.date());
00333     setMaxMinTimeIf(now);
00334 }
00335 
00336 /******************************************************************************
00337 * Set the minimum date/time, adjusting the entered date/time if necessary.
00338 * If 'dt' is invalid, any current minimum date/time is cleared.
00339 */
00340 void AlarmTimeWidget::setMinDateTime(const QDateTime& dt)
00341 {
00342     mMinDateTimeIsNow = false;
00343     mMinDateTime = dt;
00344     mDateEdit->setMinDate(dt.date());
00345     setMaxMinTimeIf(QDateTime::currentDateTime());
00346 }
00347 
00348 /******************************************************************************
00349 * Set the maximum date/time, adjusting the entered date/time if necessary.
00350 * If 'dt' is invalid, any current maximum date/time is cleared.
00351 */
00352 void AlarmTimeWidget::setMaxDateTime(const DateTime& dt)
00353 {
00354     mPastMax = false;
00355     if (dt.isValid()  &&  dt.isDateOnly())
00356         mMaxDateTime = dt.dateTime().addSecs(24*3600 - 60);
00357     else
00358         mMaxDateTime = dt.dateTime();
00359     mDateEdit->setMaxDate(mMaxDateTime.date());
00360     QDateTime now = QDateTime::currentDateTime();
00361     setMaxMinTimeIf(now);
00362     setMaxDelayTime(now);
00363 }
00364 
00365 /******************************************************************************
00366 * If the minimum and maximum date/times fall on the same date, set the minimum
00367 * and maximum times in the time edit box.
00368 */
00369 void AlarmTimeWidget::setMaxMinTimeIf(const QDateTime& now)
00370 {
00371     int   mint = 0;
00372     QTime maxt = time_23_59;
00373     mMinMaxTimeSet = false;
00374     if (mMaxDateTime.isValid())
00375     {
00376         bool set = true;
00377         QDateTime minDT;
00378         if (mMinDateTimeIsNow)
00379             minDT = now.addSecs(60);
00380         else if (mMinDateTime.isValid())
00381             minDT = mMinDateTime;
00382         else
00383             set = false;
00384         if (set  &&  mMaxDateTime.date() == minDT.date())
00385         {
00386             // The minimum and maximum times are on the same date, so
00387             // constrain the time value.
00388             mint = minDT.time().hour()*60 + minDT.time().minute();
00389             maxt = mMaxDateTime.time();
00390             mMinMaxTimeSet = true;
00391         }
00392     }
00393     mTimeEdit->setMinValue(mint);
00394     mTimeEdit->setMaxValue(maxt);
00395     mTimeEdit->setWrapping(!mint  &&  maxt == time_23_59);
00396 }
00397 
00398 /******************************************************************************
00399 * Set the maximum value for the delay time edit box, depending on the maximum
00400 * value for the date/time.
00401 */
00402 void AlarmTimeWidget::setMaxDelayTime(const QDateTime& now)
00403 {
00404     int maxVal = maxDelayTime;
00405     if (mMaxDateTime.isValid())
00406     {
00407         if (now.date().daysTo(mMaxDateTime.date()) < 100)    // avoid possible 32-bit overflow on secsTo()
00408         {
00409             QDateTime dt(now.date(), QTime(now.time().hour(), now.time().minute(), 0));   // round down to nearest minute
00410             maxVal = dt.secsTo(mMaxDateTime) / 60;
00411             if (maxVal > maxDelayTime)
00412                 maxVal = maxDelayTime;
00413         }
00414     }
00415     mDelayTimeEdit->setMaxValue(maxVal);
00416 }
00417 
00418 /******************************************************************************
00419 * Set the status for whether a time is specified, or just a date.
00420 */
00421 void AlarmTimeWidget::setAnyTime()
00422 {
00423     int old = mAnyTime;
00424     mAnyTime = (mAtTimeRadio->isOn() && mAnyTimeAllowed && mAnyTimeCheckBox && mAnyTimeCheckBox->isChecked()) ? 1 : 0;
00425     if (mAnyTime != old)
00426         emit anyTimeToggled(mAnyTime);
00427 }
00428 
00429 /******************************************************************************
00430 * Enable/disable the "any time" checkbox.
00431 */
00432 void AlarmTimeWidget::enableAnyTime(bool enable)
00433 {
00434     if (mAnyTimeCheckBox)
00435     {
00436         mAnyTimeAllowed = enable;
00437         bool at = mAtTimeRadio->isOn();
00438         mAnyTimeCheckBox->setEnabled(enable && at);
00439         if (at)
00440             mTimeEdit->setEnabled(!enable || !mAnyTimeCheckBox->isChecked());
00441         setAnyTime();
00442     }
00443 }
00444 
00445 /******************************************************************************
00446 * Called every minute to update the alarm time data entry fields.
00447 * If the maximum date/time has been reached, a 'pastMax()' signal is emitted.
00448 */
00449 void AlarmTimeWidget::slotTimer()
00450 {
00451     QDateTime now;
00452     if (mMinDateTimeIsNow)
00453     {
00454         // Make sure that the minimum date is updated when the day changes
00455         now = QDateTime::currentDateTime();
00456         mDateEdit->setMinDate(now.date());
00457     }
00458     if (mMaxDateTime.isValid())
00459     {
00460         if (!now.isValid())
00461             now = QDateTime::currentDateTime();
00462         if (!mPastMax)
00463         {
00464             // Check whether the maximum date/time has now been reached
00465             if (now.date() >= mMaxDateTime.date())
00466             {
00467                 // The current date has reached or has passed the maximum date
00468                 if (now.date() > mMaxDateTime.date()
00469                 ||  !mAnyTime && now.time() > mTimeEdit->maxTime())
00470                 {
00471                     mPastMax = true;
00472                     emit pastMax();
00473                 }
00474                 else if (mMinDateTimeIsNow  &&  !mMinMaxTimeSet)
00475                 {
00476                     // The minimum date/time tracks the clock, so set the minimum
00477                     // and maximum times
00478                     setMaxMinTimeIf(now);
00479                 }
00480             }
00481         }
00482         setMaxDelayTime(now);
00483     }
00484 
00485     if (mAtTimeRadio->isOn())
00486         dateTimeChanged();
00487     else
00488         delayTimeChanged(mDelayTimeEdit->value());
00489 }
00490 
00491 
00492 /******************************************************************************
00493 * Called when the At or After time radio button states have been set.
00494 * Updates the appropriate edit box.
00495 */
00496 void AlarmTimeWidget::slotButtonSet(int)
00497 {
00498     bool at = mAtTimeRadio->isOn();
00499     mDateEdit->setEnabled(at);
00500     mTimeEdit->setEnabled(at && (!mAnyTimeAllowed || !mAnyTimeCheckBox || !mAnyTimeCheckBox->isChecked()));
00501     if (mAnyTimeCheckBox)
00502         mAnyTimeCheckBox->setEnabled(at && mAnyTimeAllowed);
00503     // Ensure that the value of the delay edit box is > 0.
00504     QDateTime dt(mDateEdit->date(), mTimeEdit->time());
00505     int minutes = (QDateTime::currentDateTime().secsTo(dt) + 59) / 60;
00506     if (minutes <= 0)
00507         mDelayTimeEdit->setValid(true);
00508     mDelayTimeEdit->setEnabled(!at);
00509     setAnyTime();
00510 }
00511 
00512 /******************************************************************************
00513 * Called after the mAnyTimeCheckBox checkbox has been toggled.
00514 */
00515 void AlarmTimeWidget::slotAnyTimeToggled(bool on)
00516 {
00517     mTimeEdit->setEnabled((!mAnyTimeAllowed || !on) && mAtTimeRadio->isOn());
00518     setAnyTime();
00519 }
00520 
00521 /******************************************************************************
00522 * Called when the date or time edit box values have changed.
00523 * Updates the time delay edit box accordingly.
00524 */
00525 void AlarmTimeWidget::dateTimeChanged()
00526 {
00527     QDateTime dt(mDateEdit->date(), mTimeEdit->time());
00528     int minutes = (QDateTime::currentDateTime().secsTo(dt) + 59) / 60;
00529     bool blocked = mDelayTimeEdit->signalsBlocked();
00530     mDelayTimeEdit->blockSignals(true);     // prevent infinite recursion between here and delayTimeChanged()
00531     if (minutes <= 0  ||  minutes > mDelayTimeEdit->maxValue())
00532         mDelayTimeEdit->setValid(false);
00533     else
00534         mDelayTimeEdit->setValue(minutes);
00535     mDelayTimeEdit->blockSignals(blocked);
00536 }
00537 
00538 /******************************************************************************
00539 * Called when the delay time edit box value has changed.
00540 * Updates the Date and Time edit boxes accordingly.
00541 */
00542 void AlarmTimeWidget::delayTimeChanged(int minutes)
00543 {
00544     if (mDelayTimeEdit->isValid())
00545     {
00546         QDateTime dt = QDateTime::currentDateTime().addSecs(minutes * 60);
00547         bool blockedT = mTimeEdit->signalsBlocked();
00548         bool blockedD = mDateEdit->signalsBlocked();
00549         mTimeEdit->blockSignals(true);     // prevent infinite recursion between here and dateTimeChanged()
00550         mDateEdit->blockSignals(true);
00551         mTimeEdit->setValue(dt.time());
00552         mDateEdit->setDate(dt.date());
00553         mTimeEdit->blockSignals(blockedT);
00554         mDateEdit->blockSignals(blockedD);
00555     }
00556 }
KDE Home | KDE Accessibility Home | Description of Access Keys