kalarm/lib

spinbox.cpp

00001 /*
00002  *  spinbox.cpp  -  spin box with read-only option and shift-click step value
00003  *  Program:  kalarm
00004  *  Copyright (C) 2002, 2004 by David Jarvie <software@astrojar.org.uk>
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 <kdeversion.h>
00022 #include <qlineedit.h>
00023 #include <qobjectlist.h>
00024 #include "spinbox.moc"
00025 
00026 
00027 SpinBox::SpinBox(QWidget* parent, const char* name)
00028     : QSpinBox(0, 99999, 1, parent, name),
00029       mMinValue(QSpinBox::minValue()),
00030       mMaxValue(QSpinBox::maxValue())
00031 {
00032     init();
00033 }
00034 
00035 SpinBox::SpinBox(int minValue, int maxValue, int step, QWidget* parent, const char* name)
00036     : QSpinBox(minValue, maxValue, step, parent, name),
00037       mMinValue(minValue),
00038       mMaxValue(maxValue)
00039 {
00040     init();
00041 }
00042 
00043 void SpinBox::init()
00044 {
00045     int step = QSpinBox::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     QObjectList* spinwidgets = queryList("QSpinWidget", 0, false, true);
00060     QSpinWidget* spin = (QSpinWidget*)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(), SIGNAL(textChanged(const QString&)), 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);
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     QSpinBox::setMinValue(val);
00092     mShiftMinBound = false;
00093 }
00094 
00095 void SpinBox::setMaxValue(int val)
00096 {
00097     mMaxValue = val;
00098     QSpinBox::setMaxValue(val);
00099     mShiftMaxBound = false;
00100 }
00101 
00102 void SpinBox::setLineStep(int step)
00103 {
00104     mLineStep = step;
00105     if (!mShiftMouse)
00106         QSpinBox::setLineStep(step);
00107 }
00108 
00109 void SpinBox::setLineShiftStep(int step)
00110 {
00111     mLineShiftStep = step;
00112     if (mShiftMouse)
00113         QSpinBox::setLineStep(step);
00114 }
00115 
00116 void SpinBox::stepUp()
00117 {
00118     int step = QSpinBox::lineStep();
00119     addValue(step);
00120     emit stepped(step);
00121 }
00122 
00123 void SpinBox::stepDown()
00124 {
00125     int step = -QSpinBox::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 ? QSpinBox::maxValue() : mMaxValue;
00139     int minval = current ? QSpinBox::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             QSpinBox::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             QSpinBox::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         QSpinBox::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     QSpinBox::updateDisplay();
00197 }
00198 
00199 /******************************************************************************
00200 * Receives events destined for the spin widget or for the edit field.
00201 */
00202 bool SpinBox::eventFilter(QObject* obj, QEvent* e)
00203 {
00204     if (obj == editor())
00205     {
00206         if (e->type() == QEvent::KeyPress)
00207         {
00208             // Up and down arrow keys step the value
00209             QKeyEvent* ke = (QKeyEvent*)e;
00210             int key = ke->key();
00211             if (key == Qt::Key_Up  ||  key == Qt::Key_Down)
00212             {
00213                 if (mReadOnly)
00214                     return true;    // discard up/down arrow keys
00215                 int step;
00216                 if ((ke->state() & (Qt::ShiftButton | Qt::AltButton)) == Qt::ShiftButton)
00217                 {
00218                     // Shift stepping
00219                     int val = value();
00220                     if (key == Qt::Key_Up)
00221                         step = mLineShiftStep - val % mLineShiftStep;
00222                     else
00223                         step = - ((val + mLineShiftStep - 1) % mLineShiftStep + 1);
00224                 }
00225                 else
00226                     step = (key == Qt::Key_Up) ? mLineStep : -mLineStep;
00227                 addValue(step, false);
00228                 return true;
00229             }
00230         }
00231 #if KDE_IS_VERSION(3,1,90)
00232         else if (e->type() == QEvent::Leave)
00233         {
00234             if (mEdited)
00235                 interpretText();
00236         }
00237 #endif
00238     }
00239     else
00240     {
00241         int etype = e->type();    // avoid switch compile warnings
00242         switch (etype)
00243         {
00244             case QEvent::MouseButtonPress:
00245             case QEvent::MouseButtonDblClick:
00246             {
00247                 QMouseEvent* me = (QMouseEvent*)e;
00248                 if (me->button() == Qt::LeftButton)
00249                 {
00250                     // It's a left button press. Set normal or shift stepping as appropriate.
00251                     if (mReadOnly)
00252                         return true;   // discard the event
00253                     mCurrentButton = whichButton(me->pos());
00254                     if (mCurrentButton == NO_BUTTON)
00255                         return true;
00256                     bool shift = (me->state() & (Qt::ShiftButton | Qt::AltButton)) == Qt::ShiftButton;
00257                     if (setShiftStepping(shift))
00258                         return true;     // hide the event from the spin widget
00259                     return false;    // forward event to the destination widget
00260                 }
00261                 break;
00262             }
00263             case QEvent::MouseButtonRelease:
00264             {
00265                 QMouseEvent* me = (QMouseEvent*)e;
00266                 if (me->button() == Qt::LeftButton  &&  mShiftMouse)
00267                 {
00268                     setShiftStepping(false);    // cancel shift stepping
00269                     return false;    // forward event to the destination widget
00270                 }
00271                 break;
00272             }
00273             case QEvent::MouseMove:
00274             {
00275                 QMouseEvent* me = (QMouseEvent*)e;
00276                 if (me->state() & Qt::LeftButton)
00277                 {
00278                     // The left button is down. Track which spin button it's in.
00279                     if (mReadOnly)
00280                         return true;   // discard the event
00281                     int newButton = whichButton(me->pos());
00282                     if (newButton != mCurrentButton)
00283                     {
00284                         // The mouse has moved to a new spin button.
00285                         // Set normal or shift stepping as appropriate.
00286                         mCurrentButton = newButton;
00287                         bool shift = (me->state() & (Qt::ShiftButton | Qt::AltButton)) == Qt::ShiftButton;
00288                         if (setShiftStepping(shift))
00289                             return true;     // hide the event from the spin widget
00290                     }
00291                     return false;    // forward event to the destination widget
00292                 }
00293                 break;
00294             }
00295             case QEvent::KeyPress:
00296             case QEvent::KeyRelease:
00297             case QEvent::AccelOverride:      // this is needed to receive Shift presses!
00298             {
00299                 QKeyEvent* ke = (QKeyEvent*)e;
00300                 int key   = ke->key();
00301                 int state = ke->state();
00302                 if ((state & Qt::LeftButton)
00303                 &&  (key == Qt::Key_Shift  ||  key == Qt::Key_Alt))
00304                 {
00305                     // The left mouse button is down, and the Shift or Alt key has changed
00306                     if (mReadOnly)
00307                         return true;   // discard the event
00308                     state ^= (key == Qt::Key_Shift) ? Qt::ShiftButton : Qt::AltButton;    // new state
00309                     bool shift = (state & (Qt::ShiftButton | Qt::AltButton)) == Qt::ShiftButton;
00310                     if (!shift && mShiftMouse  ||  shift && !mShiftMouse)
00311                     {
00312                         // The effective shift state has changed.
00313                         // Set normal or shift stepping as appropriate.
00314                         if (setShiftStepping(shift))
00315                             return true;     // hide the event from the spin widget
00316                     }
00317                 }
00318                 break;
00319             }
00320         }
00321     }
00322     return QSpinBox::eventFilter(obj, e);
00323 }
00324 
00325 /******************************************************************************
00326 * Set spin widget stepping to the normal or shift increment.
00327 */
00328 bool SpinBox::setShiftStepping(bool shift)
00329 {
00330     if (mCurrentButton == NO_BUTTON)
00331         shift = false;
00332     if (shift  &&  !mShiftMouse)
00333     {
00334         /* The value is to be stepped to a multiple of the shift increment.
00335          * Adjust the value so that after the spin widget steps it, it will be correct.
00336          * Then, if the mouse button is held down, the spin widget will continue to
00337          * step by the shift amount.
00338          */
00339         int val = value();
00340         int step = (mCurrentButton == UP) ? mLineShiftStep : (mCurrentButton == DOWN) ? -mLineShiftStep : 0;
00341         int adjust = shiftStepAdjustment(val, step);
00342         mShiftMouse = true;
00343         if (adjust)
00344         {
00345             /* The value is to be stepped by other than the shift increment,
00346              * presumably because it is being set to a multiple of the shift
00347              * increment. Achieve this by making the adjustment here, and then
00348              * allowing the normal step processing to complete the job by
00349              * adding/subtracting the normal shift increment.
00350              */
00351             if (!wrapping())
00352             {
00353                 // Prevent the step from going past the spinbox's range, or
00354                 // to the minimum value if that has a special text unless it is
00355                 // already at the minimum value + 1.
00356                 int newval = val + adjust + step;
00357                 int svt = specialValueText().isEmpty() ? 0 : 1;
00358                 int minval = mMinValue + svt;
00359                 if (newval <= minval  ||  newval >= mMaxValue)
00360                 {
00361                     // Stepping to the minimum or maximum value
00362                     if (svt  &&  newval <= mMinValue  &&  val == mMinValue)
00363                         newval = mMinValue;
00364                     else
00365                         newval = (newval <= minval) ? minval : mMaxValue;
00366                     QSpinBox::setValue(newval);
00367                     emit stepped(step);
00368                     return true;
00369                 }
00370 
00371                 // If the interim value will lie outside the spinbox's range,
00372                 // temporarily adjust the range to allow the value to be set.
00373                 int tempval = val + adjust;
00374                 if (tempval < mMinValue)
00375                 {
00376                     QSpinBox::setMinValue(tempval);
00377                     mShiftMinBound = true;
00378                 }
00379                 else if (tempval > mMaxValue)
00380                 {
00381                     QSpinBox::setMaxValue(tempval);
00382                     mShiftMaxBound = true;
00383                 }
00384             }
00385 
00386             // Don't process changes since this new value will be stepped immediately
00387             mSuppressSignals = true;
00388             bool blocked = signalsBlocked();
00389             blockSignals(true);
00390             addValue(adjust, true);
00391             blockSignals(blocked);
00392             mSuppressSignals = false;
00393         }
00394         QSpinBox::setLineStep(mLineShiftStep);
00395     }
00396     else if (!shift  &&  mShiftMouse)
00397     {
00398         // Reinstate to normal (non-shift) stepping
00399         QSpinBox::setLineStep(mLineStep);
00400         QSpinBox::setMinValue(mMinValue);
00401         QSpinBox::setMaxValue(mMaxValue);
00402         mShiftMinBound = mShiftMaxBound = false;
00403         mShiftMouse = false;
00404     }
00405     return false;
00406 }
00407 
00408 /******************************************************************************
00409 * Return the initial adjustment to the value for a shift step up or down.
00410 * The default is to step up or down to the nearest multiple of the shift
00411 * increment, so the adjustment returned is for stepping up the decrement
00412 * required to round down to a multiple of the shift increment <= current value,
00413 * or for stepping down the increment required to round up to a multiple of the
00414 * shift increment >= current value.
00415 * This method's caller then adjusts the resultant value if necessary to cater
00416 * for the widget's minimum/maximum value, and wrapping.
00417 * This should really be a static method, but it needs to be virtual...
00418 */
00419 int SpinBox::shiftStepAdjustment(int oldValue, int shiftStep)
00420 {
00421     if (oldValue == 0  ||  shiftStep == 0)
00422         return 0;
00423     if (shiftStep > 0)
00424     {
00425         if (oldValue >= 0)
00426             return -(oldValue % shiftStep);
00427         else
00428             return (-oldValue - 1) % shiftStep + 1 - shiftStep;
00429     }
00430     else
00431     {
00432         shiftStep = -shiftStep;
00433         if (oldValue >= 0)
00434             return shiftStep - ((oldValue - 1) % shiftStep + 1);
00435         else
00436             return (-oldValue) % shiftStep;
00437     }
00438 }
00439 
00440 /******************************************************************************
00441 *  Find which spin widget button a mouse event is in.
00442 */
00443 int SpinBox::whichButton(const QPoint& pos)
00444 {
00445     if (upRect().contains(pos))
00446         return UP;
00447     if (downRect().contains(pos))
00448         return DOWN;
00449     return NO_BUTTON;
00450 }
KDE Home | KDE Accessibility Home | Description of Access Keys