QGIS API Documentation 3.41.0-Master (fda2aa46e9a)
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}
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 )
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
224 if ( reportedErrors.length() < maxOverflowErrors )
225 {
226 reportedErrors.push_back( tr( "Value '%1' has been trimmed (maximum field length: %2)" )
227 .arg( value, QString::number( mappedField.length() ) ) );
228 }
229 else if ( reportedErrors.length() == maxOverflowErrors )
230 {
231 reportedErrors.push_back( tr( "Only first %1 errors have been reported." )
232 .arg( maxOverflowErrors ) );
233 }
234 }
235
236 setRow( row, validValue, pair.second.toString() );
237
238 // Show errors if any
239 if ( !reportedErrors.isEmpty() )
240 {
241 mValueMapErrorsLabel->setVisible( true );
242 mValueMapErrorsLabel->setText( reportedErrors.join( QLatin1String( "<br>" ) ) );
243 }
244 }
245 ++row;
246 }
247}
248
249QString QgsValueMapConfigDlg::checkValueLength( const QString &value )
250{
251 if ( layer()->fields().exists( field() ) )
252 {
253 const QgsField mappedField { layer()->fields().field( field() ) };
254 if ( mappedField.length() > 0 && value.length() > mappedField.length() )
255 {
256 return value.mid( 0, mappedField.length() );
257 }
258 }
259 return value;
260}
261
262void QgsValueMapConfigDlg::populateComboBox( QComboBox *comboBox, const QVariantMap &config, bool skipNull )
263{
264 const QList<QVariant> valueList = config.value( QStringLiteral( "map" ) ).toList();
265
266 if ( !valueList.empty() )
267 {
268 for ( const QVariant &value : valueList )
269 {
270 const QVariantMap valueMap = value.toMap();
271
272 if ( skipNull && valueMap.constBegin().value() == QgsValueMapFieldFormatter::NULL_VALUE )
273 continue;
274
275 comboBox->addItem( valueMap.constBegin().key(), valueMap.constBegin().value() );
276 }
277 }
278 else
279 {
280 const QVariantMap map = config.value( QStringLiteral( "map" ) ).toMap();
281 for ( auto it = map.constBegin(); it != map.constEnd(); ++it )
282 {
283 if ( skipNull && it.value() == QgsValueMapFieldFormatter::NULL_VALUE )
284 continue;
285
286 comboBox->addItem( it.key(), it.value() );
287 }
288 }
289}
290
291bool QgsValueMapConfigDlg::eventFilter( QObject *watched, QEvent *event )
292{
293 Q_UNUSED( watched )
294 if ( event->type() == QEvent::KeyPress )
295 {
296 QKeyEvent *keyEvent = static_cast<QKeyEvent *>( event );
297 if ( keyEvent->matches( QKeySequence::Copy ) )
298 {
299 copySelectionToClipboard();
300 event->accept();
301 return true;
302 }
303 }
304 return false;
305}
306
307void QgsValueMapConfigDlg::setRow( int row, const QString &value, const QString &description )
308{
309 const QgsSettings settings;
310 QTableWidgetItem *valueCell = nullptr;
311 QTableWidgetItem *descriptionCell = new QTableWidgetItem( description );
312 tableWidget->insertRow( row );
314 {
315 QFont cellFont;
316 cellFont.setItalic( true );
317 valueCell = new QTableWidgetItem( QgsApplication::nullRepresentation() );
318 valueCell->setFont( cellFont );
319 valueCell->setFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
320 descriptionCell->setFont( cellFont );
321 }
322 else
323 {
324 valueCell = new QTableWidgetItem( value );
325 }
326 tableWidget->setItem( row, 0, valueCell );
327 tableWidget->setItem( row, 1, descriptionCell );
328}
329
330void QgsValueMapConfigDlg::copySelectionToClipboard()
331{
332 QAbstractItemModel *model = tableWidget->model();
333 QItemSelectionModel *selection = tableWidget->selectionModel();
334 const QModelIndexList indexes = selection->selectedIndexes();
335
336 QString clipboardText;
337 QModelIndex previous = indexes.first();
338 std::unique_ptr<QMimeData> mimeData = std::make_unique<QMimeData>();
339 for ( const QModelIndex &current : indexes )
340 {
341 const QString text = model->data( current ).toString();
342 if ( current.row() != previous.row() )
343 {
344 clipboardText.append( '\n' );
345 }
346 else if ( current.column() != previous.column() )
347 {
348 clipboardText.append( '\t' );
349 }
350 clipboardText.append( text );
351 previous = current;
352 }
353 mimeData->setData( QStringLiteral( "text/plain" ), clipboardText.toUtf8() );
354 QApplication::clipboard()->setMimeData( mimeData.release() );
355}
356
357void QgsValueMapConfigDlg::addNullButtonPushed()
358{
359 setRow( tableWidget->rowCount() - 1, QgsValueMapFieldFormatter::NULL_VALUE, QStringLiteral( "<NULL>" ) );
360}
361
362void QgsValueMapConfigDlg::loadFromLayerButtonPushed()
363{
364 QgsAttributeTypeLoadDialog layerDialog( layer() );
365 if ( !layerDialog.exec() )
366 return;
367
368 updateMap( layerDialog.valueMap(), layerDialog.insertNull() );
369}
370
371void QgsValueMapConfigDlg::loadFromCSVButtonPushed()
372{
373 const QgsSettings settings;
374
375 const QString fileName = QFileDialog::getOpenFileName( nullptr, tr( "Load Value Map from File" ), QDir::homePath() );
376 if ( fileName.isNull() )
377 return;
378 loadMapFromCSV( fileName );
379}
380
381void QgsValueMapConfigDlg::loadMapFromCSV( const QString &filePath )
382{
383 QFile f( filePath );
384
385 if ( !f.open( QIODevice::ReadOnly ) )
386 {
387 QMessageBox::information( nullptr,
388 tr( "Load Value Map from File" ),
389 tr( "Could not open file %1\nError was: %2" ).arg( filePath, f.errorString() ),
390 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.