[bugfix] Allow empty null representation in spinboxes

Fixes #18583 - Widget "Range": Representation for NULL value is NOT always displayed correctly
This commit is contained in:
Alessandro Pasotti 2018-09-18 12:54:17 +02:00
parent 5c15a6589d
commit d179635b92
8 changed files with 171 additions and 23 deletions

View File

@ -124,6 +124,13 @@ Returns the value used when clear() is called.
Set alignment in the embedded line edit widget
:param alignment:
%End
void setSpecialValueText( const QString &txt );
%Docstring
Set the special-value text to be ``txt``
If set, the spin box will display this text instead of a numeric value whenever the current value
is equal to minimum(). Typical use is to indicate that this choice has a special (default) meaning.
%End
virtual double valueFromText( const QString &text ) const;

View File

@ -124,6 +124,13 @@ Returns the value used when clear() is called.
Set alignment in the embedded line edit widget
:param alignment:
%End
void setSpecialValueText( const QString &txt );
%Docstring
Set the special-value text to be ``txt``
If set, the spin box will display this text instead of a numeric value whenever the current value
is equal to minimum(). Typical use is to indicate that this choice has a special (default) meaning.
%End
virtual int valueFromText( const QString &text ) const;

View File

@ -26,6 +26,12 @@
#define CLEAR_ICON_SIZE 16
// This is required because private implementation of
// QAbstractSpinBoxPrivate checks for specialText emptiness
// and skips specialText handling if it's empty
QString QgsDoubleSpinBox::SPECIAL_TEXT_WHEN_EMPTY = QChar( 0x2063 );
QgsDoubleSpinBox::QgsDoubleSpinBox( QWidget *parent )
: QDoubleSpinBox( parent )
{
@ -140,6 +146,14 @@ void QgsDoubleSpinBox::setLineEditAlignment( Qt::Alignment alignment )
mLineEdit->setAlignment( alignment );
}
void QgsDoubleSpinBox::setSpecialValueText( const QString &txt )
{
if ( txt.isEmpty() )
QDoubleSpinBox::setSpecialValueText( SPECIAL_TEXT_WHEN_EMPTY );
else
QDoubleSpinBox::setSpecialValueText( txt );
}
QString QgsDoubleSpinBox::stripped( const QString &originalText ) const
{
//adapted from QAbstractSpinBoxPrivate::stripped
@ -147,6 +161,9 @@ QString QgsDoubleSpinBox::stripped( const QString &originalText ) const
QString text = originalText;
if ( specialValueText().isEmpty() || text != specialValueText() )
{
// Strip SPECIAL_TEXT_WHEN_EMPTY
if ( text.contains( SPECIAL_TEXT_WHEN_EMPTY ) )
text = text.replace( SPECIAL_TEXT_WHEN_EMPTY, QString() );
int from = 0;
int size = text.size();
bool changed = false;

View File

@ -132,6 +132,13 @@ class GUI_EXPORT QgsDoubleSpinBox : public QDoubleSpinBox
*/
void setLineEditAlignment( Qt::Alignment alignment );
/**
* Set the special-value text to be \a txt
* If set, the spin box will display this text instead of a numeric value whenever the current value
* is equal to minimum(). Typical use is to indicate that this choice has a special (default) meaning.
*/
void setSpecialValueText( const QString &txt );
double valueFromText( const QString &text ) const override;
QValidator::State validate( QString &input, int &pos ) const override;
void paintEvent( QPaintEvent *e ) override;
@ -156,6 +163,13 @@ class GUI_EXPORT QgsDoubleSpinBox : public QDoubleSpinBox
bool mExpressionsEnabled = true;
QString stripped( const QString &originalText ) const;
// This is required because private implementation of
// QAbstractSpinBoxPrivate checks for specialText emptiness
// and skips specialText handling if it's empty
static QString SPECIAL_TEXT_WHEN_EMPTY;
friend class TestQgsRangeWidgetWrapper;
};
#endif // QGSDOUBLESPINBOX_H

View File

@ -22,6 +22,8 @@
#include "qgsdial.h"
#include "qgsslider.h"
QgsRangeWidgetWrapper::QgsRangeWidgetWrapper( QgsVectorLayer *vl, int fieldIdx, QWidget *editor, QWidget *parent )
: QgsEditorWidgetWrapper( vl, fieldIdx, editor, parent )
@ -119,7 +121,11 @@ void QgsRangeWidgetWrapper::initWidget( QWidget *editor )
// Note: call setMinimum here or setValue won't work
mDoubleSpinBox->setMinimum( minval );
mDoubleSpinBox->setValue( minval );
mDoubleSpinBox->setSpecialValueText( QgsApplication::nullRepresentation() );
QgsDoubleSpinBox *doubleSpinBox( qobject_cast<QgsDoubleSpinBox *>( mDoubleSpinBox ) );
if ( doubleSpinBox )
doubleSpinBox->setSpecialValueText( QgsApplication::nullRepresentation() );
else
mDoubleSpinBox->setSpecialValueText( QgsApplication::nullRepresentation() );
}
mDoubleSpinBox->setMinimum( minval );
mDoubleSpinBox->setMaximum( maxval );
@ -141,7 +147,11 @@ void QgsRangeWidgetWrapper::initWidget( QWidget *editor )
int stepval = step.isValid() ? step.toInt() : 1;
minval -= stepval;
mIntSpinBox->setValue( minval );
mIntSpinBox->setSpecialValueText( QgsApplication::nullRepresentation() );
QgsSpinBox *intSpinBox( qobject_cast<QgsSpinBox *>( mIntSpinBox ) );
if ( intSpinBox )
intSpinBox->setSpecialValueText( QgsApplication::nullRepresentation() );
else
mIntSpinBox->setSpecialValueText( QgsApplication::nullRepresentation() );
}
setupIntEditor( minval, max, step, mIntSpinBox, this );
if ( config( QStringLiteral( "Suffix" ) ).isValid() )

View File

@ -26,6 +26,12 @@
#define CLEAR_ICON_SIZE 16
// This is required because private implementation of
// QAbstractSpinBoxPrivate checks for specialText emptiness
// and skips specialText handling if it's empty
QString QgsSpinBox::SPECIAL_TEXT_WHEN_EMPTY = QChar( 0x2063 );
QgsSpinBox::QgsSpinBox( QWidget *parent )
: QSpinBox( parent )
{
@ -137,6 +143,14 @@ void QgsSpinBox::setLineEditAlignment( Qt::Alignment alignment )
mLineEdit->setAlignment( alignment );
}
void QgsSpinBox::setSpecialValueText( const QString &txt )
{
if ( txt.isEmpty() )
QSpinBox::setSpecialValueText( SPECIAL_TEXT_WHEN_EMPTY );
else
QSpinBox::setSpecialValueText( txt );
}
int QgsSpinBox::valueFromText( const QString &text ) const
{
if ( !mExpressionsEnabled )
@ -185,6 +199,9 @@ QString QgsSpinBox::stripped( const QString &originalText ) const
QString text = originalText;
if ( specialValueText().isEmpty() || text != specialValueText() )
{
// Strip SPECIAL_TEXT_WHEN_EMPTY
if ( text.contains( SPECIAL_TEXT_WHEN_EMPTY ) )
text = text.replace( SPECIAL_TEXT_WHEN_EMPTY, QString() );
int from = 0;
int size = text.size();
bool changed = false;

View File

@ -132,6 +132,13 @@ class GUI_EXPORT QgsSpinBox : public QSpinBox
*/
void setLineEditAlignment( Qt::Alignment alignment );
/**
* Set the special-value text to be \a txt
* If set, the spin box will display this text instead of a numeric value whenever the current value
* is equal to minimum(). Typical use is to indicate that this choice has a special (default) meaning.
*/
void setSpecialValueText( const QString &txt );
int valueFromText( const QString &text ) const override;
QValidator::State validate( QString &input, int &pos ) const override;
@ -156,7 +163,15 @@ class GUI_EXPORT QgsSpinBox : public QSpinBox
bool mExpressionsEnabled = true;
// This is required because private implementation of
// QAbstractSpinBoxPrivate checks for specialText emptiness
// and skips specialText handling if it's empty
static QString SPECIAL_TEXT_WHEN_EMPTY;
QString stripped( const QString &originalText ) const;
friend class TestQgsRangeWidgetWrapper;
};
#endif // QGSSPINBOX_H

View File

@ -20,12 +20,14 @@
#include "qgsrangewidgetwrapper.h"
#include "qgsrangeconfigdlg.h"
#include "qgsdoublespinbox.h"
#include "qgsspinbox.h"
#include "qgsapplication.h"
#include "qgslogger.h"
#include "qgsvectorlayer.h"
#include "qgsdataprovider.h"
#include "qgsfilterlineedit.h"
#include <QLineEdit>
#include <QObject>
#include <QtTest/QSignalSpy>
@ -48,14 +50,22 @@ class TestQgsRangeWidgetWrapper : public QObject
void test_setDoubleRange();
void test_setDoubleSmallerRange();
void test_setDoubleLimits();
void test_nulls();
private:
std::unique_ptr<QgsRangeWidgetWrapper> widget; // For field 1
std::unique_ptr<QgsRangeWidgetWrapper> widget0; // For field 0
std::unique_ptr<QgsRangeWidgetWrapper> widget1; // For field 1
std::unique_ptr<QgsRangeWidgetWrapper> widget2; // For field 2
std::unique_ptr<QgsVectorLayer> vl;
};
void TestQgsRangeWidgetWrapper::initTestCase()
{
// Set up the QgsSettings environment
QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) );
QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) );
QCoreApplication::setApplicationName( QStringLiteral( "QGIS-TEST-RANGE-WIDGET" ) );
QgsApplication::init();
QgsApplication::initQgis();
}
@ -108,9 +118,10 @@ void TestQgsRangeWidgetWrapper::init()
QCOMPARE( vl->featureCount( ), ( long )3 );
QgsFeature _feat1( vl->getFeature( 1 ) );
QCOMPARE( _feat1, feat1 );
widget = qgis::make_unique<QgsRangeWidgetWrapper>( vl.get(), 1, nullptr, nullptr );
widget0 = qgis::make_unique<QgsRangeWidgetWrapper>( vl.get(), 0, nullptr, nullptr );
widget1 = qgis::make_unique<QgsRangeWidgetWrapper>( vl.get(), 1, nullptr, nullptr );
widget2 = qgis::make_unique<QgsRangeWidgetWrapper>( vl.get(), 2, nullptr, nullptr );
QVERIFY( widget.get() );
QVERIFY( widget1.get() );
}
void TestQgsRangeWidgetWrapper::cleanup()
@ -123,9 +134,9 @@ void TestQgsRangeWidgetWrapper::test_setDoubleRange()
// See https://issues.qgis.org/issues/17878
// QGIS 3 Vector Layer Fields Garbled when Clicking the Toggle Editing Icon
QgsDoubleSpinBox *editor = qobject_cast<QgsDoubleSpinBox *>( widget->createWidget( nullptr ) );
QgsDoubleSpinBox *editor = qobject_cast<QgsDoubleSpinBox *>( widget1->createWidget( nullptr ) );
QVERIFY( editor );
widget->initWidget( editor );
widget1->initWidget( editor );
QgsDoubleSpinBox *editor2 = qobject_cast<QgsDoubleSpinBox *>( widget2->createWidget( nullptr ) );
QVERIFY( editor2 );
widget2->initWidget( editor2 );
@ -133,7 +144,7 @@ void TestQgsRangeWidgetWrapper::test_setDoubleRange()
QgsFeature feat( vl->getFeature( 1 ) );
QVERIFY( feat.isValid() );
QCOMPARE( feat.attribute( 1 ).toDouble(), 123.123456789 );
widget->setFeature( vl->getFeature( 1 ) );
widget1->setFeature( vl->getFeature( 1 ) );
widget2->setFeature( vl->getFeature( 1 ) );
QCOMPARE( vl->fields().at( 1 ).precision(), 9 );
// Default is 0 !!! for double, really ?
@ -151,12 +162,12 @@ void TestQgsRangeWidgetWrapper::test_setDoubleRange()
QCOMPARE( editor->maximum( ), std::numeric_limits<double>::max() );
QCOMPARE( editor2->maximum( ), std::numeric_limits<double>::max() );
widget->setFeature( vl->getFeature( 2 ) );
widget1->setFeature( vl->getFeature( 2 ) );
widget2->setFeature( vl->getFeature( 2 ) );
QCOMPARE( editor->value( ), editor->minimum() );
QCOMPARE( editor2->value( ), editor->minimum() );
widget->setFeature( vl->getFeature( 3 ) );
widget1->setFeature( vl->getFeature( 3 ) );
widget2->setFeature( vl->getFeature( 3 ) );
QCOMPARE( editor->value( ), -123.123456789 );
QCOMPARE( editor2->value( ), -123.0 );
@ -169,10 +180,10 @@ void TestQgsRangeWidgetWrapper::test_setDoubleSmallerRange()
cfg.insert( QStringLiteral( "Min" ), -100.0 );
cfg.insert( QStringLiteral( "Max" ), 100.0 );
cfg.insert( QStringLiteral( "Step" ), 1 );
widget->setConfig( cfg );
QgsDoubleSpinBox *editor = qobject_cast<QgsDoubleSpinBox *>( widget->createWidget( nullptr ) );
widget1->setConfig( cfg );
QgsDoubleSpinBox *editor = qobject_cast<QgsDoubleSpinBox *>( widget1->createWidget( nullptr ) );
QVERIFY( editor );
widget->initWidget( editor );
widget1->initWidget( editor );
widget2->setConfig( cfg );
QgsDoubleSpinBox *editor2 = qobject_cast<QgsDoubleSpinBox *>( widget2->createWidget( nullptr ) );
@ -182,7 +193,7 @@ void TestQgsRangeWidgetWrapper::test_setDoubleSmallerRange()
QgsFeature feat( vl->getFeature( 1 ) );
QVERIFY( feat.isValid() );
QCOMPARE( feat.attribute( 1 ).toDouble(), 123.123456789 );
widget->setFeature( vl->getFeature( 1 ) );
widget1->setFeature( vl->getFeature( 1 ) );
widget2->setFeature( vl->getFeature( 1 ) );
QCOMPARE( vl->fields().at( 1 ).precision(), 9 );
@ -202,13 +213,13 @@ void TestQgsRangeWidgetWrapper::test_setDoubleSmallerRange()
QCOMPARE( editor2->maximum( ), ( double )100 );
// NULL, NULL
widget->setFeature( vl->getFeature( 2 ) );
widget1->setFeature( vl->getFeature( 2 ) );
widget2->setFeature( vl->getFeature( 2 ) );
QCOMPARE( editor->value( ), editor->minimum() );
QCOMPARE( editor2->value( ), editor2->minimum() );
// negative, negative
widget->setFeature( vl->getFeature( 3 ) );
widget1->setFeature( vl->getFeature( 3 ) );
widget2->setFeature( vl->getFeature( 3 ) );
// value was changed to the minimum
QCOMPARE( editor->value( ), editor->minimum() );
@ -223,10 +234,10 @@ void TestQgsRangeWidgetWrapper::test_setDoubleLimits()
cfg.insert( QStringLiteral( "Min" ), std::numeric_limits<double>::lowest() );
cfg.insert( QStringLiteral( "Max" ), std::numeric_limits<double>::max() );
cfg.insert( QStringLiteral( "Step" ), 1 );
widget->setConfig( cfg );
QgsDoubleSpinBox *editor = qobject_cast<QgsDoubleSpinBox *>( widget->createWidget( nullptr ) );
widget1->setConfig( cfg );
QgsDoubleSpinBox *editor = qobject_cast<QgsDoubleSpinBox *>( widget1->createWidget( nullptr ) );
QVERIFY( editor );
widget->initWidget( editor );
widget1->initWidget( editor );
widget2->setConfig( cfg );
QgsDoubleSpinBox *editor2 = qobject_cast<QgsDoubleSpinBox *>( widget2->createWidget( nullptr ) );
@ -241,7 +252,7 @@ void TestQgsRangeWidgetWrapper::test_setDoubleLimits()
QgsFeature feat( vl->getFeature( 1 ) );
QVERIFY( feat.isValid() );
QCOMPARE( feat.attribute( 1 ).toDouble(), 123.123456789 );
widget->setFeature( vl->getFeature( 1 ) );
widget1->setFeature( vl->getFeature( 1 ) );
widget2->setFeature( vl->getFeature( 1 ) );
QCOMPARE( vl->fields().at( 1 ).precision(), 9 );
@ -253,13 +264,13 @@ void TestQgsRangeWidgetWrapper::test_setDoubleLimits()
QCOMPARE( editor2->value( ), 123.0 );
// NULL, NULL
widget->setFeature( vl->getFeature( 2 ) );
widget1->setFeature( vl->getFeature( 2 ) );
widget2->setFeature( vl->getFeature( 2 ) );
QCOMPARE( editor->value( ), editor->minimum() );
QCOMPARE( editor2->value( ), editor2->minimum() );
// negative, negative
widget->setFeature( vl->getFeature( 3 ) );
widget1->setFeature( vl->getFeature( 3 ) );
widget2->setFeature( vl->getFeature( 3 ) );
// value was changed to the minimum
QCOMPARE( editor->value( ), -123.123456789 );
@ -267,6 +278,56 @@ void TestQgsRangeWidgetWrapper::test_setDoubleLimits()
}
void TestQgsRangeWidgetWrapper::test_nulls()
{
QgsApplication::setNullRepresentation( QString( "" ) );
QVariantMap cfg;
cfg.insert( QStringLiteral( "Min" ), 100.00 );
cfg.insert( QStringLiteral( "Max" ), 200.00 );
cfg.insert( QStringLiteral( "Step" ), 1 );
cfg.insert( QStringLiteral( "Precision" ), 0 );
widget1->setConfig( cfg );
QgsDoubleSpinBox *editor1 = qobject_cast<QgsDoubleSpinBox *>( widget1->createWidget( nullptr ) );
QVERIFY( editor1 );
widget1->initWidget( editor1 );
// Out of range
widget1->setFeature( vl->getFeature( 3 ) );
QCOMPARE( editor1->value( ), editor1->minimum() );
QCOMPARE( widget1->value( ), QVariant( QVariant::Double ) );
widget1->setFeature( QgsFeature( vl->fields() ) );
// Null
QCOMPARE( editor1->value( ), editor1->minimum() );
QCOMPARE( widget1->value( ), QVariant( QVariant::Double ) );
QCOMPARE( editor1->mLineEdit->text(), QgsDoubleSpinBox::SPECIAL_TEXT_WHEN_EMPTY );
editor1->mLineEdit->setText( QString( "151%1" ).arg( QgsDoubleSpinBox::SPECIAL_TEXT_WHEN_EMPTY ) );
QCOMPARE( widget1->value( ), 151 );
editor1->mLineEdit->setText( QString( QgsDoubleSpinBox::SPECIAL_TEXT_WHEN_EMPTY ).append( QStringLiteral( "161" ) ) );
QCOMPARE( widget1->value( ), 161 );
QgsSpinBox *editor0 = qobject_cast<QgsSpinBox *>( widget0->createWidget( nullptr ) );
QVERIFY( editor0 );
widget0->setConfig( cfg );
widget0->initWidget( editor0 );
// Out of range
widget0->setFeature( vl->getFeature( 3 ) );
QCOMPARE( editor0->value( ), editor0->minimum() );
QCOMPARE( widget0->value( ), QVariant( QVariant::Int ) );
widget0->setFeature( QgsFeature( vl->fields() ) );
// Null
QCOMPARE( editor0->value( ), editor0->minimum() );
QCOMPARE( widget0->value( ), QVariant( QVariant::Int ) );
QCOMPARE( editor0->mLineEdit->text(), QgsDoubleSpinBox::SPECIAL_TEXT_WHEN_EMPTY );
editor0->mLineEdit->setText( QString( "150%1" ).arg( QgsDoubleSpinBox::SPECIAL_TEXT_WHEN_EMPTY ) );
QCOMPARE( widget0->value( ), 150 );
editor0->mLineEdit->setText( QString( QgsDoubleSpinBox::SPECIAL_TEXT_WHEN_EMPTY ).append( QStringLiteral( "160" ) ) );
QCOMPARE( widget0->value( ), 160 );
}
QGSTEST_MAIN( TestQgsRangeWidgetWrapper )