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