QGIS API Documentation 3.41.0-Master (cea29feecf2)
Loading...
Searching...
No Matches
qgsdoublespinbox.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsdoublespinbox.cpp
3 --------------------------------------
4 Date : 09.2014
5 Copyright : (C) 2014 Denis Rouzaud
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include <QLineEdit>
17#include <QMouseEvent>
18#include <QSettings>
19#include <QStyle>
20#include <QTimer>
21
22#include "qgsdoublespinbox.h"
23#include "moc_qgsdoublespinbox.cpp"
24#include "qgsexpression.h"
25#include "qgsapplication.h"
26#include "qgslogger.h"
27#include "qgsfilterlineedit.h"
28
29#define CLEAR_ICON_SIZE 16
30
31// This is required because private implementation of
32// QAbstractSpinBoxPrivate checks for specialText emptiness
33// and skips specialText handling if it's empty
34#ifdef _MSC_VER
35static QChar SPECIAL_TEXT_WHEN_EMPTY = QChar( 0x2063 );
36#else
37static constexpr QChar SPECIAL_TEXT_WHEN_EMPTY = QChar( 0x2063 );
38#endif
39
40
42 : QDoubleSpinBox( parent )
43{
44 mLineEdit = new QgsSpinBoxLineEdit();
45 connect( mLineEdit, &QLineEdit::returnPressed, this, &QgsDoubleSpinBox::returnPressed );
46
47 // By default, group separator is off
48 setLineEdit( mLineEdit );
49
50 const QSize msz = minimumSizeHint();
51 setMinimumSize( msz.width() + CLEAR_ICON_SIZE + 9 + frameWidth() * 2 + 2, std::max( msz.height(), CLEAR_ICON_SIZE + frameWidth() * 2 + 2 ) );
52
53 connect( mLineEdit, &QgsFilterLineEdit::cleared, this, &QgsDoubleSpinBox::clear );
54 connect( this, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsDoubleSpinBox::changed );
55
56 mLastEditTimer = new QTimer( this );
57 mLastEditTimer->setSingleShot( true );
58 mLastEditTimer->setInterval( 1000 );
59 connect( mLastEditTimer, &QTimer::timeout, this, &QgsDoubleSpinBox::onLastEditTimeout );
60}
61
62void QgsDoubleSpinBox::setShowClearButton( const bool showClearButton )
63{
64 mShowClearButton = showClearButton;
65 mLineEdit->setShowClearButton( showClearButton );
66}
67
69{
70 mExpressionsEnabled = enabled;
71}
72
73void QgsDoubleSpinBox::changeEvent( QEvent *event )
74{
75 QDoubleSpinBox::changeEvent( event );
76
77 if ( event->type() == QEvent::FontChange )
78 {
79 lineEdit()->setFont( font() );
80 }
81
82 mLineEdit->setShowClearButton( shouldShowClearForValue( value() ) );
83}
84
85void QgsDoubleSpinBox::wheelEvent( QWheelEvent *event )
86{
87 const double step = singleStep();
88 if ( event->modifiers() & Qt::ControlModifier )
89 {
90 // ctrl modifier results in finer increments - 10% of usual step
91 double newStep = step / 10;
92 // but don't ever use an increment smaller than would be visible in the widget
93 // i.e. if showing 2 decimals, smallest increment will be 0.01
94 newStep = std::max( newStep, std::pow( 10.0, 0.0 - decimals() ) );
95
96 setSingleStep( newStep );
97
98 // clear control modifier before handing off event - Qt uses it for unwanted purposes
99 // (*increasing* step size, whereas QGIS UX convention is that control modifier
100 // results in finer changes!)
101 event->setModifiers( event->modifiers() & ~Qt::ControlModifier );
102 }
103 QDoubleSpinBox::wheelEvent( event );
104 setSingleStep( step );
105}
106
107void QgsDoubleSpinBox::focusOutEvent( QFocusEvent *event )
108{
109 QDoubleSpinBox::focusOutEvent( event );
110 onLastEditTimeout();
111}
112
113void QgsDoubleSpinBox::timerEvent( QTimerEvent *event )
114{
115 // Process all events, which may include a mouse release event
116 // Only allow the timer to trigger additional value changes if the user
117 // has in fact held the mouse button, rather than the timer expiry
118 // simply appearing before the mouse release in the event queue
119 qApp->processEvents();
120 if ( QApplication::mouseButtons() & Qt::LeftButton )
121 QDoubleSpinBox::timerEvent( event );
122}
123
124void QgsDoubleSpinBox::paintEvent( QPaintEvent *event )
125{
126 mLineEdit->setShowClearButton( shouldShowClearForValue( value() ) );
127 QDoubleSpinBox::paintEvent( event );
128}
129
131{
132 const bool wasNull = mShowClearButton && value() == clearValue();
133 if ( wasNull && minimum() < 0 && maximum() > 0 && !( specialValueText().isEmpty() || specialValueText() == SPECIAL_TEXT_WHEN_EMPTY ) )
134 {
135 // value is currently null, and range allows both positive and negative numbers
136 // in this case we DON'T do the default step, as that would add one step to the NULL value,
137 // which is usually the minimum acceptable value... so the user will get a very large negative number!
138 // Instead, treat the initial value as 0 instead, and then perform the step.
139 whileBlocking( this )->setValue( 0 );
140 }
141 QDoubleSpinBox::stepBy( steps );
142}
143
145{
146 return mLastEditTimer->interval();
147}
148
150{
151 mLastEditTimer->setInterval( timeout );
152}
153
154void QgsDoubleSpinBox::changed( double value )
155{
156 mLineEdit->setShowClearButton( shouldShowClearForValue( value ) );
157 mLastEditTimer->start();
158}
159
160void QgsDoubleSpinBox::onLastEditTimeout()
161{
162 mLastEditTimer->stop();
163 const double currentValue = value();
164 if ( std::isnan( mLastEditTimeoutValue ) || mLastEditTimeoutValue != currentValue )
165 {
166 mLastEditTimeoutValue = currentValue;
167 emit editingTimeout( mLastEditTimeoutValue );
168 }
169}
170
172{
173 setValue( clearValue() );
174 if ( mLineEdit->isNull() )
175 mLineEdit->clear();
176}
177
178void QgsDoubleSpinBox::setClearValue( double customValue, const QString &specialValueText )
179{
180 if ( mClearValueMode == CustomValue && mCustomClearValue == customValue && QAbstractSpinBox::specialValueText() == specialValueText )
181 {
182 return;
183 }
184
185 mClearValueMode = CustomValue;
186 mCustomClearValue = customValue;
187
188 if ( !specialValueText.isEmpty() )
189 {
190 const double v = value();
191 clear();
192 setSpecialValueText( specialValueText );
193 setValue( v );
194 }
195}
196
198{
199 if ( mClearValueMode == mode && mCustomClearValue == 0 && QAbstractSpinBox::specialValueText() == clearValueText )
200 {
201 return;
202 }
203
204 mClearValueMode = mode;
205 mCustomClearValue = 0;
206
207 if ( !clearValueText.isEmpty() )
208 {
209 const double v = value();
210 clear();
211 setSpecialValueText( clearValueText );
212 setValue( v );
213 }
214}
215
217{
218 if ( mClearValueMode == MinimumValue )
219 return minimum();
220 else if ( mClearValueMode == MaximumValue )
221 return maximum();
222 else
223 return mCustomClearValue;
224}
225
226void QgsDoubleSpinBox::setLineEditAlignment( Qt::Alignment alignment )
227{
228 mLineEdit->setAlignment( alignment );
229}
230
232{
233 if ( txt.isEmpty() )
234 {
235 QDoubleSpinBox::setSpecialValueText( SPECIAL_TEXT_WHEN_EMPTY );
236 mLineEdit->setNullValue( SPECIAL_TEXT_WHEN_EMPTY );
237 }
238 else
239 {
240 QDoubleSpinBox::setSpecialValueText( txt );
241 mLineEdit->setNullValue( txt );
242 }
243}
244
245QString QgsDoubleSpinBox::stripped( const QString &originalText ) const
246{
247 //adapted from QAbstractSpinBoxPrivate::stripped
248 //trims whitespace, prefix and suffix from spin box text
249 QString text = originalText;
250 if ( specialValueText().isEmpty() || text != specialValueText() )
251 {
252 // Strip SPECIAL_TEXT_WHEN_EMPTY
253 if ( text.contains( SPECIAL_TEXT_WHEN_EMPTY ) )
254 text = text.replace( SPECIAL_TEXT_WHEN_EMPTY, QString() );
255 int from = 0;
256 int size = text.size();
257 bool changed = false;
258 if ( !prefix().isEmpty() && text.startsWith( prefix() ) )
259 {
260 from += prefix().size();
261 size -= from;
262 changed = true;
263 }
264 if ( !suffix().isEmpty() && text.endsWith( suffix() ) )
265 {
266 size -= suffix().size();
267 changed = true;
268 }
269 if ( changed )
270 text = text.mid( from, size );
271 }
272
273 text = text.trimmed();
274
275 return text;
276}
277
278double QgsDoubleSpinBox::valueFromText( const QString &text ) const
279{
280 if ( !mExpressionsEnabled )
281 {
282 return QDoubleSpinBox::valueFromText( text );
283 }
284
285 const QString trimmedText = stripped( text );
286 if ( trimmedText.isEmpty() )
287 {
288 return mShowClearButton ? clearValue() : value();
289 }
290
291 return QgsExpression::evaluateToDouble( trimmedText, value() );
292}
293
294QValidator::State QgsDoubleSpinBox::validate( QString &input, int &pos ) const
295{
296 if ( !mExpressionsEnabled )
297 {
298 const QValidator::State r = QDoubleSpinBox::validate( input, pos );
299 return r;
300 }
301
302 return QValidator::Acceptable;
303}
304
305int QgsDoubleSpinBox::frameWidth() const
306{
307 return style()->pixelMetric( QStyle::PM_DefaultFrameWidth );
308}
309
310bool QgsDoubleSpinBox::shouldShowClearForValue( const double value ) const
311{
312 if ( !mShowClearButton || !isEnabled() )
313 {
314 return false;
315 }
316 return value != clearValue();
317}
318
320{
321 return value() == clearValue();
322}
void paintEvent(QPaintEvent *e) override
void wheelEvent(QWheelEvent *event) override
void setLineEditAlignment(Qt::Alignment alignment)
Set alignment in the embedded line edit widget.
void setEditingTimeoutInterval(int timeout)
Sets the timeout (in milliseconds) threshold for the editingTimeout() signal to be emitted after an e...
void stepBy(int steps) override
double valueFromText(const QString &text) const override
void setSpecialValueText(const QString &txt)
Set the special-value text to be txt If set, the spin box will display this text instead of a numeric...
void setClearValueMode(ClearValueMode mode, const QString &clearValueText=QString())
Defines if the clear value should be the minimum or maximum values of the widget or a custom value.
void editingTimeout(double value)
Emitted when either:
void focusOutEvent(QFocusEvent *event) override
void changeEvent(QEvent *event) override
void clear() override
Sets the current value to the value defined by the clear value.
ClearValueMode
Behavior when widget is cleared.
@ MaximumValue
Reset value to maximum()
@ CustomValue
Reset value to custom value (see setClearValue() )
@ MinimumValue
Reset value to minimum()
QValidator::State validate(QString &input, int &pos) const override
QgsDoubleSpinBox(QWidget *parent=nullptr)
Constructor for QgsDoubleSpinBox.
void setExpressionsEnabled(bool enabled)
Sets if the widget will allow entry of simple expressions, which are evaluated and then discarded.
int editingTimeoutInterval() const
Returns the timeout (in milliseconds) threshold for the editingTimeout() signal to be emitted after a...
void setClearValue(double customValue, const QString &clearValueText=QString())
Defines the clear value as a custom value and will automatically set the clear value mode to CustomVa...
void setShowClearButton(bool showClearButton)
Sets whether the widget will show a clear button.
void timerEvent(QTimerEvent *event) override
void returnPressed()
Emitted when the Return or Enter key is used in the line edit.
static double evaluateToDouble(const QString &text, double fallbackValue)
Attempts to evaluate a text string as an expression to a resultant double value.
void cleared()
Emitted when the widget is cleared.
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition qgis.h:5928
#define CLEAR_ICON_SIZE