QGIS API Documentation 3.43.0-Master (3ee7834ace6)
qgsvaluemapconfigdlg.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsvaluemapconfigdlg.cpp
3 --------------------------------------
4 Date : 5.1.2014
5 Copyright : (C) 2014 Matthias Kuhn
6 Email : matthias at opengis dot ch
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
17#include "moc_qgsvaluemapconfigdlg.cpp"
18
21#include "qgsapplication.h"
22#include "qgssettings.h"
23
24#include <QFileDialog>
25#include <QMessageBox>
26#include <QTextStream>
27#include <QClipboard>
28#include <QKeyEvent>
29#include <QMimeData>
30#include <QRegularExpression>
31
33 : QgsEditorConfigWidget( vl, fieldIdx, parent )
34{
35 setupUi( this );
36
37 mValueMapErrorsLabel->setVisible( false );
38 mValueMapErrorsLabel->setStyleSheet( QStringLiteral( "QLabel { color : red; }" ) );
39
40 tableWidget->insertRow( 0 );
41
42 tableWidget->horizontalHeader()->setSectionsClickable( true );
43 tableWidget->setSortingEnabled( true );
44
45 connect( addNullButton, &QAbstractButton::clicked, this, &QgsValueMapConfigDlg::addNullButtonPushed );
46 connect( removeSelectedButton, &QAbstractButton::clicked, this, &QgsValueMapConfigDlg::removeSelectedButtonPushed );
47 connect( loadFromLayerButton, &QAbstractButton::clicked, this, &QgsValueMapConfigDlg::loadFromLayerButtonPushed );
48 connect( loadFromCSVButton, &QAbstractButton::clicked, this, &QgsValueMapConfigDlg::loadFromCSVButtonPushed );
49 connect( tableWidget, &QTableWidget::cellChanged, this, &QgsValueMapConfigDlg::vCellChanged );
50 tableWidget->installEventFilter( this );
51}
52
54{
55 QList<QVariant> valueList;
56
57 //store data to map
58 for ( int i = 0; i < tableWidget->rowCount() - 1; i++ )
59 {
60 QTableWidgetItem *ki = tableWidget->item( i, 0 );
61 QTableWidgetItem *vi = tableWidget->item( i, 1 );
62
63 if ( !ki )
64 continue;
65
66 QString ks = ki->text();
67 if ( ( ks == QgsApplication::nullRepresentation() ) && !( ki->flags() & Qt::ItemIsEditable ) )
69
70 QVariantMap value;
71
72 if ( !vi || vi->text().isNull() )
73 {
74 value.insert( ks, ks );
75 }
76 else
77 {
78 value.insert( vi->text(), ks );
79 }
80 valueList.append( value );
81 }
82
83 QVariantMap cfg;
84 cfg.insert( QStringLiteral( "map" ), valueList );
85 return cfg;
86}
87
88void QgsValueMapConfigDlg::setConfig( const QVariantMap &config )
89{
90 tableWidget->clearContents();
91 for ( int i = tableWidget->rowCount() - 1; i > 0; i-- )
92 {
93 tableWidget->removeRow( i );
94 }
95
96 QList<QVariant> valueList = config.value( QStringLiteral( "map" ) ).toList();
97 QList<QPair<QString, QVariant>> orderedList;
98
99 if ( valueList.count() > 0 )
100 {
101 for ( int i = 0, row = 0; i < valueList.count(); i++, row++ )
102 {
103 orderedList.append( qMakePair( valueList[i].toMap().constBegin().value().toString(), valueList[i].toMap().constBegin().key() ) );
104 }
105 }
106 else
107 {
108 int row = 0;
109 const QVariantMap values = config.value( QStringLiteral( "map" ) ).toMap();
110 for ( QVariantMap::ConstIterator mit = values.constBegin(); mit != values.constEnd(); mit++, row++ )
111 {
112 if ( QgsVariantUtils::isNull( mit.value() ) )
113 orderedList.append( qMakePair( mit.key(), QVariant() ) );
114 else
115 orderedList.append( qMakePair( mit.value().toString(), mit.key() ) );
116 }
117 }
118
119 updateMap( orderedList, false );
120}
121
122void QgsValueMapConfigDlg::vCellChanged( int row, int column )
123{
124 Q_UNUSED( column )
125 if ( row == tableWidget->rowCount() - 1 )
126 {
127 tableWidget->insertRow( row + 1 );
128 } //else check type
129
130 if ( layer()->fields().exists( field() ) )
131 {
132 // check cell value
133 QTableWidgetItem *item = tableWidget->item( row, 0 );
134 if ( item )
135 {
136 const QString validValue = checkValueLength( item->text() );
137 if ( validValue.length() != item->text().length() )
138 {
139 const QString errorMessage = tr( "Value '%1' has been trimmed (maximum field length: %2)" )
140 .arg( item->text(), QString::number( layer()->fields().field( field() ).length() ) );
141 item->setText( validValue );
142 mValueMapErrorsLabel->setVisible( true );
143 mValueMapErrorsLabel->setText( QStringLiteral( "%1<br>%2" ).arg( errorMessage, mValueMapErrorsLabel->text() ) );
144 }
145 }
146 }
147
148 emit changed();
149}
150
151void QgsValueMapConfigDlg::removeSelectedButtonPushed()
152{
153 QList<QTableWidgetItem *> list = tableWidget->selectedItems();
154 QSet<int> rowsToRemove;
155 int removed = 0;
156 int i;
157 for ( i = 0; i < list.size(); i++ )
158 {
159 if ( list[i]->column() == 0 )
160 {
161 const int row = list[i]->row();
162 if ( !rowsToRemove.contains( row ) )
163 {
164 rowsToRemove.insert( row );
165 }
166 }
167 }
168 for ( const int rowToRemoved : rowsToRemove )
169 {
170 tableWidget->removeRow( rowToRemoved - removed );
171 removed++;
172 }
173 emit changed();
174}
175
176void QgsValueMapConfigDlg::updateMap( const QMap<QString, QVariant> &map, bool insertNull )
177{
178 QList<QPair<QString, QVariant>> orderedMap;
179 const auto end = map.constEnd();
180 for ( auto it = map.constBegin(); it != end; ++it )
181 {
182 orderedMap.append( qMakePair( it.key(), it.value() ) );
183 }
184
185 updateMap( orderedMap, insertNull );
186}
187
188void QgsValueMapConfigDlg::updateMap( const QList<QPair<QString, QVariant>> &list, bool insertNull )
189{
190 tableWidget->clearContents();
191 mValueMapErrorsLabel->setVisible( false );
192
193 for ( int i = tableWidget->rowCount() - 1; i > 0; i-- )
194 {
195 tableWidget->removeRow( i );
196 }
197 int row = 0;
198
199 if ( insertNull )
200 {
201 setRow( row, QgsValueMapFieldFormatter::NULL_VALUE, QStringLiteral( "<NULL>" ) );
202 ++row;
203 }
204
205 constexpr int maxOverflowErrors { 5 };
206 QStringList reportedErrors;
207 const bool hasField { layer()->fields().exists( field() ) };
208 const QgsField mappedField { hasField ? layer()->fields().field( field() ) : QgsField() };
209
210 for ( const auto &pair : list )
211 {
212 if ( QgsVariantUtils::isNull( pair.second ) )
213 setRow( row, pair.first, QString() );
214 else
215 {
216 const QString value { pair.first };
217 // Check value
218 const QString validValue = checkValueLength( value );
219
220 if ( validValue.length() != value.length() )
221 {
222 if ( reportedErrors.length() < maxOverflowErrors )
223 {
224 reportedErrors.push_back( tr( "Value '%1' has been trimmed (maximum field length: %2)" )
225 .arg( value, QString::number( mappedField.length() ) ) );
226 }
227 else if ( reportedErrors.length() == maxOverflowErrors )
228 {
229 reportedErrors.push_back( tr( "Only first %1 errors have been reported." )
230 .arg( maxOverflowErrors ) );
231 }
232 }
233
234 setRow( row, validValue, pair.second.toString() );
235
236 // Show errors if any
237 if ( !reportedErrors.isEmpty() )
238 {
239 mValueMapErrorsLabel->setVisible( true );
240 mValueMapErrorsLabel->setText( reportedErrors.join( QLatin1String( "<br>" ) ) );
241 }
242 }
243 ++row;
244 }
245}
246
247QString QgsValueMapConfigDlg::checkValueLength( const QString &value )
248{
250 {
251 return value;
252 }
253
254 if ( layer()->fields().exists( field() ) )
255 {
256 const QgsField mappedField { layer()->fields().field( field() ) };
257 if ( mappedField.length() > 0 && value.length() > mappedField.length() )
258 {
259 return value.mid( 0, mappedField.length() );
260 }
261 }
262 return value;
263}
264
265void QgsValueMapConfigDlg::populateComboBox( QComboBox *comboBox, const QVariantMap &config, bool skipNull )
266{
267 const QList<QVariant> valueList = config.value( QStringLiteral( "map" ) ).toList();
268
269 if ( !valueList.empty() )
270 {
271 for ( const QVariant &value : valueList )
272 {
273 const QVariantMap valueMap = value.toMap();
274
275 if ( skipNull && valueMap.constBegin().value() == QgsValueMapFieldFormatter::NULL_VALUE )
276 continue;
277
278 comboBox->addItem( valueMap.constBegin().key(), valueMap.constBegin().value() );
279 }
280 }
281 else
282 {
283 const QVariantMap map = config.value( QStringLiteral( "map" ) ).toMap();
284 for ( auto it = map.constBegin(); it != map.constEnd(); ++it )
285 {
286 if ( skipNull && it.value() == QgsValueMapFieldFormatter::NULL_VALUE )
287 continue;
288
289 comboBox->addItem( it.key(), it.value() );
290 }
291 }
292}
293
294bool QgsValueMapConfigDlg::eventFilter( QObject *watched, QEvent *event )
295{
296 Q_UNUSED( watched )
297 if ( event->type() == QEvent::KeyPress )
298 {
299 QKeyEvent *keyEvent = static_cast<QKeyEvent *>( event );
300 if ( keyEvent->matches( QKeySequence::Copy ) )
301 {
302 copySelectionToClipboard();
303 event->accept();
304 return true;
305 }
306 }
307 return false;
308}
309
310void QgsValueMapConfigDlg::setRow( int row, const QString &value, const QString &description )
311{
312 const QgsSettings settings;
313 QTableWidgetItem *valueCell = nullptr;
314 QTableWidgetItem *descriptionCell = new QTableWidgetItem( description );
315 tableWidget->insertRow( row );
317 {
318 QFont cellFont;
319 cellFont.setItalic( true );
320 valueCell = new QTableWidgetItem( QgsApplication::nullRepresentation() );
321 valueCell->setFont( cellFont );
322 valueCell->setFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
323 descriptionCell->setFont( cellFont );
324 }
325 else
326 {
327 valueCell = new QTableWidgetItem( value );
328 }
329 tableWidget->setItem( row, 0, valueCell );
330 tableWidget->setItem( row, 1, descriptionCell );
331}
332
333void QgsValueMapConfigDlg::copySelectionToClipboard()
334{
335 QAbstractItemModel *model = tableWidget->model();
336 QItemSelectionModel *selection = tableWidget->selectionModel();
337 const QModelIndexList indexes = selection->selectedIndexes();
338
339 QString clipboardText;
340 QModelIndex previous = indexes.first();
341 auto mimeData = std::make_unique<QMimeData>();
342 for ( const QModelIndex &current : indexes )
343 {
344 const QString text = model->data( current ).toString();
345 if ( current.row() != previous.row() )
346 {
347 clipboardText.append( '\n' );
348 }
349 else if ( current.column() != previous.column() )
350 {
351 clipboardText.append( '\t' );
352 }
353 clipboardText.append( text );
354 previous = current;
355 }
356 mimeData->setData( QStringLiteral( "text/plain" ), clipboardText.toUtf8() );
357 QApplication::clipboard()->setMimeData( mimeData.release() );
358}
359
360void QgsValueMapConfigDlg::addNullButtonPushed()
361{
362 setRow( tableWidget->rowCount() - 1, QgsValueMapFieldFormatter::NULL_VALUE, QStringLiteral( "<NULL>" ) );
363}
364
365void QgsValueMapConfigDlg::loadFromLayerButtonPushed()
366{
367 QgsAttributeTypeLoadDialog layerDialog( layer() );
368 if ( !layerDialog.exec() )
369 return;
370
371 updateMap( layerDialog.valueMap(), layerDialog.insertNull() );
372}
373
374void QgsValueMapConfigDlg::loadFromCSVButtonPushed()
375{
376 const QgsSettings settings;
377
378 const QString fileName = QFileDialog::getOpenFileName( nullptr, tr( "Load Value Map from File" ), QDir::homePath() );
379 if ( fileName.isNull() )
380 return;
381 loadMapFromCSV( fileName );
382}
383
384void QgsValueMapConfigDlg::loadMapFromCSV( const QString &filePath )
385{
386 QFile f( filePath );
387
388 if ( !f.open( QIODevice::ReadOnly ) )
389 {
390 QMessageBox::information( nullptr, tr( "Load Value Map from File" ), tr( "Could not open file %1\nError was: %2" ).arg( filePath, f.errorString() ), QMessageBox::Cancel );
391 return;
392 }
393
394 QTextStream s( &f );
395 s.setAutoDetectUnicode( true );
396
397 const thread_local QRegularExpression re( "(?:^\"|[;,]\")(\"\"|[\\w\\W]*?)(?=\"[;,]|\"$)|(?:^(?!\")|[;,](?!\"))([^;,]*?)(?=$|[;,])|(\\r\\n|\\n)" );
398 QList<QPair<QString, QVariant>> map;
399 while ( !s.atEnd() )
400 {
401 const QString l = s.readLine().trimmed();
402 QRegularExpressionMatchIterator matches = re.globalMatch( l );
403 QStringList ceils;
404 while ( matches.hasNext() && ceils.size() < 2 )
405 {
406 const QRegularExpressionMatch match = matches.next();
407 ceils << match.capturedTexts().last().trimmed().replace( QLatin1String( "\"\"" ), QLatin1String( "\"" ) );
408 }
409
410 if ( ceils.size() != 2 )
411 continue;
412
413 QString key = ceils[0];
414 QString val = ceils[1];
417 map.append( qMakePair( key, val ) );
418 }
419
420 updateMap( map, false );
421}
static QString nullRepresentation()
Returns the string used to represent the value NULL throughout QGIS.
This class should be subclassed for every configurable editor widget type.
int field()
Returns the field for which this configuration widget applies.
QgsVectorLayer * layer()
Returns the layer for which this configuration widget applies.
void changed()
Emitted when the configuration of the widget is changed.
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:53
QgsField field(int fieldIdx) const
Returns the field at particular index (must be in range 0..N-1).
Q_INVOKABLE bool exists(int i) const
Returns if a field index is valid.
This class is a composition of two QSettings instances:
Definition qgssettings.h:64
void loadMapFromCSV(const QString &filePath)
Updates the displayed table with the values from a CSV file.
void setConfig(const QVariantMap &config) override
Update the configuration widget to represent the given configuration.
bool eventFilter(QObject *watched, QEvent *event) override
QgsValueMapConfigDlg(QgsVectorLayer *vl, int fieldIdx, QWidget *parent)
void updateMap(const QMap< QString, QVariant > &map, bool insertNull)
Updates the displayed table with the values from map.
static void populateComboBox(QComboBox *comboBox, const QVariantMap &configuration, bool skipNull)
Populates a comboBox with the appropriate entries based on a value map configuration.
QVariantMap config() override
Create a configuration from the current GUI state.
static const QString NULL_VALUE
Will be saved in the configuration when a value is NULL.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
Represents a vector layer which manages a vector based data sets.