QGIS API Documentation 4.1.0-Master (376402f9aeb)
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; i < valueList.count(); i++ )
106 {
107 orderedList.append( qMakePair( valueList[i].toMap().constBegin().value().toString(), valueList[i].toMap().constBegin().key() ) );
108 }
109 }
110 else
111 {
112 const QVariantMap values = config.value( u"map"_s ).toMap();
113 for ( QVariantMap::ConstIterator mit = values.constBegin(); mit != values.constEnd(); mit++ )
114 {
115 if ( QgsVariantUtils::isNull( mit.value() ) )
116 orderedList.append( qMakePair( mit.key(), QVariant() ) );
117 else
118 orderedList.append( qMakePair( mit.value().toString(), mit.key() ) );
119 }
120 }
121
122 updateMap( orderedList, false );
123}
124
125void QgsValueMapConfigDlg::vCellChanged( int row, int column )
126{
127 Q_UNUSED( column )
128 if ( row == tableWidget->rowCount() - 1 )
129 {
130 tableWidget->insertRow( row + 1 );
131 } //else check type
132
133 if ( layer()->fields().exists( field() ) )
134 {
135 // check cell value
136 QTableWidgetItem *item = tableWidget->item( row, 0 );
137 if ( item && item->data( Qt::ItemDataRole::UserRole ) != QgsValueMapFieldFormatter::NULL_VALUE )
138 {
139 const QString validValue = checkValueLength( item->text() );
140 if ( validValue.length() != item->text().length() )
141 {
142 const QString errorMessage = tr( "Value '%1' has been trimmed (maximum field length: %2)" ).arg( item->text(), QString::number( layer()->fields().field( field() ).length() ) );
143 item->setText( validValue );
144 mValueMapErrorsLabel->setVisible( true );
145 mValueMapErrorsLabel->setText( u"%1<br>%2"_s.arg( errorMessage, mValueMapErrorsLabel->text() ) );
146 }
147 }
148 }
149
150 emit changed();
151}
152
153void QgsValueMapConfigDlg::removeSelectedButtonPushed()
154{
155 QList<QTableWidgetItem *> list = tableWidget->selectedItems();
156 QSet<int> rowsToRemove;
157 int removed = 0;
158 int i;
159 for ( i = 0; i < list.size(); i++ )
160 {
161 if ( list[i]->column() == 0 )
162 {
163 const int row = list[i]->row();
164 if ( !rowsToRemove.contains( row ) )
165 {
166 rowsToRemove.insert( row );
167 }
168 }
169 }
170 for ( const int rowToRemoved : rowsToRemove )
171 {
172 tableWidget->removeRow( rowToRemoved - removed );
173 removed++;
174 }
175 emit changed();
176}
177
178void QgsValueMapConfigDlg::updateMap( const QMap<QString, QVariant> &map, bool insertNull )
179{
180 QList<QPair<QString, QVariant>> orderedMap;
181 const auto end = map.constEnd();
182 for ( auto it = map.constBegin(); it != end; ++it )
183 {
184 orderedMap.append( qMakePair( it.key(), it.value() ) );
185 }
186
187 updateMap( orderedMap, insertNull );
188}
189
190void QgsValueMapConfigDlg::updateMap( const QList<QPair<QString, QVariant>> &list, bool insertNull )
191{
192 tableWidget->clearContents();
193 mValueMapErrorsLabel->setVisible( false );
194
195 for ( int i = tableWidget->rowCount() - 1; i > 0; i-- )
196 {
197 tableWidget->removeRow( i );
198 }
199 int row = 0;
200
201 if ( insertNull )
202 {
203 setRow( row, QgsValueMapFieldFormatter::NULL_VALUE, u"<NULL>"_s );
204 ++row;
205 }
206
207 constexpr int maxOverflowErrors { 5 };
208 QStringList reportedErrors;
209 const bool hasField { layer()->fields().exists( field() ) };
210 const QgsField mappedField { hasField ? layer()->fields().field( field() ) : QgsField() };
211
212 for ( const auto &pair : list )
213 {
214 if ( QgsVariantUtils::isNull( pair.second ) )
215 setRow( row, pair.first, QString() );
216 else
217 {
218 const QString value { pair.first };
219 // Check value
220 const QString validValue = checkValueLength( value );
221
222 if ( validValue.length() != value.length() )
223 {
224 if ( reportedErrors.length() < maxOverflowErrors )
225 {
226 reportedErrors.push_back( tr( "Value '%1' has been trimmed (maximum field length: %2)" ).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." ).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( "<br>"_L1 ) );
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( u"map"_s ).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( u"map"_s ).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 QTableWidgetItem *valueCell = nullptr;
313 QTableWidgetItem *descriptionCell = new QTableWidgetItem( description );
314 tableWidget->insertRow( row );
316 {
317 QFont cellFont;
318 cellFont.setItalic( true );
319 valueCell = new QTableWidgetItem( QgsApplication::nullRepresentation() );
320 valueCell->setData( Qt::ItemDataRole::UserRole, QgsValueMapFieldFormatter::NULL_VALUE );
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( u"text/plain"_s, clipboardText.toUtf8() );
357 QApplication::clipboard()->setMimeData( mimeData.release() );
358}
359
360void QgsValueMapConfigDlg::addNullButtonPushed()
361{
362 setRow( tableWidget->rowCount() - 1, QgsValueMapFieldFormatter::NULL_VALUE, u"<NULL>"_s );
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( "\"\""_L1, "\""_L1 );
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.
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.