#include "FractoriumPch.h" #include "SpinBox.h" #include "FractoriumSettings.h" QTimer SpinBox::s_Timer; /// /// Constructor that passes parent to the base and sets up height and step. /// Specific focus policy is used to allow the user to hover over the control /// and change its value using the mouse wheel without explicitly having to click /// inside of it. /// /// The parent widget. Default: nullptr. /// The height of the spin box. Default: 16. /// The step used to increment/decrement the spin box when using the mouse wheel. Default: 1. SpinBox::SpinBox(QWidget* p, int h, int step) : QSpinBox(p) { m_DoubleClick = false; m_DoubleClickLowVal = 0; m_DoubleClickNonZero = 0; m_DoubleClickZero = 1; m_Step = step; m_SmallStep = 1; m_Settings = FractoriumSettings::DefInstance(); setSingleStep(step); setFrame(false); setButtonSymbols(QAbstractSpinBox::NoButtons); setFocusPolicy(Qt::StrongFocus); setMinimumHeight(h);//setGeometry() has no effect, so set both of these instead. setMaximumHeight(h); lineEdit()->installEventFilter(this); lineEdit()->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); connect(this, SIGNAL(valueChanged(int)), this, SLOT(onSpinBoxValueChanged(int)), Qt::QueuedConnection); } /// /// Set the value of the control without triggering signals. /// /// The value to set it to void SpinBox::SetValueStealth(int d) { blockSignals(true); setValue(d); blockSignals(false); } void SpinBox::SetValueStealth(size_t d) { SetValueStealth(int(d)); } /// /// Set whether to respond to double click events. /// /// True if this should respond to double click events, else false. void SpinBox::DoubleClick(bool b) { m_DoubleClick = b; } /// /// Set the value to be used instead of zero to represent the lower value /// used when responding to a double click. /// /// The value to be used for the lower value instead of zero void SpinBox::DoubleClickLowVal(int val) { m_DoubleClickLowVal = val; } /// /// Set the value to be used when the user double clicks the spinner while /// it contains zero. /// /// The value to be used void SpinBox::DoubleClickZero(int val) { m_DoubleClickZero = val; } /// /// Set the value to be used when the user double clicks the spinner while /// it contains a non-zero value. /// /// The value to be used void SpinBox::DoubleClickNonZero(int val) { m_DoubleClickNonZero = val; } /// /// Set the small step to be used when the user holds down shift while scrolling. /// The default is step / 10, so use this if something else is needed. /// /// The small step to use for scrolling while the shift key is down void SpinBox::SmallStep(int step) { m_SmallStep = std::min(1, step); } /// /// Expose the underlying QLineEdit control to the caller. /// /// A pointer to the QLineEdit QLineEdit* SpinBox::lineEdit() { return QSpinBox::lineEdit(); } /// /// Another workaround for the persistent text selection bug in Qt. /// void SpinBox::onSpinBoxValueChanged(int i) { lineEdit()->deselect();//Gets rid of nasty "feature" that always has text selected. } /// /// Called while the timer is activated due to the right mouse button being held down. /// void SpinBox::OnTimeout() { int xdistance = m_MouseMovePoint.x() - m_MouseDownPoint.x(); int ydistance = m_MouseMovePoint.y() - m_MouseDownPoint.y(); int distance = abs(xdistance) > abs(ydistance) ? xdistance : ydistance; double scale, val; int d = value(); bool shift = QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier); bool ctrl = QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier); double amount = (m_SmallStep + m_Step) * 0.5; if (shift) scale = 0.001; else if (ctrl) scale = 0.01; else scale = 0.01; val = d + (distance * amount * scale); setValue(int(val)); } /// /// Event filter for taking special action on double click events. /// /// The object /// The eevent /// false bool SpinBox::eventFilter(QObject* o, QEvent* e) { auto me = dynamic_cast(e); if (isEnabled() && me) { if (!m_Settings->ToggleType() &&//Ensure double click toggles, not right click. me->type() == QMouseEvent::MouseButtonPress && me->button() == Qt::RightButton) { m_MouseDownPoint = m_MouseMovePoint = me->pos(); StartTimer(); } else if (!m_Settings->ToggleType() && me->type() == QMouseEvent::MouseButtonRelease && me->button() == Qt::RightButton) { StopTimer(); m_MouseDownPoint = m_MouseMovePoint = me->pos(); } else if (!m_Settings->ToggleType() && me->type() == QMouseEvent::MouseMove && QGuiApplication::mouseButtons() & Qt::RightButton) { m_MouseMovePoint = me->pos(); } else if (m_DoubleClick && ((!m_Settings->ToggleType() && e->type() == QMouseEvent::MouseButtonDblClick && me->button() == Qt::LeftButton) || (m_Settings->ToggleType() && me->type() == QMouseEvent::MouseButtonRelease && me->button() == Qt::RightButton))) { if (IsClose(m_DoubleClickLowVal, value())) setValue(m_DoubleClickZero); else setValue(m_DoubleClickNonZero); } } else { if (e->type() == QEvent::Wheel) { //Take special action for shift to reduce the scroll amount. Control already //increases it automatically. if (QWheelEvent* we = dynamic_cast(e)) { Qt::KeyboardModifiers mod = we->modifiers(); if (mod.testFlag(Qt::ShiftModifier)) setSingleStep(m_SmallStep); else setSingleStep(m_Step); } } } return QSpinBox::eventFilter(o, e); } /// /// Called when focus enters the spinner. /// /// The event void SpinBox::focusInEvent(QFocusEvent* e) { StopTimer(); QSpinBox::focusInEvent(e); } /// /// Called when focus leaves the spinner. /// Qt has a nasty "feature" that leaves the text in a spinner selected /// and the cursor visible, regardless of whether it has the focus. /// Manually clear both here. /// /// The event void SpinBox::focusOutEvent(QFocusEvent* e) { StopTimer(); QSpinBox::focusOutEvent(e); } /// /// Called when focus enters the spinner. /// Must set the focus to make sure key down messages don't erroneously go to the GLWidget. /// /// The event void SpinBox::enterEvent(QEvent* e) { StopTimer(); QSpinBox::enterEvent(e); } /// /// Called when focus leaves the spinner. /// Must clear the focus to make sure key down messages don't erroneously go to the GLWidget. /// /// The event void SpinBox::leaveEvent(QEvent* e) { StopTimer(); QSpinBox::leaveEvent(e); } /// /// Start the timer in response to the right mouse button being pressed. /// void SpinBox::StartTimer() { s_Timer.stop(); connect(&s_Timer, SIGNAL(timeout()), this, SLOT(OnTimeout())); s_Timer.start(300); } /// /// Stop the timer in response to the left mouse button being pressed. /// void SpinBox::StopTimer() { s_Timer.stop(); disconnect(&s_Timer, SIGNAL(timeout()), this, SLOT(OnTimeout())); }