Newer
Older
/* -*- 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
*****************************************************************************/
#include "qwt_wheel.h"
#include "qwt_math.h"
#include "qwt_painter.h"
#include <qevent.h>
#include <qdrawutil.h>
#include <qpainter.h>
#include <qstyle.h>
#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
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 )
{
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;
d_data = new PrivateData;
setFocusPolicy( Qt::StrongFocus );
setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
setAttribute( Qt::WA_WState_OwnSizePolicy, false );
/*!
\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 )
/*!
\return True, when tracking is enabled
\sa setTracking(), valueChanged(), wheelMoved()
*/
bool QwtWheel::isTracking() const
{
return d_data->tracking;
}
/*!
\brief Specify the update interval when the wheel is flying
\param interval Interval in milliseconds
\sa updateInterval(), setMass(), setTracking()
*/
void QwtWheel::setUpdateInterval( int interval )
{
d_data->updateInterval = qMax( interval, 50 );
/*!
\return Update interval when the wheel is flying
\sa setUpdateInterval(), mass(), isTracking()
*/
int QwtWheel::updateInterval() const
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();
}
/*!
\brief Mouse Move Event handler
Turn the wheel according to the mouse position
\param event Mouse event
*/
void QwtWheel::mouseMoveEvent( QMouseEvent *event )
double mouseValue = valueAt( event->pos() );
if ( d_data->mass > 0.0 )
{
double ms = d_data->time.restart();
// the interval when mouse move events are posted are somehow
// random. To avoid unrealistic speed values we limit ms
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;
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
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();
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;
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 );
}
}
/*!
\brief Adjust the number of grooves in the wheel's surface.
Values outside this range will be clipped.
The default value is 10.
\param count Number of grooves per 360 degrees
\sa tickCount()
if ( count != d_data->tickCount )
{
d_data->tickCount = qBound( 6, count, 50 );
update();
}
\return Number of grooves in the wheel's surface.
\sa setTickCnt()
and is limited in dependence on the wheel's size.
Values outside the allowed range will be clipped.
The wheel border defaults to 2.
\param borderWidth Border width
\sa internalBorder()
const int d = qMin( width(), height() ) / 3;
borderWidth = qMin( borderWidth, d );
d_data->wheelBorderWidth = qMax( borderWidth, 1 );
update();
/*!
\return Wheel border width
\sa setWheelBorderWidth()
*/
int QwtWheel::wheelBorderWidth() const
\param width Border width
\sa borderWidth()
*/
void QwtWheel::setBorderWidth( int width )
{
d_data->borderWidth = qMax( width, 0 );
update();
}
/*!
\return Border width
\sa setBorderWidth()
*/
int QwtWheel::borderWidth() const
{
return d_data->borderWidth;
/*!
\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 );
}
/*!
\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.
{
if ( angle < 0.0 )
angle = 0.0;
d_data->totalAngle = angle;
update();
}
/*!
\return Total angle which the wheel can be turned.
\sa setTotalAngle()
*/
double QwtWheel::totalAngle() const
{
return d_data->totalAngle;
}
/*!
\brief Set the wheel's orientation.
The default orientation is Qt::Horizontal.
\param orientation Qt::Horizontal or Qt::Vertical.
\sa orientation()
void QwtWheel::setOrientation( Qt::Orientation orientation )
d_data->orientation = orientation;
update();
}
/*!
\return Orientation
\sa setOrientation()
*/
Qt::Orientation QwtWheel::orientation() const
{
return d_data->orientation;
}
/*!
\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.
/*!
\return Visible portion of the wheel
\sa setViewAngle(), totalAngle()
*/
double QwtWheel::viewAngle() const
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();
}
Draw the Wheel's ticks
\param painter Painter
\param rect Geometry for the wheel
void QwtWheel::drawTicks( QPainter *painter, const QRectF &rect )
const QPen lightPen( palette().color( QPalette::Light ),
0, Qt::SolidLine, Qt::FlatCap );
const QPen darkPen( palette().color( QPalette::Dark ),
0, Qt::SolidLine, Qt::FlatCap );
const double cnvFactor = qAbs( d_data->totalAngle / range );
const double halfIntv = 0.5 * d_data->viewAngle / cnvFactor;
const double loValue = value() - halfIntv;
const double hiValue = value() + halfIntv;
const double tickWidth = 360.0 / double( d_data->tickCount ) / cnvFactor;
const double sinArc = qFastSin( d_data->viewAngle * M_PI / 360.0 );
if ( d_data->orientation == Qt::Horizontal )
{
const double radius = rect.width() * 0.5;
double l1 = rect.top() + d_data->wheelBorderWidth;
double l2 = rect.bottom() - d_data->wheelBorderWidth - 1;
if ( d_data->wheelBorderWidth > 1 )
{
l1--;
l2++;
const double maxpos = rect.right() - 2;
const double minpos = rect.left() + 2;
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 ) );
}
else // Qt::Vertical
{
const double radius = rect.height() * 0.5;
double l1 = rect.left() + d_data->wheelBorderWidth;
double l2 = rect.right() - d_data->wheelBorderWidth - 1;
const double maxpos = rect.bottom() - 2;
const double minpos = rect.top() + 2;
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 ) );
/*!
\brief Set the width of the wheel
Corresponds to the wheel height for horizontal orientation,
and the wheel width for vertical orientation.
\param width the wheel's width
\sa wheelWidth()
*/
void QwtWheel::setWheelWidth( int width )
/*!
\return Width of the wheel
\sa setWheelWidth()
*/
int QwtWheel::wheelWidth() const
{
return d_data->wheelWidth;
}
/*!
\return a size hint
*/
QSize QwtWheel::sizeHint() const
{
const QSize hint = minimumSizeHint();
return hint.expandedTo( QApplication::globalStrut() );
}
/*!
\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;
/*!
\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 )
/*!
\return Single step size
\sa setSingleStep()
*/
double QwtWheel::singleStep() const
/*!
\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 )