/****************************************************************************** * QSkinny - Copyright (C) 2016 Uwe Rathmann * This file may be used under the terms of the QSkinny License, Version 1.0 *****************************************************************************/ #include "QskSpinBox.h" #include "QskLinearBox.h" #include "QskGridBox.h" #include "QskTextInput.h" #include "QskBoxShapeMetrics.h" #include "QskBoxBorderColors.h" #include "QskBoxBorderMetrics.h" #include "QskSkinlet.h" #include "QskIntervalF.h" #include "QskEvent.h" #include #include #include #include QSK_SUBCONTROL(QskSpinBox, Inc) QSK_SUBCONTROL(QskSpinBox, Dec) QSK_SUBCONTROL(QskSpinBox, IncText) QSK_SUBCONTROL(QskSpinBox, DecText) QSK_SUBCONTROL(QskSpinBox, Text) QSK_SUBCONTROL(QskSpinBox, TextPanel) QSK_SUBCONTROL(QskSpinBox, Layout) QSK_SYSTEM_STATE(QskSpinBox, Pressed, ( QskAspect::QskAspect::FirstSystemState << 0)) class QskSpinBox::PrivateData { public: enum FocusIndeces : int { Dec = 0, Text = 1, Inc = 2, None = 3 }; explicit PrivateData(QskSpinBox* const parent) : q(parent) { } void setValue(qreal value) { value = qBound(q->minimum(), value, q->maximum()); if(!qFuzzyCompare(m_value, value)) { m_value = value; Q_EMIT q->valueChanged(m_value); q->polish(); q->update(); } } qreal value() const { return m_value; } FocusIndeces defaultFocusIndex() const { const auto layout = q->alignmentHint(QskSpinBox::Layout); if(layout == Qt::AlignLeft) return Text; if(layout == Qt::AlignRight) return Dec; if(layout == Qt::AlignHCenter) return Dec; if(layout == Qt::AlignTop) return Text; if(layout == Qt::AlignBottom) return Inc; if(layout == Qt::AlignVCenter) return Inc; if(layout == (Qt::AlignLeft | Qt::AlignVCenter)) return Text; if(layout == (Qt::AlignRight | Qt::AlignVCenter)) return Inc; if(layout == (Qt::AlignTop | Qt::AlignHCenter)) return Text; if(layout == (Qt::AlignBottom | Qt::AlignHCenter)) return Dec; return None; } FocusIndeces nextFocusIndex() const { const auto layout = q->alignmentHint(QskSpinBox::Layout); // [0 ][1 ][2 ][3 ] // [Dec][Text][Inc][None] using LUT = std::array; if(layout == Qt::AlignLeft) return LUT{Inc,Dec,None,Text}[m_focusIndex]; if(layout == Qt::AlignRight) return LUT{Inc,None,Text,Dec}[m_focusIndex]; if(layout == Qt::AlignHCenter) return LUT{Text,Inc,None,Dec}[m_focusIndex]; if(layout == Qt::AlignTop) return LUT{Inc,Dec,None,Text}[m_focusIndex]; if(layout == Qt::AlignBottom) return LUT{Inc,None,Text,Dec}[m_focusIndex]; if(layout == Qt::AlignVCenter) return LUT{None,Dec,Text,Inc}[m_focusIndex]; if(layout == (Qt::AlignLeft | Qt::AlignVCenter)) return LUT{None,Inc,Dec,Text}[m_focusIndex]; if(layout == (Qt::AlignRight | Qt::AlignVCenter)) return LUT{Text,None,Dec,Inc}[m_focusIndex]; if(layout == (Qt::AlignTop | Qt::AlignHCenter)) return LUT{Inc,Dec,None,Text}[m_focusIndex]; if(layout == (Qt::AlignBottom | Qt::AlignHCenter)) return LUT{Inc,None,Text,Dec}[m_focusIndex]; return None; } FocusIndeces previousFocusIndex() const { const auto layout = q->alignmentHint(QskSpinBox::Layout); // [0 ][1 ][2 ][3 ] // [Dec][Text][Inc][None] using LUT = std::array; if(layout == Qt::AlignLeft) return LUT{None,Dec,Text,Inc}[m_focusIndex]; if(layout == Qt::AlignRight) return LUT{None,Inc,Dec,Text}[m_focusIndex]; if(layout == Qt::AlignHCenter) return LUT{None,Dec,Text,Inc}[m_focusIndex]; if(layout == Qt::AlignTop) return LUT{Text,None,Dec,Inc}[m_focusIndex]; if(layout == Qt::AlignBottom) return LUT{None,Inc,Dec,Text}[m_focusIndex]; if(layout == Qt::AlignVCenter) return LUT{Text,Inc,None,Dec}[m_focusIndex]; if(layout == (Qt::AlignLeft | Qt::AlignVCenter)) return LUT{Inc,None,Text,Dec}[m_focusIndex]; if(layout == (Qt::AlignRight | Qt::AlignVCenter)) return LUT{Inc,Dec,None,Text}[m_focusIndex]; if(layout == (Qt::AlignTop | Qt::AlignHCenter)) return LUT{Text,None,Dec,Inc}[m_focusIndex]; if(layout == (Qt::AlignBottom | Qt::AlignHCenter)) return LUT{None,Inc,Dec,Text}[m_focusIndex]; return None; } FocusIndeces focusIndex() const { return m_focusIndex; } void focusNext() { const auto index = nextFocusIndex(); setFocus(index); } void focusPrevious() { const auto index = previousFocusIndex(); setFocus(index); } void focusDefault() { setFocus(defaultFocusIndex()); } void setFocus(const FocusIndeces index) { Q_ASSERT(index == Dec || index == Text || index == Inc || index == None); if(index == Dec || index == Text || index == Inc || index == None) { m_focusIndex = index; Q_EMIT q->focusIndexChanged(m_focusIndex); // TODO register enum } } QRectF focusIndicatorRect() const { switch(m_focusIndex) { case PrivateData::FocusIndeces::Dec: return q->subControlRect(QskSpinBox::Dec); case PrivateData::FocusIndeces::Text: return q->subControlRect(QskSpinBox::TextPanel); case PrivateData::FocusIndeces::Inc: return q->subControlRect(QskSpinBox::Inc); default: return {}; } } void saveMousePosition(const QPointF& pos) { q->setSkinHint(QskSpinBox::Layout | QskAspect::Metric | QskAspect::Position, pos ); } private: qreal m_value{0.0f}; QskSpinBox* const q; FocusIndeces m_focusIndex = FocusIndeces::None; }; using S = QskSpinBox; QskSpinBox::QskSpinBox(QQuickItem* const parent) : Inherited(parent) , m_data( new PrivateData( this ) ) { setBoundaries(0.0,1.0); setAcceptHoverEvents(true); setAcceptedMouseButtons(Qt::LeftButton); setFocusPolicy( Qt::StrongFocus ); connect( this, &S::focusIndexChanged, this, &S::focusIndicatorRectChanged ); connect( this, &S::boundariesChanged, this, [this](const QskIntervalF& interval){ if(!interval.contains(value())) m_data->setValue(minimum()); }); connect( this, &S::valueChanged, this, [this](){ polish(); }); } QskSpinBox::~QskSpinBox() = default; void QskSpinBox::hoverEnterEvent(QHoverEvent* event) { m_data->saveMousePosition( qskHoverPosition( event ) ); } void QskSpinBox::hoverLeaveEvent(QHoverEvent* ) { m_data->saveMousePosition( {} ); } void QskSpinBox::hoverMoveEvent(QHoverEvent *event) { m_data->saveMousePosition( qskHoverPosition( event ) ); } void QskSpinBox::mouseReleaseEvent(QMouseEvent *event) { m_data->saveMousePosition( qskMousePosition( event ) ); const auto focus = ( focusPolicy() & Qt::ClickFocus ) == Qt::ClickFocus && !QGuiApplication::styleHints()->setFocusOnTouchRelease(); if(subControlRect(QskSpinBox::Inc).contains( event->pos() )) { increment(+stepSize()); if( focus ) { m_data->setFocus(PrivateData::Inc); } return; } if(subControlRect(QskSpinBox::Dec).contains( event->pos() )) { increment(-stepSize()); if( focus ) { m_data->setFocus(PrivateData::Dec); } return; } if(subControlRect(QskSpinBox::TextPanel).contains( event->pos() )) { if( focus ) { m_data->setFocus(PrivateData::Text); } return; } event->ignore(); } void QskSpinBox::mousePressEvent(QMouseEvent *event) { m_data->saveMousePosition( -1 * qskMousePosition( event ) ); const auto focus = ( focusPolicy() & Qt::ClickFocus ) == Qt::ClickFocus && !QGuiApplication::styleHints()->setFocusOnTouchRelease(); if(subControlRect(QskSpinBox::Inc).contains( event->pos() )) { if( focus ) { m_data->setFocus(PrivateData::Inc); } return; } if(subControlRect(QskSpinBox::Dec).contains( event->pos() )) { if( focus ) { m_data->setFocus(PrivateData::Dec); } return; } event->ignore(); } void QskSpinBox::keyPressEvent(QKeyEvent *event) { switch( event->key() ) { case Qt::Key_Plus: case Qt::Key_Up: case Qt::Key_Right: // TODO increment break; case Qt::Key_Minus: case Qt::Key_Down: case Qt::Key_Left: // TODO decrement break; case Qt::Key_Select: case Qt::Key_Space: // TODO click currently focused -/+ break; default: { const int steps = qskFocusChainIncrement( event ); if(steps != 0) { for(int i = 0; i < steps; ++i) { m_data->focusNext(); } for(int i = steps; i < 0; ++i) { m_data->focusPrevious(); } } } } Inherited::keyPressEvent( event ); } void QskSpinBox::keyReleaseEvent( QKeyEvent* event ) { if( event->key() == Qt::Key_Select || event->key() == Qt::Key_Space ) { return; } Inherited::keyReleaseEvent( event ); } void QskSpinBox::focusInEvent(QFocusEvent *event) { switch( event->reason() ) { case Qt::TabFocusReason: m_data->focusNext(); break; case Qt::BacktabFocusReason: m_data->focusPrevious(); break; default: if(m_data->focusIndex() == PrivateData::None) { m_data->focusDefault(); return; } } Inherited::focusInEvent( event ); } QRectF QskSpinBox::focusIndicatorRect() const { auto rect = m_data->focusIndicatorRect(); return rect; } void QskSpinBox::increment(qreal offset) { m_data->setValue(m_data->value() + offset); } qreal QskSpinBox::value() const { return m_data->value(); }