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.
785 lines
18 KiB
785 lines
18 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_arrow_button.h" |
|
#include "qwt_math.h" |
|
#include "qwt_counter.h" |
|
#include <qlayout.h> |
|
#include <qlineedit.h> |
|
#include <qvalidator.h> |
|
#include <qevent.h> |
|
#include <qstyle.h> |
|
|
|
class QwtCounter::PrivateData |
|
{ |
|
public: |
|
PrivateData(): |
|
minimum( 0.0 ), |
|
maximum( 0.0 ), |
|
singleStep( 1.0 ), |
|
isValid( false ), |
|
value( 0.0 ), |
|
wrapping( false ) |
|
{ |
|
increment[Button1] = 1; |
|
increment[Button2] = 10; |
|
increment[Button3] = 100; |
|
} |
|
|
|
QwtArrowButton *buttonDown[ButtonCnt]; |
|
QwtArrowButton *buttonUp[ButtonCnt]; |
|
QLineEdit *valueEdit; |
|
|
|
int increment[ButtonCnt]; |
|
int numButtons; |
|
|
|
double minimum; |
|
double maximum; |
|
double singleStep; |
|
|
|
bool isValid; |
|
double value; |
|
|
|
bool wrapping; |
|
}; |
|
|
|
/*! |
|
The counter is initialized with a range is set to [0.0, 1.0] with |
|
0.01 as single step size. The value is invalid. |
|
|
|
The default number of buttons is set to 2. The default increments are: |
|
\li Button 1: 1 step |
|
\li Button 2: 10 steps |
|
\li Button 3: 100 steps |
|
|
|
\param parent |
|
*/ |
|
QwtCounter::QwtCounter( QWidget *parent ): |
|
QWidget( parent ) |
|
{ |
|
initCounter(); |
|
} |
|
|
|
void QwtCounter::initCounter() |
|
{ |
|
d_data = new PrivateData; |
|
|
|
QHBoxLayout *layout = new QHBoxLayout( this ); |
|
layout->setSpacing( 0 ); |
|
layout->setMargin( 0 ); |
|
|
|
for ( int i = ButtonCnt - 1; i >= 0; i-- ) |
|
{ |
|
QwtArrowButton *btn = |
|
new QwtArrowButton( i + 1, Qt::DownArrow, this ); |
|
btn->setFocusPolicy( Qt::NoFocus ); |
|
btn->installEventFilter( this ); |
|
layout->addWidget( btn ); |
|
|
|
connect( btn, SIGNAL( released() ), SLOT( btnReleased() ) ); |
|
connect( btn, SIGNAL( clicked() ), SLOT( btnClicked() ) ); |
|
|
|
d_data->buttonDown[i] = btn; |
|
} |
|
|
|
d_data->valueEdit = new QLineEdit( this ); |
|
d_data->valueEdit->setReadOnly( false ); |
|
d_data->valueEdit->setValidator( new QDoubleValidator( d_data->valueEdit ) ); |
|
layout->addWidget( d_data->valueEdit ); |
|
|
|
connect( d_data->valueEdit, SIGNAL( editingFinished() ), |
|
SLOT( textChanged() ) ); |
|
|
|
layout->setStretchFactor( d_data->valueEdit, 10 ); |
|
|
|
for ( int i = 0; i < ButtonCnt; i++ ) |
|
{ |
|
QwtArrowButton *btn = |
|
new QwtArrowButton( i + 1, Qt::UpArrow, this ); |
|
btn->setFocusPolicy( Qt::NoFocus ); |
|
btn->installEventFilter( this ); |
|
layout->addWidget( btn ); |
|
|
|
connect( btn, SIGNAL( released() ), SLOT( btnReleased() ) ); |
|
connect( btn, SIGNAL( clicked() ), SLOT( btnClicked() ) ); |
|
|
|
d_data->buttonUp[i] = btn; |
|
} |
|
|
|
setNumButtons( 2 ); |
|
setRange( 0.0, 1.0 ); |
|
setSingleStep( 0.001 ); |
|
setValue( 0.0 ); |
|
|
|
setSizePolicy( |
|
QSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed ) ); |
|
|
|
setFocusProxy( d_data->valueEdit ); |
|
setFocusPolicy( Qt::StrongFocus ); |
|
} |
|
|
|
//! Destructor |
|
QwtCounter::~QwtCounter() |
|
{ |
|
delete d_data; |
|
} |
|
|
|
/*! |
|
Set the counter to be in valid/invalid state |
|
|
|
When the counter is set to invalid, no numbers are displayed and |
|
the buttons are disabled. |
|
|
|
\param on If true the counter will be set as valid |
|
|
|
\sa setValue(), isValid() |
|
*/ |
|
void QwtCounter::setValid( bool on ) |
|
{ |
|
if ( on != d_data->isValid ) |
|
{ |
|
d_data->isValid = on; |
|
|
|
updateButtons(); |
|
|
|
if ( d_data->isValid ) |
|
{ |
|
showNumber( value() ); |
|
Q_EMIT valueChanged( value() ); |
|
} |
|
else |
|
{ |
|
d_data->valueEdit->setText( QString::null ); |
|
} |
|
} |
|
} |
|
|
|
/*! |
|
\return True, if the value is valid |
|
\sa setValid(), setValue() |
|
*/ |
|
bool QwtCounter::isValid() const |
|
{ |
|
return d_data->isValid; |
|
} |
|
|
|
/*! |
|
\brief Allow/disallow the user to manually edit the value |
|
|
|
\param on True disable editing |
|
\sa isReadOnly() |
|
*/ |
|
void QwtCounter::setReadOnly( bool on ) |
|
{ |
|
d_data->valueEdit->setReadOnly( on ); |
|
} |
|
|
|
/*! |
|
\return True, when the line line edit is read only. (default is no) |
|
\sa setReadOnly() |
|
*/ |
|
bool QwtCounter::isReadOnly() const |
|
{ |
|
return d_data->valueEdit->isReadOnly(); |
|
} |
|
|
|
/*! |
|
\brief Set a new value without adjusting to the step raster |
|
|
|
The state of the counter is set to be valid. |
|
|
|
\param value New value |
|
|
|
\sa isValid(), value(), valueChanged() |
|
\warning The value is clipped when it lies outside the range. |
|
*/ |
|
|
|
void QwtCounter::setValue( double value ) |
|
{ |
|
const double vmin = qMin( d_data->minimum, d_data->maximum ); |
|
const double vmax = qMax( d_data->minimum, d_data->maximum ); |
|
|
|
value = qBound( vmin, value, vmax ); |
|
|
|
if ( !d_data->isValid || value != d_data->value ) |
|
{ |
|
d_data->isValid = true; |
|
d_data->value = value; |
|
|
|
showNumber( value ); |
|
updateButtons(); |
|
|
|
Q_EMIT valueChanged( value ); |
|
} |
|
} |
|
|
|
/*! |
|
\return Current value of the counter |
|
\sa setValue(), valueChanged() |
|
*/ |
|
double QwtCounter::value() const |
|
{ |
|
return d_data->value; |
|
} |
|
|
|
/*! |
|
\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 QwtCounter::setRange( double min, double max ) |
|
{ |
|
max = qMax( min, max ); |
|
|
|
if ( d_data->maximum == max && d_data->minimum == min ) |
|
return; |
|
|
|
d_data->minimum = min; |
|
d_data->maximum = max; |
|
|
|
setSingleStep( singleStep() ); |
|
|
|
const double value = qBound( min, d_data->value, max ); |
|
|
|
if ( value != d_data->value ) |
|
{ |
|
d_data->value = value; |
|
|
|
if ( d_data->isValid ) |
|
{ |
|
showNumber( value ); |
|
Q_EMIT valueChanged( value ); |
|
} |
|
} |
|
|
|
updateButtons(); |
|
} |
|
|
|
/*! |
|
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 QwtCounter::setMinimum( double value ) |
|
{ |
|
setRange( value, maximum() ); |
|
} |
|
|
|
/*! |
|
\return The minimum of the range |
|
\sa setRange(), setMinimum(), maximum() |
|
*/ |
|
double QwtCounter::minimum() const |
|
{ |
|
return d_data->minimum; |
|
} |
|
|
|
/*! |
|
Set the maximum value of the range |
|
|
|
\param value Maximum value |
|
\sa setRange(), setMinimum(), maximum() |
|
*/ |
|
void QwtCounter::setMaximum( double value ) |
|
{ |
|
setRange( minimum(), value ); |
|
} |
|
|
|
/*! |
|
\return The maximum of the range |
|
\sa setRange(), setMaximum(), minimum() |
|
*/ |
|
double QwtCounter::maximum() const |
|
{ |
|
return d_data->maximum; |
|
} |
|
|
|
/*! |
|
\brief Set the step size of the counter |
|
|
|
A value <= 0.0 disables stepping |
|
|
|
\param stepSize Single step size |
|
\sa singleStep() |
|
*/ |
|
void QwtCounter::setSingleStep( double stepSize ) |
|
{ |
|
d_data->singleStep = qMax( stepSize, 0.0 ); |
|
} |
|
|
|
/*! |
|
\return Single step size |
|
\sa setSingleStep() |
|
*/ |
|
double QwtCounter::singleStep() const |
|
{ |
|
return d_data->singleStep; |
|
} |
|
|
|
/*! |
|
\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 QwtCounter::setWrapping( bool on ) |
|
{ |
|
d_data->wrapping = on; |
|
} |
|
|
|
/*! |
|
\return True, when wrapping is set |
|
\sa setWrapping() |
|
*/ |
|
bool QwtCounter::wrapping() const |
|
{ |
|
return d_data->wrapping; |
|
} |
|
|
|
/*! |
|
Specify the number of buttons on each side of the label |
|
|
|
\param numButtons Number of buttons |
|
\sa numButtons() |
|
*/ |
|
void QwtCounter::setNumButtons( int numButtons ) |
|
{ |
|
if ( numButtons < 0 || numButtons > QwtCounter::ButtonCnt ) |
|
return; |
|
|
|
for ( int i = 0; i < QwtCounter::ButtonCnt; i++ ) |
|
{ |
|
if ( i < numButtons ) |
|
{ |
|
d_data->buttonDown[i]->show(); |
|
d_data->buttonUp[i]->show(); |
|
} |
|
else |
|
{ |
|
d_data->buttonDown[i]->hide(); |
|
d_data->buttonUp[i]->hide(); |
|
} |
|
} |
|
|
|
d_data->numButtons = numButtons; |
|
} |
|
|
|
/*! |
|
\return The number of buttons on each side of the widget. |
|
\sa setNumButtons() |
|
*/ |
|
int QwtCounter::numButtons() const |
|
{ |
|
return d_data->numButtons; |
|
} |
|
|
|
/*! |
|
Specify the number of steps by which the value |
|
is incremented or decremented when a specified button |
|
is pushed. |
|
|
|
\param button Button index |
|
\param numSteps Number of steps |
|
|
|
\sa incSteps() |
|
*/ |
|
void QwtCounter::setIncSteps( QwtCounter::Button button, int numSteps ) |
|
{ |
|
if ( button >= 0 && button < QwtCounter::ButtonCnt ) |
|
d_data->increment[ button ] = numSteps; |
|
} |
|
|
|
/*! |
|
\return The number of steps by which a specified button increments the value |
|
or 0 if the button is invalid. |
|
\param button Button index |
|
|
|
\sa setIncSteps() |
|
*/ |
|
int QwtCounter::incSteps( QwtCounter::Button button ) const |
|
{ |
|
if ( button >= 0 && button < QwtCounter::ButtonCnt ) |
|
return d_data->increment[ button ]; |
|
|
|
return 0; |
|
} |
|
|
|
|
|
/*! |
|
Set the number of increment steps for button 1 |
|
\param nSteps Number of steps |
|
*/ |
|
void QwtCounter::setStepButton1( int nSteps ) |
|
{ |
|
setIncSteps( QwtCounter::Button1, nSteps ); |
|
} |
|
|
|
//! returns the number of increment steps for button 1 |
|
int QwtCounter::stepButton1() const |
|
{ |
|
return incSteps( QwtCounter::Button1 ); |
|
} |
|
|
|
/*! |
|
Set the number of increment steps for button 2 |
|
\param nSteps Number of steps |
|
*/ |
|
void QwtCounter::setStepButton2( int nSteps ) |
|
{ |
|
setIncSteps( QwtCounter::Button2, nSteps ); |
|
} |
|
|
|
//! returns the number of increment steps for button 2 |
|
int QwtCounter::stepButton2() const |
|
{ |
|
return incSteps( QwtCounter::Button2 ); |
|
} |
|
|
|
/*! |
|
Set the number of increment steps for button 3 |
|
\param nSteps Number of steps |
|
*/ |
|
void QwtCounter::setStepButton3( int nSteps ) |
|
{ |
|
setIncSteps( QwtCounter::Button3, nSteps ); |
|
} |
|
|
|
//! returns the number of increment steps for button 3 |
|
int QwtCounter::stepButton3() const |
|
{ |
|
return incSteps( QwtCounter::Button3 ); |
|
} |
|
|
|
//! Set from lineedit |
|
void QwtCounter::textChanged() |
|
{ |
|
bool converted = false; |
|
|
|
const double value = d_data->valueEdit->text().toDouble( &converted ); |
|
if ( converted ) |
|
setValue( value ); |
|
} |
|
|
|
/*! |
|
Handle QEvent::PolishRequest events |
|
\param event Event |
|
\return see QWidget::event() |
|
*/ |
|
bool QwtCounter::event( QEvent *event ) |
|
{ |
|
if ( event->type() == QEvent::PolishRequest ) |
|
{ |
|
const int w = d_data->valueEdit->fontMetrics().width( "W" ) + 8; |
|
for ( int i = 0; i < ButtonCnt; i++ ) |
|
{ |
|
d_data->buttonDown[i]->setMinimumWidth( w ); |
|
d_data->buttonUp[i]->setMinimumWidth( w ); |
|
} |
|
} |
|
|
|
return QWidget::event( event ); |
|
} |
|
|
|
/*! |
|
Handle key events |
|
|
|
- Ctrl + Qt::Key_Home\n |
|
Step to minimum() |
|
- Ctrl + Qt::Key_End\n |
|
Step to maximum() |
|
- Qt::Key_Up\n |
|
Increment by incSteps(QwtCounter::Button1) |
|
- Qt::Key_Down\n |
|
Decrement by incSteps(QwtCounter::Button1) |
|
- Qt::Key_PageUp\n |
|
Increment by incSteps(QwtCounter::Button2) |
|
- Qt::Key_PageDown\n |
|
Decrement by incSteps(QwtCounter::Button2) |
|
- Shift + Qt::Key_PageUp\n |
|
Increment by incSteps(QwtCounter::Button3) |
|
- Shift + Qt::Key_PageDown\n |
|
Decrement by incSteps(QwtCounter::Button3) |
|
|
|
\param event Key event |
|
*/ |
|
void QwtCounter::keyPressEvent ( QKeyEvent *event ) |
|
{ |
|
bool accepted = true; |
|
|
|
switch ( event->key() ) |
|
{ |
|
case Qt::Key_Home: |
|
{ |
|
if ( event->modifiers() & Qt::ControlModifier ) |
|
setValue( minimum() ); |
|
else |
|
accepted = false; |
|
break; |
|
} |
|
case Qt::Key_End: |
|
{ |
|
if ( event->modifiers() & Qt::ControlModifier ) |
|
setValue( maximum() ); |
|
else |
|
accepted = false; |
|
break; |
|
} |
|
case Qt::Key_Up: |
|
{ |
|
incrementValue( d_data->increment[0] ); |
|
break; |
|
} |
|
case Qt::Key_Down: |
|
{ |
|
incrementValue( -d_data->increment[0] ); |
|
break; |
|
} |
|
case Qt::Key_PageUp: |
|
case Qt::Key_PageDown: |
|
{ |
|
int increment = d_data->increment[0]; |
|
if ( d_data->numButtons >= 2 ) |
|
increment = d_data->increment[1]; |
|
if ( d_data->numButtons >= 3 ) |
|
{ |
|
if ( event->modifiers() & Qt::ShiftModifier ) |
|
increment = d_data->increment[2]; |
|
} |
|
if ( event->key() == Qt::Key_PageDown ) |
|
increment = -increment; |
|
incrementValue( increment ); |
|
break; |
|
} |
|
default: |
|
{ |
|
accepted = false; |
|
} |
|
} |
|
|
|
if ( accepted ) |
|
{ |
|
event->accept(); |
|
return; |
|
} |
|
|
|
QWidget::keyPressEvent ( event ); |
|
} |
|
|
|
/*! |
|
Handle wheel events |
|
\param event Wheel event |
|
*/ |
|
void QwtCounter::wheelEvent( QWheelEvent *event ) |
|
{ |
|
event->accept(); |
|
|
|
if ( d_data->numButtons <= 0 ) |
|
return; |
|
|
|
int increment = d_data->increment[0]; |
|
if ( d_data->numButtons >= 2 ) |
|
{ |
|
if ( event->modifiers() & Qt::ControlModifier ) |
|
increment = d_data->increment[1]; |
|
} |
|
if ( d_data->numButtons >= 3 ) |
|
{ |
|
if ( event->modifiers() & Qt::ShiftModifier ) |
|
increment = d_data->increment[2]; |
|
} |
|
|
|
for ( int i = 0; i < d_data->numButtons; i++ ) |
|
{ |
|
if ( d_data->buttonDown[i]->geometry().contains( event->pos() ) || |
|
d_data->buttonUp[i]->geometry().contains( event->pos() ) ) |
|
{ |
|
increment = d_data->increment[i]; |
|
} |
|
} |
|
|
|
const int wheel_delta = 120; |
|
|
|
#if 1 |
|
int delta = event->delta(); |
|
if ( delta >= 2 * wheel_delta ) |
|
delta /= 2; // Never saw an abs(delta) < 240 |
|
#endif |
|
|
|
incrementValue( delta / wheel_delta * increment ); |
|
} |
|
|
|
void QwtCounter::incrementValue( int numSteps ) |
|
{ |
|
const double min = d_data->minimum; |
|
const double max = d_data->maximum; |
|
double stepSize = d_data->singleStep; |
|
|
|
if ( !d_data->isValid || min >= max || stepSize <= 0.0 ) |
|
return; |
|
|
|
|
|
#if 1 |
|
stepSize = qMax( stepSize, 1.0e-10 * ( max - min ) ); |
|
#endif |
|
|
|
double value = d_data->value + numSteps * stepSize; |
|
|
|
if ( d_data->wrapping ) |
|
{ |
|
const double range = max - min; |
|
|
|
if ( value < min ) |
|
{ |
|
value += ::ceil( ( min - value ) / range ) * range; |
|
} |
|
else if ( value > max ) |
|
{ |
|
value -= ::ceil( ( value - max ) / range ) * range; |
|
} |
|
} |
|
else |
|
{ |
|
value = qBound( min, value, max ); |
|
} |
|
|
|
value = min + qRound( ( value - min ) / 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, max ) ) |
|
{ |
|
// correct rounding error at the border |
|
value = max; |
|
} |
|
} |
|
|
|
if ( value != d_data->value ) |
|
{ |
|
d_data->value = value; |
|
showNumber( d_data->value ); |
|
updateButtons(); |
|
|
|
Q_EMIT valueChanged( d_data->value ); |
|
} |
|
} |
|
|
|
|
|
/*! |
|
\brief Update buttons according to the current value |
|
|
|
When the QwtCounter under- or over-flows, the focus is set to the smallest |
|
up- or down-button and counting is disabled. |
|
|
|
Counting is re-enabled on a button release event (mouse or space bar). |
|
*/ |
|
void QwtCounter::updateButtons() |
|
{ |
|
if ( d_data->isValid ) |
|
{ |
|
// 1. save enabled state of the smallest down- and up-button |
|
// 2. change enabled state on under- or over-flow |
|
|
|
for ( int i = 0; i < QwtCounter::ButtonCnt; i++ ) |
|
{ |
|
d_data->buttonDown[i]->setEnabled( value() > minimum() ); |
|
d_data->buttonUp[i]->setEnabled( value() < maximum() ); |
|
} |
|
} |
|
else |
|
{ |
|
for ( int i = 0; i < QwtCounter::ButtonCnt; i++ ) |
|
{ |
|
d_data->buttonDown[i]->setEnabled( false ); |
|
d_data->buttonUp[i]->setEnabled( false ); |
|
} |
|
} |
|
} |
|
/*! |
|
Display number string |
|
|
|
\param number Number |
|
*/ |
|
void QwtCounter::showNumber( double number ) |
|
{ |
|
QString text; |
|
text.setNum( number ); |
|
|
|
const int cursorPos = d_data->valueEdit->cursorPosition(); |
|
d_data->valueEdit->setText( text ); |
|
d_data->valueEdit->setCursorPosition( cursorPos ); |
|
} |
|
|
|
//! Button clicked |
|
void QwtCounter::btnClicked() |
|
{ |
|
for ( int i = 0; i < ButtonCnt; i++ ) |
|
{ |
|
if ( d_data->buttonUp[i] == sender() ) |
|
incrementValue( d_data->increment[i] ); |
|
|
|
if ( d_data->buttonDown[i] == sender() ) |
|
incrementValue( -d_data->increment[i] ); |
|
} |
|
} |
|
|
|
//! Button released |
|
void QwtCounter::btnReleased() |
|
{ |
|
Q_EMIT buttonReleased( value() ); |
|
} |
|
|
|
//! A size hint |
|
QSize QwtCounter::sizeHint() const |
|
{ |
|
QString tmp; |
|
|
|
int w = tmp.setNum( minimum() ).length(); |
|
int w1 = tmp.setNum( maximum() ).length(); |
|
if ( w1 > w ) |
|
w = w1; |
|
w1 = tmp.setNum( minimum() + singleStep() ).length(); |
|
if ( w1 > w ) |
|
w = w1; |
|
w1 = tmp.setNum( maximum() - singleStep() ).length(); |
|
if ( w1 > w ) |
|
w = w1; |
|
|
|
tmp.fill( '9', w ); |
|
|
|
QFontMetrics fm( d_data->valueEdit->font() ); |
|
w = fm.width( tmp ) + 2; |
|
if ( d_data->valueEdit->hasFrame() ) |
|
w += 2 * style()->pixelMetric( QStyle::PM_DefaultFrameWidth ); |
|
|
|
// Now we replace default sizeHint contribution of d_data->valueEdit by |
|
// what we really need. |
|
|
|
w += QWidget::sizeHint().width() - d_data->valueEdit->sizeHint().width(); |
|
|
|
const int h = qMin( QWidget::sizeHint().height(), |
|
d_data->valueEdit->minimumSizeHint().height() ); |
|
return QSize( w, h ); |
|
}
|
|
|