Qwt User's Guide  6.2.0
qwt_knob.cpp
1 /******************************************************************************
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 "qwt_knob.h"
11 #include "qwt_round_scale_draw.h"
12 #include "qwt_painter.h"
13 #include "qwt_scale_map.h"
14 #include "qwt_math.h"
15 #include "qwt.h"
16 
17 #include <qpainter.h>
18 #include <qpalette.h>
19 #include <qstyle.h>
20 #include <qstyleoption.h>
21 #include <qevent.h>
22 #include <qmargins.h>
23 #include <qmath.h>
24 
25 static QSize qwtKnobSizeHint( const QwtKnob* knob, int min )
26 {
27  int knobWidth = knob->knobWidth();
28  if ( knobWidth <= 0 )
29  knobWidth = qMax( 3 * knob->markerSize(), min );
30 
31  // Add the scale radial thickness to the knobWidth
32  const int extent = qwtCeil( knob->scaleDraw()->extent( knob->font() ) );
33  const int d = 2 * ( extent + 4 ) + knobWidth;
34 
35  const QMargins m = knob->contentsMargins();
36  return QSize( d + m.left() + m.right(), d + m.top() + m.bottom() );
37 }
38 
39 static inline double qwtToScaleAngle( double angle )
40 {
41  // the map is counter clockwise with the origin
42  // at 90° using angles from -180° -> 180°
43 
44  double a = 90.0 - angle;
45  if ( a <= -180.0 )
46  a += 360.0;
47  else if ( a >= 180.0 )
48  a -= 360.0;
49 
50  return a;
51 }
52 
53 static double qwtToDegrees( double value )
54 {
55  return qwtNormalizeDegrees( 90.0 - value );
56 }
57 
58 class QwtKnob::PrivateData
59 {
60  public:
61  PrivateData()
64  , borderWidth( 2 )
65  , borderDist( 4 )
66  , scaleDist( 4 )
67  , maxScaleTicks( 11 )
68  , knobWidth( 0 )
69  , alignment( Qt::AlignCenter )
70  , markerSize( 8 )
71  , totalAngle( 270.0 )
72  , mouseOffset( 0.0 )
73  {
74  }
75 
78 
79  int borderWidth;
80  int borderDist;
81  int scaleDist;
82  int maxScaleTicks;
83  int knobWidth;
84  Qt::Alignment alignment;
85  int markerSize;
86 
87  double totalAngle;
88 
89  double mouseOffset;
90 };
91 
103 QwtKnob::QwtKnob( QWidget* parent )
104  : QwtAbstractSlider( parent )
105 {
106  m_data = new PrivateData;
107 
109 
110  setTotalAngle( 270.0 );
111 
112  setScale( 0.0, 10.0 );
113  setValue( 0.0 );
114 
115  setSizePolicy( QSizePolicy::MinimumExpanding,
116  QSizePolicy::MinimumExpanding );
117 }
118 
121 {
122  delete m_data;
123 }
124 
132 {
133  if ( m_data->knobStyle != knobStyle )
134  {
135  m_data->knobStyle = knobStyle;
136  update();
137  }
138 }
139 
145 {
146  return m_data->knobStyle;
147 }
148 
156 {
157  if ( m_data->markerStyle != markerStyle )
158  {
159  m_data->markerStyle = markerStyle;
160  update();
161  }
162 }
163 
169 {
170  return m_data->markerStyle;
171 }
172 
185 void QwtKnob::setTotalAngle ( double angle )
186 {
187  angle = qBound( 10.0, angle, 360.0 );
188 
189  if ( angle != m_data->totalAngle )
190  {
191  m_data->totalAngle = angle;
192 
193  scaleDraw()->setAngleRange( -0.5 * m_data->totalAngle,
194  0.5 * m_data->totalAngle );
195 
196  updateGeometry();
197  update();
198  }
199 }
200 
205 double QwtKnob::totalAngle() const
206 {
207  return m_data->totalAngle;
208 }
209 
219 void QwtKnob::setNumTurns( int numTurns )
220 {
221  numTurns = qMax( numTurns, 1 );
222 
223  if ( numTurns == 1 && m_data->totalAngle <= 360.0 )
224  return;
225 
226  const double angle = numTurns * 360.0;
227  if ( angle != m_data->totalAngle )
228  {
229  m_data->totalAngle = angle;
230 
231  scaleDraw()->setAngleRange( -0.5 * m_data->totalAngle,
232  0.5 * m_data->totalAngle );
233 
234  updateGeometry();
235  update();
236  }
237 }
238 
245 int QwtKnob::numTurns() const
246 {
247  return qwtCeil( m_data->totalAngle / 360.0 );
248 }
249 
260 {
262  setTotalAngle( m_data->totalAngle );
263 
264  updateGeometry();
265  update();
266 }
267 
273 {
274  return static_cast< const QwtRoundScaleDraw* >( abstractScaleDraw() );
275 }
276 
282 {
283  return static_cast< QwtRoundScaleDraw* >( abstractScaleDraw() );
284 }
285 
292 QRect QwtKnob::knobRect() const
293 {
294  const QRect cr = contentsRect();
295 
296  const int extent = qwtCeil( scaleDraw()->extent( font() ) );
297  const int d = extent + m_data->scaleDist;
298 
299  int w = m_data->knobWidth;
300  if ( w <= 0 )
301  {
302  const int dim = qMin( cr.width(), cr.height() );
303 
304  w = dim - 2 * ( d );
305  w = qMax( 0, w );
306  }
307 
308  QRect r( 0, 0, w, w );
309 
310  if ( m_data->alignment & Qt::AlignLeft )
311  {
312  r.moveLeft( cr.left() + d );
313  }
314  else if ( m_data->alignment & Qt::AlignRight )
315  {
316  r.moveRight( cr.right() - d );
317  }
318  else
319  {
320  r.moveCenter( QPoint( cr.center().x(), r.center().y() ) );
321  }
322 
323  if ( m_data->alignment & Qt::AlignTop )
324  {
325  r.moveTop( cr.top() + d );
326  }
327  else if ( m_data->alignment & Qt::AlignBottom )
328  {
329  r.moveBottom( cr.bottom() - d );
330  }
331  else
332  {
333  r.moveCenter( QPoint( r.center().x(), cr.center().y() ) );
334  }
335 
336  return r;
337 }
338 
347 bool QwtKnob::isScrollPosition( const QPoint& pos ) const
348 {
349  const QRect kr = knobRect();
350 
351  const QRegion region( kr, QRegion::Ellipse );
352  if ( region.contains( pos ) && ( pos != kr.center() ) )
353  {
354  const double angle = QLineF( kr.center(), pos ).angle();
355  const double valueAngle = qwtToDegrees( scaleMap().transform( value() ) );
356 
357  m_data->mouseOffset = qwtNormalizeDegrees( angle - valueAngle );
358 
359  return true;
360  }
361 
362  return false;
363 }
364 
373 double QwtKnob::scrolledTo( const QPoint& pos ) const
374 {
375  double angle = QLineF( rect().center(), pos ).angle();
376  angle = qwtNormalizeDegrees( angle - m_data->mouseOffset );
377 
378  if ( scaleMap().pDist() > 360.0 )
379  {
380  angle = qwtToDegrees( angle );
381 
382  const double v = scaleMap().transform( value() );
383 
384  int numTurns = qwtFloor( ( v - scaleMap().p1() ) / 360.0 );
385 
386  double valueAngle = qwtNormalizeDegrees( v );
387  if ( qAbs( valueAngle - angle ) > 180.0 )
388  {
389  numTurns += ( angle > valueAngle ) ? -1 : 1;
390  }
391 
392  angle += scaleMap().p1() + numTurns * 360.0;
393 
394  if ( !wrapping() )
395  {
396  const double boundedAngle =
397  qBound( scaleMap().p1(), angle, scaleMap().p2() );
398 
399  m_data->mouseOffset += ( boundedAngle - angle );
400  angle = boundedAngle;
401  }
402  }
403  else
404  {
405  angle = qwtToScaleAngle( angle );
406 
407  double boundedAngle = qBound( scaleMap().p1(), angle, scaleMap().p2() );
408 
409  if ( !wrapping() )
410  {
411  const double currentAngle = scaleMap().transform( value() );
412 
413  if ( ( currentAngle > 90.0 ) && ( boundedAngle < -90.0 ) )
414  boundedAngle = scaleMap().p2();
415  else if ( ( currentAngle < -90.0 ) && ( boundedAngle > 90.0 ) )
416  boundedAngle = scaleMap().p1();
417 
418  m_data->mouseOffset += ( boundedAngle - angle );
419  }
420 
421  angle = boundedAngle;
422  }
423 
424  return scaleMap().invTransform( angle );
425 }
426 
431 void QwtKnob::changeEvent( QEvent* event )
432 {
433  switch( event->type() )
434  {
435  case QEvent::StyleChange:
436  case QEvent::FontChange:
437  {
438  updateGeometry();
439  update();
440  break;
441  }
442  default:
443  break;
444  }
445 }
446 
451 void QwtKnob::paintEvent( QPaintEvent* event )
452 {
453  const QRectF knobRect = this->knobRect();
454 
455  QPainter painter( this );
456  painter.setClipRegion( event->region() );
457 
458  QStyleOption opt;
459  opt.initFrom(this);
460  style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);
461 
462  painter.setRenderHint( QPainter::Antialiasing, true );
463 
464  if ( !knobRect.contains( event->region().boundingRect() ) )
465  {
466  scaleDraw()->setRadius( 0.5 * knobRect.width() + m_data->scaleDist );
467  scaleDraw()->moveCenter( knobRect.center() );
468 
469  scaleDraw()->draw( &painter, palette() );
470  }
471 
472  drawKnob( &painter, knobRect );
473 
474  drawMarker( &painter, knobRect,
475  qwtNormalizeDegrees( scaleMap().transform( value() ) ) );
476 
477  painter.setRenderHint( QPainter::Antialiasing, false );
478 
479  if ( hasFocus() )
480  drawFocusIndicator( &painter );
481 }
482 
489 void QwtKnob::drawKnob( QPainter* painter, const QRectF& knobRect ) const
490 {
491  double dim = qMin( knobRect.width(), knobRect.height() );
492  dim -= m_data->borderWidth * 0.5;
493 
494  QRectF aRect( 0, 0, dim, dim );
495  aRect.moveCenter( knobRect.center() );
496 
497  QPen pen( Qt::NoPen );
498  if ( m_data->borderWidth > 0 )
499  {
500  QColor c1 = palette().color( QPalette::Light );
501  QColor c2 = palette().color( QPalette::Dark );
502 
503  QLinearGradient gradient( aRect.topLeft(), aRect.bottomRight() );
504  gradient.setColorAt( 0.0, c1 );
505  gradient.setColorAt( 0.3, c1 );
506  gradient.setColorAt( 0.7, c2 );
507  gradient.setColorAt( 1.0, c2 );
508 
509  pen = QPen( gradient, m_data->borderWidth );
510  }
511 
512  QBrush brush;
513  switch( m_data->knobStyle )
514  {
515  case QwtKnob::Raised:
516  {
517  double off = 0.3 * knobRect.width();
518  QRadialGradient gradient( knobRect.center(),
519  knobRect.width(), knobRect.topLeft() + QPointF( off, off ) );
520 
521  gradient.setColorAt( 0.0, palette().color( QPalette::Midlight ) );
522  gradient.setColorAt( 1.0, palette().color( QPalette::Button ) );
523 
524  brush = QBrush( gradient );
525 
526  break;
527  }
528  case QwtKnob::Styled:
529  {
530  QRadialGradient gradient(knobRect.center().x() - knobRect.width() / 3,
531  knobRect.center().y() - knobRect.height() / 2,
532  knobRect.width() * 1.3,
533  knobRect.center().x(),
534  knobRect.center().y() - knobRect.height() / 2);
535 
536  const QColor c = palette().color( QPalette::Button );
537  gradient.setColorAt(0, c.lighter(110) );
538  gradient.setColorAt( 0.5, c);
539  gradient.setColorAt( 0.501, c.darker(102) );
540  gradient.setColorAt(1, c.darker(115) );
541 
542  brush = QBrush( gradient );
543 
544  break;
545  }
546  case QwtKnob::Sunken:
547  {
548  QLinearGradient gradient(
549  knobRect.topLeft(), knobRect.bottomRight() );
550  gradient.setColorAt( 0.0, palette().color( QPalette::Mid ) );
551  gradient.setColorAt( 0.5, palette().color( QPalette::Button ) );
552  gradient.setColorAt( 1.0, palette().color( QPalette::Midlight ) );
553  brush = QBrush( gradient );
554 
555  break;
556  }
557  case QwtKnob::Flat:
558  default:
559  brush = palette().brush( QPalette::Button );
560  }
561 
562  painter->setPen( pen );
563  painter->setBrush( brush );
564  painter->drawEllipse( aRect );
565 }
566 
567 
576 void QwtKnob::drawMarker( QPainter* painter,
577  const QRectF& rect, double angle ) const
578 {
579  if ( m_data->markerStyle == NoMarker || !isValid() )
580  return;
581 
582  const double radians = qwtRadians( angle );
583  const double sinA = -qFastSin( radians );
584  const double cosA = qFastCos( radians );
585 
586  const double xm = rect.center().x();
587  const double ym = rect.center().y();
588  const double margin = 4.0;
589 
590  double radius = 0.5 * ( rect.width() - m_data->borderWidth ) - margin;
591  if ( radius < 1.0 )
592  radius = 1.0;
593 
594  double markerSize = m_data->markerSize;
595  if ( markerSize <= 0 )
596  markerSize = qRound( 0.4 * radius );
597 
598  switch ( m_data->markerStyle )
599  {
600  case Notch:
601  case Nub:
602  {
603  const double dotWidth = qwtMinF( markerSize, radius );
604 
605  const double dotCenterDist = radius - 0.5 * dotWidth;
606  if ( dotCenterDist > 0.0 )
607  {
608  const QPointF center( xm - sinA * dotCenterDist,
609  ym - cosA * dotCenterDist );
610 
611  QRectF ellipse( 0.0, 0.0, dotWidth, dotWidth );
612  ellipse.moveCenter( center );
613 
614  QColor c1 = palette().color( QPalette::Light );
615  QColor c2 = palette().color( QPalette::Mid );
616 
617  if ( m_data->markerStyle == Notch )
618  qSwap( c1, c2 );
619 
620  QLinearGradient gradient(
621  ellipse.topLeft(), ellipse.bottomRight() );
622  gradient.setColorAt( 0.0, c1 );
623  gradient.setColorAt( 1.0, c2 );
624 
625  painter->setPen( Qt::NoPen );
626  painter->setBrush( gradient );
627 
628  painter->drawEllipse( ellipse );
629  }
630  break;
631  }
632  case Dot:
633  {
634  const double dotWidth = qwtMinF( markerSize, radius);
635 
636  const double dotCenterDist = radius - 0.5 * dotWidth;
637  if ( dotCenterDist > 0.0 )
638  {
639  const QPointF center( xm - sinA * dotCenterDist,
640  ym - cosA * dotCenterDist );
641 
642  QRectF ellipse( 0.0, 0.0, dotWidth, dotWidth );
643  ellipse.moveCenter( center );
644 
645  painter->setPen( Qt::NoPen );
646  painter->setBrush( palette().color( QPalette::ButtonText ) );
647  painter->drawEllipse( ellipse );
648  }
649 
650  break;
651  }
652  case Tick:
653  {
654  const double rb = qwtMaxF( radius - markerSize, 1.0 );
655  const double re = radius;
656 
657  const QLineF line( xm - sinA * rb, ym - cosA * rb,
658  xm - sinA * re, ym - cosA * re );
659 
660  QPen pen( palette().color( QPalette::ButtonText ), 0 );
661  pen.setCapStyle( Qt::FlatCap );
662  painter->setPen( pen );
663  painter->drawLine ( line );
664 
665  break;
666  }
667  case Triangle:
668  {
669  const double rb = qwtMaxF( radius - markerSize, 1.0 );
670  const double re = radius;
671 
672  painter->translate( rect.center() );
673  painter->rotate( angle - 90.0 );
674 
675  QPolygonF polygon;
676  polygon += QPointF( re, 0.0 );
677  polygon += QPointF( rb, 0.5 * ( re - rb ) );
678  polygon += QPointF( rb, -0.5 * ( re - rb ) );
679 
680  painter->setPen( Qt::NoPen );
681  painter->setBrush( palette().color( QPalette::ButtonText ) );
682  painter->drawPolygon( polygon );
683 
684  painter->resetTransform();
685 
686  break;
687  }
688  default:
689  break;
690  }
691 }
692 
697 void QwtKnob::drawFocusIndicator( QPainter* painter ) const
698 {
699  const QRect cr = contentsRect();
700 
701  int w = m_data->knobWidth;
702  if ( w <= 0 )
703  {
704  w = qMin( cr.width(), cr.height() );
705  }
706  else
707  {
708  const int extent = qCeil( scaleDraw()->extent( font() ) );
709  w += 2 * ( extent + m_data->scaleDist );
710  }
711 
712  QRect focusRect( 0, 0, w, w );
713  focusRect.moveCenter( cr.center() );
714 
715  QwtPainter::drawFocusRect( painter, this, focusRect );
716 }
717 
730 void QwtKnob::setAlignment( Qt::Alignment alignment )
731 {
732  if ( m_data->alignment != alignment )
733  {
734  m_data->alignment = alignment;
735  update();
736  }
737 }
738 
743 Qt::Alignment QwtKnob::alignment() const
744 {
745  return m_data->alignment;
746 }
747 
759 void QwtKnob::setKnobWidth( int width )
760 {
761  width = qMax( width, 0 );
762 
763  if ( width != m_data->knobWidth )
764  {
765  QSizePolicy::Policy policy;
766  if ( width > 0 )
767  policy = QSizePolicy::Minimum;
768  else
769  policy = QSizePolicy::MinimumExpanding;
770 
771  setSizePolicy( policy, policy );
772 
773  m_data->knobWidth = width;
774 
775  updateGeometry();
776  update();
777  }
778 }
779 
782 {
783  return m_data->knobWidth;
784 }
785 
790 void QwtKnob::setBorderWidth( int borderWidth )
791 {
792  m_data->borderWidth = qMax( borderWidth, 0 );
793 
794  updateGeometry();
795  update();
796 }
797 
800 {
801  return m_data->borderWidth;
802 }
803 
812 void QwtKnob::setMarkerSize( int size )
813 {
814  if ( m_data->markerSize != size )
815  {
816  m_data->markerSize = size;
817  update();
818  }
819 }
820 
826 {
827  return m_data->markerSize;
828 }
829 
833 QSize QwtKnob::sizeHint() const
834 {
835  const QSize hint = qwtKnobSizeHint( this, 50 );
836  return qwtExpandedToGlobalStrut( hint );
837 }
838 
844 {
845  return qwtKnobSizeHint( this, 20 );
846 }
847 
848 #if QWT_MOC_INCLUDE
849 #include "moc_qwt_knob.cpp"
850 #endif
virtual void draw(QPainter *, const QPalette &) const
Draw the scale.
const QwtScaleMap & scaleMap() const
const QwtAbstractScaleDraw * abstractScaleDraw() const
int transform(double) const
void setAbstractScaleDraw(QwtAbstractScaleDraw *)
Set a scale draw.
void setScale(double lowerBound, double upperBound)
Specify a scale.
An abstract base class for slider widgets with a scale.
void setValue(double value)
double value() const
Returns the current value.
The Knob Widget.
Definition: qwt_knob.h:43
virtual double scrolledTo(const QPoint &) const override
Determine the value for a new position of the mouse.
Definition: qwt_knob.cpp:373
virtual ~QwtKnob()
Destructor.
Definition: qwt_knob.cpp:120
void setKnobWidth(int)
Change the knob's width.
Definition: qwt_knob.cpp:759
void setKnobStyle(KnobStyle)
Set the knob type.
Definition: qwt_knob.cpp:131
int knobWidth() const
Return the width of the knob.
Definition: qwt_knob.cpp:781
Qt::Alignment alignment() const
Definition: qwt_knob.cpp:743
virtual bool isScrollPosition(const QPoint &) const override
Determine what to do when the user presses a mouse button.
Definition: qwt_knob.cpp:347
MarkerStyle
Marker type.
Definition: qwt_knob.h:96
@ Dot
Paint a circle in QPalette::ButtonText color.
Definition: qwt_knob.h:107
@ Tick
Paint a single tick in QPalette::ButtonText color.
Definition: qwt_knob.h:101
@ Triangle
Paint a triangle in QPalette::ButtonText color.
Definition: qwt_knob.h:104
@ Notch
Definition: qwt_knob.h:119
@ NoMarker
Don't paint any marker.
Definition: qwt_knob.h:98
const QwtRoundScaleDraw * scaleDraw() const
Definition: qwt_knob.cpp:272
QwtKnob(QWidget *parent=NULL)
Constructor.
Definition: qwt_knob.cpp:103
void setTotalAngle(double angle)
Set the total angle by which the knob can be turned.
Definition: qwt_knob.cpp:185
void setMarkerSize(int)
Set the size of the marker.
Definition: qwt_knob.cpp:812
virtual void drawFocusIndicator(QPainter *) const
Definition: qwt_knob.cpp:697
void setNumTurns(int)
Set the number of turns.
Definition: qwt_knob.cpp:219
MarkerStyle markerStyle() const
Definition: qwt_knob.cpp:168
QRect knobRect() const
Definition: qwt_knob.cpp:292
int markerSize() const
Definition: qwt_knob.cpp:825
virtual void changeEvent(QEvent *) override
Definition: qwt_knob.cpp:431
void setMarkerStyle(MarkerStyle)
Set the marker type of the knob.
Definition: qwt_knob.cpp:155
KnobStyle knobStyle() const
Definition: qwt_knob.cpp:144
double totalAngle() const
Definition: qwt_knob.cpp:205
void setAlignment(Qt::Alignment)
Set the alignment of the knob.
Definition: qwt_knob.cpp:730
virtual void paintEvent(QPaintEvent *) override
Definition: qwt_knob.cpp:451
virtual void drawMarker(QPainter *, const QRectF &, double angle) const
Draw the marker at the knob's front.
Definition: qwt_knob.cpp:576
virtual void drawKnob(QPainter *, const QRectF &) const
Draw the knob.
Definition: qwt_knob.cpp:489
KnobStyle
Style of the knob surface.
Definition: qwt_knob.h:67
@ Flat
Fill the knob with a brush from QPalette::Button.
Definition: qwt_knob.h:69
@ Sunken
Definition: qwt_knob.h:78
@ Raised
Build a gradient from QPalette::Midlight and QPalette::Button.
Definition: qwt_knob.h:72
@ Styled
Definition: qwt_knob.h:84
int numTurns() const
Definition: qwt_knob.cpp:245
int borderWidth() const
Return the border width.
Definition: qwt_knob.cpp:799
virtual QSize minimumSizeHint() const override
Definition: qwt_knob.cpp:843
void setBorderWidth(int)
Set the knob's border width.
Definition: qwt_knob.cpp:790
virtual QSize sizeHint() const override
Definition: qwt_knob.cpp:833
void setScaleDraw(QwtRoundScaleDraw *)
Definition: qwt_knob.cpp:259
static void drawFocusRect(QPainter *, const QWidget *)
Draw a focus rectangle on a widget using its style.
A class for drawing round scales.
virtual double extent(const QFont &) const override
void setRadius(double radius)
void setAngleRange(double angle1, double angle2)
Adjust the baseline circle segment for round scales.
void moveCenter(double x, double y)
Move the center of the scale draw, leaving the radius unchanged.
double p1() const
Definition: qwt_scale_map.h:99
double transform(double s) const
double invTransform(double p) const
double p2() const