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