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 
42 static const TQTime time_23_59(23, 59);
43 
44 
45 const int AlarmTimeWidget::maxDelayTime = 999*60 + 59; // < 1000 hours
46 
47 TQString AlarmTimeWidget::i18n_w_TimeFromNow() { return i18n("Time from no&w:"); }
48 TQString 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 */
58 AlarmTimeWidget::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 */
70 AlarmTimeWidget::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 
80 void 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 */
191 void 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 */
205 void 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 */
222 DateTime 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 */
300 void 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 */
327 void 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 */
340 void 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 */
352 void 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 */
369 void 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 */
402 void 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 */
421 void 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 */
432 void 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 */
449 void 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 */
496 void 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 */
515 void 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 */
525 void 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 */
542 void 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 }