summaryrefslogtreecommitdiffstats
path: root/kalarm/alarmtimewidget.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kalarm/alarmtimewidget.cpp')
-rw-r--r--kalarm/alarmtimewidget.cpp556
1 files changed, 556 insertions, 0 deletions
diff --git a/kalarm/alarmtimewidget.cpp b/kalarm/alarmtimewidget.cpp
new file mode 100644
index 00000000..1e7e777b
--- /dev/null
+++ b/kalarm/alarmtimewidget.cpp
@@ -0,0 +1,556 @@
+/*
+ * alarmtimewidget.cpp - alarm date/time entry widget
+ * Program: kalarm
+ * Copyright © 2001-2007 by David Jarvie <software@astrojar.org.uk>
+ *
+ * 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 <qlayout.h>
+#include <qgroupbox.h>
+#include <qhbox.h>
+#include <qpushbutton.h>
+#include <qwhatsthis.h>
+
+#include <kdialog.h>
+#include <kmessagebox.h>
+#include <klocale.h>
+
+#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 QTime time_23_59(23, 59);
+
+
+const int AlarmTimeWidget::maxDelayTime = 999*60 + 59; // < 1000 hours
+
+QString AlarmTimeWidget::i18n_w_TimeFromNow() { return i18n("Time from no&w:"); }
+QString 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 QString& groupBoxTitle, int mode, QWidget* 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, QWidget* parent, const char* name)
+ : ButtonGroup(parent, name),
+ mMinDateTimeIsNow(false),
+ mPastMax(false),
+ mMinMaxTimeSet(false)
+{
+ setFrameStyle(QFrame::NoFrame);
+ init(mode);
+}
+
+void AlarmTimeWidget::init(int mode)
+{
+ static const QString 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, SIGNAL(buttonSet(int)), SLOT(slotButtonSet(int)));
+ QBoxLayout* topLayout = new QVBoxLayout(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());
+ QWhatsThis::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, SIGNAL(dateEntered(const QDate&)), SLOT(dateTimeChanged()));
+ static const QString enterDateText = i18n("Enter the date to schedule the alarm.");
+ QWhatsThis::add(mDateEdit, ((mode & DEFER_TIME) ? enterDateText
+ : QString("%1\n%2").arg(enterDateText).arg(recurText)));
+ mAtTimeRadio->setFocusWidget(mDateEdit);
+
+ // Time edit box and Any time checkbox
+ QHBox* timeBox = new QHBox(this);
+ timeBox->setSpacing(2*KDialog::spacingHint());
+ mTimeEdit = new TimeEdit(timeBox);
+ mTimeEdit->setFixedSize(mTimeEdit->sizeHint());
+ connect(mTimeEdit, SIGNAL(valueChanged(int)), SLOT(dateTimeChanged()));
+ static const QString enterTimeText = i18n("Enter the time to schedule the alarm.");
+ QWhatsThis::add(mTimeEdit,
+ ((mode & DEFER_TIME) ? QString("%1\n\n%2").arg(enterTimeText).arg(TimeSpinBox::shiftWhatsThis())
+ : QString("%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, SIGNAL(toggled(bool)), SLOT(slotAnyTimeToggled(bool)));
+ QWhatsThis::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());
+ QWhatsThis::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, SIGNAL(valueChanged(int)), SLOT(delayTimeChanged(int)));
+ QWhatsThis::add(mDelayTimeEdit,
+ ((mode & DEFER_TIME) ? QString("%1\n\n%2").arg(i18n_TimeAfterPeriod()).arg(TimeSpinBox::shiftWhatsThis())
+ : QString("%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)
+ {
+ QGridLayout* grid = new QGridLayout(topLayout, 2, 2, KDialog::spacingHint());
+ grid->addWidget(mAtTimeRadio, 0, 0);
+ grid->addWidget(mDateEdit, 0, 1, Qt::AlignAuto);
+ grid->addWidget(timeBox, 1, 1, Qt::AlignAuto);
+ grid->setColStretch(2, 1);
+ topLayout->addStretch();
+ QBoxLayout* layout = new QHBoxLayout(topLayout, KDialog::spacingHint());
+ layout->addWidget(mAfterTimeRadio);
+ layout->addWidget(mDelayTimeEdit);
+ layout->addStretch();
+ }
+ else
+ {
+ QGridLayout* grid = new QGridLayout(topLayout, 2, 3, KDialog::spacingHint());
+ grid->addWidget(mAtTimeRadio, 0, 0, Qt::AlignAuto);
+ grid->addWidget(mDateEdit, 0, 1, Qt::AlignAuto);
+ grid->addWidget(timeBox, 0, 2, Qt::AlignAuto);
+ grid->setRowStretch(0, 1);
+ grid->addWidget(mAfterTimeRadio, 1, 0, Qt::AlignAuto);
+ grid->addWidget(mDelayTimeEdit, 1, 1, Qt::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(this, 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, QWidget** errorWidget) const
+{
+ if (minsFromNow)
+ *minsFromNow = 0;
+ if (errorWidget)
+ *errorWidget = 0;
+ QTime nowt = QTime::currentTime();
+ QDateTime now(QDate::currentDate(), QTime(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<AlarmTimeWidget*>(this), i18n("Invalid date"));
+ if (errorWidget)
+ *errorWidget = mDateEdit;
+ }
+ else
+ {
+ if (showErrorMessage)
+ KMessageBox::sorry(const_cast<AlarmTimeWidget*>(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<AlarmTimeWidget*>(this), i18n("Alarm date has already expired"));
+ if (errorWidget)
+ *errorWidget = mDateEdit;
+ return DateTime();
+ }
+ }
+ else
+ {
+ result.set(mDateEdit->date(), mTimeEdit->time());
+ if (checkExpired && result <= now.addSecs(1))
+ {
+ if (showErrorMessage)
+ KMessageBox::sorry(const_cast<AlarmTimeWidget*>(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<AlarmTimeWidget*>(this), i18n("Invalid time"));
+ if (errorWidget)
+ *errorWidget = mDelayTimeEdit;
+ return DateTime();
+ }
+ int delayMins = mDelayTimeEdit->value();
+ if (minsFromNow)
+ *minsFromNow = delayMins;
+ return 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 = QDateTime();
+ QDateTime now = QDateTime::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 QDateTime& dt)
+{
+ mMinDateTimeIsNow = false;
+ mMinDateTime = dt;
+ mDateEdit->setMinDate(dt.date());
+ setMaxMinTimeIf(QDateTime::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());
+ QDateTime now = QDateTime::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 QDateTime& now)
+{
+ int mint = 0;
+ QTime maxt = time_23_59;
+ mMinMaxTimeSet = false;
+ if (mMaxDateTime.isValid())
+ {
+ bool set = true;
+ QDateTime 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 QDateTime& now)
+{
+ int maxVal = maxDelayTime;
+ if (mMaxDateTime.isValid())
+ {
+ if (now.date().daysTo(mMaxDateTime.date()) < 100) // avoid possible 32-bit overflow on secsTo()
+ {
+ QDateTime dt(now.date(), QTime(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()
+{
+ QDateTime now;
+ if (mMinDateTimeIsNow)
+ {
+ // Make sure that the minimum date is updated when the day changes
+ now = QDateTime::currentDateTime();
+ mDateEdit->setMinDate(now.date());
+ }
+ if (mMaxDateTime.isValid())
+ {
+ if (!now.isValid())
+ now = QDateTime::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.
+ QDateTime dt(mDateEdit->date(), mTimeEdit->time());
+ int minutes = (QDateTime::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()
+{
+ QDateTime dt(mDateEdit->date(), mTimeEdit->time());
+ int minutes = (QDateTime::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())
+ {
+ QDateTime dt = QDateTime::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);
+ }
+}