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 }
void addValue(int change)
Adds a value to the current value of the spin box.
Definition: spinbox.h:71
virtual void setReadOnly(bool readOnly)
Sets whether the spin box can be changed by the user.
Definition: spinbox.cpp:69
void stepped(int step)
Signal emitted when the spin box's value is stepped (by the shifted or unshifted increment).
int bound(int val) const
Returns the specified value clamped to the range of the spin box.
Definition: spinbox.cpp:80
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
virtual void stepDown()
Decrements the value of the spin box by the unshifted step increment.
Definition: spinbox.cpp:120
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
void setLineShiftStep(int step)
Sets the shifted step increment, i.e.
Definition: spinbox.cpp:106
void setMinValue(int val)
Sets the minimum value of the spin box.
Definition: spinbox.cpp:85
void setLineStep(int step)
Sets the unshifted step increment, i.e.
Definition: spinbox.cpp:99
virtual void updateDisplay()
Updates the contents of the embedded TQLineEdit to reflect the current value using mapValueToText().
Definition: spinbox.cpp:190
virtual void valueChange()
A virtual method called whenever the value of the spin box has changed.
Definition: spinbox.cpp:155
virtual bool eventFilter(TQObject *, TQEvent *)
Receives events destined for the spin widget or for the edit field.
Definition: spinbox.cpp:199
void setMaxValue(int val)
Sets the maximum value of the spin box.
Definition: spinbox.cpp:92