QGIS API Documentation 3.99.0-Master (2fe06baccd8)
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
18
19#include "qgsaddattrdialog.h"
20#include "qgsapplication.h"
23#include "qgsgui.h"
24#include "qgsnative.h"
25#include "qgsproject.h"
26#include "qgsvectorlayer.h"
27
28#include "moc_qgssourcefieldsproperties.cpp"
29
31 : QWidget( parent )
32 , mLayer( layer )
33{
34 if ( !layer )
35 return;
36
37 setupUi( this );
38 layout()->setContentsMargins( 0, 0, 0, 0 );
39
40 //button appearance
41 mAddAttributeButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionNewAttribute.svg" ) ) );
42 mDeleteAttributeButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDeleteAttribute.svg" ) ) );
43 mToggleEditingButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ) );
44 mCalculateFieldButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCalculateField.svg" ) ) );
45 mSaveLayerEditsButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveAllEdits.svg" ) ) );
46
47 //button signals
48 connect( mToggleEditingButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::toggleEditing );
49 connect( mAddAttributeButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::addAttributeClicked );
50 connect( mDeleteAttributeButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::deleteAttributeClicked );
51 connect( mCalculateFieldButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::calculateFieldClicked );
52 connect( mSaveLayerEditsButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::saveLayerEditsClicked );
53
54 //slots
55 connect( mLayer, &QgsVectorLayer::editingStarted, this, &QgsSourceFieldsProperties::editingToggled );
56 connect( mLayer, &QgsVectorLayer::editingStopped, this, &QgsSourceFieldsProperties::editingToggled );
57 connect( mLayer, &QgsVectorLayer::attributeAdded, this, &QgsSourceFieldsProperties::attributeAdded );
58 connect( mLayer, &QgsVectorLayer::attributeDeleted, this, &QgsSourceFieldsProperties::attributeDeleted );
59
60 //field list appearance
61 mFieldsList->setColumnCount( AttrColCount );
62 mFieldsList->setSelectionBehavior( QAbstractItemView::SelectRows );
63 mFieldsList->setDragDropMode( QAbstractItemView::DragOnly );
64 mFieldsList->setHorizontalHeaderItem( AttrIdCol, new QTableWidgetItem( tr( "Id" ) ) );
65 mFieldsList->setHorizontalHeaderItem( AttrNameCol, new QTableWidgetItem( tr( "Name" ) ) );
66 mFieldsList->setHorizontalHeaderItem( AttrTypeCol, new QTableWidgetItem( tr( "Type" ) ) );
67 mFieldsList->setHorizontalHeaderItem( AttrTypeNameCol, new QTableWidgetItem( tr( "Type name" ) ) );
68 mFieldsList->setHorizontalHeaderItem( AttrLengthCol, new QTableWidgetItem( tr( "Length" ) ) );
69 mFieldsList->setHorizontalHeaderItem( AttrPrecCol, new QTableWidgetItem( tr( "Precision" ) ) );
70 mFieldsList->setHorizontalHeaderItem( AttrCommentCol, new QTableWidgetItem( tr( "Comment" ) ) );
71 const auto configurationFlagsWi = new QTableWidgetItem( tr( "Configuration" ) );
72 configurationFlagsWi->setToolTip( tr( "Configures the field" ) );
73 mFieldsList->setHorizontalHeaderItem( AttrConfigurationFlagsCol, configurationFlagsWi );
74 mFieldsList->setHorizontalHeaderItem( AttrAliasCol, new QTableWidgetItem( tr( "Alias" ) ) );
75
76 mFieldsList->setSortingEnabled( true );
77 mFieldsList->sortByColumn( 0, Qt::AscendingOrder );
78 mFieldsList->setSelectionBehavior( QAbstractItemView::SelectRows );
79 mFieldsList->setSelectionMode( QAbstractItemView::ExtendedSelection );
80 mFieldsList->horizontalHeader()->setStretchLastSection( true );
81 mFieldsList->verticalHeader()->hide();
82
83 //load buttons and field list
85}
86
91
93{
94 disconnect( mFieldsList, &QTableWidget::cellChanged, this, &QgsSourceFieldsProperties::attributesListCellChanged );
95 const QgsFields &fields = mLayer->fields();
96
97 mIndexedWidgets.clear();
98 mFieldsList->setRowCount( 0 );
99
100 for ( int i = 0; i < fields.count(); ++i )
101 attributeAdded( i );
102
103 mFieldsList->resizeColumnsToContents();
104 connect( mFieldsList, &QTableWidget::cellChanged, this, &QgsSourceFieldsProperties::attributesListCellChanged );
105
106 connect( mFieldsList, &QTableWidget::cellPressed, this, &QgsSourceFieldsProperties::attributesListCellPressed );
107
109 updateFieldRenamingStatus();
110}
111
112void QgsSourceFieldsProperties::updateFieldRenamingStatus()
113{
114 const bool canRenameFields = mLayer->isEditable() && ( mLayer->dataProvider()->capabilities() & Qgis::VectorProviderCapability::RenameAttributes ) && !mLayer->readOnly();
115
116 for ( int row = 0; row < mFieldsList->rowCount(); ++row )
117 {
118 if ( canRenameFields )
119 mFieldsList->item( row, AttrNameCol )->setFlags( mFieldsList->item( row, AttrNameCol )->flags() | Qt::ItemIsEditable );
120 else
121 mFieldsList->item( row, AttrNameCol )->setFlags( mFieldsList->item( row, AttrNameCol )->flags() & ~Qt::ItemIsEditable );
122 }
123}
124
125void QgsSourceFieldsProperties::updateExpression()
126{
127 QToolButton *btn = qobject_cast<QToolButton *>( sender() );
128 Q_ASSERT( btn );
129
130 const int index = btn->property( "Index" ).toInt();
131
132 const QString exp = mLayer->expressionField( index );
133
134 QgsExpressionContext context;
137
138 QgsExpressionBuilderDialog dlg( mLayer, exp, nullptr, QStringLiteral( "generic" ), context );
139
140 if ( dlg.exec() )
141 {
142 mLayer->updateExpressionField( index, dlg.expressionText() );
143 loadRows();
144 }
145}
146
147void QgsSourceFieldsProperties::attributeAdded( int idx )
148{
149 const bool sorted = mFieldsList->isSortingEnabled();
150 if ( sorted )
151 mFieldsList->setSortingEnabled( false );
152
153 const QgsFields &fields = mLayer->fields();
154 const int row = mFieldsList->rowCount();
155 mFieldsList->insertRow( row );
156 setRow( row, idx, fields.at( idx ) );
157 mFieldsList->setCurrentCell( row, idx );
158
159 const QColor expressionColor = QColor( 103, 0, 243, 44 );
160 const QColor joinColor = QColor( 0, 243, 79, 44 );
161 const QColor defaultColor = QColor( 252, 255, 79, 44 );
162
163 for ( int i = 0; i < mFieldsList->columnCount(); i++ )
164 {
165 if ( i == AttrConfigurationFlagsCol )
166 continue;
167
168 switch ( mLayer->fields().fieldOrigin( idx ) )
169 {
171 if ( i == 7 )
172 continue;
173 mFieldsList->item( row, i )->setBackground( expressionColor );
174 break;
175
177 mFieldsList->item( row, i )->setBackground( joinColor );
178 break;
179
180 default:
181 if ( defaultColor.isValid() )
182 mFieldsList->item( row, i )->setBackground( defaultColor );
183 break;
184 }
185 }
186
187 if ( sorted )
188 mFieldsList->setSortingEnabled( true );
189}
190
191
192void QgsSourceFieldsProperties::attributeDeleted( int idx )
193{
194 mFieldsList->removeRow( mIndexedWidgets.at( idx )->row() );
195 mIndexedWidgets.removeAt( idx );
196 for ( int i = idx; i < mIndexedWidgets.count(); i++ )
197 {
198 mIndexedWidgets.at( i )->setData( Qt::DisplayRole, i );
199 }
200}
201
202void QgsSourceFieldsProperties::setRow( int row, int idx, const QgsField &field )
203{
204 QTableWidgetItem *dataItem = new QTableWidgetItem();
205 dataItem->setData( Qt::DisplayRole, idx );
206 dataItem->setIcon( mLayer->fields().iconForField( idx, true ) );
207 mFieldsList->setItem( row, AttrIdCol, dataItem );
208
209 // in case we insert and not append reindex remaining widgets by 1
210 for ( int i = idx + 1; i < mIndexedWidgets.count(); i++ )
211 mIndexedWidgets.at( i )->setData( Qt::DisplayRole, i );
212 mIndexedWidgets.insert( idx, mFieldsList->item( row, 0 ) );
213
214 mFieldsList->setItem( row, AttrNameCol, new QTableWidgetItem( field.name() ) );
215 mFieldsList->setItem( row, AttrAliasCol, new QTableWidgetItem( field.alias() ) );
216 mFieldsList->setItem( row, AttrTypeCol, new QTableWidgetItem( field.friendlyTypeString() ) );
217 mFieldsList->setItem( row, AttrTypeNameCol, new QTableWidgetItem( field.typeName() ) );
218 mFieldsList->setItem( row, AttrLengthCol, new QTableWidgetItem( QString::number( field.length() ) ) );
219 mFieldsList->setItem( row, AttrPrecCol, new QTableWidgetItem( QString::number( field.precision() ) ) );
220
221 if ( mLayer->fields().fieldOrigin( idx ) == Qgis::FieldOrigin::Expression )
222 {
223 QWidget *expressionWidget = new QWidget;
224 expressionWidget->setLayout( new QHBoxLayout );
225 QToolButton *editExpressionButton = new QToolButton;
226 editExpressionButton->setProperty( "Index", idx );
227 editExpressionButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconExpression.svg" ) ) );
228 connect( editExpressionButton, &QAbstractButton::clicked, this, &QgsSourceFieldsProperties::updateExpression );
229 expressionWidget->layout()->setContentsMargins( 0, 0, 0, 0 );
230 expressionWidget->layout()->addWidget( editExpressionButton );
231 expressionWidget->layout()->addWidget( new QLabel( mLayer->expressionField( idx ) ) );
232 expressionWidget->setStyleSheet( "*[class~=\"QWidget\"] { background-color: rgba( 103, 0, 243, 0.12 ); } QToolButton { background-color: rgba( 203, 100, 243, 0.6 ); }" );
233 mFieldsList->setCellWidget( row, AttrCommentCol, expressionWidget );
234 }
235 else
236 {
237 mFieldsList->setItem( row, AttrCommentCol, new QTableWidgetItem( field.comment() ) );
238 }
239
240 QList<int> notEditableCols = QList<int>()
241 << AttrIdCol
242 << AttrNameCol
243 << AttrAliasCol
244 << AttrTypeCol
247 << AttrPrecCol
249
250 const auto constNotEditableCols = notEditableCols;
251 for ( const int i : constNotEditableCols )
252 {
253 if ( notEditableCols[i] != AttrCommentCol || mLayer->fields().fieldOrigin( idx ) != Qgis::FieldOrigin::Expression )
254 mFieldsList->item( row, i )->setFlags( mFieldsList->item( row, i )->flags() & ~Qt::ItemIsEditable );
255 if ( notEditableCols[i] == AttrAliasCol )
256 mFieldsList->item( row, i )->setToolTip( tr( "Edit alias in the Form config tab" ) );
257 }
258 const bool canRenameFields = mLayer->isEditable() && ( mLayer->dataProvider()->capabilities() & Qgis::VectorProviderCapability::RenameAttributes ) && !mLayer->readOnly();
259 if ( canRenameFields )
260 mFieldsList->item( row, AttrNameCol )->setFlags( mFieldsList->item( row, AttrNameCol )->flags() | Qt::ItemIsEditable );
261 else
262 mFieldsList->item( row, AttrNameCol )->setFlags( mFieldsList->item( row, AttrNameCol )->flags() & ~Qt::ItemIsEditable );
263
264 // Flags
265 QgsCheckableComboBox *cb = new QgsCheckableComboBox( mFieldsList );
266 const QList<Qgis::FieldConfigurationFlag> flagList = qgsEnumList<Qgis::FieldConfigurationFlag>();
267 for ( const Qgis::FieldConfigurationFlag flag : flagList )
268 {
270 continue;
271
272 cb->addItemWithCheckState( QgsField::readableConfigurationFlag( flag ), mLayer->fieldConfigurationFlags( idx ).testFlag( flag ) ? Qt::Checked : Qt::Unchecked, 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 {
283 mLayer->endEditCommand();
284 return true;
285 }
286 else
287 {
288 mLayer->destroyEditCommand();
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();
299 Qgis::FieldConfigurationFlags flags = mLayer->fieldConfigurationFlags( idx );
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
353 if ( mLayer->fields().fieldOrigin( idx ) == Qgis::FieldOrigin::Expression )
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() ) )
367 mLayer->endEditCommand();
368 else
369 mLayer->destroyEditCommand();
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 || nameItem->text().isEmpty() || !mLayer->fields().exists( idx ) || mLayer->fields().at( idx ).name() == nameItem->text() )
401 return;
402
403 mLayer->beginEditCommand( tr( "Rename attribute" ) );
404 if ( mLayer->renameAttribute( idx, nameItem->text() ) )
405 {
406 mLayer->endEditCommand();
407 }
408 else
409 {
410 mLayer->destroyEditCommand();
411 QMessageBox::critical( this, tr( "Rename Field" ), tr( "Failed to rename field to '%1'. Is the field name unique?" ).arg( nameItem->text() ) );
412 }
413 }
414}
415
416void QgsSourceFieldsProperties::attributesListCellPressed( int /*row*/, int /*column*/ )
417{
419}
420
421//NICE FUNCTIONS
423{
424 QgsVectorDataProvider *provider = mLayer->dataProvider();
425 if ( !provider )
426 return;
427 const Qgis::VectorProviderCapabilities cap = provider->capabilities();
428
429 mToggleEditingButton->setEnabled( ( cap & Qgis::VectorProviderCapability::ChangeAttributeValues ) && !mLayer->readOnly() );
430
431 if ( mLayer->isEditable() )
432 {
433 mDeleteAttributeButton->setEnabled( cap & Qgis::VectorProviderCapability::DeleteAttributes );
434 mAddAttributeButton->setEnabled( cap & Qgis::VectorProviderCapability::AddAttributes );
435 mToggleEditingButton->setChecked( true );
436 mSaveLayerEditsButton->setEnabled( true );
437 mSaveLayerEditsButton->setChecked( true );
438 }
439 else
440 {
441 mToggleEditingButton->setChecked( false );
442 mAddAttributeButton->setEnabled( false );
443 mSaveLayerEditsButton->setEnabled( false );
444
445 // Enable delete button if items are selected
446 mDeleteAttributeButton->setEnabled( !mFieldsList->selectedItems().isEmpty() );
447 // and only if all selected items have their origin in an expression
448 const auto constSelectedItems = mFieldsList->selectedItems();
449 for ( QTableWidgetItem *item : constSelectedItems )
450 {
451 if ( item->column() == 0 )
452 {
453 const int idx = mIndexedWidgets.indexOf( item );
454 if ( mLayer->fields().fieldOrigin( idx ) != Qgis::FieldOrigin::Expression )
455 {
456 mDeleteAttributeButton->setEnabled( false );
457 break;
458 }
459 }
460 }
461 }
462}
@ AddAttributes
Allows addition of new attributes (fields).
Definition qgis.h:504
@ RenameAttributes
Supports renaming attributes (fields).
Definition qgis.h:516
@ DeleteAttributes
Allows deletion of attributes (fields).
Definition qgis.h:505
@ ChangeAttributeValues
Allows modification of attribute values.
Definition qgis.h:503
QFlags< VectorProviderCapability > VectorProviderCapabilities
Vector data provider capabilities.
Definition qgis.h:536
@ Expression
Field is calculated from an expression.
Definition qgis.h:1709
@ Join
Field originates from a joined layer.
Definition qgis.h:1707
FieldConfigurationFlag
Configuration flags for fields These flags are meant to be user-configurable and are not describing a...
Definition qgis.h:1721
@ NoFlag
No flag is defined.
Definition qgis.h:1722
QFlags< FieldConfigurationFlag > FieldConfigurationFlags
Configuration flags for fields These flags are meant to be user-configurable and are not describing a...
Definition qgis.h:1736
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.
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.
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:54
QString typeName() const
Gets the field type.
Definition qgsfield.cpp:163
QString name
Definition qgsfield.h:63
int precision
Definition qgsfield.h:60
int length
Definition qgsfield.h:59
QString friendlyTypeString() const
Returns a user friendly, translated representation of the field type.
Definition qgsfield.cpp:141
static QString readableConfigurationFlag(Qgis::FieldConfigurationFlag flag)
Returns the readable and translated value of the configuration flag.
Definition qgsfield.cpp:453
QString alias
Definition qgsfield.h:64
QString comment
Definition qgsfield.h:62
Container of fields for a vector layer.
Definition qgsfields.h:46
int count
Definition qgsfields.h:50
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
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)
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 dataset.
bool isEditable() const final
Returns true if the provider is in editing mode.
void attributeAdded(int idx)
Will be emitted, when a new attribute has been added to this vector layer.
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.
const QList< T > qgsEnumList()
Returns a list all enum entries.
Definition qgis.h:6764
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:61