QGIS API Documentation  3.24.2-Tisler (13c1a02865)
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 
16 #include "qgsvaluemapconfigdlg.h"
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 
31 QgsValueMapConfigDlg::QgsValueMapConfigDlg( QgsVectorLayer *vl, int fieldIdx, QWidget *parent )
32  : QgsEditorConfigWidget( vl, fieldIdx, parent )
33 {
34  setupUi( this );
35 
36  tableWidget->insertRow( 0 );
37 
38  tableWidget->horizontalHeader()->setSectionsClickable( true );
39  tableWidget->setSortingEnabled( true );
40 
41  connect( addNullButton, &QAbstractButton::clicked, this, &QgsValueMapConfigDlg::addNullButtonPushed );
42  connect( removeSelectedButton, &QAbstractButton::clicked, this, &QgsValueMapConfigDlg::removeSelectedButtonPushed );
43  connect( loadFromLayerButton, &QAbstractButton::clicked, this, &QgsValueMapConfigDlg::loadFromLayerButtonPushed );
44  connect( loadFromCSVButton, &QAbstractButton::clicked, this, &QgsValueMapConfigDlg::loadFromCSVButtonPushed );
45  connect( tableWidget, &QTableWidget::cellChanged, this, &QgsValueMapConfigDlg::vCellChanged );
46  tableWidget->installEventFilter( this );
47 }
48 
50 {
51  QList<QVariant> valueList;
52 
53  //store data to map
54  for ( int i = 0; i < tableWidget->rowCount() - 1; i++ )
55  {
56  QTableWidgetItem *ki = tableWidget->item( i, 0 );
57  QTableWidgetItem *vi = tableWidget->item( i, 1 );
58 
59  if ( !ki )
60  continue;
61 
62  QString ks = ki->text();
63  if ( ( ks == QgsApplication::nullRepresentation() ) && !( ki->flags() & Qt::ItemIsEditable ) )
65 
66  QVariantMap value;
67 
68  if ( !vi || vi->text().isNull() )
69  {
70  value.insert( ks, ks );
71  }
72  else
73  {
74  value.insert( vi->text(), ks );
75  }
76  valueList.append( value );
77  }
78 
79  QVariantMap cfg;
80  cfg.insert( QStringLiteral( "map" ), valueList );
81  return cfg;
82 }
83 
84 void QgsValueMapConfigDlg::setConfig( const QVariantMap &config )
85 {
86  tableWidget->clearContents();
87  for ( int i = tableWidget->rowCount() - 1; i > 0; i-- )
88  {
89  tableWidget->removeRow( i );
90  }
91 
92  QList<QVariant> valueList = config.value( QStringLiteral( "map" ) ).toList();
93 
94  if ( valueList.count() > 0 )
95  {
96  for ( int i = 0, row = 0; i < valueList.count(); i++, row++ )
97  {
98  setRow( row, valueList[i].toMap().constBegin().value().toString(), valueList[i].toMap().constBegin().key() );
99  }
100  }
101  else
102  {
103  int row = 0;
104  const QVariantMap values = config.value( QStringLiteral( "map" ) ).toMap();
105  for ( QVariantMap::ConstIterator mit = values.constBegin(); mit != values.constEnd(); mit++, row++ )
106  {
107  if ( mit.value().isNull() )
108  setRow( row, mit.key(), QString() );
109  else
110  setRow( row, mit.value().toString(), mit.key() );
111  }
112  }
113 }
114 
115 void QgsValueMapConfigDlg::vCellChanged( int row, int column )
116 {
117  Q_UNUSED( column )
118  if ( row == tableWidget->rowCount() - 1 )
119  {
120  tableWidget->insertRow( row + 1 );
121  } //else check type
122 
123  emit changed();
124 }
125 
126 void QgsValueMapConfigDlg::removeSelectedButtonPushed()
127 {
128  QList<QTableWidgetItem *> list = tableWidget->selectedItems();
129  QSet<int> rowsToRemove;
130  int removed = 0;
131  int i;
132  for ( i = 0; i < list.size(); i++ )
133  {
134  if ( list[i]->column() == 0 )
135  {
136  const int row = list[i]->row();
137  if ( !rowsToRemove.contains( row ) )
138  {
139  rowsToRemove.insert( row );
140  }
141  }
142  }
143  for ( i = 0; i < rowsToRemove.size(); i++ )
144  {
145  tableWidget->removeRow( rowsToRemove.values().at( i ) - removed );
146  removed++;
147  }
148  emit changed();
149 }
150 
151 void QgsValueMapConfigDlg::updateMap( const QMap<QString, QVariant> &map, bool insertNull )
152 {
153  QList<QPair<QString, QVariant>> orderedMap;
154  const auto end = map.constEnd();
155  for ( auto it = map.constBegin(); it != end; ++it )
156  {
157  orderedMap.append( qMakePair( it.key(), it.value() ) );
158  }
159 
160  updateMap( orderedMap, insertNull );
161 }
162 
163 void QgsValueMapConfigDlg::updateMap( const QList<QPair<QString, QVariant>> &list, bool insertNull )
164 {
165  tableWidget->clearContents();
166  for ( int i = tableWidget->rowCount() - 1; i > 0; i-- )
167  {
168  tableWidget->removeRow( i );
169  }
170  int row = 0;
171 
172  if ( insertNull )
173  {
174  setRow( row, QgsValueMapFieldFormatter::NULL_VALUE, QStringLiteral( "<NULL>" ) );
175  ++row;
176  }
177 
178  for ( const auto &pair : list )
179  {
180  if ( pair.second.isNull() )
181  setRow( row, pair.first, QString() );
182  else
183  setRow( row, pair.first, pair.second.toString() );
184  ++row;
185  }
186 }
187 
188 void QgsValueMapConfigDlg::populateComboBox( QComboBox *comboBox, const QVariantMap &config, bool skipNull )
189 {
190  const QList<QVariant> valueList = config.value( QStringLiteral( "map" ) ).toList();
191 
192  if ( !valueList.empty() )
193  {
194  for ( const QVariant &value : valueList )
195  {
196  const QVariantMap valueMap = value.toMap();
197 
198  if ( skipNull && valueMap.constBegin().value() == QgsValueMapFieldFormatter::NULL_VALUE )
199  continue;
200 
201  comboBox->addItem( valueMap.constBegin().key(), valueMap.constBegin().value() );
202  }
203  }
204  else
205  {
206  const QVariantMap map = config.value( QStringLiteral( "map" ) ).toMap();
207  for ( auto it = map.constBegin(); it != map.constEnd(); ++it )
208  {
209  if ( skipNull && it.value() == QgsValueMapFieldFormatter::NULL_VALUE )
210  continue;
211 
212  comboBox->addItem( it.key(), it.value() );
213  }
214  }
215 }
216 
217 bool QgsValueMapConfigDlg::eventFilter( QObject *watched, QEvent *event )
218 {
219  Q_UNUSED( watched )
220  if ( event->type() == QEvent::KeyPress )
221  {
222  QKeyEvent *keyEvent = static_cast<QKeyEvent *>( event );
223  if ( keyEvent->matches( QKeySequence::Copy ) )
224  {
225  copySelectionToClipboard();
226  event->accept();
227  return true;
228  }
229  }
230  return false;
231 }
232 
233 void QgsValueMapConfigDlg::setRow( int row, const QString &value, const QString &description )
234 {
235  const QgsSettings settings;
236  QTableWidgetItem *valueCell = nullptr;
237  QTableWidgetItem *descriptionCell = new QTableWidgetItem( description );
238  tableWidget->insertRow( row );
240  {
241  QFont cellFont;
242  cellFont.setItalic( true );
243  valueCell = new QTableWidgetItem( QgsApplication::nullRepresentation() );
244  valueCell->setFont( cellFont );
245  valueCell->setFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
246  descriptionCell->setFont( cellFont );
247  }
248  else
249  {
250  valueCell = new QTableWidgetItem( value );
251  }
252  tableWidget->setItem( row, 0, valueCell );
253  tableWidget->setItem( row, 1, descriptionCell );
254 }
255 
256 void QgsValueMapConfigDlg::copySelectionToClipboard()
257 {
258  QAbstractItemModel *model = tableWidget->model();
259  QItemSelectionModel *selection = tableWidget->selectionModel();
260  const QModelIndexList indexes = selection->selectedIndexes();
261 
262  QString clipboardText;
263  QModelIndex previous = indexes.first();
264  std::unique_ptr<QMimeData> mimeData = std::make_unique<QMimeData>();
265  for ( const QModelIndex &current : indexes )
266  {
267  const QString text = model->data( current ).toString();
268  if ( current.row() != previous.row() )
269  {
270  clipboardText.append( '\n' );
271  }
272  else if ( current.column() != previous.column() )
273  {
274  clipboardText.append( '\t' );
275  }
276  clipboardText.append( text );
277  previous = current;
278  }
279  mimeData->setData( QStringLiteral( "text/plain" ), clipboardText.toUtf8() );
280  QApplication::clipboard()->setMimeData( mimeData.release() );
281 }
282 
283 void QgsValueMapConfigDlg::addNullButtonPushed()
284 {
285  setRow( tableWidget->rowCount() - 1, QgsValueMapFieldFormatter::NULL_VALUE, QStringLiteral( "<NULL>" ) );
286 }
287 
288 void QgsValueMapConfigDlg::loadFromLayerButtonPushed()
289 {
290  QgsAttributeTypeLoadDialog layerDialog( layer() );
291  if ( !layerDialog.exec() )
292  return;
293 
294  updateMap( layerDialog.valueMap(), layerDialog.insertNull() );
295 }
296 
297 void QgsValueMapConfigDlg::loadFromCSVButtonPushed()
298 {
299  const QgsSettings settings;
300 
301  const QString fileName = QFileDialog::getOpenFileName( nullptr, tr( "Load Value Map from File" ), QDir::homePath() );
302  if ( fileName.isNull() )
303  return;
304  loadMapFromCSV( fileName );
305 }
306 
307 void QgsValueMapConfigDlg::loadMapFromCSV( const QString &filePath )
308 {
309  QFile f( filePath );
310 
311  if ( !f.open( QIODevice::ReadOnly ) )
312  {
313  QMessageBox::information( nullptr,
314  tr( "Load Value Map from File" ),
315  tr( "Could not open file %1\nError was: %2" ).arg( filePath, f.errorString() ),
316  QMessageBox::Cancel );
317  return;
318  }
319 
320  QTextStream s( &f );
321  s.setAutoDetectUnicode( true );
322 
323  const thread_local QRegularExpression re( "(?:^\"|[;,]\")(\"\"|[\\w\\W]*?)(?=\"[;,]|\"$)|(?:^(?!\")|[;,](?!\"))([^;,]*?)(?=$|[;,])|(\\r\\n|\\n)" );
324  QList<QPair<QString, QVariant>> map;
325  while ( !s.atEnd() )
326  {
327  const QString l = s.readLine().trimmed();
328  QRegularExpressionMatchIterator matches = re.globalMatch( l );
329  QStringList ceils;
330  while ( matches.hasNext() && ceils.size() < 2 )
331  {
332  const QRegularExpressionMatch match = matches.next();
333  ceils << match.capturedTexts().last().trimmed().replace( QLatin1String( "\"\"" ), QLatin1String( "\"" ) );
334  }
335 
336  if ( ceils.size() != 2 )
337  continue;
338 
339  QString key = ceils[0];
340  QString val = ceils[1];
341  if ( key == QgsApplication::nullRepresentation() )
343  map.append( qMakePair( key, val ) );
344  }
345 
346  updateMap( map, false );
347 }
static QString nullRepresentation()
This string is used to represent the value NULL throughout QGIS.
This class should be subclassed for every configurable editor widget type.
QgsVectorLayer * layer()
Returns the layer for which this configuration widget applies.
void changed()
Emitted when the configuration of the widget is changed.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:62
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.
Represents a vector layer which manages a vector based data sets.