Commit 873e617e authored by David Jarvie's avatar David Jarvie
Browse files

Bug 443062: Allow stepping hour in time edit fields, in Breeze app style

When using application styles which place the two spin arrows on
different sides of the spinbox, allow the hour value to be stepped by
using the control key. Also don't round the hour value when stepping
with the control key pressed, when using other application styles.
parent b6060e19
Pipeline #90050 canceled with stage
KAlarm Change Log
=== Version 3.3.2 (KDE Applications 21.08.3) --- 19 October 2021 ===
=== Version 3.3.2 (KDE Applications 21.08.3) --- 20 October 2021 ===
* Make time edit field arrows work with Breeze application style [KDE Bug 443062]
=== Version 3.3.1 (KDE Applications 21.08.1) --- 29 August 2021 ===
......
/*
* spinbox.cpp - spin box with read-only option and shift-click step value
* Program: kalarm
* SPDX-FileCopyrightText: 2002-2020 David Jarvie <djarvie@kde.org>
* SPDX-FileCopyrightText: 2002-2021 David Jarvie <djarvie@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "spinbox.h"
#include "spinbox_p.h"
#include "kalarm_debug.h"
......@@ -44,6 +45,7 @@ void SpinBox::init()
int step = QSpinBox::singleStep();
mLineStep = step;
mLineShiftStep = step;
mLineControlStep = 0;
mCurrentButton = NO_BUTTON;
lineEdit()->installEventFilter(this); // handle shift-up/down arrow presses
......@@ -53,6 +55,11 @@ void SpinBox::init()
connect(this, &SpinBox::valueChanged, this, &SpinBox::valueChange);
}
SpinBox::~SpinBox()
{
delete mControlStyle;
}
void SpinBox::setReadOnly(bool ro)
{
if (ro != mReadOnly)
......@@ -60,7 +67,7 @@ void SpinBox::setReadOnly(bool ro)
mReadOnly = ro;
lineEdit()->setReadOnly(ro);
if (ro)
setShiftStepping(false, mCurrentButton);
setShiftStepping(NoModifier, mCurrentButton);
}
}
......@@ -86,22 +93,36 @@ void SpinBox::setMaximum(int val)
void SpinBox::setSingleStep(int step)
{
mLineStep = step;
if (!mShiftMouse)
if (mMouseKey == NoModifier)
QSpinBox::setSingleStep(step);
}
void SpinBox::setSingleShiftStep(int step)
{
mLineShiftStep = step;
if (mShiftMouse)
if (mMouseKey == ShiftModifier)
QSpinBox::setSingleStep(step);
}
void SpinBox::setSingleControlStep(int step, bool mod)
{
if (mLineControlStep != step || mModControlStep != mod)
{
mLineControlStep = step;
mModControlStep = step && mod;
if (mMouseKey == ControlModifier)
QSpinBox::setSingleStep(mLineControlStep ? mLineControlStep : mLineStep);
if (mLineControlStep && !mControlStyle)
mControlStyle = new SpinBoxStyle;
setStyle(mLineControlStep ? mControlStyle : QApplication::style());
}
}
void SpinBox::stepBy(int steps)
{
int increment = steps * QSpinBox::singleStep();
addValue(increment);
Q_EMIT stepped(increment);
Q_EMIT stepped(increment, steps != 1);
}
/******************************************************************************
......@@ -198,17 +219,19 @@ bool SpinBox::eventFilter(QObject* obj, QEvent* e)
if (mReadOnly)
return true; // discard up/down arrow keys
auto* ie = (QInputEvent*)e;
if ((ie->modifiers() & (Qt::ShiftModifier | Qt::AltModifier)) == Qt::ShiftModifier)
const Modifier modifier = getModifier(ie->modifiers());
const int lineStep = (modifier == ShiftModifier) ? mLineShiftStep : (modifier == ControlModifier && mLineControlStep) ? mLineControlStep : mLineStep;
if (modifier == ShiftModifier || (modifier == ControlModifier && mModControlStep))
{
// Shift stepping
// Shift/control stepping, to a multiple of the step
const int val = value();
if (step > 0)
step = mLineShiftStep - val % mLineShiftStep;
step = lineStep - val % lineStep;
else
step = - ((val + mLineShiftStep - 1) % mLineShiftStep + 1);
step = - ((val + lineStep - 1) % lineStep + 1);
}
else
step = (step > 0) ? mLineStep : -mLineStep;
step = (step > 0) ? lineStep : -lineStep;
addValue(step, false);
return true;
}
......@@ -251,8 +274,7 @@ bool SpinBox::clickEvent(QMouseEvent* e)
e->accept();
return true;
}
const bool shift = (e->modifiers() & (Qt::ShiftModifier | Qt::AltModifier)) == Qt::ShiftModifier;
if (setShiftStepping(shift, mCurrentButton))
if (setShiftStepping(getModifier(e->modifiers()), mCurrentButton))
{
e->accept();
return true; // hide the event from the spin widget
......@@ -265,8 +287,7 @@ void SpinBox::wheelEvent(QWheelEvent* e)
{
if (mReadOnly)
return; // discard the event
const bool shift = (e->modifiers() & (Qt::ShiftModifier | Qt::AltModifier)) == Qt::ShiftModifier;
if (setShiftStepping(shift, (e->angleDelta().y() > 0 ? UP : DOWN)))
if (setShiftStepping(getModifier(e->modifiers()), (e->angleDelta().y() > 0 ? UP : DOWN)))
{
e->accept();
return; // hide the event from the spin widget
......@@ -276,8 +297,8 @@ void SpinBox::wheelEvent(QWheelEvent* e)
void SpinBox::mouseReleaseEvent(QMouseEvent* e)
{
if (e->button() == Qt::LeftButton && mShiftMouse)
setShiftStepping(false, mCurrentButton); // cancel shift stepping
if (e->button() == Qt::LeftButton && mMouseKey != NoModifier)
setShiftStepping(NoModifier, mCurrentButton); // cancel shift stepping
QSpinBox::mouseReleaseEvent(e);
}
......@@ -294,8 +315,7 @@ void SpinBox::mouseMoveEvent(QMouseEvent* e)
// The mouse has moved to a new spin button.
// Set normal or shift stepping as appropriate.
mCurrentButton = newButton;
const bool shift = (e->modifiers() & (Qt::ShiftModifier | Qt::AltModifier)) == Qt::ShiftModifier;
if (setShiftStepping(shift, mCurrentButton))
if (setShiftStepping(getModifier(e->modifiers()), mCurrentButton))
{
e->accept();
return; // hide the event from the spin widget
......@@ -319,20 +339,19 @@ void SpinBox::keyReleaseEvent(QKeyEvent* e)
bool SpinBox::keyEvent(QKeyEvent* e)
{
const int key = e->key();
const int state = e->modifiers();
const int key = e->key();
if ((QApplication::mouseButtons() & Qt::LeftButton)
&& (key == Qt::Key_Shift || key == Qt::Key_Alt))
&& (key == Qt::Key_Shift || key == Qt::Key_Control || key == Qt::Key_Alt))
{
// The left mouse button is down, and the Shift or Alt key has changed
// The left mouse button is down, and the Shift, Control or Alt key has changed
if (mReadOnly)
return true; // discard the event
const bool shift = (state & (Qt::ShiftModifier | Qt::AltModifier)) == Qt::ShiftModifier;
if ((!shift && mShiftMouse) || (shift && !mShiftMouse))
const Modifier modifier = getModifier(e->modifiers());
if (modifier != mMouseKey)
{
// The effective shift state has changed.
// The effective shift or control state has changed.
// Set normal or shift stepping as appropriate.
if (setShiftStepping(shift, mCurrentButton))
if (setShiftStepping(modifier, mCurrentButton))
{
e->accept();
return true; // hide the event from the spin widget
......@@ -345,21 +364,24 @@ bool SpinBox::keyEvent(QKeyEvent* e)
/******************************************************************************
* Set spin widget stepping to the normal or shift increment.
*/
bool SpinBox::setShiftStepping(bool shift, int currentButton)
bool SpinBox::setShiftStepping(Modifier modifier, int currentButton)
{
if (currentButton == NO_BUTTON)
shift = false;
if (shift && !mShiftMouse)
modifier = NoModifier;
if (modifier == ControlModifier && !mLineControlStep)
modifier = NoModifier; // Qt automatically handles Control key modifier
if (modifier != NoModifier && modifier != mMouseKey)
{
/* The value is to be stepped to a multiple of the shift increment.
/* The value is to be stepped to a multiple of the shift or control 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.
*/
const int val = value();
const int step = (currentButton == UP) ? mLineShiftStep : (currentButton == DOWN) ? -mLineShiftStep : 0;
const int adjust = shiftStepAdjustment(val, step);
mShiftMouse = true;
const int lineStep = (modifier == ShiftModifier) ? mLineShiftStep : mLineControlStep;
const int step = (currentButton == UP) ? lineStep : (currentButton == DOWN) ? -lineStep : 0;
const int adjust = (modifier == ShiftModifier || mModControlStep) ? shiftStepAdjustment(val, step) : 0;
mMouseKey = modifier;
if (adjust)
{
/* The value is to be stepped by other than the shift increment,
......@@ -384,7 +406,7 @@ bool SpinBox::setShiftStepping(bool shift, int currentButton)
else
newval = (newval <= minval) ? minval : mMaxValue;
QSpinBox::setValue(newval);
Q_EMIT stepped(step);
Q_EMIT stepped(step, false);
return true;
}
......@@ -411,16 +433,16 @@ bool SpinBox::setShiftStepping(bool shift, int currentButton)
blockSignals(blocked);
mSuppressSignals = false;
}
QSpinBox::setSingleStep(mLineShiftStep);
QSpinBox::setSingleStep(lineStep);
}
else if (!shift && mShiftMouse)
else if (modifier == NoModifier && mMouseKey != NoModifier)
{
// Reinstate to normal (non-shift) stepping
QSpinBox::setSingleStep(mLineStep);
QSpinBox::setMinimum(mMinValue);
QSpinBox::setMaximum(mMaxValue);
mShiftMinBound = mShiftMaxBound = false;
mShiftMouse = false;
mMouseKey = NoModifier;
}
return false;
}
......@@ -517,4 +539,159 @@ void SpinBox::initStyleOption(QStyleOptionSpinBox& so) const
so.stepEnabled = stepEnabled();
}
/******************************************************************************
* Given input modifier keys, find which modifier is active.
*/
SpinBox::Modifier SpinBox::getModifier(Qt::KeyboardModifiers modifiers)
{
const int state = modifiers & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier);
return (state == Qt::ShiftModifier) ? ShiftModifier
: (state == Qt::ControlModifier) ? ControlModifier
: NoModifier;
}
/*=============================================================================
* SpinBoxStyle class.
*/
SpinBoxStyle::SpinBoxStyle()
{
}
void SpinBoxStyle::polish(QWidget* widget)
{
QApplication::style()->polish(widget);
}
void SpinBoxStyle::unpolish(QWidget* widget)
{
QApplication::style()->unpolish(widget);
}
void SpinBoxStyle::polish(QApplication* application)
{
QApplication::style()->polish(application);
}
void SpinBoxStyle::unpolish(QApplication* application)
{
QApplication::style()->unpolish(application);
}
void SpinBoxStyle::polish(QPalette& palette)
{
QApplication::style()->polish(palette);
}
QRect SpinBoxStyle::itemTextRect(const QFontMetrics& fm, const QRect& r,
int flags, bool enabled,
const QString& text) const
{
return QApplication::style()->itemTextRect(fm, r, flags, enabled, text);
}
QRect SpinBoxStyle::itemPixmapRect(const QRect& r, int flags, const QPixmap& pixmap) const
{
return QApplication::style()->itemPixmapRect(r, flags, pixmap);
}
void SpinBoxStyle::drawItemText(QPainter* painter, const QRect& rect,
int flags, const QPalette& pal, bool enabled,
const QString& text, QPalette::ColorRole textRole) const
{
QApplication::style()->drawItemText(painter, rect, flags, pal, enabled, text, textRole);
}
void SpinBoxStyle::drawItemPixmap(QPainter* painter, const QRect& rect,
int alignment, const QPixmap& pixmap) const
{
QApplication::style()->drawItemPixmap(painter, rect, alignment, pixmap);
}
QPalette SpinBoxStyle::standardPalette() const
{
return QApplication::style()->standardPalette();
}
void SpinBoxStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption* opt, QPainter* p,
const QWidget* w) const
{
QApplication::style()->drawPrimitive(pe, opt, p, w);
}
void SpinBoxStyle::drawControl(ControlElement element, const QStyleOption* opt, QPainter* p,
const QWidget* w) const
{
QApplication::style()->drawControl(element, opt, p, w);
}
QRect SpinBoxStyle::subElementRect(SubElement subElement, const QStyleOption* option,
const QWidget* widget) const
{
return QApplication::style()->subElementRect(subElement, option, widget);
}
void SpinBoxStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex* opt, QPainter* p,
const QWidget* widget) const
{
QApplication::style()->drawComplexControl(cc, opt, p, widget);
}
QStyle::SubControl SpinBoxStyle::hitTestComplexControl(ComplexControl cc, const QStyleOptionComplex* opt,
const QPoint& pt, const QWidget* widget) const
{
return QApplication::style()->hitTestComplexControl(cc, opt, pt, widget);
}
QRect SpinBoxStyle::subControlRect(ComplexControl cc, const QStyleOptionComplex* opt,
SubControl sc, const QWidget* widget) const
{
return QApplication::style()->subControlRect(cc, opt, sc, widget);
}
int SpinBoxStyle::pixelMetric(PixelMetric metric, const QStyleOption* option,
const QWidget* widget) const
{
return QApplication::style()->pixelMetric(metric, option, widget);
}
QSize SpinBoxStyle::sizeFromContents(ContentsType ct, const QStyleOption* opt,
const QSize& contentsSize, const QWidget* w) const
{
return QApplication::style()->sizeFromContents(ct, opt, contentsSize, w);
}
int SpinBoxStyle::styleHint(StyleHint stylehint, const QStyleOption* opt,
const QWidget* widget, QStyleHintReturn* returnData) const
{
if (stylehint == SH_SpinBox_StepModifier)
return Qt::NoModifier;
return QApplication::style()->styleHint(stylehint, opt, widget, returnData);
}
QPixmap SpinBoxStyle::standardPixmap(StandardPixmap standardPixmap, const QStyleOption* opt,
const QWidget* widget) const
{
return QApplication::style()->standardPixmap(standardPixmap, opt, widget);
}
QIcon SpinBoxStyle::standardIcon(StandardPixmap standardIcon, const QStyleOption* option,
const QWidget* widget) const
{
return QApplication::style()->standardIcon(standardIcon, option, widget);
}
QPixmap SpinBoxStyle::generatedIconPixmap(QIcon::Mode iconMode, const QPixmap& pixmap,
const QStyleOption* opt) const
{
return QApplication::style()->generatedIconPixmap(iconMode, pixmap, opt);
}
int SpinBoxStyle::layoutSpacing(QSizePolicy::ControlType control1,
QSizePolicy::ControlType control2, Qt::Orientation orientation,
const QStyleOption* option, const QWidget* widget) const
{
return QApplication::style()->layoutSpacing(control1, control2, orientation, option, widget);
}
// vim: et sw=4:
/*
* spinbox.h - spin box with shift-click step value and read-only option
* Program: kalarm
* SPDX-FileCopyrightText: 2002-2019 David Jarvie <djarvie@kde.org>
* SPDX-FileCopyrightText: 2002-2021 David Jarvie <djarvie@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
......@@ -10,18 +10,20 @@
#include <QSpinBox>
class QEvent;
class QStyle;
class QStyleOptionSpinBox;
/**
* @short Spin box with accelerated shift key stepping and read-only option.
* @short Spin box with accelerated shift or control key stepping, and read-only option.
*
* The SpinBox class provides a QSpinBox with accelerated stepping using the shift key.
* The SpinBox class provides a QSpinBox with accelerated stepping using the shift or
* control key.
*
* A separate step increment may optionally be specified for use when the shift key is
* held down. Typically this would be larger than the normal step. Then, when the user
* clicks the spin buttons, he/she can increment or decrement the value faster by holding
* the shift key down.
* A separate step increment may optionally be specified for use when the shift or
* control key is held down. Typically this would be larger than the normal step. Then,
* when the user clicks the spin buttons, he/she can increment or decrement the value
* faster by holding the shift key down.
*
* The widget may be set as read-only. This has the same effect as disabling it, except
* that its appearance is unchanged.
......@@ -30,132 +32,183 @@ class QStyleOptionSpinBox;
*/
class SpinBox : public QSpinBox
{
Q_OBJECT
public:
/** Constructor.
* @param parent The parent object of this widget.
*/
explicit SpinBox(QWidget* parent = nullptr);
/** Constructor.
* @param minValue The minimum value which the spin box can have.
* @param maxValue The maximum value which the spin box can have.
* @param parent The parent object of this widget.
*/
SpinBox(int minValue, int maxValue, QWidget* parent = nullptr);
/** Returns true if the widget is read only. */
bool isReadOnly() const { return mReadOnly; }
/** Sets whether the spin box can be changed by the user.
* @param readOnly True to set the widget read-only, false to set it read-write.
*/
virtual void setReadOnly(bool readOnly);
/** Returns whether the spin box value text is selected when its value is stepped. */
bool selectOnStep() const { return mSelectOnStep; }
/** Sets whether the spin box value text should be selected when its value is stepped. */
void setSelectOnStep(bool sel) { mSelectOnStep = sel; }
/** Adds a value to the current value of the spin box. */
void addValue(int change) { addValue(change, false); }
/** Returns the minimum value of the spin box. */
int minimum() const { return mMinValue; }
/** Returns the maximum value of the spin box. */
int maximum() const { return mMaxValue; }
/** Sets the minimum value of the spin box. */
void setMinimum(int val);
/** Sets the maximum value of the spin box. */
void setMaximum(int val);
/** Sets the minimum and maximum values of the spin box. */
void setRange(int minValue, int maxValue) { setMinimum(minValue); setMaximum(maxValue); }
/** Returns the specified value clamped to the range of the spin box. */
int bound(int val) const;
/** Called whenever the user triggers a step, to adjust the value of
* the spin box by the unshifted increment.
*/
void stepBy(int steps) override;
/** Returns the unshifted step increment, i.e. the amount by which the spin box value
* changes when a spin button is clicked without the shift key being pressed.
*/
int singleStep() const { return mLineStep; }
/** Sets the unshifted step increment, i.e. the amount by which the spin box value
* changes when a spin button is clicked without the shift key being pressed.
*/
void setSingleStep(int step);
/** Returns the shifted step increment, i.e. the amount by which the spin box value
* changes when a spin button is clicked while the shift key is pressed.
*/
int singleShiftStep() const { return mLineShiftStep; }
/** Sets the shifted step increment, i.e. the amount by which the spin box value
* changes when a spin button is clicked while the shift key is pressed.
*/
void setSingleShiftStep(int step);
/** Returns the rectangle containing the up arrow. */
QRect upRect() const;
/** Returns the rectangle containing the down arrow. */
QRect downRect() const;
/** Returns the rectangle containing the up and down arrows. */
QRect upDownRect() const;
/** Sets whether the edit field is displayed. */
void setUpDownOnly(bool only) { mUpDownOnly = only; }
/** Initialise a QStyleOptionSpinBox with this instance's details. */
void initStyleOption(QStyleOptionSpinBox&) const;
Q_SIGNALS:
/** Signal emitted when the spin box's value is stepped (by the shifted or unshifted increment).
* @param step The requested step in the spin box's value. Note that the actual change in value
* may have been less than this.
*/
void stepped(int step);
protected:
/** Returns 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...
*/
virtual int shiftStepAdjustment(int oldValue, int shiftStep);
/** Receives events destined for the spin widget or for the edit field. */
bool eventFilter(QObject*, QEvent*) override;
void paintEvent(QPaintEvent*) override;
void focusOutEvent(QFocusEvent*) override;
void mousePressEvent(QMouseEvent*) override;
void mouseDoubleClickEvent(QMouseEvent*) override;
void mouseReleaseEvent(QMouseEvent*) override;
void mouseMoveEvent(QMouseEvent*) override;
void keyPressEvent(QKeyEvent*) override;
void keyReleaseEvent(QKeyEvent*) override;
void wheelEvent(QWheelEvent*) override;
private Q_SLOTS:
void textEdited();
void valueChange();
private:
void init();
void addValue(int change, bool current);
int whichButton(const QPoint&);
bool setShiftStepping(bool, int currentButton);
bool clickEvent(QMouseEvent*);
bool keyEvent(QKeyEvent*);
enum { NO_BUTTON, UP, DOWN };
int mMinValue;
int mMaxValue;
int mLineStep; // step when spin arrows are pressed
int mLineShiftStep; // step when spin arrows are pressed with shift key
int mCurrentButton {NO_BUTTON}; // current spin widget button
bool mShiftMouse {false}; // true while left button is being held down with shift key
bool mShiftMinBound {false}; // true if a temporary minimum bound has been set during shift stepping
bool mShiftMaxBound {false}; // true if a temporary maximum bound has been set during shift stepping
bool mSelectOnStep {true}; // select the editor text whenever spin buttons are clicked (default)
bool mUpDownOnly {false}; // true if edit field isn't displayed
bool mReadOnly {false}; // value cannot be changed
bool mSuppressSignals {false};
bool mEdited {false}; // text field has been edited
};
Q_OBJECT
public:
/** Constructor.
* @param parent The parent object of this widget.
*/
explicit SpinBox(QWidget* parent = nullptr);
/** Constructor.
* @param minValue The minimum value which the spin box can have.
* @param maxValue The maximum value which the spin box can have.
* @param parent The parent object of this widget.
*/
SpinBox(int minValue, int maxValue, QWidget* parent = nullptr);
~SpinBox() override;
/** Returns true if the widget is read only. */
bool isReadOnly() const { return mReadOnly; }
/** Sets whether the spin box can be changed by the user.
* @param readOnly True to set the widget read-only, false to set it read-write.
*/
virtual void setReadOnly(bool readOnly);
/** Returns whether the spin box value text is selected when its value is stepped. */
bool selectOnStep() const { return mSelectOnStep; }
/** Sets whether the spin box value text should be selected when its value is stepped. */
void setSelectOnStep(bool sel) { mSelectOnStep = sel; }
/** Adds a value to the current value of the spin box. */
void addValue(int change) { addValue(change, false); }
/** Returns the minimum value of the spin box. */
int minimum() const { return mMinValue; }
/** Returns the maximum value of the spin box. */
int maximum() const { return mMaxValue; }
/** Sets the minimum value of the spin box. */
void setMinimum(int val);
/** Sets the maximum value of the spin box. */
void setMaximum(int val);
/** Sets the minimum and maximum values of the spin box. */
void setRange(int minValue, int maxValue) { setMinimum(minValue); setMaximum(maxValue); }
/** Returns the specified value clamped to the range of the spin box. */
int bound(int val) const;