kalarm/lib

timespinbox.cpp

00001 /*
00002  *  timespinbox.cpp  -  time spinbox widget
00003  *  Program:  kalarm
00004  *  Copyright © 2001-2004,2007,2008 by David Jarvie <djarvie@kde.org>
00005  *
00006  *  This program is free software; you can redistribute it and/or modify
00007  *  it under the terms of the GNU General Public License as published by
00008  *  the Free Software Foundation; either version 2 of the License, or
00009  *  (at your option) any later version.
00010  *
00011  *  This program is distributed in the hope that it will be useful,
00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00014  *  GNU General Public License for more details.
00015  *
00016  *  You should have received a copy of the GNU General Public License along
00017  *  with this program; if not, write to the Free Software Foundation, Inc.,
00018  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00019  */
00020 
00021 #include "kalarm.h"
00022 
00023 #include <tqvalidator.h>
00024 #include <tqlineedit.h>
00025 #include <klocale.h>
00026 
00027 #include "timespinbox.moc"
00028 
00029 
00030 class TimeSpinBox::TimeValidator : public QValidator
00031 {
00032     public:
00033         TimeValidator(int minMin, int maxMin, TQWidget* parent, const char* name = 0)
00034             : TQValidator(parent, name),
00035                   minMinute(minMin), maxMinute(maxMin), m12Hour(false), mPm(false) { }
00036         virtual State validate(TQString&, int&) const;
00037         int  minMinute, maxMinute;
00038         bool m12Hour;
00039         bool mPm;
00040 };
00041 
00042 
00043 /*=============================================================================
00044 = Class TimeSpinBox
00045 = This is a spin box displaying a time in the format hh:mm, with a pair of
00046 = spin buttons for each of the hour and minute values.
00047 = It can operate in 3 modes:
00048 =  1) a time of day using the 24-hour clock.
00049 =  2) a time of day using the 12-hour clock. The value is held as 0:00 - 23:59,
00050 =     but is displayed as 12:00 - 11:59. This is for use in a TimeEdit widget.
00051 =  3) a length of time, not restricted to the length of a day.
00052 =============================================================================*/
00053 
00054 /******************************************************************************
00055  * Construct a wrapping 00:00 - 23:59, or 12:00 - 11:59 time spin box.
00056  */
00057 TimeSpinBox::TimeSpinBox(bool use24hour, TQWidget* parent, const char* name)
00058     : SpinBox2(0, 1439, 1, 60, parent, name),
00059       mMinimumValue(0),
00060       m12Hour(!use24hour),
00061       mPm(false),
00062       mInvalid(false),
00063       mEnteredSetValue(false)
00064 {
00065     mValidator = new TimeValidator(0, 1439, this, "TimeSpinBox validator");
00066     mValidator->m12Hour = m12Hour;
00067     setValidator(mValidator);
00068     setWrapping(true);
00069     setReverseWithLayout(false);   // keep buttons the same way round even if right-to-left language
00070     setShiftSteps(5, 360);    // shift-left button increments 5 min / 6 hours
00071     setSelectOnStep(false);
00072     setAlignment(Qt::AlignHCenter);
00073     connect(this, TQT_SIGNAL(valueChanged(int)), TQT_SLOT(slotValueChanged(int)));
00074 }
00075 
00076 /******************************************************************************
00077  * Construct a non-wrapping time spin box.
00078  */
00079 TimeSpinBox::TimeSpinBox(int minMinute, int maxMinute, TQWidget* parent, const char* name)
00080     : SpinBox2(minMinute, maxMinute, 1, 60, parent, name),
00081       mMinimumValue(minMinute),
00082       m12Hour(false),
00083       mInvalid(false),
00084       mEnteredSetValue(false)
00085 {
00086     mValidator = new TimeValidator(minMinute, maxMinute, this, "TimeSpinBox validator");
00087     setValidator(mValidator);
00088     setReverseWithLayout(false);   // keep buttons the same way round even if right-to-left language
00089     setShiftSteps(5, 300);    // shift-left button increments 5 min / 5 hours
00090     setSelectOnStep(false);
00091     setAlignment(TQApplication::reverseLayout() ? Qt::AlignLeft : Qt::AlignRight);
00092 }
00093 
00094 TQString TimeSpinBox::shiftWhatsThis()
00095 {
00096     return i18n("Press the Shift key while clicking the spin buttons to adjust the time by a larger step (6 hours / 5 minutes).");
00097 }
00098 
00099 TQTime TimeSpinBox::time() const
00100 {
00101     return TQTime(value() / 60, value() % 60);
00102 }
00103 
00104 TQString TimeSpinBox::mapValueToText(int v)
00105 {
00106     if (m12Hour)
00107     {
00108         if (v < 60)
00109             v += 720;      // convert 0:nn to 12:nn
00110         else if (v >= 780)
00111             v -= 720;      // convert 13 - 23 hours to 1 - 11
00112     }
00113     TQString s;
00114     s.sprintf((wrapping() ? "%02d:%02d" : "%d:%02d"), v/60, v%60);
00115     return s;
00116 }
00117 
00118 /******************************************************************************
00119  * Convert the user-entered text to a value in minutes.
00120  * The allowed formats are:
00121  *    [hour]:[minute], where minute must be non-blank, or
00122  *    hhmm, 4 digits, where hour < 24.
00123  */
00124 int TimeSpinBox::mapTextToValue(bool* ok)
00125 {
00126     TQString text = cleanText();
00127     int colon = text.find(':');
00128     if (colon >= 0)
00129     {
00130         // [h]:m format for any time value
00131         TQString hour   = text.left(colon).stripWhiteSpace();
00132         TQString minute = text.mid(colon + 1).stripWhiteSpace();
00133         if (!minute.isEmpty())
00134         {
00135             bool okmin;
00136             bool okhour = true;
00137             int m = minute.toUInt(&okmin);
00138             int h = 0;
00139             if (!hour.isEmpty())
00140                 h = hour.toUInt(&okhour);
00141             if (okhour  &&  okmin  &&  m < 60)
00142             {
00143                 if (m12Hour)
00144                 {
00145                     if (h == 0  ||  h > 12)
00146                         h = 100;     // error
00147                     else if (h == 12)
00148                         h = 0;       // convert 12:nn to 0:nn
00149                     if (mPm)
00150                         h += 12;     // convert to PM
00151                 }
00152                 int t = h * 60 + m;
00153                 if (t >= mMinimumValue  &&  t <= maxValue())
00154                 {
00155                     if (ok)
00156                         *ok = true;
00157                     return t;
00158                 }
00159             }
00160         }
00161     }
00162     else if (text.length() == 4)
00163     {
00164         // hhmm format for time of day
00165         bool okn;
00166         int mins = text.toUInt(&okn);
00167         if (okn)
00168         {
00169             int m = mins % 100;
00170             int h = mins / 100;
00171             if (m12Hour)
00172             {
00173                 if (h == 0  ||  h > 12)
00174                     h = 100;    // error
00175                 else if (h == 12)
00176                     h = 0;      // convert 12:nn to 0:nn
00177                 if (mPm)
00178                     h += 12;    // convert to PM
00179             }
00180             int t = h * 60 + m;
00181             if (h < 24  &&  m < 60  &&  t >= mMinimumValue  &&  t <= maxValue())
00182             {
00183                 if (ok)
00184                     *ok = true;
00185                 return t;
00186             }
00187         }
00188 
00189     }
00190     if (ok)
00191         *ok = false;
00192     return 0;
00193 }
00194 
00195 /******************************************************************************
00196  * Set the spin box as valid or invalid.
00197  * If newly invalid, the value is displayed as asterisks.
00198  * If newly valid, the value is set to the minimum value.
00199  */
00200 void TimeSpinBox::setValid(bool valid)
00201 {
00202     if (valid  &&  mInvalid)
00203     {
00204         mInvalid = false;
00205         if (value() < mMinimumValue)
00206             SpinBox2::setValue(mMinimumValue);
00207         setSpecialValueText(TQString());
00208         SpinBox2::setMinValue(mMinimumValue);
00209     }
00210     else if (!valid  &&  !mInvalid)
00211     {
00212         mInvalid = true;
00213         SpinBox2::setMinValue(mMinimumValue - 1);
00214         setSpecialValueText(TQString::fromLatin1("**:**"));
00215         SpinBox2::setValue(mMinimumValue - 1);
00216     }
00217 }
00218 
00219 /******************************************************************************
00220 * Set the spin box's minimum value.
00221 */
00222 void TimeSpinBox::setMinValue(int minutes)
00223 {
00224     mMinimumValue = minutes;
00225     SpinBox2::setMinValue(mMinimumValue - (mInvalid ? 1 : 0));
00226 }
00227 
00228 /******************************************************************************
00229  * Set the spin box's value.
00230  */
00231 void TimeSpinBox::setValue(int minutes)
00232 {
00233     if (!mEnteredSetValue)
00234     {
00235         mEnteredSetValue = true;
00236         mPm = (minutes >= 720);
00237         if (minutes > maxValue())
00238             setValid(false);
00239         else
00240         {
00241             if (mInvalid)
00242             {
00243                 mInvalid = false;
00244                 setSpecialValueText(TQString());
00245                 SpinBox2::setMinValue(mMinimumValue);
00246             }
00247             SpinBox2::setValue(minutes);
00248             mEnteredSetValue = false;
00249         }
00250     }
00251 }
00252 
00253 /******************************************************************************
00254  * Step the spin box value.
00255  * If it was invalid, set it valid and set the value to the minimum.
00256  */
00257 void TimeSpinBox::stepUp()
00258 {
00259     if (mInvalid)
00260         setValid(true);
00261     else
00262         SpinBox2::stepUp();
00263 }
00264 
00265 void TimeSpinBox::stepDown()
00266 {
00267     if (mInvalid)
00268         setValid(true);
00269     else
00270         SpinBox2::stepDown();
00271 }
00272 
00273 bool TimeSpinBox::isValid() const
00274 {
00275     return value() >= mMinimumValue;
00276 }
00277 
00278 void TimeSpinBox::slotValueChanged(int value)
00279 {
00280     mPm = mValidator->mPm = (value >= 720);
00281 }
00282 
00283 TQSize TimeSpinBox::sizeHint() const
00284 {
00285     TQSize sz = SpinBox2::sizeHint();
00286     TQFontMetrics fm(font());
00287     return TQSize(sz.width() + fm.width(":"), sz.height());
00288 }
00289 
00290 TQSize TimeSpinBox::minimumSizeHint() const
00291 {
00292     TQSize sz = SpinBox2::minimumSizeHint();
00293     TQFontMetrics fm(font());
00294     return TQSize(sz.width() + fm.width(":"), sz.height());
00295 }
00296 
00297 /******************************************************************************
00298  * Validate the time spin box input.
00299  * The entered time must either be 4 digits, or it must contain a colon, but
00300  * hours may be blank.
00301  */
00302 TQValidator::State TimeSpinBox::TimeValidator::validate(TQString& text, int& /*cursorPos*/) const
00303 {
00304     TQString cleanText = text.stripWhiteSpace();
00305     if (cleanText.isEmpty())
00306         return TQValidator::Intermediate;
00307     TQValidator::State state = TQValidator::Acceptable;
00308     TQString hour;
00309     bool ok;
00310     int hr = 0;
00311     int mn = 0;
00312     int colon = cleanText.find(':');
00313     if (colon >= 0)
00314     {
00315         TQString minute = cleanText.mid(colon + 1);
00316         if (minute.isEmpty())
00317             state = TQValidator::Intermediate;
00318         else if ((mn = minute.toUInt(&ok)) >= 60  ||  !ok)
00319             return TQValidator::Invalid;
00320 
00321         hour = cleanText.left(colon);
00322     }
00323     else if (maxMinute >= 1440)
00324     {
00325         // The hhmm form of entry is only allowed for time-of-day, i.e. <= 2359
00326         hour = cleanText;
00327         state = TQValidator::Intermediate;
00328     }
00329     else
00330     {
00331         if (cleanText.length() > 4)
00332             return TQValidator::Invalid;
00333         if (cleanText.length() < 4)
00334             state = TQValidator::Intermediate;
00335         hour = cleanText.left(2);
00336         TQString minute = cleanText.mid(2);
00337         if (!minute.isEmpty()
00338         &&  ((mn = minute.toUInt(&ok)) >= 60  ||  !ok))
00339             return TQValidator::Invalid;
00340     }
00341 
00342     if (!hour.isEmpty())
00343     {
00344         hr = hour.toUInt(&ok);
00345         if (m12Hour)
00346         {
00347             if (hr == 0  ||  hr > 12)
00348                 hr = 100;    // error;
00349             else if (hr == 12)
00350                 hr = 0;      // convert 12:nn to 0:nn
00351             if (mPm)
00352                 hr += 12;    // convert to PM
00353         }
00354         if (!ok  ||  hr > maxMinute/60)
00355             return TQValidator::Invalid;
00356     }
00357     if (state == TQValidator::Acceptable)
00358     {
00359         int t = hr * 60 + mn;
00360         if (t < minMinute  ||  t > maxMinute)
00361             return TQValidator::Invalid;
00362     }
00363     return state;
00364 }