kalarm/lib

spinbox.cpp
00001 /*
00002  *  spinbox.cpp  -  spin box with read-only option and shift-click step value
00003  *  Program:  kalarm
00004  *  Copyright © 2002,2004,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 <tdeversion.h>
00022 #include <tqlineedit.h>
00023 #include <tqobjectlist.h>
00024 #include "spinbox.moc"
00025 
00026 
00027 SpinBox::SpinBox(TQWidget* parent, const char* name)
00028     : TQSpinBox(0, 99999, 1, parent, name),
00029       mMinValue(TQSpinBox::minValue()),
00030       mMaxValue(TQSpinBox::maxValue())
00031 {
00032     init();
00033 }
00034 
00035 SpinBox::SpinBox(int minValue, int maxValue, int step, TQWidget* parent, const char* name)
00036     : TQSpinBox(minValue, maxValue, step, parent, name),
00037       mMinValue(minValue),
00038       mMaxValue(maxValue)
00039 {
00040     init();
00041 }
00042 
00043 void SpinBox::init()
00044 {
00045     int step = TQSpinBox::lineStep();
00046     mLineStep        = step;
00047     mLineShiftStep   = step;
00048     mCurrentButton   = NO_BUTTON;
00049     mShiftMouse      = false;
00050     mShiftMinBound   = false;
00051     mShiftMaxBound   = false;
00052     mSelectOnStep    = true;
00053     mReadOnly        = false;
00054     mSuppressSignals = false;
00055     mEdited          = false;
00056 
00057     // Find the spin widgets which are part of the spin boxes, in order to
00058     // handle their shift-button presses.
00059     TQObjectList* spinwidgets = queryList(TQSPINWIDGET_OBJECT_NAME_STRING, 0, false, true);
00060     TQSpinWidget* spin = (TQSpinWidget*)spinwidgets->getFirst();
00061     if (spin)
00062         spin->installEventFilter(this);   // handle shift-button presses
00063     delete spinwidgets;
00064     editor()->installEventFilter(this);   // handle shift-up/down arrow presses
00065 
00066 #if KDE_IS_VERSION(3,1,90)
00067     // Detect when the text field is edited
00068     connect(editor(), TQT_SIGNAL(textChanged(const TQString&)), TQT_SLOT(textEdited()));
00069 #endif
00070 }
00071 
00072 void SpinBox::setReadOnly(bool ro)
00073 {
00074     if ((int)ro != (int)mReadOnly)
00075     {
00076         mReadOnly = ro;
00077         editor()->setReadOnly(ro);
00078         if (ro)
00079             setShiftStepping(false, mCurrentButton);
00080     }
00081 }
00082 
00083 int SpinBox::bound(int val) const
00084 {
00085     return (val < mMinValue) ? mMinValue : (val > mMaxValue) ? mMaxValue : val;
00086 }
00087 
00088 void SpinBox::setMinValue(int val)
00089 {
00090     mMinValue = val;
00091     TQSpinBox::setMinValue(val);
00092     mShiftMinBound = false;
00093 }
00094 
00095 void SpinBox::setMaxValue(int val)
00096 {
00097     mMaxValue = val;
00098     TQSpinBox::setMaxValue(val);
00099     mShiftMaxBound = false;
00100 }
00101 
00102 void SpinBox::setLineStep(int step)
00103 {
00104     mLineStep = step;
00105     if (!mShiftMouse)
00106         TQSpinBox::setLineStep(step);
00107 }
00108 
00109 void SpinBox::setLineShiftStep(int step)
00110 {
00111     mLineShiftStep = step;
00112     if (mShiftMouse)
00113         TQSpinBox::setLineStep(step);
00114 }
00115 
00116 void SpinBox::stepUp()
00117 {
00118     int step = TQSpinBox::lineStep();
00119     addValue(step);
00120     emit stepped(step);
00121 }
00122 
00123 void SpinBox::stepDown()
00124 {
00125     int step = -TQSpinBox::lineStep();
00126     addValue(step);
00127     emit stepped(step);
00128 }
00129 
00130 /******************************************************************************
00131 * Adds a positive or negative increment to the current value, wrapping as appropriate.
00132 * If 'current' is true, any temporary 'shift' values for the range are used instead
00133 * of the real maximum and minimum values.
00134 */
00135 void SpinBox::addValue(int change, bool current)
00136 {
00137     int newval = value() + change;
00138     int maxval = current ? TQSpinBox::maxValue() : mMaxValue;
00139     int minval = current ? TQSpinBox::minValue() : mMinValue;
00140     if (wrapping())
00141     {
00142         int range = maxval - minval + 1;
00143         if (newval > maxval)
00144             newval = minval + (newval - maxval - 1) % range;
00145         else if (newval < minval)
00146             newval = maxval - (minval - 1 - newval) % range;
00147     }
00148     else
00149     {
00150         if (newval > maxval)
00151             newval = maxval;
00152         else if (newval < minval)
00153             newval = minval;
00154     }
00155     setValue(newval);
00156 }
00157 
00158 void SpinBox::valueChange()
00159 {
00160     if (!mSuppressSignals)
00161     {
00162         int val = value();
00163         if (mShiftMinBound  &&  val >= mMinValue)
00164         {
00165             // Reinstate the minimum bound now that the value has returned to the normal range.
00166             TQSpinBox::setMinValue(mMinValue);
00167             mShiftMinBound = false;
00168         }
00169         if (mShiftMaxBound  &&  val <= mMaxValue)
00170         {
00171             // Reinstate the maximum bound now that the value has returned to the normal range.
00172             TQSpinBox::setMaxValue(mMaxValue);
00173             mShiftMaxBound = false;
00174         }
00175 
00176         bool focus = !mSelectOnStep && hasFocus();
00177         if (focus)
00178             clearFocus();     // prevent selection of the spin box text
00179         TQSpinBox::valueChange();
00180         if (focus)
00181             setFocus();
00182     }
00183 }
00184 
00185 /******************************************************************************
00186 * Called whenever the line edit text is changed.
00187 */
00188 void SpinBox::textEdited()
00189 {
00190     mEdited = true;
00191 }
00192 
00193 void SpinBox::updateDisplay()
00194 {
00195     mEdited = false;
00196     TQSpinBox::updateDisplay();
00197 }
00198 
00199 /******************************************************************************
00200 * Receives events destined for the spin widget or for the edit field.
00201 */
00202 bool SpinBox::eventFilter(TQObject* obj, TQEvent* e)
00203 {
00204     if (TQT_BASE_OBJECT(obj) == TQT_BASE_OBJECT(editor()))
00205     {
00206         int step = 0;
00207         bool shift = false;
00208         switch (e->type())
00209         {
00210             case TQEvent::KeyPress:
00211             {
00212                 // Up and down arrow keys step the value
00213                 TQKeyEvent* ke = (TQKeyEvent*)e;
00214                 int key = ke->key();
00215                 if (key == TQt::Key_Up)
00216                     step = 1;
00217                 else if (key == TQt::Key_Down)
00218                     step = -1;
00219                 shift = ((ke->state() & (TQt::ShiftButton | TQt::AltButton)) == TQt::ShiftButton);
00220                 break;
00221             }
00222             case TQEvent::Wheel:
00223             {
00224                 TQWheelEvent* we = (TQWheelEvent*)e;
00225                 step = (we->delta() > 0) ? 1 : -1;
00226                 shift = ((we->state() & (TQt::ShiftButton | TQt::AltButton)) == TQt::ShiftButton);
00227                 break;
00228             }
00229 #if KDE_IS_VERSION(3,1,90)
00230             case TQEvent::Leave:
00231                 if (mEdited)
00232                     interpretText();
00233                 break;
00234 #endif
00235             default:
00236                 break;
00237         }
00238         if (step)
00239         {
00240             if (mReadOnly)
00241                 return true;    // discard up/down arrow keys or wheel
00242             if (shift)
00243             {
00244                 // Shift stepping
00245                 int val = value();
00246                 if (step > 0)
00247                     step = mLineShiftStep - val % mLineShiftStep;
00248                 else
00249                     step = - ((val + mLineShiftStep - 1) % mLineShiftStep + 1);
00250             }
00251             else
00252                 step = (step > 0) ? mLineStep : -mLineStep;
00253             addValue(step, false);
00254             return true;
00255         }
00256     }
00257     else
00258     {
00259         int etype = e->type();    // avoid switch compile warnings
00260         switch (etype)
00261         {
00262             case TQEvent::MouseButtonPress:
00263             case TQEvent::MouseButtonDblClick:
00264             {
00265                 TQMouseEvent* me = (TQMouseEvent*)e;
00266                 if (me->button() == Qt::LeftButton)
00267                 {
00268                     // It's a left button press. Set normal or shift stepping as appropriate.
00269                     if (mReadOnly)
00270                         return true;   // discard the event
00271                     mCurrentButton = whichButton(me->pos());
00272                     if (mCurrentButton == NO_BUTTON)
00273                         return true;
00274                     bool shift = (me->state() & (TQt::ShiftButton | TQt::AltButton)) == TQt::ShiftButton;
00275                     if (setShiftStepping(shift, mCurrentButton))
00276                         return true;     // hide the event from the spin widget
00277                     return false;    // forward event to the destination widget
00278                 }
00279                 break;
00280             }
00281             case TQEvent::MouseButtonRelease:
00282             {
00283                 TQMouseEvent* me = (TQMouseEvent*)e;
00284                 if (me->button() == Qt::LeftButton  &&  mShiftMouse)
00285                 {
00286                     setShiftStepping(false, mCurrentButton);    // cancel shift stepping
00287                     return false;    // forward event to the destination widget
00288                 }
00289                 break;
00290             }
00291             case TQEvent::MouseMove:
00292             {
00293                 TQMouseEvent* me = (TQMouseEvent*)e;
00294                 if (me->state() & Qt::LeftButton)
00295                 {
00296                     // The left button is down. Track which spin button it's in.
00297                     if (mReadOnly)
00298                         return true;   // discard the event
00299                     int newButton = whichButton(me->pos());
00300                     if (newButton != mCurrentButton)
00301                     {
00302                         // The mouse has moved to a new spin button.
00303                         // Set normal or shift stepping as appropriate.
00304                         mCurrentButton = newButton;
00305                         bool shift = (me->state() & (TQt::ShiftButton | TQt::AltButton)) == TQt::ShiftButton;
00306                         if (setShiftStepping(shift, mCurrentButton))
00307                             return true;     // hide the event from the spin widget
00308                     }
00309                     return false;    // forward event to the destination widget
00310                 }
00311                 break;
00312             }
00313             case TQEvent::Wheel:
00314             {
00315                 TQWheelEvent* we = (TQWheelEvent*)e;
00316                 bool shift = (we->state() & (TQt::ShiftButton | TQt::AltButton)) == TQt::ShiftButton;
00317                 if (setShiftStepping(shift, (we->delta() > 0 ? UP : DOWN)))
00318                     return true;     // hide the event from the spin widget
00319                 return false;    // forward event to the destination widget
00320             }
00321             case TQEvent::KeyPress:
00322             case TQEvent::KeyRelease:
00323             case TQEvent::AccelOverride:      // this is needed to receive Shift presses!
00324             {
00325                 TQKeyEvent* ke = (TQKeyEvent*)e;
00326                 int key   = ke->key();
00327                 int state = ke->state();
00328                 if ((state & Qt::LeftButton)
00329                 &&  (key == TQt::Key_Shift  ||  key == TQt::Key_Alt))
00330                 {
00331                     // The left mouse button is down, and the Shift or Alt key has changed
00332                     if (mReadOnly)
00333                         return true;   // discard the event
00334                     state ^= (key == TQt::Key_Shift) ? TQt::ShiftButton : TQt::AltButton;    // new state
00335                     bool shift = (state & (TQt::ShiftButton | TQt::AltButton)) == TQt::ShiftButton;
00336                     if ((!shift && mShiftMouse)  ||  (shift && !mShiftMouse))
00337                     {
00338                         // The effective shift state has changed.
00339                         // Set normal or shift stepping as appropriate.
00340                         if (setShiftStepping(shift, mCurrentButton))
00341                             return true;     // hide the event from the spin widget
00342                     }
00343                 }
00344                 break;
00345             }
00346         }
00347     }
00348     return TQSpinBox::eventFilter(obj, e);
00349 }
00350 
00351 /******************************************************************************
00352 * Set spin widget stepping to the normal or shift increment.
00353 */
00354 bool SpinBox::setShiftStepping(bool shift, int currentButton)
00355 {
00356     if (currentButton == NO_BUTTON)
00357         shift = false;
00358     if (shift  &&  !mShiftMouse)
00359     {
00360         /* The value is to be stepped to a multiple of the shift increment.
00361          * Adjust the value so that after the spin widget steps it, it will be correct.
00362          * Then, if the mouse button is held down, the spin widget will continue to
00363          * step by the shift amount.
00364          */
00365         int val = value();
00366         int step = (currentButton == UP) ? mLineShiftStep : (currentButton == DOWN) ? -mLineShiftStep : 0;
00367         int adjust = shiftStepAdjustment(val, step);
00368         mShiftMouse = true;
00369         if (adjust)
00370         {
00371             /* The value is to be stepped by other than the shift increment,
00372              * presumably because it is being set to a multiple of the shift
00373              * increment. Achieve this by making the adjustment here, and then
00374              * allowing the normal step processing to complete the job by
00375              * adding/subtracting the normal shift increment.
00376              */
00377             if (!wrapping())
00378             {
00379                 // Prevent the step from going past the spinbox's range, or
00380                 // to the minimum value if that has a special text unless it is
00381                 // already at the minimum value + 1.
00382                 int newval = val + adjust + step;
00383                 int svt = specialValueText().isEmpty() ? 0 : 1;
00384                 int minval = mMinValue + svt;
00385                 if (newval <= minval  ||  newval >= mMaxValue)
00386                 {
00387                     // Stepping to the minimum or maximum value
00388                     if (svt  &&  newval <= mMinValue  &&  val == mMinValue)
00389                         newval = mMinValue;
00390                     else
00391                         newval = (newval <= minval) ? minval : mMaxValue;
00392                     TQSpinBox::setValue(newval);
00393                     emit stepped(step);
00394                     return true;
00395                 }
00396 
00397                 // If the interim value will lie outside the spinbox's range,
00398                 // temporarily adjust the range to allow the value to be set.
00399                 int tempval = val + adjust;
00400                 if (tempval < mMinValue)
00401                 {
00402                     TQSpinBox::setMinValue(tempval);
00403                     mShiftMinBound = true;
00404                 }
00405                 else if (tempval > mMaxValue)
00406                 {
00407                     TQSpinBox::setMaxValue(tempval);
00408                     mShiftMaxBound = true;
00409                 }
00410             }
00411 
00412             // Don't process changes since this new value will be stepped immediately
00413             mSuppressSignals = true;
00414             bool blocked = signalsBlocked();
00415             blockSignals(true);
00416             addValue(adjust, true);
00417             blockSignals(blocked);
00418             mSuppressSignals = false;
00419         }
00420         TQSpinBox::setLineStep(mLineShiftStep);
00421     }
00422     else if (!shift  &&  mShiftMouse)
00423     {
00424         // Reinstate to normal (non-shift) stepping
00425         TQSpinBox::setLineStep(mLineStep);
00426         TQSpinBox::setMinValue(mMinValue);
00427         TQSpinBox::setMaxValue(mMaxValue);
00428         mShiftMinBound = mShiftMaxBound = false;
00429         mShiftMouse = false;
00430     }
00431     return false;
00432 }
00433 
00434 /******************************************************************************
00435 * Return the initial adjustment to the value for a shift step up or down.
00436 * The default is to step up or down to the nearest multiple of the shift
00437 * increment, so the adjustment returned is for stepping up the decrement
00438 * required to round down to a multiple of the shift increment <= current value,
00439 * or for stepping down the increment required to round up to a multiple of the
00440 * shift increment >= current value.
00441 * This method's caller then adjusts the resultant value if necessary to cater
00442 * for the widget's minimum/maximum value, and wrapping.
00443 * This should really be a static method, but it needs to be virtual...
00444 */
00445 int SpinBox::shiftStepAdjustment(int oldValue, int shiftStep)
00446 {
00447     if (oldValue == 0  ||  shiftStep == 0)
00448         return 0;
00449     if (shiftStep > 0)
00450     {
00451         if (oldValue >= 0)
00452             return -(oldValue % shiftStep);
00453         else
00454             return (-oldValue - 1) % shiftStep + 1 - shiftStep;
00455     }
00456     else
00457     {
00458         shiftStep = -shiftStep;
00459         if (oldValue >= 0)
00460             return shiftStep - ((oldValue - 1) % shiftStep + 1);
00461         else
00462             return (-oldValue) % shiftStep;
00463     }
00464 }
00465 
00466 /******************************************************************************
00467 *  Find which spin widget button a mouse event is in.
00468 */
00469 int SpinBox::whichButton(const TQPoint& pos)
00470 {
00471     if (upRect().contains(pos))
00472         return UP;
00473     if (downRect().contains(pos))
00474         return DOWN;
00475     return NO_BUTTON;
00476 }