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