diff options
Diffstat (limited to 'kalarm/lib/spinbox.cpp')
-rw-r--r-- | kalarm/lib/spinbox.cpp | 476 |
1 files changed, 476 insertions, 0 deletions
diff --git a/kalarm/lib/spinbox.cpp b/kalarm/lib/spinbox.cpp new file mode 100644 index 000000000..bb9ef2441 --- /dev/null +++ b/kalarm/lib/spinbox.cpp @@ -0,0 +1,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; +} |