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 }