Skip to content

Commit 2ee6122

Browse files
authoredSep 18, 2018
Merge pull request #7943 from elpaso/bugfix-18583-range-widget-nulls
[bugfix] Allow empty null representation in spinboxes
2 parents 5b7e60a + 0edb224 commit 2ee6122

File tree

8 files changed

+171
-23
lines changed

8 files changed

+171
-23
lines changed
 

‎python/gui/auto_generated/editorwidgets/qgsdoublespinbox.sip.in

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,13 @@ Returns the value used when clear() is called.
124124
Set alignment in the embedded line edit widget
125125

126126
:param alignment:
127+
%End
128+
129+
void setSpecialValueText( const QString &txt );
130+
%Docstring
131+
Set the special-value text to be ``txt``
132+
If set, the spin box will display this text instead of a numeric value whenever the current value
133+
is equal to minimum(). Typical use is to indicate that this choice has a special (default) meaning.
127134
%End
128135

129136
virtual double valueFromText( const QString &text ) const;

‎python/gui/auto_generated/editorwidgets/qgsspinbox.sip.in

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,13 @@ Returns the value used when clear() is called.
124124
Set alignment in the embedded line edit widget
125125

126126
:param alignment:
127+
%End
128+
129+
void setSpecialValueText( const QString &txt );
130+
%Docstring
131+
Set the special-value text to be ``txt``
132+
If set, the spin box will display this text instead of a numeric value whenever the current value
133+
is equal to minimum(). Typical use is to indicate that this choice has a special (default) meaning.
127134
%End
128135

129136
virtual int valueFromText( const QString &text ) const;

‎src/gui/editorwidgets/qgsdoublespinbox.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@
2626

2727
#define CLEAR_ICON_SIZE 16
2828

29+
// This is required because private implementation of
30+
// QAbstractSpinBoxPrivate checks for specialText emptiness
31+
// and skips specialText handling if it's empty
32+
QString QgsDoubleSpinBox::SPECIAL_TEXT_WHEN_EMPTY = QChar( 0x2063 );
33+
34+
2935
QgsDoubleSpinBox::QgsDoubleSpinBox( QWidget *parent )
3036
: QDoubleSpinBox( parent )
3137
{
@@ -140,13 +146,24 @@ void QgsDoubleSpinBox::setLineEditAlignment( Qt::Alignment alignment )
140146
mLineEdit->setAlignment( alignment );
141147
}
142148

149+
void QgsDoubleSpinBox::setSpecialValueText( const QString &txt )
150+
{
151+
if ( txt.isEmpty() )
152+
QDoubleSpinBox::setSpecialValueText( SPECIAL_TEXT_WHEN_EMPTY );
153+
else
154+
QDoubleSpinBox::setSpecialValueText( txt );
155+
}
156+
143157
QString QgsDoubleSpinBox::stripped( const QString &originalText ) const
144158
{
145159
//adapted from QAbstractSpinBoxPrivate::stripped
146160
//trims whitespace, prefix and suffix from spin box text
147161
QString text = originalText;
148162
if ( specialValueText().isEmpty() || text != specialValueText() )
149163
{
164+
// Strip SPECIAL_TEXT_WHEN_EMPTY
165+
if ( text.contains( SPECIAL_TEXT_WHEN_EMPTY ) )
166+
text = text.replace( SPECIAL_TEXT_WHEN_EMPTY, QString() );
150167
int from = 0;
151168
int size = text.size();
152169
bool changed = false;

‎src/gui/editorwidgets/qgsdoublespinbox.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,13 @@ class GUI_EXPORT QgsDoubleSpinBox : public QDoubleSpinBox
132132
*/
133133
void setLineEditAlignment( Qt::Alignment alignment );
134134

135+
/**
136+
* Set the special-value text to be \a txt
137+
* If set, the spin box will display this text instead of a numeric value whenever the current value
138+
* is equal to minimum(). Typical use is to indicate that this choice has a special (default) meaning.
139+
*/
140+
void setSpecialValueText( const QString &txt );
141+
135142
double valueFromText( const QString &text ) const override;
136143
QValidator::State validate( QString &input, int &pos ) const override;
137144
void paintEvent( QPaintEvent *e ) override;
@@ -156,6 +163,13 @@ class GUI_EXPORT QgsDoubleSpinBox : public QDoubleSpinBox
156163
bool mExpressionsEnabled = true;
157164

158165
QString stripped( const QString &originalText ) const;
166+
167+
// This is required because private implementation of
168+
// QAbstractSpinBoxPrivate checks for specialText emptiness
169+
// and skips specialText handling if it's empty
170+
static QString SPECIAL_TEXT_WHEN_EMPTY;
171+
172+
friend class TestQgsRangeWidgetWrapper;
159173
};
160174

161175
#endif // QGSDOUBLESPINBOX_H

‎src/gui/editorwidgets/qgsrangewidgetwrapper.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
#include "qgsdial.h"
2323
#include "qgsslider.h"
2424

25+
26+
2527
QgsRangeWidgetWrapper::QgsRangeWidgetWrapper( QgsVectorLayer *vl, int fieldIdx, QWidget *editor, QWidget *parent )
2628
: QgsEditorWidgetWrapper( vl, fieldIdx, editor, parent )
2729

@@ -119,7 +121,11 @@ void QgsRangeWidgetWrapper::initWidget( QWidget *editor )
119121
// Note: call setMinimum here or setValue won't work
120122
mDoubleSpinBox->setMinimum( minval );
121123
mDoubleSpinBox->setValue( minval );
122-
mDoubleSpinBox->setSpecialValueText( QgsApplication::nullRepresentation() );
124+
QgsDoubleSpinBox *doubleSpinBox( qobject_cast<QgsDoubleSpinBox *>( mDoubleSpinBox ) );
125+
if ( doubleSpinBox )
126+
doubleSpinBox->setSpecialValueText( QgsApplication::nullRepresentation() );
127+
else
128+
mDoubleSpinBox->setSpecialValueText( QgsApplication::nullRepresentation() );
123129
}
124130
mDoubleSpinBox->setMinimum( minval );
125131
mDoubleSpinBox->setMaximum( maxval );
@@ -141,7 +147,11 @@ void QgsRangeWidgetWrapper::initWidget( QWidget *editor )
141147
int stepval = step.isValid() ? step.toInt() : 1;
142148
minval -= stepval;
143149
mIntSpinBox->setValue( minval );
144-
mIntSpinBox->setSpecialValueText( QgsApplication::nullRepresentation() );
150+
QgsSpinBox *intSpinBox( qobject_cast<QgsSpinBox *>( mIntSpinBox ) );
151+
if ( intSpinBox )
152+
intSpinBox->setSpecialValueText( QgsApplication::nullRepresentation() );
153+
else
154+
mIntSpinBox->setSpecialValueText( QgsApplication::nullRepresentation() );
145155
}
146156
setupIntEditor( minval, max, step, mIntSpinBox, this );
147157
if ( config( QStringLiteral( "Suffix" ) ).isValid() )

‎src/gui/editorwidgets/qgsspinbox.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@
2626

2727
#define CLEAR_ICON_SIZE 16
2828

29+
// This is required because private implementation of
30+
// QAbstractSpinBoxPrivate checks for specialText emptiness
31+
// and skips specialText handling if it's empty
32+
QString QgsSpinBox::SPECIAL_TEXT_WHEN_EMPTY = QChar( 0x2063 );
33+
34+
2935
QgsSpinBox::QgsSpinBox( QWidget *parent )
3036
: QSpinBox( parent )
3137
{
@@ -137,6 +143,14 @@ void QgsSpinBox::setLineEditAlignment( Qt::Alignment alignment )
137143
mLineEdit->setAlignment( alignment );
138144
}
139145

146+
void QgsSpinBox::setSpecialValueText( const QString &txt )
147+
{
148+
if ( txt.isEmpty() )
149+
QSpinBox::setSpecialValueText( SPECIAL_TEXT_WHEN_EMPTY );
150+
else
151+
QSpinBox::setSpecialValueText( txt );
152+
}
153+
140154
int QgsSpinBox::valueFromText( const QString &text ) const
141155
{
142156
if ( !mExpressionsEnabled )
@@ -185,6 +199,9 @@ QString QgsSpinBox::stripped( const QString &originalText ) const
185199
QString text = originalText;
186200
if ( specialValueText().isEmpty() || text != specialValueText() )
187201
{
202+
// Strip SPECIAL_TEXT_WHEN_EMPTY
203+
if ( text.contains( SPECIAL_TEXT_WHEN_EMPTY ) )
204+
text = text.replace( SPECIAL_TEXT_WHEN_EMPTY, QString() );
188205
int from = 0;
189206
int size = text.size();
190207
bool changed = false;

‎src/gui/editorwidgets/qgsspinbox.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,13 @@ class GUI_EXPORT QgsSpinBox : public QSpinBox
132132
*/
133133
void setLineEditAlignment( Qt::Alignment alignment );
134134

135+
/**
136+
* Set the special-value text to be \a txt
137+
* If set, the spin box will display this text instead of a numeric value whenever the current value
138+
* is equal to minimum(). Typical use is to indicate that this choice has a special (default) meaning.
139+
*/
140+
void setSpecialValueText( const QString &txt );
141+
135142
int valueFromText( const QString &text ) const override;
136143
QValidator::State validate( QString &input, int &pos ) const override;
137144

@@ -156,7 +163,15 @@ class GUI_EXPORT QgsSpinBox : public QSpinBox
156163

157164
bool mExpressionsEnabled = true;
158165

166+
// This is required because private implementation of
167+
// QAbstractSpinBoxPrivate checks for specialText emptiness
168+
// and skips specialText handling if it's empty
169+
static QString SPECIAL_TEXT_WHEN_EMPTY;
170+
159171
QString stripped( const QString &originalText ) const;
172+
173+
friend class TestQgsRangeWidgetWrapper;
174+
160175
};
161176

162177
#endif // QGSSPINBOX_H

‎tests/src/gui/testqgsrangewidgetwrapper.cpp

Lines changed: 82 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@
2020
#include "qgsrangewidgetwrapper.h"
2121
#include "qgsrangeconfigdlg.h"
2222
#include "qgsdoublespinbox.h"
23+
#include "qgsspinbox.h"
2324
#include "qgsapplication.h"
2425
#include "qgslogger.h"
2526
#include "qgsvectorlayer.h"
2627
#include "qgsdataprovider.h"
28+
#include "qgsfilterlineedit.h"
2729

28-
30+
#include <QLineEdit>
2931
#include <QObject>
3032
#include <QtTest/QSignalSpy>
3133

@@ -48,14 +50,22 @@ class TestQgsRangeWidgetWrapper : public QObject
4850
void test_setDoubleRange();
4951
void test_setDoubleSmallerRange();
5052
void test_setDoubleLimits();
53+
void test_nulls();
54+
5155
private:
52-
std::unique_ptr<QgsRangeWidgetWrapper> widget; // For field 1
56+
std::unique_ptr<QgsRangeWidgetWrapper> widget0; // For field 0
57+
std::unique_ptr<QgsRangeWidgetWrapper> widget1; // For field 1
5358
std::unique_ptr<QgsRangeWidgetWrapper> widget2; // For field 2
5459
std::unique_ptr<QgsVectorLayer> vl;
5560
};
5661

5762
void TestQgsRangeWidgetWrapper::initTestCase()
5863
{
64+
// Set up the QgsSettings environment
65+
QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) );
66+
QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) );
67+
QCoreApplication::setApplicationName( QStringLiteral( "QGIS-TEST-RANGE-WIDGET" ) );
68+
5969
QgsApplication::init();
6070
QgsApplication::initQgis();
6171
}
@@ -108,9 +118,10 @@ void TestQgsRangeWidgetWrapper::init()
108118
QCOMPARE( vl->featureCount( ), ( long )3 );
109119
QgsFeature _feat1( vl->getFeature( 1 ) );
110120
QCOMPARE( _feat1, feat1 );
111-
widget = qgis::make_unique<QgsRangeWidgetWrapper>( vl.get(), 1, nullptr, nullptr );
121+
widget0 = qgis::make_unique<QgsRangeWidgetWrapper>( vl.get(), 0, nullptr, nullptr );
122+
widget1 = qgis::make_unique<QgsRangeWidgetWrapper>( vl.get(), 1, nullptr, nullptr );
112123
widget2 = qgis::make_unique<QgsRangeWidgetWrapper>( vl.get(), 2, nullptr, nullptr );
113-
QVERIFY( widget.get() );
124+
QVERIFY( widget1.get() );
114125
}
115126

116127
void TestQgsRangeWidgetWrapper::cleanup()
@@ -123,17 +134,17 @@ void TestQgsRangeWidgetWrapper::test_setDoubleRange()
123134
// See https://issues.qgis.org/issues/17878
124135
// QGIS 3 Vector Layer Fields Garbled when Clicking the Toggle Editing Icon
125136

126-
QgsDoubleSpinBox *editor = qobject_cast<QgsDoubleSpinBox *>( widget->createWidget( nullptr ) );
137+
QgsDoubleSpinBox *editor = qobject_cast<QgsDoubleSpinBox *>( widget1->createWidget( nullptr ) );
127138
QVERIFY( editor );
128-
widget->initWidget( editor );
139+
widget1->initWidget( editor );
129140
QgsDoubleSpinBox *editor2 = qobject_cast<QgsDoubleSpinBox *>( widget2->createWidget( nullptr ) );
130141
QVERIFY( editor2 );
131142
widget2->initWidget( editor2 );
132143

133144
QgsFeature feat( vl->getFeature( 1 ) );
134145
QVERIFY( feat.isValid() );
135146
QCOMPARE( feat.attribute( 1 ).toDouble(), 123.123456789 );
136-
widget->setFeature( vl->getFeature( 1 ) );
147+
widget1->setFeature( vl->getFeature( 1 ) );
137148
widget2->setFeature( vl->getFeature( 1 ) );
138149
QCOMPARE( vl->fields().at( 1 ).precision(), 9 );
139150
// Default is 0 !!! for double, really ?
@@ -151,12 +162,12 @@ void TestQgsRangeWidgetWrapper::test_setDoubleRange()
151162
QCOMPARE( editor->maximum( ), std::numeric_limits<double>::max() );
152163
QCOMPARE( editor2->maximum( ), std::numeric_limits<double>::max() );
153164

154-
widget->setFeature( vl->getFeature( 2 ) );
165+
widget1->setFeature( vl->getFeature( 2 ) );
155166
widget2->setFeature( vl->getFeature( 2 ) );
156167
QCOMPARE( editor->value( ), editor->minimum() );
157168
QCOMPARE( editor2->value( ), editor->minimum() );
158169

159-
widget->setFeature( vl->getFeature( 3 ) );
170+
widget1->setFeature( vl->getFeature( 3 ) );
160171
widget2->setFeature( vl->getFeature( 3 ) );
161172
QCOMPARE( editor->value( ), -123.123456789 );
162173
QCOMPARE( editor2->value( ), -123.0 );
@@ -169,10 +180,10 @@ void TestQgsRangeWidgetWrapper::test_setDoubleSmallerRange()
169180
cfg.insert( QStringLiteral( "Min" ), -100.0 );
170181
cfg.insert( QStringLiteral( "Max" ), 100.0 );
171182
cfg.insert( QStringLiteral( "Step" ), 1 );
172-
widget->setConfig( cfg );
173-
QgsDoubleSpinBox *editor = qobject_cast<QgsDoubleSpinBox *>( widget->createWidget( nullptr ) );
183+
widget1->setConfig( cfg );
184+
QgsDoubleSpinBox *editor = qobject_cast<QgsDoubleSpinBox *>( widget1->createWidget( nullptr ) );
174185
QVERIFY( editor );
175-
widget->initWidget( editor );
186+
widget1->initWidget( editor );
176187

177188
widget2->setConfig( cfg );
178189
QgsDoubleSpinBox *editor2 = qobject_cast<QgsDoubleSpinBox *>( widget2->createWidget( nullptr ) );
@@ -182,7 +193,7 @@ void TestQgsRangeWidgetWrapper::test_setDoubleSmallerRange()
182193
QgsFeature feat( vl->getFeature( 1 ) );
183194
QVERIFY( feat.isValid() );
184195
QCOMPARE( feat.attribute( 1 ).toDouble(), 123.123456789 );
185-
widget->setFeature( vl->getFeature( 1 ) );
196+
widget1->setFeature( vl->getFeature( 1 ) );
186197
widget2->setFeature( vl->getFeature( 1 ) );
187198

188199
QCOMPARE( vl->fields().at( 1 ).precision(), 9 );
@@ -202,13 +213,13 @@ void TestQgsRangeWidgetWrapper::test_setDoubleSmallerRange()
202213
QCOMPARE( editor2->maximum( ), ( double )100 );
203214

204215
// NULL, NULL
205-
widget->setFeature( vl->getFeature( 2 ) );
216+
widget1->setFeature( vl->getFeature( 2 ) );
206217
widget2->setFeature( vl->getFeature( 2 ) );
207218
QCOMPARE( editor->value( ), editor->minimum() );
208219
QCOMPARE( editor2->value( ), editor2->minimum() );
209220

210221
// negative, negative
211-
widget->setFeature( vl->getFeature( 3 ) );
222+
widget1->setFeature( vl->getFeature( 3 ) );
212223
widget2->setFeature( vl->getFeature( 3 ) );
213224
// value was changed to the minimum
214225
QCOMPARE( editor->value( ), editor->minimum() );
@@ -223,10 +234,10 @@ void TestQgsRangeWidgetWrapper::test_setDoubleLimits()
223234
cfg.insert( QStringLiteral( "Min" ), std::numeric_limits<double>::lowest() );
224235
cfg.insert( QStringLiteral( "Max" ), std::numeric_limits<double>::max() );
225236
cfg.insert( QStringLiteral( "Step" ), 1 );
226-
widget->setConfig( cfg );
227-
QgsDoubleSpinBox *editor = qobject_cast<QgsDoubleSpinBox *>( widget->createWidget( nullptr ) );
237+
widget1->setConfig( cfg );
238+
QgsDoubleSpinBox *editor = qobject_cast<QgsDoubleSpinBox *>( widget1->createWidget( nullptr ) );
228239
QVERIFY( editor );
229-
widget->initWidget( editor );
240+
widget1->initWidget( editor );
230241

231242
widget2->setConfig( cfg );
232243
QgsDoubleSpinBox *editor2 = qobject_cast<QgsDoubleSpinBox *>( widget2->createWidget( nullptr ) );
@@ -241,7 +252,7 @@ void TestQgsRangeWidgetWrapper::test_setDoubleLimits()
241252
QgsFeature feat( vl->getFeature( 1 ) );
242253
QVERIFY( feat.isValid() );
243254
QCOMPARE( feat.attribute( 1 ).toDouble(), 123.123456789 );
244-
widget->setFeature( vl->getFeature( 1 ) );
255+
widget1->setFeature( vl->getFeature( 1 ) );
245256
widget2->setFeature( vl->getFeature( 1 ) );
246257

247258
QCOMPARE( vl->fields().at( 1 ).precision(), 9 );
@@ -253,20 +264,70 @@ void TestQgsRangeWidgetWrapper::test_setDoubleLimits()
253264
QCOMPARE( editor2->value( ), 123.0 );
254265

255266
// NULL, NULL
256-
widget->setFeature( vl->getFeature( 2 ) );
267+
widget1->setFeature( vl->getFeature( 2 ) );
257268
widget2->setFeature( vl->getFeature( 2 ) );
258269
QCOMPARE( editor->value( ), editor->minimum() );
259270
QCOMPARE( editor2->value( ), editor2->minimum() );
260271

261272
// negative, negative
262-
widget->setFeature( vl->getFeature( 3 ) );
273+
widget1->setFeature( vl->getFeature( 3 ) );
263274
widget2->setFeature( vl->getFeature( 3 ) );
264275
// value was changed to the minimum
265276
QCOMPARE( editor->value( ), -123.123456789 );
266277
QCOMPARE( editor2->value( ), -123.0 );
267278

268279
}
269280

281+
void TestQgsRangeWidgetWrapper::test_nulls()
282+
{
283+
284+
QgsApplication::setNullRepresentation( QString( "" ) );
285+
286+
QVariantMap cfg;
287+
cfg.insert( QStringLiteral( "Min" ), 100.00 );
288+
cfg.insert( QStringLiteral( "Max" ), 200.00 );
289+
cfg.insert( QStringLiteral( "Step" ), 1 );
290+
cfg.insert( QStringLiteral( "Precision" ), 0 );
291+
widget1->setConfig( cfg );
292+
QgsDoubleSpinBox *editor1 = qobject_cast<QgsDoubleSpinBox *>( widget1->createWidget( nullptr ) );
293+
QVERIFY( editor1 );
294+
widget1->initWidget( editor1 );
295+
// Out of range
296+
widget1->setFeature( vl->getFeature( 3 ) );
297+
QCOMPARE( editor1->value( ), editor1->minimum() );
298+
QCOMPARE( widget1->value( ), QVariant( QVariant::Double ) );
299+
widget1->setFeature( QgsFeature( vl->fields() ) );
300+
// Null
301+
QCOMPARE( editor1->value( ), editor1->minimum() );
302+
QCOMPARE( widget1->value( ), QVariant( QVariant::Double ) );
303+
QCOMPARE( editor1->mLineEdit->text(), QgsDoubleSpinBox::SPECIAL_TEXT_WHEN_EMPTY );
304+
editor1->mLineEdit->setText( QString( "151%1" ).arg( QgsDoubleSpinBox::SPECIAL_TEXT_WHEN_EMPTY ) );
305+
QCOMPARE( widget1->value( ).toInt(), 151 );
306+
editor1->mLineEdit->setText( QString( QgsDoubleSpinBox::SPECIAL_TEXT_WHEN_EMPTY ).append( QStringLiteral( "161" ) ) );
307+
QCOMPARE( widget1->value( ).toInt(), 161 );
308+
309+
310+
QgsSpinBox *editor0 = qobject_cast<QgsSpinBox *>( widget0->createWidget( nullptr ) );
311+
QVERIFY( editor0 );
312+
widget0->setConfig( cfg );
313+
widget0->initWidget( editor0 );
314+
// Out of range
315+
widget0->setFeature( vl->getFeature( 3 ) );
316+
QCOMPARE( editor0->value( ), editor0->minimum() );
317+
QCOMPARE( widget0->value( ), QVariant( QVariant::Int ) );
318+
widget0->setFeature( QgsFeature( vl->fields() ) );
319+
// Null
320+
QCOMPARE( editor0->value( ), editor0->minimum() );
321+
QCOMPARE( widget0->value( ), QVariant( QVariant::Int ) );
322+
QCOMPARE( editor0->mLineEdit->text(), QgsDoubleSpinBox::SPECIAL_TEXT_WHEN_EMPTY );
323+
324+
editor0->mLineEdit->setText( QString( "150%1" ).arg( QgsDoubleSpinBox::SPECIAL_TEXT_WHEN_EMPTY ) );
325+
QCOMPARE( widget0->value( ).toInt(), 150 );
326+
editor0->mLineEdit->setText( QString( QgsDoubleSpinBox::SPECIAL_TEXT_WHEN_EMPTY ).append( QStringLiteral( "160" ) ) );
327+
QCOMPARE( widget0->value( ).toInt(), 160 );
328+
329+
}
330+
270331

271332

272333
QGSTEST_MAIN( TestQgsRangeWidgetWrapper )

0 commit comments

Comments
 (0)
Please sign in to comment.