qt_demoe/qwtdemo/qwt/qwt_knob.cpp

856 lines
20 KiB
C++
Raw Normal View History

2019-11-07 02:55:57 +00:00
/* -*- 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_knob.h"
#include "qwt_round_scale_draw.h"
#include "qwt_math.h"
#include "qwt_painter.h"
#include "qwt_scale_map.h"
#include <qpainter.h>
#include <qpalette.h>
#include <qstyle.h>
#include <qstyleoption.h>
#include <qevent.h>
#include <qmath.h>
#include <qapplication.h>
#if QT_VERSION < 0x040601
#define qAtan2(y, x) ::atan2(y, x)
#define qFabs(x) ::fabs(x)
#define qFastCos(x) qCos(x)
#define qFastSin(x) qSin(x)
#endif
static QSize qwtKnobSizeHint( const QwtKnob *knob, int min )
{
int knobWidth = knob->knobWidth();
if ( knobWidth <= 0 )
knobWidth = qMax( 3 * knob->markerSize(), min );
// Add the scale radial thickness to the knobWidth
const int extent = qCeil( knob->scaleDraw()->extent( knob->font() ) );
const int d = 2 * ( extent + 4 ) + knobWidth;
int left, right, top, bottom;
knob->getContentsMargins( &left, &top, &right, &bottom );
return QSize( d + left + right, d + top + bottom );
}
static inline double qwtToScaleAngle( double angle )
{
// the map is counter clockwise with the origin
// at 90° using angles from -180° -> 180°
double a = 90.0 - angle;
if ( a <= -180.0 )
a += 360.0;
else if ( a >= 180.0 )
a -= 360.0;
return a;
}
static double qwtToDegrees( double value )
{
return qwtNormalizeDegrees( 90.0 - value );
}
class QwtKnob::PrivateData
{
public:
PrivateData():
knobStyle( QwtKnob::Raised ),
markerStyle( QwtKnob::Notch ),
borderWidth( 2 ),
borderDist( 4 ),
scaleDist( 4 ),
maxScaleTicks( 11 ),
knobWidth( 0 ),
alignment( Qt::AlignCenter ),
markerSize( 8 ),
totalAngle( 270.0 ),
mouseOffset( 0.0 )
{
}
QwtKnob::KnobStyle knobStyle;
QwtKnob::MarkerStyle markerStyle;
int borderWidth;
int borderDist;
int scaleDist;
int maxScaleTicks;
int knobWidth;
Qt::Alignment alignment;
int markerSize;
double totalAngle;
double mouseOffset;
};
/*!
\brief Constructor
Construct a knob with an angle of 270°. The style is
QwtKnob::Raised and the marker style is QwtKnob::Notch.
The width of the knob is set to 50 pixels.
\param parent Parent widget
\sa setTotalAngle()
*/
QwtKnob::QwtKnob( QWidget* parent ):
QwtAbstractSlider( parent )
{
d_data = new PrivateData;
setScaleDraw( new QwtRoundScaleDraw() );
setTotalAngle( 270.0 );
setScale( 0.0, 10.0 );
setValue( 0.0 );
setSizePolicy( QSizePolicy::MinimumExpanding,
QSizePolicy::MinimumExpanding );
}
//! Destructor
QwtKnob::~QwtKnob()
{
delete d_data;
}
/*!
\brief Set the knob type
\param knobStyle Knob type
\sa knobStyle(), setBorderWidth()
*/
void QwtKnob::setKnobStyle( KnobStyle knobStyle )
{
if ( d_data->knobStyle != knobStyle )
{
d_data->knobStyle = knobStyle;
update();
}
}
/*!
\return Marker type of the knob
\sa setKnobStyle(), setBorderWidth()
*/
QwtKnob::KnobStyle QwtKnob::knobStyle() const
{
return d_data->knobStyle;
}
/*!
\brief Set the marker type of the knob
\param markerStyle Marker type
\sa markerStyle(), setMarkerSize()
*/
void QwtKnob::setMarkerStyle( MarkerStyle markerStyle )
{
if ( d_data->markerStyle != markerStyle )
{
d_data->markerStyle = markerStyle;
update();
}
}
/*!
\return Marker type of the knob
\sa setMarkerStyle(), setMarkerSize()
*/
QwtKnob::MarkerStyle QwtKnob::markerStyle() const
{
return d_data->markerStyle;
}
/*!
\brief Set the total angle by which the knob can be turned
\param angle Angle in degrees.
The angle has to be between [10, 360] degrees. Angles above
360 ( so that the knob can be turned several times around its axis )
have to be set using setNumTurns().
The default angle is 270 degrees.
\sa totalAngle(), setNumTurns()
*/
void QwtKnob::setTotalAngle ( double angle )
{
angle = qBound( 10.0, angle, 360.0 );
if ( angle != d_data->totalAngle )
{
d_data->totalAngle = angle;
scaleDraw()->setAngleRange( -0.5 * d_data->totalAngle,
0.5 * d_data->totalAngle );
updateGeometry();
update();
}
}
/*!
\return the total angle
\sa setTotalAngle(), setNumTurns(), numTurns()
*/
double QwtKnob::totalAngle() const
{
return d_data->totalAngle;
}
/*!
\brief Set the number of turns
When numTurns > 1 the knob can be turned several times around its axis
- otherwise the total angle is floored to 360°.
\sa numTurns(), totalAngle(), setTotalAngle()
*/
void QwtKnob::setNumTurns( int numTurns )
{
numTurns = qMax( numTurns, 1 );
if ( numTurns == 1 && d_data->totalAngle <= 360.0 )
return;
const double angle = numTurns * 360.0;
if ( angle != d_data->totalAngle )
{
d_data->totalAngle = angle;
scaleDraw()->setAngleRange( -0.5 * d_data->totalAngle,
0.5 * d_data->totalAngle );
updateGeometry();
update();
}
}
/*!
\return Number of turns.
When the total angle is below 360° numTurns() is ceiled to 1.
\sa setNumTurns(), setTotalAngle(), totalAngle()
*/
int QwtKnob::numTurns() const
{
return qCeil( d_data->totalAngle / 360.0 );
}
/*!
Change the scale draw of the knob
For changing the labels of the scales, it
is necessary to derive from QwtRoundScaleDraw and
overload QwtRoundScaleDraw::label().
\sa scaleDraw()
*/
void QwtKnob::setScaleDraw( QwtRoundScaleDraw *scaleDraw )
{
setAbstractScaleDraw( scaleDraw );
setTotalAngle( d_data->totalAngle );
updateGeometry();
update();
}
/*!
\return the scale draw of the knob
\sa setScaleDraw()
*/
const QwtRoundScaleDraw *QwtKnob::scaleDraw() const
{
return static_cast<const QwtRoundScaleDraw *>( abstractScaleDraw() );
}
/*!
\return the scale draw of the knob
\sa setScaleDraw()
*/
QwtRoundScaleDraw *QwtKnob::scaleDraw()
{
return static_cast<QwtRoundScaleDraw *>( abstractScaleDraw() );
}
/*!
Calculate the bounding rectangle of the knob without the scale
\return Bounding rectangle of the knob
\sa knobWidth(), alignment(), QWidget::contentsRect()
*/
QRect QwtKnob::knobRect() const
{
const QRect cr = contentsRect();
const int extent = qCeil( scaleDraw()->extent( font() ) );
const int d = extent + d_data->scaleDist;
int w = d_data->knobWidth;
if ( w <= 0 )
{
const int dim = qMin( cr.width(), cr.height() );
w = dim - 2 * ( d );
w = qMax( 0, w );
}
QRect r( 0, 0, w, w );
if ( d_data->alignment & Qt::AlignLeft )
{
r.moveLeft( cr.left() + d );
}
else if ( d_data->alignment & Qt::AlignRight )
{
r.moveRight( cr.right() - d );
}
else
{
r.moveCenter( QPoint( cr.center().x(), r.center().y() ) );
}
if ( d_data->alignment & Qt::AlignTop )
{
r.moveTop( cr.top() + d );
}
else if ( d_data->alignment & Qt::AlignBottom )
{
r.moveBottom( cr.bottom() - d );
}
else
{
r.moveCenter( QPoint( r.center().x(), cr.center().y() ) );
}
return r;
}
/*!
\brief Determine what to do when the user presses a mouse button.
\param pos Mouse position
\retval True, when pos is inside the circle of the knob.
\sa scrolledTo()
*/
bool QwtKnob::isScrollPosition( const QPoint &pos ) const
{
const QRect kr = knobRect();
const QRegion region( kr, QRegion::Ellipse );
if ( region.contains( pos ) && ( pos != kr.center() ) )
{
const double angle = QLineF( kr.center(), pos ).angle();
const double valueAngle = qwtToDegrees( scaleMap().transform( value() ) );
d_data->mouseOffset = qwtNormalizeDegrees( angle - valueAngle );
return true;
}
return false;
}
/*!
\brief Determine the value for a new position of the mouse
\param pos Mouse position
\return Value for the mouse position
\sa isScrollPosition()
*/
double QwtKnob::scrolledTo( const QPoint &pos ) const
{
double angle = QLineF( rect().center(), pos ).angle();
angle = qwtNormalizeDegrees( angle - d_data->mouseOffset );
if ( scaleMap().pDist() > 360.0 )
{
angle = qwtToDegrees( angle );
const double v = scaleMap().transform( value() );
int numTurns = qFloor( ( v - scaleMap().p1() ) / 360.0 );
double valueAngle = qwtNormalizeDegrees( v );
if ( qAbs( valueAngle - angle ) > 180.0 )
{
numTurns += ( angle > valueAngle ) ? -1 : 1;
}
angle += scaleMap().p1() + numTurns * 360.0;
if ( !wrapping() )
{
const double boundedAngle =
qBound( scaleMap().p1(), angle, scaleMap().p2() );
d_data->mouseOffset += ( boundedAngle - angle );
angle = boundedAngle;
}
}
else
{
angle = qwtToScaleAngle( angle );
double boundedAngle = qBound( scaleMap().p1(), angle, scaleMap().p2() );
if ( !wrapping() )
{
const double currentAngle = scaleMap().transform( value() );
if ( ( currentAngle > 90.0 ) && ( boundedAngle < -90.0 ) )
boundedAngle = scaleMap().p2();
else if ( ( currentAngle < -90.0 ) && ( boundedAngle > 90.0 ) )
boundedAngle = scaleMap().p1();
d_data->mouseOffset += ( boundedAngle - angle );
}
angle = boundedAngle;
}
return scaleMap().invTransform( angle );
}
/*!
Handle QEvent::StyleChange and QEvent::FontChange;
\param event Change event
*/
void QwtKnob::changeEvent( QEvent *event )
{
switch( event->type() )
{
case QEvent::StyleChange:
case QEvent::FontChange:
{
updateGeometry();
update();
break;
}
default:
break;
}
}
/*!
Repaint the knob
\param event Paint event
*/
void QwtKnob::paintEvent( QPaintEvent *event )
{
const QRectF knobRect = this->knobRect();
QPainter painter( this );
painter.setClipRegion( event->region() );
QStyleOption opt;
opt.init(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);
painter.setRenderHint( QPainter::Antialiasing, true );
if ( !knobRect.contains( event->region().boundingRect() ) )
{
scaleDraw()->setRadius( 0.5 * knobRect.width() + d_data->scaleDist );
scaleDraw()->moveCenter( knobRect.center() );
scaleDraw()->draw( &painter, palette() );
}
drawKnob( &painter, knobRect );
drawMarker( &painter, knobRect,
qwtNormalizeDegrees( scaleMap().transform( value() ) ) );
painter.setRenderHint( QPainter::Antialiasing, false );
if ( hasFocus() )
drawFocusIndicator( &painter );
}
/*!
\brief Draw the knob
\param painter painter
\param knobRect Bounding rectangle of the knob (without scale)
*/
void QwtKnob::drawKnob( QPainter *painter, const QRectF &knobRect ) const
{
double dim = qMin( knobRect.width(), knobRect.height() );
dim -= d_data->borderWidth * 0.5;
QRectF aRect( 0, 0, dim, dim );
aRect.moveCenter( knobRect.center() );
QPen pen( Qt::NoPen );
if ( d_data->borderWidth > 0 )
{
QColor c1 = palette().color( QPalette::Light );
QColor c2 = palette().color( QPalette::Dark );
QLinearGradient gradient( aRect.topLeft(), aRect.bottomRight() );
gradient.setColorAt( 0.0, c1 );
gradient.setColorAt( 0.3, c1 );
gradient.setColorAt( 0.7, c2 );
gradient.setColorAt( 1.0, c2 );
pen = QPen( gradient, d_data->borderWidth );
}
QBrush brush;
switch( d_data->knobStyle )
{
case QwtKnob::Raised:
{
double off = 0.3 * knobRect.width();
QRadialGradient gradient( knobRect.center(),
knobRect.width(), knobRect.topLeft() + QPointF( off, off ) );
gradient.setColorAt( 0.0, palette().color( QPalette::Midlight ) );
gradient.setColorAt( 1.0, palette().color( QPalette::Button ) );
brush = QBrush( gradient );
break;
}
case QwtKnob::Styled:
{
QRadialGradient gradient(knobRect.center().x() - knobRect.width() / 3,
knobRect.center().y() - knobRect.height() / 2,
knobRect.width() * 1.3,
knobRect.center().x(),
knobRect.center().y() - knobRect.height() / 2);
const QColor c = palette().color( QPalette::Button );
gradient.setColorAt(0, c.lighter(110));
gradient.setColorAt(qreal(0.5), c);
gradient.setColorAt(qreal(0.501), c.darker(102));
gradient.setColorAt(1, c.darker(115));
brush = QBrush( gradient );
break;
}
case QwtKnob::Sunken:
{
QLinearGradient gradient(
knobRect.topLeft(), knobRect.bottomRight() );
gradient.setColorAt( 0.0, palette().color( QPalette::Mid ) );
gradient.setColorAt( 0.5, palette().color( QPalette::Button ) );
gradient.setColorAt( 1.0, palette().color( QPalette::Midlight ) );
brush = QBrush( gradient );
break;
}
case QwtKnob::Flat:
default:
brush = palette().brush( QPalette::Button );
}
painter->setPen( pen );
painter->setBrush( brush );
painter->drawEllipse( aRect );
}
/*!
\brief Draw the marker at the knob's front
\param painter Painter
\param rect Bounding rectangle of the knob without scale
\param angle Angle of the marker in degrees
( clockwise, 0 at the 12 o'clock position )
*/
void QwtKnob::drawMarker( QPainter *painter,
const QRectF &rect, double angle ) const
{
if ( d_data->markerStyle == NoMarker || !isValid() )
return;
const double radians = qwtRadians( angle );
const double sinA = -qFastSin( radians );
const double cosA = qFastCos( radians );
const double xm = rect.center().x();
const double ym = rect.center().y();
const double margin = 4.0;
double radius = 0.5 * ( rect.width() - d_data->borderWidth ) - margin;
if ( radius < 1.0 )
radius = 1.0;
int markerSize = d_data->markerSize;
if ( markerSize <= 0 )
markerSize = qRound( 0.4 * radius );
switch ( d_data->markerStyle )
{
case Notch:
case Nub:
{
const double dotWidth =
qMin( double( markerSize ), radius);
const double dotCenterDist = radius - 0.5 * dotWidth;
if ( dotCenterDist > 0.0 )
{
const QPointF center( xm - sinA * dotCenterDist,
ym - cosA * dotCenterDist );
QRectF ellipse( 0.0, 0.0, dotWidth, dotWidth );
ellipse.moveCenter( center );
QColor c1 = palette().color( QPalette::Light );
QColor c2 = palette().color( QPalette::Mid );
if ( d_data->markerStyle == Notch )
qSwap( c1, c2 );
QLinearGradient gradient(
ellipse.topLeft(), ellipse.bottomRight() );
gradient.setColorAt( 0.0, c1 );
gradient.setColorAt( 1.0, c2 );
painter->setPen( Qt::NoPen );
painter->setBrush( gradient );
painter->drawEllipse( ellipse );
}
break;
}
case Dot:
{
const double dotWidth =
qMin( double( markerSize ), radius);
const double dotCenterDist = radius - 0.5 * dotWidth;
if ( dotCenterDist > 0.0 )
{
const QPointF center( xm - sinA * dotCenterDist,
ym - cosA * dotCenterDist );
QRectF ellipse( 0.0, 0.0, dotWidth, dotWidth );
ellipse.moveCenter( center );
painter->setPen( Qt::NoPen );
painter->setBrush( palette().color( QPalette::ButtonText ) );
painter->drawEllipse( ellipse );
}
break;
}
case Tick:
{
const double rb = qMax( radius - markerSize, 1.0 );
const double re = radius;
const QLineF line( xm - sinA * rb, ym - cosA * rb,
xm - sinA * re, ym - cosA * re );
QPen pen( palette().color( QPalette::ButtonText ), 0 );
pen.setCapStyle( Qt::FlatCap );
painter->setPen( pen );
painter->drawLine ( line );
break;
}
case Triangle:
{
const double rb = qMax( radius - markerSize, 1.0 );
const double re = radius;
painter->translate( rect.center() );
painter->rotate( angle - 90.0 );
QPolygonF polygon;
polygon += QPointF( re, 0.0 );
polygon += QPointF( rb, 0.5 * ( re - rb ) );
polygon += QPointF( rb, -0.5 * ( re - rb ) );
painter->setPen( Qt::NoPen );
painter->setBrush( palette().color( QPalette::ButtonText ) );
painter->drawPolygon( polygon );
painter->resetTransform();
break;
}
default:
break;
}
}
/*!
Draw the focus indicator
\param painter Painter
*/
void QwtKnob::drawFocusIndicator( QPainter *painter ) const
{
const QRect cr = contentsRect();
int w = d_data->knobWidth;
if ( w <= 0 )
{
w = qMin( cr.width(), cr.height() );
}
else
{
const int extent = qCeil( scaleDraw()->extent( font() ) );
w += 2 * ( extent + d_data->scaleDist );
}
QRect focusRect( 0, 0, w, w );
focusRect.moveCenter( cr.center() );
QwtPainter::drawFocusRect( painter, this, focusRect );
}
/*!
\brief Set the alignment of the knob
Similar to a QLabel::alignment() the flags decide how
to align the knob inside of contentsRect().
The default setting is Qt::AlignCenter
\param alignment Or'd alignment flags
\sa alignment(), setKnobWidth(), knobRect()
*/
void QwtKnob::setAlignment( Qt::Alignment alignment )
{
if ( d_data->alignment != alignment )
{
d_data->alignment = alignment;
update();
}
}
/*!
\return Alignment of the knob inside of contentsRect()
\sa setAlignment(), knobWidth(), knobRect()
*/
Qt::Alignment QwtKnob::alignment() const
{
return d_data->alignment;
}
/*!
\brief Change the knob's width.
Setting a fixed value for the diameter of the knob
is helpful for aligning several knobs in a row.
\param width New width
\sa knobWidth(), setAlignment()
\note Modifies the sizePolicy()
*/
void QwtKnob::setKnobWidth( int width )
{
width = qMax( width, 0 );
if ( width != d_data->knobWidth )
{
QSizePolicy::Policy policy;
if ( width > 0 )
policy = QSizePolicy::Minimum;
else
policy = QSizePolicy::MinimumExpanding;
setSizePolicy( policy, policy );
d_data->knobWidth = width;
updateGeometry();
update();
}
}
//! Return the width of the knob
int QwtKnob::knobWidth() const
{
return d_data->knobWidth;
}
/*!
\brief Set the knob's border width
\param borderWidth new border width
*/
void QwtKnob::setBorderWidth( int borderWidth )
{
d_data->borderWidth = qMax( borderWidth, 0 );
updateGeometry();
update();
}
//! Return the border width
int QwtKnob::borderWidth() const
{
return d_data->borderWidth;
}
/*!
\brief Set the size of the marker
When setting a size <= 0 the marker will
automatically scaled to 40% of the radius of the knob.
\sa markerSize(), markerStyle()
*/
void QwtKnob::setMarkerSize( int size )
{
if ( d_data->markerSize != size )
{
d_data->markerSize = size;
update();
}
}
/*!
\return Marker size
\sa setMarkerSize()
*/
int QwtKnob::markerSize() const
{
return d_data->markerSize;
}
/*!
\return sizeHint()
*/
QSize QwtKnob::sizeHint() const
{
const QSize hint = qwtKnobSizeHint( this, 50 );
return hint.expandedTo( QApplication::globalStrut() );
}
/*!
\return Minimum size hint
\sa sizeHint()
*/
QSize QwtKnob::minimumSizeHint() const
{
return qwtKnobSizeHint( this, 20 );
}