Skip to content
Snippets Groups Projects
qwt_wheel.cpp 16 KiB
Newer Older
  • Learn to ignore specific revisions
  • pixhawk's avatar
    pixhawk committed
    /* -*- 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 <qevent.h>
    #include <qdrawutil.h>
    #include <qpainter.h>
    #include <qstyle.h>
    #include "qwt_math.h"
    #include "qwt_painter.h"
    #include "qwt_paint_buffer.h"
    #include "qwt_wheel.h"
    
    #define NUM_COLORS 30
    
    class QwtWheel::PrivateData
    {
    public:
    
        PrivateData() {
    
    pixhawk's avatar
    pixhawk committed
            viewAngle = 175.0;
            totalAngle = 360.0;
            tickCnt = 10;
            intBorder = 2;
            borderWidth = 2;
            wheelWidth = 20;
    #if QT_VERSION < 0x040000
            allocContext = 0;
    #endif
        };
    
        QRect sliderRect;
        double viewAngle;
        double totalAngle;
        int tickCnt;
        int intBorder;
        int borderWidth;
        int wheelWidth;
    #if QT_VERSION < 0x040000
        int allocContext;
    #endif
        QColor colors[NUM_COLORS];
    };
    
    //! Constructor
    
    QwtWheel::QwtWheel(QWidget *parent):
    
    pixhawk's avatar
    pixhawk committed
        QwtAbstractSlider(Qt::Horizontal, parent)
    {
        initWheel();
    }
    
    #if QT_VERSION < 0x040000
    
    QwtWheel::QwtWheel(QWidget *parent, const char *name):
    
    pixhawk's avatar
    pixhawk committed
        QwtAbstractSlider(Qt::Horizontal, parent)
    {
        setName(name);
        initWheel();
    }
    #endif
    
    void QwtWheel::initWheel()
    {
        d_data = new PrivateData;
    
    #if QT_VERSION < 0x040000
        setWFlags(Qt::WNoAutoErase);
    #endif
    
        setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
    
    #if QT_VERSION >= 0x040000
        setAttribute(Qt::WA_WState_OwnSizePolicy, false);
    #else
        clearWState( WState_OwnSizePolicy );
    #endif
    
        setUpdateTime(50);
    }
    
    //! Destructor
    
    QwtWheel::~QwtWheel()
    
    pixhawk's avatar
    pixhawk committed
    {
    #if QT_VERSION < 0x040000
        if ( d_data->allocContext )
            QColor::destroyAllocContext( d_data->allocContext );
    #endif
        delete d_data;
    }
    
    //! Set up the color array for the background pixmap.
    void QwtWheel::setColorArray()
    {
    
        if ( !d_data->colors )
    
    pixhawk's avatar
    pixhawk committed
            return;
    
    #if QT_VERSION < 0x040000
        const QColor light = colorGroup().light();
        const QColor dark = colorGroup().dark();
    #else
        const QColor light = palette().color(QPalette::Light);
        const QColor dark = palette().color(QPalette::Dark);
    #endif
    
        if ( !d_data->colors[0].isValid() ||
    
                d_data->colors[0] != light ||
                d_data->colors[NUM_COLORS - 1] != dark ) {
    
    pixhawk's avatar
    pixhawk committed
    #if QT_VERSION < 0x040000
            if ( d_data->allocContext )
                QColor::destroyAllocContext( d_data->allocContext );
    
            d_data->allocContext = QColor::enterAllocContext();
    #endif
    
            d_data->colors[0] = light;
            d_data->colors[NUM_COLORS - 1] = dark;
    
            int dh, ds, dv, lh, ls, lv;
    #if QT_VERSION < 0x040000
            d_data->colors[0].rgb(&lh, &ls, &lv);
            d_data->colors[NUM_COLORS - 1].rgb(&dh, &ds, &dv);
    #else
            d_data->colors[0].getRgb(&lh, &ls, &lv);
            d_data->colors[NUM_COLORS - 1].getRgb(&dh, &ds, &dv);
    #endif
    
    
            for ( int i = 1; i < NUM_COLORS - 1; ++i ) {
    
    pixhawk's avatar
    pixhawk committed
                const double factor = double(i) / double(NUM_COLORS);
    
                d_data->colors[i].setRgb( lh + int( double(dh - lh) * factor ),
    
                                          ls + int( double(ds - ls) * factor ),
                                          lv + int( double(dv - lv) * factor ));
    
    pixhawk's avatar
    pixhawk committed
            }
    #if QT_VERSION < 0x040000
            QColor::leaveAllocContext();
    #endif
        }
    }
    
    /*!
      \brief Adjust the number of grooves in the wheel's surface.
    
      The number of grooves is limited to 6 <= cnt <= 50.
      Values outside this range will be clipped.
      The default value is 10.
      \param cnt Number of grooves per 360 degrees
    */
    void QwtWheel::setTickCnt(int cnt)
    {
        d_data->tickCnt = qwtLim( cnt, 6, 50 );
        update();
    }
    
    
    int QwtWheel::tickCnt() const
    
    pixhawk's avatar
    pixhawk committed
    {
        return d_data->tickCnt;
    }
    
    /*!
        \return mass
    */
    double QwtWheel::mass() const
    {
        return QwtAbstractSlider::mass();
    }
    
    /*!
      \brief Set the internal border width of the wheel.
    
      The internal border must not be smaller than 1
      and is limited in dependence on the wheel's size.
      Values outside the allowed range will be clipped.
    
      The internal border defaults to 2.
      \param w border width
    */
    void QwtWheel::setInternalBorder( int w )
    {
        const int d = qwtMin( width(), height() ) / 3;
        w = qwtMin( w, d );
        d_data->intBorder = qwtMax( w, 1 );
        layoutWheel();
    }
    
    
    int QwtWheel::internalBorder() const
    
    pixhawk's avatar
    pixhawk committed
    {
        return d_data->intBorder;
    }
    
    //! Draw the Wheel's background gradient
    void QwtWheel::drawWheelBackground( QPainter *p, const QRect &r )
    {
        p->save();
    
        //
        // initialize pens
        //
    #if QT_VERSION < 0x040000
        const QColor light = colorGroup().light();
        const QColor dark = colorGroup().dark();
    #else
        const QColor light = palette().color(QPalette::Light);
        const QColor dark = palette().color(QPalette::Dark);
    #endif
    
        QPen lightPen;
        lightPen.setColor(light);
        lightPen.setWidth(d_data->intBorder);
    
        QPen darkPen;
        darkPen.setColor(dark);
        darkPen.setWidth(d_data->intBorder);
    
        setColorArray();
    
        //
        // initialize auxiliary variables
        //
    
        const int nFields = NUM_COLORS * 13 / 10;
        const int hiPos = nFields - NUM_COLORS + 1;
    
    
        if ( orientation() == Qt::Horizontal ) {
    
    pixhawk's avatar
    pixhawk committed
            const int rx = r.x();
            int ry = r.y() + d_data->intBorder;
            const int rh = r.height() - 2* d_data->intBorder;
            const int rw = r.width();
            //
            //  draw shaded background
            //
            int x1 = rx;
    
            for (int i = 1; i < nFields; i++ ) {
    
    pixhawk's avatar
    pixhawk committed
                const int x2 = rx + (rw * i) / nFields;
                p->fillRect(x1, ry, x2-x1 + 1 ,rh, d_data->colors[qwtAbs(i-hiPos)]);
                x1 = x2 + 1;
            }
            p->fillRect(x1, ry, rw - (x1 - rx), rh, d_data->colors[NUM_COLORS - 1]);
    
            //
            // draw internal border
            //
            p->setPen(lightPen);
            ry = r.y() + d_data->intBorder / 2;
            p->drawLine(r.x(), ry, r.x() + r.width() , ry);
    
            p->setPen(darkPen);
            ry = r.y() + r.height() - (d_data->intBorder - d_data->intBorder / 2);
            p->drawLine(r.x(), ry , r.x() + r.width(), ry);
    
        } else { // Qt::Vertical
    
    pixhawk's avatar
    pixhawk committed
            int rx = r.x() + d_data->intBorder;
            const int ry = r.y();
            const int rh = r.height();
            const int rw = r.width() - 2 * d_data->intBorder;
    
            //
            // draw shaded background
            //
            int y1 = ry;
    
            for ( int i = 1; i < nFields; i++ ) {
    
    pixhawk's avatar
    pixhawk committed
                const int y2 = ry + (rh * i) / nFields;
                p->fillRect(rx, y1, rw, y2-y1 + 1, d_data->colors[qwtAbs(i-hiPos)]);
                y1 = y2 + 1;
            }
            p->fillRect(rx, y1, rw, rh - (y1 - ry), d_data->colors[NUM_COLORS - 1]);
    
            //
            //  draw internal borders
            //
            p->setPen(lightPen);
            rx = r.x() + d_data->intBorder / 2;
            p->drawLine(rx, r.y(), rx, r.y() + r.height());
    
            p->setPen(darkPen);
            rx = r.x() + r.width() - (d_data->intBorder - d_data->intBorder / 2);
            p->drawLine(rx, r.y(), rx , r.y() + r.height());
        }
    
        p->restore();
    }
    
    
    /*!
      \brief Set the total angle which the wheel can be turned.
    
      One full turn of the wheel corresponds to an angle of
      360 degrees. A total angle of n*360 degrees means
      that the wheel has to be turned n times around its axis
      to get from the minimum value to the maximum value.
    
      The default setting of the total angle is 360 degrees.
      \param angle total angle in degrees
    */
    void QwtWheel::setTotalAngle(double angle)
    {
        if ( angle < 0.0 )
            angle = 0.0;
    
        d_data->totalAngle = angle;
        update();
    }
    
    
    double QwtWheel::totalAngle() const
    
    pixhawk's avatar
    pixhawk committed
    {
        return d_data->totalAngle;
    }
    
    /*!
      \brief Set the wheel's orientation.
      \param o Orientation. Allowed values are
               Qt::Horizontal and Qt::Vertical.
       Defaults to Qt::Horizontal.
      \sa QwtAbstractSlider::orientation()
    */
    void QwtWheel::setOrientation(Qt::Orientation o)
    {
        if ( orientation() == o )
            return;
    
    #if QT_VERSION >= 0x040000
        if ( !testAttribute(Qt::WA_WState_OwnSizePolicy) )
    #else
    
        if ( !testWState( WState_OwnSizePolicy ) )
    
    pixhawk's avatar
    pixhawk committed
    #endif
        {
            QSizePolicy sp = sizePolicy();
            sp.transpose();
            setSizePolicy(sp);
    
    #if QT_VERSION >= 0x040000
            setAttribute(Qt::WA_WState_OwnSizePolicy, false);
    #else
            clearWState( WState_OwnSizePolicy );
    #endif
        }
    
        QwtAbstractSlider::setOrientation(o);
        layoutWheel();
    }
    
    /*!
      \brief Specify the visible portion of the wheel.
    
      You may use this function for fine-tuning the appearance of
      the wheel. The default value is 175 degrees. The value is
      limited from 10 to 175 degrees.
      \param angle Visible angle in degrees
    */
    void QwtWheel::setViewAngle(double angle)
    {
        d_data->viewAngle = qwtLim( angle, 10.0, 175.0 );
        update();
    }
    
    
    double QwtWheel::viewAngle() const
    
    pixhawk's avatar
    pixhawk committed
    {
        return d_data->viewAngle;
    }
    
    /*!
      \brief Redraw the wheel
      \param p painter
      \param r contents rectangle
    */
    void QwtWheel::drawWheel( QPainter *p, const QRect &r )
    {
        //
        // draw background gradient
        //
        drawWheelBackground( p, r );
    
        if ( maxValue() == minValue() || d_data->totalAngle == 0.0 )
            return;
    
    #if QT_VERSION < 0x040000
        const QColor light = colorGroup().light();
        const QColor dark = colorGroup().dark();
    #else
        const QColor light = palette().color(QPalette::Light);
        const QColor dark = palette().color(QPalette::Dark);
    #endif
    
        const double sign = (minValue() < maxValue()) ? 1.0 : -1.0;
        double cnvFactor = qwtAbs(d_data->totalAngle / (maxValue() - minValue()));
        const double halfIntv = 0.5 * d_data->viewAngle / cnvFactor;
        const double loValue = value() - halfIntv;
        const double hiValue = value() + halfIntv;
        const double tickWidth = 360.0 / double(d_data->tickCnt) / cnvFactor;
        const double sinArc = sin(d_data->viewAngle * M_PI / 360.0);
        cnvFactor *= M_PI / 180.0;
    
    
        //
        // draw grooves
        //
    
        if ( orientation() == Qt::Horizontal ) {
    
    pixhawk's avatar
    pixhawk committed
            const double halfSize = double(r.width()) * 0.5;
    
            int l1 = r.y() + d_data->intBorder;
            int l2 = r.y() + r.height() - d_data->intBorder - 1;
    
            // draw one point over the border if border > 1
    
            if ( d_data->intBorder > 1 ) {
    
    pixhawk's avatar
    pixhawk committed
                l1 --;
                l2 ++;
            }
    
            const int maxpos = r.x() + r.width() - 2;
            const int minpos = r.x() + 2;
    
            //
            // draw tick marks
            //
            for ( double tickValue = ceil(loValue / tickWidth) * tickWidth;
    
                    tickValue < hiValue; tickValue += tickWidth ) {
    
    pixhawk's avatar
    pixhawk committed
                //
                //  calculate position
                //
                const int tickPos = r.x() + r.width()
    
                                    - int( halfSize
                                           * (sinArc + sign *  sin((tickValue - value()) * cnvFactor))
                                           / sinArc);
    
    pixhawk's avatar
    pixhawk committed
                //
                // draw vertical line
                //
    
                if ( (tickPos <= maxpos) && (tickPos > minpos) ) {
    
    pixhawk's avatar
    pixhawk committed
                    p->setPen(dark);
    
                    p->drawLine(tickPos -1 , l1, tickPos - 1,  l2 );
    
    pixhawk's avatar
    pixhawk committed
                    p->setPen(light);
    
                    p->drawLine(tickPos, l1, tickPos, l2);
    
    pixhawk's avatar
    pixhawk committed
                }
            }
    
        } else if ( orientation() == Qt::Vertical ) {
    
    pixhawk's avatar
    pixhawk committed
            const double halfSize = double(r.height()) * 0.5;
    
            int l1 = r.x() + d_data->intBorder;
            int l2 = r.x() + r.width() - d_data->intBorder - 1;
    
    
            if ( d_data->intBorder > 1 ) {
    
    pixhawk's avatar
    pixhawk committed
                l1--;
                l2++;
            }
    
            const int maxpos = r.y() + r.height() - 2;
            const int minpos = r.y() + 2;
    
            //
            // draw tick marks
            //
            for ( double tickValue = ceil(loValue / tickWidth) * tickWidth;
    
                    tickValue < hiValue; tickValue += tickWidth ) {
    
    pixhawk's avatar
    pixhawk committed
    
                //
                // calculate position
                //
                const int tickPos = r.y() + int( halfSize *
    
                                                 (sinArc + sign * sin((tickValue - value()) * cnvFactor))
                                                 / sinArc);
    
    pixhawk's avatar
    pixhawk committed
    
                //
                //  draw horizontal line
                //
    
                if ( (tickPos <= maxpos) && (tickPos > minpos) ) {
    
    pixhawk's avatar
    pixhawk committed
                    p->setPen(dark);
    
                    p->drawLine(l1, tickPos - 1 ,l2, tickPos - 1);
    
    pixhawk's avatar
    pixhawk committed
                    p->setPen(light);
    
                    p->drawLine(l1, tickPos, l2, tickPos);
    
    pixhawk's avatar
    pixhawk committed
                }
            }
        }
    }
    
    
    //! Determine the value corresponding to a specified point
    double QwtWheel::getValue( const QPoint &p )
    {
        // The reference position is arbitrary, but the
        // sign of the offset is important
        int w, dx;
    
        if ( orientation() == Qt::Vertical ) {
    
    pixhawk's avatar
    pixhawk committed
            w = d_data->sliderRect.height();
            dx = d_data->sliderRect.y() - p.y();
    
    pixhawk's avatar
    pixhawk committed
            w = d_data->sliderRect.width();
            dx = p.x() - d_data->sliderRect.x();
        }
    
        // w pixels is an arc of viewAngle degrees,
        // so we convert change in pixels to change in angle
        const double ang = dx * d_data->viewAngle / w;
    
        // value range maps to totalAngle degrees,
        // so convert the change in angle to a change in value
        const double val = ang * ( maxValue() - minValue() ) / d_data->totalAngle;
    
        // Note, range clamping and rasterizing to step is automatically
        // handled by QwtAbstractSlider, so we simply return the change in value
        return val;
    }
    
    //! Qt Resize Event
    void QwtWheel::resizeEvent(QResizeEvent *)
    {
        layoutWheel( false );
    }
    
    //! Recalculate the slider's geometry and layout based on
    //  the current rect and fonts.
    //  \param update_geometry  notify the layout system and call update
    //         to redraw the scale
    void QwtWheel::layoutWheel( bool update_geometry )
    {
        const QRect r = this->rect();
        d_data->sliderRect.setRect(r.x() + d_data->borderWidth, r.y() + d_data->borderWidth,
    
                                   r.width() - 2*d_data->borderWidth, r.height() - 2*d_data->borderWidth);
    
    pixhawk's avatar
    pixhawk committed
    
    
        if ( update_geometry ) {
    
    pixhawk's avatar
    pixhawk committed
            updateGeometry();
            update();
        }
    }
    
    //! Qt Paint Event
    void QwtWheel::paintEvent(QPaintEvent *e)
    {
        // Use double-buffering
        const QRect &ur = e->rect();
    
        if ( ur.isValid() ) {
    
    pixhawk's avatar
    pixhawk committed
    #if QT_VERSION < 0x040000
            QwtPaintBuffer paintBuffer(this, ur);
            draw(paintBuffer.painter(), ur);
    #else
            QPainter painter(this);
            draw(&painter, ur);
    #endif
        }
    }
    
    //! Redraw panel and wheel
    void QwtWheel::draw(QPainter *painter, const QRect&)
    {
        qDrawShadePanel( painter, rect().x(), rect().y(),
    
                         rect().width(), rect().height(),
    
    pixhawk's avatar
    pixhawk committed
    #if QT_VERSION < 0x040000
    
                         colorGroup(),
    
    pixhawk's avatar
    pixhawk committed
    #else
    
    pixhawk's avatar
    pixhawk committed
    #endif
    
                         true, d_data->borderWidth );
    
    pixhawk's avatar
    pixhawk committed
    
        drawWheel( painter, d_data->sliderRect );
    
        if ( hasFocus() )
            QwtPainter::drawFocusRect(painter, this);
    }
    
    
    //! Notify value change
    
    pixhawk's avatar
    pixhawk committed
    void QwtWheel::valueChange()
    {
        QwtAbstractSlider::valueChange();
        update();
    }
    
    
    /*!
      \brief Determine the scrolling mode and direction corresponding
             to a specified point
      \param p point
      \param scrollMode scrolling mode
      \param direction direction
    */
    void QwtWheel::getScrollMode( const QPoint &p, int &scrollMode, int &direction)
    {
        if ( d_data->sliderRect.contains(p) )
            scrollMode = ScrMouse;
        else
            scrollMode = ScrNone;
    
        direction = 0;
    }
    
    /*!
      \brief Set the mass of the wheel
    
      Assigning a mass turns the wheel into a flywheel.
      \param val the wheel's mass
    */
    void QwtWheel::setMass(double val)
    {
        QwtAbstractSlider::setMass(val);
    }
    
    /*!
      \brief Set the width of the wheel
    
      Corresponds to the wheel height for horizontal orientation,
      and the wheel width for vertical orientation.
      \param w the wheel's width
    */
    void QwtWheel::setWheelWidth(int w)
    {
        d_data->wheelWidth = w;
        layoutWheel();
    }
    
    /*!
      \return a size hint
    */
    QSize QwtWheel::sizeHint() const
    {
        return minimumSizeHint();
    }
    
    /*!
      \brief Return a minimum size hint
      \warning The return value is based on the wheel width.
    */
    QSize QwtWheel::minimumSizeHint() const
    {
        QSize sz( 3*d_data->wheelWidth + 2*d_data->borderWidth,
    
                  d_data->wheelWidth + 2*d_data->borderWidth );
    
    pixhawk's avatar
    pixhawk committed
        if ( orientation() != Qt::Horizontal )
            sz.transpose();
        return sz;
    }
    
    /*!
      \brief Call update() when the palette changes
    */
    void QwtWheel::paletteChange( const QPalette& )
    {
        update();
    }