source: ntrip/trunk/BNC/qwt/qwt_dial.cpp@ 8820

Last change on this file since 8820 was 8127, checked in by stoecker, 8 years ago

update qwt and qwtpolar, many QT5 fixes (unfinished)

File size: 19.0 KB
Line 
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 "qwt_dial.h"
11#include "qwt_dial_needle.h"
12#include "qwt_math.h"
13#include "qwt_scale_engine.h"
14#include "qwt_scale_map.h"
15#include "qwt_round_scale_draw.h"
16#include "qwt_painter.h"
17#include <qpainter.h>
18#include <qpalette.h>
19#include <qpixmap.h>
20#include <qevent.h>
21#include <qalgorithms.h>
22#include <qmath.h>
23#include <qstyle.h>
24#include <qstyleoption.h>
25#include <qapplication.h>
26
27static inline double qwtAngleDist( double a1, double a2 )
28{
29 double dist = qAbs( a2 - a1 );
30 if ( dist > 360.0 )
31 dist -= 360.0;
32
33 return dist;
34}
35
36static inline bool qwtIsOnArc( double angle, double min, double max )
37{
38 if ( min < max )
39 {
40 return ( angle >= min ) && ( angle <= max );
41 }
42 else
43 {
44 return ( angle >= min ) || ( angle <= max );
45 }
46}
47
48static inline double qwtBoundedAngle( double min, double angle, double max )
49{
50 double from = qwtNormalizeDegrees( min );
51 double to = qwtNormalizeDegrees( max );
52
53 double a;
54
55 if ( qwtIsOnArc( angle, from, to ) )
56 {
57 a = angle;
58 if ( a < min )
59 a += 360.0;
60 }
61 else
62 {
63 if ( qwtAngleDist( angle, from ) <
64 qwtAngleDist( angle, to ) )
65 {
66 a = min;
67 }
68 else
69 {
70 a = max;
71 }
72 }
73
74 return a;
75}
76
77class QwtDial::PrivateData
78{
79public:
80 PrivateData():
81 frameShadow( Sunken ),
82 lineWidth( 0 ),
83 mode( RotateNeedle ),
84 origin( 90.0 ),
85 minScaleArc( 0.0 ),
86 maxScaleArc( 0.0 ),
87 needle( NULL ),
88 arcOffset( 0.0 ),
89 mouseOffset( 0.0 )
90 {
91 }
92
93 ~PrivateData()
94 {
95 delete needle;
96 }
97 Shadow frameShadow;
98 int lineWidth;
99
100 QwtDial::Mode mode;
101
102 double origin;
103 double minScaleArc;
104 double maxScaleArc;
105
106 QwtDialNeedle *needle;
107
108 double arcOffset;
109 double mouseOffset;
110
111 QPixmap pixmapCache;
112};
113
114/*!
115 \brief Constructor
116 \param parent Parent widget
117
118 Create a dial widget with no needle. The scale is initialized
119 to [ 0.0, 360.0 ] and 360 steps ( QwtAbstractSlider::setTotalSteps() ).
120 The origin of the scale is at 90°,
121
122 The value is set to 0.0.
123
124 The default mode is QwtDial::RotateNeedle.
125*/
126QwtDial::QwtDial( QWidget* parent ):
127 QwtAbstractSlider( parent )
128{
129 d_data = new PrivateData;
130
131 setFocusPolicy( Qt::TabFocus );
132
133 QPalette p = palette();
134 for ( int i = 0; i < QPalette::NColorGroups; i++ )
135 {
136 const QPalette::ColorGroup colorGroup =
137 static_cast<QPalette::ColorGroup>( i );
138
139 // Base: background color of the circle inside the frame.
140 // WindowText: background color of the circle inside the scale
141
142 p.setColor( colorGroup, QPalette::WindowText,
143 p.color( colorGroup, QPalette::Base ) );
144 }
145 setPalette( p );
146
147 QwtRoundScaleDraw* scaleDraw = new QwtRoundScaleDraw();
148 scaleDraw->setRadius( 0 );
149
150 setScaleDraw( scaleDraw );
151
152 setScaleArc( 0.0, 360.0 ); // scale as a full circle
153
154 setScaleMaxMajor( 10 );
155 setScaleMaxMinor( 5 );
156
157 setValue( 0.0 );
158}
159
160//! Destructor
161QwtDial::~QwtDial()
162{
163 delete d_data;
164}
165
166/*!
167 Sets the frame shadow value from the frame style.
168
169 \param shadow Frame shadow
170 \sa setLineWidth(), QFrame::setFrameShadow()
171*/
172void QwtDial::setFrameShadow( Shadow shadow )
173{
174 if ( shadow != d_data->frameShadow )
175 {
176 invalidateCache();
177
178 d_data->frameShadow = shadow;
179 if ( lineWidth() > 0 )
180 update();
181 }
182}
183
184/*!
185 \return Frame shadow
186 /sa setFrameShadow(), lineWidth(), QFrame::frameShadow()
187*/
188QwtDial::Shadow QwtDial::frameShadow() const
189{
190 return d_data->frameShadow;
191}
192
193/*!
194 Sets the line width of the frame
195
196 \param lineWidth Line width
197 \sa setFrameShadow()
198*/
199void QwtDial::setLineWidth( int lineWidth )
200{
201 if ( lineWidth < 0 )
202 lineWidth = 0;
203
204 if ( d_data->lineWidth != lineWidth )
205 {
206 invalidateCache();
207
208 d_data->lineWidth = lineWidth;
209 update();
210 }
211}
212
213/*!
214 \return Line width of the frame
215 \sa setLineWidth(), frameShadow(), lineWidth()
216*/
217int QwtDial::lineWidth() const
218{
219 return d_data->lineWidth;
220}
221
222/*!
223 \return bounding rectangle of the circle inside the frame
224 \sa setLineWidth(), scaleInnerRect(), boundingRect()
225*/
226QRect QwtDial::innerRect() const
227{
228 const int lw = lineWidth();
229 return boundingRect().adjusted( lw, lw, -lw, -lw );
230}
231
232/*!
233 \return bounding rectangle of the dial including the frame
234 \sa setLineWidth(), scaleInnerRect(), innerRect()
235*/
236QRect QwtDial::boundingRect() const
237{
238 const QRect cr = contentsRect();
239
240 const double dim = qMin( cr.width(), cr.height() );
241
242 QRect inner( 0, 0, dim, dim );
243 inner.moveCenter( cr.center() );
244
245 return inner;
246}
247
248/*!
249 \return rectangle inside the scale
250 \sa setLineWidth(), boundingRect(), innerRect()
251*/
252QRect QwtDial::scaleInnerRect() const
253{
254 QRect rect = innerRect();
255
256 const QwtAbstractScaleDraw *sd = scaleDraw();
257 if ( sd )
258 {
259 int scaleDist = qCeil( sd->extent( font() ) );
260 scaleDist++; // margin
261
262 rect.adjust( scaleDist, scaleDist, -scaleDist, -scaleDist );
263 }
264
265 return rect;
266}
267
268/*!
269 \brief Change the mode of the dial.
270 \param mode New mode
271
272 In case of QwtDial::RotateNeedle the needle is rotating, in case of
273 QwtDial::RotateScale, the needle points to origin()
274 and the scale is rotating.
275
276 The default mode is QwtDial::RotateNeedle.
277
278 \sa mode(), setValue(), setOrigin()
279*/
280void QwtDial::setMode( Mode mode )
281{
282 if ( mode != d_data->mode )
283 {
284 invalidateCache();
285
286 d_data->mode = mode;
287 sliderChange();
288 }
289}
290
291/*!
292 \return Mode of the dial.
293 \sa setMode(), origin(), setScaleArc(), value()
294*/
295QwtDial::Mode QwtDial::mode() const
296{
297 return d_data->mode;
298}
299
300/*!
301 Invalidate the internal caches used to speed up repainting
302 */
303void QwtDial::invalidateCache()
304{
305 d_data->pixmapCache = QPixmap();
306}
307
308/*!
309 Paint the dial
310 \param event Paint event
311*/
312void QwtDial::paintEvent( QPaintEvent *event )
313{
314 QPainter painter( this );
315 painter.setClipRegion( event->region() );
316
317 QStyleOption opt;
318 opt.init(this);
319 style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);
320
321 if ( d_data->mode == QwtDial::RotateScale )
322 {
323 painter.save();
324 painter.setRenderHint( QPainter::Antialiasing, true );
325
326 drawContents( &painter );
327
328 painter.restore();
329 }
330
331 const QRect r = contentsRect();
332 if ( r.size() != d_data->pixmapCache.size() )
333 {
334 d_data->pixmapCache = QwtPainter::backingStore( this, r.size() );
335 d_data->pixmapCache.fill( Qt::transparent );
336
337 QPainter p( &d_data->pixmapCache );
338 p.setRenderHint( QPainter::Antialiasing, true );
339 p.translate( -r.topLeft() );
340
341 if ( d_data->mode != QwtDial::RotateScale )
342 drawContents( &p );
343
344 if ( lineWidth() > 0 )
345 drawFrame( &p );
346
347 if ( d_data->mode != QwtDial::RotateNeedle )
348 drawNeedle( &p );
349 }
350
351 painter.drawPixmap( r.topLeft(), d_data->pixmapCache );
352
353 if ( d_data->mode == QwtDial::RotateNeedle )
354 drawNeedle( &painter );
355
356 if ( hasFocus() )
357 drawFocusIndicator( &painter );
358}
359
360/*!
361 Draw the focus indicator
362 \param painter Painter
363*/
364void QwtDial::drawFocusIndicator( QPainter *painter ) const
365{
366 QwtPainter::drawFocusRect( painter, this, boundingRect() );
367}
368
369/*!
370 Draw the frame around the dial
371
372 \param painter Painter
373 \sa lineWidth(), frameShadow()
374*/
375void QwtDial::drawFrame( QPainter *painter )
376{
377 QwtPainter::drawRoundFrame( painter, boundingRect(),
378 palette(), lineWidth(), d_data->frameShadow );
379}
380
381/*!
382 \brief Draw the contents inside the frame
383
384 QPalette::Window is the background color outside of the frame.
385 QPalette::Base is the background color inside the frame.
386 QPalette::WindowText is the background color inside the scale.
387
388 \param painter Painter
389
390 \sa boundingRect(), innerRect(),
391 scaleInnerRect(), QWidget::setPalette()
392*/
393void QwtDial::drawContents( QPainter *painter ) const
394{
395 if ( testAttribute( Qt::WA_NoSystemBackground ) ||
396 palette().brush( QPalette::Base ) !=
397 palette().brush( QPalette::Window ) )
398 {
399 const QRectF br = boundingRect();
400
401 painter->save();
402 painter->setPen( Qt::NoPen );
403 painter->setBrush( palette().brush( QPalette::Base ) );
404 painter->drawEllipse( br );
405 painter->restore();
406 }
407
408 const QRectF insideScaleRect = scaleInnerRect();
409 if ( palette().brush( QPalette::WindowText ) !=
410 palette().brush( QPalette::Base ) )
411 {
412 painter->save();
413 painter->setPen( Qt::NoPen );
414 painter->setBrush( palette().brush( QPalette::WindowText ) );
415 painter->drawEllipse( insideScaleRect );
416 painter->restore();
417 }
418
419 const QPointF center = insideScaleRect.center();
420 const double radius = 0.5 * insideScaleRect.width();
421
422 painter->save();
423 drawScale( painter, center, radius );
424 painter->restore();
425
426 painter->save();
427 drawScaleContents( painter, center, radius );
428 painter->restore();
429}
430
431/*!
432 Draw the needle
433
434 \param painter Painter
435 \param center Center of the dial
436 \param radius Length for the needle
437 \param direction Direction of the needle in degrees, counter clockwise
438 \param colorGroup ColorGroup
439*/
440void QwtDial::drawNeedle( QPainter *painter, const QPointF &center,
441 double radius, double direction, QPalette::ColorGroup colorGroup ) const
442{
443 if ( d_data->needle )
444 {
445 direction = 360.0 - direction; // counter clockwise
446 d_data->needle->draw( painter, center, radius, direction, colorGroup );
447 }
448}
449
450void QwtDial::drawNeedle( QPainter *painter ) const
451{
452 if ( !isValid() )
453 return;
454
455 QPalette::ColorGroup colorGroup;
456 if ( isEnabled() )
457 colorGroup = hasFocus() ? QPalette::Active : QPalette::Inactive;
458 else
459 colorGroup = QPalette::Disabled;
460
461 const QRectF sr = scaleInnerRect();
462
463 painter->save();
464 painter->setRenderHint( QPainter::Antialiasing, true );
465 drawNeedle( painter, sr.center(), 0.5 * sr.width(),
466 scaleMap().transform( value() ) + 270.0, colorGroup );
467 painter->restore();
468}
469
470/*!
471 Draw the scale
472
473 \param painter Painter
474 \param center Center of the dial
475 \param radius Radius of the scale
476*/
477void QwtDial::drawScale( QPainter *painter,
478 const QPointF &center, double radius ) const
479{
480 QwtRoundScaleDraw *sd = const_cast<QwtRoundScaleDraw *>( scaleDraw() );
481 if ( sd == NULL )
482 return;
483
484 sd->setRadius( radius );
485 sd->moveCenter( center );
486
487 QPalette pal = palette();
488
489 const QColor textColor = pal.color( QPalette::Text );
490 pal.setColor( QPalette::WindowText, textColor ); // ticks, backbone
491
492 painter->setFont( font() );
493 painter->setPen( QPen( textColor, sd->penWidth() ) );
494
495 painter->setBrush( Qt::red );
496 sd->draw( painter, pal );
497}
498
499/*!
500 Draw the contents inside the scale
501
502 Paints nothing.
503
504 \param painter Painter
505 \param center Center of the contents circle
506 \param radius Radius of the contents circle
507*/
508void QwtDial::drawScaleContents( QPainter *painter,
509 const QPointF &center, double radius ) const
510{
511 Q_UNUSED(painter);
512 Q_UNUSED(center);
513 Q_UNUSED(radius);
514}
515
516/*!
517 Set a needle for the dial
518
519 \param needle Needle
520
521 \warning The needle will be deleted, when a different needle is
522 set or in ~QwtDial()
523*/
524void QwtDial::setNeedle( QwtDialNeedle *needle )
525{
526 if ( needle != d_data->needle )
527 {
528 if ( d_data->needle )
529 delete d_data->needle;
530
531 d_data->needle = needle;
532 update();
533 }
534}
535
536/*!
537 \return needle
538 \sa setNeedle()
539*/
540const QwtDialNeedle *QwtDial::needle() const
541{
542 return d_data->needle;
543}
544
545/*!
546 \return needle
547 \sa setNeedle()
548*/
549QwtDialNeedle *QwtDial::needle()
550{
551 return d_data->needle;
552}
553
554//! \return the scale draw
555QwtRoundScaleDraw *QwtDial::scaleDraw()
556{
557 return static_cast<QwtRoundScaleDraw *>( abstractScaleDraw() );
558}
559
560//! \return the scale draw
561const QwtRoundScaleDraw *QwtDial::scaleDraw() const
562{
563 return static_cast<const QwtRoundScaleDraw *>( abstractScaleDraw() );
564}
565
566/*!
567 Set an individual scale draw
568
569 The motivation for setting a scale draw is often
570 to overload QwtRoundScaleDraw::label() to return
571 individual tick labels.
572
573 \param scaleDraw Scale draw
574 \warning The previous scale draw is deleted
575*/
576void QwtDial::setScaleDraw( QwtRoundScaleDraw *scaleDraw )
577{
578 setAbstractScaleDraw( scaleDraw );
579 sliderChange();
580}
581
582/*!
583 Change the arc of the scale
584
585 \param minArc Lower limit
586 \param maxArc Upper limit
587
588 \sa minScaleArc(), maxScaleArc()
589*/
590void QwtDial::setScaleArc( double minArc, double maxArc )
591{
592 if ( minArc != 360.0 && minArc != -360.0 )
593 minArc = ::fmod( minArc, 360.0 );
594 if ( maxArc != 360.0 && maxArc != -360.0 )
595 maxArc = ::fmod( maxArc, 360.0 );
596
597 double minScaleArc = qMin( minArc, maxArc );
598 double maxScaleArc = qMax( minArc, maxArc );
599
600 if ( maxScaleArc - minScaleArc > 360.0 )
601 maxScaleArc = minScaleArc + 360.0;
602
603 if ( ( minScaleArc != d_data->minScaleArc ) ||
604 ( maxScaleArc != d_data->maxScaleArc ) )
605 {
606 d_data->minScaleArc = minScaleArc;
607 d_data->maxScaleArc = maxScaleArc;
608
609 invalidateCache();
610 sliderChange();
611 }
612}
613
614/*!
615 Set the lower limit for the scale arc
616
617 \param min Lower limit of the scale arc
618 \sa setScaleArc(), setMaxScaleArc()
619 */
620void QwtDial::setMinScaleArc( double min )
621{
622 setScaleArc( min, d_data->maxScaleArc );
623}
624
625/*!
626 \return Lower limit of the scale arc
627 \sa setScaleArc()
628*/
629double QwtDial::minScaleArc() const
630{
631 return d_data->minScaleArc;
632}
633
634/*!
635 Set the upper limit for the scale arc
636
637 \param max Upper limit of the scale arc
638 \sa setScaleArc(), setMinScaleArc()
639 */
640void QwtDial::setMaxScaleArc( double max )
641{
642 setScaleArc( d_data->minScaleArc, max );
643}
644
645/*!
646 \return Upper limit of the scale arc
647 \sa setScaleArc()
648*/
649double QwtDial::maxScaleArc() const
650{
651 return d_data->maxScaleArc;
652}
653
654/*!
655 \brief Change the origin
656
657 The origin is the angle where scale and needle is relative to.
658
659 \param origin New origin
660 \sa origin()
661*/
662void QwtDial::setOrigin( double origin )
663{
664 invalidateCache();
665
666 d_data->origin = origin;
667 sliderChange();
668}
669
670/*!
671 The origin is the angle where scale and needle is relative to.
672
673 \return Origin of the dial
674 \sa setOrigin()
675*/
676double QwtDial::origin() const
677{
678 return d_data->origin;
679}
680
681/*!
682 \return Size hint
683 \sa minimumSizeHint()
684*/
685QSize QwtDial::sizeHint() const
686{
687 int sh = 0;
688 if ( scaleDraw() )
689 sh = qCeil( scaleDraw()->extent( font() ) );
690
691 const int d = 6 * sh + 2 * lineWidth();
692
693 QSize hint( d, d );
694 if ( !isReadOnly() )
695 hint = hint.expandedTo( QApplication::globalStrut() );
696
697 return hint;
698}
699
700/*!
701 \return Minimum size hint
702 \sa sizeHint()
703*/
704QSize QwtDial::minimumSizeHint() const
705{
706 int sh = 0;
707 if ( scaleDraw() )
708 sh = qCeil( scaleDraw()->extent( font() ) );
709
710 const int d = 3 * sh + 2 * lineWidth();
711
712 return QSize( d, d );
713}
714
715/*!
716 \brief Determine what to do when the user presses a mouse button.
717
718 \param pos Mouse position
719
720 \retval True, when the inner circle contains pos
721 \sa scrolledTo()
722*/
723bool QwtDial::isScrollPosition( const QPoint &pos ) const
724{
725 const QRegion region( innerRect(), QRegion::Ellipse );
726 if ( region.contains( pos ) && ( pos != innerRect().center() ) )
727 {
728 double angle = QLineF( rect().center(), pos ).angle();
729 if ( d_data->mode == QwtDial::RotateScale )
730 angle = 360.0 - angle;
731
732 double valueAngle =
733 qwtNormalizeDegrees( 90.0 - scaleMap().transform( value() ) );
734
735 d_data->mouseOffset = qwtNormalizeDegrees( angle - valueAngle );
736 d_data->arcOffset = scaleMap().p1();
737
738 return true;
739 }
740
741 return false;
742}
743
744/*!
745 \brief Determine the value for a new position of the
746 slider handle.
747
748 \param pos Mouse position
749
750 \return Value for the mouse position
751 \sa isScrollPosition()
752*/
753double QwtDial::scrolledTo( const QPoint &pos ) const
754{
755 double angle = QLineF( rect().center(), pos ).angle();
756 if ( d_data->mode == QwtDial::RotateScale )
757 {
758 angle += scaleMap().p1() - d_data->arcOffset;
759 angle = 360.0 - angle;
760 }
761
762 angle = qwtNormalizeDegrees( angle - d_data->mouseOffset );
763 angle = qwtNormalizeDegrees( 90.0 - angle );
764
765 if ( scaleMap().pDist() >= 360.0 )
766 {
767 if ( angle < scaleMap().p1() )
768 angle += 360.0;
769
770 if ( !wrapping() )
771 {
772 double boundedAngle = angle;
773
774 const double arc = angle - scaleMap().transform( value() );
775 if ( qAbs( arc ) > 180.0 )
776 {
777 boundedAngle = ( arc > 0 )
778 ? scaleMap().p1() : scaleMap().p2();
779 }
780
781 d_data->mouseOffset += ( boundedAngle - angle );
782
783 angle = boundedAngle;
784 }
785 }
786 else
787 {
788 const double boundedAngle =
789 qwtBoundedAngle( scaleMap().p1(), angle, scaleMap().p2() );
790
791 if ( !wrapping() )
792 d_data->mouseOffset += ( boundedAngle - angle );
793
794 angle = boundedAngle;
795 }
796
797 return scaleMap().invTransform( angle );
798}
799
800/*!
801 Change Event handler
802 \param event Change event
803
804 Invalidates internal paint caches if necessary
805*/
806void QwtDial::changeEvent( QEvent *event )
807{
808 switch( event->type() )
809 {
810 case QEvent::EnabledChange:
811 case QEvent::FontChange:
812 case QEvent::StyleChange:
813 case QEvent::PaletteChange:
814 case QEvent::LanguageChange:
815 case QEvent::LocaleChange:
816 {
817 invalidateCache();
818 break;
819 }
820 default:
821 break;
822 }
823
824 QwtAbstractSlider::changeEvent( event );
825}
826
827/*!
828 Wheel Event handler
829 \param event Wheel event
830*/
831void QwtDial::wheelEvent( QWheelEvent *event )
832{
833 const QRegion region( innerRect(), QRegion::Ellipse );
834 if ( region.contains( event->pos() ) )
835 QwtAbstractSlider::wheelEvent( event );
836}
837
838void QwtDial::setAngleRange( double angle, double span )
839{
840 QwtRoundScaleDraw *sd = const_cast<QwtRoundScaleDraw *>( scaleDraw() );
841 if ( sd )
842 {
843 angle = qwtNormalizeDegrees( angle - 270.0 );
844 sd->setAngleRange( angle, angle + span );
845 }
846}
847
848/*!
849 Invalidate the internal caches and call
850 QwtAbstractSlider::scaleChange()
851 */
852void QwtDial::scaleChange()
853{
854 invalidateCache();
855 QwtAbstractSlider::scaleChange();
856}
857
858void QwtDial::sliderChange()
859{
860 setAngleRange( d_data->origin + d_data->minScaleArc,
861 d_data->maxScaleArc - d_data->minScaleArc );
862
863 if ( mode() == RotateScale )
864 {
865 const double arc = scaleMap().transform( value() ) - scaleMap().p1();
866 setAngleRange( d_data->origin - arc,
867 d_data->maxScaleArc - d_data->minScaleArc );
868 }
869
870 QwtAbstractSlider::sliderChange();
871}
Note: See TracBrowser for help on using the repository browser.