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