/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_abstract_slider.h" #include "qwt_abstract_scale_draw.h" #include "qwt_math.h" #include "qwt_scale_map.h" #include #if QT_VERSION < 0x040601 #define qFabs(x) ::fabs(x) #endif static double qwtAlignToScaleDiv( const QwtAbstractSlider *slider, double value ) { const QwtScaleDiv &sd = slider->scaleDiv(); const int tValue = slider->transform( value ); if ( tValue == slider->transform( sd.lowerBound() ) ) return sd.lowerBound(); if ( tValue == slider->transform( sd.upperBound() ) ) return sd.upperBound(); for ( int i = 0; i < QwtScaleDiv::NTickTypes; i++ ) { const QList ticks = sd.ticks( i ); for ( int j = 0; j < ticks.size(); j++ ) { if ( slider->transform( ticks[ j ] ) == tValue ) return ticks[ j ]; } } return value; } class QwtAbstractSlider::PrivateData { public: PrivateData(): isScrolling( false ), isTracking( true ), pendingValueChanged( false ), readOnly( false ), totalSteps( 100 ), singleSteps( 1 ), pageSteps( 10 ), stepAlignment( true ), isValid( false ), value( 0.0 ), wrapping( false ), invertedControls( false ) { } bool isScrolling; bool isTracking; bool pendingValueChanged; bool readOnly; uint totalSteps; uint singleSteps; uint pageSteps; bool stepAlignment; bool isValid; double value; bool wrapping; bool invertedControls; }; /*! \brief Constructor The scale is initialized to [0.0, 100.0], the number of steps is set to 100 with 1 and 10 and single an page step sizes. Step alignment is enabled. The initial value is invalid. \param parent Parent widget */ QwtAbstractSlider::QwtAbstractSlider( QWidget *parent ): QwtAbstractScale( parent ) { d_data = new QwtAbstractSlider::PrivateData; setScale( 0.0, 100.0 ); setFocusPolicy( Qt::StrongFocus ); } //! Destructor QwtAbstractSlider::~QwtAbstractSlider() { delete d_data; } /*! Set the value to be valid/invalid \param on When true, the value is invalidated \sa setValue() */ void QwtAbstractSlider::setValid( bool on ) { if ( on != d_data->isValid ) { d_data->isValid = on; sliderChange(); Q_EMIT valueChanged( d_data->value ); } } //! \return True, when the value is invalid bool QwtAbstractSlider::isValid() const { return d_data->isValid; } /*! En/Disable read only mode In read only mode the slider can't be controlled by mouse or keyboard. \param on Enables in case of true \sa isReadOnly() \warning The focus policy is set to Qt::StrongFocus or Qt::NoFocus */ void QwtAbstractSlider::setReadOnly( bool on ) { if ( d_data->readOnly != on ) { d_data->readOnly = on; setFocusPolicy( on ? Qt::StrongFocus : Qt::NoFocus ); update(); } } /*! In read only mode the slider can't be controlled by mouse or keyboard. \return true if read only \sa setReadOnly() */ bool QwtAbstractSlider::isReadOnly() const { return d_data->readOnly; } /*! \brief Enables or disables tracking. If tracking is enabled, the slider emits the valueChanged() signal while the movable part of the slider is being dragged. If tracking is disabled, the slider emits the valueChanged() signal only when the user releases the slider. Tracking is enabled by default. \param on \c true (enable) or \c false (disable) tracking. \sa isTracking(), sliderMoved() */ void QwtAbstractSlider::setTracking( bool on ) { d_data->isTracking = on; } /*! \return True, when tracking has been enabled \sa setTracking() */ bool QwtAbstractSlider::isTracking() const { return d_data->isTracking; } /*! Mouse press event handler \param event Mouse event */ void QwtAbstractSlider::mousePressEvent( QMouseEvent *event ) { if ( isReadOnly() ) { event->ignore(); return; } if ( !d_data->isValid || lowerBound() == upperBound() ) return; d_data->isScrolling = isScrollPosition( event->pos() ); if ( d_data->isScrolling ) { d_data->pendingValueChanged = false; Q_EMIT sliderPressed(); } } /*! Mouse Move Event handler \param event Mouse event */ void QwtAbstractSlider::mouseMoveEvent( QMouseEvent *event ) { if ( isReadOnly() ) { event->ignore(); return; } if ( d_data->isValid && d_data->isScrolling ) { double value = scrolledTo( event->pos() ); if ( value != d_data->value ) { value = boundedValue( value ); if ( d_data->stepAlignment ) { value = alignedValue( value ); } else { value = qwtAlignToScaleDiv( this, value ); } if ( value != d_data->value ) { d_data->value = value; sliderChange(); Q_EMIT sliderMoved( d_data->value ); if ( d_data->isTracking ) Q_EMIT valueChanged( d_data->value ); else d_data->pendingValueChanged = true; } } } } /*! Mouse Release Event handler \param event Mouse event */ void QwtAbstractSlider::mouseReleaseEvent( QMouseEvent *event ) { if ( isReadOnly() ) { event->ignore(); return; } if ( d_data->isScrolling && d_data->isValid ) { d_data->isScrolling = false; if ( d_data->pendingValueChanged ) Q_EMIT valueChanged( d_data->value ); Q_EMIT sliderReleased(); } } /*! Wheel Event handler In/decreases the value by s number of steps. The direction depends on the invertedControls() property. When the control or shift modifier is pressed the wheel delta ( divided by 120 ) is mapped to an increment according to pageSteps(). Otherwise it is mapped to singleSteps(). \param event Wheel event */ void QwtAbstractSlider::wheelEvent( QWheelEvent *event ) { if ( isReadOnly() ) { event->ignore(); return; } if ( !d_data->isValid || d_data->isScrolling ) return; int numSteps = 0; if ( ( event->modifiers() & Qt::ControlModifier) || ( event->modifiers() & Qt::ShiftModifier ) ) { // one page regardless of delta numSteps = d_data->pageSteps; if ( event->delta() < 0 ) numSteps = -numSteps; } else { const int numTurns = ( event->delta() / 120 ); numSteps = numTurns * d_data->singleSteps; } if ( d_data->invertedControls ) numSteps = -numSteps; const double value = incrementedValue( d_data->value, numSteps ); if ( value != d_data->value ) { d_data->value = value; sliderChange(); Q_EMIT sliderMoved( d_data->value ); Q_EMIT valueChanged( d_data->value ); } } /*! Handles key events QwtAbstractSlider handles the following keys: - Qt::Key_Left\n Add/Subtract singleSteps() in direction to lowerBound(); - Qt::Key_Right\n Add/Subtract singleSteps() in direction to upperBound(); - Qt::Key_Down\n Subtract singleSteps(), when invertedControls() is false - Qt::Key_Up\n Add singleSteps(), when invertedControls() is false - Qt::Key_PageDown\n Subtract pageSteps(), when invertedControls() is false - Qt::Key_PageUp\n Add pageSteps(), when invertedControls() is false - Qt::Key_Home\n Set the value to the minimum() - Qt::Key_End\n Set the value to the maximum() \param event Key event \sa isReadOnly() */ void QwtAbstractSlider::keyPressEvent( QKeyEvent *event ) { if ( isReadOnly() ) { event->ignore(); return; } if ( !d_data->isValid || d_data->isScrolling ) return; int numSteps = 0; double value = d_data->value; switch ( event->key() ) { case Qt::Key_Left: { numSteps = -static_cast( d_data->singleSteps ); if ( isInverted() ) numSteps = -numSteps; break; } case Qt::Key_Right: { numSteps = d_data->singleSteps; if ( isInverted() ) numSteps = -numSteps; break; } case Qt::Key_Down: { numSteps = -static_cast( d_data->singleSteps ); if ( d_data->invertedControls ) numSteps = -numSteps; break; } case Qt::Key_Up: { numSteps = d_data->singleSteps; if ( d_data->invertedControls ) numSteps = -numSteps; break; } case Qt::Key_PageUp: { numSteps = d_data->pageSteps; if ( d_data->invertedControls ) numSteps = -numSteps; break; } case Qt::Key_PageDown: { numSteps = -static_cast( d_data->pageSteps ); if ( d_data->invertedControls ) numSteps = -numSteps; break; } case Qt::Key_Home: { value = minimum(); break; } case Qt::Key_End: { value = maximum(); break; } default:; { event->ignore(); } } if ( numSteps != 0 ) { value = incrementedValue( d_data->value, numSteps ); } if ( value != d_data->value ) { d_data->value = value; sliderChange(); Q_EMIT sliderMoved( d_data->value ); Q_EMIT valueChanged( d_data->value ); } } /*! \brief Set the number of steps The range of the slider is divided into a number of steps from which the value increments according to user inputs depend. The default setting is 100. \param stepCount Number of steps \sa totalSteps(), setSingleSteps(), setPageSteps() */ void QwtAbstractSlider::setTotalSteps( uint stepCount ) { d_data->totalSteps = stepCount; } /*! \return Number of steps \sa setTotalSteps(), singleSteps(), pageSteps() */ uint QwtAbstractSlider::totalSteps() const { return d_data->totalSteps; } /*! \brief Set the number of steps for a single increment The range of the slider is divided into a number of steps from which the value increments according to user inputs depend. \param stepCount Number of steps \sa singleSteps(), setTotalSteps(), setPageSteps() */ void QwtAbstractSlider::setSingleSteps( uint stepCount ) { d_data->singleSteps = stepCount; } /*! \return Number of steps \sa setSingleSteps(), totalSteps(), pageSteps() */ uint QwtAbstractSlider::singleSteps() const { return d_data->singleSteps; } /*! \brief Set the number of steps for a page increment The range of the slider is divided into a number of steps from which the value increments according to user inputs depend. \param stepCount Number of steps \sa pageSteps(), setTotalSteps(), setSingleSteps() */ void QwtAbstractSlider::setPageSteps( uint stepCount ) { d_data->pageSteps = stepCount; } /*! \return Number of steps \sa setPageSteps(), totalSteps(), singleSteps() */ uint QwtAbstractSlider::pageSteps() const { return d_data->pageSteps; } /*! \brief Enable step alignment When step alignment is enabled values resulting from slider movements are aligned to the step size. \param on Enable step alignment when true \sa stepAlignment() */ void QwtAbstractSlider::setStepAlignment( bool on ) { if ( on != d_data->stepAlignment ) { d_data->stepAlignment = on; } } /*! \return True, when step alignment is enabled \sa setStepAlignment() */ bool QwtAbstractSlider::stepAlignment() const { return d_data->stepAlignment; } /*! Set the slider to the specified value \param value New value \sa setValid(), sliderChange(), valueChanged() */ void QwtAbstractSlider::setValue( double value ) { value = qBound( minimum(), value, maximum() ); const bool changed = ( d_data->value != value ) || !d_data->isValid; d_data->value = value; d_data->isValid = true; if ( changed ) { sliderChange(); Q_EMIT valueChanged( d_data->value ); } } //! Returns the current value. double QwtAbstractSlider::value() const { return d_data->value; } /*! If wrapping is true stepping up from upperBound() value will take you to the minimum() value and vice versa. \param on En/Disable wrapping \sa wrapping() */ void QwtAbstractSlider::setWrapping( bool on ) { d_data->wrapping = on; } /*! \return True, when wrapping is set \sa setWrapping() */ bool QwtAbstractSlider::wrapping() const { return d_data->wrapping; } /*! Invert wheel and key events Usually scrolling the mouse wheel "up" and using keys like page up will increase the slider's value towards its maximum. When invertedControls() is enabled the value is scrolled towards its minimum. Inverting the controls might be f.e. useful for a vertical slider with an inverted scale ( decreasing from top to bottom ). \param on Invert controls, when true \sa invertedControls(), keyEvent(), wheelEvent() */ void QwtAbstractSlider::setInvertedControls( bool on ) { d_data->invertedControls = on; } /*! \return True, when the controls are inverted \sa setInvertedControls() */ bool QwtAbstractSlider::invertedControls() const { return d_data->invertedControls; } /*! Increment the slider The step size depends on the number of totalSteps() \param stepCount Number of steps \sa setTotalSteps(), incrementedValue() */ void QwtAbstractSlider::incrementValue( int stepCount ) { const double value = incrementedValue( d_data->value, stepCount ); if ( value != d_data->value ) { d_data->value = value; sliderChange(); } } /*! Increment a value \param value Value \param stepCount Number of steps \return Incremented value */ double QwtAbstractSlider::incrementedValue( double value, int stepCount ) const { if ( d_data->totalSteps == 0 ) return value; const QwtTransform *transformation = scaleMap().transformation(); if ( transformation == NULL ) { const double range = maximum() - minimum(); value += stepCount * range / d_data->totalSteps; } else { QwtScaleMap map = scaleMap(); map.setPaintInterval( 0, d_data->totalSteps ); // we need equidant steps according to // paint device coordinates const double range = transformation->transform( maximum() ) - transformation->transform( minimum() ); const double stepSize = range / d_data->totalSteps; double v = transformation->transform( value ); v = qRound( v / stepSize ) * stepSize; v += stepCount * range / d_data->totalSteps; value = transformation->invTransform( v ); } value = boundedValue( value ); if ( d_data->stepAlignment ) value = alignedValue( value ); return value; } double QwtAbstractSlider::boundedValue( double value ) const { const double vmin = minimum(); const double vmax = maximum(); if ( d_data->wrapping && vmin != vmax ) { const int fullCircle = 360 * 16; const double pd = scaleMap().pDist(); if ( int( pd / fullCircle ) * fullCircle == pd ) { // full circle scales: min and max are the same const double range = vmax - vmin; if ( value < vmin ) { value += ::ceil( ( vmin - value ) / range ) * range; } else if ( value > vmax ) { value -= ::ceil( ( value - vmax ) / range ) * range; } } else { if ( value < vmin ) value = vmax; else if ( value > vmax ) value = vmin; } } else { value = qBound( vmin, value, vmax ); } return value; } double QwtAbstractSlider::alignedValue( double value ) const { if ( d_data->totalSteps == 0 ) return value; double stepSize; if ( scaleMap().transformation() == NULL ) { stepSize = ( maximum() - minimum() ) / d_data->totalSteps; if ( stepSize > 0.0 ) { value = lowerBound() + qRound( ( value - lowerBound() ) / stepSize ) * stepSize; } } else { stepSize = ( scaleMap().p2() - scaleMap().p1() ) / d_data->totalSteps; if ( stepSize > 0.0 ) { double v = scaleMap().transform( value ); v = scaleMap().p1() + qRound( ( v - scaleMap().p1() ) / stepSize ) * stepSize; value = scaleMap().invTransform( v ); } } if ( qAbs( stepSize ) > 1e-12 ) { if ( qFuzzyCompare( value + 1.0, 1.0 ) ) { // correct rounding error if value = 0 value = 0.0; } else { // correct rounding error at the border if ( qFuzzyCompare( value, upperBound() ) ) value = upperBound(); else if ( qFuzzyCompare( value, lowerBound() ) ) value = lowerBound(); } } return value; } /*! Update the slider according to modifications of the scale */ void QwtAbstractSlider::scaleChange() { const double value = qBound( minimum(), d_data->value, maximum() ); const bool changed = ( value != d_data->value ); if ( changed ) { d_data->value = value; } if ( d_data->isValid || changed ) Q_EMIT valueChanged( d_data->value ); updateGeometry(); update(); } //! Calling update() void QwtAbstractSlider::sliderChange() { update(); }