QGIS API Documentation 3.39.0-Master (47f7b3a4989)
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
21#include "qgsspinbox.h"
22#include "qgsexpression.h"
23#include "qgsapplication.h"
24#include "qgslogger.h"
25#include "qgsfilterlineedit.h"
26
27#define CLEAR_ICON_SIZE 16
28
29// This is required because private implementation of
30// QAbstractSpinBoxPrivate checks for specialText emptiness
31// and skips specialText handling if it's empty
32#ifdef _MSC_VER
33static QChar SPECIAL_TEXT_WHEN_EMPTY = QChar( 0x2063 );
34#else
35static constexpr QChar SPECIAL_TEXT_WHEN_EMPTY = QChar( 0x2063 );
36#endif
37
38QgsSpinBox::QgsSpinBox( QWidget *parent )
39 : QSpinBox( parent )
40{
41 mLineEdit = new QgsSpinBoxLineEdit();
42 setLineEdit( mLineEdit );
43
44 const QSize msz = minimumSizeHint();
45 setMinimumSize( msz.width() + CLEAR_ICON_SIZE + 9 + frameWidth() * 2 + 2,
46 std::max( msz.height(), CLEAR_ICON_SIZE + frameWidth() * 2 + 2 ) );
47
48 connect( mLineEdit, &QgsFilterLineEdit::cleared, this, &QgsSpinBox::clear );
49 connect( this, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsSpinBox::changed );
50}
51
52void QgsSpinBox::setShowClearButton( const bool showClearButton )
53{
54 mShowClearButton = showClearButton;
55 mLineEdit->setShowClearButton( showClearButton );
56}
57
58void QgsSpinBox::setExpressionsEnabled( const bool enabled )
59{
60 mExpressionsEnabled = enabled;
61}
62
63void QgsSpinBox::changeEvent( QEvent *event )
64{
65 QSpinBox::changeEvent( event );
66
67 if ( event->type() == QEvent::FontChange )
68 {
69 lineEdit()->setFont( font() );
70 }
71
72 mLineEdit->setShowClearButton( shouldShowClearForValue( value() ) );
73}
74
75void QgsSpinBox::paintEvent( QPaintEvent *event )
76{
77 mLineEdit->setShowClearButton( shouldShowClearForValue( value() ) );
78 QSpinBox::paintEvent( event );
79}
80
81void QgsSpinBox::wheelEvent( QWheelEvent *event )
82{
83 const int step = singleStep();
84 if ( event->modifiers() & Qt::ControlModifier )
85 {
86 // ctrl modifier results in finer increments - 10% of usual step
87 int newStep = step / 10;
88 // step should be at least 1
89 newStep = std::max( newStep, 1 );
90
91 setSingleStep( newStep );
92
93 // clear control modifier before handing off event - Qt uses it for unwanted purposes
94 // (*increasing* step size, whereas QGIS UX convention is that control modifier
95 // results in finer changes!)
96 event->setModifiers( event->modifiers() & ~Qt::ControlModifier );
97 }
98 QSpinBox::wheelEvent( event );
99 setSingleStep( step );
100}
101
102void QgsSpinBox::timerEvent( QTimerEvent *event )
103{
104 // Process all events, which may include a mouse release event
105 // Only allow the timer to trigger additional value changes if the user
106 // has in fact held the mouse button, rather than the timer expiry
107 // simply appearing before the mouse release in the event queue
108 qApp->processEvents();
109 if ( QApplication::mouseButtons() & Qt::LeftButton )
110 QSpinBox::timerEvent( event );
111}
112
113void QgsSpinBox::changed( int value )
114{
115 mLineEdit->setShowClearButton( shouldShowClearForValue( value ) );
116}
117
119{
120 setValue( clearValue() );
121 if ( mLineEdit->isNull() )
122 mLineEdit->clear();
123}
124
125void QgsSpinBox::setClearValue( int customValue, const QString &specialValueText )
126{
127 if ( mClearValueMode == CustomValue && mCustomClearValue == customValue && QAbstractSpinBox::specialValueText() == specialValueText )
128 {
129 return;
130 }
131
132 mClearValueMode = CustomValue;
133 mCustomClearValue = customValue;
134
135 if ( !specialValueText.isEmpty() )
136 {
137 const int v = value();
138 clear();
139 setSpecialValueText( specialValueText );
140 setValue( v );
141 }
142}
143
144void QgsSpinBox::setClearValueMode( QgsSpinBox::ClearValueMode mode, const QString &specialValueText )
145{
146 if ( mClearValueMode == mode && mCustomClearValue == 0 && QAbstractSpinBox::specialValueText() == specialValueText )
147 {
148 return;
149 }
150
151 mClearValueMode = mode;
152 mCustomClearValue = 0;
153
154 if ( !specialValueText.isEmpty() )
155 {
156 const int v = value();
157 clear();
158 setSpecialValueText( specialValueText );
159 setValue( v );
160 }
161}
162
164{
165 if ( mClearValueMode == MinimumValue )
166 return minimum();
167 else if ( mClearValueMode == MaximumValue )
168 return maximum();
169 else
170 return mCustomClearValue;
171}
172
173void QgsSpinBox::setLineEditAlignment( Qt::Alignment alignment )
174{
175 mLineEdit->setAlignment( alignment );
176}
177
178void QgsSpinBox::setSpecialValueText( const QString &txt )
179{
180 if ( txt.isEmpty() )
181 {
182 QSpinBox::setSpecialValueText( SPECIAL_TEXT_WHEN_EMPTY );
183 mLineEdit->setNullValue( SPECIAL_TEXT_WHEN_EMPTY );
184 }
185 else
186 {
187 QSpinBox::setSpecialValueText( txt );
188 mLineEdit->setNullValue( txt );
189 }
190}
191
192int QgsSpinBox::valueFromText( const QString &text ) const
193{
194 if ( !mExpressionsEnabled )
195 {
196 return QSpinBox::valueFromText( text );
197 }
198
199 const QString trimmedText = stripped( text );
200 if ( trimmedText.isEmpty() )
201 {
202 return mShowClearButton ? clearValue() : value();
203 }
204
205 return std::round( QgsExpression::evaluateToDouble( trimmedText, value() ) );
206}
207
208QValidator::State QgsSpinBox::validate( QString &input, int &pos ) const
209{
210 if ( !mExpressionsEnabled )
211 {
212 const QValidator::State r = QSpinBox::validate( input, pos );
213 return r;
214 }
215
216 return QValidator::Acceptable;
217}
218
219void QgsSpinBox::stepBy( int steps )
220{
221 const bool wasNull = mShowClearButton && value() == clearValue();
222 if ( wasNull && minimum() < 0 && maximum() > 0 )
223 {
224 // value is currently null, and range allows both positive and negative numbers
225 // in this case we DON'T do the default step, as that would add one step to the NULL value,
226 // which is usually the minimum acceptable value... so the user will get a very large negative number!
227 // Instead, treat the initial value as 0 instead, and then perform the step.
228 whileBlocking( this )->setValue( 0 );
229 }
230 QSpinBox::stepBy( steps );
231}
232
233int QgsSpinBox::frameWidth() const
234{
235 return style()->pixelMetric( QStyle::PM_DefaultFrameWidth );
236}
237
238bool QgsSpinBox::shouldShowClearForValue( const int value ) const
239{
240 if ( !mShowClearButton || !isEnabled() )
241 {
242 return false;
243 }
244 return value != clearValue();
245}
246
247QString QgsSpinBox::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}
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.
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:55
void wheelEvent(QWheelEvent *event) override
void setShowClearButton(bool showClearButton)
Sets whether the widget will show a clear button.
QgsSpinBox(QWidget *parent=nullptr)
Constructor for QgsSpinBox.
QValidator::State validate(QString &input, int &pos) const override
void stepBy(int steps) override
void paintEvent(QPaintEvent *event) override
bool clearValue
Definition qgsspinbox.h:56
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 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:5369
#define CLEAR_ICON_SIZE