QGIS API Documentation 3.27.0-Master (f261cc1f8b)
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"
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
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
105 updateFieldRenamingStatus();
106}
107
108void 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
121void 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
143void 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
187void 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
197void 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
242 << AttrPrecCol
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 = qgsEnumList<QgsField::ConfigurationFlag>();
262 for ( const QgsField::ConfigurationFlag flag : flagList )
263 {
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
316void QgsSourceFieldsProperties::editingToggled()
317{
319 updateFieldRenamingStatus();
320}
321
322void 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
337void 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
370void 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
384void 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
412void QgsSourceFieldsProperties::attributesListCellPressed( int /*row*/, int /*column*/ )
413{
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:139
QString name
Definition: qgsfield.h:60
int precision
Definition: qgsfield.h:57
static QString readableConfigurationFlag(QgsField::ConfigurationFlag flag)
Returns the readable and translated value of the configuration flag.
Definition: qgsfield.cpp:380
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:480
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