diff --git a/src/lib/spinbox2.cpp b/src/lib/spinbox2.cpp index 8d9f9db7..1413ef7a 100644 --- a/src/lib/spinbox2.cpp +++ b/src/lib/spinbox2.cpp @@ -1,780 +1,788 @@ /* * spinbox2.cpp - spin box with extra pair of spin buttons * Program: kalarm * Copyright © 2001-2020 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 "spinbox2.h" #include "spinbox2_p.h" #include "kalarm_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include /* List of styles which look better using spin buttons mirrored left-to-right. * This is needed for some styles which use rounded corners. */ static const char* mirrorStyles[] = { "QPlastiqueStyle", "QCleanlooksStyle", nullptr // list terminator }; static bool isMirrorStyle(const QStyle*); static bool isOxygenStyle(const QWidget*); static QRect spinBoxEditFieldRect(const QWidget*, const QStyleOptionSpinBox&); static inline QPixmap grabWidget(QWidget* w, QRect r = QRect()) { QPixmap p(r.isEmpty() ? w->size() : r.size()); w->render(&p, QPoint(0,0), r, QWidget::DrawWindowBackground | QWidget::DrawChildren | QWidget::IgnoreMask); return p; } int SpinBox2::mRightToLeft = -1; SpinBox2::SpinBox2(QWidget* parent) : QFrame(parent) { mSpinboxFrame = new QFrame(this); mUpdown2 = new ExtraSpinBox(this); // mSpinbox = new MainSpinBox(0, 1, this, mSpinboxFrame); mSpinbox = new MainSpinBox(this, mSpinboxFrame); init(); } SpinBox2::SpinBox2(int minValue, int maxValue, int pageStep, QWidget* parent) : QFrame(parent) { mSpinboxFrame = new QFrame(this); mUpdown2 = new ExtraSpinBox(minValue, maxValue, this); mSpinbox = new MainSpinBox(minValue, maxValue, this, mSpinboxFrame); setSteps(1, pageStep); init(); } void SpinBox2::init() { if (mRightToLeft < 0) mRightToLeft = QApplication::isRightToLeft() ? 1 : 0; mMinValue = mSpinbox->minimum(); mMaxValue = mSpinbox->maximum(); mSingleStep = mSpinbox->singleStep(); mSingleShiftStep = mSpinbox->singleShiftStep(); mPageStep = mUpdown2->singleStep(); mPageShiftStep = mUpdown2->singleShiftStep(); mSpinbox->setSelectOnStep(false); // default mUpdown2->setSelectOnStep(false); // always false setFocusProxy(mSpinbox); mUpdown2->setFocusPolicy(Qt::NoFocus); mSpinMirror = new SpinMirror(mUpdown2, mSpinbox, this); mSpinbox->installEventFilter(this); mUpdown2->installEventFilter(this); connect(mSpinbox, SIGNAL(valueChanged(int)), SLOT(valueChange())); connect(mSpinbox, SIGNAL(valueChanged(int)), SIGNAL(valueChanged(int))); connect(mSpinbox, SIGNAL(valueChanged(QString)), SIGNAL(valueChanged(QString))); connect(mUpdown2, &SpinBox::stepped, this, &SpinBox2::stepPage); connect(mUpdown2, &ExtraSpinBox::painted, this, &SpinBox2::paintTimer); } void SpinBox2::setReadOnly(bool ro) { if (static_cast(ro) != static_cast(mSpinbox->isReadOnly())) { mSpinbox->setReadOnly(ro); mUpdown2->setReadOnly(ro); mSpinMirror->setReadOnly(ro); } } void SpinBox2::setReverseWithLayout(bool reverse) { if (reverse != mReverseWithLayout) { mReverseWithLayout = reverse; setSteps(mSingleStep, mPageStep); setShiftSteps(mSingleShiftStep, mPageShiftStep); } } void SpinBox2::setEnabled(bool enabled) { QFrame::setEnabled(enabled); mSpinbox->setEnabled(enabled); mUpdown2->setEnabled(enabled); updateMirror(); } void SpinBox2::setWrapping(bool on) { mSpinbox->setWrapping(on); mUpdown2->setWrapping(on); } QRect SpinBox2::up2Rect() const { return mUpdown2->upRect(); } QRect SpinBox2::down2Rect() const { return mUpdown2->downRect(); } void SpinBox2::setSingleStep(int step) { mSingleStep = step; if (reverseButtons()) mUpdown2->setSingleStep(step); // reverse layout, but still set the right buttons else mSpinbox->setSingleStep(step); } void SpinBox2::setSteps(int single, int page) { mSingleStep = single; mPageStep = page; if (reverseButtons()) { mUpdown2->setSingleStep(single); // reverse layout, but still set the right buttons mSpinbox->setSingleStep(page); } else { mSpinbox->setSingleStep(single); mUpdown2->setSingleStep(page); } } void SpinBox2::setShiftSteps(int single, int page) { mSingleShiftStep = single; mPageShiftStep = page; if (reverseButtons()) { mUpdown2->setSingleShiftStep(single); // reverse layout, but still set the right buttons mSpinbox->setSingleShiftStep(page); } else { mSpinbox->setSingleShiftStep(single); mUpdown2->setSingleShiftStep(page); } } void SpinBox2::setButtonSymbols(QSpinBox::ButtonSymbols newSymbols) { if (mSpinbox->buttonSymbols() == newSymbols) return; mSpinbox->setButtonSymbols(newSymbols); mUpdown2->setButtonSymbols(newSymbols); } int SpinBox2::bound(int val) const { return (val < mMinValue) ? mMinValue : (val > mMaxValue) ? mMaxValue : val; } void SpinBox2::setMinimum(int val) { mMinValue = val; mSpinbox->setMinimum(val); mUpdown2->setMinimum(val); } void SpinBox2::setMaximum(int val) { mMaxValue = val; mSpinbox->setMaximum(val); mUpdown2->setMaximum(val); } void SpinBox2::valueChange() { const int val = mSpinbox->value(); const bool blocked = mUpdown2->signalsBlocked(); mUpdown2->blockSignals(true); mUpdown2->setValue(val); mUpdown2->blockSignals(blocked); } /****************************************************************************** * Called when the widget is about to be displayed. * (At construction time, the spin button widths cannot be determined correctly, * so we need to wait until now to definitively rearrange the widget.) */ void SpinBox2::showEvent(QShowEvent*) { setUpdown2Size(); // set the new size of the second pair of spin buttons arrange(); mSpinMirror->setFrame(); } QSize SpinBox2::sizeHint() const { getMetrics(); QSize size = mSpinbox->sizeHint(); size.setWidth(size.width() - wSpinboxHide + wUpdown2); return size; } QSize SpinBox2::minimumSizeHint() const { getMetrics(); QSize size = mSpinbox->minimumSizeHint(); size.setWidth(size.width() - wSpinboxHide + wUpdown2); return size; } void SpinBox2::styleChange(QStyle&) { setUpdown2Size(); // set the new size of the second pair of spin buttons arrange(); mSpinMirror->setFrame(); } void SpinBox2::paintEvent(QPaintEvent* e) { QFrame::paintEvent(e); QTimer::singleShot(0, this, &SpinBox2::updateMirrorFrame); } void SpinBox2::paintTimer() { QTimer::singleShot(0, this, &SpinBox2::updateMirrorButtons); } void SpinBox2::updateMirrorButtons() { mSpinMirror->setButtons(); } void SpinBox2::updateMirrorFrame() { mSpinMirror->setFrame(); } void SpinBox2::spinboxResized(QResizeEvent* e) { const int h = e->size().height(); if (h != mUpdown2->height()) { mUpdown2->setFixedSize(mUpdown2->width(), e->size().height()); setUpdown2Size(); } } /****************************************************************************** * Set the size of the second spin button widget. * It is necessary to fix the size to avoid infinite recursion in arrange(). */ void SpinBox2::setUpdown2Size() { mSpinMirror->setButtons(); } /****************************************************************************** * Called when the extra pair of spin buttons has repainted after a style change. * Updates the mirror image of the spin buttons. */ void SpinBox2::updateMirror() { mSpinMirror->setButtons(); mSpinMirror->setFrame(); } bool SpinBox2::eventFilter(QObject* obj, QEvent* e) { bool updateButtons = false; if (obj == mSpinbox) { //if (e->type() != QEvent::Paint) qCDebug(KALARM_LOG)<type(); switch (e->type()) { case QEvent::Enter: case QEvent::Leave: QApplication::postEvent(mUpdown2, new QEvent(e->type())); updateButtons = true; break; case QEvent::HoverEnter: { QHoverEvent* he = (QHoverEvent*)e; QApplication::postEvent(mUpdown2, new QHoverEvent(e->type(), QPoint(1, he->pos().y()), he->oldPos())); updateButtons = true; break; } case QEvent::HoverLeave: { QHoverEvent* he = (QHoverEvent*)e; QApplication::postEvent(mUpdown2, new QHoverEvent(e->type(), he->pos(), QPoint(1, he->oldPos().y()))); updateButtons = true; break; } case QEvent::FocusIn: case QEvent::FocusOut: { QFocusEvent* fe = (QFocusEvent*)e; QApplication::postEvent(mUpdown2, new QFocusEvent(e->type(), fe->reason())); updateButtons = true; break; } default: break; } } else if (obj == mUpdown2) { switch (e->type()) { case QEvent::Enter: case QEvent::Leave: case QEvent::HoverEnter: case QEvent::HoverLeave: case QEvent::EnabledChange: updateButtons = true; break; default: break; } } if (updateButtons) QTimer::singleShot(0, this, &SpinBox2::updateMirrorButtons); return false; } /****************************************************************************** * Set the positions and sizes of all the child widgets. */ void SpinBox2::arrange() { getMetrics(); mUpdown2->move(-mUpdown2->width(), 0); // keep completely hidden const QRect arrowRect = style()->visualRect((mRightToLeft ? Qt::RightToLeft : Qt::LeftToRight), rect(), QRect(0, 0, wUpdown2, height())); QRect r(wUpdown2, 0, width() - wUpdown2, height()); if (mRightToLeft) r.moveLeft(0); mSpinboxFrame->setGeometry(r); mSpinbox->setGeometry(mRightToLeft ? 0 : -wSpinboxHide, 0, mSpinboxFrame->width() + wSpinboxHide, height()); // qCDebug(KALARM_LOG) << "arrowRect="<style()->subControlRect(QStyle::CC_SpinBox, &option, QStyle::SC_SpinBoxFrame).right() - r.right() : r.left(); const QRect edRect = spinBoxEditFieldRect(mUpdown2, option); int butx; if (isMirrorStyle(udStyle)) { if (mRightToLeft) { wUpdown2 = edRect.left(); butx = butRect.left(); } else { int x = edRect.right() + 1; wUpdown2 = mUpdown2->width() - x; butx = butRect.left() - x; } } else { r = udStyle->subControlRect(QStyle::CC_SpinBox, &option, QStyle::SC_SpinBoxFrame); if (mRightToLeft) { wUpdown2 = edRect.left() - r.left(); butx = wUpdown2 - (butRect.left() - r.left() + butRect.width()); } else { wUpdown2 = r.width() - edRect.right() - 1; butx = r.right() - butRect.right(); } } mButtonPos = QPoint(butx, butRect.top()); // qCDebug(KALARM_LOG) << "wUpdown2="<= 4.4 Oxygen style const bool oxygen1 = mMainSpinbox->style()->inherits("OxygenStyle"); // KDE <= 4.3 Oxygen style const int editOffsetY = oxygen ? 5 : oxygen1 ? 6 : 2; // offset to edit field const int editOffsetX = (oxygen || oxygen1) ? 4 : 2; // offset to edit field int x = rtl ? r.right() - editOffsetX : r.left() + editOffsetX; p = grabWidget(mMainSpinbox, QRect(x, 0, 1, height())); // Blot out edit field stuff from the middle of the slice const QPixmap dot = grabWidget(mMainSpinbox, QRect(x, editOffsetY, 1, 1)); QPainter painter(&p); painter.drawTiledPixmap(0, editOffsetY, 1, height() - 2*editOffsetY, dot, 0, 0); painter.end(); // Horizontally fill the mirror widget with the vertical slice p = p.scaled(size()); // Grab the left hand border of the main spinbox, and draw it into the mirror widget. QRect endr = rect(); if (rtl) { const int mr = mMainSpinbox->width() - 1; endr.setWidth(mr - r.right() + editOffsetX); endr.moveRight(mr); } else endr.setWidth(r.left() + editOffsetX); x = rtl ? width() - endr.width() : 0; mMainSpinbox->render(&p, QPoint(x, 0), endr, QWidget::DrawWindowBackground | QWidget::DrawChildren | QWidget::IgnoreMask); } c->setBackgroundBrush(p); } void SpinMirror::setButtons() { mSpinbox->inhibitPaintSignal(2); QStyleOptionSpinBox option; mSpinbox->initStyleOption(option); QStyle* st = mSpinbox->style(); QRect r = st->subControlRect(QStyle::CC_SpinBox, &option, QStyle::SC_SpinBoxUp) | st->subControlRect(QStyle::CC_SpinBox, &option, QStyle::SC_SpinBoxDown); if (isOxygenStyle(mSpinbox)) { // They don't use all their height, so shorten them to // allow frame highlighting to work properly. r.setTop(r.top() + 1); r.setHeight(r.height() - 2); } mSpinbox->inhibitPaintSignal(1); mButtons->setPixmap(grabWidget(mSpinbox, r)); mSpinbox->inhibitPaintSignal(0); } void SpinMirror::setButtonPos(const QPoint& pos) { //qCDebug(KALARM_LOG)<setPos(x, y); } void SpinMirror::resizeEvent(QResizeEvent* e) { const QSize sz = e->size(); scene()->setSceneRect(0, 0, sz.width(), sz.height()); setMirroredState(); } void SpinMirror::styleChange(QStyle& st) { mMirrored = isMirrorStyle(&st); setMirroredState(true); } /****************************************************************************** * Pass on to the extra spinbox all mouse events which occur over the spin * button area. */ void SpinMirror::mouseEvent(QMouseEvent* e) { if (mReadOnly) return; QPointF pt = e->pos(); QGraphicsItem* item = scene()->itemAt(pt, QTransform()); if (item == mButtons) pt = spinboxPoint(pt); else pt = QPointF(0, 0); // allow auto-repeat to stop QApplication::postEvent(mSpinbox, new QMouseEvent(e->type(), pt, e->button(), e->buttons(), e->modifiers())); } /****************************************************************************** * Pass on to the extra spinbox all wheel events which occur over the spin * button area. */ void SpinMirror::wheelEvent(QWheelEvent* e) { if (mReadOnly) return; +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) QPointF pt = e->position(); +#else + QPointF pt = e->posF(); +#endif QGraphicsItem* item = scene()->itemAt(pt, QTransform()); if (item == mButtons) { pt = spinboxPoint(pt); +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) + QApplication::postEvent(mSpinbox, new QWheelEvent(pt, e->globalPosF(), e->pixelDelta(), e->angleDelta(), e->buttons(), e->modifiers(), e->phase(), e->inverted(), e->source())); +#else QApplication::postEvent(mSpinbox, new QWheelEvent(pt, e->globalPosition(), e->pixelDelta(), e->angleDelta(), e->buttons(), e->modifiers(), e->phase(), e->inverted(), e->source())); +#endif } } /****************************************************************************** * Translate SpinMirror coordinates to those of the mirrored spinbox. */ QPointF SpinMirror::spinboxPoint(const QPointF& param) const { const QRect r = mSpinbox->upRect(); const QPointF ptf = mButtons->mapFromScene(param.x(), param.y()); QPointF pt(ptf.x(), ptf.y()); pt.setX(ptf.x() + r.left()); pt.setY(ptf.y() + r.top()); return pt; } /****************************************************************************** * Pass on to the main spinbox events which are needed to activate mouseover and * other graphic effects when the mouse cursor enters and leaves the widget. */ bool SpinMirror::event(QEvent* e) { //qCDebug(KALARM_LOG)<type(); QHoverEvent* he = nullptr; switch (e->type()) { case QEvent::Leave: if (mMainSpinbox->rect().contains(mMainSpinbox->mapFromGlobal(QCursor::pos()))) break; // fall through to QEvent::Enter Q_FALLTHROUGH(); case QEvent::Enter: QApplication::postEvent(mMainSpinbox, new QEvent(e->type())); break; case QEvent::HoverLeave: he = (QHoverEvent*)e; if (mMainSpinbox->rect().contains(mMainSpinbox->mapFromGlobal(QCursor::pos()))) break; // fall through to QEvent::HoverEnter Q_FALLTHROUGH(); case QEvent::HoverEnter: he = (QHoverEvent*)e; QApplication::postEvent(mMainSpinbox, new QHoverEvent(e->type(), he->pos(), he->oldPos())); break; case QEvent::HoverMove: he = (QHoverEvent*)e; break; case QEvent::FocusIn: mMainSpinbox->setFocus(); break; default: break; } if (he) { QApplication::postEvent(mSpinbox, new QHoverEvent(e->type(), spinboxPoint(he->posF()), spinboxPoint(he->oldPosF()))); setButtons(); } return QGraphicsView::event(e); } /****************************************************************************** * Determine whether the extra pair of spin buttons needs to be mirrored * left-to-right in the specified style. */ static bool isMirrorStyle(const QStyle* style) { for (const char** s = mirrorStyles; *s; ++s) if (style->inherits(*s)) return true; return false; } static bool isOxygenStyle(const QWidget* w) { return w->style()->inherits("Oxygen::Style") || w->style()->inherits("OxygenStyle"); } static QRect spinBoxEditFieldRect(const QWidget* w, const QStyleOptionSpinBox& option) { QRect r = w->style()->subControlRect(QStyle::CC_SpinBox, &option, QStyle::SC_SpinBoxEditField); if (isOxygenStyle(w)) { int xadjust = 3; r.adjust(xadjust, 2, -xadjust, -2); } return r; } // vim: et sw=4: