/* -*- 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_knob.h" #include "qwt_round_scale_draw.h" #include "qwt_math.h" #include "qwt_painter.h" #include "qwt_scale_map.h" #include #include #include #include #include #include #include #if QT_VERSION < 0x040601 #define qAtan2(y, x) ::atan2(y, x) #define qFabs(x) ::fabs(x) #define qFastCos(x) qCos(x) #define qFastSin(x) qSin(x) #endif static QSize qwtKnobSizeHint( const QwtKnob *knob, int min ) { int knobWidth = knob->knobWidth(); if ( knobWidth <= 0 ) knobWidth = qMax( 3 * knob->markerSize(), min ); // Add the scale radial thickness to the knobWidth const int extent = qCeil( knob->scaleDraw()->extent( knob->font() ) ); const int d = 2 * ( extent + 4 ) + knobWidth; int left, right, top, bottom; knob->getContentsMargins( &left, &top, &right, &bottom ); return QSize( d + left + right, d + top + bottom ); } static inline double qwtToScaleAngle( double angle ) { // the map is counter clockwise with the origin // at 90° using angles from -180° -> 180° double a = 90.0 - angle; if ( a <= -180.0 ) a += 360.0; else if ( a >= 180.0 ) a -= 360.0; return a; } static double qwtToDegrees( double value ) { return qwtNormalizeDegrees( 90.0 - value ); } class QwtKnob::PrivateData { public: PrivateData(): knobStyle( QwtKnob::Raised ), markerStyle( QwtKnob::Notch ), borderWidth( 2 ), borderDist( 4 ), scaleDist( 4 ), maxScaleTicks( 11 ), knobWidth( 0 ), alignment( Qt::AlignCenter ), markerSize( 8 ), totalAngle( 270.0 ), mouseOffset( 0.0 ) { } QwtKnob::KnobStyle knobStyle; QwtKnob::MarkerStyle markerStyle; int borderWidth; int borderDist; int scaleDist; int maxScaleTicks; int knobWidth; Qt::Alignment alignment; int markerSize; double totalAngle; double mouseOffset; }; /*! \brief Constructor Construct a knob with an angle of 270°. The style is QwtKnob::Raised and the marker style is QwtKnob::Notch. The width of the knob is set to 50 pixels. \param parent Parent widget \sa setTotalAngle() */ QwtKnob::QwtKnob( QWidget* parent ): QwtAbstractSlider( parent ) { d_data = new PrivateData; setScaleDraw( new QwtRoundScaleDraw() ); setTotalAngle( 270.0 ); setScale( 0.0, 10.0 ); setValue( 0.0 ); setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding ); } //! Destructor QwtKnob::~QwtKnob() { delete d_data; } /*! \brief Set the knob type \param knobStyle Knob type \sa knobStyle(), setBorderWidth() */ void QwtKnob::setKnobStyle( KnobStyle knobStyle ) { if ( d_data->knobStyle != knobStyle ) { d_data->knobStyle = knobStyle; update(); } } /*! \return Marker type of the knob \sa setKnobStyle(), setBorderWidth() */ QwtKnob::KnobStyle QwtKnob::knobStyle() const { return d_data->knobStyle; } /*! \brief Set the marker type of the knob \param markerStyle Marker type \sa markerStyle(), setMarkerSize() */ void QwtKnob::setMarkerStyle( MarkerStyle markerStyle ) { if ( d_data->markerStyle != markerStyle ) { d_data->markerStyle = markerStyle; update(); } } /*! \return Marker type of the knob \sa setMarkerStyle(), setMarkerSize() */ QwtKnob::MarkerStyle QwtKnob::markerStyle() const { return d_data->markerStyle; } /*! \brief Set the total angle by which the knob can be turned \param angle Angle in degrees. The angle has to be between [10, 360] degrees. Angles above 360 ( so that the knob can be turned several times around its axis ) have to be set using setNumTurns(). The default angle is 270 degrees. \sa totalAngle(), setNumTurns() */ void QwtKnob::setTotalAngle ( double angle ) { angle = qBound( 10.0, angle, 360.0 ); if ( angle != d_data->totalAngle ) { d_data->totalAngle = angle; scaleDraw()->setAngleRange( -0.5 * d_data->totalAngle, 0.5 * d_data->totalAngle ); updateGeometry(); update(); } } /*! \return the total angle \sa setTotalAngle(), setNumTurns(), numTurns() */ double QwtKnob::totalAngle() const { return d_data->totalAngle; } /*! \brief Set the number of turns When numTurns > 1 the knob can be turned several times around its axis - otherwise the total angle is floored to 360°. \sa numTurns(), totalAngle(), setTotalAngle() */ void QwtKnob::setNumTurns( int numTurns ) { numTurns = qMax( numTurns, 1 ); if ( numTurns == 1 && d_data->totalAngle <= 360.0 ) return; const double angle = numTurns * 360.0; if ( angle != d_data->totalAngle ) { d_data->totalAngle = angle; scaleDraw()->setAngleRange( -0.5 * d_data->totalAngle, 0.5 * d_data->totalAngle ); updateGeometry(); update(); } } /*! \return Number of turns. When the total angle is below 360° numTurns() is ceiled to 1. \sa setNumTurns(), setTotalAngle(), totalAngle() */ int QwtKnob::numTurns() const { return qCeil( d_data->totalAngle / 360.0 ); } /*! Change the scale draw of the knob For changing the labels of the scales, it is necessary to derive from QwtRoundScaleDraw and overload QwtRoundScaleDraw::label(). \sa scaleDraw() */ void QwtKnob::setScaleDraw( QwtRoundScaleDraw *scaleDraw ) { setAbstractScaleDraw( scaleDraw ); setTotalAngle( d_data->totalAngle ); updateGeometry(); update(); } /*! \return the scale draw of the knob \sa setScaleDraw() */ const QwtRoundScaleDraw *QwtKnob::scaleDraw() const { return static_cast( abstractScaleDraw() ); } /*! \return the scale draw of the knob \sa setScaleDraw() */ QwtRoundScaleDraw *QwtKnob::scaleDraw() { return static_cast( abstractScaleDraw() ); } /*! Calculate the bounding rectangle of the knob without the scale \return Bounding rectangle of the knob \sa knobWidth(), alignment(), QWidget::contentsRect() */ QRect QwtKnob::knobRect() const { const QRect cr = contentsRect(); const int extent = qCeil( scaleDraw()->extent( font() ) ); const int d = extent + d_data->scaleDist; int w = d_data->knobWidth; if ( w <= 0 ) { const int dim = qMin( cr.width(), cr.height() ); w = dim - 2 * ( d ); w = qMax( 0, w ); } QRect r( 0, 0, w, w ); if ( d_data->alignment & Qt::AlignLeft ) { r.moveLeft( cr.left() + d ); } else if ( d_data->alignment & Qt::AlignRight ) { r.moveRight( cr.right() - d ); } else { r.moveCenter( QPoint( cr.center().x(), r.center().y() ) ); } if ( d_data->alignment & Qt::AlignTop ) { r.moveTop( cr.top() + d ); } else if ( d_data->alignment & Qt::AlignBottom ) { r.moveBottom( cr.bottom() - d ); } else { r.moveCenter( QPoint( r.center().x(), cr.center().y() ) ); } return r; } /*! \brief Determine what to do when the user presses a mouse button. \param pos Mouse position \retval True, when pos is inside the circle of the knob. \sa scrolledTo() */ bool QwtKnob::isScrollPosition( const QPoint &pos ) const { const QRect kr = knobRect(); const QRegion region( kr, QRegion::Ellipse ); if ( region.contains( pos ) && ( pos != kr.center() ) ) { const double angle = QLineF( kr.center(), pos ).angle(); const double valueAngle = qwtToDegrees( scaleMap().transform( value() ) ); d_data->mouseOffset = qwtNormalizeDegrees( angle - valueAngle ); return true; } return false; } /*! \brief Determine the value for a new position of the mouse \param pos Mouse position \return Value for the mouse position \sa isScrollPosition() */ double QwtKnob::scrolledTo( const QPoint &pos ) const { double angle = QLineF( rect().center(), pos ).angle(); angle = qwtNormalizeDegrees( angle - d_data->mouseOffset ); if ( scaleMap().pDist() > 360.0 ) { angle = qwtToDegrees( angle ); const double v = scaleMap().transform( value() ); int numTurns = qFloor( ( v - scaleMap().p1() ) / 360.0 ); double valueAngle = qwtNormalizeDegrees( v ); if ( qAbs( valueAngle - angle ) > 180.0 ) { numTurns += ( angle > valueAngle ) ? -1 : 1; } angle += scaleMap().p1() + numTurns * 360.0; if ( !wrapping() ) { const double boundedAngle = qBound( scaleMap().p1(), angle, scaleMap().p2() ); d_data->mouseOffset += ( boundedAngle - angle ); angle = boundedAngle; } } else { angle = qwtToScaleAngle( angle ); double boundedAngle = qBound( scaleMap().p1(), angle, scaleMap().p2() ); if ( !wrapping() ) { const double currentAngle = scaleMap().transform( value() ); if ( ( currentAngle > 90.0 ) && ( boundedAngle < -90.0 ) ) boundedAngle = scaleMap().p2(); else if ( ( currentAngle < -90.0 ) && ( boundedAngle > 90.0 ) ) boundedAngle = scaleMap().p1(); d_data->mouseOffset += ( boundedAngle - angle ); } angle = boundedAngle; } return scaleMap().invTransform( angle ); } /*! Handle QEvent::StyleChange and QEvent::FontChange; \param event Change event */ void QwtKnob::changeEvent( QEvent *event ) { switch( event->type() ) { case QEvent::StyleChange: case QEvent::FontChange: { updateGeometry(); update(); break; } default: break; } } /*! Repaint the knob \param event Paint event */ void QwtKnob::paintEvent( QPaintEvent *event ) { const QRectF knobRect = this->knobRect(); QPainter painter( this ); painter.setClipRegion( event->region() ); QStyleOption opt; opt.init(this); style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this); painter.setRenderHint( QPainter::Antialiasing, true ); if ( !knobRect.contains( event->region().boundingRect() ) ) { scaleDraw()->setRadius( 0.5 * knobRect.width() + d_data->scaleDist ); scaleDraw()->moveCenter( knobRect.center() ); scaleDraw()->draw( &painter, palette() ); } drawKnob( &painter, knobRect ); drawMarker( &painter, knobRect, qwtNormalizeDegrees( scaleMap().transform( value() ) ) ); painter.setRenderHint( QPainter::Antialiasing, false ); if ( hasFocus() ) drawFocusIndicator( &painter ); } /*! \brief Draw the knob \param painter painter \param knobRect Bounding rectangle of the knob (without scale) */ void QwtKnob::drawKnob( QPainter *painter, const QRectF &knobRect ) const { double dim = qMin( knobRect.width(), knobRect.height() ); dim -= d_data->borderWidth * 0.5; QRectF aRect( 0, 0, dim, dim ); aRect.moveCenter( knobRect.center() ); QPen pen( Qt::NoPen ); if ( d_data->borderWidth > 0 ) { QColor c1 = palette().color( QPalette::Light ); QColor c2 = palette().color( QPalette::Dark ); QLinearGradient gradient( aRect.topLeft(), aRect.bottomRight() ); gradient.setColorAt( 0.0, c1 ); gradient.setColorAt( 0.3, c1 ); gradient.setColorAt( 0.7, c2 ); gradient.setColorAt( 1.0, c2 ); pen = QPen( gradient, d_data->borderWidth ); } QBrush brush; switch( d_data->knobStyle ) { case QwtKnob::Raised: { double off = 0.3 * knobRect.width(); QRadialGradient gradient( knobRect.center(), knobRect.width(), knobRect.topLeft() + QPointF( off, off ) ); gradient.setColorAt( 0.0, palette().color( QPalette::Midlight ) ); gradient.setColorAt( 1.0, palette().color( QPalette::Button ) ); brush = QBrush( gradient ); break; } case QwtKnob::Styled: { QRadialGradient gradient(knobRect.center().x() - knobRect.width() / 3, knobRect.center().y() - knobRect.height() / 2, knobRect.width() * 1.3, knobRect.center().x(), knobRect.center().y() - knobRect.height() / 2); const QColor c = palette().color( QPalette::Button ); gradient.setColorAt(0, c.lighter(110)); gradient.setColorAt(qreal(0.5), c); gradient.setColorAt(qreal(0.501), c.darker(102)); gradient.setColorAt(1, c.darker(115)); brush = QBrush( gradient ); break; } case QwtKnob::Sunken: { QLinearGradient gradient( knobRect.topLeft(), knobRect.bottomRight() ); gradient.setColorAt( 0.0, palette().color( QPalette::Mid ) ); gradient.setColorAt( 0.5, palette().color( QPalette::Button ) ); gradient.setColorAt( 1.0, palette().color( QPalette::Midlight ) ); brush = QBrush( gradient ); break; } case QwtKnob::Flat: default: brush = palette().brush( QPalette::Button ); } painter->setPen( pen ); painter->setBrush( brush ); painter->drawEllipse( aRect ); } /*! \brief Draw the marker at the knob's front \param painter Painter \param rect Bounding rectangle of the knob without scale \param angle Angle of the marker in degrees ( clockwise, 0 at the 12 o'clock position ) */ void QwtKnob::drawMarker( QPainter *painter, const QRectF &rect, double angle ) const { if ( d_data->markerStyle == NoMarker || !isValid() ) return; const double radians = qwtRadians( angle ); const double sinA = -qFastSin( radians ); const double cosA = qFastCos( radians ); const double xm = rect.center().x(); const double ym = rect.center().y(); const double margin = 4.0; double radius = 0.5 * ( rect.width() - d_data->borderWidth ) - margin; if ( radius < 1.0 ) radius = 1.0; int markerSize = d_data->markerSize; if ( markerSize <= 0 ) markerSize = qRound( 0.4 * radius ); switch ( d_data->markerStyle ) { case Notch: case Nub: { const double dotWidth = qMin( double( markerSize ), radius); const double dotCenterDist = radius - 0.5 * dotWidth; if ( dotCenterDist > 0.0 ) { const QPointF center( xm - sinA * dotCenterDist, ym - cosA * dotCenterDist ); QRectF ellipse( 0.0, 0.0, dotWidth, dotWidth ); ellipse.moveCenter( center ); QColor c1 = palette().color( QPalette::Light ); QColor c2 = palette().color( QPalette::Mid ); if ( d_data->markerStyle == Notch ) qSwap( c1, c2 ); QLinearGradient gradient( ellipse.topLeft(), ellipse.bottomRight() ); gradient.setColorAt( 0.0, c1 ); gradient.setColorAt( 1.0, c2 ); painter->setPen( Qt::NoPen ); painter->setBrush( gradient ); painter->drawEllipse( ellipse ); } break; } case Dot: { const double dotWidth = qMin( double( markerSize ), radius); const double dotCenterDist = radius - 0.5 * dotWidth; if ( dotCenterDist > 0.0 ) { const QPointF center( xm - sinA * dotCenterDist, ym - cosA * dotCenterDist ); QRectF ellipse( 0.0, 0.0, dotWidth, dotWidth ); ellipse.moveCenter( center ); painter->setPen( Qt::NoPen ); painter->setBrush( palette().color( QPalette::ButtonText ) ); painter->drawEllipse( ellipse ); } break; } case Tick: { const double rb = qMax( radius - markerSize, 1.0 ); const double re = radius; const QLineF line( xm - sinA * rb, ym - cosA * rb, xm - sinA * re, ym - cosA * re ); QPen pen( palette().color( QPalette::ButtonText ), 0 ); pen.setCapStyle( Qt::FlatCap ); painter->setPen( pen ); painter->drawLine ( line ); break; } case Triangle: { const double rb = qMax( radius - markerSize, 1.0 ); const double re = radius; painter->translate( rect.center() ); painter->rotate( angle - 90.0 ); QPolygonF polygon; polygon += QPointF( re, 0.0 ); polygon += QPointF( rb, 0.5 * ( re - rb ) ); polygon += QPointF( rb, -0.5 * ( re - rb ) ); painter->setPen( Qt::NoPen ); painter->setBrush( palette().color( QPalette::ButtonText ) ); painter->drawPolygon( polygon ); painter->resetTransform(); break; } default: break; } } /*! Draw the focus indicator \param painter Painter */ void QwtKnob::drawFocusIndicator( QPainter *painter ) const { const QRect cr = contentsRect(); int w = d_data->knobWidth; if ( w <= 0 ) { w = qMin( cr.width(), cr.height() ); } else { const int extent = qCeil( scaleDraw()->extent( font() ) ); w += 2 * ( extent + d_data->scaleDist ); } QRect focusRect( 0, 0, w, w ); focusRect.moveCenter( cr.center() ); QwtPainter::drawFocusRect( painter, this, focusRect ); } /*! \brief Set the alignment of the knob Similar to a QLabel::alignment() the flags decide how to align the knob inside of contentsRect(). The default setting is Qt::AlignCenter \param alignment Or'd alignment flags \sa alignment(), setKnobWidth(), knobRect() */ void QwtKnob::setAlignment( Qt::Alignment alignment ) { if ( d_data->alignment != alignment ) { d_data->alignment = alignment; update(); } } /*! \return Alignment of the knob inside of contentsRect() \sa setAlignment(), knobWidth(), knobRect() */ Qt::Alignment QwtKnob::alignment() const { return d_data->alignment; } /*! \brief Change the knob's width. Setting a fixed value for the diameter of the knob is helpful for aligning several knobs in a row. \param width New width \sa knobWidth(), setAlignment() \note Modifies the sizePolicy() */ void QwtKnob::setKnobWidth( int width ) { width = qMax( width, 0 ); if ( width != d_data->knobWidth ) { QSizePolicy::Policy policy; if ( width > 0 ) policy = QSizePolicy::Minimum; else policy = QSizePolicy::MinimumExpanding; setSizePolicy( policy, policy ); d_data->knobWidth = width; updateGeometry(); update(); } } //! Return the width of the knob int QwtKnob::knobWidth() const { return d_data->knobWidth; } /*! \brief Set the knob's border width \param borderWidth new border width */ void QwtKnob::setBorderWidth( int borderWidth ) { d_data->borderWidth = qMax( borderWidth, 0 ); updateGeometry(); update(); } //! Return the border width int QwtKnob::borderWidth() const { return d_data->borderWidth; } /*! \brief Set the size of the marker When setting a size <= 0 the marker will automatically scaled to 40% of the radius of the knob. \sa markerSize(), markerStyle() */ void QwtKnob::setMarkerSize( int size ) { if ( d_data->markerSize != size ) { d_data->markerSize = size; update(); } } /*! \return Marker size \sa setMarkerSize() */ int QwtKnob::markerSize() const { return d_data->markerSize; } /*! \return sizeHint() */ QSize QwtKnob::sizeHint() const { const QSize hint = qwtKnobSizeHint( this, 50 ); return hint.expandedTo( QApplication::globalStrut() ); } /*! \return Minimum size hint \sa sizeHint() */ QSize QwtKnob::minimumSizeHint() const { return qwtKnobSizeHint( this, 20 ); }