QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
qgssourcefieldsproperties.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgssourcefieldsproperties.cpp
3  ---------------------
4  begin : July 2017
5  copyright : (C) 2017 by David Signer
6  email : david at opengis dot ch
7 
8  ***************************************************************************
9  * *
10  * This program is free software; you can redistribute it and/or modify *
11  * it under the terms of the GNU General Public License as published by *
12  * the Free Software Foundation; either version 2 of the License, or *
13  * (at your option) any later version. *
14  * *
15  ***************************************************************************/
16 
17 #include "qgsaddattrdialog.h"
18 #include "qgscheckablecombobox.h"
20 #include "qgsvectorlayer.h"
21 #include "qgsproject.h"
22 #include "qgsapplication.h"
24 #include "qgsgui.h"
25 #include "qgsnative.h"
26 
27 
29  : QWidget( parent )
30  , mLayer( layer )
31 {
32  if ( !layer )
33  return;
34 
35  setupUi( this );
36  layout()->setContentsMargins( 0, 0, 0, 0 );
37 
38  //button appearance
39  mAddAttributeButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionNewAttribute.svg" ) ) );
40  mDeleteAttributeButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDeleteAttribute.svg" ) ) );
41  mToggleEditingButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ) );
42  mCalculateFieldButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCalculateField.svg" ) ) );
43 
44  //button signals
45  connect( mToggleEditingButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::toggleEditing );
46  connect( mAddAttributeButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::addAttributeClicked );
47  connect( mDeleteAttributeButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::deleteAttributeClicked );
48  connect( mCalculateFieldButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::calculateFieldClicked );
49 
50  //slots
51  connect( mLayer, &QgsVectorLayer::editingStarted, this, &QgsSourceFieldsProperties::editingToggled );
52  connect( mLayer, &QgsVectorLayer::editingStopped, this, &QgsSourceFieldsProperties::editingToggled );
53  connect( mLayer, &QgsVectorLayer::attributeAdded, this, &QgsSourceFieldsProperties::attributeAdded );
54  connect( mLayer, &QgsVectorLayer::attributeDeleted, this, &QgsSourceFieldsProperties::attributeDeleted );
55 
56  //field list appearance
57  mFieldsList->setColumnCount( AttrColCount );
58  mFieldsList->setSelectionBehavior( QAbstractItemView::SelectRows );
59  mFieldsList->setDragDropMode( QAbstractItemView::DragOnly );
60  mFieldsList->setHorizontalHeaderItem( AttrIdCol, new QTableWidgetItem( tr( "Id" ) ) );
61  mFieldsList->setHorizontalHeaderItem( AttrNameCol, new QTableWidgetItem( tr( "Name" ) ) );
62  mFieldsList->setHorizontalHeaderItem( AttrTypeCol, new QTableWidgetItem( tr( "Type" ) ) );
63  mFieldsList->setHorizontalHeaderItem( AttrTypeNameCol, new QTableWidgetItem( tr( "Type name" ) ) );
64  mFieldsList->setHorizontalHeaderItem( AttrLengthCol, new QTableWidgetItem( tr( "Length" ) ) );
65  mFieldsList->setHorizontalHeaderItem( AttrPrecCol, new QTableWidgetItem( tr( "Precision" ) ) );
66  mFieldsList->setHorizontalHeaderItem( AttrCommentCol, new QTableWidgetItem( tr( "Comment" ) ) );
67  const auto configurationFlagsWi = new QTableWidgetItem( tr( "Configuration" ) );
68  configurationFlagsWi->setToolTip( tr( "Configures the field" ) );
69  mFieldsList->setHorizontalHeaderItem( AttrConfigurationFlagsCol, configurationFlagsWi );
70  mFieldsList->setHorizontalHeaderItem( AttrAliasCol, new QTableWidgetItem( tr( "Alias" ) ) );
71 
72  mFieldsList->setSortingEnabled( true );
73  mFieldsList->sortByColumn( 0, Qt::AscendingOrder );
74  mFieldsList->setSelectionBehavior( QAbstractItemView::SelectRows );
75  mFieldsList->setSelectionMode( QAbstractItemView::ExtendedSelection );
76  mFieldsList->horizontalHeader()->setStretchLastSection( true );
77  mFieldsList->verticalHeader()->hide();
78 
79  //load buttons and field list
80  updateButtons();
81 }
82 
84 {
85  loadRows();
86 }
87 
89 {
90  disconnect( mFieldsList, &QTableWidget::cellChanged, this, &QgsSourceFieldsProperties::attributesListCellChanged );
91  const QgsFields &fields = mLayer->fields();
92 
93  mIndexedWidgets.clear();
94  mFieldsList->setRowCount( 0 );
95 
96  for ( int i = 0; i < fields.count(); ++i )
97  attributeAdded( i );
98 
99  mFieldsList->resizeColumnsToContents();
100  connect( mFieldsList, &QTableWidget::cellChanged, this, &QgsSourceFieldsProperties::attributesListCellChanged );
101 
102  connect( mFieldsList, &QTableWidget::cellPressed, this, &QgsSourceFieldsProperties::attributesListCellPressed );
103 
104  updateButtons();
105  updateFieldRenamingStatus();
106 }
107 
108 void QgsSourceFieldsProperties::updateFieldRenamingStatus()
109 {
110  const bool canRenameFields = mLayer->isEditable() && ( mLayer->dataProvider()->capabilities() & QgsVectorDataProvider::RenameAttributes ) && !mLayer->readOnly();
111 
112  for ( int row = 0; row < mFieldsList->rowCount(); ++row )
113  {
114  if ( canRenameFields )
115  mFieldsList->item( row, AttrNameCol )->setFlags( mFieldsList->item( row, AttrNameCol )->flags() | Qt::ItemIsEditable );
116  else
117  mFieldsList->item( row, AttrNameCol )->setFlags( mFieldsList->item( row, AttrNameCol )->flags() & ~Qt::ItemIsEditable );
118  }
119 }
120 
121 void QgsSourceFieldsProperties::updateExpression()
122 {
123  QToolButton *btn = qobject_cast<QToolButton *>( sender() );
124  Q_ASSERT( btn );
125 
126  const int index = btn->property( "Index" ).toInt();
127 
128  const QString exp = mLayer->expressionField( index );
129 
130  QgsExpressionContext context;
133 
134  QgsExpressionBuilderDialog dlg( mLayer, exp, nullptr, QStringLiteral( "generic" ), context );
135 
136  if ( dlg.exec() )
137  {
138  mLayer->updateExpressionField( index, dlg.expressionText() );
139  loadRows();
140  }
141 }
142 
143 void QgsSourceFieldsProperties::attributeAdded( int idx )
144 {
145  const bool sorted = mFieldsList->isSortingEnabled();
146  if ( sorted )
147  mFieldsList->setSortingEnabled( false );
148 
149  const QgsFields &fields = mLayer->fields();
150  const int row = mFieldsList->rowCount();
151  mFieldsList->insertRow( row );
152  setRow( row, idx, fields.at( idx ) );
153  mFieldsList->setCurrentCell( row, idx );
154 
155  const QColor expressionColor = QColor( 103, 0, 243, 44 );
156  const QColor joinColor = QColor( 0, 243, 79, 44 );
157  const QColor defaultColor = QColor( 252, 255, 79, 44 );
158 
159  for ( int i = 0; i < mFieldsList->columnCount(); i++ )
160  {
161  if ( i == AttrConfigurationFlagsCol )
162  continue;
163 
164  switch ( mLayer->fields().fieldOrigin( idx ) )
165  {
167  if ( i == 7 ) continue;
168  mFieldsList->item( row, i )->setBackground( expressionColor );
169  break;
170 
172  mFieldsList->item( row, i )->setBackground( joinColor );
173  break;
174 
175  default:
176  if ( defaultColor.isValid() )
177  mFieldsList->item( row, i )->setBackground( defaultColor );
178  break;
179  }
180  }
181 
182  if ( sorted )
183  mFieldsList->setSortingEnabled( true );
184 }
185 
186 
187 void QgsSourceFieldsProperties::attributeDeleted( int idx )
188 {
189  mFieldsList->removeRow( mIndexedWidgets.at( idx )->row() );
190  mIndexedWidgets.removeAt( idx );
191  for ( int i = idx; i < mIndexedWidgets.count(); i++ )
192  {
193  mIndexedWidgets.at( i )->setData( Qt::DisplayRole, i );
194  }
195 }
196 
197 void QgsSourceFieldsProperties::setRow( int row, int idx, const QgsField &field )
198 {
199  QTableWidgetItem *dataItem = new QTableWidgetItem();
200  dataItem->setData( Qt::DisplayRole, idx );
201  dataItem->setIcon( mLayer->fields().iconForField( idx, true ) );
202  mFieldsList->setItem( row, AttrIdCol, dataItem );
203 
204  // in case we insert and not append reindex remaining widgets by 1
205  for ( int i = idx + 1; i < mIndexedWidgets.count(); i++ )
206  mIndexedWidgets.at( i )->setData( Qt::DisplayRole, i );
207  mIndexedWidgets.insert( idx, mFieldsList->item( row, 0 ) );
208 
209  mFieldsList->setItem( row, AttrNameCol, new QTableWidgetItem( field.name() ) );
210  mFieldsList->setItem( row, AttrAliasCol, new QTableWidgetItem( field.alias() ) );
211  mFieldsList->setItem( row, AttrTypeCol, new QTableWidgetItem( QVariant::typeToName( field.type() ) ) );
212  mFieldsList->setItem( row, AttrTypeNameCol, new QTableWidgetItem( field.typeName() ) );
213  mFieldsList->setItem( row, AttrLengthCol, new QTableWidgetItem( QString::number( field.length() ) ) );
214  mFieldsList->setItem( row, AttrPrecCol, new QTableWidgetItem( QString::number( field.precision() ) ) );
215 
217  {
218  QWidget *expressionWidget = new QWidget;
219  expressionWidget->setLayout( new QHBoxLayout );
220  QToolButton *editExpressionButton = new QToolButton;
221  editExpressionButton->setProperty( "Index", idx );
222  editExpressionButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconExpression.svg" ) ) );
223  connect( editExpressionButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::updateExpression );
224  expressionWidget->layout()->setContentsMargins( 0, 0, 0, 0 );
225  expressionWidget->layout()->addWidget( editExpressionButton );
226  expressionWidget->layout()->addWidget( new QLabel( mLayer->expressionField( idx ) ) );
227  expressionWidget->setStyleSheet( "background-color: rgb( 200, 200, 255 )" );
228  mFieldsList->setCellWidget( row, AttrCommentCol, expressionWidget );
229  }
230  else
231  {
232  mFieldsList->setItem( row, AttrCommentCol, new QTableWidgetItem( field.comment() ) );
233  }
234 
235  QList<int> notEditableCols = QList<int>()
236  << AttrIdCol
237  << AttrNameCol
238  << AttrAliasCol
239  << AttrTypeCol
240  << AttrTypeNameCol
241  << AttrLengthCol
242  << AttrPrecCol
243  << AttrCommentCol;
244 
245  const auto constNotEditableCols = notEditableCols;
246  for ( const int i : constNotEditableCols )
247  {
248  if ( notEditableCols[i] != AttrCommentCol || mLayer->fields().fieldOrigin( idx ) != QgsFields::OriginExpression )
249  mFieldsList->item( row, i )->setFlags( mFieldsList->item( row, i )->flags() & ~Qt::ItemIsEditable );
250  if ( notEditableCols[i] == AttrAliasCol )
251  mFieldsList->item( row, i )->setToolTip( tr( "Edit alias in the Form config tab" ) );
252  }
253  const bool canRenameFields = mLayer->isEditable() && ( mLayer->dataProvider()->capabilities() & QgsVectorDataProvider::RenameAttributes ) && !mLayer->readOnly();
254  if ( canRenameFields )
255  mFieldsList->item( row, AttrNameCol )->setFlags( mFieldsList->item( row, AttrNameCol )->flags() | Qt::ItemIsEditable );
256  else
257  mFieldsList->item( row, AttrNameCol )->setFlags( mFieldsList->item( row, AttrNameCol )->flags() & ~Qt::ItemIsEditable );
258 
259  // Flags
260  QgsCheckableComboBox *cb = new QgsCheckableComboBox( mFieldsList );
261  const QList<QgsField::ConfigurationFlag> flagList = qgsEnumMap<QgsField::ConfigurationFlag>().keys();
262  for ( const QgsField::ConfigurationFlag flag : flagList )
263  {
264  if ( flag == QgsField::ConfigurationFlag::None )
265  continue;
266 
268  mLayer->fieldConfigurationFlags( idx ).testFlag( flag ) ? Qt::Checked : Qt::Unchecked,
269  QVariant::fromValue( flag ) );
270  }
271  mFieldsList->setCellWidget( row, AttrConfigurationFlagsCol, cb );
272 }
273 
275 {
276  QgsDebugMsg( "inserting attribute " + field.name() + " of type " + field.typeName() );
277  mLayer->beginEditCommand( tr( "Added attribute" ) );
278  if ( mLayer->addAttribute( field ) )
279  {
281  return true;
282  }
283  else
284  {
286  QMessageBox::critical( this, tr( "Add Field" ), tr( "Failed to add field '%1' of type '%2'. Is the field name unique?" ).arg( field.name(), field.typeName() ) );
287  return false;
288  }
289 }
290 
292 {
293  for ( int i = 0; i < mFieldsList->rowCount(); i++ )
294  {
295  const int idx = mFieldsList->item( i, AttrIdCol )->data( Qt::DisplayRole ).toInt();
296  QgsField::ConfigurationFlags flags = mLayer->fieldConfigurationFlags( idx );
297 
298  QgsCheckableComboBox *cb = qobject_cast<QgsCheckableComboBox *>( mFieldsList->cellWidget( i, AttrConfigurationFlagsCol ) );
299  if ( cb )
300  {
301  QgsCheckableItemModel *model = cb->model();
302  for ( int r = 0; r < model->rowCount(); ++r )
303  {
304  const QModelIndex index = model->index( r, 0 );
305  const QgsField::ConfigurationFlag flag = model->data( index, Qt::UserRole ).value<QgsField::ConfigurationFlag>();
306  const bool active = model->data( index, Qt::CheckStateRole ).value<Qt::CheckState>() == Qt::Checked ? true : false;
307  flags.setFlag( flag, active );
308  }
309  mLayer->setFieldConfigurationFlags( idx, flags );
310  }
311  }
312 }
313 
314 //SLOTS
315 
316 void QgsSourceFieldsProperties::editingToggled()
317 {
318  updateButtons();
319  updateFieldRenamingStatus();
320 }
321 
322 void QgsSourceFieldsProperties::addAttributeClicked()
323 {
324  if ( !mLayer || !mLayer->dataProvider() )
325  {
326  return;
327  }
328 
329  QgsAddAttrDialog dialog( mLayer, this );
330  if ( dialog.exec() == QDialog::Accepted )
331  {
332  addAttribute( dialog.field() );
333  loadRows();
334  }
335 }
336 
337 void QgsSourceFieldsProperties::deleteAttributeClicked()
338 {
339  QSet<int> providerFields;
340  QSet<int> expressionFields;
341  const auto constSelectedItems = mFieldsList->selectedItems();
342  for ( QTableWidgetItem *item : constSelectedItems )
343  {
344  if ( item->column() == 0 )
345  {
346  const int idx = mIndexedWidgets.indexOf( item );
347  if ( idx < 0 )
348  continue;
349 
351  expressionFields << idx;
352  else
353  providerFields << idx;
354  }
355  }
356 
357  if ( !expressionFields.isEmpty() )
358  mLayer->deleteAttributes( expressionFields.values() );
359 
360  if ( !providerFields.isEmpty() )
361  {
362  mLayer->beginEditCommand( tr( "Deleted attributes" ) );
363  if ( mLayer->deleteAttributes( providerFields.values() ) )
365  else
367  }
368 }
369 
370 void QgsSourceFieldsProperties::calculateFieldClicked()
371 {
372  if ( !mLayer || !mLayer->dataProvider() )
373  {
374  return;
375  }
376 
377  QgsFieldCalculator calc( mLayer, this );
378  if ( calc.exec() == QDialog::Accepted )
379  {
380  loadRows();
381  }
382 }
383 
384 void QgsSourceFieldsProperties::attributesListCellChanged( int row, int column )
385 {
386  if ( column == AttrNameCol && mLayer && mLayer->isEditable() )
387  {
388  const int idx = mIndexedWidgets.indexOf( mFieldsList->item( row, AttrIdCol ) );
389 
390  QTableWidgetItem *nameItem = mFieldsList->item( row, column );
391  //avoiding that something will be changed, just because this is triggered by simple re-sorting
392  if ( !nameItem ||
393  nameItem->text().isEmpty() ||
394  !mLayer->fields().exists( idx ) ||
395  mLayer->fields().at( idx ).name() == nameItem->text()
396  )
397  return;
398 
399  mLayer->beginEditCommand( tr( "Rename attribute" ) );
400  if ( mLayer->renameAttribute( idx, nameItem->text() ) )
401  {
403  }
404  else
405  {
407  QMessageBox::critical( this, tr( "Rename Field" ), tr( "Failed to rename field to '%1'. Is the field name unique?" ).arg( nameItem->text() ) );
408  }
409  }
410 }
411 
412 void QgsSourceFieldsProperties::attributesListCellPressed( int /*row*/, int /*column*/ )
413 {
414  updateButtons();
415 }
416 
417 //NICE FUNCTIONS
419 {
421  if ( !provider )
422  return;
423  const QgsVectorDataProvider::Capabilities cap = provider->capabilities();
424 
425  mToggleEditingButton->setEnabled( ( cap & QgsVectorDataProvider::ChangeAttributeValues ) && !mLayer->readOnly() );
426 
427  if ( mLayer->isEditable() )
428  {
429  mDeleteAttributeButton->setEnabled( cap & QgsVectorDataProvider::DeleteAttributes );
430  mAddAttributeButton->setEnabled( cap & QgsVectorDataProvider::AddAttributes );
431  mToggleEditingButton->setChecked( true );
432  }
433  else
434  {
435  mToggleEditingButton->setChecked( false );
436  mAddAttributeButton->setEnabled( false );
437 
438  // Enable delete button if items are selected
439  mDeleteAttributeButton->setEnabled( !mFieldsList->selectedItems().isEmpty() );
440  // and only if all selected items have their origin in an expression
441  const auto constSelectedItems = mFieldsList->selectedItems();
442  for ( QTableWidgetItem *item : constSelectedItems )
443  {
444  if ( item->column() == 0 )
445  {
446  const int idx = mIndexedWidgets.indexOf( item );
448  {
449  mDeleteAttributeButton->setEnabled( false );
450  break;
451  }
452  }
453  }
454  }
455 }
Dialog to add a source field attribute.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
QComboBox subclass which allows selecting multiple items.
QgsCheckableItemModel * model() const
Returns the custom item model which handles checking the items.
void addItemWithCheckState(const QString &text, Qt::CheckState state, const QVariant &userData=QVariant())
Adds an item to the combobox with the given text, check state (stored in the Qt::CheckStateRole) and ...
QStandardItemModel subclass which makes all items checkable by default.
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
Returns the data stored under the given role for the item referred to by the index.
A generic dialog for building expression strings.
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
A dialog class that provides calculation of new fields using existing fields, values and a set of ope...
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:51
QString typeName() const
Gets the field type.
Definition: qgsfield.cpp:138
QString name
Definition: qgsfield.h:60
int precision
Definition: qgsfield.h:57
static QString readableConfigurationFlag(QgsField::ConfigurationFlag flag)
Returns the reabable and translated value of the configuration flag.
Definition: qgsfield.cpp:362
int length
Definition: qgsfield.h:56
QVariant::Type type
Definition: qgsfield.h:58
QString alias
Definition: qgsfield.h:61
QString comment
Definition: qgsfield.h:59
ConfigurationFlag
Configuration flags for fields These flags are meant to be user-configurable and are not describing a...
Definition: qgsfield.h:80
@ None
No flag is defined.
Container of fields for a vector layer.
Definition: qgsfields.h:45
@ OriginExpression
Field is calculated from an expression.
Definition: qgsfields.h:54
@ OriginJoin
Field comes from a joined layer (originIndex / 1000 = index of the join, originIndex % 1000 = index w...
Definition: qgsfields.h:52
int count() const
Returns number of items.
Definition: qgsfields.cpp:133
FieldOrigin fieldOrigin(int fieldIdx) const
Returns the field's origin (value from an enumeration).
Definition: qgsfields.cpp:189
bool exists(int i) const
Returns if a field index is valid.
Definition: qgsfields.cpp:153
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
Definition: qgsfields.cpp:163
QIcon iconForField(int fieldIdx, bool considerOrigin=false) const
Returns an icon corresponding to a field index, based on the field's type and source.
Definition: qgsfields.cpp:275
void editingStopped()
Emitted when edited changes have been successfully written to the data provider.
void editingStarted()
Emitted when editing on this layer has started.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:467
QList< QTableWidgetItem * > mIndexedWidgets
QgsSourceFieldsProperties(QgsVectorLayer *layer, QWidget *parent=nullptr)
bool addAttribute(const QgsField &field)
Adds an attribute to the table (but does not commit it yet)
void setRow(int row, int idx, const QgsField &field)
This is the base class for vector data providers.
@ DeleteAttributes
Allows deletion of attributes (fields)
@ AddAttributes
Allows addition of new attributes (fields)
@ RenameAttributes
Supports renaming attributes (fields). Since QGIS 2.16.
@ ChangeAttributeValues
Allows modification of attribute values.
virtual Q_INVOKABLE QgsVectorDataProvider::Capabilities capabilities() const
Returns flags containing the supported capabilities.
Represents a vector layer which manages a vector based data sets.
bool addAttribute(const QgsField &field)
Add an attribute field (but does not commit it) returns true if the field was added.
void attributeAdded(int idx)
Will be emitted, when a new attribute has been added to this vector layer.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
QString expressionField(int index) const
Returns the expression used for a given expression field.
void setFieldConfigurationFlags(int index, QgsField::ConfigurationFlags flags)
Sets the configuration flags of the field at given index.
void updateExpressionField(int index, const QString &exp)
Changes the expression used to define an expression based (virtual) field.
void endEditCommand()
Finish edit command and add it to undo/redo stack.
void destroyEditCommand()
Destroy active command and reverts all changes in it.
bool isEditable() const FINAL
Returns true if the provider is in editing mode.
void attributeDeleted(int idx)
Will be emitted, when an attribute has been deleted from this vector layer.
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer's data provider, it may be nullptr.
QgsField::ConfigurationFlags fieldConfigurationFlags(int index) const
Returns the configuration flags of the field at given index.
bool deleteAttributes(const QList< int > &attrs)
Deletes a list of attribute fields (but does not commit it)
bool renameAttribute(int index, const QString &newName)
Renames an attribute field (but does not commit it).
void beginEditCommand(const QString &text)
Create edit command for undo/redo operations.
const QgsField & field
Definition: qgsfield.h:463
#define QgsDebugMsg(str)
Definition: qgslogger.h:38