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