qwt_wheel.cpp 29.4 KB
Newer Older
pixhawk's avatar
pixhawk committed
1
2
3
4
5
6
7
8
9
/* -*- 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
 *****************************************************************************/

Bryant's avatar
Bryant committed
10
11
12
#include "qwt_wheel.h"
#include "qwt_math.h"
#include "qwt_painter.h"
pixhawk's avatar
pixhawk committed
13
14
15
16
#include <qevent.h>
#include <qdrawutil.h>
#include <qpainter.h>
#include <qstyle.h>
Bryant's avatar
Bryant committed
17
18
19
20
21
22
23
24
25
#include <qstyleoption.h>
#include <qapplication.h>
#include <qdatetime.h>

#if QT_VERSION < 0x040601
#define qFabs(x) ::fabs(x)
#define qFastSin(x) ::sin(x)
#define qExp(x) ::exp(x)
#endif
pixhawk's avatar
pixhawk committed
26
27
28
29

class QwtWheel::PrivateData
{
public:
Bryant's avatar
Bryant committed
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
    PrivateData():
        orientation( Qt::Horizontal ),
        viewAngle( 175.0 ),
        totalAngle( 360.0 ),
        tickCount( 10 ),
        wheelBorderWidth( 2 ),
        borderWidth( 2 ),
        wheelWidth( 20 ),
        isScrolling( false ),
        mouseOffset( 0.0 ),
        tracking( true ),
        pendingValueChanged( false ),
        updateInterval( 50 ),
        mass( 0.0 ),
        timerId( 0 ),
        speed( 0.0 ),
        mouseValue( 0.0 ),
        flyingValue( 0.0 ),
        minimum( 0.0 ),
        maximum( 100.0 ),
        singleStep( 1.0 ),
        pageStepCount( 1 ),
        stepAlignment( true ),
        value( 0.0 ),
        inverted( false ),
        wrapping( false )
    {
pixhawk's avatar
pixhawk committed
57
58
    };

Bryant's avatar
Bryant committed
59
    Qt::Orientation orientation;
pixhawk's avatar
pixhawk committed
60
61
    double viewAngle;
    double totalAngle;
Bryant's avatar
Bryant committed
62
63
    int tickCount;
    int wheelBorderWidth;
pixhawk's avatar
pixhawk committed
64
65
    int borderWidth;
    int wheelWidth;
Bryant's avatar
Bryant committed
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93

    bool isScrolling;
    double mouseOffset;

    bool tracking;
    bool pendingValueChanged; // when not tracking

    int updateInterval;
    double mass;

    // for the flying wheel effect
    int timerId;
    QTime time;
    double speed;
    double mouseValue;
    double flyingValue;

    double minimum;
    double maximum;

    double singleStep;
    int pageStepCount;
    bool stepAlignment;

    double value;

    bool inverted;
    bool wrapping;
pixhawk's avatar
pixhawk committed
94
95
96
};

//! Constructor
Bryant's avatar
Bryant committed
97
98
QwtWheel::QwtWheel( QWidget *parent ):
    QWidget( parent )
pixhawk's avatar
pixhawk committed
99
{
Bryant's avatar
Bryant committed
100
101
102
103
104
    d_data = new PrivateData;

    setFocusPolicy( Qt::StrongFocus );
    setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
    setAttribute( Qt::WA_WState_OwnSizePolicy, false );
pixhawk's avatar
pixhawk committed
105
106
}

Bryant's avatar
Bryant committed
107
108
//! Destructor
QwtWheel::~QwtWheel()
pixhawk's avatar
pixhawk committed
109
{
Bryant's avatar
Bryant committed
110
    delete d_data;
pixhawk's avatar
pixhawk committed
111
112
}

Bryant's avatar
Bryant committed
113
114
115
116
117
118
119
120
121
122
123
124
125
/*!
  \brief En/Disable tracking

  If tracking is enabled (the default), the wheel emits the valueChanged() 
  signal while the wheel is moving. If tracking is disabled, the wheel 
  emits the valueChanged() signal only when the wheel movement is terminated.

  The wheelMoved() signal is emitted regardless id tracking is enabled or not.

  \param enable On/Off
  \sa isTracking()
 */
void QwtWheel::setTracking( bool enable )
pixhawk's avatar
pixhawk committed
126
{
Bryant's avatar
Bryant committed
127
128
    d_data->tracking = enable;
}
pixhawk's avatar
pixhawk committed
129

Bryant's avatar
Bryant committed
130
131
132
133
134
135
136
137
/*!
  \return True, when tracking is enabled
  \sa setTracking(), valueChanged(), wheelMoved()
*/
bool QwtWheel::isTracking() const
{
    return d_data->tracking;
}
pixhawk's avatar
pixhawk committed
138

Bryant's avatar
Bryant committed
139
140
/*!
  \brief Specify the update interval when the wheel is flying
pixhawk's avatar
pixhawk committed
141

Bryant's avatar
Bryant committed
142
  Default and minimum value is 50 ms.
pixhawk's avatar
pixhawk committed
143

Bryant's avatar
Bryant committed
144
145
146
147
148
149
  \param interval Interval in milliseconds
  \sa updateInterval(), setMass(), setTracking()
*/
void QwtWheel::setUpdateInterval( int interval )
{
    d_data->updateInterval = qMax( interval, 50 );
pixhawk's avatar
pixhawk committed
150
151
}

Bryant's avatar
Bryant committed
152
153
154
155
156
/*!
  \return Update interval when the wheel is flying
  \sa setUpdateInterval(), mass(), isTracking()
 */
int QwtWheel::updateInterval() const
pixhawk's avatar
pixhawk committed
157
{
Bryant's avatar
Bryant committed
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
    return d_data->updateInterval;
}

/*!
   \brief Mouse press event handler

   Start movement of the wheel. 

   \param event Mouse event
*/
void QwtWheel::mousePressEvent( QMouseEvent *event )
{
    stopFlying();

    d_data->isScrolling = wheelRect().contains( event->pos() );

    if ( d_data->isScrolling )
    {
        d_data->time.start();
        d_data->speed = 0.0;
        d_data->mouseValue = valueAt( event->pos() );
        d_data->mouseOffset = d_data->mouseValue - d_data->value;
        d_data->pendingValueChanged = false;

        Q_EMIT wheelPressed();
    }
pixhawk's avatar
pixhawk committed
184
185
}

Bryant's avatar
Bryant committed
186
187
188
189
190
191
192
193
/*!
   \brief Mouse Move Event handler

   Turn the wheel according to the mouse position

   \param event Mouse event
*/
void QwtWheel::mouseMoveEvent( QMouseEvent *event )
pixhawk's avatar
pixhawk committed
194
{
Bryant's avatar
Bryant committed
195
    if ( !d_data->isScrolling )
pixhawk's avatar
pixhawk committed
196
197
        return;

Bryant's avatar
Bryant committed
198
199
200
201
202
    double mouseValue = valueAt( event->pos() );

    if ( d_data->mass > 0.0 )
    {
        double ms = d_data->time.restart();
pixhawk's avatar
pixhawk committed
203

Bryant's avatar
Bryant committed
204
205
        // the interval when mouse move events are posted are somehow
        // random. To avoid unrealistic speed values we limit ms
pixhawk's avatar
pixhawk committed
206

Bryant's avatar
Bryant committed
207
        ms = qMax( ms, 5.0 );
pixhawk's avatar
pixhawk committed
208

Bryant's avatar
Bryant committed
209
210
211
212
213
214
215
216
217
218
219
220
        d_data->speed = ( mouseValue - d_data->mouseValue ) / ms;
    }
    
    d_data->mouseValue = mouseValue; 

    double value = boundedValue( mouseValue - d_data->mouseOffset );
    if ( d_data->stepAlignment )
        value = alignedValue( value );
        
    if ( value != d_data->value )
    {
        d_data->value = value;
pixhawk's avatar
pixhawk committed
221

Bryant's avatar
Bryant committed
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
        update();

        Q_EMIT wheelMoved( d_data->value );

        if ( d_data->tracking )
            Q_EMIT valueChanged( d_data->value );
        else
            d_data->pendingValueChanged = true;
    }
}

/*!
   \brief Mouse Release Event handler

   When the wheel has no mass the movement of the wheel stops, otherwise
   it starts flying.

   \param event Mouse event
*/  

void QwtWheel::mouseReleaseEvent( QMouseEvent *event )
{
    Q_UNUSED( event );

    if ( !d_data->isScrolling )
        return;

    d_data->isScrolling = false;

    bool startFlying = false;

    if ( d_data->mass > 0.0 )
    {
        const int ms = d_data->time.elapsed();
        if ( ( qFabs( d_data->speed ) > 0.0 ) && ( ms < 50 ) )
            startFlying = true;
    }

    if ( startFlying )
    {
        d_data->flyingValue = 
            boundedValue( d_data->mouseValue - d_data->mouseOffset );

        d_data->timerId = startTimer( d_data->updateInterval );
    }
    else
    {
        if ( d_data->pendingValueChanged )
            Q_EMIT valueChanged( d_data->value );
    }

    d_data->pendingValueChanged = false;
    d_data->mouseOffset = 0.0;

    Q_EMIT wheelReleased();
}

/*!
  \brief Qt timer event
pixhawk's avatar
pixhawk committed
281

Bryant's avatar
Bryant committed
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
  The flying wheel effect is implemented using a timer
   
  \param event Timer event

  \sa updateInterval()
 */
void QwtWheel::timerEvent( QTimerEvent *event )
{
    if ( event->timerId() != d_data->timerId )
    {
        QWidget::timerEvent( event );
        return;
    }

    d_data->speed *= qExp( -d_data->updateInterval * 0.001 / d_data->mass );

    d_data->flyingValue += d_data->speed * d_data->updateInterval;
    d_data->flyingValue = boundedValue( d_data->flyingValue );

    double value = d_data->flyingValue;
    if ( d_data->stepAlignment )
        value = alignedValue( value );

    if ( qFabs( d_data->speed ) < 0.001 * d_data->singleStep )
    {
        // stop if d_data->speed < one step per second
        stopFlying();
    }

    if ( value != d_data->value )
    {
        d_data->value = value;
        update();
pixhawk's avatar
pixhawk committed
315

Bryant's avatar
Bryant committed
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
        if ( d_data->tracking || d_data->timerId == 0 )
            Q_EMIT valueChanged( d_data->value );
    }
}


/*!
  \brief Handle wheel events

  In/Decrement the value 

  \param event Wheel event
*/
void QwtWheel::wheelEvent( QWheelEvent *event )
{
    if ( !wheelRect().contains( event->pos() ) )
    {
        event->ignore();
        return;
    }

    if ( d_data->isScrolling )
        return;

    stopFlying();

    double increment = 0.0;

    if ( ( event->modifiers() & Qt::ControlModifier) || 
        ( event->modifiers() & Qt::ShiftModifier ) )
    {
        // one page regardless of delta
        increment = d_data->singleStep * d_data->pageStepCount;
        if ( event->delta() < 0 )
            increment = -increment;
    }
    else
    {
        const int numSteps = event->delta() / 120;
        increment = d_data->singleStep * numSteps;
    }

    if ( d_data->orientation == Qt::Vertical && d_data->inverted )
        increment = -increment;

    double value = boundedValue( d_data->value + increment );

    if ( d_data->stepAlignment )
        value = alignedValue( value );

    if ( value != d_data->value )
    {
        d_data->value = value;
        update();

        Q_EMIT valueChanged( d_data->value );
        Q_EMIT wheelMoved( d_data->value );
    }
}

/*!
  Handle key events

  - Qt::Key_Home\n
    Step to minimum()

  - Qt::Key_End\n
    Step to maximum()

  - Qt::Key_Up\n
    In case of a horizontal or not inverted vertical wheel the value 
    will be incremented by the step size. For an inverted vertical wheel
    the value will be decremented by the step size.

  - Qt::Key_Down\n
    In case of a horizontal or not inverted vertical wheel the value 
    will be decremented by the step size. For an inverted vertical wheel
    the value will be incremented by the step size.

  - Qt::Key_PageUp\n
    The value will be incremented by pageStepSize() * singleStepSize().

  - Qt::Key_PageDown\n
    The value will be decremented by pageStepSize() * singleStepSize().

  \param event Key event
*/
void QwtWheel::keyPressEvent( QKeyEvent *event )
{
    if ( d_data->isScrolling )
    {
        // don't interfere mouse scrolling
        return;
    }

    double value = d_data->value;
    double increment = 0.0;

    switch ( event->key() )
    {
        case Qt::Key_Down:
        {
            if ( d_data->orientation == Qt::Vertical && d_data->inverted )
                increment = d_data->singleStep;
            else
                increment = -d_data->singleStep;

            break;
pixhawk's avatar
pixhawk committed
424
        }
Bryant's avatar
Bryant committed
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
        case Qt::Key_Up:
        {
            if ( d_data->orientation == Qt::Vertical && d_data->inverted )
                increment = -d_data->singleStep;
            else
                increment = d_data->singleStep;

            break;
        }
        case Qt::Key_Left:
        {
            if ( d_data->orientation == Qt::Horizontal )
            {
                if ( d_data->inverted )
                    increment = d_data->singleStep;
                else
                    increment = -d_data->singleStep;
            }
            break;
        }
        case Qt::Key_Right:
        {
            if ( d_data->orientation == Qt::Horizontal )
            {
                if ( d_data->inverted )
                    increment = -d_data->singleStep;
                else
                    increment = d_data->singleStep;
            }
            break;
        }
        case Qt::Key_PageUp:
        {
            increment = d_data->pageStepCount * d_data->singleStep;
            break;
        }
        case Qt::Key_PageDown:
        {
            increment = -d_data->pageStepCount * d_data->singleStep;
            break;
        }
        case Qt::Key_Home:
        {
            value = d_data->minimum;
            break;
        }
        case Qt::Key_End:
        {
            value = d_data->maximum;
            break;
        }
        default:;
        {
            event->ignore();
        }
    }

    if ( event->isAccepted() )
        stopFlying();
    
    if ( increment != 0.0 )
    {
        value = boundedValue( d_data->value + increment );

        if ( d_data->stepAlignment )
            value = alignedValue( value );
    }

    if ( value != d_data->value )
    {
        d_data->value = value;
        update();

        Q_EMIT valueChanged( d_data->value );
        Q_EMIT wheelMoved( d_data->value );
pixhawk's avatar
pixhawk committed
500
501
502
503
504
505
    }
}

/*!
  \brief Adjust the number of grooves in the wheel's surface.

Bryant's avatar
Bryant committed
506
  The number of grooves is limited to 6 <= count <= 50.
pixhawk's avatar
pixhawk committed
507
508
  Values outside this range will be clipped.
  The default value is 10.
Bryant's avatar
Bryant committed
509
510
511

  \param count Number of grooves per 360 degrees
  \sa tickCount()
pixhawk's avatar
pixhawk committed
512
*/
Bryant's avatar
Bryant committed
513
void QwtWheel::setTickCount( int count )
pixhawk's avatar
pixhawk committed
514
{
Bryant's avatar
Bryant committed
515
    count = qBound( 6, count, 50 );
pixhawk's avatar
pixhawk committed
516

Bryant's avatar
Bryant committed
517
518
519
520
521
    if ( count != d_data->tickCount )
    {
        d_data->tickCount = qBound( 6, count, 50 );
        update();
    }
pixhawk's avatar
pixhawk committed
522
523
524
}

/*!
Bryant's avatar
Bryant committed
525
526
  \return Number of grooves in the wheel's surface.
  \sa setTickCnt()
pixhawk's avatar
pixhawk committed
527
*/
Bryant's avatar
Bryant committed
528
int QwtWheel::tickCount() const
pixhawk's avatar
pixhawk committed
529
{
Bryant's avatar
Bryant committed
530
    return d_data->tickCount;
pixhawk's avatar
pixhawk committed
531
532
533
}

/*!
Bryant's avatar
Bryant committed
534
  \brief Set the wheel border width of the wheel.
pixhawk's avatar
pixhawk committed
535

Bryant's avatar
Bryant committed
536
  The wheel border must not be smaller than 1
pixhawk's avatar
pixhawk committed
537
538
539
  and is limited in dependence on the wheel's size.
  Values outside the allowed range will be clipped.

Bryant's avatar
Bryant committed
540
541
542
543
  The wheel border defaults to 2.

  \param borderWidth Border width
  \sa internalBorder()
pixhawk's avatar
pixhawk committed
544
*/
Bryant's avatar
Bryant committed
545
void QwtWheel::setWheelBorderWidth( int borderWidth )
pixhawk's avatar
pixhawk committed
546
{
Bryant's avatar
Bryant committed
547
548
549
550
    const int d = qMin( width(), height() ) / 3;
    borderWidth = qMin( borderWidth, d );
    d_data->wheelBorderWidth = qMax( borderWidth, 1 );
    update();
pixhawk's avatar
pixhawk committed
551
552
}

Bryant's avatar
Bryant committed
553
554
555
556
557
/*!
   \return Wheel border width 
   \sa setWheelBorderWidth()
*/
int QwtWheel::wheelBorderWidth() const
pixhawk's avatar
pixhawk committed
558
{
Bryant's avatar
Bryant committed
559
    return d_data->wheelBorderWidth;
pixhawk's avatar
pixhawk committed
560
561
}

Bryant's avatar
Bryant committed
562
563
/*!
  \brief Set the border width 
pixhawk's avatar
pixhawk committed
564

Bryant's avatar
Bryant committed
565
  The border defaults to 2.
pixhawk's avatar
pixhawk committed
566

Bryant's avatar
Bryant committed
567
568
569
570
571
572
573
574
  \param width Border width
  \sa borderWidth()
*/
void QwtWheel::setBorderWidth( int width )
{
    d_data->borderWidth = qMax( width, 0 );
    update();
}
pixhawk's avatar
pixhawk committed
575

Bryant's avatar
Bryant committed
576
577
578
579
580
581
582
/*!
   \return Border width 
   \sa setBorderWidth()
*/
int QwtWheel::borderWidth() const
{
    return d_data->borderWidth;
pixhawk's avatar
pixhawk committed
583
584
}

Bryant's avatar
Bryant committed
585
586
587
588
589
590
591
592
/*!
   \return Rectangle of the wheel without the outer border
*/
QRect QwtWheel::wheelRect() const
{
    const int bw = d_data->borderWidth;
    return contentsRect().adjusted( bw, bw, -bw, -bw );
}
pixhawk's avatar
pixhawk committed
593
594
595
596
597
598
599
600
601
602

/*!
  \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.
Bryant's avatar
Bryant committed
603

pixhawk's avatar
pixhawk committed
604
  \param angle total angle in degrees
Bryant's avatar
Bryant committed
605
  \sa totalAngle()
pixhawk's avatar
pixhawk committed
606
*/
Bryant's avatar
Bryant committed
607
void QwtWheel::setTotalAngle( double angle )
pixhawk's avatar
pixhawk committed
608
609
610
611
612
613
614
615
{
    if ( angle < 0.0 )
        angle = 0.0;

    d_data->totalAngle = angle;
    update();
}

Bryant's avatar
Bryant committed
616
617
618
619
/*!
  \return Total angle which the wheel can be turned.
  \sa setTotalAngle()
*/
620
double QwtWheel::totalAngle() const
pixhawk's avatar
pixhawk committed
621
622
623
624
625
626
{
    return d_data->totalAngle;
}

/*!
  \brief Set the wheel's orientation.
Bryant's avatar
Bryant committed
627
628
629
630
631

  The default orientation is Qt::Horizontal.

  \param orientation Qt::Horizontal or Qt::Vertical.
  \sa orientation()
pixhawk's avatar
pixhawk committed
632
*/
Bryant's avatar
Bryant committed
633
void QwtWheel::setOrientation( Qt::Orientation orientation )
pixhawk's avatar
pixhawk committed
634
{
Bryant's avatar
Bryant committed
635
    if ( d_data->orientation == orientation )
pixhawk's avatar
pixhawk committed
636
637
        return;

Bryant's avatar
Bryant committed
638
    if ( !testAttribute( Qt::WA_WState_OwnSizePolicy ) )
pixhawk's avatar
pixhawk committed
639
640
641
    {
        QSizePolicy sp = sizePolicy();
        sp.transpose();
Bryant's avatar
Bryant committed
642
        setSizePolicy( sp );
pixhawk's avatar
pixhawk committed
643

Bryant's avatar
Bryant committed
644
        setAttribute( Qt::WA_WState_OwnSizePolicy, false );
pixhawk's avatar
pixhawk committed
645
646
    }

Bryant's avatar
Bryant committed
647
648
649
650
651
652
653
654
655
656
657
    d_data->orientation = orientation;
    update();
}

/*!
  \return Orientation
  \sa setOrientation()
*/
Qt::Orientation QwtWheel::orientation() const
{
    return d_data->orientation;
pixhawk's avatar
pixhawk committed
658
659
660
661
662
663
664
665
}

/*!
  \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.
Bryant's avatar
Bryant committed
666

pixhawk's avatar
pixhawk committed
667
  \param angle Visible angle in degrees
Bryant's avatar
Bryant committed
668
  \sa viewAngle(), setTotalAngle()
pixhawk's avatar
pixhawk committed
669
*/
Bryant's avatar
Bryant committed
670
void QwtWheel::setViewAngle( double angle )
pixhawk's avatar
pixhawk committed
671
{
Bryant's avatar
Bryant committed
672
    d_data->viewAngle = qBound( 10.0, angle, 175.0 );
pixhawk's avatar
pixhawk committed
673
674
675
    update();
}

Bryant's avatar
Bryant committed
676
677
678
679
/*!
  \return Visible portion of the wheel
  \sa setViewAngle(), totalAngle()
*/
680
double QwtWheel::viewAngle() const
pixhawk's avatar
pixhawk committed
681
682
683
684
{
    return d_data->viewAngle;
}

Bryant's avatar
Bryant committed
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
/*! 
  Determine the value corresponding to a specified point

  \param pos Position
  \return Value corresponding to pos
*/
double QwtWheel::valueAt( const QPoint &pos ) const
{
    const QRectF rect = wheelRect();

    double w, dx;
    if ( d_data->orientation == Qt::Vertical )
    {
        w = rect.height();
        dx = rect.top() - pos.y();
    }
    else
    {
        w = rect.width();
        dx = pos.x() - rect.left();
    }

    if ( w == 0.0 )
        return 0.0;

    if ( d_data->inverted )
    {
        dx = w - dx;
    }

    // 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 * ( maximum() - minimum() ) / d_data->totalAngle;

    return val;
}

/*! 
   \brief Qt Paint Event
   \param event Paint event
*/
void QwtWheel::paintEvent( QPaintEvent *event )
{
    QPainter painter( this );
    painter.setClipRegion( event->region() );

    QStyleOption opt;
    opt.init(this);
    style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);

    qDrawShadePanel( &painter, 
        contentsRect(), palette(), true, d_data->borderWidth );

    drawWheelBackground( &painter, wheelRect() );
    drawTicks( &painter, wheelRect() );

    if ( hasFocus() )
        QwtPainter::drawFocusRect( &painter, this );
}

/*!
   Draw the Wheel's background gradient

   \param painter Painter
   \param rect Geometry for the wheel
*/
void QwtWheel::drawWheelBackground( 
    QPainter *painter, const QRectF &rect )
{
    painter->save();

    QPalette pal = palette();

    //  draw shaded background
    QLinearGradient gradient( rect.topLeft(), 
        ( d_data->orientation == Qt::Horizontal ) ? rect.topRight() : rect.bottomLeft() );
    gradient.setColorAt( 0.0, pal.color( QPalette::Button ) );
    gradient.setColorAt( 0.2, pal.color( QPalette::Midlight ) );
    gradient.setColorAt( 0.7, pal.color( QPalette::Mid ) );
    gradient.setColorAt( 1.0, pal.color( QPalette::Dark ) );

    painter->fillRect( rect, gradient );

    // draw internal border

    const QPen lightPen( palette().color( QPalette::Light ), 
        d_data->wheelBorderWidth, Qt::SolidLine, Qt::FlatCap );
    const QPen darkPen( pal.color( QPalette::Dark ), 
        d_data->wheelBorderWidth, Qt::SolidLine, Qt::FlatCap );

    const double bw2 = 0.5 * d_data->wheelBorderWidth;

    if ( d_data->orientation == Qt::Horizontal )
    {
        painter->setPen( lightPen );
        painter->drawLine( QPointF( rect.left(), rect.top() + bw2 ), 
            QPointF( rect.right(), rect.top() + bw2 ) );

        painter->setPen( darkPen );
        painter->drawLine( QPointF( rect.left(), rect.bottom() - bw2 ), 
            QPointF( rect.right(), rect.bottom() - bw2 ) );
    }
    else // Qt::Vertical
    {
        painter->setPen( lightPen );
        painter->drawLine( QPointF( rect.left() + bw2, rect.top() ), 
            QPointF( rect.left() + bw2, rect.bottom() ) );

        painter->setPen( darkPen );
        painter->drawLine( QPointF( rect.right() - bw2, rect.top() ), 
            QPointF( rect.right() - bw2, rect.bottom() ) );
    }

    painter->restore();
}

pixhawk's avatar
pixhawk committed
805
/*!
Bryant's avatar
Bryant committed
806
807
808
809
   Draw the Wheel's ticks

   \param painter Painter
   \param rect Geometry for the wheel
pixhawk's avatar
pixhawk committed
810
*/
Bryant's avatar
Bryant committed
811
void QwtWheel::drawTicks( QPainter *painter, const QRectF &rect )
pixhawk's avatar
pixhawk committed
812
{
Bryant's avatar
Bryant committed
813
    const double range = d_data->maximum - d_data->minimum;
pixhawk's avatar
pixhawk committed
814

Bryant's avatar
Bryant committed
815
816
    if ( range == 0.0 || d_data->totalAngle == 0.0 )
    {
pixhawk's avatar
pixhawk committed
817
        return;
Bryant's avatar
Bryant committed
818
    }
pixhawk's avatar
pixhawk committed
819

Bryant's avatar
Bryant committed
820
821
822
823
    const QPen lightPen( palette().color( QPalette::Light ), 
        0, Qt::SolidLine, Qt::FlatCap );
    const QPen darkPen( palette().color( QPalette::Dark ), 
        0, Qt::SolidLine, Qt::FlatCap );
pixhawk's avatar
pixhawk committed
824

Bryant's avatar
Bryant committed
825
    const double cnvFactor = qAbs( d_data->totalAngle / range );
pixhawk's avatar
pixhawk committed
826
827
828
    const double halfIntv = 0.5 * d_data->viewAngle / cnvFactor;
    const double loValue = value() - halfIntv;
    const double hiValue = value() + halfIntv;
Bryant's avatar
Bryant committed
829
830
    const double tickWidth = 360.0 / double( d_data->tickCount ) / cnvFactor;
    const double sinArc = qFastSin( d_data->viewAngle * M_PI / 360.0 );
pixhawk's avatar
pixhawk committed
831

Bryant's avatar
Bryant committed
832
833
834
    if ( d_data->orientation == Qt::Horizontal )
    {
        const double radius = rect.width() * 0.5;
pixhawk's avatar
pixhawk committed
835

Bryant's avatar
Bryant committed
836
837
        double l1 = rect.top() + d_data->wheelBorderWidth;
        double l2 = rect.bottom() - d_data->wheelBorderWidth - 1;
pixhawk's avatar
pixhawk committed
838
839

        // draw one point over the border if border > 1
Bryant's avatar
Bryant committed
840
841
842
843
        if ( d_data->wheelBorderWidth > 1 )
        {
            l1--;
            l2++;
pixhawk's avatar
pixhawk committed
844
845
        }

Bryant's avatar
Bryant committed
846
847
        const double maxpos = rect.right() - 2;
        const double minpos = rect.left() + 2;
pixhawk's avatar
pixhawk committed
848
849

        // draw tick marks
Bryant's avatar
Bryant committed
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
        for ( double tickValue = ::ceil( loValue / tickWidth ) * tickWidth;
            tickValue < hiValue; tickValue += tickWidth )
        {
            const double angle = qwtRadians( tickValue - value() );
            const double s = qFastSin( angle * cnvFactor );

            const double off = radius * ( sinArc + s ) / sinArc;

            double tickPos;
            if ( d_data->inverted ) 
                tickPos = rect.left() + off;
            else
                tickPos = rect.right() - off;

            if ( ( tickPos <= maxpos ) && ( tickPos > minpos ) )
            {
                painter->setPen( darkPen );
                painter->drawLine( QPointF( tickPos - 1 , l1 ), 
                    QPointF( tickPos - 1,  l2 ) );
                painter->setPen( lightPen );
                painter->drawLine( QPointF( tickPos, l1 ), 
                    QPointF( tickPos, l2 ) );
pixhawk's avatar
pixhawk committed
872
873
            }
        }
Bryant's avatar
Bryant committed
874
875
876
877
    }
    else // Qt::Vertical
    {
        const double radius = rect.height() * 0.5;
pixhawk's avatar
pixhawk committed
878

Bryant's avatar
Bryant committed
879
880
        double l1 = rect.left() + d_data->wheelBorderWidth;
        double l2 = rect.right() - d_data->wheelBorderWidth - 1;
pixhawk's avatar
pixhawk committed
881

Bryant's avatar
Bryant committed
882
883
        if ( d_data->wheelBorderWidth > 1 )
        {
pixhawk's avatar
pixhawk committed
884
885
886
887
            l1--;
            l2++;
        }

Bryant's avatar
Bryant committed
888
889
        const double maxpos = rect.bottom() - 2;
        const double minpos = rect.top() + 2;
pixhawk's avatar
pixhawk committed
890

Bryant's avatar
Bryant committed
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
        for ( double tickValue = ::ceil( loValue / tickWidth ) * tickWidth;
            tickValue < hiValue; tickValue += tickWidth )
        {
            const double angle = qwtRadians( tickValue - value() );
            const double s = qFastSin( angle * cnvFactor );

            const double off = radius * ( sinArc + s ) / sinArc;

            double tickPos;

            if ( d_data->inverted )
                tickPos = rect.bottom() - off;
            else
                tickPos = rect.top() + off;

            if ( ( tickPos <= maxpos ) && ( tickPos > minpos ) )
            {
                painter->setPen( darkPen );
                painter->drawLine( QPointF( l1, tickPos - 1 ), 
                    QPointF( l2, tickPos - 1 ) );
                painter->setPen( lightPen );
                painter->drawLine( QPointF( l1, tickPos ), 
                    QPointF( l2, tickPos ) );
pixhawk's avatar
pixhawk committed
914
915
916
917
918
            }
        }
    }
}

Bryant's avatar
Bryant committed
919
920
921
922
923
/*!
  \brief Set the width of the wheel

  Corresponds to the wheel height for horizontal orientation,
  and the wheel width for vertical orientation.
pixhawk's avatar
pixhawk committed
924

Bryant's avatar
Bryant committed
925
926
927
928
  \param width the wheel's width
  \sa wheelWidth()
*/
void QwtWheel::setWheelWidth( int width )
pixhawk's avatar
pixhawk committed
929
{
Bryant's avatar
Bryant committed
930
931
932
    d_data->wheelWidth = width;
    update();
}
pixhawk's avatar
pixhawk committed
933

Bryant's avatar
Bryant committed
934
935
936
937
938
939
940
941
/*!
  \return Width of the wheel
  \sa setWheelWidth()
*/
int QwtWheel::wheelWidth() const
{
    return d_data->wheelWidth;
}
pixhawk's avatar
pixhawk committed
942

Bryant's avatar
Bryant committed
943
944
945
946
947
948
949
950
/*!
  \return a size hint
*/
QSize QwtWheel::sizeHint() const
{
    const QSize hint = minimumSizeHint();
    return hint.expandedTo( QApplication::globalStrut() );
}
pixhawk's avatar
pixhawk committed
951

Bryant's avatar
Bryant committed
952
953
954
955
956
957
958
959
960
961
962
963
/*!
  \return 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 );
    if ( d_data->orientation != Qt::Horizontal )
        sz.transpose();

    return sz;
pixhawk's avatar
pixhawk committed
964
965
}

Bryant's avatar
Bryant committed
966
967
968
969
970
971
972
973
974
/*!
  \brief Set the step size of the counter

  A value <= 0.0 disables stepping

  \param stepSize Single step size
  \sa singleStep(), setPageStepCount()
*/
void QwtWheel::setSingleStep( double stepSize )
pixhawk's avatar
pixhawk committed
975
{
Bryant's avatar
Bryant committed
976
    d_data->singleStep = qMax( stepSize, 0.0 );
pixhawk's avatar
pixhawk committed
977
978
}

Bryant's avatar
Bryant committed
979
980
981
982
983
/*!
  \return Single step size
  \sa setSingleStep()
 */
double QwtWheel::singleStep() const
pixhawk's avatar
pixhawk committed
984
{
Bryant's avatar
Bryant committed
985
986
    return d_data->singleStep;
}
pixhawk's avatar
pixhawk committed
987

Bryant's avatar
Bryant committed
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
/*!
  \brief En/Disable step alignment

  When step alignment is enabled value changes initiated by
  user input ( mouse, keyboard, wheel ) are aligned to
  the multiples of the single step.

  \param on On/Off
  \sa stepAlignment(), setSingleStep()
 */
void QwtWheel::setStepAlignment( bool on )
{
    if ( on != d_data->stepAlignment )
    {
        d_data->stepAlignment = on;
pixhawk's avatar
pixhawk committed
1003
1004
1005
    }
}

Bryant's avatar
Bryant committed
1006
1007
1008
1009
1010
/*!
  \return True, when the step alignment is enabled
  \sa setStepAlignment(), singleStep()
 */
bool QwtWheel::stepAlignment() const
pixhawk's avatar
pixhawk committed
1011
{
Bryant's avatar
Bryant committed
1012
    return d_data->stepAlignment;
pixhawk's avatar
pixhawk committed
1013
1014
}

Bryant's avatar
Bryant committed
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
/*!
  \brief Set the page step count  
    
  pageStepCount is a multiplicator for the single step size
  that typically corresponds to the user pressing PageUp or PageDown.
    
  A value of 0 disables page stepping. 

  The default value is 1.

  \param count Multiplicator for the single step size
  \sa pageStepCount(), setSingleStep()
 */
void QwtWheel::setPageStepCount( int count )
pixhawk's avatar
pixhawk committed
1029
{
Bryant's avatar
Bryant committed
1030
1031
    d_data->pageStepCount = qMax( 0, count );
}
pixhawk's avatar
pixhawk committed
1032

Bryant's avatar
Bryant committed
1033
1034
1035
1036
1037
1038
1039
1040
/*! 
  \return Page step count
  \sa setPageStepCount(), singleStep()
 */
int QwtWheel::pageStepCount() const
{
    return d_data->pageStepCount;
}
pixhawk's avatar
pixhawk committed
1041

Bryant's avatar
Bryant committed
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
/*!
  \brief Set the minimum and maximum values

  The maximum is adjusted if necessary to ensure that the range remains valid.
  The value might be modified to be inside of the range.

  \param min Minimum value
  \param max Maximum value

  \sa minimum(), maximum()
 */
void QwtWheel::setRange( double min, double max )
{
    max = qMax( min, max );

    if ( d_data->minimum == min && d_data->maximum == max )
        return;

    d_data->minimum = min;
    d_data->maximum = max;

    if ( d_data->value < min || d_data->value > max )
    {
        d_data->value = qBound( min, d_data->value, max );

        update();
        Q_EMIT valueChanged( d_data->value );
    }
pixhawk's avatar
pixhawk committed
1070
}
Bryant's avatar
Bryant committed
1071
1072
/*!
  Set the minimum value of the range
pixhawk's avatar
pixhawk committed
1073

Bryant's avatar
Bryant committed
1074
1075
1076
1077
1078
1079
  \param value Minimum value
  \sa setRange(), setMaximum(), minimum()

  \note The maximum is adjusted if necessary to ensure that the range remains valid.
*/
void QwtWheel::setMinimum( double value )
pixhawk's avatar
pixhawk committed
1080
{
Bryant's avatar
Bryant committed
1081
    setRange( value, maximum() );
pixhawk's avatar
pixhawk committed
1082
1083
}

Bryant's avatar
Bryant committed
1084
1085
1086
1087
1088
1089
1090
1091
/*!
  \return The minimum of the range
  \sa setRange(), setMinimum(), maximum()
*/
double QwtWheel::minimum() const
{
    return d_data->minimum;
}
pixhawk's avatar
pixhawk committed
1092
1093

/*!
Bryant's avatar
Bryant committed
1094
1095
1096
1097
  Set the maximum value of the range

  \param value Maximum value
  \sa setRange(), setMinimum(), maximum()
pixhawk's avatar
pixhawk committed
1098
*/
Bryant's avatar
Bryant committed
1099
void QwtWheel::setMaximum( double value )
pixhawk's avatar
pixhawk committed
1100
{
Bryant's avatar
Bryant committed
1101
1102
    setRange( minimum(), value );
}
pixhawk's avatar
pixhawk committed
1103

Bryant's avatar
Bryant committed
1104
1105
1106
1107
1108
1109
1110
/*!
  \return The maximum of the range
  \sa setRange(), setMaximum(), minimum()
*/
double QwtWheel::maximum() const
{
    return d_data->maximum;
pixhawk's avatar
pixhawk committed
1111
1112
1113
}

/*!
Bryant's avatar
Bryant committed
1114
1115
1116
  \brief Set a new value without adjusting to the step raster

  \param value New value
pixhawk's avatar
pixhawk committed
1117

Bryant's avatar
Bryant committed
1118
1119
  \sa value(), valueChanged()
  \warning The value is clipped when it lies outside the range.
pixhawk's avatar
pixhawk committed
1120
*/
Bryant's avatar
Bryant committed
1121
void QwtWheel::setValue( double value )
pixhawk's avatar
pixhawk committed
1122
{
Bryant's avatar
Bryant committed
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
    stopFlying();
    d_data->isScrolling = false;

    value = qBound( d_data->minimum, value, d_data->maximum );

    if ( d_data->value != value )
    {
        d_data->value = value;

        update();
        Q_EMIT valueChanged( d_data->value );
    }
pixhawk's avatar
pixhawk committed
1135
1136
1137
}

/*!
Bryant's avatar
Bryant committed
1138
1139
1140
1141
1142
1143
1144
  \return Current value of the wheel
  \sa setValue(), valueChanged()
 */
double QwtWheel::value() const
{
    return d_data->value;
}
pixhawk's avatar
pixhawk committed
1145

Bryant's avatar
Bryant committed
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
/*!
  \brief En/Disable inverted appearance

  An inverted wheel increases its values in the opposite direction.
  The direction of an inverted horizontal wheel will be from right to left
  an inverted vertical wheel will increase from bottom to top.
  
  \param on En/Disable inverted appearance
  \sa isInverted()
 
 */
void QwtWheel::setInverted( bool on )
pixhawk's avatar
pixhawk committed
1158
{
Bryant's avatar
Bryant committed
1159
1160
1161
1162
1163
    if ( d_data->inverted != on )
    {
        d_data->inverted = on;
        update();
    }
pixhawk's avatar
pixhawk committed
1164
1165
1166
}

/*!
Bryant's avatar
Bryant committed
1167
1168
1169
1170
  \return True, when the wheel is inverted
  \sa setInverted()
 */
bool QwtWheel::isInverted() const
pixhawk's avatar
pixhawk committed
1171
{
Bryant's avatar
Bryant committed
1172
    return d_data->inverted;
pixhawk's avatar
pixhawk committed
1173
1174
1175
}

/*!
Bryant's avatar
Bryant committed
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
  \brief En/Disable wrapping

  If wrapping is true stepping up from maximum() value will take 
  you to the minimum() value and vice versa. 

  \param on En/Disable wrapping
  \sa wrapping()
 */
void QwtWheel::setWrapping( bool on )
{
    d_data->wrapping = on;
}

/*!
  \return True, when wrapping is set
  \sa setWrapping()
 */
bool QwtWheel::wrapping() const
{
    return d_data->wrapping;
}

/*!
  \brief Set the slider's mass for flywheel effect.

  If the slider's mass is greater then 0, it will continue
  to move after the mouse button has been released. Its speed
  decreases with time at a rate depending on the slider's mass.
  A large mass means that it will continue to move for a
  long time.

  Derived widgets may overload this function to make it public.

  \param mass New mass in kg

  \bug If the mass is smaller than 1g, it is set to zero.
       The maximal mass is limited to 100kg.
  \sa mass()
pixhawk's avatar
pixhawk committed
1214
*/
Bryant's avatar
Bryant committed
1215
void QwtWheel::setMass( double mass )
pixhawk's avatar
pixhawk committed
1216
{
Bryant's avatar
Bryant committed
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
    if ( mass < 0.001 )
    {
        d_data->mass = 0.0;
    }
    else
    {
        d_data->mass = qMin( 100.0, mass );
    }

    if ( d_data->mass <= 0.0 )
        stopFlying();
pixhawk's avatar
pixhawk committed
1228
1229
1230
}

/*!
Bryant's avatar
Bryant committed
1231
1232
  \return mass
  \sa setMass()
pixhawk's avatar
pixhawk committed
1233
*/
Bryant's avatar
Bryant committed
1234
double QwtWheel::mass() const
pixhawk's avatar
pixhawk committed
1235
{
Bryant's avatar
Bryant committed
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
    return d_data->mass;
}

//!  Stop the flying movement of the wheel
void QwtWheel::stopFlying()
{
    if ( d_data->timerId != 0 )
    {
        killTimer( d_data->timerId );
        d_data->timerId = 0;
        d_data->speed = 0.0;
    }
}

double QwtWheel::boundedValue( double value ) const
{
    const double range = d_data->maximum - d_data->minimum;
    
    if ( d_data->wrapping && range >= 0.0 )
    {
        if ( value < d_data->minimum )
        {
            value += ::ceil( ( d_data->minimum - value ) / range ) * range;
        }       
        else if ( value > d_data->maximum )
        {
            value -= ::ceil( ( value - d_data->maximum ) / range ) * range;
        }
    }
    else
    {
        value = qBound( d_data->minimum, value, d_data->maximum );
    }

    return value;
}

double QwtWheel::alignedValue( double value ) const
{
    const double stepSize = d_data->singleStep;

    if ( stepSize > 0.0 )
    {
        value = d_data->minimum +
            qRound( ( value - d_data->minimum ) / stepSize ) * stepSize;

        if ( stepSize > 1e-12 )
        {
            if ( qFuzzyCompare( value + 1.0, 1.0 ) )
            {
                // correct rounding error if value = 0
                value = 0.0;
            }
            else if ( qFuzzyCompare( value, d_data->maximum ) )
            {
                // correct rounding error at the border
                value = d_data->maximum;
            }
        }
    }       

    return value;
pixhawk's avatar
pixhawk committed
1298
1299
}