QGIS API Documentation 3.41.0-Master (a6c88b06562)
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 "moc_qgssourcefieldsproperties.cpp"
21#include "qgsvectorlayer.h"
22#include "qgsproject.h"
23#include "qgsapplication.h"
25#include "qgsgui.h"
26#include "qgsnative.h"
27
28
30 : QWidget( parent )
31 , mLayer( layer )
32{
33 if ( !layer )
34 return;
35
36 setupUi( this );
37 layout()->setContentsMargins( 0, 0, 0, 0 );
38
39 //button appearance
40 mAddAttributeButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionNewAttribute.svg" ) ) );
41 mDeleteAttributeButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDeleteAttribute.svg" ) ) );
42 mToggleEditingButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ) );
43 mCalculateFieldButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCalculateField.svg" ) ) );
44 mSaveLayerEditsButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveAllEdits.svg" ) ) );
45
46 //button signals
47 connect( mToggleEditingButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::toggleEditing );
48 connect( mAddAttributeButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::addAttributeClicked );
49 connect( mDeleteAttributeButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::deleteAttributeClicked );
50 connect( mCalculateFieldButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::calculateFieldClicked );
51 connect( mSaveLayerEditsButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::saveLayerEditsClicked );
52
53 //slots
54 connect( mLayer, &QgsVectorLayer::editingStarted, this, &QgsSourceFieldsProperties::editingToggled );
55 connect( mLayer, &QgsVectorLayer::editingStopped, this, &QgsSourceFieldsProperties::editingToggled );
56 connect( mLayer, &QgsVectorLayer::attributeAdded, this, &QgsSourceFieldsProperties::attributeAdded );
57 connect( mLayer, &QgsVectorLayer::attributeDeleted, this, &QgsSourceFieldsProperties::attributeDeleted );
58
59 //field list appearance
60 mFieldsList->setColumnCount( AttrColCount );
61 mFieldsList->setSelectionBehavior( QAbstractItemView::SelectRows );
62 mFieldsList->setDragDropMode( QAbstractItemView::DragOnly );
63 mFieldsList->setHorizontalHeaderItem( AttrIdCol, new QTableWidgetItem( tr( "Id" ) ) );
64 mFieldsList->setHorizontalHeaderItem( AttrNameCol, new QTableWidgetItem( tr( "Name" ) ) );
65 mFieldsList->setHorizontalHeaderItem( AttrTypeCol, new QTableWidgetItem( tr( "Type" ) ) );
66 mFieldsList->setHorizontalHeaderItem( AttrTypeNameCol, new QTableWidgetItem( tr( "Type name" ) ) );
67 mFieldsList->setHorizontalHeaderItem( AttrLengthCol, new QTableWidgetItem( tr( "Length" ) ) );
68 mFieldsList->setHorizontalHeaderItem( AttrPrecCol, new QTableWidgetItem( tr( "Precision" ) ) );
69 mFieldsList->setHorizontalHeaderItem( AttrCommentCol, new QTableWidgetItem( tr( "Comment" ) ) );
70 const auto configurationFlagsWi = new QTableWidgetItem( tr( "Configuration" ) );
71 configurationFlagsWi->setToolTip( tr( "Configures the field" ) );
72 mFieldsList->setHorizontalHeaderItem( AttrConfigurationFlagsCol, configurationFlagsWi );
73 mFieldsList->setHorizontalHeaderItem( AttrAliasCol, new QTableWidgetItem( tr( "Alias" ) ) );
74
75 mFieldsList->setSortingEnabled( true );
76 mFieldsList->sortByColumn( 0, Qt::AscendingOrder );
77 mFieldsList->setSelectionBehavior( QAbstractItemView::SelectRows );
78 mFieldsList->setSelectionMode( QAbstractItemView::ExtendedSelection );
79 mFieldsList->horizontalHeader()->setStretchLastSection( true );
80 mFieldsList->verticalHeader()->hide();
81
82 //load buttons and field list
84}
85
90
92{
93 disconnect( mFieldsList, &QTableWidget::cellChanged, this, &QgsSourceFieldsProperties::attributesListCellChanged );
94 const QgsFields &fields = mLayer->fields();
95
97 mFieldsList->setRowCount( 0 );
98
99 for ( int i = 0; i < fields.count(); ++i )
100 attributeAdded( i );
101
102 mFieldsList->resizeColumnsToContents();
103 connect( mFieldsList, &QTableWidget::cellChanged, this, &QgsSourceFieldsProperties::attributesListCellChanged );
104
105 connect( mFieldsList, &QTableWidget::cellPressed, this, &QgsSourceFieldsProperties::attributesListCellPressed );
106
108 updateFieldRenamingStatus();
109}
110
111void QgsSourceFieldsProperties::updateFieldRenamingStatus()
112{
114
115 for ( int row = 0; row < mFieldsList->rowCount(); ++row )
116 {
117 if ( canRenameFields )
118 mFieldsList->item( row, AttrNameCol )->setFlags( mFieldsList->item( row, AttrNameCol )->flags() | Qt::ItemIsEditable );
119 else
120 mFieldsList->item( row, AttrNameCol )->setFlags( mFieldsList->item( row, AttrNameCol )->flags() & ~Qt::ItemIsEditable );
121 }
122}
123
124void QgsSourceFieldsProperties::updateExpression()
125{
126 QToolButton *btn = qobject_cast<QToolButton *>( sender() );
127 Q_ASSERT( btn );
128
129 const int index = btn->property( "Index" ).toInt();
130
131 const QString exp = mLayer->expressionField( index );
132
133 QgsExpressionContext context;
136
137 QgsExpressionBuilderDialog dlg( mLayer, exp, nullptr, QStringLiteral( "generic" ), context );
138
139 if ( dlg.exec() )
140 {
141 mLayer->updateExpressionField( index, dlg.expressionText() );
142 loadRows();
143 }
144}
145
146void QgsSourceFieldsProperties::attributeAdded( int idx )
147{
148 const bool sorted = mFieldsList->isSortingEnabled();
149 if ( sorted )
150 mFieldsList->setSortingEnabled( false );
151
152 const QgsFields &fields = mLayer->fields();
153 const int row = mFieldsList->rowCount();
154 mFieldsList->insertRow( row );
155 setRow( row, idx, fields.at( idx ) );
156 mFieldsList->setCurrentCell( row, idx );
157
158 const QColor expressionColor = QColor( 103, 0, 243, 44 );
159 const QColor joinColor = QColor( 0, 243, 79, 44 );
160 const QColor defaultColor = QColor( 252, 255, 79, 44 );
161
162 for ( int i = 0; i < mFieldsList->columnCount(); i++ )
163 {
164 if ( i == AttrConfigurationFlagsCol )
165 continue;
166
167 switch ( mLayer->fields().fieldOrigin( idx ) )
168 {
170 if ( i == 7 ) continue;
171 mFieldsList->item( row, i )->setBackground( expressionColor );
172 break;
173
175 mFieldsList->item( row, i )->setBackground( joinColor );
176 break;
177
178 default:
179 if ( defaultColor.isValid() )
180 mFieldsList->item( row, i )->setBackground( defaultColor );
181 break;
182 }
183 }
184
185 if ( sorted )
186 mFieldsList->setSortingEnabled( true );
187}
188
189
190void QgsSourceFieldsProperties::attributeDeleted( int idx )
191{
192 mFieldsList->removeRow( mIndexedWidgets.at( idx )->row() );
193 mIndexedWidgets.removeAt( idx );
194 for ( int i = idx; i < mIndexedWidgets.count(); i++ )
195 {
196 mIndexedWidgets.at( i )->setData( Qt::DisplayRole, i );
197 }
198}
199
200void QgsSourceFieldsProperties::setRow( int row, int idx, const QgsField &field )
201{
202 QTableWidgetItem *dataItem = new QTableWidgetItem();
203 dataItem->setData( Qt::DisplayRole, idx );
204 dataItem->setIcon( mLayer->fields().iconForField( idx, true ) );
205 mFieldsList->setItem( row, AttrIdCol, dataItem );
206
207 // in case we insert and not append reindex remaining widgets by 1
208 for ( int i = idx + 1; i < mIndexedWidgets.count(); i++ )
209 mIndexedWidgets.at( i )->setData( Qt::DisplayRole, i );
210 mIndexedWidgets.insert( idx, mFieldsList->item( row, 0 ) );
211
212 mFieldsList->setItem( row, AttrNameCol, new QTableWidgetItem( field.name() ) );
213 mFieldsList->setItem( row, AttrAliasCol, new QTableWidgetItem( field.alias() ) );
214 mFieldsList->setItem( row, AttrTypeCol, new QTableWidgetItem( field.friendlyTypeString() ) );
215 mFieldsList->setItem( row, AttrTypeNameCol, new QTableWidgetItem( field.typeName() ) );
216 mFieldsList->setItem( row, AttrLengthCol, new QTableWidgetItem( QString::number( field.length() ) ) );
217 mFieldsList->setItem( row, AttrPrecCol, new QTableWidgetItem( QString::number( field.precision() ) ) );
218
220 {
221 QWidget *expressionWidget = new QWidget;
222 expressionWidget->setLayout( new QHBoxLayout );
223 QToolButton *editExpressionButton = new QToolButton;
224 editExpressionButton->setProperty( "Index", idx );
225 editExpressionButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconExpression.svg" ) ) );
226 connect( editExpressionButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::updateExpression );
227 expressionWidget->layout()->setContentsMargins( 0, 0, 0, 0 );
228 expressionWidget->layout()->addWidget( editExpressionButton );
229 expressionWidget->layout()->addWidget( new QLabel( mLayer->expressionField( idx ) ) );
230 expressionWidget->setStyleSheet( "*[class~=\"QWidget\"] { background-color: rgba( 103, 0, 243, 0.12 ); } QToolButton { background-color: rgba( 203, 100, 243, 0.6 ); }" );
231 mFieldsList->setCellWidget( row, AttrCommentCol, expressionWidget );
232 }
233 else
234 {
235 mFieldsList->setItem( row, AttrCommentCol, new QTableWidgetItem( field.comment() ) );
236 }
237
238 QList<int> notEditableCols = QList<int>()
239 << AttrIdCol
240 << AttrNameCol
241 << AttrAliasCol
242 << AttrTypeCol
245 << AttrPrecCol
247
248 const auto constNotEditableCols = notEditableCols;
249 for ( const int i : constNotEditableCols )
250 {
251 if ( notEditableCols[i] != AttrCommentCol || mLayer->fields().fieldOrigin( idx ) != Qgis::FieldOrigin::Expression )
252 mFieldsList->item( row, i )->setFlags( mFieldsList->item( row, i )->flags() & ~Qt::ItemIsEditable );
253 if ( notEditableCols[i] == AttrAliasCol )
254 mFieldsList->item( row, i )->setToolTip( tr( "Edit alias in the Form config tab" ) );
255 }
257 if ( canRenameFields )
258 mFieldsList->item( row, AttrNameCol )->setFlags( mFieldsList->item( row, AttrNameCol )->flags() | Qt::ItemIsEditable );
259 else
260 mFieldsList->item( row, AttrNameCol )->setFlags( mFieldsList->item( row, AttrNameCol )->flags() & ~Qt::ItemIsEditable );
261
262 // Flags
263 QgsCheckableComboBox *cb = new QgsCheckableComboBox( mFieldsList );
264 const QList<Qgis::FieldConfigurationFlag> flagList = qgsEnumList<Qgis::FieldConfigurationFlag>();
265 for ( const Qgis::FieldConfigurationFlag flag : flagList )
266 {
268 continue;
269
271 mLayer->fieldConfigurationFlags( idx ).testFlag( flag ) ? Qt::Checked : Qt::Unchecked,
272 QVariant::fromValue( flag ) );
273 }
274 mFieldsList->setCellWidget( row, AttrConfigurationFlagsCol, cb );
275}
276
278{
279 QgsDebugMsgLevel( "inserting attribute " + field.name() + " of type " + field.typeName(), 2 );
280 mLayer->beginEditCommand( tr( "Added attribute" ) );
281 if ( mLayer->addAttribute( field ) )
282 {
284 return true;
285 }
286 else
287 {
289 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() ) );
290 return false;
291 }
292}
293
295{
296 for ( int i = 0; i < mFieldsList->rowCount(); i++ )
297 {
298 const int idx = mFieldsList->item( i, AttrIdCol )->data( Qt::DisplayRole ).toInt();
300
301 QgsCheckableComboBox *cb = qobject_cast<QgsCheckableComboBox *>( mFieldsList->cellWidget( i, AttrConfigurationFlagsCol ) );
302 if ( cb )
303 {
304 QgsCheckableItemModel *model = cb->model();
305 for ( int r = 0; r < model->rowCount(); ++r )
306 {
307 const QModelIndex index = model->index( r, 0 );
308 const Qgis::FieldConfigurationFlag flag = model->data( index, Qt::UserRole ).value<Qgis::FieldConfigurationFlag>();
309 const bool active = model->data( index, Qt::CheckStateRole ).value<Qt::CheckState>() == Qt::Checked ? true : false;
310 flags.setFlag( flag, active );
311 }
312 mLayer->setFieldConfigurationFlags( idx, flags );
313 }
314 }
315}
316
317//SLOTS
318
319void QgsSourceFieldsProperties::editingToggled()
320{
322 updateFieldRenamingStatus();
323}
324
325void QgsSourceFieldsProperties::addAttributeClicked()
326{
327 if ( !mLayer || !mLayer->dataProvider() )
328 {
329 return;
330 }
331
332 QgsAddAttrDialog dialog( mLayer, this );
333 if ( dialog.exec() == QDialog::Accepted )
334 {
335 addAttribute( dialog.field() );
336 loadRows();
337 }
338}
339
340void QgsSourceFieldsProperties::deleteAttributeClicked()
341{
342 QSet<int> providerFields;
343 QSet<int> expressionFields;
344 const auto constSelectedItems = mFieldsList->selectedItems();
345 for ( QTableWidgetItem *item : constSelectedItems )
346 {
347 if ( item->column() == 0 )
348 {
349 const int idx = mIndexedWidgets.indexOf( item );
350 if ( idx < 0 )
351 continue;
352
354 expressionFields << idx;
355 else
356 providerFields << idx;
357 }
358 }
359
360 if ( !expressionFields.isEmpty() )
361 mLayer->deleteAttributes( expressionFields.values() );
362
363 if ( !providerFields.isEmpty() )
364 {
365 mLayer->beginEditCommand( tr( "Deleted attributes" ) );
366 if ( mLayer->deleteAttributes( providerFields.values() ) )
368 else
370 }
371}
372
373void QgsSourceFieldsProperties::calculateFieldClicked()
374{
375 if ( !mLayer || !mLayer->dataProvider() )
376 {
377 return;
378 }
379
380 QgsFieldCalculator calc( mLayer, this );
381 if ( calc.exec() == QDialog::Accepted )
382 {
383 loadRows();
384 }
385}
386
387void QgsSourceFieldsProperties::saveLayerEditsClicked()
388{
389 mLayer->commitChanges( false );
390}
391
392void QgsSourceFieldsProperties::attributesListCellChanged( int row, int column )
393{
394 if ( column == AttrNameCol && mLayer && mLayer->isEditable() )
395 {
396 const int idx = mIndexedWidgets.indexOf( mFieldsList->item( row, AttrIdCol ) );
397
398 QTableWidgetItem *nameItem = mFieldsList->item( row, column );
399 //avoiding that something will be changed, just because this is triggered by simple re-sorting
400 if ( !nameItem ||
401 nameItem->text().isEmpty() ||
402 !mLayer->fields().exists( idx ) ||
403 mLayer->fields().at( idx ).name() == nameItem->text()
404 )
405 return;
406
407 mLayer->beginEditCommand( tr( "Rename attribute" ) );
408 if ( mLayer->renameAttribute( idx, nameItem->text() ) )
409 {
411 }
412 else
413 {
415 QMessageBox::critical( this, tr( "Rename Field" ), tr( "Failed to rename field to '%1'. Is the field name unique?" ).arg( nameItem->text() ) );
416 }
417 }
418}
419
420void QgsSourceFieldsProperties::attributesListCellPressed( int /*row*/, int /*column*/ )
421{
423}
424
425//NICE FUNCTIONS
427{
429 if ( !provider )
430 return;
431 const Qgis::VectorProviderCapabilities cap = provider->capabilities();
432
433 mToggleEditingButton->setEnabled( ( cap & Qgis::VectorProviderCapability::ChangeAttributeValues ) && !mLayer->readOnly() );
434
435 if ( mLayer->isEditable() )
436 {
437 mDeleteAttributeButton->setEnabled( cap & Qgis::VectorProviderCapability::DeleteAttributes );
438 mAddAttributeButton->setEnabled( cap & Qgis::VectorProviderCapability::AddAttributes );
439 mToggleEditingButton->setChecked( true );
440 mSaveLayerEditsButton->setEnabled( true );
441 mSaveLayerEditsButton->setChecked( true );
442 }
443 else
444 {
445 mToggleEditingButton->setChecked( false );
446 mAddAttributeButton->setEnabled( false );
447 mSaveLayerEditsButton->setEnabled( false );
448
449 // Enable delete button if items are selected
450 mDeleteAttributeButton->setEnabled( !mFieldsList->selectedItems().isEmpty() );
451 // and only if all selected items have their origin in an expression
452 const auto constSelectedItems = mFieldsList->selectedItems();
453 for ( QTableWidgetItem *item : constSelectedItems )
454 {
455 if ( item->column() == 0 )
456 {
457 const int idx = mIndexedWidgets.indexOf( item );
459 {
460 mDeleteAttributeButton->setEnabled( false );
461 break;
462 }
463 }
464 }
465 }
466}
@ 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:161
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:139
static QString readableConfigurationFlag(Qgis::FieldConfigurationFlag flag)
Returns the readable and translated value of the configuration flag.
Definition qgsfield.cpp:451
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:58
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