1300 lines
29 KiB
C++
1300 lines
29 KiB
C++
|
/* -*- 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
|
||
|
|
||
|
class QwtWheel::PrivateData
|
||
|
{
|
||
|
public:
|
||
|
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 )
|
||
|
{
|
||
|
};
|
||
|
|
||
|
Qt::Orientation orientation;
|
||
|
double viewAngle;
|
||
|
double totalAngle;
|
||
|
int tickCount;
|
||
|
int wheelBorderWidth;
|
||
|
int borderWidth;
|
||
|
int wheelWidth;
|
||
|
|
||
|
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;
|
||
|
};
|
||
|
|
||
|
//! Constructor
|
||
|
QwtWheel::QwtWheel( QWidget *parent ):
|
||
|
QWidget( parent )
|
||
|
{
|
||
|
d_data = new PrivateData;
|
||
|
|
||
|
setFocusPolicy( Qt::StrongFocus );
|
||
|
setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
|
||
|
setAttribute( Qt::WA_WState_OwnSizePolicy, false );
|
||
|
}
|
||
|
|
||
|
//! Destructor
|
||
|
QwtWheel::~QwtWheel()
|
||
|
{
|
||
|
delete d_data;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
\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 )
|
||
|
{
|
||
|
d_data->tracking = 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
|
||
|
|
||
|
Default and minimum value is 50 ms.
|
||
|
|
||
|
\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
|
||
|
{
|
||
|
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 )
|
||
|
{
|
||
|
if ( !d_data->isScrolling )
|
||
|
return;
|
||
|
|
||
|
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
|
||
|
|
||
|
ms = qMax( ms, 5.0 );
|
||
|
|
||
|
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;
|
||
|
|
||
|
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
|
||
|
|
||
|
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();
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
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.
|
||
|
|
||
|
The number of grooves is limited to 6 <= count <= 50.
|
||
|
Values outside this range will be clipped.
|
||
|
The default value is 10.
|
||
|
|
||
|
\param count Number of grooves per 360 degrees
|
||
|
\sa tickCount()
|
||
|
*/
|
||
|
void QwtWheel::setTickCount( int count )
|
||
|
{
|
||
|
count = qBound( 6, count, 50 );
|
||
|
|
||
|
if ( count != d_data->tickCount )
|
||
|
{
|
||
|
d_data->tickCount = qBound( 6, count, 50 );
|
||
|
update();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
\return Number of grooves in the wheel's surface.
|
||
|
\sa setTickCnt()
|
||
|
*/
|
||
|
int QwtWheel::tickCount() const
|
||
|
{
|
||
|
return d_data->tickCount;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
\brief Set the wheel border width of the wheel.
|
||
|
|
||
|
The wheel border must not be smaller than 1
|
||
|
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()
|
||
|
*/
|
||
|
void QwtWheel::setWheelBorderWidth( int borderWidth )
|
||
|
{
|
||
|
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
|
||
|
{
|
||
|
return d_data->wheelBorderWidth;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
\brief Set the border width
|
||
|
|
||
|
The border defaults to 2.
|
||
|
|
||
|
\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.
|
||
|
|
||
|
\param angle total angle in degrees
|
||
|
\sa totalAngle()
|
||
|
*/
|
||
|
void QwtWheel::setTotalAngle( double angle )
|
||
|
{
|
||
|
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 )
|
||
|
{
|
||
|
if ( d_data->orientation == orientation )
|
||
|
return;
|
||
|
|
||
|
if ( !testAttribute( Qt::WA_WState_OwnSizePolicy ) )
|
||
|
{
|
||
|
QSizePolicy sp = sizePolicy();
|
||
|
sp.transpose();
|
||
|
setSizePolicy( sp );
|
||
|
|
||
|
setAttribute( Qt::WA_WState_OwnSizePolicy, false );
|
||
|
}
|
||
|
|
||
|
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.
|
||
|
|
||
|
\param angle Visible angle in degrees
|
||
|
\sa viewAngle(), setTotalAngle()
|
||
|
*/
|
||
|
void QwtWheel::setViewAngle( double angle )
|
||
|
{
|
||
|
d_data->viewAngle = qBound( 10.0, angle, 175.0 );
|
||
|
update();
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
\return Visible portion of the wheel
|
||
|
\sa setViewAngle(), totalAngle()
|
||
|
*/
|
||
|
double QwtWheel::viewAngle() const
|
||
|
{
|
||
|
return d_data->viewAngle;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
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 double range = d_data->maximum - d_data->minimum;
|
||
|
|
||
|
if ( range == 0.0 || d_data->totalAngle == 0.0 )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
|
||
|
// draw one point over the border if border > 1
|
||
|
if ( d_data->wheelBorderWidth > 1 )
|
||
|
{
|
||
|
l1--;
|
||
|
l2++;
|
||
|
}
|
||
|
|
||
|
const double maxpos = rect.right() - 2;
|
||
|
const double minpos = rect.left() + 2;
|
||
|
|
||
|
// draw tick marks
|
||
|
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;
|
||
|
|
||
|
if ( d_data->wheelBorderWidth > 1 )
|
||
|
{
|
||
|
l1--;
|
||
|
l2++;
|
||
|
}
|
||
|
|
||
|
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 )
|
||
|
{
|
||
|
d_data->wheelWidth = width;
|
||
|
update();
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
\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 )
|
||
|
{
|
||
|
d_data->singleStep = qMax( stepSize, 0.0 );
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
\return Single step size
|
||
|
\sa setSingleStep()
|
||
|
*/
|
||
|
double QwtWheel::singleStep() const
|
||
|
{
|
||
|
return d_data->singleStep;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
\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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
\return True, when the step alignment is enabled
|
||
|
\sa setStepAlignment(), singleStep()
|
||
|
*/
|
||
|
bool QwtWheel::stepAlignment() const
|
||
|
{
|
||
|
return d_data->stepAlignment;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
\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 )
|
||
|
{
|
||
|
d_data->pageStepCount = qMax( 0, count );
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
\return Page step count
|
||
|
\sa setPageStepCount(), singleStep()
|
||
|
*/
|
||
|
int QwtWheel::pageStepCount() const
|
||
|
{
|
||
|
return d_data->pageStepCount;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
\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 );
|
||
|
}
|
||
|
}
|
||
|
/*!
|
||
|
Set the minimum value of the range
|
||
|
|
||
|
\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 )
|
||
|
{
|
||
|
setRange( value, maximum() );
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
\return The minimum of the range
|
||
|
\sa setRange(), setMinimum(), maximum()
|
||
|
*/
|
||
|
double QwtWheel::minimum() const
|
||
|
{
|
||
|
return d_data->minimum;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
Set the maximum value of the range
|
||
|
|
||
|
\param value Maximum value
|
||
|
\sa setRange(), setMinimum(), maximum()
|
||
|
*/
|
||
|
void QwtWheel::setMaximum( double value )
|
||
|
{
|
||
|
setRange( minimum(), value );
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
\return The maximum of the range
|
||
|
\sa setRange(), setMaximum(), minimum()
|
||
|
*/
|
||
|
double QwtWheel::maximum() const
|
||
|
{
|
||
|
return d_data->maximum;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
\brief Set a new value without adjusting to the step raster
|
||
|
|
||
|
\param value New value
|
||
|
|
||
|
\sa value(), valueChanged()
|
||
|
\warning The value is clipped when it lies outside the range.
|
||
|
*/
|
||
|
void QwtWheel::setValue( double value )
|
||
|
{
|
||
|
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 );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
\return Current value of the wheel
|
||
|
\sa setValue(), valueChanged()
|
||
|
*/
|
||
|
double QwtWheel::value() const
|
||
|
{
|
||
|
return d_data->value;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
\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 )
|
||
|
{
|
||
|
if ( d_data->inverted != on )
|
||
|
{
|
||
|
d_data->inverted = on;
|
||
|
update();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
\return True, when the wheel is inverted
|
||
|
\sa setInverted()
|
||
|
*/
|
||
|
bool QwtWheel::isInverted() const
|
||
|
{
|
||
|
return d_data->inverted;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
\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()
|
||
|
*/
|
||
|
void QwtWheel::setMass( double mass )
|
||
|
{
|
||
|
if ( mass < 0.001 )
|
||
|
{
|
||
|
d_data->mass = 0.0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
d_data->mass = qMin( 100.0, mass );
|
||
|
}
|
||
|
|
||
|
if ( d_data->mass <= 0.0 )
|
||
|
stopFlying();
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
\return mass
|
||
|
\sa setMass()
|
||
|
*/
|
||
|
double QwtWheel::mass() const
|
||
|
{
|
||
|
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;
|
||
|
}
|
||
|
|