kalarm/lib

timespinbox.cpp
1 /*
2  * timespinbox.cpp - time spinbox widget
3  * Program: kalarm
4  * Copyright © 2001-2004,2007,2008 by David Jarvie <djarvie@kde.org>
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 <tqvalidator.h>
24 #include <tqlineedit.h>
25 #include <klocale.h>
26 
27 #include "timespinbox.moc"
28 
29 
30 class TimeSpinBox::TimeValidator : public TQValidator
31 {
32  public:
33  TimeValidator(int minMin, int maxMin, TQWidget* parent, const char* name = 0)
34  : TQValidator(TQT_TQOBJECT(parent), name),
35  minMinute(minMin), maxMinute(maxMin), m12Hour(false), mPm(false) { }
36  virtual State validate(TQString&, int&) const;
37  int minMinute, maxMinute;
38  bool m12Hour;
39  bool mPm;
40 };
41 
42 
43 /*=============================================================================
44 = Class TimeSpinBox
45 = This is a spin box displaying a time in the format hh:mm, with a pair of
46 = spin buttons for each of the hour and minute values.
47 = It can operate in 3 modes:
48 = 1) a time of day using the 24-hour clock.
49 = 2) a time of day using the 12-hour clock. The value is held as 0:00 - 23:59,
50 = but is displayed as 12:00 - 11:59. This is for use in a TimeEdit widget.
51 = 3) a length of time, not restricted to the length of a day.
52 =============================================================================*/
53 
54 /******************************************************************************
55  * Construct a wrapping 00:00 - 23:59, or 12:00 - 11:59 time spin box.
56  */
57 TimeSpinBox::TimeSpinBox(bool use24hour, TQWidget* parent, const char* name)
58  : SpinBox2(0, 1439, 1, 60, parent, name),
59  mMinimumValue(0),
60  m12Hour(!use24hour),
61  mPm(false),
62  mInvalid(false),
63  mEnteredSetValue(false)
64 {
65  mValidator = new TimeValidator(0, 1439, this, "TimeSpinBox validator");
66  mValidator->m12Hour = m12Hour;
67  setValidator(mValidator);
68  setWrapping(true);
69  setReverseWithLayout(false); // keep buttons the same way round even if right-to-left language
70  setShiftSteps(5, 360); // shift-left button increments 5 min / 6 hours
71  setSelectOnStep(false);
72  setAlignment(TQt::AlignHCenter);
73  connect(this, TQT_SIGNAL(valueChanged(int)), TQT_SLOT(slotValueChanged(int)));
74 }
75 
76 /******************************************************************************
77  * Construct a non-wrapping time spin box.
78  */
79 TimeSpinBox::TimeSpinBox(int minMinute, int maxMinute, TQWidget* parent, const char* name)
80  : SpinBox2(minMinute, maxMinute, 1, 60, parent, name),
81  mMinimumValue(minMinute),
82  m12Hour(false),
83  mInvalid(false),
84  mEnteredSetValue(false)
85 {
86  mValidator = new TimeValidator(minMinute, maxMinute, this, "TimeSpinBox validator");
87  setValidator(mValidator);
88  setReverseWithLayout(false); // keep buttons the same way round even if right-to-left language
89  setShiftSteps(5, 300); // shift-left button increments 5 min / 5 hours
90  setSelectOnStep(false);
91  setAlignment(TQApplication::reverseLayout() ? TQt::AlignLeft : TQt::AlignRight);
92 }
93 
95 {
96  return i18n("Press the Shift key while clicking the spin buttons to adjust the time by a larger step (6 hours / 5 minutes).");
97 }
98 
99 TQTime TimeSpinBox::time() const
100 {
101  return TQTime(value() / 60, value() % 60);
102 }
103 
104 TQString TimeSpinBox::mapValueToText(int v)
105 {
106  if (m12Hour)
107  {
108  if (v < 60)
109  v += 720; // convert 0:nn to 12:nn
110  else if (v >= 780)
111  v -= 720; // convert 13 - 23 hours to 1 - 11
112  }
113  TQString s;
114  s.sprintf((wrapping() ? "%02d:%02d" : "%d:%02d"), v/60, v%60);
115  return s;
116 }
117 
118 /******************************************************************************
119  * Convert the user-entered text to a value in minutes.
120  * The allowed formats are:
121  * [hour]:[minute], where minute must be non-blank, or
122  * hhmm, 4 digits, where hour < 24.
123  */
124 int TimeSpinBox::mapTextToValue(bool* ok)
125 {
126  TQString text = cleanText();
127  int colon = text.find(':');
128  if (colon >= 0)
129  {
130  // [h]:m format for any time value
131  TQString hour = text.left(colon).stripWhiteSpace();
132  TQString minute = text.mid(colon + 1).stripWhiteSpace();
133  if (!minute.isEmpty())
134  {
135  bool okmin;
136  bool okhour = true;
137  int m = minute.toUInt(&okmin);
138  int h = 0;
139  if (!hour.isEmpty())
140  h = hour.toUInt(&okhour);
141  if (okhour && okmin && m < 60)
142  {
143  if (m12Hour)
144  {
145  if (h == 0 || h > 12)
146  h = 100; // error
147  else if (h == 12)
148  h = 0; // convert 12:nn to 0:nn
149  if (mPm)
150  h += 12; // convert to PM
151  }
152  int t = h * 60 + m;
153  if (t >= mMinimumValue && t <= maxValue())
154  {
155  if (ok)
156  *ok = true;
157  return t;
158  }
159  }
160  }
161  }
162  else if (text.length() == 4)
163  {
164  // hhmm format for time of day
165  bool okn;
166  int mins = text.toUInt(&okn);
167  if (okn)
168  {
169  int m = mins % 100;
170  int h = mins / 100;
171  if (m12Hour)
172  {
173  if (h == 0 || h > 12)
174  h = 100; // error
175  else if (h == 12)
176  h = 0; // convert 12:nn to 0:nn
177  if (mPm)
178  h += 12; // convert to PM
179  }
180  int t = h * 60 + m;
181  if (h < 24 && m < 60 && t >= mMinimumValue && t <= maxValue())
182  {
183  if (ok)
184  *ok = true;
185  return t;
186  }
187  }
188 
189  }
190  if (ok)
191  *ok = false;
192  return 0;
193 }
194 
195 /******************************************************************************
196  * Set the spin box as valid or invalid.
197  * If newly invalid, the value is displayed as asterisks.
198  * If newly valid, the value is set to the minimum value.
199  */
200 void TimeSpinBox::setValid(bool valid)
201 {
202  if (valid && mInvalid)
203  {
204  mInvalid = false;
205  if (value() < mMinimumValue)
206  SpinBox2::setValue(mMinimumValue);
207  setSpecialValueText(TQString());
208  SpinBox2::setMinValue(mMinimumValue);
209  }
210  else if (!valid && !mInvalid)
211  {
212  mInvalid = true;
213  SpinBox2::setMinValue(mMinimumValue - 1);
214  setSpecialValueText(TQString::fromLatin1("**:**"));
215  SpinBox2::setValue(mMinimumValue - 1);
216  }
217 }
218 
219 /******************************************************************************
220 * Set the spin box's minimum value.
221 */
222 void TimeSpinBox::setMinValue(int minutes)
223 {
224  mMinimumValue = minutes;
225  SpinBox2::setMinValue(mMinimumValue - (mInvalid ? 1 : 0));
226 }
227 
228 /******************************************************************************
229  * Set the spin box's value.
230  */
231 void TimeSpinBox::setValue(int minutes)
232 {
233  if (!mEnteredSetValue)
234  {
235  mEnteredSetValue = true;
236  mPm = (minutes >= 720);
237  if (minutes > maxValue())
238  setValid(false);
239  else
240  {
241  if (mInvalid)
242  {
243  mInvalid = false;
244  setSpecialValueText(TQString());
245  SpinBox2::setMinValue(mMinimumValue);
246  }
247  SpinBox2::setValue(minutes);
248  mEnteredSetValue = false;
249  }
250  }
251 }
252 
253 /******************************************************************************
254  * Step the spin box value.
255  * If it was invalid, set it valid and set the value to the minimum.
256  */
258 {
259  if (mInvalid)
260  setValid(true);
261  else
263 }
264 
266 {
267  if (mInvalid)
268  setValid(true);
269  else
271 }
272 
274 {
275  return value() >= mMinimumValue;
276 }
277 
278 void TimeSpinBox::slotValueChanged(int value)
279 {
280  mPm = mValidator->mPm = (value >= 720);
281 }
282 
283 TQSize TimeSpinBox::sizeHint() const
284 {
285  TQSize sz = SpinBox2::sizeHint();
286  TQFontMetrics fm(font());
287  return TQSize(sz.width() + fm.width(":"), sz.height());
288 }
289 
290 TQSize TimeSpinBox::minimumSizeHint() const
291 {
292  TQSize sz = SpinBox2::minimumSizeHint();
293  TQFontMetrics fm(font());
294  return TQSize(sz.width() + fm.width(":"), sz.height());
295 }
296 
297 /******************************************************************************
298  * Validate the time spin box input.
299  * The entered time must either be 4 digits, or it must contain a colon, but
300  * hours may be blank.
301  */
302 TQValidator::State TimeSpinBox::TimeValidator::validate(TQString& text, int& /*cursorPos*/) const
303 {
304  TQString cleanText = text.stripWhiteSpace();
305  if (cleanText.isEmpty())
306  return TQValidator::Intermediate;
307  TQValidator::State state = TQValidator::Acceptable;
308  TQString hour;
309  bool ok;
310  int hr = 0;
311  int mn = 0;
312  int colon = cleanText.find(':');
313  if (colon >= 0)
314  {
315  TQString minute = cleanText.mid(colon + 1);
316  if (minute.isEmpty())
317  state = TQValidator::Intermediate;
318  else if ((mn = minute.toUInt(&ok)) >= 60 || !ok)
319  return TQValidator::Invalid;
320 
321  hour = cleanText.left(colon);
322  }
323  else if (maxMinute >= 1440)
324  {
325  // The hhmm form of entry is only allowed for time-of-day, i.e. <= 2359
326  hour = cleanText;
327  state = TQValidator::Intermediate;
328  }
329  else
330  {
331  if (cleanText.length() > 4)
332  return TQValidator::Invalid;
333  if (cleanText.length() < 4)
334  state = TQValidator::Intermediate;
335  hour = cleanText.left(2);
336  TQString minute = cleanText.mid(2);
337  if (!minute.isEmpty()
338  && ((mn = minute.toUInt(&ok)) >= 60 || !ok))
339  return TQValidator::Invalid;
340  }
341 
342  if (!hour.isEmpty())
343  {
344  hr = hour.toUInt(&ok);
345  if (m12Hour)
346  {
347  if (hr == 0 || hr > 12)
348  hr = 100; // error;
349  else if (hr == 12)
350  hr = 0; // convert 12:nn to 0:nn
351  if (mPm)
352  hr += 12; // convert to PM
353  }
354  if (!ok || hr > maxMinute/60)
355  return TQValidator::Invalid;
356  }
357  if (state == TQValidator::Acceptable)
358  {
359  int t = hr * 60 + mn;
360  if (t < minMinute || t > maxMinute)
361  return TQValidator::Invalid;
362  }
363  return state;
364 }