You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1299 lines
29 KiB
1299 lines
29 KiB
/* -*- 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; |
|
} |
|
|
|
|