1 /* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
2  * Qwt Widget Library
3  * Copyright (C) 1997   Josef Wilgen
4  * Copyright (C) 2002   Uwe Rathmann
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the Qwt License, Version 1.0
8  *****************************************************************************/
9 
10 #include <qevent.h>
11 #include <qdrawutil.h>
12 #include <qpainter.h>
13 #include <qstyle.h>
14 #include "qwt_math.h"
15 #include "qwt_painter.h"
16 #include "qwt_paint_buffer.h"
17 #include "qwt_wheel.h"
18 
19 #define NUM_COLORS 30
20 
21 class QwtWheel::PrivateData
22 {
23 public:
PrivateData()24     PrivateData()
25     {
26         viewAngle = 175.0;
27         totalAngle = 360.0;
28         tickCnt = 10;
29         intBorder = 2;
30         borderWidth = 2;
31         wheelWidth = 20;
32 #if QT_VERSION < 0x040000
33         allocContext = 0;
34 #endif
35     };
36 
37     QRect sliderRect;
38     double viewAngle;
39     double totalAngle;
40     int tickCnt;
41     int intBorder;
42     int borderWidth;
43     int wheelWidth;
44 #if QT_VERSION < 0x040000
45     int allocContext;
46 #endif
47     QColor colors[NUM_COLORS];
48 };
49 
50 //! Constructor
QwtWheel(QWidget * parent)51 QwtWheel::QwtWheel(QWidget *parent):
52     QwtAbstractSlider(Qt::Horizontal, parent)
53 {
54     initWheel();
55 }
56 
57 #if QT_VERSION < 0x040000
QwtWheel(QWidget * parent,const char * name)58 QwtWheel::QwtWheel(QWidget *parent, const char *name):
59     QwtAbstractSlider(Qt::Horizontal, parent)
60 {
61     setName(name);
62     initWheel();
63 }
64 #endif
65 
initWheel()66 void QwtWheel::initWheel()
67 {
68     d_data = new PrivateData;
69 
70 #if QT_VERSION < 0x040000
71     setWFlags(Qt::WNoAutoErase);
72 #endif
73 
74     setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
75 
76 #if QT_VERSION >= 0x040000
77     setAttribute(Qt::WA_WState_OwnSizePolicy, false);
78 #else
79     clearWState( WState_OwnSizePolicy );
80 #endif
81 
82     setUpdateTime(50);
83 }
84 
85 //! Destructor
~QwtWheel()86 QwtWheel::~QwtWheel()
87 {
88 #if QT_VERSION < 0x040000
89     if ( d_data->allocContext )
90         QColor::destroyAllocContext( d_data->allocContext );
91 #endif
92     delete d_data;
93 }
94 
95 //! Set up the color array for the background pixmap.
setColorArray()96 void QwtWheel::setColorArray()
97 {
98     if ( d_data->colors[0].isValid() )
99         return;
100 
101 #if QT_VERSION < 0x040000
102     const QColor light = colorGroup().light();
103     const QColor dark = colorGroup().dark();
104 #else
105     const QColor light = palette().color(QPalette::Light);
106     const QColor dark = palette().color(QPalette::Dark);
107 #endif
108 
109     if ( !d_data->colors[0].isValid() ||
110         d_data->colors[0] != light ||
111         d_data->colors[NUM_COLORS - 1] != dark )
112     {
113 #if QT_VERSION < 0x040000
114         if ( d_data->allocContext )
115             QColor::destroyAllocContext( d_data->allocContext );
116 
117         d_data->allocContext = QColor::enterAllocContext();
118 #endif
119 
120         d_data->colors[0] = light;
121         d_data->colors[NUM_COLORS - 1] = dark;
122 
123         int dh, ds, dv, lh, ls, lv;
124 #if QT_VERSION < 0x040000
125         d_data->colors[0].rgb(&lh, &ls, &lv);
126         d_data->colors[NUM_COLORS - 1].rgb(&dh, &ds, &dv);
127 #else
128         d_data->colors[0].getRgb(&lh, &ls, &lv);
129         d_data->colors[NUM_COLORS - 1].getRgb(&dh, &ds, &dv);
130 #endif
131 
132         for ( int i = 1; i < NUM_COLORS - 1; ++i )
133         {
134             const double factor = double(i) / double(NUM_COLORS);
135 
136             d_data->colors[i].setRgb( lh + int( double(dh - lh) * factor ),
137                       ls + int( double(ds - ls) * factor ),
138                       lv + int( double(dv - lv) * factor ));
139         }
140 #if QT_VERSION < 0x040000
141         QColor::leaveAllocContext();
142 #endif
143     }
144 }
145 
146 /*!
147   \brief Adjust the number of grooves in the wheel's surface.
148 
149   The number of grooves is limited to 6 <= cnt <= 50.
150   Values outside this range will be clipped.
151   The default value is 10.
152 
153   \param cnt Number of grooves per 360 degrees
154   \sa tickCnt()
155 */
setTickCnt(int cnt)156 void QwtWheel::setTickCnt(int cnt)
157 {
158     d_data->tickCnt = qwtLim( cnt, 6, 50 );
159     update();
160 }
161 
162 /*!
163   \return Number of grooves in the wheel's surface.
164   \sa setTickCnt()
165 */
tickCnt() const166 int QwtWheel::tickCnt() const
167 {
168     return d_data->tickCnt;
169 }
170 
171 /*!
172     \return mass
173 */
mass() const174 double QwtWheel::mass() const
175 {
176     return QwtAbstractSlider::mass();
177 }
178 
179 /*!
180   \brief Set the internal border width of the wheel.
181 
182   The internal border must not be smaller than 1
183   and is limited in dependence on the wheel's size.
184   Values outside the allowed range will be clipped.
185 
186   The internal border defaults to 2.
187 
188   \param w border width
189   \sa internalBorder()
190 */
setInternalBorder(int w)191 void QwtWheel::setInternalBorder(int w)
192 {
193     const int d = qwtMin( width(), height() ) / 3;
194     w = qwtMin( w, d );
195     d_data->intBorder = qwtMax( w, 1 );
196     layoutWheel();
197 }
198 
199 /*!
200    \return Internal border width of the wheel.
201    \sa setInternalBorder()
202 */
internalBorder() const203 int QwtWheel::internalBorder() const
204 {
205     return d_data->intBorder;
206 }
207 
208 /*!
209    Draw the Wheel's background gradient
210 
211    \param painter Painter
212    \param r Bounding rectangle
213 */
drawWheelBackground(QPainter * painter,const QRect & r)214 void QwtWheel::drawWheelBackground(QPainter *painter, const QRect &r )
215 {
216     painter->save();
217 
218     //
219     // initialize pens
220     //
221 #if QT_VERSION < 0x040000
222     const QColor light = colorGroup().light();
223     const QColor dark = colorGroup().dark();
224 #else
225     const QColor light = palette().color(QPalette::Light);
226     const QColor dark = palette().color(QPalette::Dark);
227 #endif
228 
229     QPen lightPen;
230     lightPen.setColor(light);
231     lightPen.setWidth(d_data->intBorder);
232 
233     QPen darkPen;
234     darkPen.setColor(dark);
235     darkPen.setWidth(d_data->intBorder);
236 
237     setColorArray();
238 
239     //
240     // initialize auxiliary variables
241     //
242 
243     const int nFields = NUM_COLORS * 13 / 10;
244     const int hiPos = nFields - NUM_COLORS + 1;
245 
246     if ( orientation() == Qt::Horizontal )
247     {
248         const int rx = r.x();
249         int ry = r.y() + d_data->intBorder;
250         const int rh = r.height() - 2* d_data->intBorder;
251         const int rw = r.width();
252         //
253         //  draw shaded background
254         //
255         int x1 = rx;
256         for (int i = 1; i < nFields; i++ )
257         {
258             const int x2 = rx + (rw * i) / nFields;
259             painter->fillRect(x1, ry, x2-x1 + 1 ,rh,
260                 d_data->colors[qwtAbs(i-hiPos)]);
261             x1 = x2 + 1;
262         }
263         painter->fillRect(x1, ry, rw - (x1 - rx), rh,
264             d_data->colors[NUM_COLORS - 1]);
265 
266         //
267         // draw internal border
268         //
269         painter->setPen(lightPen);
270         ry = r.y() + d_data->intBorder / 2;
271         painter->drawLine(r.x(), ry, r.x() + r.width() , ry);
272 
273         painter->setPen(darkPen);
274         ry = r.y() + r.height() - (d_data->intBorder - d_data->intBorder / 2);
275         painter->drawLine(r.x(), ry , r.x() + r.width(), ry);
276     }
277     else // Qt::Vertical
278     {
279         int rx = r.x() + d_data->intBorder;
280         const int ry = r.y();
281         const int rh = r.height();
282         const int rw = r.width() - 2 * d_data->intBorder;
283 
284         //
285         // draw shaded background
286         //
287         int y1 = ry;
288         for ( int i = 1; i < nFields; i++ )
289         {
290             const int y2 = ry + (rh * i) / nFields;
291             painter->fillRect(rx, y1, rw, y2-y1 + 1,
292                 d_data->colors[qwtAbs(i-hiPos)]);
293             y1 = y2 + 1;
294         }
295         painter->fillRect(rx, y1, rw, rh - (y1 - ry),
296             d_data->colors[NUM_COLORS - 1]);
297 
298         //
299         //  draw internal borders
300         //
301         painter->setPen(lightPen);
302         rx = r.x() + d_data->intBorder / 2;
303         painter->drawLine(rx, r.y(), rx, r.y() + r.height());
304 
305         painter->setPen(darkPen);
306         rx = r.x() + r.width() - (d_data->intBorder - d_data->intBorder / 2);
307         painter->drawLine(rx, r.y(), rx , r.y() + r.height());
308     }
309 
310     painter->restore();
311 }
312 
313 
314 /*!
315   \brief Set the total angle which the wheel can be turned.
316 
317   One full turn of the wheel corresponds to an angle of
318   360 degrees. A total angle of n*360 degrees means
319   that the wheel has to be turned n times around its axis
320   to get from the minimum value to the maximum value.
321 
322   The default setting of the total angle is 360 degrees.
323 
324   \param angle total angle in degrees
325   \sa totalAngle()
326 */
setTotalAngle(double angle)327 void QwtWheel::setTotalAngle(double angle)
328 {
329     if ( angle < 0.0 )
330         angle = 0.0;
331 
332     d_data->totalAngle = angle;
333     update();
334 }
335 
336 /*!
337   \return Total angle which the wheel can be turned.
338   \sa setTotalAngle()
339 */
totalAngle() const340 double QwtWheel::totalAngle() const
341 {
342     return d_data->totalAngle;
343 }
344 
345 /*!
346   \brief Set the wheel's orientation.
347   \param o Orientation. Allowed values are
348            Qt::Horizontal and Qt::Vertical.
349    Defaults to Qt::Horizontal.
350   \sa QwtAbstractSlider::orientation()
351 */
setOrientation(Qt::Orientation o)352 void QwtWheel::setOrientation(Qt::Orientation o)
353 {
354     if ( orientation() == o )
355         return;
356 
357 #if QT_VERSION >= 0x040000
358     if ( !testAttribute(Qt::WA_WState_OwnSizePolicy) )
359 #else
360     if ( !testWState( WState_OwnSizePolicy ) )
361 #endif
362     {
363         QSizePolicy sp = sizePolicy();
364         sp.transpose();
365         setSizePolicy(sp);
366 
367 #if QT_VERSION >= 0x040000
368         setAttribute(Qt::WA_WState_OwnSizePolicy, false);
369 #else
370         clearWState( WState_OwnSizePolicy );
371 #endif
372     }
373 
374     QwtAbstractSlider::setOrientation(o);
375     layoutWheel();
376 }
377 
378 /*!
379   \brief Specify the visible portion of the wheel.
380 
381   You may use this function for fine-tuning the appearance of
382   the wheel. The default value is 175 degrees. The value is
383   limited from 10 to 175 degrees.
384 
385   \param angle Visible angle in degrees
386   \sa viewAngle(), setTotalAngle()
387 */
setViewAngle(double angle)388 void QwtWheel::setViewAngle(double angle)
389 {
390     d_data->viewAngle = qwtLim( angle, 10.0, 175.0 );
391     update();
392 }
393 
394 /*!
395   \return Visible portion of the wheel
396   \sa setViewAngle(), totalAngle()
397 */
viewAngle() const398 double QwtWheel::viewAngle() const
399 {
400     return d_data->viewAngle;
401 }
402 
403 /*!
404   \brief Redraw the wheel
405   \param painter painter
406   \param r contents rectangle
407 */
drawWheel(QPainter * painter,const QRect & r)408 void QwtWheel::drawWheel( QPainter *painter, const QRect &r )
409 {
410     //
411     // draw background gradient
412     //
413     drawWheelBackground( painter, r );
414 
415     if ( maxValue() == minValue() || d_data->totalAngle == 0.0 )
416         return;
417 
418 #if QT_VERSION < 0x040000
419     const QColor light = colorGroup().light();
420     const QColor dark = colorGroup().dark();
421 #else
422     const QColor light = palette().color(QPalette::Light);
423     const QColor dark = palette().color(QPalette::Dark);
424 #endif
425 
426     const double sign = (minValue() < maxValue()) ? 1.0 : -1.0;
427     double cnvFactor = qwtAbs(d_data->totalAngle / (maxValue() - minValue()));
428     const double halfIntv = 0.5 * d_data->viewAngle / cnvFactor;
429     const double loValue = value() - halfIntv;
430     const double hiValue = value() + halfIntv;
431     const double tickWidth = 360.0 / double(d_data->tickCnt) / cnvFactor;
432     const double sinArc = sin(d_data->viewAngle * M_PI / 360.0);
433     cnvFactor *= M_PI / 180.0;
434 
435 
436     //
437     // draw grooves
438     //
439     if ( orientation() == Qt::Horizontal )
440     {
441         const double halfSize = double(r.width()) * 0.5;
442 
443         int l1 = r.y() + d_data->intBorder;
444         int l2 = r.y() + r.height() - d_data->intBorder - 1;
445 
446         // draw one point over the border if border > 1
447         if ( d_data->intBorder > 1 )
448         {
449             l1 --;
450             l2 ++;
451         }
452 
453         const int maxpos = r.x() + r.width() - 2;
454         const int minpos = r.x() + 2;
455 
456         //
457         // draw tick marks
458         //
459         for ( double tickValue = ceil(loValue / tickWidth) * tickWidth;
460             tickValue < hiValue; tickValue += tickWidth )
461         {
462             //
463             //  calculate position
464             //
465             const int tickPos = r.x() + r.width()
466                 - int( halfSize
467                     * (sinArc + sign *  sin((tickValue - value()) * cnvFactor))
468                     / sinArc);
469             //
470             // draw vertical line
471             //
472             if ( (tickPos <= maxpos) && (tickPos > minpos) )
473             {
474                 painter->setPen(dark);
475                 painter->drawLine(tickPos -1 , l1, tickPos - 1,  l2 );
476                 painter->setPen(light);
477                 painter->drawLine(tickPos, l1, tickPos, l2);
478             }
479         }
480     }
481     else if ( orientation() == Qt::Vertical )
482     {
483         const double halfSize = double(r.height()) * 0.5;
484 
485         int l1 = r.x() + d_data->intBorder;
486         int l2 = r.x() + r.width() - d_data->intBorder - 1;
487 
488         if ( d_data->intBorder > 1 )
489         {
490             l1--;
491             l2++;
492         }
493 
494         const int maxpos = r.y() + r.height() - 2;
495         const int minpos = r.y() + 2;
496 
497         //
498         // draw tick marks
499         //
500         for ( double tickValue = ceil(loValue / tickWidth) * tickWidth;
501             tickValue < hiValue; tickValue += tickWidth )
502         {
503 
504             //
505             // calculate position
506             //
507             const int tickPos = r.y() + int( halfSize *
508                 (sinArc + sign * sin((tickValue - value()) * cnvFactor))
509                 / sinArc);
510 
511             //
512             //  draw horizontal line
513             //
514             if ( (tickPos <= maxpos) && (tickPos > minpos) )
515             {
516                 painter->setPen(dark);
517                 painter->drawLine(l1, tickPos - 1 ,l2, tickPos - 1);
518                 painter->setPen(light);
519                 painter->drawLine(l1, tickPos, l2, tickPos);
520             }
521         }
522     }
523 }
524 
525 
526 //! Determine the value corresponding to a specified point
getValue(const QPoint & p)527 double QwtWheel::getValue( const QPoint &p )
528 {
529     // The reference position is arbitrary, but the
530     // sign of the offset is important
531     int w, dx;
532     if ( orientation() == Qt::Vertical )
533     {
534         w = d_data->sliderRect.height();
535         dx = d_data->sliderRect.y() - p.y();
536     }
537     else
538     {
539         w = d_data->sliderRect.width();
540         dx = p.x() - d_data->sliderRect.x();
541     }
542 
543     // w pixels is an arc of viewAngle degrees,
544     // so we convert change in pixels to change in angle
545     const double ang = dx * d_data->viewAngle / w;
546 
547     // value range maps to totalAngle degrees,
548     // so convert the change in angle to a change in value
549     const double val = ang * ( maxValue() - minValue() ) / d_data->totalAngle;
550 
551     // Note, range clamping and rasterizing to step is automatically
552     // handled by QwtAbstractSlider, so we simply return the change in value
553     return val;
554 }
555 
556 //! Qt Resize Event
resizeEvent(QResizeEvent *)557 void QwtWheel::resizeEvent(QResizeEvent *)
558 {
559     layoutWheel( false );
560 }
561 
562 //! Recalculate the slider's geometry and layout based on
563 //  the current rect and fonts.
564 //  \param update_geometry  notify the layout system and call update
565 //         to redraw the scale
layoutWheel(bool update_geometry)566 void QwtWheel::layoutWheel( bool update_geometry )
567 {
568     const QRect r = this->rect();
569     d_data->sliderRect.setRect(r.x() + d_data->borderWidth, r.y() + d_data->borderWidth,
570         r.width() - 2*d_data->borderWidth, r.height() - 2*d_data->borderWidth);
571 
572     if ( update_geometry )
573     {
574         updateGeometry();
575         update();
576     }
577 }
578 
579 //! Qt Paint Event
paintEvent(QPaintEvent * e)580 void QwtWheel::paintEvent(QPaintEvent *e)
581 {
582     // Use double-buffering
583     const QRect &ur = e->rect();
584     if ( ur.isValid() )
585     {
586 #if QT_VERSION < 0x040000
587         QwtPaintBuffer paintBuffer(this, ur);
588         draw(paintBuffer.painter(), ur);
589 #else
590         QPainter painter(this);
591         draw(&painter, ur);
592 #endif
593     }
594 }
595 
596 /*!
597    Redraw panel and wheel
598    \param painter Painter
599 */
draw(QPainter * painter,const QRect &)600 void QwtWheel::draw(QPainter *painter, const QRect&)
601 {
602     qDrawShadePanel( painter, rect().x(), rect().y(),
603         rect().width(), rect().height(),
604 #if QT_VERSION < 0x040000
605         colorGroup(),
606 #else
607         palette(),
608 #endif
609         true, d_data->borderWidth );
610 
611     drawWheel( painter, d_data->sliderRect );
612 
613     if ( hasFocus() )
614         QwtPainter::drawFocusRect(painter, this);
615 }
616 
617 //! Notify value change
valueChange()618 void QwtWheel::valueChange()
619 {
620     QwtAbstractSlider::valueChange();
621     update();
622 }
623 
624 
625 /*!
626   \brief Determine the scrolling mode and direction corresponding
627          to a specified point
628   \param p point
629   \param scrollMode scrolling mode
630   \param direction direction
631 */
getScrollMode(const QPoint & p,int & scrollMode,int & direction)632 void QwtWheel::getScrollMode( const QPoint &p, int &scrollMode, int &direction)
633 {
634     if ( d_data->sliderRect.contains(p) )
635         scrollMode = ScrMouse;
636     else
637         scrollMode = ScrNone;
638 
639     direction = 0;
640 }
641 
642 /*!
643   \brief Set the mass of the wheel
644 
645   Assigning a mass turns the wheel into a flywheel.
646   \param val the wheel's mass
647 */
setMass(double val)648 void QwtWheel::setMass(double val)
649 {
650     QwtAbstractSlider::setMass(val);
651 }
652 
653 /*!
654   \brief Set the width of the wheel
655 
656   Corresponds to the wheel height for horizontal orientation,
657   and the wheel width for vertical orientation.
658   \param w the wheel's width
659 */
setWheelWidth(int w)660 void QwtWheel::setWheelWidth(int w)
661 {
662     d_data->wheelWidth = w;
663     layoutWheel();
664 }
665 
666 /*!
667   \return a size hint
668 */
sizeHint() const669 QSize QwtWheel::sizeHint() const
670 {
671     return minimumSizeHint();
672 }
673 
674 /*!
675   \brief Return a minimum size hint
676   \warning The return value is based on the wheel width.
677 */
minimumSizeHint() const678 QSize QwtWheel::minimumSizeHint() const
679 {
680     QSize sz( 3*d_data->wheelWidth + 2*d_data->borderWidth,
681     d_data->wheelWidth + 2*d_data->borderWidth );
682     if ( orientation() != Qt::Horizontal )
683         sz.transpose();
684     return sz;
685 }
686 
687 /*!
688   \brief Call update() when the palette changes
689 */
paletteChange(const QPalette &)690 void QwtWheel::paletteChange( const QPalette& )
691 {
692     update();
693 }
694 
695