kalarm/lib

spinbox.cpp
1 /*
2  * spinbox.cpp - spin box with read-only option and shift-click step value
3  * Program: kalarm
4  * Copyright © 2002,2004,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 <tqlineedit.h>
22 #include <tqobjectlist.h>
23 #include "spinbox.moc"
24 
25 
26 SpinBox::SpinBox(TQWidget* parent, const char* name)
27  : TQSpinBox(0, 99999, 1, parent, name),
28  mMinValue(TQSpinBox::minValue()),
29  mMaxValue(TQSpinBox::maxValue())
30 {
31  init();
32 }
33 
34 SpinBox::SpinBox(int minValue, int maxValue, int step, TQWidget* parent, const char* name)
35  : TQSpinBox(minValue, maxValue, step, parent, name),
36  mMinValue(minValue),
37  mMaxValue(maxValue)
38 {
39  init();
40 }
41 
42 void SpinBox::init()
43 {
44  int step = TQSpinBox::lineStep();
45  mLineStep = step;
46  mLineShiftStep = step;
47  mCurrentButton = NO_BUTTON;
48  mShiftMouse = false;
49  mShiftMinBound = false;
50  mShiftMaxBound = false;
51  mSelectOnStep = true;
52  mReadOnly = false;
53  mSuppressSignals = false;
54  mEdited = false;
55 
56  // Find the spin widgets which are part of the spin boxes, in order to
57  // handle their shift-button presses.
58  TQObjectList* spinwidgets = queryList("TQSpinWidget", 0, false, true);
59  TQSpinWidget* spin = (TQSpinWidget*)spinwidgets->getFirst();
60  if (spin)
61  spin->installEventFilter(this); // handle shift-button presses
62  delete spinwidgets;
63  editor()->installEventFilter(this); // handle shift-up/down arrow presses
64 
65  // Detect when the text field is edited
66  connect(editor(), TQ_SIGNAL(textChanged(const TQString&)), TQ_SLOT(textEdited()));
67 }
68 
69 void SpinBox::setReadOnly(bool ro)
70 {
71  if ((int)ro != (int)mReadOnly)
72  {
73  mReadOnly = ro;
74  editor()->setReadOnly(ro);
75  if (ro)
76  setShiftStepping(false, mCurrentButton);
77  }
78 }
79 
80 int SpinBox::bound(int val) const
81 {
82  return (val < mMinValue) ? mMinValue : (val > mMaxValue) ? mMaxValue : val;
83 }
84 
85 void SpinBox::setMinValue(int val)
86 {
87  mMinValue = val;
88  TQSpinBox::setMinValue(val);
89  mShiftMinBound = false;
90 }
91 
92 void SpinBox::setMaxValue(int val)
93 {
94  mMaxValue = val;
95  TQSpinBox::setMaxValue(val);
96  mShiftMaxBound = false;
97 }
98 
99 void SpinBox::setLineStep(int step)
100 {
101  mLineStep = step;
102  if (!mShiftMouse)
103  TQSpinBox::setLineStep(step);
104 }
105 
107 {
108  mLineShiftStep = step;
109  if (mShiftMouse)
110  TQSpinBox::setLineStep(step);
111 }
112 
114 {
115  int step = TQSpinBox::lineStep();
116  addValue(step);
117  emit stepped(step);
118 }
119 
121 {
122  int step = -TQSpinBox::lineStep();
123  addValue(step);
124  emit stepped(step);
125 }
126 
127 /******************************************************************************
128 * Adds a positive or negative increment to the current value, wrapping as appropriate.
129 * If 'current' is true, any temporary 'shift' values for the range are used instead
130 * of the real maximum and minimum values.
131 */
132 void SpinBox::addValue(int change, bool current)
133 {
134  int newval = value() + change;
135  int maxval = current ? TQSpinBox::maxValue() : mMaxValue;
136  int minval = current ? TQSpinBox::minValue() : mMinValue;
137  if (wrapping())
138  {
139  int range = maxval - minval + 1;
140  if (newval > maxval)
141  newval = minval + (newval - maxval - 1) % range;
142  else if (newval < minval)
143  newval = maxval - (minval - 1 - newval) % range;
144  }
145  else
146  {
147  if (newval > maxval)
148  newval = maxval;
149  else if (newval < minval)
150  newval = minval;
151  }
152  setValue(newval);
153 }
154 
156 {
157  if (!mSuppressSignals)
158  {
159  int val = value();
160  if (mShiftMinBound && val >= mMinValue)
161  {
162  // Reinstate the minimum bound now that the value has returned to the normal range.
163  TQSpinBox::setMinValue(mMinValue);
164  mShiftMinBound = false;
165  }
166  if (mShiftMaxBound && val <= mMaxValue)
167  {
168  // Reinstate the maximum bound now that the value has returned to the normal range.
169  TQSpinBox::setMaxValue(mMaxValue);
170  mShiftMaxBound = false;
171  }
172 
173  bool focus = !mSelectOnStep && hasFocus();
174  if (focus)
175  clearFocus(); // prevent selection of the spin box text
176  TQSpinBox::valueChange();
177  if (focus)
178  setFocus();
179  }
180 }
181 
182 /******************************************************************************
183 * Called whenever the line edit text is changed.
184 */
185 void SpinBox::textEdited()
186 {
187  mEdited = true;
188 }
189 
191 {
192  mEdited = false;
193  TQSpinBox::updateDisplay();
194 }
195 
196 /******************************************************************************
197 * Receives events destined for the spin widget or for the edit field.
198 */
199 bool SpinBox::eventFilter(TQObject* obj, TQEvent* e)
200 {
201  if (obj == editor())
202  {
203  int step = 0;
204  bool shift = false;
205  switch (e->type())
206  {
207  case TQEvent::KeyPress:
208  {
209  // Up and down arrow keys step the value
210  TQKeyEvent* ke = (TQKeyEvent*)e;
211  int key = ke->key();
212  if (key == TQt::Key_Up)
213  step = 1;
214  else if (key == TQt::Key_Down)
215  step = -1;
216  shift = ((ke->state() & (TQt::ShiftButton | TQt::AltButton)) == TQt::ShiftButton);
217  break;
218  }
219  case TQEvent::Wheel:
220  {
221  TQWheelEvent* we = (TQWheelEvent*)e;
222  step = (we->delta() > 0) ? 1 : -1;
223  shift = ((we->state() & (TQt::ShiftButton | TQt::AltButton)) == TQt::ShiftButton);
224  break;
225  }
226  case TQEvent::Leave:
227  if (mEdited)
228  interpretText();
229  break;
230  default:
231  break;
232  }
233  if (step)
234  {
235  if (mReadOnly)
236  return true; // discard up/down arrow keys or wheel
237  if (shift)
238  {
239  // Shift stepping
240  int val = value();
241  if (step > 0)
242  step = mLineShiftStep - val % mLineShiftStep;
243  else
244  step = - ((val + mLineShiftStep - 1) % mLineShiftStep + 1);
245  }
246  else
247  step = (step > 0) ? mLineStep : -mLineStep;
248  addValue(step, false);
249  return true;
250  }
251  }
252  else
253  {
254  int etype = e->type(); // avoid switch compile warnings
255  switch (etype)
256  {
257  case TQEvent::MouseButtonPress:
258  case TQEvent::MouseButtonDblClick:
259  {
260  TQMouseEvent* me = (TQMouseEvent*)e;
261  if (me->button() == TQt::LeftButton)
262  {
263  // It's a left button press. Set normal or shift stepping as appropriate.
264  if (mReadOnly)
265  return true; // discard the event
266  mCurrentButton = whichButton(me->pos());
267  if (mCurrentButton == NO_BUTTON)
268  return true;
269  bool shift = (me->state() & (TQt::ShiftButton | TQt::AltButton)) == TQt::ShiftButton;
270  if (setShiftStepping(shift, mCurrentButton))
271  return true; // hide the event from the spin widget
272  return false; // forward event to the destination widget
273  }
274  break;
275  }
276  case TQEvent::MouseButtonRelease:
277  {
278  TQMouseEvent* me = (TQMouseEvent*)e;
279  if (me->button() == TQt::LeftButton && mShiftMouse)
280  {
281  setShiftStepping(false, mCurrentButton); // cancel shift stepping
282  return false; // forward event to the destination widget
283  }
284  break;
285  }
286  case TQEvent::MouseMove:
287  {
288  TQMouseEvent* me = (TQMouseEvent*)e;
289  if (me->state() & TQt::LeftButton)
290  {
291  // The left button is down. Track which spin button it's in.
292  if (mReadOnly)
293  return true; // discard the event
294  int newButton = whichButton(me->pos());
295  if (newButton != mCurrentButton)
296  {
297  // The mouse has moved to a new spin button.
298  // Set normal or shift stepping as appropriate.
299  mCurrentButton = newButton;
300  bool shift = (me->state() & (TQt::ShiftButton | TQt::AltButton)) == TQt::ShiftButton;
301  if (setShiftStepping(shift, mCurrentButton))
302  return true; // hide the event from the spin widget
303  }
304  return false; // forward event to the destination widget
305  }
306  break;
307  }
308  case TQEvent::Wheel:
309  {
310  TQWheelEvent* we = (TQWheelEvent*)e;
311  bool shift = (we->state() & (TQt::ShiftButton | TQt::AltButton)) == TQt::ShiftButton;
312  if (setShiftStepping(shift, (we->delta() > 0 ? UP : DOWN)))
313  return true; // hide the event from the spin widget
314  return false; // forward event to the destination widget
315  }
316  case TQEvent::KeyPress:
317  case TQEvent::KeyRelease:
318  case TQEvent::AccelOverride: // this is needed to receive Shift presses!
319  {
320  TQKeyEvent* ke = (TQKeyEvent*)e;
321  int key = ke->key();
322  int state = ke->state();
323  if ((state & TQt::LeftButton)
324  && (key == TQt::Key_Shift || key == TQt::Key_Alt))
325  {
326  // The left mouse button is down, and the Shift or Alt key has changed
327  if (mReadOnly)
328  return true; // discard the event
329  state ^= (key == TQt::Key_Shift) ? TQt::ShiftButton : TQt::AltButton; // new state
330  bool shift = (state & (TQt::ShiftButton | TQt::AltButton)) == TQt::ShiftButton;
331  if ((!shift && mShiftMouse) || (shift && !mShiftMouse))
332  {
333  // The effective shift state has changed.
334  // Set normal or shift stepping as appropriate.
335  if (setShiftStepping(shift, mCurrentButton))
336  return true; // hide the event from the spin widget
337  }
338  }
339  break;
340  }
341  }
342  }
343  return TQSpinBox::eventFilter(obj, e);
344 }
345 
346 /******************************************************************************
347 * Set spin widget stepping to the normal or shift increment.
348 */
349 bool SpinBox::setShiftStepping(bool shift, int currentButton)
350 {
351  if (currentButton == NO_BUTTON)
352  shift = false;
353  if (shift && !mShiftMouse)
354  {
355  /* The value is to be stepped to a multiple of the shift increment.
356  * Adjust the value so that after the spin widget steps it, it will be correct.
357  * Then, if the mouse button is held down, the spin widget will continue to
358  * step by the shift amount.
359  */
360  int val = value();
361  int step = (currentButton == UP) ? mLineShiftStep : (currentButton == DOWN) ? -mLineShiftStep : 0;
362  int adjust = shiftStepAdjustment(val, step);
363  mShiftMouse = true;
364  if (adjust)
365  {
366  /* The value is to be stepped by other than the shift increment,
367  * presumably because it is being set to a multiple of the shift
368  * increment. Achieve this by making the adjustment here, and then
369  * allowing the normal step processing to complete the job by
370  * adding/subtracting the normal shift increment.
371  */
372  if (!wrapping())
373  {
374  // Prevent the step from going past the spinbox's range, or
375  // to the minimum value if that has a special text unless it is
376  // already at the minimum value + 1.
377  int newval = val + adjust + step;
378  int svt = specialValueText().isEmpty() ? 0 : 1;
379  int minval = mMinValue + svt;
380  if (newval <= minval || newval >= mMaxValue)
381  {
382  // Stepping to the minimum or maximum value
383  if (svt && newval <= mMinValue && val == mMinValue)
384  newval = mMinValue;
385  else
386  newval = (newval <= minval) ? minval : mMaxValue;
387  TQSpinBox::setValue(newval);
388  emit stepped(step);
389  return true;
390  }
391 
392  // If the interim value will lie outside the spinbox's range,
393  // temporarily adjust the range to allow the value to be set.
394  int tempval = val + adjust;
395  if (tempval < mMinValue)
396  {
397  TQSpinBox::setMinValue(tempval);
398  mShiftMinBound = true;
399  }
400  else if (tempval > mMaxValue)
401  {
402  TQSpinBox::setMaxValue(tempval);
403  mShiftMaxBound = true;
404  }
405  }
406 
407  // Don't process changes since this new value will be stepped immediately
408  mSuppressSignals = true;
409  bool blocked = signalsBlocked();
410  blockSignals(true);
411  addValue(adjust, true);
412  blockSignals(blocked);
413  mSuppressSignals = false;
414  }
415  TQSpinBox::setLineStep(mLineShiftStep);
416  }
417  else if (!shift && mShiftMouse)
418  {
419  // Reinstate to normal (non-shift) stepping
420  TQSpinBox::setLineStep(mLineStep);
421  TQSpinBox::setMinValue(mMinValue);
422  TQSpinBox::setMaxValue(mMaxValue);
423  mShiftMinBound = mShiftMaxBound = false;
424  mShiftMouse = false;
425  }
426  return false;
427 }
428 
429 /******************************************************************************
430 * Return the initial adjustment to the value for a shift step up or down.
431 * The default is to step up or down to the nearest multiple of the shift
432 * increment, so the adjustment returned is for stepping up the decrement
433 * required to round down to a multiple of the shift increment <= current value,
434 * or for stepping down the increment required to round up to a multiple of the
435 * shift increment >= current value.
436 * This method's caller then adjusts the resultant value if necessary to cater
437 * for the widget's minimum/maximum value, and wrapping.
438 * This should really be a static method, but it needs to be virtual...
439 */
440 int SpinBox::shiftStepAdjustment(int oldValue, int shiftStep)
441 {
442  if (oldValue == 0 || shiftStep == 0)
443  return 0;
444  if (shiftStep > 0)
445  {
446  if (oldValue >= 0)
447  return -(oldValue % shiftStep);
448  else
449  return (-oldValue - 1) % shiftStep + 1 - shiftStep;
450  }
451  else
452  {
453  shiftStep = -shiftStep;
454  if (oldValue >= 0)
455  return shiftStep - ((oldValue - 1) % shiftStep + 1);
456  else
457  return (-oldValue) % shiftStep;
458  }
459 }
460 
461 /******************************************************************************
462 * Find which spin widget button a mouse event is in.
463 */
464 int SpinBox::whichButton(const TQPoint& pos)
465 {
466  if (upRect().contains(pos))
467  return UP;
468  if (downRect().contains(pos))
469  return DOWN;
470  return NO_BUTTON;
471 }
virtual bool eventFilter(TQObject *, TQEvent *)
Receives events destined for the spin widget or for the edit field.
Definition: spinbox.cpp:199
virtual void valueChange()
A virtual method called whenever the value of the spin box has changed.
Definition: spinbox.cpp:155
void stepped(int step)
Signal emitted when the spin box's value is stepped (by the shifted or unshifted increment).
void setLineShiftStep(int step)
Sets the shifted step increment, i.e.
Definition: spinbox.cpp:106
virtual void setReadOnly(bool readOnly)
Sets whether the spin box can be changed by the user.
Definition: spinbox.cpp:69
void setMinValue(int val)
Sets the minimum value of the spin box.
Definition: spinbox.cpp:85
virtual void stepUp()
Increments the value of the spin box by the unshifted step increment.
Definition: spinbox.cpp:113
SpinBox(TQWidget *parent=0, const char *name=0)
Constructor.
Definition: spinbox.cpp:26
void setLineStep(int step)
Sets the unshifted step increment, i.e.
Definition: spinbox.cpp:99
virtual int shiftStepAdjustment(int oldValue, int shiftStep)
Returns the initial adjustment to the value for a shift step up or down.
Definition: spinbox.cpp:440
int bound(int val) const
Returns the specified value clamped to the range of the spin box.
Definition: spinbox.cpp:80
void addValue(int change)
Adds a value to the current value of the spin box.
Definition: spinbox.h:71
virtual void stepDown()
Decrements the value of the spin box by the unshifted step increment.
Definition: spinbox.cpp:120
void setMaxValue(int val)
Sets the maximum value of the spin box.
Definition: spinbox.cpp:92
virtual void updateDisplay()
Updates the contents of the embedded TQLineEdit to reflect the current value using mapValueToText().
Definition: spinbox.cpp:190