Skip to content

Commit 73f7bd3

Browse files
dgoedkoopwonder-sk
authored andcommittedApr 16, 2018
Add null handling to value map edit widget (fixes #15215) (#3274)
* Add null handling to value map edit widget (fixes #15215) * Return QVariant type * Use hardcoded value for 'null' representation * Detect "null" value when loading value map from csv; use null QString constructor * Use configured "null" representation for display in value map * Use single definition for value map null representation guid * Added unit test for value map widget and fixed value displaying bug (cherry picked from commit d2c9863)
1 parent 36749c1 commit 73f7bd3

File tree

6 files changed

+155
-43
lines changed

6 files changed

+155
-43
lines changed
 

‎src/gui/editorwidgets/qgsvaluemapconfigdlg.cpp

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ QgsValueMapConfigDlg::QgsValueMapConfigDlg( QgsVectorLayer* vl, int fieldIdx, QW
2929

3030
tableWidget->insertRow( 0 );
3131

32+
connect( addNullButton, SIGNAL( clicked() ), this, SLOT( addNullButtonPushed() ) );
3233
connect( removeSelectedButton, SIGNAL( clicked() ), this, SLOT( removeSelectedButtonPushed() ) );
3334
connect( loadFromLayerButton, SIGNAL( clicked() ), this, SLOT( loadFromLayerButtonPushed() ) );
3435
connect( loadFromCSVButton, SIGNAL( clicked() ), this, SLOT( loadFromCSVButtonPushed() ) );
@@ -38,6 +39,7 @@ QgsValueMapConfigDlg::QgsValueMapConfigDlg( QgsVectorLayer* vl, int fieldIdx, QW
3839
QgsEditorWidgetConfig QgsValueMapConfigDlg::config()
3940
{
4041
QgsEditorWidgetConfig cfg;
42+
QSettings settings;
4143

4244
//store data to map
4345
for ( int i = 0; i < tableWidget->rowCount() - 1; i++ )
@@ -48,13 +50,17 @@ QgsEditorWidgetConfig QgsValueMapConfigDlg::config()
4850
if ( !ki )
4951
continue;
5052

53+
QString ks = ki->text();
54+
if (( ks == settings.value( "qgis/nullValue", "NULL" ).toString() ) && !( ki->flags() & Qt::ItemIsEditable ) )
55+
ks = VALUEMAP_NULL_TEXT;
56+
5157
if ( !vi || vi->text().isNull() )
5258
{
53-
cfg.insert( ki->text(), ki->text() );
59+
cfg.insert( ks, ks );
5460
}
5561
else
5662
{
57-
cfg.insert( vi->text(), ki->text() );
63+
cfg.insert( vi->text(), ks );
5864
}
5965
}
6066

@@ -72,16 +78,10 @@ void QgsValueMapConfigDlg::setConfig( const QgsEditorWidgetConfig& config )
7278
int row = 0;
7379
for ( QgsEditorWidgetConfig::ConstIterator mit = config.begin(); mit != config.end(); mit++, row++ )
7480
{
75-
tableWidget->insertRow( row );
7681
if ( mit.value().isNull() )
77-
{
78-
tableWidget->setItem( row, 0, new QTableWidgetItem( mit.key() ) );
79-
}
82+
setRow( row, mit.key(), QString() );
8083
else
81-
{
82-
tableWidget->setItem( row, 0, new QTableWidgetItem( mit.value().toString() ) );
83-
tableWidget->setItem( row, 1, new QTableWidgetItem( mit.key() ) );
84-
}
84+
setRow( row, mit.value().toString(), mit.key() );
8585
}
8686
}
8787

@@ -132,27 +132,47 @@ void QgsValueMapConfigDlg::updateMap( const QMap<QString, QVariant> &map, bool i
132132

133133
if ( insertNull )
134134
{
135-
QSettings settings;
136-
tableWidget->setItem( row, 0, new QTableWidgetItem( settings.value( "qgis/nullValue", "NULL" ).toString() ) );
137-
tableWidget->setItem( row, 1, new QTableWidgetItem( "<NULL>" ) );
135+
setRow( row, VALUEMAP_NULL_TEXT, "<NULL>" );
138136
++row;
139137
}
140138

141139
for ( QMap<QString, QVariant>::const_iterator mit = map.begin(); mit != map.end(); ++mit, ++row )
142140
{
143-
tableWidget->insertRow( row );
144141
if ( mit.value().isNull() )
145-
{
146-
tableWidget->setItem( row, 0, new QTableWidgetItem( mit.key() ) );
147-
}
142+
setRow( row, mit.key(), QString() );
148143
else
149-
{
150-
tableWidget->setItem( row, 0, new QTableWidgetItem( mit.key() ) );
151-
tableWidget->setItem( row, 1, new QTableWidgetItem( mit.value().toString() ) );
152-
}
144+
setRow( row, mit.key(), mit.value().toString() );
153145
}
154146
}
155147

148+
void QgsValueMapConfigDlg::setRow( int row, const QString value, const QString description )
149+
{
150+
QSettings settings;
151+
QTableWidgetItem* valueCell;
152+
QTableWidgetItem* descriptionCell = new QTableWidgetItem( description );
153+
tableWidget->insertRow( row );
154+
if ( value == QString( VALUEMAP_NULL_TEXT ) )
155+
{
156+
QFont cellFont;
157+
cellFont.setItalic( true );
158+
valueCell = new QTableWidgetItem( settings.value( "qgis/nullValue", "NULL" ).toString() );
159+
valueCell->setFont( cellFont );
160+
valueCell->setFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
161+
descriptionCell->setFont( cellFont );
162+
}
163+
else
164+
{
165+
valueCell = new QTableWidgetItem( value );
166+
}
167+
tableWidget->setItem( row, 0, valueCell );
168+
tableWidget->setItem( row, 1, descriptionCell );
169+
}
170+
171+
void QgsValueMapConfigDlg::addNullButtonPushed()
172+
{
173+
setRow( tableWidget->rowCount() - 1, VALUEMAP_NULL_TEXT, "<NULL>" );
174+
}
175+
156176
void QgsValueMapConfigDlg::loadFromLayerButtonPushed()
157177
{
158178
QgsAttributeTypeLoadDialog layerDialog( layer() );
@@ -164,6 +184,8 @@ void QgsValueMapConfigDlg::loadFromLayerButtonPushed()
164184

165185
void QgsValueMapConfigDlg::loadFromCSVButtonPushed()
166186
{
187+
QSettings settings;
188+
167189
QString fileName = QFileDialog::getOpenFileName( nullptr, tr( "Select a file" ), QDir::homePath() );
168190
if ( fileName.isNull() )
169191
return;
@@ -220,6 +242,9 @@ void QgsValueMapConfigDlg::loadFromCSVButtonPushed()
220242
val = val.mid( 1, val.length() - 2 );
221243
}
222244

245+
if ( key == settings.value( "qgis/nullValue", "NULL" ).toString() )
246+
key = QString( VALUEMAP_NULL_TEXT );
247+
223248
map[ key ] = val;
224249
}
225250

‎src/gui/editorwidgets/qgsvaluemapconfigdlg.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
#include "qgseditorconfigwidget.h"
2222

23+
#define VALUEMAP_NULL_TEXT "{2839923C-8B7D-419E-B84B-CA2FE9B80EC7}"
24+
2325
/** \ingroup gui
2426
* \class QgsValueMapConfigDlg
2527
* \note not available in Python bindings
@@ -36,8 +38,12 @@ class GUI_EXPORT QgsValueMapConfigDlg : public QgsEditorConfigWidget, private Ui
3638

3739
void updateMap( const QMap<QString, QVariant> &map, bool insertNull );
3840

41+
private:
42+
void setRow( int row, const QString value, const QString description );
43+
3944
private slots:
4045
void vCellChanged( int row, int column );
46+
void addNullButtonPushed();
4147
void removeSelectedButtonPushed();
4248
void loadFromLayerButtonPushed();
4349
void loadFromCSVButtonPushed();

‎src/gui/editorwidgets/qgsvaluemapwidgetfactory.cpp

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
#include "qgsdefaultsearchwidgetwrapper.h"
2121
#include "qgsvaluemapconfigdlg.h"
2222

23+
#include <QSettings>
24+
2325
QgsValueMapWidgetFactory::QgsValueMapWidgetFactory( const QString& name )
2426
: QgsEditorWidgetFactory( name )
2527
{
@@ -82,11 +84,17 @@ void QgsValueMapWidgetFactory::writeConfig( const QgsEditorWidgetConfig& config,
8284

8385
QString QgsValueMapWidgetFactory::representValue( QgsVectorLayer* vl, int fieldIdx, const QgsEditorWidgetConfig& config, const QVariant& cache, const QVariant& value ) const
8486
{
85-
Q_UNUSED( vl )
86-
Q_UNUSED( fieldIdx )
8787
Q_UNUSED( cache )
8888

89-
return config.key( value, QVariant( QString( "(%1)" ).arg( value.toString() ) ).toString() );
89+
QString valueInternalText;
90+
QString valueDisplayText;
91+
QSettings settings;
92+
if ( value.isNull() )
93+
valueInternalText = QString( VALUEMAP_NULL_TEXT );
94+
else
95+
valueInternalText = value.toString();
96+
97+
return config.key( valueInternalText, QVariant( QString( "(%1)" ).arg( vl->fields().at( fieldIdx ).displayString( value ) ) ).toString() );
9098
}
9199

92100
QVariant QgsValueMapWidgetFactory::sortValue( QgsVectorLayer* vl, int fieldIdx, const QgsEditorWidgetConfig& config, const QVariant& cache, const QVariant& value ) const

‎src/gui/editorwidgets/qgsvaluemapwidgetwrapper.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
***************************************************************************/
1515

1616
#include "qgsvaluemapwidgetwrapper.h"
17+
#include "qgsvaluemapconfigdlg.h"
18+
19+
#include <QSettings>
1720

1821
QgsValueMapWidgetWrapper::QgsValueMapWidgetWrapper( QgsVectorLayer* vl, int fieldIdx, QWidget* editor, QWidget* parent )
1922
: QgsEditorWidgetWrapper( vl, fieldIdx, editor, parent )
@@ -29,6 +32,9 @@ QVariant QgsValueMapWidgetWrapper::value() const
2932
if ( mComboBox )
3033
v = mComboBox->itemData( mComboBox->currentIndex() );
3134

35+
if ( v == QString( VALUEMAP_NULL_TEXT ) )
36+
v = QVariant( field().type() );
37+
3238
return v;
3339
}
3440

@@ -70,6 +76,12 @@ bool QgsValueMapWidgetWrapper::valid() const
7076

7177
void QgsValueMapWidgetWrapper::setValue( const QVariant& value )
7278
{
79+
QString v;
80+
if ( value.isNull() )
81+
v = QString( VALUEMAP_NULL_TEXT );
82+
else
83+
v = value.toString();
84+
7385
if ( mComboBox )
74-
mComboBox->setCurrentIndex( mComboBox->findData( value ) );
86+
mComboBox->setCurrentIndex( mComboBox->findData( v ) );
7587
}

‎src/ui/editorwidgets/qgsvaluemapconfigdlgbase.ui

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
<string>Form</string>
1515
</property>
1616
<layout class="QGridLayout" name="gridLayout">
17-
<item row="0" column="0" colspan="3">
17+
<item row="0" column="0" colspan="5">
1818
<widget class="QLabel" name="valueMapLabel">
1919
<property name="text">
2020
<string>Combo box with predefined items. Value is stored in the attribute, description is shown in the combo box.</string>
@@ -31,14 +31,7 @@
3131
</property>
3232
</widget>
3333
</item>
34-
<item row="1" column="1">
35-
<widget class="QPushButton" name="loadFromCSVButton">
36-
<property name="text">
37-
<string>Load Data from CSV File</string>
38-
</property>
39-
</widget>
40-
</item>
41-
<item row="1" column="2">
34+
<item row="1" column="4">
4235
<spacer name="horizontalSpacer">
4336
<property name="orientation">
4437
<enum>Qt::Horizontal</enum>
@@ -51,7 +44,7 @@
5144
</property>
5245
</spacer>
5346
</item>
54-
<item row="2" column="0" colspan="3">
47+
<item row="2" column="0" colspan="5">
5548
<widget class="QTableWidget" name="tableWidget">
5649
<column>
5750
<property name="text">
@@ -65,14 +58,7 @@
6558
</column>
6659
</widget>
6760
</item>
68-
<item row="3" column="0">
69-
<widget class="QPushButton" name="removeSelectedButton">
70-
<property name="text">
71-
<string>Remove Selected</string>
72-
</property>
73-
</widget>
74-
</item>
75-
<item row="3" column="1" colspan="2">
61+
<item row="3" column="3" colspan="2">
7662
<spacer name="horizontalSpacer_2">
7763
<property name="orientation">
7864
<enum>Qt::Horizontal</enum>
@@ -85,6 +71,27 @@
8571
</property>
8672
</spacer>
8773
</item>
74+
<item row="3" column="0">
75+
<widget class="QPushButton" name="addNullButton">
76+
<property name="text">
77+
<string>Add &quot;NULL&quot; value</string>
78+
</property>
79+
</widget>
80+
</item>
81+
<item row="3" column="1">
82+
<widget class="QPushButton" name="removeSelectedButton">
83+
<property name="text">
84+
<string>Remove Selected</string>
85+
</property>
86+
</widget>
87+
</item>
88+
<item row="1" column="1">
89+
<widget class="QPushButton" name="loadFromCSVButton">
90+
<property name="text">
91+
<string>Load Data from CSV File</string>
92+
</property>
93+
</widget>
94+
</item>
8895
</layout>
8996
</widget>
9097
<resources/>

‎tests/src/python/test_qgseditwidgets.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727

2828
class TestQgsTextEditWidget(unittest.TestCase):
2929

30+
VALUEMAP_NULL_TEXT = "{2839923C-8B7D-419E-B84B-CA2FE9B80EC7}"
31+
3032
@classmethod
3133
def setUpClass(cls):
3234
QgsEditorWidgetRegistry.initEditors()
@@ -93,6 +95,57 @@ def testStringWithMaxLen(self):
9395

9496
QgsMapLayerRegistry.instance().removeAllMapLayers()
9597

98+
def test_ValueMap_representValue(self):
99+
layer = QgsVectorLayer("none?field=number1:integer&field=number2:double&field=text1:string&field=number3:integer&field=number4:double&field=text2:string",
100+
"layer", "memory")
101+
assert layer.isValid()
102+
QgsMapLayerRegistry.instance().addMapLayer(layer)
103+
f = QgsFeature()
104+
f.setAttributes([2, 2.5, 'NULL', None, None, None])
105+
assert layer.dataProvider().addFeatures([f])
106+
reg = QgsEditorWidgetRegistry.instance()
107+
factory = reg.factory("ValueMap")
108+
self.assertIsNotNone(factory)
109+
110+
# Tests with different value types occuring in the value map
111+
config = {'two': '2', 'twoandhalf': '2.5', 'NULL text': 'NULL',
112+
'nothing': self.VALUEMAP_NULL_TEXT}
113+
self.assertEqual(factory.representValue(layer, 0, config, None, 2), 'two')
114+
self.assertEqual(factory.representValue(layer, 1, config, None, 2.5), 'twoandhalf')
115+
self.assertEqual(factory.representValue(layer, 2, config, None, 'NULL'), 'NULL text')
116+
# Tests with null values of different types, if value map contains null
117+
self.assertEqual(factory.representValue(layer, 3, config, None, None), 'nothing')
118+
self.assertEqual(factory.representValue(layer, 4, config, None, None), 'nothing')
119+
self.assertEqual(factory.representValue(layer, 5, config, None, None), 'nothing')
120+
# Tests with fallback display for different value types
121+
config = {}
122+
self.assertEqual(factory.representValue(layer, 0, config, None, 2), '(2)')
123+
self.assertEqual(factory.representValue(layer, 1, config, None, 2.5), '(2.50000)')
124+
self.assertEqual(factory.representValue(layer, 2, config, None, 'NULL'), '(NULL)')
125+
# Tests with fallback display for null in different types of fields
126+
self.assertEqual(factory.representValue(layer, 3, config, None, None), '(NULL)')
127+
self.assertEqual(factory.representValue(layer, 4, config, None, None), '(NULL)')
128+
self.assertEqual(factory.representValue(layer, 5, config, None, None), '(NULL)')
129+
130+
QgsMapLayerRegistry.instance().removeAllMapLayers()
131+
132+
def test_ValueMap_set_get(self):
133+
layer = QgsVectorLayer("none?field=number:integer", "layer", "memory")
134+
assert layer.isValid()
135+
QgsMapLayerRegistry.instance().addMapLayer(layer)
136+
reg = QgsEditorWidgetRegistry.instance()
137+
configWdg = reg.createConfigWidget('ValueMap', layer, 0, None)
138+
139+
config = {'two': '2', 'twoandhalf': '2.5', 'NULL text': 'NULL',
140+
'nothing': self.VALUEMAP_NULL_TEXT}
141+
142+
# Set a configuration containing values and NULL and check if it
143+
# is returned intact.
144+
configWdg.setConfig(config)
145+
self.assertEqual(configWdg.config(), config)
146+
147+
QgsMapLayerRegistry.instance().removeAllMapLayers()
148+
96149
def test_ValueRelation_representValue(self):
97150

98151
first_layer = QgsVectorLayer("none?field=foreign_key:integer",
@@ -227,5 +280,6 @@ def test_RelationReference_representValue(self):
227280

228281
QgsMapLayerRegistry.instance().removeAllMapLayers()
229282

283+
230284
if __name__ == '__main__':
231285
unittest.main()

0 commit comments

Comments
 (0)
Please sign in to comment.