QGIS API Documentation 3.39.0-Master (3783037d301)
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
20#include "qgsapplication.h"
21#include "qgssettings.h"
22
23#include <QFileDialog>
24#include <QMessageBox>
25#include <QTextStream>
26#include <QClipboard>
27#include <QKeyEvent>
28#include <QMimeData>
29#include <QRegularExpression>
30
32 : QgsEditorConfigWidget( vl, fieldIdx, parent )
33{
34 setupUi( this );
35
36 mValueMapErrorsLabel->setVisible( false );
37 mValueMapErrorsLabel->setStyleSheet( QStringLiteral( "QLabel { color : red; }" ) );
38
39 tableWidget->insertRow( 0 );
40
41 tableWidget->horizontalHeader()->setSectionsClickable( true );
42 tableWidget->setSortingEnabled( true );
43
44 connect( addNullButton, &QAbstractButton::clicked, this, &QgsValueMapConfigDlg::addNullButtonPushed );
45 connect( removeSelectedButton, &QAbstractButton::clicked, this, &QgsValueMapConfigDlg::removeSelectedButtonPushed );
46 connect( loadFromLayerButton, &QAbstractButton::clicked, this, &QgsValueMapConfigDlg::loadFromLayerButtonPushed );
47 connect( loadFromCSVButton, &QAbstractButton::clicked, this, &QgsValueMapConfigDlg::loadFromCSVButtonPushed );
48 connect( tableWidget, &QTableWidget::cellChanged, this, &QgsValueMapConfigDlg::vCellChanged );
49 tableWidget->installEventFilter( this );
50}
51
53{
54 QList<QVariant> valueList;
55
56 //store data to map
57 for ( int i = 0; i < tableWidget->rowCount() - 1; i++ )
58 {
59 QTableWidgetItem *ki = tableWidget->item( i, 0 );
60 QTableWidgetItem *vi = tableWidget->item( i, 1 );
61
62 if ( !ki )
63 continue;
64
65 QString ks = ki->text();
66 if ( ( ks == QgsApplication::nullRepresentation() ) && !( ki->flags() & Qt::ItemIsEditable ) )
68
69 QVariantMap value;
70
71 if ( !vi || vi->text().isNull() )
72 {
73 value.insert( ks, ks );
74 }
75 else
76 {
77 value.insert( vi->text(), ks );
78 }
79 valueList.append( value );
80 }
81
82 QVariantMap cfg;
83 cfg.insert( QStringLiteral( "map" ), valueList );
84 return cfg;
85}
86
87void QgsValueMapConfigDlg::setConfig( const QVariantMap &config )
88{
89 tableWidget->clearContents();
90 for ( int i = tableWidget->rowCount() - 1; i > 0; i-- )
91 {
92 tableWidget->removeRow( i );
93 }
94
95 QList<QVariant> valueList = config.value( QStringLiteral( "map" ) ).toList();
96 QList<QPair<QString, QVariant>> orderedList;
97
98 if ( valueList.count() > 0 )
99 {
100 for ( int i = 0, row = 0; i < valueList.count(); i++, row++ )
101 {
102 orderedList.append( qMakePair( valueList[i].toMap().constBegin().value().toString(), valueList[i].toMap().constBegin().key() ) );
103 }
104 }
105 else
106 {
107 int row = 0;
108 const QVariantMap values = config.value( QStringLiteral( "map" ) ).toMap();
109 for ( QVariantMap::ConstIterator mit = values.constBegin(); mit != values.constEnd(); mit++, row++ )
110 {
111 if ( QgsVariantUtils::isNull( mit.value() ) )
112 orderedList.append( qMakePair( mit.key(), QVariant() ) );
113 else
114 orderedList.append( qMakePair( mit.value().toString(), mit.key() ) );
115 }
116 }
117
118 updateMap( orderedList, false );
119
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
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{
250 if ( layer()->fields().exists( field() ) )
251 {
252 const QgsField mappedField { layer()->fields().field( field() ) };
253 if ( mappedField.length() > 0 && value.length() > mappedField.length() )
254 {
255 return value.mid( 0, mappedField.length() );
256 }
257 }
258 return value;
259}
260
261void QgsValueMapConfigDlg::populateComboBox( QComboBox *comboBox, const QVariantMap &config, bool skipNull )
262{
263 const QList<QVariant> valueList = config.value( QStringLiteral( "map" ) ).toList();
264
265 if ( !valueList.empty() )
266 {
267 for ( const QVariant &value : valueList )
268 {
269 const QVariantMap valueMap = value.toMap();
270
271 if ( skipNull && valueMap.constBegin().value() == QgsValueMapFieldFormatter::NULL_VALUE )
272 continue;
273
274 comboBox->addItem( valueMap.constBegin().key(), valueMap.constBegin().value() );
275 }
276 }
277 else
278 {
279 const QVariantMap map = config.value( QStringLiteral( "map" ) ).toMap();
280 for ( auto it = map.constBegin(); it != map.constEnd(); ++it )
281 {
282 if ( skipNull && it.value() == QgsValueMapFieldFormatter::NULL_VALUE )
283 continue;
284
285 comboBox->addItem( it.key(), it.value() );
286 }
287 }
288}
289
290bool QgsValueMapConfigDlg::eventFilter( QObject *watched, QEvent *event )
291{
292 Q_UNUSED( watched )
293 if ( event->type() == QEvent::KeyPress )
294 {
295 QKeyEvent *keyEvent = static_cast<QKeyEvent *>( event );
296 if ( keyEvent->matches( QKeySequence::Copy ) )
297 {
298 copySelectionToClipboard();
299 event->accept();
300 return true;
301 }
302 }
303 return false;
304}
305
306void QgsValueMapConfigDlg::setRow( int row, const QString &value, const QString &description )
307{
308 const QgsSettings settings;
309 QTableWidgetItem *valueCell = nullptr;
310 QTableWidgetItem *descriptionCell = new QTableWidgetItem( description );
311 tableWidget->insertRow( row );
313 {
314 QFont cellFont;
315 cellFont.setItalic( true );
316 valueCell = new QTableWidgetItem( QgsApplication::nullRepresentation() );
317 valueCell->setFont( cellFont );
318 valueCell->setFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
319 descriptionCell->setFont( cellFont );
320 }
321 else
322 {
323 valueCell = new QTableWidgetItem( value );
324 }
325 tableWidget->setItem( row, 0, valueCell );
326 tableWidget->setItem( row, 1, descriptionCell );
327}
328
329void QgsValueMapConfigDlg::copySelectionToClipboard()
330{
331 QAbstractItemModel *model = tableWidget->model();
332 QItemSelectionModel *selection = tableWidget->selectionModel();
333 const QModelIndexList indexes = selection->selectedIndexes();
334
335 QString clipboardText;
336 QModelIndex previous = indexes.first();
337 std::unique_ptr<QMimeData> mimeData = std::make_unique<QMimeData>();
338 for ( const QModelIndex &current : indexes )
339 {
340 const QString text = model->data( current ).toString();
341 if ( current.row() != previous.row() )
342 {
343 clipboardText.append( '\n' );
344 }
345 else if ( current.column() != previous.column() )
346 {
347 clipboardText.append( '\t' );
348 }
349 clipboardText.append( text );
350 previous = current;
351 }
352 mimeData->setData( QStringLiteral( "text/plain" ), clipboardText.toUtf8() );
353 QApplication::clipboard()->setMimeData( mimeData.release() );
354}
355
356void QgsValueMapConfigDlg::addNullButtonPushed()
357{
358 setRow( tableWidget->rowCount() - 1, QgsValueMapFieldFormatter::NULL_VALUE, QStringLiteral( "<NULL>" ) );
359}
360
361void QgsValueMapConfigDlg::loadFromLayerButtonPushed()
362{
363 QgsAttributeTypeLoadDialog layerDialog( layer() );
364 if ( !layerDialog.exec() )
365 return;
366
367 updateMap( layerDialog.valueMap(), layerDialog.insertNull() );
368}
369
370void QgsValueMapConfigDlg::loadFromCSVButtonPushed()
371{
372 const QgsSettings settings;
373
374 const QString fileName = QFileDialog::getOpenFileName( nullptr, tr( "Load Value Map from File" ), QDir::homePath() );
375 if ( fileName.isNull() )
376 return;
377 loadMapFromCSV( fileName );
378}
379
380void QgsValueMapConfigDlg::loadMapFromCSV( const QString &filePath )
381{
382 QFile f( filePath );
383
384 if ( !f.open( QIODevice::ReadOnly ) )
385 {
386 QMessageBox::information( nullptr,
387 tr( "Load Value Map from File" ),
388 tr( "Could not open file %1\nError was: %2" ).arg( filePath, f.errorString() ),
389 QMessageBox::Cancel );
390 return;
391 }
392
393 QTextStream s( &f );
394 s.setAutoDetectUnicode( true );
395
396 const thread_local QRegularExpression re( "(?:^\"|[;,]\")(\"\"|[\\w\\W]*?)(?=\"[;,]|\"$)|(?:^(?!\")|[;,](?!\"))([^;,]*?)(?=$|[;,])|(\\r\\n|\\n)" );
397 QList<QPair<QString, QVariant>> map;
398 while ( !s.atEnd() )
399 {
400 const QString l = s.readLine().trimmed();
401 QRegularExpressionMatchIterator matches = re.globalMatch( l );
402 QStringList ceils;
403 while ( matches.hasNext() && ceils.size() < 2 )
404 {
405 const QRegularExpressionMatch match = matches.next();
406 ceils << match.capturedTexts().last().trimmed().replace( QLatin1String( "\"\"" ), QLatin1String( "\"" ) );
407 }
408
409 if ( ceils.size() != 2 )
410 continue;
411
412 QString key = ceils[0];
413 QString val = ceils[1];
416 map.append( qMakePair( key, val ) );
417 }
418
419 updateMap( map, false );
420}
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.