spinbox.cpp 16.5 KB
Newer Older
1 2 3
/*
 *  spinbox.cpp  -  spin box with read-only option and shift-click step value
 *  Program:  kalarm
David Jarvie's avatar
David Jarvie committed
4
 *  Copyright © 2002,2004-2008 by David Jarvie <djarvie@kde.org>
5 6 7 8 9 10 11 12 13 14 15
 *
 *  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.
 *
David Jarvie's avatar
David Jarvie committed
16 17 18
 *  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.
19 20
 */

Laurent Montel's avatar
Laurent Montel committed
21
#include "spinbox.h"
22

23
#include <QLineEdit>
24
#include <QObject>
David Jarvie's avatar
David Jarvie committed
25
#include <QApplication>
David Jarvie's avatar
David Jarvie committed
26 27
#include <QStyle>
#include <QStyleOptionSpinBox>
David Jarvie's avatar
David Jarvie committed
28
#include <QPainter>
Laurent Montel's avatar
Qt3to4  
Laurent Montel committed
29 30 31
#include <QMouseEvent>
#include <QKeyEvent>
#include <QEvent>
David Jarvie's avatar
David Jarvie committed
32
#include <qdebug.h>
David Jarvie's avatar
David Jarvie committed
33

34

David Jarvie's avatar
David Jarvie committed
35
SpinBox::SpinBox(QWidget* parent)
36 37 38
    : QSpinBox(parent),
      mMinValue(QSpinBox::minimum()),
      mMaxValue(QSpinBox::maximum())
39
{
40 41
    setRange(0, 99999);
    init();
42 43
}

44
SpinBox::SpinBox(int minValue, int maxValue, QWidget* parent)
45 46 47
    : QSpinBox(parent),
      mMinValue(minValue),
      mMaxValue(maxValue)
48
{
49 50
    setRange(minValue, maxValue);
    init();
51 52 53 54
}

void SpinBox::init()
{
55 56 57 58 59 60 61 62 63 64 65 66
    int step = QSpinBox::singleStep();
    mLineStep        = step;
    mLineShiftStep   = step;
    mCurrentButton   = NO_BUTTON;
    mShiftMouse      = false;
    mShiftMinBound   = false;
    mShiftMaxBound   = false;
    mSelectOnStep    = true;
    mUpDownOnly      = false;
    mReadOnly        = false;
    mSuppressSignals = false;
    mEdited          = false;
67

68
    lineEdit()->installEventFilter(this);   // handle shift-up/down arrow presses
69

70
    // Detect when the text field is edited
Laurent Montel's avatar
Laurent Montel committed
71
    connect(lineEdit(), SIGNAL(textChanged(QString)), SLOT(textEdited()));
Laurent Montel's avatar
Laurent Montel committed
72
    connect(this, static_cast<void (SpinBox::*)(int)>(&SpinBox::valueChanged), this, &SpinBox::valueChange);
73 74 75 76
}

void SpinBox::setReadOnly(bool ro)
{
77 78 79 80 81 82 83
    if ((int)ro != (int)mReadOnly)
    {
        mReadOnly = ro;
        lineEdit()->setReadOnly(ro);
        if (ro)
            setShiftStepping(false, mCurrentButton);
    }
84 85 86 87
}

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

David Jarvie's avatar
David Jarvie committed
91
void SpinBox::setMinimum(int val)
92
{
93 94 95
    mMinValue = val;
    QSpinBox::setMinimum(val);
    mShiftMinBound = false;
96 97
}

David Jarvie's avatar
David Jarvie committed
98
void SpinBox::setMaximum(int val)
99
{
100 101 102
    mMaxValue = val;
    QSpinBox::setMaximum(val);
    mShiftMaxBound = false;
103 104
}

David Jarvie's avatar
David Jarvie committed
105
void SpinBox::setSingleStep(int step)
106
{
107 108 109
    mLineStep = step;
    if (!mShiftMouse)
        QSpinBox::setSingleStep(step);
110 111
}

David Jarvie's avatar
David Jarvie committed
112
void SpinBox::setSingleShiftStep(int step)
113
{
114 115 116
    mLineShiftStep = step;
    if (mShiftMouse)
        QSpinBox::setSingleStep(step);
117 118
}

David Jarvie's avatar
David Jarvie committed
119
void SpinBox::stepBy(int steps)
120
{
121 122 123
    int increment = steps * QSpinBox::singleStep();
    addValue(increment);
    emit stepped(increment);
124 125
}

126 127 128 129 130
/******************************************************************************
* 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.
*/
131 132
void SpinBox::addValue(int change, bool current)
{
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
    int newval = value() + change;
    int maxval = current ? QSpinBox::maximum() : mMaxValue;
    int minval = current ? QSpinBox::minimum() : 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);
152 153 154 155
}

void SpinBox::valueChange()
{
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
    if (!mSuppressSignals)
    {
        int val = value();
        if (mShiftMinBound  &&  val >= mMinValue)
        {
            // Reinstate the minimum bound now that the value has returned to the normal range.
            QSpinBox::setMinimum(mMinValue);
            mShiftMinBound = false;
        }
        if (mShiftMaxBound  &&  val <= mMaxValue)
        {
            // Reinstate the maximum bound now that the value has returned to the normal range.
            QSpinBox::setMaximum(mMaxValue);
            mShiftMaxBound = false;
        }

         if (!mSelectOnStep && hasFocus())
            lineEdit()->deselect();   // prevent selection of the spin box text
    }
175 176
}

177 178 179
/******************************************************************************
* Called whenever the line edit text is changed.
*/
180 181
void SpinBox::textEdited()
{
182
    mEdited = true;
183 184
}

185 186 187
/******************************************************************************
* Receives events destined for the spin widget or for the edit field.
*/
188 189
bool SpinBox::eventFilter(QObject* obj, QEvent* e)
{
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
    if (obj == lineEdit())
    {
        int step = 0;
        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;
                break;
            }
            case QEvent::Wheel:
            {
                QWheelEvent* we = (QWheelEvent*)e;
                step = (we->delta() > 0) ? 1 : -1;
                break;
            }
            default:
                break;
        }
        if (step)
        {
            if (mReadOnly)
                return true;    // discard up/down arrow keys
            QInputEvent* ie = (QInputEvent*)e;
            if ((ie->modifiers() & (Qt::ShiftModifier | Qt::AltModifier)) == Qt::ShiftModifier)
            {
                // 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;
        }
    }
    return QSpinBox::eventFilter(obj, e);
David Jarvie's avatar
David Jarvie committed
236 237
}

238 239
void SpinBox::focusOutEvent(QFocusEvent* e)
{
240 241 242 243 244 245
    if (mEdited)
    {
        interpretText();
        mEdited = false;
    }
    QSpinBox::focusOutEvent(e);
246 247
}

David Jarvie's avatar
David Jarvie committed
248 249
void SpinBox::mousePressEvent(QMouseEvent* e)
{
250 251
    if (!clickEvent(e))
        QSpinBox::mousePressEvent(e);
David Jarvie's avatar
David Jarvie committed
252 253 254 255
}

void SpinBox::mouseDoubleClickEvent(QMouseEvent* e)
{
256 257
    if (!clickEvent(e))
        QSpinBox::mouseDoubleClickEvent(e);
David Jarvie's avatar
David Jarvie committed
258 259 260 261
}

bool SpinBox::clickEvent(QMouseEvent* e)
{
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
    if (e->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(e->pos());
        if (mCurrentButton == NO_BUTTON)
        {
            e->accept();
            return true;
        }
        bool shift = (e->modifiers() & (Qt::ShiftModifier | Qt::AltModifier)) == Qt::ShiftModifier;
        if (setShiftStepping(shift, mCurrentButton))
        {
            e->accept();
            return true;     // hide the event from the spin widget
        }
    }
    return false;
David Jarvie's avatar
David Jarvie committed
281 282
}

283 284
void SpinBox::wheelEvent(QWheelEvent* e)
{
285 286 287 288 289 290 291 292 293
    if (mReadOnly)
        return;   // discard the event
    bool shift = (e->modifiers() & (Qt::ShiftModifier | Qt::AltModifier)) == Qt::ShiftModifier;
    if (setShiftStepping(shift, (e->delta() > 0 ? UP : DOWN)))
    {
        e->accept();
        return;     // hide the event from the spin widget
    }
    QSpinBox::wheelEvent(e);
294 295
}

David Jarvie's avatar
David Jarvie committed
296 297
void SpinBox::mouseReleaseEvent(QMouseEvent* e)
{
298 299 300
    if (e->button() == Qt::LeftButton  &&  mShiftMouse)
        setShiftStepping(false, mCurrentButton);    // cancel shift stepping
    QSpinBox::mouseReleaseEvent(e);
David Jarvie's avatar
David Jarvie committed
301 302 303 304
}

void SpinBox::mouseMoveEvent(QMouseEvent* e)
{
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324
    if (e->buttons() & Qt::LeftButton)
    {
        // The left button is down. Track which spin button it's in.
        if (mReadOnly)
            return;   // discard the event
        int newButton = whichButton(e->pos());
        if (newButton != mCurrentButton)
        {
            // The mouse has moved to a new spin button.
            // Set normal or shift stepping as appropriate.
            mCurrentButton = newButton;
            bool shift = (e->modifiers() & (Qt::ShiftModifier | Qt::AltModifier)) == Qt::ShiftModifier;
            if (setShiftStepping(shift, mCurrentButton))
            {
                e->accept();
                return;     // hide the event from the spin widget
            }
        }
    }
    QSpinBox::mouseMoveEvent(e);
David Jarvie's avatar
David Jarvie committed
325 326 327 328
}

void SpinBox::keyPressEvent(QKeyEvent* e)
{
329 330
    if (!keyEvent(e))
        QSpinBox::keyPressEvent(e);
David Jarvie's avatar
David Jarvie committed
331 332 333 334
}

void SpinBox::keyReleaseEvent(QKeyEvent* e)
{
335 336
    if (!keyEvent(e))
        QSpinBox::keyReleaseEvent(e);
David Jarvie's avatar
David Jarvie committed
337 338 339 340
}

bool SpinBox::keyEvent(QKeyEvent* e)
{
341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361
    int key   = e->key();
    int state = e->modifiers();
    if ((QApplication::mouseButtons() & 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
        bool shift = (state & (Qt::ShiftModifier | Qt::AltModifier)) == Qt::ShiftModifier;
        if ((!shift && mShiftMouse)  ||  (shift && !mShiftMouse))
        {
            // The effective shift state has changed.
            // Set normal or shift stepping as appropriate.
            if (setShiftStepping(shift, mCurrentButton))
            {
                e->accept();
                return true;     // hide the event from the spin widget
            }
        }
    }
    return false;
362 363
}

364 365 366
/******************************************************************************
* Set spin widget stepping to the normal or shift increment.
*/
367
bool SpinBox::setShiftStepping(bool shift, int currentButton)
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
    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::setMinimum(tempval);
                    mShiftMinBound = true;
                }
                else if (tempval > mMaxValue)
                {
                    QSpinBox::setMaximum(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::setSingleStep(mLineShiftStep);
    }
    else if (!shift  &&  mShiftMouse)
    {
        // Reinstate to normal (non-shift) stepping
        QSpinBox::setSingleStep(mLineStep);
        QSpinBox::setMinimum(mMinValue);
        QSpinBox::setMaximum(mMaxValue);
        mShiftMinBound = mShiftMaxBound = false;
        mShiftMouse = false;
    }
    return false;
445 446
}

447 448 449 450 451 452 453 454 455 456 457 458 459
/******************************************************************************
* 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)
{
460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476
    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;
    }
477 478 479 480 481
}

/******************************************************************************
*  Find which spin widget button a mouse event is in.
*/
482 483
int SpinBox::whichButton(const QPoint& pos)
{
484 485 486 487 488 489 490
    QStyleOptionSpinBox option;
    initStyleOption(option);
    if (style()->subControlRect(QStyle::CC_SpinBox, &option, QStyle::SC_SpinBoxUp).contains(pos))
        return UP;
    if (style()->subControlRect(QStyle::CC_SpinBox, &option, QStyle::SC_SpinBoxDown).contains(pos))
        return DOWN;
    return NO_BUTTON;
491
}
David Jarvie's avatar
David Jarvie committed
492 493 494

QRect SpinBox::upRect() const
{
495 496 497
    QStyleOptionSpinBox option;
    initStyleOption(option);
    return style()->subControlRect(QStyle::CC_SpinBox, &option, QStyle::SC_SpinBoxUp);
David Jarvie's avatar
David Jarvie committed
498 499 500 501
}

QRect SpinBox::downRect() const
{
502 503 504
    QStyleOptionSpinBox option;
    initStyleOption(option);
    return style()->subControlRect(QStyle::CC_SpinBox, &option, QStyle::SC_SpinBoxDown);
David Jarvie's avatar
David Jarvie committed
505
}
David Jarvie's avatar
David Jarvie committed
506 507 508

QRect SpinBox::upDownRect() const
{
509 510 511 512
    QStyleOptionSpinBox option;
    initStyleOption(option);
    return style()->subControlRect(QStyle::CC_SpinBox, &option, QStyle::SC_SpinBoxUp)
         | style()->subControlRect(QStyle::CC_SpinBox, &option, QStyle::SC_SpinBoxDown);
David Jarvie's avatar
David Jarvie committed
513 514 515 516
}

void SpinBox::paintEvent(QPaintEvent* pe)
{
517 518 519 520 521 522 523 524 525
    if (mUpDownOnly)
    {
        QStyleOptionSpinBox option;
        initStyleOption(option);
        QPainter painter(this);
        style()->drawComplexControl(QStyle::CC_SpinBox, &option, &painter, this);
    }
    else
        QSpinBox::paintEvent(pe);
David Jarvie's avatar
David Jarvie committed
526 527 528 529
}

void SpinBox::initStyleOption(QStyleOptionSpinBox& so) const
{
530 531
    so.init(this);
//    so.activeSubControls = ??;
532 533
    so.subControls   = mUpDownOnly ? (QStyle::SC_SpinBoxUp | QStyle::SC_SpinBoxDown | QStyle::SC_SpinBoxFrame)
                                   : (QStyle::SC_SpinBoxUp | QStyle::SC_SpinBoxDown | QStyle::SC_SpinBoxFrame | QStyle::SC_SpinBoxEditField);
534 535 536
    so.buttonSymbols = buttonSymbols();
    so.frame         = hasFrame();
    so.stepEnabled   = stepEnabled();
David Jarvie's avatar
David Jarvie committed
537
}
538

539
// vim: et sw=4: