QGIS API Documentation 3.39.0-Master (d85f3c2a281)
Loading...
Searching...
No Matches
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 mSaveLayerEditsButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveAllEdits.svg" ) ) );
44
45 //button signals
46 connect( mToggleEditingButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::toggleEditing );
47 connect( mAddAttributeButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::addAttributeClicked );
48 connect( mDeleteAttributeButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::deleteAttributeClicked );
49 connect( mCalculateFieldButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::calculateFieldClicked );
50 connect( mSaveLayerEditsButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::saveLayerEditsClicked );
51
52 //slots
53 connect( mLayer, &QgsVectorLayer::editingStarted, this, &QgsSourceFieldsProperties::editingToggled );
54 connect( mLayer, &QgsVectorLayer::editingStopped, this, &QgsSourceFieldsProperties::editingToggled );
55 connect( mLayer, &QgsVectorLayer::attributeAdded, this, &QgsSourceFieldsProperties::attributeAdded );
56 connect( mLayer, &QgsVectorLayer::attributeDeleted, this, &QgsSourceFieldsProperties::attributeDeleted );
57
58 //field list appearance
59 mFieldsList->setColumnCount( AttrColCount );
60 mFieldsList->setSelectionBehavior( QAbstractItemView::SelectRows );
61 mFieldsList->setDragDropMode( QAbstractItemView::DragOnly );
62 mFieldsList->setHorizontalHeaderItem( AttrIdCol, new QTableWidgetItem( tr( "Id" ) ) );
63 mFieldsList->setHorizontalHeaderItem( AttrNameCol, new QTableWidgetItem( tr( "Name" ) ) );
64 mFieldsList->setHorizontalHeaderItem( AttrTypeCol, new QTableWidgetItem( tr( "Type" ) ) );
65 mFieldsList->setHorizontalHeaderItem( AttrTypeNameCol, new QTableWidgetItem( tr( "Type name" ) ) );
66 mFieldsList->setHorizontalHeaderItem( AttrLengthCol, new QTableWidgetItem( tr( "Length" ) ) );
67 mFieldsList->setHorizontalHeaderItem( AttrPrecCol, new QTableWidgetItem( tr( "Precision" ) ) );
68 mFieldsList->setHorizontalHeaderItem( AttrCommentCol, new QTableWidgetItem( tr( "Comment" ) ) );
69 const auto configurationFlagsWi = new QTableWidgetItem( tr( "Configuration" ) );
70 configurationFlagsWi->setToolTip( tr( "Configures the field" ) );
71 mFieldsList->setHorizontalHeaderItem( AttrConfigurationFlagsCol, configurationFlagsWi );
72 mFieldsList->setHorizontalHeaderItem( AttrAliasCol, new QTableWidgetItem( tr( "Alias" ) ) );
73
74 mFieldsList->setSortingEnabled( true );
75 mFieldsList->sortByColumn( 0, Qt::AscendingOrder );
76 mFieldsList->setSelectionBehavior( QAbstractItemView::SelectRows );
77 mFieldsList->setSelectionMode( QAbstractItemView::ExtendedSelection );
78 mFieldsList->horizontalHeader()->setStretchLastSection( true );
79 mFieldsList->verticalHeader()->hide();
80
81 //load buttons and field list
83}
84
89
91{
92 disconnect( mFieldsList, &QTableWidget::cellChanged, this, &QgsSourceFieldsProperties::attributesListCellChanged );
93 const QgsFields &fields = mLayer->fields();
94
96 mFieldsList->setRowCount( 0 );
97
98 for ( int i = 0; i < fields.count(); ++i )
99 attributeAdded( i );
100
101 mFieldsList->resizeColumnsToContents();
102 connect( mFieldsList, &QTableWidget::cellChanged, this, &QgsSourceFieldsProperties::attributesListCellChanged );
103
104 connect( mFieldsList, &QTableWidget::cellPressed, this, &QgsSourceFieldsProperties::attributesListCellPressed );
105
107 updateFieldRenamingStatus();
108}
109
110void QgsSourceFieldsProperties::updateFieldRenamingStatus()
111{
113
114 for ( int row = 0; row < mFieldsList->rowCount(); ++row )
115 {
116 if ( canRenameFields )
117 mFieldsList->item( row, AttrNameCol )->setFlags( mFieldsList->item( row, AttrNameCol )->flags() | Qt::ItemIsEditable );
118 else
119 mFieldsList->item( row, AttrNameCol )->setFlags( mFieldsList->item( row, AttrNameCol )->flags() & ~Qt::ItemIsEditable );
120 }
121}
122
123void QgsSourceFieldsProperties::updateExpression()
124{
125 QToolButton *btn = qobject_cast<QToolButton *>( sender() );
126 Q_ASSERT( btn );
127
128 const int index = btn->property( "Index" ).toInt();
129
130 const QString exp = mLayer->expressionField( index );
131
132 QgsExpressionContext context;
135
136 QgsExpressionBuilderDialog dlg( mLayer, exp, nullptr, QStringLiteral( "generic" ), context );
137
138 if ( dlg.exec() )
139 {
140 mLayer->updateExpressionField( index, dlg.expressionText() );
141 loadRows();
142 }
143}
144
145void QgsSourceFieldsProperties::attributeAdded( int idx )
146{
147 const bool sorted = mFieldsList->isSortingEnabled();
148 if ( sorted )
149 mFieldsList->setSortingEnabled( false );
150
151 const QgsFields &fields = mLayer->fields();
152 const int row = mFieldsList->rowCount();
153 mFieldsList->insertRow( row );
154 setRow( row, idx, fields.at( idx ) );
155 mFieldsList->setCurrentCell( row, idx );
156
157 const QColor expressionColor = QColor( 103, 0, 243, 44 );
158 const QColor joinColor = QColor( 0, 243, 79, 44 );
159 const QColor defaultColor = QColor( 252, 255, 79, 44 );
160
161 for ( int i = 0; i < mFieldsList->columnCount(); i++ )
162 {
163 if ( i == AttrConfigurationFlagsCol )
164 continue;
165
166 switch ( mLayer->fields().fieldOrigin( idx ) )
167 {
169 if ( i == 7 ) continue;
170 mFieldsList->item( row, i )->setBackground( expressionColor );
171 break;
172
174 mFieldsList->item( row, i )->setBackground( joinColor );
175 break;
176
177 default:
178 if ( defaultColor.isValid() )
179 mFieldsList->item( row, i )->setBackground( defaultColor );
180 break;
181 }
182 }
183
184 if ( sorted )
185 mFieldsList->setSortingEnabled( true );
186}
187
188
189void QgsSourceFieldsProperties::attributeDeleted( int idx )
190{
191 mFieldsList->removeRow( mIndexedWidgets.at( idx )->row() );
192 mIndexedWidgets.removeAt( idx );
193 for ( int i = idx; i < mIndexedWidgets.count(); i++ )
194 {
195 mIndexedWidgets.at( i )->setData( Qt::DisplayRole, i );
196 }
197}
198
199void QgsSourceFieldsProperties::setRow( int row, int idx, const QgsField &field )
200{
201 QTableWidgetItem *dataItem = new QTableWidgetItem();
202 dataItem->setData( Qt::DisplayRole, idx );
203 dataItem->setIcon( mLayer->fields().iconForField( idx, true ) );
204 mFieldsList->setItem( row, AttrIdCol, dataItem );
205
206 // in case we insert and not append reindex remaining widgets by 1
207 for ( int i = idx + 1; i < mIndexedWidgets.count(); i++ )
208 mIndexedWidgets.at( i )->setData( Qt::DisplayRole, i );
209 mIndexedWidgets.insert( idx, mFieldsList->item( row, 0 ) );
210
211 mFieldsList->setItem( row, AttrNameCol, new QTableWidgetItem( field.name() ) );
212 mFieldsList->setItem( row, AttrAliasCol, new QTableWidgetItem( field.alias() ) );
213 mFieldsList->setItem( row, AttrTypeCol, new QTableWidgetItem( field.friendlyTypeString() ) );
214 mFieldsList->setItem( row, AttrTypeNameCol, new QTableWidgetItem( field.typeName() ) );
215 mFieldsList->setItem( row, AttrLengthCol, new QTableWidgetItem( QString::number( field.length() ) ) );
216 mFieldsList->setItem( row, AttrPrecCol, new QTableWidgetItem( QString::number( field.precision() ) ) );
217
219 {
220 QWidget *expressionWidget = new QWidget;
221 expressionWidget->setLayout( new QHBoxLayout );
222 QToolButton *editExpressionButton = new QToolButton;
223 editExpressionButton->setProperty( "Index", idx );
224 editExpressionButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconExpression.svg" ) ) );
225 connect( editExpressionButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::updateExpression );
226 expressionWidget->layout()->setContentsMargins( 0, 0, 0, 0 );
227 expressionWidget->layout()->addWidget( editExpressionButton );
228 expressionWidget->layout()->addWidget( new QLabel( mLayer->expressionField( idx ) ) );
229 expressionWidget->setStyleSheet( "*[class~=\"QWidget\"] { background-color: rgba( 103, 0, 243, 0.12 ); } QToolButton { background-color: rgba( 203, 100, 243, 0.6 ); }" );
230 mFieldsList->setCellWidget( row, AttrCommentCol, expressionWidget );
231 }
232 else
233 {
234 mFieldsList->setItem( row, AttrCommentCol, new QTableWidgetItem( field.comment() ) );
235 }
236
237 QList<int> notEditableCols = QList<int>()
238 << AttrIdCol
239 << AttrNameCol
240 << AttrAliasCol
241 << AttrTypeCol
244 << AttrPrecCol
246
247 const auto constNotEditableCols = notEditableCols;
248 for ( const int i : constNotEditableCols )
249 {
250 if ( notEditableCols[i] != AttrCommentCol || mLayer->fields().fieldOrigin( idx ) != Qgis::FieldOrigin::Expression )
251 mFieldsList->item( row, i )->setFlags( mFieldsList->item( row, i )->flags() & ~Qt::ItemIsEditable );
252 if ( notEditableCols[i] == AttrAliasCol )
253 mFieldsList->item( row, i )->setToolTip( tr( "Edit alias in the Form config tab" ) );
254 }
256 if ( canRenameFields )
257 mFieldsList->item( row, AttrNameCol )->setFlags( mFieldsList->item( row, AttrNameCol )->flags() | Qt::ItemIsEditable );
258 else
259 mFieldsList->item( row, AttrNameCol )->setFlags( mFieldsList->item( row, AttrNameCol )->flags() & ~Qt::ItemIsEditable );
260
261 // Flags
262 QgsCheckableComboBox *cb = new QgsCheckableComboBox( mFieldsList );
263 const QList<Qgis::FieldConfigurationFlag> flagList = qgsEnumList<Qgis::FieldConfigurationFlag>();
264 for ( const Qgis::FieldConfigurationFlag flag : flagList )
265 {
267 continue;
268
270 mLayer->fieldConfigurationFlags( idx ).testFlag( flag ) ? Qt::Checked : Qt::Unchecked,
271 QVariant::fromValue( flag ) );
272 }
273 mFieldsList->setCellWidget( row, AttrConfigurationFlagsCol, cb );
274}
275
277{
278 QgsDebugMsgLevel( "inserting attribute " + field.name() + " of type " + field.typeName(), 2 );
279 mLayer->beginEditCommand( tr( "Added attribute" ) );
280 if ( mLayer->addAttribute( field ) )
281 {
283 return true;
284 }
285 else
286 {
288 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() ) );
289 return false;
290 }
291}
292
294{
295 for ( int i = 0; i < mFieldsList->rowCount(); i++ )
296 {
297 const int idx = mFieldsList->item( i, AttrIdCol )->data( Qt::DisplayRole ).toInt();
299
300 QgsCheckableComboBox *cb = qobject_cast<QgsCheckableComboBox *>( mFieldsList->cellWidget( i, AttrConfigurationFlagsCol ) );
301 if ( cb )
302 {
303 QgsCheckableItemModel *model = cb->model();
304 for ( int r = 0; r < model->rowCount(); ++r )
305 {
306 const QModelIndex index = model->index( r, 0 );
307 const Qgis::FieldConfigurationFlag flag = model->data( index, Qt::UserRole ).value<Qgis::FieldConfigurationFlag>();
308 const bool active = model->data( index, Qt::CheckStateRole ).value<Qt::CheckState>() == Qt::Checked ? true : false;
309 flags.setFlag( flag, active );
310 }
311 mLayer->setFieldConfigurationFlags( idx, flags );
312 }
313 }
314}
315
316//SLOTS
317
318void QgsSourceFieldsProperties::editingToggled()
319{
321 updateFieldRenamingStatus();
322}
323
324void QgsSourceFieldsProperties::addAttributeClicked()
325{
326 if ( !mLayer || !mLayer->dataProvider() )
327 {
328 return;
329 }
330
331 QgsAddAttrDialog dialog( mLayer, this );
332 if ( dialog.exec() == QDialog::Accepted )
333 {
334 addAttribute( dialog.field() );
335 loadRows();
336 }
337}
338
339void QgsSourceFieldsProperties::deleteAttributeClicked()
340{
341 QSet<int> providerFields;
342 QSet<int> expressionFields;
343 const auto constSelectedItems = mFieldsList->selectedItems();
344 for ( QTableWidgetItem *item : constSelectedItems )
345 {
346 if ( item->column() == 0 )
347 {
348 const int idx = mIndexedWidgets.indexOf( item );
349 if ( idx < 0 )
350 continue;
351
353 expressionFields << idx;
354 else
355 providerFields << idx;
356 }
357 }
358
359 if ( !expressionFields.isEmpty() )
360 mLayer->deleteAttributes( expressionFields.values() );
361
362 if ( !providerFields.isEmpty() )
363 {
364 mLayer->beginEditCommand( tr( "Deleted attributes" ) );
365 if ( mLayer->deleteAttributes( providerFields.values() ) )
367 else
369 }
370}
371
372void QgsSourceFieldsProperties::calculateFieldClicked()
373{
374 if ( !mLayer || !mLayer->dataProvider() )
375 {
376 return;
377 }
378
379 QgsFieldCalculator calc( mLayer, this );
380 if ( calc.exec() == QDialog::Accepted )
381 {
382 loadRows();
383 }
384}
385
386void QgsSourceFieldsProperties::saveLayerEditsClicked()
387{
388 mLayer->commitChanges( false );
389}
390
391void QgsSourceFieldsProperties::attributesListCellChanged( int row, int column )
392{
393 if ( column == AttrNameCol && mLayer && mLayer->isEditable() )
394 {
395 const int idx = mIndexedWidgets.indexOf( mFieldsList->item( row, AttrIdCol ) );
396
397 QTableWidgetItem *nameItem = mFieldsList->item( row, column );
398 //avoiding that something will be changed, just because this is triggered by simple re-sorting
399 if ( !nameItem ||
400 nameItem->text().isEmpty() ||
401 !mLayer->fields().exists( idx ) ||
402 mLayer->fields().at( idx ).name() == nameItem->text()
403 )
404 return;
405
406 mLayer->beginEditCommand( tr( "Rename attribute" ) );
407 if ( mLayer->renameAttribute( idx, nameItem->text() ) )
408 {
410 }
411 else
412 {
414 QMessageBox::critical( this, tr( "Rename Field" ), tr( "Failed to rename field to '%1'. Is the field name unique?" ).arg( nameItem->text() ) );
415 }
416 }
417}
418
419void QgsSourceFieldsProperties::attributesListCellPressed( int /*row*/, int /*column*/ )
420{
422}
423
424//NICE FUNCTIONS
426{
428 if ( !provider )
429 return;
430 const Qgis::VectorProviderCapabilities cap = provider->capabilities();
431
432 mToggleEditingButton->setEnabled( ( cap & Qgis::VectorProviderCapability::ChangeAttributeValues ) && !mLayer->readOnly() );
433
434 if ( mLayer->isEditable() )
435 {
436 mDeleteAttributeButton->setEnabled( cap & Qgis::VectorProviderCapability::DeleteAttributes );
437 mAddAttributeButton->setEnabled( cap & Qgis::VectorProviderCapability::AddAttributes );
438 mToggleEditingButton->setChecked( true );
439 mSaveLayerEditsButton->setEnabled( true );
440 mSaveLayerEditsButton->setChecked( true );
441 }
442 else
443 {
444 mToggleEditingButton->setChecked( false );
445 mAddAttributeButton->setEnabled( false );
446 mSaveLayerEditsButton->setEnabled( false );
447
448 // Enable delete button if items are selected
449 mDeleteAttributeButton->setEnabled( !mFieldsList->selectedItems().isEmpty() );
450 // and only if all selected items have their origin in an expression
451 const auto constSelectedItems = mFieldsList->selectedItems();
452 for ( QTableWidgetItem *item : constSelectedItems )
453 {
454 if ( item->column() == 0 )
455 {
456 const int idx = mIndexedWidgets.indexOf( item );
458 {
459 mDeleteAttributeButton->setEnabled( false );
460 break;
461 }
462 }
463 }
464 }
465}
@ AddAttributes
Allows addition of new attributes (fields)
@ RenameAttributes
Supports renaming attributes (fields)
@ DeleteAttributes
Allows deletion of attributes (fields)
@ ChangeAttributeValues
Allows modification of attribute values.
QFlags< VectorProviderCapability > VectorProviderCapabilities
Vector data provider capabilities.
Definition qgis.h:500
@ Expression
Field is calculated from an expression.
@ Join
Field originates from a joined layer.
FieldConfigurationFlag
Configuration flags for fields These flags are meant to be user-configurable and are not describing a...
Definition qgis.h:1568
@ NoFlag
No flag is defined.
QFlags< FieldConfigurationFlag > FieldConfigurationFlags
Configuration flags for fields These flags are meant to be user-configurable and are not describing a...
Definition qgis.h:1583
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:53
QString typeName() const
Gets the field type.
Definition qgsfield.cpp:160
QString name
Definition qgsfield.h:62
int precision
Definition qgsfield.h:59
int length
Definition qgsfield.h:58
QString friendlyTypeString() const
Returns a user friendly, translated representation of the field type.
Definition qgsfield.cpp:138
static QString readableConfigurationFlag(Qgis::FieldConfigurationFlag flag)
Returns the readable and translated value of the configuration flag.
Definition qgsfield.cpp:450
QString alias
Definition qgsfield.h:63
QString comment
Definition qgsfield.h:61
Container of fields for a vector layer.
Definition qgsfields.h:46
int count
Definition qgsfields.h:50
Qgis::FieldOrigin fieldOrigin(int fieldIdx) const
Returns the field's origin (value from an enumeration).
Q_INVOKABLE bool exists(int i) const
Returns if a field index is valid.
void clear()
Removes all fields.
Definition qgsfields.cpp:57
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
QIcon iconForField(int fieldIdx, bool considerOrigin=false) const
Returns an icon corresponding to a field index, based on the field's type and source.
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.
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.
virtual Q_INVOKABLE Qgis::VectorProviderCapabilities capabilities() const
Returns flags containing the supported capabilities.
Represents a vector layer which manages a vector based data sets.
void setFieldConfigurationFlags(int index, Qgis::FieldConfigurationFlags flags)
Sets the configuration flags of the field at given index.
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.
QString expressionField(int index) const
Returns the expression used for a given expression field.
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.
Qgis::FieldConfigurationFlags fieldConfigurationFlags(int index) const
Returns the configuration flags of the field at given index.
Q_INVOKABLE bool commitChanges(bool stopEditing=true)
Attempts to commit to the underlying data provider any buffered changes made since the last to call t...
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer's data provider, it may be nullptr.
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.
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:39