QGIS API Documentation 3.99.0-Master (2fe06baccd8)
Loading...
Searching...
No Matches
qgsspinbox.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsspinbox.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 "qgsspinbox.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_qgsspinbox.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
42QgsSpinBox::QgsSpinBox( QWidget *parent )
43 : QSpinBox( parent )
44{
45 mLineEdit = new QgsSpinBoxLineEdit();
46 connect( mLineEdit, &QLineEdit::returnPressed, this, &QgsSpinBox::returnPressed );
47 connect( mLineEdit, &QLineEdit::textEdited, this, &QgsSpinBox::textEdited );
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, &QgsSpinBox::clear );
54 connect( this, static_cast<void ( QSpinBox::* )( int )>( &QSpinBox::valueChanged ), this, &QgsSpinBox::changed );
55
56 mLastEditTimer = new QTimer( this );
57 mLastEditTimer->setSingleShot( true );
58 mLastEditTimer->setInterval( 1000 );
59 connect( mLastEditTimer, &QTimer::timeout, this, &QgsSpinBox::onLastEditTimeout );
60}
61
63{
64 mShowClearButton = showClearButton;
65 mLineEdit->setShowClearButton( showClearButton );
66}
67
68void QgsSpinBox::setExpressionsEnabled( const bool enabled )
69{
70 mExpressionsEnabled = enabled;
71}
72
73void QgsSpinBox::changeEvent( QEvent *event )
74{
75 QSpinBox::changeEvent( event );
76
77 if ( event->type() == QEvent::FontChange )
78 {
79 lineEdit()->setFont( font() );
80 }
81
82 mLineEdit->setShowClearButton( shouldShowClearForValue( value() ) );
83}
84
85void QgsSpinBox::paintEvent( QPaintEvent *event )
86{
87 mLineEdit->setShowClearButton( shouldShowClearForValue( value() ) );
88 QSpinBox::paintEvent( event );
89}
90
91void QgsSpinBox::wheelEvent( QWheelEvent *event )
92{
93 const int step = singleStep();
94 if ( event->modifiers() & Qt::ControlModifier )
95 {
96 // ctrl modifier results in finer increments - 10% of usual step
97 int newStep = step / 10;
98 // step should be at least 1
99 newStep = std::max( newStep, 1 );
100
101 setSingleStep( newStep );
102
103 // clear control modifier before handing off event - Qt uses it for unwanted purposes
104 // (*increasing* step size, whereas QGIS UX convention is that control modifier
105 // results in finer changes!)
106 event->setModifiers( event->modifiers() & ~Qt::ControlModifier );
107 }
108 QSpinBox::wheelEvent( event );
109 setSingleStep( step );
110}
111
112void QgsSpinBox::timerEvent( QTimerEvent *event )
113{
114 // Process all events, which may include a mouse release event
115 // Only allow the timer to trigger additional value changes if the user
116 // has in fact held the mouse button, rather than the timer expiry
117 // simply appearing before the mouse release in the event queue
118 qApp->processEvents();
119 if ( QApplication::mouseButtons() & Qt::LeftButton )
120 QSpinBox::timerEvent( event );
121}
122
123void QgsSpinBox::focusOutEvent( QFocusEvent *event )
124{
125 QSpinBox::focusOutEvent( event );
126 onLastEditTimeout();
127}
128
129void QgsSpinBox::changed( int value )
130{
131 mLineEdit->setShowClearButton( shouldShowClearForValue( value ) );
132 mLastEditTimer->start();
133}
134
135void QgsSpinBox::onLastEditTimeout()
136{
137 mLastEditTimer->stop();
138 const int currentValue = value();
139 if ( !mHasEmittedEditTimeout || mLastEditTimeoutValue != currentValue )
140 {
141 mHasEmittedEditTimeout = true;
142 mLastEditTimeoutValue = currentValue;
143 emit editingTimeout( mLastEditTimeoutValue );
144 }
145}
146
148{
149 setValue( clearValue() );
150 if ( mLineEdit->isNull() )
151 mLineEdit->clear();
152}
153
154void QgsSpinBox::setClearValue( int customValue, const QString &specialValueText )
155{
156 if ( mClearValueMode == CustomValue && mCustomClearValue == customValue && QAbstractSpinBox::specialValueText() == specialValueText )
157 {
158 return;
159 }
160
161 mClearValueMode = CustomValue;
162 mCustomClearValue = customValue;
163
164 if ( !specialValueText.isEmpty() )
165 {
166 const int v = value();
167 clear();
168 setSpecialValueText( specialValueText );
169 setValue( v );
170 }
171}
172
173void QgsSpinBox::setClearValueMode( QgsSpinBox::ClearValueMode mode, const QString &specialValueText )
174{
175 if ( mClearValueMode == mode && mCustomClearValue == 0 && QAbstractSpinBox::specialValueText() == specialValueText )
176 {
177 return;
178 }
179
180 mClearValueMode = mode;
181 mCustomClearValue = 0;
182
183 if ( !specialValueText.isEmpty() )
184 {
185 const int v = value();
186 clear();
187 setSpecialValueText( specialValueText );
188 setValue( v );
189 }
190}
191
193{
194 if ( mClearValueMode == MinimumValue )
195 return minimum();
196 else if ( mClearValueMode == MaximumValue )
197 return maximum();
198 else
199 return mCustomClearValue;
200}
201
202void QgsSpinBox::setLineEditAlignment( Qt::Alignment alignment )
203{
204 mLineEdit->setAlignment( alignment );
205}
206
207void QgsSpinBox::setSpecialValueText( const QString &txt )
208{
209 if ( txt.isEmpty() )
210 {
211 QSpinBox::setSpecialValueText( SPECIAL_TEXT_WHEN_EMPTY );
212 mLineEdit->setNullValue( SPECIAL_TEXT_WHEN_EMPTY );
213 }
214 else
215 {
216 QSpinBox::setSpecialValueText( txt );
217 mLineEdit->setNullValue( txt );
218 }
219}
220
221int QgsSpinBox::valueFromText( const QString &text ) const
222{
223 if ( !mExpressionsEnabled )
224 {
225 return QSpinBox::valueFromText( text );
226 }
227
228 const QString trimmedText = stripped( text );
229 if ( trimmedText.isEmpty() )
230 {
231 return mShowClearButton ? clearValue() : value();
232 }
233
234 return std::round( QgsExpression::evaluateToDouble( trimmedText, value() ) );
235}
236
237QValidator::State QgsSpinBox::validate( QString &input, int &pos ) const
238{
239 if ( !mExpressionsEnabled )
240 {
241 const QValidator::State r = QSpinBox::validate( input, pos );
242 return r;
243 }
244
245 return QValidator::Acceptable;
246}
247
248void QgsSpinBox::stepBy( int steps )
249{
250 const bool wasNull = mShowClearButton && value() == clearValue();
251 if ( wasNull && minimum() < 0 && maximum() > 0 && !( specialValueText().isEmpty() || specialValueText() == SPECIAL_TEXT_WHEN_EMPTY ) )
252 {
253 // value is currently null, and range allows both positive and negative numbers
254 // in this case we DON'T do the default step, as that would add one step to the NULL value,
255 // which is usually the minimum acceptable value... so the user will get a very large negative number!
256 // Instead, treat the initial value as 0 instead, and then perform the step.
257 whileBlocking( this )->setValue( 0 );
258 }
259 QSpinBox::stepBy( steps );
260}
261
263{
264 return mLastEditTimer->interval();
265}
266
268{
269 mLastEditTimer->setInterval( timeout );
270}
271
272int QgsSpinBox::frameWidth() const
273{
274 return style()->pixelMetric( QStyle::PM_DefaultFrameWidth );
275}
276
277bool QgsSpinBox::shouldShowClearForValue( const int value ) const
278{
279 if ( !mShowClearButton || !isEnabled() )
280 {
281 return false;
282 }
283 return value != clearValue();
284}
285
286QString QgsSpinBox::stripped( const QString &originalText ) const
287{
288 //adapted from QAbstractSpinBoxPrivate::stripped
289 //trims whitespace, prefix and suffix from spin box text
290 QString text = originalText;
291 if ( specialValueText().isEmpty() || text != specialValueText() )
292 {
293 // Strip SPECIAL_TEXT_WHEN_EMPTY
294 if ( text.contains( SPECIAL_TEXT_WHEN_EMPTY ) )
295 text = text.replace( SPECIAL_TEXT_WHEN_EMPTY, QString() );
296 int from = 0;
297 int size = text.size();
298 bool changed = false;
299 if ( !prefix().isEmpty() && text.startsWith( prefix() ) )
300 {
301 from += prefix().size();
302 size -= from;
303 changed = true;
304 }
305 if ( !suffix().isEmpty() && text.endsWith( suffix() ) )
306 {
307 size -= suffix().size();
308 changed = true;
309 }
310 if ( changed )
311 text = text.mid( from, size );
312 }
313
314 text = text.trimmed();
315
316 return text;
317}
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.
void setLineEditAlignment(Qt::Alignment alignment)
Set alignment in the embedded line edit widget.
void returnPressed()
Emitted when the Return or Enter key is used in the line edit.
ClearValueMode
Behavior when widget is cleared.
Definition qgsspinbox.h:63
@ MaximumValue
Reset value to maximum().
Definition qgsspinbox.h:65
@ MinimumValue
Reset value to minimum().
Definition qgsspinbox.h:64
@ CustomValue
Reset value to custom value (see setClearValue() ).
Definition qgsspinbox.h:66
bool showClearButton
Definition qgsspinbox.h:56
void wheelEvent(QWheelEvent *event) override
void editingTimeout(int value)
Emitted when either:
void setShowClearButton(bool showClearButton)
Sets whether the widget will show a clear button.
void focusOutEvent(QFocusEvent *event) override
QgsSpinBox(QWidget *parent=nullptr)
Constructor for QgsSpinBox.
QValidator::State validate(QString &input, int &pos) const override
void stepBy(int steps) override
void textEdited(const QString &text)
Emitted when the the value has been manually edited via line edit.
void paintEvent(QPaintEvent *event) override
bool clearValue
Definition qgsspinbox.h:57
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.
int valueFromText(const QString &text) const override
void setEditingTimeoutInterval(int timeout)
Sets the timeout (in milliseconds) threshold for the editingTimeout() signal to be emitted after an e...
int editingTimeoutInterval() const
Returns the timeout (in milliseconds) threshold for the editingTimeout() signal to be emitted after a...
void timerEvent(QTimerEvent *event) override
void setClearValue(int customValue, const QString &clearValueText=QString())
Defines the clear value as a custom value and will automatically set the clear value mode to CustomVa...
void setExpressionsEnabled(bool enabled)
Sets if the widget will allow entry of simple expressions, which are evaluated and then discarded.
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 changeEvent(QEvent *event) override
void clear() override
Sets the current value to the value defined by the clear value.
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