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