kalarm

alarmtimewidget.cpp
1/*
2 * alarmtimewidget.cpp - alarm date/time entry widget
3 * Program: kalarm
4 * Copyright © 2001-2007 by David Jarvie <software@astrojar.org.uk>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 */
20
21#include "kalarm.h"
22
23#include <tqlayout.h>
24#include <tqgroupbox.h>
25#include <tqhbox.h>
26#include <tqpushbutton.h>
27#include <tqwhatsthis.h>
28
29#include <kdialog.h>
30#include <tdemessagebox.h>
31#include <tdelocale.h>
32
33#include "checkbox.h"
34#include "dateedit.h"
35#include "datetime.h"
36#include "radiobutton.h"
37#include "synchtimer.h"
38#include "timeedit.h"
39#include "timespinbox.h"
40#include "alarmtimewidget.moc"
41
42static const TQTime time_23_59(23, 59);
43
44
45const int AlarmTimeWidget::maxDelayTime = 999*60 + 59; // < 1000 hours
46
47TQString AlarmTimeWidget::i18n_w_TimeFromNow() { return i18n("Time from no&w:"); }
48TQString AlarmTimeWidget::i18n_TimeAfterPeriod()
49{
50 return i18n("Enter the length of time (in hours and minutes) after "
51 "the current time to schedule the alarm.");
52}
53
54
55/******************************************************************************
56* Construct a widget with a group box and title.
57*/
58AlarmTimeWidget::AlarmTimeWidget(const TQString& groupBoxTitle, int mode, TQWidget* parent, const char* name)
59 : ButtonGroup(groupBoxTitle, parent, name),
60 mMinDateTimeIsNow(false),
61 mPastMax(false),
62 mMinMaxTimeSet(false)
63{
64 init(mode);
65}
66
67/******************************************************************************
68* Construct a widget without a group box or title.
69*/
70AlarmTimeWidget::AlarmTimeWidget(int mode, TQWidget* parent, const char* name)
71 : ButtonGroup(parent, name),
72 mMinDateTimeIsNow(false),
73 mPastMax(false),
74 mMinMaxTimeSet(false)
75{
76 setFrameStyle(TQFrame::NoFrame);
77 init(mode);
78}
79
80void AlarmTimeWidget::init(int mode)
81{
82 static const TQString recurText = i18n("For a simple repetition, enter the date/time of the first occurrence.\n"
83 "If a recurrence is configured, the start date/time will be adjusted "
84 "to the first recurrence on or after the entered date/time.");
85
86 connect(this, TQ_SIGNAL(buttonSet(int)), TQ_SLOT(slotButtonSet(int)));
87 TQBoxLayout* topLayout = new TQVBoxLayout(this, 0, KDialog::spacingHint());
88 if (!title().isEmpty())
89 {
90 topLayout->setMargin(KDialog::marginHint());
91 topLayout->addSpacing(fontMetrics().lineSpacing()/2);
92 }
93
94 // At time radio button/label
95 mAtTimeRadio = new RadioButton(((mode & DEFER_TIME) ? i18n("&Defer to date/time:") : i18n("At &date/time:")), this, "atTimeRadio");
96 mAtTimeRadio->setFixedSize(mAtTimeRadio->sizeHint());
97 TQWhatsThis::add(mAtTimeRadio,
98 ((mode & DEFER_TIME) ? i18n("Reschedule the alarm to the specified date and time.")
99 : i18n("Schedule the alarm at the specified date and time.")));
100
101 // Date edit box
102 mDateEdit = new DateEdit(this);
103 mDateEdit->setFixedSize(mDateEdit->sizeHint());
104 connect(mDateEdit, TQ_SIGNAL(dateEntered(const TQDate&)), TQ_SLOT(dateTimeChanged()));
105 static const TQString enterDateText = i18n("Enter the date to schedule the alarm.");
106 TQWhatsThis::add(mDateEdit, ((mode & DEFER_TIME) ? enterDateText
107 : TQString("%1\n%2").arg(enterDateText).arg(recurText)));
108 mAtTimeRadio->setFocusWidget(mDateEdit);
109
110 // Time edit box and Any time checkbox
111 TQHBox* timeBox = new TQHBox(this);
112 timeBox->setSpacing(2*KDialog::spacingHint());
113 mTimeEdit = new TimeEdit(timeBox);
114 mTimeEdit->setFixedSize(mTimeEdit->sizeHint());
115 connect(mTimeEdit, TQ_SIGNAL(valueChanged(int)), TQ_SLOT(dateTimeChanged()));
116 static const TQString enterTimeText = i18n("Enter the time to schedule the alarm.");
117 TQWhatsThis::add(mTimeEdit,
118 ((mode & DEFER_TIME) ? TQString("%1\n\n%2").arg(enterTimeText).arg(TimeSpinBox::shiftWhatsThis())
119 : TQString("%1\n%2\n\n%3").arg(enterTimeText).arg(recurText).arg(TimeSpinBox::shiftWhatsThis())));
120
121 mAnyTime = -1; // current status is uninitialised
122 if (mode & DEFER_TIME)
123 {
124 mAnyTimeAllowed = false;
125 mAnyTimeCheckBox = 0;
126 }
127 else
128 {
129 mAnyTimeAllowed = true;
130 mAnyTimeCheckBox = new CheckBox(i18n("An&y time"), timeBox);
131 mAnyTimeCheckBox->setFixedSize(mAnyTimeCheckBox->sizeHint());
132 connect(mAnyTimeCheckBox, TQ_SIGNAL(toggled(bool)), TQ_SLOT(slotAnyTimeToggled(bool)));
133 TQWhatsThis::add(mAnyTimeCheckBox, i18n("Schedule the alarm for any time during the day"));
134 }
135
136 // 'Time from now' radio button/label
137 mAfterTimeRadio = new RadioButton(((mode & DEFER_TIME) ? i18n("Defer for time &interval:") : i18n_w_TimeFromNow()),
138 this, "afterTimeRadio");
139 mAfterTimeRadio->setFixedSize(mAfterTimeRadio->sizeHint());
140 TQWhatsThis::add(mAfterTimeRadio,
141 ((mode & DEFER_TIME) ? i18n("Reschedule the alarm for the specified time interval after now.")
142 : i18n("Schedule the alarm after the specified time interval from now.")));
143
144 // Delay time spin box
145 mDelayTimeEdit = new TimeSpinBox(1, maxDelayTime, this);
146 mDelayTimeEdit->setValue(maxDelayTime);
147 mDelayTimeEdit->setFixedSize(mDelayTimeEdit->sizeHint());
148 connect(mDelayTimeEdit, TQ_SIGNAL(valueChanged(int)), TQ_SLOT(delayTimeChanged(int)));
149 TQWhatsThis::add(mDelayTimeEdit,
150 ((mode & DEFER_TIME) ? TQString("%1\n\n%2").arg(i18n_TimeAfterPeriod()).arg(TimeSpinBox::shiftWhatsThis())
151 : TQString("%1\n%2\n\n%3").arg(i18n_TimeAfterPeriod()).arg(recurText).arg(TimeSpinBox::shiftWhatsThis())));
152 mAfterTimeRadio->setFocusWidget(mDelayTimeEdit);
153
154 // Set up the layout, either narrow or wide
155 if (mode & NARROW)
156 {
157 TQGridLayout* grid = new TQGridLayout(topLayout, 2, 2, KDialog::spacingHint());
158 grid->addWidget(mAtTimeRadio, 0, 0);
159 grid->addWidget(mDateEdit, 0, 1, TQt::AlignAuto);
160 grid->addWidget(timeBox, 1, 1, TQt::AlignAuto);
161 grid->setColStretch(2, 1);
162 topLayout->addStretch();
163 TQBoxLayout* layout = new TQHBoxLayout(topLayout, KDialog::spacingHint());
164 layout->addWidget(mAfterTimeRadio);
165 layout->addWidget(mDelayTimeEdit);
166 layout->addStretch();
167 }
168 else
169 {
170 TQGridLayout* grid = new TQGridLayout(topLayout, 2, 3, KDialog::spacingHint());
171 grid->addWidget(mAtTimeRadio, 0, 0, TQt::AlignAuto);
172 grid->addWidget(mDateEdit, 0, 1, TQt::AlignAuto);
173 grid->addWidget(timeBox, 0, 2, TQt::AlignAuto);
174 grid->setRowStretch(0, 1);
175 grid->addWidget(mAfterTimeRadio, 1, 0, TQt::AlignAuto);
176 grid->addWidget(mDelayTimeEdit, 1, 1, TQt::AlignAuto);
177 grid->setColStretch(3, 1);
178 topLayout->addStretch();
179 }
180
181 // Initialise the radio button statuses
182 setButton(id(mAtTimeRadio));
183
184 // Timeout every minute to update alarm time fields.
185 MinuteTimer::connect(this, TQ_SLOT(slotTimer()));
186}
187
188/******************************************************************************
189* Set or clear read-only status for the controls
190*/
191void AlarmTimeWidget::setReadOnly(bool ro)
192{
193 mAtTimeRadio->setReadOnly(ro);
194 mDateEdit->setReadOnly(ro);
195 mTimeEdit->setReadOnly(ro);
196 if (mAnyTimeCheckBox)
197 mAnyTimeCheckBox->setReadOnly(ro);
198 mAfterTimeRadio->setReadOnly(ro);
199 mDelayTimeEdit->setReadOnly(ro);
200}
201
202/******************************************************************************
203* Select the "Time from now" radio button.
204*/
205void AlarmTimeWidget::selectTimeFromNow(int minutes)
206{
207 mAfterTimeRadio->setChecked(true);
208 slotButtonSet(1);
209 if (minutes > 0)
210 mDelayTimeEdit->setValue(minutes);
211}
212
213/******************************************************************************
214* Fetch the entered date/time.
215* If 'checkExpired' is true and the entered value <= current time, an error occurs.
216* If 'minsFromNow' is non-null, it is set to the number of minutes' delay selected,
217* or to zero if a date/time was entered.
218* In this case, if 'showErrorMessage' is true, output an error message.
219* 'errorWidget' if non-null, is set to point to the widget containing the error.
220* Reply = invalid date/time if error.
221*/
222DateTime AlarmTimeWidget::getDateTime(int* minsFromNow, bool checkExpired, bool showErrorMessage, TQWidget** errorWidget) const
223{
224 if (minsFromNow)
225 *minsFromNow = 0;
226 if (errorWidget)
227 *errorWidget = 0;
228 TQTime nowt = TQTime::currentTime();
229 TQDateTime now(TQDate::currentDate(), TQTime(nowt.hour(), nowt.minute()));
230 if (mAtTimeRadio->isOn())
231 {
232 bool anyTime = mAnyTimeAllowed && mAnyTimeCheckBox && mAnyTimeCheckBox->isChecked();
233 if (!mDateEdit->isValid() || !mTimeEdit->isValid())
234 {
235 // The date and/or time is invalid
236 if (!mDateEdit->isValid())
237 {
238 if (showErrorMessage)
239 KMessageBox::sorry(const_cast<AlarmTimeWidget*>(this), i18n("Invalid date"));
240 if (errorWidget)
241 *errorWidget = mDateEdit;
242 }
243 else
244 {
245 if (showErrorMessage)
246 KMessageBox::sorry(const_cast<AlarmTimeWidget*>(this), i18n("Invalid time"));
247 if (errorWidget)
248 *errorWidget = mTimeEdit;
249 }
250 return DateTime();
251 }
252
253 DateTime result;
254 if (anyTime)
255 {
256 result = mDateEdit->date();
257 if (checkExpired && result.date() < now.date())
258 {
259 if (showErrorMessage)
260 KMessageBox::sorry(const_cast<AlarmTimeWidget*>(this), i18n("Alarm date has already expired"));
261 if (errorWidget)
262 *errorWidget = mDateEdit;
263 return DateTime();
264 }
265 }
266 else
267 {
268 result.set(mDateEdit->date(), mTimeEdit->time());
269 if (checkExpired && result <= TQDateTime(now.addSecs(1)))
270 {
271 if (showErrorMessage)
272 KMessageBox::sorry(const_cast<AlarmTimeWidget*>(this), i18n("Alarm time has already expired"));
273 if (errorWidget)
274 *errorWidget = mTimeEdit;
275 return DateTime();
276 }
277 }
278 return result;
279 }
280 else
281 {
282 if (!mDelayTimeEdit->isValid())
283 {
284 if (showErrorMessage)
285 KMessageBox::sorry(const_cast<AlarmTimeWidget*>(this), i18n("Invalid time"));
286 if (errorWidget)
287 *errorWidget = mDelayTimeEdit;
288 return DateTime();
289 }
290 int delayMins = mDelayTimeEdit->value();
291 if (minsFromNow)
292 *minsFromNow = delayMins;
293 return TQDateTime(now.addSecs(delayMins * 60));
294 }
295}
296
297/******************************************************************************
298* Set the date/time.
299*/
300void AlarmTimeWidget::setDateTime(const DateTime& dt)
301{
302 if (dt.date().isValid())
303 {
304 mTimeEdit->setValue(dt.time());
305 mDateEdit->setDate(dt.date());
306 dateTimeChanged(); // update the delay time edit box
307 }
308 else
309 {
310 mTimeEdit->setValid(false);
311 mDateEdit->setInvalid();
312 mDelayTimeEdit->setValid(false);
313 }
314 if (mAnyTimeCheckBox)
315 {
316 bool anyTime = dt.isDateOnly();
317 if (anyTime)
318 mAnyTimeAllowed = true;
319 mAnyTimeCheckBox->setChecked(anyTime);
320 setAnyTime();
321 }
322}
323
324/******************************************************************************
325* Set the minimum date/time to track the current time.
326*/
327void AlarmTimeWidget::setMinDateTimeIsCurrent()
328{
329 mMinDateTimeIsNow = true;
330 mMinDateTime = TQDateTime();
331 TQDateTime now = TQDateTime::currentDateTime();
332 mDateEdit->setMinDate(now.date());
333 setMaxMinTimeIf(now);
334}
335
336/******************************************************************************
337* Set the minimum date/time, adjusting the entered date/time if necessary.
338* If 'dt' is invalid, any current minimum date/time is cleared.
339*/
340void AlarmTimeWidget::setMinDateTime(const TQDateTime& dt)
341{
342 mMinDateTimeIsNow = false;
343 mMinDateTime = dt;
344 mDateEdit->setMinDate(dt.date());
345 setMaxMinTimeIf(TQDateTime::currentDateTime());
346}
347
348/******************************************************************************
349* Set the maximum date/time, adjusting the entered date/time if necessary.
350* If 'dt' is invalid, any current maximum date/time is cleared.
351*/
352void AlarmTimeWidget::setMaxDateTime(const DateTime& dt)
353{
354 mPastMax = false;
355 if (dt.isValid() && dt.isDateOnly())
356 mMaxDateTime = dt.dateTime().addSecs(24*3600 - 60);
357 else
358 mMaxDateTime = dt.dateTime();
359 mDateEdit->setMaxDate(mMaxDateTime.date());
360 TQDateTime now = TQDateTime::currentDateTime();
361 setMaxMinTimeIf(now);
362 setMaxDelayTime(now);
363}
364
365/******************************************************************************
366* If the minimum and maximum date/times fall on the same date, set the minimum
367* and maximum times in the time edit box.
368*/
369void AlarmTimeWidget::setMaxMinTimeIf(const TQDateTime& now)
370{
371 int mint = 0;
372 TQTime maxt = time_23_59;
373 mMinMaxTimeSet = false;
374 if (mMaxDateTime.isValid())
375 {
376 bool set = true;
377 TQDateTime minDT;
378 if (mMinDateTimeIsNow)
379 minDT = now.addSecs(60);
380 else if (mMinDateTime.isValid())
381 minDT = mMinDateTime;
382 else
383 set = false;
384 if (set && mMaxDateTime.date() == minDT.date())
385 {
386 // The minimum and maximum times are on the same date, so
387 // constrain the time value.
388 mint = minDT.time().hour()*60 + minDT.time().minute();
389 maxt = mMaxDateTime.time();
390 mMinMaxTimeSet = true;
391 }
392 }
393 mTimeEdit->setMinValue(mint);
394 mTimeEdit->setMaxValue(maxt);
395 mTimeEdit->setWrapping(!mint && maxt == time_23_59);
396}
397
398/******************************************************************************
399* Set the maximum value for the delay time edit box, depending on the maximum
400* value for the date/time.
401*/
402void AlarmTimeWidget::setMaxDelayTime(const TQDateTime& now)
403{
404 int maxVal = maxDelayTime;
405 if (mMaxDateTime.isValid())
406 {
407 if (now.date().daysTo(mMaxDateTime.date()) < 100) // avoid possible 32-bit overflow on secsTo()
408 {
409 TQDateTime dt(now.date(), TQTime(now.time().hour(), now.time().minute(), 0)); // round down to nearest minute
410 maxVal = dt.secsTo(mMaxDateTime) / 60;
411 if (maxVal > maxDelayTime)
412 maxVal = maxDelayTime;
413 }
414 }
415 mDelayTimeEdit->setMaxValue(maxVal);
416}
417
418/******************************************************************************
419* Set the status for whether a time is specified, or just a date.
420*/
421void AlarmTimeWidget::setAnyTime()
422{
423 int old = mAnyTime;
424 mAnyTime = (mAtTimeRadio->isOn() && mAnyTimeAllowed && mAnyTimeCheckBox && mAnyTimeCheckBox->isChecked()) ? 1 : 0;
425 if (mAnyTime != old)
426 emit anyTimeToggled(mAnyTime);
427}
428
429/******************************************************************************
430* Enable/disable the "any time" checkbox.
431*/
432void AlarmTimeWidget::enableAnyTime(bool enable)
433{
434 if (mAnyTimeCheckBox)
435 {
436 mAnyTimeAllowed = enable;
437 bool at = mAtTimeRadio->isOn();
438 mAnyTimeCheckBox->setEnabled(enable && at);
439 if (at)
440 mTimeEdit->setEnabled(!enable || !mAnyTimeCheckBox->isChecked());
441 setAnyTime();
442 }
443}
444
445/******************************************************************************
446* Called every minute to update the alarm time data entry fields.
447* If the maximum date/time has been reached, a 'pastMax()' signal is emitted.
448*/
449void AlarmTimeWidget::slotTimer()
450{
451 TQDateTime now;
452 if (mMinDateTimeIsNow)
453 {
454 // Make sure that the minimum date is updated when the day changes
455 now = TQDateTime::currentDateTime();
456 mDateEdit->setMinDate(now.date());
457 }
458 if (mMaxDateTime.isValid())
459 {
460 if (!now.isValid())
461 now = TQDateTime::currentDateTime();
462 if (!mPastMax)
463 {
464 // Check whether the maximum date/time has now been reached
465 if (now.date() >= mMaxDateTime.date())
466 {
467 // The current date has reached or has passed the maximum date
468 if (now.date() > mMaxDateTime.date()
469 || (!mAnyTime && now.time() > mTimeEdit->maxTime()))
470 {
471 mPastMax = true;
472 emit pastMax();
473 }
474 else if (mMinDateTimeIsNow && !mMinMaxTimeSet)
475 {
476 // The minimum date/time tracks the clock, so set the minimum
477 // and maximum times
478 setMaxMinTimeIf(now);
479 }
480 }
481 }
482 setMaxDelayTime(now);
483 }
484
485 if (mAtTimeRadio->isOn())
486 dateTimeChanged();
487 else
488 delayTimeChanged(mDelayTimeEdit->value());
489}
490
491
492/******************************************************************************
493* Called when the At or After time radio button states have been set.
494* Updates the appropriate edit box.
495*/
496void AlarmTimeWidget::slotButtonSet(int)
497{
498 bool at = mAtTimeRadio->isOn();
499 mDateEdit->setEnabled(at);
500 mTimeEdit->setEnabled(at && (!mAnyTimeAllowed || !mAnyTimeCheckBox || !mAnyTimeCheckBox->isChecked()));
501 if (mAnyTimeCheckBox)
502 mAnyTimeCheckBox->setEnabled(at && mAnyTimeAllowed);
503 // Ensure that the value of the delay edit box is > 0.
504 TQDateTime dt(mDateEdit->date(), mTimeEdit->time());
505 int minutes = (TQDateTime::currentDateTime().secsTo(dt) + 59) / 60;
506 if (minutes <= 0)
507 mDelayTimeEdit->setValid(true);
508 mDelayTimeEdit->setEnabled(!at);
509 setAnyTime();
510}
511
512/******************************************************************************
513* Called after the mAnyTimeCheckBox checkbox has been toggled.
514*/
515void AlarmTimeWidget::slotAnyTimeToggled(bool on)
516{
517 mTimeEdit->setEnabled((!mAnyTimeAllowed || !on) && mAtTimeRadio->isOn());
518 setAnyTime();
519}
520
521/******************************************************************************
522* Called when the date or time edit box values have changed.
523* Updates the time delay edit box accordingly.
524*/
525void AlarmTimeWidget::dateTimeChanged()
526{
527 TQDateTime dt(mDateEdit->date(), mTimeEdit->time());
528 int minutes = (TQDateTime::currentDateTime().secsTo(dt) + 59) / 60;
529 bool blocked = mDelayTimeEdit->signalsBlocked();
530 mDelayTimeEdit->blockSignals(true); // prevent infinite recursion between here and delayTimeChanged()
531 if (minutes <= 0 || minutes > mDelayTimeEdit->maxValue())
532 mDelayTimeEdit->setValid(false);
533 else
534 mDelayTimeEdit->setValue(minutes);
535 mDelayTimeEdit->blockSignals(blocked);
536}
537
538/******************************************************************************
539* Called when the delay time edit box value has changed.
540* Updates the Date and Time edit boxes accordingly.
541*/
542void AlarmTimeWidget::delayTimeChanged(int minutes)
543{
544 if (mDelayTimeEdit->isValid())
545 {
546 TQDateTime dt = TQDateTime::currentDateTime().addSecs(minutes * 60);
547 bool blockedT = mTimeEdit->signalsBlocked();
548 bool blockedD = mDateEdit->signalsBlocked();
549 mTimeEdit->blockSignals(true); // prevent infinite recursion between here and dateTimeChanged()
550 mDateEdit->blockSignals(true);
551 mTimeEdit->setValue(dt.time());
552 mDateEdit->setDate(dt.date());
553 mTimeEdit->blockSignals(blockedT);
554 mDateEdit->blockSignals(blockedD);
555 }
556}