QGIS API Documentation 3.29.0-Master (19d7edcfed)
qgsattributeactiondialog.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsattributeactiondialog.cpp - attribute action dialog
3 -------------------
4
5This class creates and manages the Action tab of the Vector Layer
6Properties dialog box. Changes made in the dialog box are propagated
7back to QgsVectorLayer.
8
9 begin : October 2004
10 copyright : (C) 2004 by Gavin Macaulay
11 email : gavin at macaulay dot co dot nz
12 ***************************************************************************/
13
14/***************************************************************************
15 * *
16 * This program is free software; you can redistribute it and/or modify *
17 * it under the terms of the GNU General Public License as published by *
18 * the Free Software Foundation; either version 2 of the License, or *
19 * (at your option) any later version. *
20 * *
21 ***************************************************************************/
22
24#include "qgsactionmanager.h"
25#include "qgsvectorlayer.h"
26#include "qgsaction.h"
28
29#include <QFileDialog>
30#include <QHeaderView>
31#include <QMessageBox>
32#include <QSettings>
33#include <QImageWriter>
34#include <QTableWidget>
35
37 : QWidget( parent )
38 , mLayer( actions.layer() )
39{
40 setupUi( this );
41 QHeaderView *header = mAttributeActionTable->horizontalHeader();
42 header->setHighlightSections( false );
43 header->setStretchLastSection( true );
44 mAttributeActionTable->setColumnWidth( 0, 100 );
45 mAttributeActionTable->setColumnWidth( 1, 230 );
46 mAttributeActionTable->setCornerButtonEnabled( false );
47 mAttributeActionTable->setEditTriggers( QAbstractItemView::AnyKeyPressed | QAbstractItemView::SelectedClicked );
48
49 connect( mAttributeActionTable, &QTableWidget::itemDoubleClicked, this, &QgsAttributeActionDialog::itemDoubleClicked );
50 connect( mAttributeActionTable, &QTableWidget::itemSelectionChanged, this, &QgsAttributeActionDialog::updateButtons );
51 connect( mMoveUpButton, &QAbstractButton::clicked, this, &QgsAttributeActionDialog::moveUp );
52 connect( mMoveDownButton, &QAbstractButton::clicked, this, &QgsAttributeActionDialog::moveDown );
53 connect( mRemoveButton, &QAbstractButton::clicked, this, &QgsAttributeActionDialog::remove );
54 connect( mAddButton, &QAbstractButton::clicked, this, &QgsAttributeActionDialog::insert );
55 connect( mAddDefaultActionsButton, &QAbstractButton::clicked, this, &QgsAttributeActionDialog::addDefaultActions );
56
57 init( actions, mLayer->attributeTableConfig() );
58}
59
60void QgsAttributeActionDialog::init( const QgsActionManager &actions, const QgsAttributeTableConfig &attributeTableConfig )
61{
62 // Start from a fresh slate.
63 mAttributeActionTable->setRowCount( 0 );
64
65 int i = 0;
66 // Populate with our actions.
67 const auto constActions = actions.actions();
68 for ( const QgsAction &action : constActions )
69 {
70 insertRow( i++, action );
71 }
72
73 updateButtons();
74
76 visibleActionWidgetConfig.type = QgsAttributeTableConfig::Action;
77 visibleActionWidgetConfig.hidden = false;
78
79 mShowInAttributeTable->setChecked( attributeTableConfig.actionWidgetVisible() );
80 mAttributeTableWidgetType->setCurrentIndex( attributeTableConfig.actionWidgetStyle() );
81}
82
83QList<QgsAction> QgsAttributeActionDialog::actions() const
84{
85 QList<QgsAction> actions;
86
87 for ( int i = 0; i < mAttributeActionTable->rowCount(); ++i )
88 {
89 actions.append( rowToAction( i ) );
90 }
91
92 return actions;
93}
94
96{
97 return mShowInAttributeTable->isChecked();
98}
99
101{
102 return static_cast<QgsAttributeTableConfig::ActionWidgetStyle>( mAttributeTableWidgetType->currentIndex() );
103}
104
105void QgsAttributeActionDialog::insertRow( int row, const QgsAction &action )
106{
107 QTableWidgetItem *item = nullptr;
108 mAttributeActionTable->insertRow( row );
109
110 // Type
111 item = new QTableWidgetItem( textForType( action.type() ) );
112 item->setData( Role::ActionType, static_cast< int >( action.type() ) );
113 item->setData( Role::ActionId, action.id() );
114 item->setFlags( item->flags() & ~Qt::ItemIsEditable );
115 mAttributeActionTable->setItem( row, Type, item );
116
117 // Description
118 mAttributeActionTable->setItem( row, Description, new QTableWidgetItem( action.name() ) );
119
120 // Short Title
121 mAttributeActionTable->setItem( row, ShortTitle, new QTableWidgetItem( action.shortTitle() ) );
122
123 // Action text
124 item = new QTableWidgetItem( action.command().length() > 30 ? action.command().left( 27 ) + "…" : action.command() );
125 item->setData( Qt::UserRole, action.command() );
126 mAttributeActionTable->setItem( row, ActionText, item );
127
128 // Capture output
129 item = new QTableWidgetItem();
130 item->setFlags( item->flags() & ~( Qt::ItemIsEditable ) );
131 item->setCheckState( action.capture() ? Qt::Checked : Qt::Unchecked );
132 mAttributeActionTable->setItem( row, Capture, item );
133
134 // Scopes
135 item = new QTableWidgetItem();
136 item->setFlags( item->flags() & ~( Qt::ItemIsEditable ) );
137 QStringList actionScopes = qgis::setToList( action.actionScopes() );
138 std::sort( actionScopes.begin(), actionScopes.end() );
139 item->setText( actionScopes.join( QLatin1String( ", " ) ) );
140 item->setData( Qt::UserRole, QVariant::fromValue<QSet<QString>>( action.actionScopes() ) );
141 mAttributeActionTable->setItem( row, ActionScopes, item );
142
143 // Icon
144 const QIcon icon = action.icon();
145 QTableWidgetItem *headerItem = new QTableWidgetItem( icon, QString() );
146 headerItem->setData( Qt::UserRole, action.iconPath() );
147 mAttributeActionTable->setVerticalHeaderItem( row, headerItem );
148
149 // Notification message
150 mAttributeActionTable->setItem( row, NotificationMessage, new QTableWidgetItem( action.notificationMessage() ) );
151
152 // EnabledOnlyWhenEditable
153 item = new QTableWidgetItem();
154 item->setFlags( item->flags() & ~( Qt::ItemIsEditable ) );
155 item->setCheckState( action.isEnabledOnlyWhenEditable() ? Qt::Checked : Qt::Unchecked );
156 mAttributeActionTable->setItem( row, EnabledOnlyWhenEditable, item );
157
158 updateButtons();
159}
160
161void QgsAttributeActionDialog::insertRow( int row, Qgis::AttributeActionType type, const QString &name, const QString &actionText, const QString &iconPath, bool capture, const QString &shortTitle, const QSet<QString> &actionScopes, const QString &notificationMessage, bool isEnabledOnlyWhenEditable )
162{
163 if ( uniqueName( name ) == name )
164 insertRow( row, QgsAction( type, name, actionText, iconPath, capture, shortTitle, actionScopes, notificationMessage, isEnabledOnlyWhenEditable ) );
165}
166
167void QgsAttributeActionDialog::moveUp()
168{
169 // Swap the selected row with the one above
170
171 int row1 = -1, row2 = -1;
172 QList<QTableWidgetItem *> selection = mAttributeActionTable->selectedItems();
173 if ( !selection.isEmpty() )
174 {
175 row1 = selection.first()->row();
176 }
177
178 if ( row1 > 0 )
179 row2 = row1 - 1;
180
181 if ( row1 != -1 && row2 != -1 )
182 {
183 swapRows( row1, row2 );
184 // Move the selection to follow
185 mAttributeActionTable->selectRow( row2 );
186 }
187}
188
189void QgsAttributeActionDialog::moveDown()
190{
191 // Swap the selected row with the one below
192 int row1 = -1, row2 = -1;
193 QList<QTableWidgetItem *> selection = mAttributeActionTable->selectedItems();
194 if ( !selection.isEmpty() )
195 {
196 row1 = selection.first()->row();
197 }
198
199 if ( row1 < mAttributeActionTable->rowCount() - 1 )
200 row2 = row1 + 1;
201
202 if ( row1 != -1 && row2 != -1 )
203 {
204 swapRows( row1, row2 );
205 // Move the selection to follow
206 mAttributeActionTable->selectRow( row2 );
207 }
208}
209
210void QgsAttributeActionDialog::swapRows( int row1, int row2 )
211{
212 const int colCount = mAttributeActionTable->columnCount();
213 for ( int col = 0; col < colCount; col++ )
214 {
215 QTableWidgetItem *item = mAttributeActionTable->takeItem( row1, col );
216 mAttributeActionTable->setItem( row1, col, mAttributeActionTable->takeItem( row2, col ) );
217 mAttributeActionTable->setItem( row2, col, item );
218 }
219 QTableWidgetItem *header = mAttributeActionTable->takeVerticalHeaderItem( row1 );
220 mAttributeActionTable->setVerticalHeaderItem( row1, mAttributeActionTable->takeVerticalHeaderItem( row2 ) );
221 mAttributeActionTable->setVerticalHeaderItem( row2, header );
222}
223
224QgsAction QgsAttributeActionDialog::rowToAction( int row ) const
225{
226 const QUuid id { mAttributeActionTable->item( row, Type )->data( Role::ActionId ).toUuid() };
227 QgsAction action( id,
228 static_cast<Qgis::AttributeActionType>( mAttributeActionTable->item( row, Type )->data( Role::ActionType ).toInt() ),
229 mAttributeActionTable->item( row, Description )->text(),
230 mAttributeActionTable->item( row, ActionText )->data( Qt::UserRole ).toString(),
231 mAttributeActionTable->verticalHeaderItem( row )->data( Qt::UserRole ).toString(),
232 mAttributeActionTable->item( row, Capture )->checkState() == Qt::Checked,
233 mAttributeActionTable->item( row, ShortTitle )->text(),
234 mAttributeActionTable->item( row, ActionScopes )->data( Qt::UserRole ).value<QSet<QString>>(),
235 mAttributeActionTable->item( row, NotificationMessage )->text(),
236 mAttributeActionTable->item( row, EnabledOnlyWhenEditable )->checkState() == Qt::Checked
237 );
238 return action;
239}
240
241QString QgsAttributeActionDialog::textForType( Qgis::AttributeActionType type )
242{
243 switch ( type )
244 {
246 return tr( "Generic" );
248 return tr( "Python" );
250 return tr( "macOS" );
252 return tr( "Windows" );
254 return tr( "Unix" );
256 return tr( "Open URL" );
258 return tr( "Submit URL (urlencoded or JSON)" );
260 return tr( "Submit URL (multipart)" );
261 }
262 return QString();
263}
264
265void QgsAttributeActionDialog::remove()
266{
267 QList<QTableWidgetItem *> selection = mAttributeActionTable->selectedItems();
268 if ( !selection.isEmpty() )
269 {
270 // Remove the selected row.
271 int row = selection.first()->row();
272 mAttributeActionTable->removeRow( row );
273
274 // And select the row below the one that was selected or the last one.
275 if ( row >= mAttributeActionTable->rowCount() )
276 row = mAttributeActionTable->rowCount() - 1;
277 mAttributeActionTable->selectRow( row );
278
279 updateButtons();
280 }
281}
282
283void QgsAttributeActionDialog::insert()
284{
285 // Add the action details as a new row in the table.
286 const int pos = mAttributeActionTable->rowCount();
287
288 QgsAttributeActionPropertiesDialog dlg( mLayer, this );
289 dlg.setWindowTitle( tr( "Add New Action" ) );
290
291 if ( dlg.exec() )
292 {
293 const QString name = uniqueName( dlg.description() );
294
295 insertRow( pos, dlg.type(), name, dlg.actionText(), dlg.iconPath(), dlg.capture(), dlg.shortTitle(), dlg.actionScopes(), dlg.notificationMessage(), dlg.isEnabledOnlyWhenEditable() );
296 }
297}
298
299void QgsAttributeActionDialog::updateButtons()
300{
301 QList<QTableWidgetItem *> selection = mAttributeActionTable->selectedItems();
302 const bool hasSelection = !selection.isEmpty();
303
304 if ( hasSelection )
305 {
306 const int row = selection.first()->row();
307 mMoveUpButton->setEnabled( row >= 1 );
308 mMoveDownButton->setEnabled( row >= 0 && row < mAttributeActionTable->rowCount() - 1 );
309 }
310 else
311 {
312 mMoveUpButton->setEnabled( false );
313 mMoveDownButton->setEnabled( false );
314 }
315
316 mRemoveButton->setEnabled( hasSelection );
317}
318
319void QgsAttributeActionDialog::addDefaultActions()
320{
321 int pos = 0;
322 insertRow( pos++, Qgis::AttributeActionType::Generic, tr( "Echo attribute's value" ), QStringLiteral( "echo \"[% @field_value %]\"" ), QString(), true, tr( "Attribute Value" ), QSet<QString>() << QStringLiteral( "Field" ), QString() );
323 insertRow( pos++, Qgis::AttributeActionType::Generic, tr( "Run an application" ), QStringLiteral( "ogr2ogr -f \"GPKG\" \"[% \"OUTPUT_PATH\" %]\" \"[% \"INPUT_FILE\" %]\"" ), QString(), true, tr( "Run application" ), QSet<QString>() << QStringLiteral( "Feature" ) << QStringLiteral( "Canvas" ), QString() );
324 insertRow( pos++, Qgis::AttributeActionType::GenericPython, tr( "Display the feature id in the message bar" ), QStringLiteral( "from qgis.utils import iface\n\niface.messageBar().pushInfo(\"Feature id\", \"The feature id is [% $id %]\")" ), QString(), false, tr( "Feature ID" ), QSet<QString>() << QStringLiteral( "Feature" ) << QStringLiteral( "Canvas" ), QString() );
325 insertRow( pos++, Qgis::AttributeActionType::GenericPython, tr( "Selected field's value (Identify features tool)" ), QStringLiteral( "from qgis.PyQt import QtWidgets\n\nQtWidgets.QMessageBox.information(None, \"Current field's value\", \"[% @field_name %] = [% @field_value %]\")" ), QString(), false, tr( "Field Value" ), QSet<QString>() << QStringLiteral( "Field" ), QString() );
326 insertRow( pos++, Qgis::AttributeActionType::GenericPython, tr( "Clicked coordinates (Run feature actions tool)" ), QStringLiteral( "from qgis.PyQt import QtWidgets\n\nQtWidgets.QMessageBox.information(None, \"Clicked coords\", \"layer: [% @layer_id %]\\ncoords: ([% @click_x %],[% @click_y %])\")" ), QString(), false, tr( "Clicked Coordinate" ), QSet<QString>() << QStringLiteral( "Canvas" ), QString() );
327 insertRow( pos++, Qgis::AttributeActionType::OpenUrl, tr( "Open file" ), QStringLiteral( "[% \"PATH\" %]" ), QString(), false, tr( "Open file" ), QSet<QString>() << QStringLiteral( "Feature" ) << QStringLiteral( "Canvas" ), QString() );
328 insertRow( pos++, Qgis::AttributeActionType::OpenUrl, tr( "Search on web based on attribute's value" ), QStringLiteral( "https://www.google.com/search?q=[% @field_value %]" ), QString(), false, tr( "Search Web" ), QSet<QString>() << QStringLiteral( "Field" ), QString() );
329 insertRow( pos++, Qgis::AttributeActionType::GenericPython, tr( "List feature ids" ), QStringLiteral( "from qgis.PyQt import QtWidgets\n\nlayer = QgsProject.instance().mapLayer('[% @layer_id %]')\nif layer.selectedFeatureCount():\n ids = layer.selectedFeatureIds()\nelse:\n ids = [f.id() for f in layer.getFeatures()]\n\nQtWidgets.QMessageBox.information(None, \"Feature ids\", ', '.join([str(id) for id in ids]))" ), QString(), false, tr( "List feature ids" ), QSet<QString>() << QStringLiteral( "Layer" ), QString() );
330 insertRow( pos++, Qgis::AttributeActionType::GenericPython, tr( "Duplicate selected features" ), QStringLiteral( "project = QgsProject.instance()\nlayer = QgsProject.instance().mapLayer('[% @layer_id %]')\nif not layer.isEditable():\n qgis.utils.iface.messageBar().pushMessage( 'Cannot duplicate feature in not editable mode on layer {layer}'.format( layer=layer.name() ) )\nelse:\n features=[]\n if len('[% $id %]')>0:\n features.append( layer.getFeature( [% $id %] ) )\n else:\n for x in layer.selectedFeatures():\n features.append( x )\n feature_count=0\n children_info=''\n featureids=[]\n for f in features:\n result=QgsVectorLayerUtils.duplicateFeature(layer, f, project, 0 )\n featureids.append( result[0].id() )\n feature_count+=1\n for ch_layer in result[1].layers():\n children_info+='{number_of_children} children on layer {children_layer}\\n'.format( number_of_children=str( len( result[1].duplicatedFeatures(ch_layer) ) ), children_layer=ch_layer.name() )\n ch_layer.selectByIds( result[1].duplicatedFeatures(ch_layer) )\n layer.selectByIds( featureids )\n qgis.utils.iface.messageBar().pushMessage( '{number_of_features} features on layer {layer} duplicated with\\n{children_info}'.format( number_of_features=str( feature_count ), layer=layer.name(), children_info=children_info ) )" ), QString(), false, tr( "Duplicate selected" ), QSet<QString>() << QStringLiteral( "Layer" ), QString(), true );
331
332}
333
334void QgsAttributeActionDialog::itemDoubleClicked( QTableWidgetItem *item )
335{
336 const int row = item->row();
337
338 QgsAttributeActionPropertiesDialog actionProperties(
339 static_cast<Qgis::AttributeActionType>( mAttributeActionTable->item( row, Type )->data( Role::ActionType ).toInt() ),
340 mAttributeActionTable->item( row, Description )->text(),
341 mAttributeActionTable->item( row, ShortTitle )->text(),
342 mAttributeActionTable->verticalHeaderItem( row )->data( Qt::UserRole ).toString(),
343 mAttributeActionTable->item( row, ActionText )->data( Qt::UserRole ).toString(),
344 mAttributeActionTable->item( row, Capture )->checkState() == Qt::Checked,
345 mAttributeActionTable->item( row, ActionScopes )->data( Qt::UserRole ).value<QSet<QString>>(),
346 mAttributeActionTable->item( row, NotificationMessage )->text(),
347 mAttributeActionTable->item( row, EnabledOnlyWhenEditable )->checkState() == Qt::Checked,
348 mLayer
349 );
350
351 actionProperties.setWindowTitle( tr( "Edit Action" ) );
352
353 if ( actionProperties.exec() )
354 {
355 mAttributeActionTable->item( row, Type )->setData( Role::ActionType, static_cast< int >( actionProperties.type() ) );
356 mAttributeActionTable->item( row, Type )->setText( textForType( actionProperties.type() ) );
357 mAttributeActionTable->item( row, Description )->setText( actionProperties.description() );
358 mAttributeActionTable->item( row, ShortTitle )->setText( actionProperties.shortTitle() );
359 mAttributeActionTable->item( row, ActionText )->setText( actionProperties.actionText().length() > 30 ? actionProperties.actionText().left( 27 ) + "…" : actionProperties.actionText() );
360 mAttributeActionTable->item( row, ActionText )->setData( Qt::UserRole, actionProperties.actionText() );
361 mAttributeActionTable->item( row, Capture )->setCheckState( actionProperties.capture() ? Qt::Checked : Qt::Unchecked );
362 mAttributeActionTable->item( row, NotificationMessage )->setText( actionProperties.notificationMessage() );
363 mAttributeActionTable->item( row, EnabledOnlyWhenEditable )->setCheckState( actionProperties.isEnabledOnlyWhenEditable() ? Qt::Checked : Qt::Unchecked );
364
365 QTableWidgetItem *item = mAttributeActionTable->item( row, ActionScopes );
366 QStringList actionScopes = qgis::setToList( actionProperties.actionScopes() );
367 std::sort( actionScopes.begin(), actionScopes.end() );
368 item->setText( actionScopes.join( QLatin1String( ", " ) ) );
369 item->setData( Qt::UserRole, QVariant::fromValue<QSet<QString>>( actionProperties.actionScopes() ) );
370
371 mAttributeActionTable->verticalHeaderItem( row )->setData( Qt::UserRole, actionProperties.iconPath() );
372 mAttributeActionTable->verticalHeaderItem( row )->setIcon( QIcon( actionProperties.iconPath() ) );
373 }
374}
375
376QString QgsAttributeActionDialog::uniqueName( QString name )
377{
378 // Make sure that the given name is unique, adding a numerical
379 // suffix if necessary.
380
381 const int pos = mAttributeActionTable->rowCount();
382 bool unique = true;
383
384 for ( int i = 0; i < pos; ++i )
385 {
386 if ( mAttributeActionTable->item( i, Description )->text() == name )
387 unique = false;
388 }
389
390 if ( !unique )
391 {
392 int suffix_num = 1;
393 QString new_name;
394 while ( !unique )
395 {
396 const QString suffix = QString::number( suffix_num );
397 new_name = name + '_' + suffix;
398 unique = true;
399 for ( int i = 0; i < pos; ++i )
400 if ( mAttributeActionTable->item( i, 0 )->text() == new_name )
401 unique = false;
402 ++suffix_num;
403 }
404 name = new_name;
405 }
406 return name;
407}
AttributeActionType
Map layer action flags.
Definition: qgis.h:2648
@ Mac
MacOS specific.
@ OpenUrl
Open URL action.
@ SubmitUrlMultipart
POST data to an URL using "multipart/form-data".
@ Windows
Windows specific.
@ SubmitUrlEncoded
POST data to an URL, using "application/x-www-form-urlencoded" or "application/json" if the body is v...
Storage and management of actions associated with a layer.
Utility class that encapsulates an action based on vector attributes.
Definition: qgsaction.h:37
QString notificationMessage() const
Returns the notification message that triggers the action.
Definition: qgsaction.h:157
QString name() const
The name of the action. This may be a longer description.
Definition: qgsaction.h:118
QSet< QString > actionScopes() const
The action scopes define where an action will be available.
Definition: qgsaction.cpp:319
Qgis::AttributeActionType type() const
The action type.
Definition: qgsaction.h:160
QIcon icon() const
The icon.
Definition: qgsaction.h:141
QString iconPath() const
The path to the icon.
Definition: qgsaction.h:138
QString command() const
Returns the command that is executed by this action.
Definition: qgsaction.h:150
QString shortTitle() const
The short title is used to label user interface elements like buttons.
Definition: qgsaction.h:121
bool isEnabledOnlyWhenEditable() const
Returns whether only enabled in editable mode.
Definition: qgsaction.h:167
bool capture() const
Whether to capture output for display when this action is run.
Definition: qgsaction.h:163
QUuid id() const
Returns a unique id for this action.
Definition: qgsaction.h:128
void init(const QgsActionManager &action, const QgsAttributeTableConfig &attributeTableConfig)
QgsAttributeActionDialog(const QgsActionManager &actions, QWidget *parent=nullptr)
QList< QgsAction > actions() const
QgsAttributeTableConfig::ActionWidgetStyle attributeTableWidgetStyle() const
This is a container for configuration of the attribute table.
@ Action
This column represents an action widget.
ActionWidgetStyle
The style of the action widget in the attribute table.
bool actionWidgetVisible() const
Returns true if the action widget is visible.
ActionWidgetStyle actionWidgetStyle() const
Gets the style of the action widget.
QgsAttributeTableConfig attributeTableConfig() const
Returns the attribute table configuration object.
Defines the configuration of a column in the attribute table.
QgsAttributeTableConfig::Type type
The type of this column.
bool hidden
Flag that controls if the column is hidden.