/* * alarmtimewidget.cpp - alarm date/time entry widget * Program: kalarm * Copyright © 2001-2007 by David Jarvie * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kalarm.h" #include #include #include #include #include #include #include #include #include "checkbox.h" #include "dateedit.h" #include "datetime.h" #include "radiobutton.h" #include "synchtimer.h" #include "timeedit.h" #include "timespinbox.h" #include "alarmtimewidget.moc" static const TQTime time_23_59(23, 59); const int AlarmTimeWidget::maxDelayTime = 999*60 + 59; // < 1000 hours TQString AlarmTimeWidget::i18n_w_TimeFromNow() { return i18n("Time from no&w:"); } TQString AlarmTimeWidget::i18n_TimeAfterPeriod() { return i18n("Enter the length of time (in hours and minutes) after " "the current time to schedule the alarm."); } /****************************************************************************** * Construct a widget with a group box and title. */ AlarmTimeWidget::AlarmTimeWidget(const TQString& groupBoxTitle, int mode, TQWidget* parent, const char* name) : ButtonGroup(groupBoxTitle, parent, name), mMinDateTimeIsNow(false), mPastMax(false), mMinMaxTimeSet(false) { init(mode); } /****************************************************************************** * Construct a widget without a group box or title. */ AlarmTimeWidget::AlarmTimeWidget(int mode, TQWidget* parent, const char* name) : ButtonGroup(parent, name), mMinDateTimeIsNow(false), mPastMax(false), mMinMaxTimeSet(false) { setFrameStyle(TQFrame::NoFrame); init(mode); } void AlarmTimeWidget::init(int mode) { static const TQString recurText = i18n("For a simple repetition, enter the date/time of the first occurrence.\n" "If a recurrence is configured, the start date/time will be adjusted " "to the first recurrence on or after the entered date/time."); connect(this, TQT_SIGNAL(buttonSet(int)), TQT_SLOT(slotButtonSet(int))); TQBoxLayout* topLayout = new TQVBoxLayout(this, 0, KDialog::spacingHint()); if (!title().isEmpty()) { topLayout->setMargin(KDialog::marginHint()); topLayout->addSpacing(fontMetrics().lineSpacing()/2); } // At time radio button/label mAtTimeRadio = new RadioButton(((mode & DEFER_TIME) ? i18n("&Defer to date/time:") : i18n("At &date/time:")), this, "atTimeRadio"); mAtTimeRadio->setFixedSize(mAtTimeRadio->sizeHint()); TQWhatsThis::add(mAtTimeRadio, ((mode & DEFER_TIME) ? i18n("Reschedule the alarm to the specified date and time.") : i18n("Schedule the alarm at the specified date and time."))); // Date edit box mDateEdit = new DateEdit(this); mDateEdit->setFixedSize(mDateEdit->sizeHint()); connect(mDateEdit, TQT_SIGNAL(dateEntered(const TQDate&)), TQT_SLOT(dateTimeChanged())); static const TQString enterDateText = i18n("Enter the date to schedule the alarm."); TQWhatsThis::add(mDateEdit, ((mode & DEFER_TIME) ? enterDateText : TQString("%1\n%2").arg(enterDateText).arg(recurText))); mAtTimeRadio->setFocusWidget(mDateEdit); // Time edit box and Any time checkbox TQHBox* timeBox = new TQHBox(this); timeBox->setSpacing(2*KDialog::spacingHint()); mTimeEdit = new TimeEdit(timeBox); mTimeEdit->setFixedSize(mTimeEdit->sizeHint()); connect(mTimeEdit, TQT_SIGNAL(valueChanged(int)), TQT_SLOT(dateTimeChanged())); static const TQString enterTimeText = i18n("Enter the time to schedule the alarm."); TQWhatsThis::add(mTimeEdit, ((mode & DEFER_TIME) ? TQString("%1\n\n%2").arg(enterTimeText).arg(TimeSpinBox::shiftWhatsThis()) : TQString("%1\n%2\n\n%3").arg(enterTimeText).arg(recurText).arg(TimeSpinBox::shiftWhatsThis()))); mAnyTime = -1; // current status is uninitialised if (mode & DEFER_TIME) { mAnyTimeAllowed = false; mAnyTimeCheckBox = 0; } else { mAnyTimeAllowed = true; mAnyTimeCheckBox = new CheckBox(i18n("An&y time"), timeBox); mAnyTimeCheckBox->setFixedSize(mAnyTimeCheckBox->sizeHint()); connect(mAnyTimeCheckBox, TQT_SIGNAL(toggled(bool)), TQT_SLOT(slotAnyTimeToggled(bool))); TQWhatsThis::add(mAnyTimeCheckBox, i18n("Schedule the alarm for any time during the day")); } // 'Time from now' radio button/label mAfterTimeRadio = new RadioButton(((mode & DEFER_TIME) ? i18n("Defer for time &interval:") : i18n_w_TimeFromNow()), this, "afterTimeRadio"); mAfterTimeRadio->setFixedSize(mAfterTimeRadio->sizeHint()); TQWhatsThis::add(mAfterTimeRadio, ((mode & DEFER_TIME) ? i18n("Reschedule the alarm for the specified time interval after now.") : i18n("Schedule the alarm after the specified time interval from now."))); // Delay time spin box mDelayTimeEdit = new TimeSpinBox(1, maxDelayTime, this); mDelayTimeEdit->setValue(maxDelayTime); mDelayTimeEdit->setFixedSize(mDelayTimeEdit->sizeHint()); connect(mDelayTimeEdit, TQT_SIGNAL(valueChanged(int)), TQT_SLOT(delayTimeChanged(int))); TQWhatsThis::add(mDelayTimeEdit, ((mode & DEFER_TIME) ? TQString("%1\n\n%2").arg(i18n_TimeAfterPeriod()).arg(TimeSpinBox::shiftWhatsThis()) : TQString("%1\n%2\n\n%3").arg(i18n_TimeAfterPeriod()).arg(recurText).arg(TimeSpinBox::shiftWhatsThis()))); mAfterTimeRadio->setFocusWidget(mDelayTimeEdit); // Set up the layout, either narrow or wide if (mode & NARROW) { TQGridLayout* grid = new TQGridLayout(topLayout, 2, 2, KDialog::spacingHint()); grid->addWidget(mAtTimeRadio, 0, 0); grid->addWidget(mDateEdit, 0, 1, TQt::AlignAuto); grid->addWidget(timeBox, 1, 1, TQt::AlignAuto); grid->setColStretch(2, 1); topLayout->addStretch(); TQBoxLayout* layout = new TQHBoxLayout(topLayout, KDialog::spacingHint()); layout->addWidget(mAfterTimeRadio); layout->addWidget(mDelayTimeEdit); layout->addStretch(); } else { TQGridLayout* grid = new TQGridLayout(topLayout, 2, 3, KDialog::spacingHint()); grid->addWidget(mAtTimeRadio, 0, 0, TQt::AlignAuto); grid->addWidget(mDateEdit, 0, 1, TQt::AlignAuto); grid->addWidget(timeBox, 0, 2, TQt::AlignAuto); grid->setRowStretch(0, 1); grid->addWidget(mAfterTimeRadio, 1, 0, TQt::AlignAuto); grid->addWidget(mDelayTimeEdit, 1, 1, TQt::AlignAuto); grid->setColStretch(3, 1); topLayout->addStretch(); } // Initialise the radio button statuses setButton(id(mAtTimeRadio)); // Timeout every minute to update alarm time fields. MinuteTimer::connect(TQT_TQOBJECT(this), TQT_SLOT(slotTimer())); } /****************************************************************************** * Set or clear read-only status for the controls */ void AlarmTimeWidget::setReadOnly(bool ro) { mAtTimeRadio->setReadOnly(ro); mDateEdit->setReadOnly(ro); mTimeEdit->setReadOnly(ro); if (mAnyTimeCheckBox) mAnyTimeCheckBox->setReadOnly(ro); mAfterTimeRadio->setReadOnly(ro); mDelayTimeEdit->setReadOnly(ro); } /****************************************************************************** * Select the "Time from now" radio button. */ void AlarmTimeWidget::selectTimeFromNow(int minutes) { mAfterTimeRadio->setChecked(true); slotButtonSet(1); if (minutes > 0) mDelayTimeEdit->setValue(minutes); } /****************************************************************************** * Fetch the entered date/time. * If 'checkExpired' is true and the entered value <= current time, an error occurs. * If 'minsFromNow' is non-null, it is set to the number of minutes' delay selected, * or to zero if a date/time was entered. * In this case, if 'showErrorMessage' is true, output an error message. * 'errorWidget' if non-null, is set to point to the widget containing the error. * Reply = invalid date/time if error. */ DateTime AlarmTimeWidget::getDateTime(int* minsFromNow, bool checkExpired, bool showErrorMessage, TQWidget** errorWidget) const { if (minsFromNow) *minsFromNow = 0; if (errorWidget) *errorWidget = 0; TQTime nowt = TQTime::currentTime(); TQDateTime now(TQDate::currentDate(), TQTime(nowt.hour(), nowt.minute())); if (mAtTimeRadio->isOn()) { bool anyTime = mAnyTimeAllowed && mAnyTimeCheckBox && mAnyTimeCheckBox->isChecked(); if (!mDateEdit->isValid() || !mTimeEdit->isValid()) { // The date and/or time is invalid if (!mDateEdit->isValid()) { if (showErrorMessage) KMessageBox::sorry(const_cast(this), i18n("Invalid date")); if (errorWidget) *errorWidget = mDateEdit; } else { if (showErrorMessage) KMessageBox::sorry(const_cast(this), i18n("Invalid time")); if (errorWidget) *errorWidget = mTimeEdit; } return DateTime(); } DateTime result; if (anyTime) { result = mDateEdit->date(); if (checkExpired && result.date() < now.date()) { if (showErrorMessage) KMessageBox::sorry(const_cast(this), i18n("Alarm date has already expired")); if (errorWidget) *errorWidget = mDateEdit; return DateTime(); } } else { result.set(mDateEdit->date(), mTimeEdit->time()); if (checkExpired && result <= TQDateTime(now.addSecs(1))) { if (showErrorMessage) KMessageBox::sorry(const_cast(this), i18n("Alarm time has already expired")); if (errorWidget) *errorWidget = mTimeEdit; return DateTime(); } } return result; } else { if (!mDelayTimeEdit->isValid()) { if (showErrorMessage) KMessageBox::sorry(const_cast(this), i18n("Invalid time")); if (errorWidget) *errorWidget = mDelayTimeEdit; return DateTime(); } int delayMins = mDelayTimeEdit->value(); if (minsFromNow) *minsFromNow = delayMins; return TQDateTime(now.addSecs(delayMins * 60)); } } /****************************************************************************** * Set the date/time. */ void AlarmTimeWidget::setDateTime(const DateTime& dt) { if (dt.date().isValid()) { mTimeEdit->setValue(dt.time()); mDateEdit->setDate(dt.date()); dateTimeChanged(); // update the delay time edit box } else { mTimeEdit->setValid(false); mDateEdit->setInvalid(); mDelayTimeEdit->setValid(false); } if (mAnyTimeCheckBox) { bool anyTime = dt.isDateOnly(); if (anyTime) mAnyTimeAllowed = true; mAnyTimeCheckBox->setChecked(anyTime); setAnyTime(); } } /****************************************************************************** * Set the minimum date/time to track the current time. */ void AlarmTimeWidget::setMinDateTimeIsCurrent() { mMinDateTimeIsNow = true; mMinDateTime = TQDateTime(); TQDateTime now = TQDateTime::currentDateTime(); mDateEdit->setMinDate(now.date()); setMaxMinTimeIf(now); } /****************************************************************************** * Set the minimum date/time, adjusting the entered date/time if necessary. * If 'dt' is invalid, any current minimum date/time is cleared. */ void AlarmTimeWidget::setMinDateTime(const TQDateTime& dt) { mMinDateTimeIsNow = false; mMinDateTime = dt; mDateEdit->setMinDate(dt.date()); setMaxMinTimeIf(TQDateTime::currentDateTime()); } /****************************************************************************** * Set the maximum date/time, adjusting the entered date/time if necessary. * If 'dt' is invalid, any current maximum date/time is cleared. */ void AlarmTimeWidget::setMaxDateTime(const DateTime& dt) { mPastMax = false; if (dt.isValid() && dt.isDateOnly()) mMaxDateTime = dt.dateTime().addSecs(24*3600 - 60); else mMaxDateTime = dt.dateTime(); mDateEdit->setMaxDate(mMaxDateTime.date()); TQDateTime now = TQDateTime::currentDateTime(); setMaxMinTimeIf(now); setMaxDelayTime(now); } /****************************************************************************** * If the minimum and maximum date/times fall on the same date, set the minimum * and maximum times in the time edit box. */ void AlarmTimeWidget::setMaxMinTimeIf(const TQDateTime& now) { int mint = 0; TQTime maxt = time_23_59; mMinMaxTimeSet = false; if (mMaxDateTime.isValid()) { bool set = true; TQDateTime minDT; if (mMinDateTimeIsNow) minDT = now.addSecs(60); else if (mMinDateTime.isValid()) minDT = mMinDateTime; else set = false; if (set && mMaxDateTime.date() == minDT.date()) { // The minimum and maximum times are on the same date, so // constrain the time value. mint = minDT.time().hour()*60 + minDT.time().minute(); maxt = mMaxDateTime.time(); mMinMaxTimeSet = true; } } mTimeEdit->setMinValue(mint); mTimeEdit->setMaxValue(maxt); mTimeEdit->setWrapping(!mint && maxt == time_23_59); } /****************************************************************************** * Set the maximum value for the delay time edit box, depending on the maximum * value for the date/time. */ void AlarmTimeWidget::setMaxDelayTime(const TQDateTime& now) { int maxVal = maxDelayTime; if (mMaxDateTime.isValid()) { if (now.date().daysTo(mMaxDateTime.date()) < 100) // avoid possible 32-bit overflow on secsTo() { TQDateTime dt(now.date(), TQTime(now.time().hour(), now.time().minute(), 0)); // round down to nearest minute maxVal = dt.secsTo(mMaxDateTime) / 60; if (maxVal > maxDelayTime) maxVal = maxDelayTime; } } mDelayTimeEdit->setMaxValue(maxVal); } /****************************************************************************** * Set the status for whether a time is specified, or just a date. */ void AlarmTimeWidget::setAnyTime() { int old = mAnyTime; mAnyTime = (mAtTimeRadio->isOn() && mAnyTimeAllowed && mAnyTimeCheckBox && mAnyTimeCheckBox->isChecked()) ? 1 : 0; if (mAnyTime != old) emit anyTimeToggled(mAnyTime); } /****************************************************************************** * Enable/disable the "any time" checkbox. */ void AlarmTimeWidget::enableAnyTime(bool enable) { if (mAnyTimeCheckBox) { mAnyTimeAllowed = enable; bool at = mAtTimeRadio->isOn(); mAnyTimeCheckBox->setEnabled(enable && at); if (at) mTimeEdit->setEnabled(!enable || !mAnyTimeCheckBox->isChecked()); setAnyTime(); } } /****************************************************************************** * Called every minute to update the alarm time data entry fields. * If the maximum date/time has been reached, a 'pastMax()' signal is emitted. */ void AlarmTimeWidget::slotTimer() { TQDateTime now; if (mMinDateTimeIsNow) { // Make sure that the minimum date is updated when the day changes now = TQDateTime::currentDateTime(); mDateEdit->setMinDate(now.date()); } if (mMaxDateTime.isValid()) { if (!now.isValid()) now = TQDateTime::currentDateTime(); if (!mPastMax) { // Check whether the maximum date/time has now been reached if (now.date() >= mMaxDateTime.date()) { // The current date has reached or has passed the maximum date if (now.date() > mMaxDateTime.date() || (!mAnyTime && now.time() > mTimeEdit->maxTime())) { mPastMax = true; emit pastMax(); } else if (mMinDateTimeIsNow && !mMinMaxTimeSet) { // The minimum date/time tracks the clock, so set the minimum // and maximum times setMaxMinTimeIf(now); } } } setMaxDelayTime(now); } if (mAtTimeRadio->isOn()) dateTimeChanged(); else delayTimeChanged(mDelayTimeEdit->value()); } /****************************************************************************** * Called when the At or After time radio button states have been set. * Updates the appropriate edit box. */ void AlarmTimeWidget::slotButtonSet(int) { bool at = mAtTimeRadio->isOn(); mDateEdit->setEnabled(at); mTimeEdit->setEnabled(at && (!mAnyTimeAllowed || !mAnyTimeCheckBox || !mAnyTimeCheckBox->isChecked())); if (mAnyTimeCheckBox) mAnyTimeCheckBox->setEnabled(at && mAnyTimeAllowed); // Ensure that the value of the delay edit box is > 0. TQDateTime dt(mDateEdit->date(), mTimeEdit->time()); int minutes = (TQDateTime::currentDateTime().secsTo(dt) + 59) / 60; if (minutes <= 0) mDelayTimeEdit->setValid(true); mDelayTimeEdit->setEnabled(!at); setAnyTime(); } /****************************************************************************** * Called after the mAnyTimeCheckBox checkbox has been toggled. */ void AlarmTimeWidget::slotAnyTimeToggled(bool on) { mTimeEdit->setEnabled((!mAnyTimeAllowed || !on) && mAtTimeRadio->isOn()); setAnyTime(); } /****************************************************************************** * Called when the date or time edit box values have changed. * Updates the time delay edit box accordingly. */ void AlarmTimeWidget::dateTimeChanged() { TQDateTime dt(mDateEdit->date(), mTimeEdit->time()); int minutes = (TQDateTime::currentDateTime().secsTo(dt) + 59) / 60; bool blocked = mDelayTimeEdit->signalsBlocked(); mDelayTimeEdit->blockSignals(true); // prevent infinite recursion between here and delayTimeChanged() if (minutes <= 0 || minutes > mDelayTimeEdit->maxValue()) mDelayTimeEdit->setValid(false); else mDelayTimeEdit->setValue(minutes); mDelayTimeEdit->blockSignals(blocked); } /****************************************************************************** * Called when the delay time edit box value has changed. * Updates the Date and Time edit boxes accordingly. */ void AlarmTimeWidget::delayTimeChanged(int minutes) { if (mDelayTimeEdit->isValid()) { TQDateTime dt = TQDateTime::currentDateTime().addSecs(minutes * 60); bool blockedT = mTimeEdit->signalsBlocked(); bool blockedD = mDateEdit->signalsBlocked(); mTimeEdit->blockSignals(true); // prevent infinite recursion between here and dateTimeChanged() mDateEdit->blockSignals(true); mTimeEdit->setValue(dt.time()); mDateEdit->setDate(dt.date()); mTimeEdit->blockSignals(blockedT); mDateEdit->blockSignals(blockedD); } }