summaryrefslogtreecommitdiffstats
path: root/kalarm/lib/spinbox.cpp
blob: bb9ef2441e7370fe407d0d5299022d2568ca2be4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
/*
 *  spinbox.cpp  -  spin box with read-only option and shift-click step value
 *  Program:  kalarm
 *  Copyright © 2002,2004,2008 by David Jarvie <djarvie@kde.org>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include <kdeversion.h>
#include <qlineedit.h>
#include <qobjectlist.h>
#include "spinbox.moc"


SpinBox::SpinBox(QWidget* parent, const char* name)
	: QSpinBox(0, 99999, 1, parent, name),
	  mMinValue(QSpinBox::minValue()),
	  mMaxValue(QSpinBox::maxValue())
{
	init();
}

SpinBox::SpinBox(int minValue, int maxValue, int step, QWidget* parent, const char* name)
	: QSpinBox(minValue, maxValue, step, parent, name),
	  mMinValue(minValue),
	  mMaxValue(maxValue)
{
	init();
}

void SpinBox::init()
{
	int step = QSpinBox::lineStep();
	mLineStep        = step;
	mLineShiftStep   = step;
	mCurrentButton   = NO_BUTTON;
	mShiftMouse      = false;
	mShiftMinBound   = false;
	mShiftMaxBound   = false;
	mSelectOnStep    = true;
	mReadOnly        = false;
	mSuppressSignals = false;
	mEdited          = false;

	// Find the spin widgets which are part of the spin boxes, in order to
	// handle their shift-button presses.
	QObjectList* spinwidgets = queryList("QSpinWidget", 0, false, true);
	QSpinWidget* spin = (QSpinWidget*)spinwidgets->getFirst();
	if (spin)
		spin->installEventFilter(this);   // handle shift-button presses
	delete spinwidgets;
	editor()->installEventFilter(this);   // handle shift-up/down arrow presses

#if KDE_IS_VERSION(3,1,90)
	// Detect when the text field is edited
	connect(editor(), SIGNAL(textChanged(const QString&)), SLOT(textEdited()));
#endif
}

void SpinBox::setReadOnly(bool ro)
{
	if ((int)ro != (int)mReadOnly)
	{
		mReadOnly = ro;
		editor()->setReadOnly(ro);
		if (ro)
			setShiftStepping(false, mCurrentButton);
	}
}

int SpinBox::bound(int val) const
{
	return (val < mMinValue) ? mMinValue : (val > mMaxValue) ? mMaxValue : val;
}

void SpinBox::setMinValue(int val)
{
	mMinValue = val;
	QSpinBox::setMinValue(val);
	mShiftMinBound = false;
}

void SpinBox::setMaxValue(int val)
{
	mMaxValue = val;
	QSpinBox::setMaxValue(val);
	mShiftMaxBound = false;
}

void SpinBox::setLineStep(int step)
{
	mLineStep = step;
	if (!mShiftMouse)
		QSpinBox::setLineStep(step);
}

void SpinBox::setLineShiftStep(int step)
{
	mLineShiftStep = step;
	if (mShiftMouse)
		QSpinBox::setLineStep(step);
}

void SpinBox::stepUp()
{
	int step = QSpinBox::lineStep();
	addValue(step);
	emit stepped(step);
}

void SpinBox::stepDown()
{
	int step = -QSpinBox::lineStep();
	addValue(step);
	emit stepped(step);
}

/******************************************************************************
* Adds a positive or negative increment to the current value, wrapping as appropriate.
* If 'current' is true, any temporary 'shift' values for the range are used instead
* of the real maximum and minimum values.
*/
void SpinBox::addValue(int change, bool current)
{
	int newval = value() + change;
	int maxval = current ? QSpinBox::maxValue() : mMaxValue;
	int minval = current ? QSpinBox::minValue() : mMinValue;
	if (wrapping())
	{
		int range = maxval - minval + 1;
		if (newval > maxval)
			newval = minval + (newval - maxval - 1) % range;
		else if (newval < minval)
			newval = maxval - (minval - 1 - newval) % range;
	}
	else
	{
		if (newval > maxval)
			newval = maxval;
		else if (newval < minval)
			newval = minval;
	}
	setValue(newval);
}

void SpinBox::valueChange()
{
	if (!mSuppressSignals)
	{
		int val = value();
		if (mShiftMinBound  &&  val >= mMinValue)
		{
			// Reinstate the minimum bound now that the value has returned to the normal range.
			QSpinBox::setMinValue(mMinValue);
			mShiftMinBound = false;
		}
		if (mShiftMaxBound  &&  val <= mMaxValue)
		{
			// Reinstate the maximum bound now that the value has returned to the normal range.
			QSpinBox::setMaxValue(mMaxValue);
			mShiftMaxBound = false;
		}

		bool focus = !mSelectOnStep && hasFocus();
		if (focus)
			clearFocus();     // prevent selection of the spin box text
		QSpinBox::valueChange();
		if (focus)
			setFocus();
	}
}

/******************************************************************************
* Called whenever the line edit text is changed.
*/
void SpinBox::textEdited()
{
	mEdited = true;
}

void SpinBox::updateDisplay()
{
	mEdited = false;
	QSpinBox::updateDisplay();
}

/******************************************************************************
* Receives events destined for the spin widget or for the edit field.
*/
bool SpinBox::eventFilter(QObject* obj, QEvent* e)
{
	if (obj == editor())
	{
		int step = 0;
		bool shift = false;
		switch (e->type())
		{
			case QEvent::KeyPress:
			{
				// Up and down arrow keys step the value
				QKeyEvent* ke = (QKeyEvent*)e;
				int key = ke->key();
				if (key == Qt::Key_Up)
					step = 1;
				else if (key == Qt::Key_Down)
					step = -1;
				shift = ((ke->state() & (Qt::ShiftButton | Qt::AltButton)) == Qt::ShiftButton);
				break;
			}
			case QEvent::Wheel:
			{
				QWheelEvent* we = (QWheelEvent*)e;
				step = (we->delta() > 0) ? 1 : -1;
				shift = ((we->state() & (Qt::ShiftButton | Qt::AltButton)) == Qt::ShiftButton);
				break;
			}
#if KDE_IS_VERSION(3,1,90)
			case QEvent::Leave:
				if (mEdited)
					interpretText();
				break;
#endif
			default:
				break;
		}
		if (step)
		{
			if (mReadOnly)
				return true;    // discard up/down arrow keys or wheel
			if (shift)
			{
				// Shift stepping
				int val = value();
				if (step > 0)
					step = mLineShiftStep - val % mLineShiftStep;
				else
					step = - ((val + mLineShiftStep - 1) % mLineShiftStep + 1);
			}
			else
				step = (step > 0) ? mLineStep : -mLineStep;
			addValue(step, false);
			return true;
		}
	}
	else
	{
		int etype = e->type();    // avoid switch compile warnings
		switch (etype)
		{
			case QEvent::MouseButtonPress:
			case QEvent::MouseButtonDblClick:
			{
				QMouseEvent* me = (QMouseEvent*)e;
				if (me->button() == Qt::LeftButton)
				{
					// It's a left button press. Set normal or shift stepping as appropriate.
					if (mReadOnly)
						return true;   // discard the event
					mCurrentButton = whichButton(me->pos());
					if (mCurrentButton == NO_BUTTON)
						return true;
					bool shift = (me->state() & (Qt::ShiftButton | Qt::AltButton)) == Qt::ShiftButton;
					if (setShiftStepping(shift, mCurrentButton))
						return true;     // hide the event from the spin widget
					return false;    // forward event to the destination widget
				}
				break;
			}
			case QEvent::MouseButtonRelease:
			{
				QMouseEvent* me = (QMouseEvent*)e;
				if (me->button() == Qt::LeftButton  &&  mShiftMouse)
				{
					setShiftStepping(false, mCurrentButton);    // cancel shift stepping
					return false;    // forward event to the destination widget
				}
				break;
			}
			case QEvent::MouseMove:
			{
				QMouseEvent* me = (QMouseEvent*)e;
				if (me->state() & Qt::LeftButton)
				{
					// The left button is down. Track which spin button it's in.
					if (mReadOnly)
						return true;   // discard the event
					int newButton = whichButton(me->pos());
					if (newButton != mCurrentButton)
					{
						// The mouse has moved to a new spin button.
						// Set normal or shift stepping as appropriate.
						mCurrentButton = newButton;
						bool shift = (me->state() & (Qt::ShiftButton | Qt::AltButton)) == Qt::ShiftButton;
						if (setShiftStepping(shift, mCurrentButton))
							return true;     // hide the event from the spin widget
					}
					return false;    // forward event to the destination widget
				}
				break;
			}
			case QEvent::Wheel:
			{
				QWheelEvent* we = (QWheelEvent*)e;
				bool shift = (we->state() & (Qt::ShiftButton | Qt::AltButton)) == Qt::ShiftButton;
				if (setShiftStepping(shift, (we->delta() > 0 ? UP : DOWN)))
					return true;     // hide the event from the spin widget
				return false;    // forward event to the destination widget
			}
			case QEvent::KeyPress:
			case QEvent::KeyRelease:
			case QEvent::AccelOverride:      // this is needed to receive Shift presses!
			{
				QKeyEvent* ke = (QKeyEvent*)e;
				int key   = ke->key();
				int state = ke->state();
				if ((state & Qt::LeftButton)
				&&  (key == Qt::Key_Shift  ||  key == Qt::Key_Alt))
				{
					// The left mouse button is down, and the Shift or Alt key has changed
					if (mReadOnly)
						return true;   // discard the event
					state ^= (key == Qt::Key_Shift) ? Qt::ShiftButton : Qt::AltButton;    // new state
					bool shift = (state & (Qt::ShiftButton | Qt::AltButton)) == Qt::ShiftButton;
					if ((!shift && mShiftMouse)  ||  (shift && !mShiftMouse))
					{
						// The effective shift state has changed.
						// Set normal or shift stepping as appropriate.
						if (setShiftStepping(shift, mCurrentButton))
							return true;     // hide the event from the spin widget
					}
				}
				break;
			}
		}
	}
	return QSpinBox::eventFilter(obj, e);
}

/******************************************************************************
* Set spin widget stepping to the normal or shift increment.
*/
bool SpinBox::setShiftStepping(bool shift, int currentButton)
{
	if (currentButton == NO_BUTTON)
		shift = false;
	if (shift  &&  !mShiftMouse)
	{
		/* The value is to be stepped to a multiple of the shift increment.
		 * Adjust the value so that after the spin widget steps it, it will be correct.
		 * Then, if the mouse button is held down, the spin widget will continue to
		 * step by the shift amount.
		 */
		int val = value();
		int step = (currentButton == UP) ? mLineShiftStep : (currentButton == DOWN) ? -mLineShiftStep : 0;
		int adjust = shiftStepAdjustment(val, step);
		mShiftMouse = true;
		if (adjust)
		{
			/* The value is to be stepped by other than the shift increment,
			 * presumably because it is being set to a multiple of the shift
			 * increment. Achieve this by making the adjustment here, and then
			 * allowing the normal step processing to complete the job by
			 * adding/subtracting the normal shift increment.
			 */
			if (!wrapping())
			{
				// Prevent the step from going past the spinbox's range, or
				// to the minimum value if that has a special text unless it is
				// already at the minimum value + 1.
				int newval = val + adjust + step;
				int svt = specialValueText().isEmpty() ? 0 : 1;
				int minval = mMinValue + svt;
				if (newval <= minval  ||  newval >= mMaxValue)
				{
					// Stepping to the minimum or maximum value
					if (svt  &&  newval <= mMinValue  &&  val == mMinValue)
						newval = mMinValue;
					else
						newval = (newval <= minval) ? minval : mMaxValue;
					QSpinBox::setValue(newval);
					emit stepped(step);
					return true;
				}

				// If the interim value will lie outside the spinbox's range,
				// temporarily adjust the range to allow the value to be set.
				int tempval = val + adjust;
				if (tempval < mMinValue)
				{
					QSpinBox::setMinValue(tempval);
					mShiftMinBound = true;
				}
				else if (tempval > mMaxValue)
				{
					QSpinBox::setMaxValue(tempval);
					mShiftMaxBound = true;
				}
			}

			// Don't process changes since this new value will be stepped immediately
			mSuppressSignals = true;
			bool blocked = signalsBlocked();
			blockSignals(true);
			addValue(adjust, true);
			blockSignals(blocked);
			mSuppressSignals = false;
		}
		QSpinBox::setLineStep(mLineShiftStep);
	}
	else if (!shift  &&  mShiftMouse)
	{
		// Reinstate to normal (non-shift) stepping
		QSpinBox::setLineStep(mLineStep);
		QSpinBox::setMinValue(mMinValue);
		QSpinBox::setMaxValue(mMaxValue);
		mShiftMinBound = mShiftMaxBound = false;
		mShiftMouse = false;
	}
	return false;
}

/******************************************************************************
* Return the initial adjustment to the value for a shift step up or down.
* The default is to step up or down to the nearest multiple of the shift
* increment, so the adjustment returned is for stepping up the decrement
* required to round down to a multiple of the shift increment <= current value,
* or for stepping down the increment required to round up to a multiple of the
* shift increment >= current value.
* This method's caller then adjusts the resultant value if necessary to cater
* for the widget's minimum/maximum value, and wrapping.
* This should really be a static method, but it needs to be virtual...
*/
int SpinBox::shiftStepAdjustment(int oldValue, int shiftStep)
{
	if (oldValue == 0  ||  shiftStep == 0)
		return 0;
	if (shiftStep > 0)
	{
		if (oldValue >= 0)
			return -(oldValue % shiftStep);
		else
			return (-oldValue - 1) % shiftStep + 1 - shiftStep;
	}
	else
	{
		shiftStep = -shiftStep;
		if (oldValue >= 0)
			return shiftStep - ((oldValue - 1) % shiftStep + 1);
		else
			return (-oldValue) % shiftStep;
	}
}

/******************************************************************************
*  Find which spin widget button a mouse event is in.
*/
int SpinBox::whichButton(const QPoint& pos)
{
	if (upRect().contains(pos))
		return UP;
	if (downRect().contains(pos))
		return DOWN;
	return NO_BUTTON;
}