/* * spinbox.cpp - spin box with read-only option and shift-click step value * Program: kalarm * Copyright © 2002,2004,2008 by David Jarvie * * 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 #include #include #include "spinbox.moc" SpinBox::SpinBox(TQWidget* parent, const char* name) : TQSpinBox(0, 99999, 1, parent, name), mMinValue(TQSpinBox::minValue()), mMaxValue(TQSpinBox::maxValue()) { init(); } SpinBox::SpinBox(int minValue, int maxValue, int step, TQWidget* parent, const char* name) : TQSpinBox(minValue, maxValue, step, parent, name), mMinValue(minValue), mMaxValue(maxValue) { init(); } void SpinBox::init() { int step = TQSpinBox::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. TQObjectList* spinwidgets = queryList("TQSpinWidget", 0, false, true); TQSpinWidget* spin = (TQSpinWidget*)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(), TQT_SIGNAL(textChanged(const TQString&)), TQT_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; TQSpinBox::setMinValue(val); mShiftMinBound = false; } void SpinBox::setMaxValue(int val) { mMaxValue = val; TQSpinBox::setMaxValue(val); mShiftMaxBound = false; } void SpinBox::setLineStep(int step) { mLineStep = step; if (!mShiftMouse) TQSpinBox::setLineStep(step); } void SpinBox::setLineShiftStep(int step) { mLineShiftStep = step; if (mShiftMouse) TQSpinBox::setLineStep(step); } void SpinBox::stepUp() { int step = TQSpinBox::lineStep(); addValue(step); emit stepped(step); } void SpinBox::stepDown() { int step = -TQSpinBox::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 ? TQSpinBox::maxValue() : mMaxValue; int minval = current ? TQSpinBox::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. TQSpinBox::setMinValue(mMinValue); mShiftMinBound = false; } if (mShiftMaxBound && val <= mMaxValue) { // Reinstate the maximum bound now that the value has returned to the normal range. TQSpinBox::setMaxValue(mMaxValue); mShiftMaxBound = false; } bool focus = !mSelectOnStep && hasFocus(); if (focus) clearFocus(); // prevent selection of the spin box text TQSpinBox::valueChange(); if (focus) setFocus(); } } /****************************************************************************** * Called whenever the line edit text is changed. */ void SpinBox::textEdited() { mEdited = true; } void SpinBox::updateDisplay() { mEdited = false; TQSpinBox::updateDisplay(); } /****************************************************************************** * Receives events destined for the spin widget or for the edit field. */ bool SpinBox::eventFilter(TQObject* obj, TQEvent* e) { if (TQT_BASE_OBJECT(obj) == TQT_BASE_OBJECT(editor())) { int step = 0; bool shift = false; switch (e->type()) { case TQEvent::KeyPress: { // Up and down arrow keys step the value TQKeyEvent* ke = (TQKeyEvent*)e; int key = ke->key(); if (key == TQt::Key_Up) step = 1; else if (key == TQt::Key_Down) step = -1; shift = ((ke->state() & (TQt::ShiftButton | TQt::AltButton)) == TQt::ShiftButton); break; } case TQEvent::Wheel: { TQWheelEvent* we = (TQWheelEvent*)e; step = (we->delta() > 0) ? 1 : -1; shift = ((we->state() & (TQt::ShiftButton | TQt::AltButton)) == TQt::ShiftButton); break; } #if KDE_IS_VERSION(3,1,90) case TQEvent::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 TQEvent::MouseButtonPress: case TQEvent::MouseButtonDblClick: { TQMouseEvent* me = (TQMouseEvent*)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() & (TQt::ShiftButton | TQt::AltButton)) == TQt::ShiftButton; if (setShiftStepping(shift, mCurrentButton)) return true; // hide the event from the spin widget return false; // forward event to the destination widget } break; } case TQEvent::MouseButtonRelease: { TQMouseEvent* me = (TQMouseEvent*)e; if (me->button() == Qt::LeftButton && mShiftMouse) { setShiftStepping(false, mCurrentButton); // cancel shift stepping return false; // forward event to the destination widget } break; } case TQEvent::MouseMove: { TQMouseEvent* me = (TQMouseEvent*)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() & (TQt::ShiftButton | TQt::AltButton)) == TQt::ShiftButton; if (setShiftStepping(shift, mCurrentButton)) return true; // hide the event from the spin widget } return false; // forward event to the destination widget } break; } case TQEvent::Wheel: { TQWheelEvent* we = (TQWheelEvent*)e; bool shift = (we->state() & (TQt::ShiftButton | TQt::AltButton)) == TQt::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 TQEvent::KeyPress: case TQEvent::KeyRelease: case TQEvent::AccelOverride: // this is needed to receive Shift presses! { TQKeyEvent* ke = (TQKeyEvent*)e; int key = ke->key(); int state = ke->state(); if ((state & Qt::LeftButton) && (key == TQt::Key_Shift || key == TQt::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 == TQt::Key_Shift) ? TQt::ShiftButton : TQt::AltButton; // new state bool shift = (state & (TQt::ShiftButton | TQt::AltButton)) == TQt::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 TQSpinBox::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; TQSpinBox::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) { TQSpinBox::setMinValue(tempval); mShiftMinBound = true; } else if (tempval > mMaxValue) { TQSpinBox::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; } TQSpinBox::setLineStep(mLineShiftStep); } else if (!shift && mShiftMouse) { // Reinstate to normal (non-shift) stepping TQSpinBox::setLineStep(mLineStep); TQSpinBox::setMinValue(mMinValue); TQSpinBox::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 TQPoint& pos) { if (upRect().contains(pos)) return UP; if (downRect().contains(pos)) return DOWN; return NO_BUTTON; }