QGIS API Documentation 3.41.0-Master (af5edcb665c)
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#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{
249 if ( layer()->fields().exists( field() ) )
250 {
251 const QgsField mappedField { layer()->fields().field( field() ) };
252 if ( mappedField.length() > 0 && value.length() > mappedField.length() )
253 {
254 return value.mid( 0, mappedField.length() );
255 }
256 }
257 return value;
258}
259
260void QgsValueMapConfigDlg::populateComboBox( QComboBox *comboBox, const QVariantMap &config, bool skipNull )
261{
262 const QList<QVariant> valueList = config.value( QStringLiteral( "map" ) ).toList();
263
264 if ( !valueList.empty() )
265 {
266 for ( const QVariant &value : valueList )
267 {
268 const QVariantMap valueMap = value.toMap();
269
270 if ( skipNull && valueMap.constBegin().value() == QgsValueMapFieldFormatter::NULL_VALUE )
271 continue;
272
273 comboBox->addItem( valueMap.constBegin().key(), valueMap.constBegin().value() );
274 }
275 }
276 else
277 {
278 const QVariantMap map = config.value( QStringLiteral( "map" ) ).toMap();
279 for ( auto it = map.constBegin(); it != map.constEnd(); ++it )
280 {
281 if ( skipNull && it.value() == QgsValueMapFieldFormatter::NULL_VALUE )
282 continue;
283
284 comboBox->addItem( it.key(), it.value() );
285 }
286 }
287}
288
289bool QgsValueMapConfigDlg::eventFilter( QObject *watched, QEvent *event )
290{
291 Q_UNUSED( watched )
292 if ( event->type() == QEvent::KeyPress )
293 {
294 QKeyEvent *keyEvent = static_cast<QKeyEvent *>( event );
295 if ( keyEvent->matches( QKeySequence::Copy ) )
296 {
297 copySelectionToClipboard();
298 event->accept();
299 return true;
300 }
301 }
302 return false;
303}
304
305void QgsValueMapConfigDlg::setRow( int row, const QString &value, const QString &description )
306{
307 const QgsSettings settings;
308 QTableWidgetItem *valueCell = nullptr;
309 QTableWidgetItem *descriptionCell = new QTableWidgetItem( description );
310 tableWidget->insertRow( row );
312 {
313 QFont cellFont;
314 cellFont.setItalic( true );
315 valueCell = new QTableWidgetItem( QgsApplication::nullRepresentation() );
316 valueCell->setFont( cellFont );
317 valueCell->setFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
318 descriptionCell->setFont( cellFont );
319 }
320 else
321 {
322 valueCell = new QTableWidgetItem( value );
323 }
324 tableWidget->setItem( row, 0, valueCell );
325 tableWidget->setItem( row, 1, descriptionCell );
326}
327
328void QgsValueMapConfigDlg::copySelectionToClipboard()
329{
330 QAbstractItemModel *model = tableWidget->model();
331 QItemSelectionModel *selection = tableWidget->selectionModel();
332 const QModelIndexList indexes = selection->selectedIndexes();
333
334 QString clipboardText;
335 QModelIndex previous = indexes.first();
336 std::unique_ptr<QMimeData> mimeData = std::make_unique<QMimeData>();
337 for ( const QModelIndex &current : indexes )
338 {
339 const QString text = model->data( current ).toString();
340 if ( current.row() != previous.row() )
341 {
342 clipboardText.append( '\n' );
343 }
344 else if ( current.column() != previous.column() )
345 {
346 clipboardText.append( '\t' );
347 }
348 clipboardText.append( text );
349 previous = current;
350 }
351 mimeData->setData( QStringLiteral( "text/plain" ), clipboardText.toUtf8() );
352 QApplication::clipboard()->setMimeData( mimeData.release() );
353}
354
355void QgsValueMapConfigDlg::addNullButtonPushed()
356{
357 setRow( tableWidget->rowCount() - 1, QgsValueMapFieldFormatter::NULL_VALUE, QStringLiteral( "<NULL>" ) );
358}
359
360void QgsValueMapConfigDlg::loadFromLayerButtonPushed()
361{
362 QgsAttributeTypeLoadDialog layerDialog( layer() );
363 if ( !layerDialog.exec() )
364 return;
365
366 updateMap( layerDialog.valueMap(), layerDialog.insertNull() );
367}
368
369void QgsValueMapConfigDlg::loadFromCSVButtonPushed()
370{
371 const QgsSettings settings;
372
373 const QString fileName = QFileDialog::getOpenFileName( nullptr, tr( "Load Value Map from File" ), QDir::homePath() );
374 if ( fileName.isNull() )
375 return;
376 loadMapFromCSV( fileName );
377}
378
379void QgsValueMapConfigDlg::loadMapFromCSV( const QString &filePath )
380{
381 QFile f( filePath );
382
383 if ( !f.open( QIODevice::ReadOnly ) )
384 {
385 QMessageBox::information( nullptr, tr( "Load Value Map from File" ), tr( "Could not open file %1\nError was: %2" ).arg( filePath, f.errorString() ), QMessageBox::Cancel );
386 return;
387 }
388
389 QTextStream s( &f );
390 s.setAutoDetectUnicode( true );
391
392 const thread_local QRegularExpression re( "(?:^\"|[;,]\")(\"\"|[\\w\\W]*?)(?=\"[;,]|\"$)|(?:^(?!\")|[;,](?!\"))([^;,]*?)(?=$|[;,])|(\\r\\n|\\n)" );
393 QList<QPair<QString, QVariant>> map;
394 while ( !s.atEnd() )
395 {
396 const QString l = s.readLine().trimmed();
397 QRegularExpressionMatchIterator matches = re.globalMatch( l );
398 QStringList ceils;
399 while ( matches.hasNext() && ceils.size() < 2 )
400 {
401 const QRegularExpressionMatch match = matches.next();
402 ceils << match.capturedTexts().last().trimmed().replace( QLatin1String( "\"\"" ), QLatin1String( "\"" ) );
403 }
404
405 if ( ceils.size() != 2 )
406 continue;
407
408 QString key = ceils[0];
409 QString val = ceils[1];
412 map.append( qMakePair( key, val ) );
413 }
414
415 updateMap( map, false );
416}
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.