QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
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" );
259  }
260  return QString();
261 }
262 
263 void QgsAttributeActionDialog::remove()
264 {
265  QList<QTableWidgetItem *> selection = mAttributeActionTable->selectedItems();
266  if ( !selection.isEmpty() )
267  {
268  // Remove the selected row.
269  int row = selection.first()->row();
270  mAttributeActionTable->removeRow( row );
271 
272  // And select the row below the one that was selected or the last one.
273  if ( row >= mAttributeActionTable->rowCount() )
274  row = mAttributeActionTable->rowCount() - 1;
275  mAttributeActionTable->selectRow( row );
276 
277  updateButtons();
278  }
279 }
280 
281 void QgsAttributeActionDialog::insert()
282 {
283  // Add the action details as a new row in the table.
284  const int pos = mAttributeActionTable->rowCount();
285 
286  QgsAttributeActionPropertiesDialog dlg( mLayer, this );
287  dlg.setWindowTitle( tr( "Add New Action" ) );
288 
289  if ( dlg.exec() )
290  {
291  const QString name = uniqueName( dlg.description() );
292 
293  insertRow( pos, dlg.type(), name, dlg.actionText(), dlg.iconPath(), dlg.capture(), dlg.shortTitle(), dlg.actionScopes(), dlg.notificationMessage(), dlg.isEnabledOnlyWhenEditable() );
294  }
295 }
296 
297 void QgsAttributeActionDialog::updateButtons()
298 {
299  QList<QTableWidgetItem *> selection = mAttributeActionTable->selectedItems();
300  const bool hasSelection = !selection.isEmpty();
301 
302  if ( hasSelection )
303  {
304  const int row = selection.first()->row();
305  mMoveUpButton->setEnabled( row >= 1 );
306  mMoveDownButton->setEnabled( row >= 0 && row < mAttributeActionTable->rowCount() - 1 );
307  }
308  else
309  {
310  mMoveUpButton->setEnabled( false );
311  mMoveDownButton->setEnabled( false );
312  }
313 
314  mRemoveButton->setEnabled( hasSelection );
315 }
316 
317 void QgsAttributeActionDialog::addDefaultActions()
318 {
319  int pos = 0;
320  insertRow( pos++, QgsAction::Generic, tr( "Echo attribute's value" ), QStringLiteral( "echo \"[% \"MY_FIELD\" %]\"" ), QString(), true, tr( "Attribute Value" ), QSet<QString>() << QStringLiteral( "Field" ), QString() );
321  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() );
322  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() );
323  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() );
324  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() );
325  insertRow( pos++, QgsAction::OpenUrl, tr( "Open file" ), QStringLiteral( "[% \"PATH\" %]" ), QString(), false, tr( "Open file" ), QSet<QString>() << QStringLiteral( "Feature" ) << QStringLiteral( "Canvas" ), QString() );
326  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() );
327  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() );
328  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 );
329 
330 }
331 
332 void QgsAttributeActionDialog::itemDoubleClicked( QTableWidgetItem *item )
333 {
334  const int row = item->row();
335 
336  QgsAttributeActionPropertiesDialog actionProperties(
337  static_cast<QgsAction::ActionType>( mAttributeActionTable->item( row, Type )->data( Role::ActionType ).toInt() ),
338  mAttributeActionTable->item( row, Description )->text(),
339  mAttributeActionTable->item( row, ShortTitle )->text(),
340  mAttributeActionTable->verticalHeaderItem( row )->data( Qt::UserRole ).toString(),
341  mAttributeActionTable->item( row, ActionText )->data( Qt::UserRole ).toString(),
342  mAttributeActionTable->item( row, Capture )->checkState() == Qt::Checked,
343  mAttributeActionTable->item( row, ActionScopes )->data( Qt::UserRole ).value<QSet<QString>>(),
344  mAttributeActionTable->item( row, NotificationMessage )->text(),
345  mAttributeActionTable->item( row, EnabledOnlyWhenEditable )->checkState() == Qt::Checked,
346  mLayer
347  );
348 
349  actionProperties.setWindowTitle( tr( "Edit Action" ) );
350 
351  if ( actionProperties.exec() )
352  {
353  mAttributeActionTable->item( row, Type )->setData( Role::ActionType, actionProperties.type() );
354  mAttributeActionTable->item( row, Type )->setText( textForType( actionProperties.type() ) );
355  mAttributeActionTable->item( row, Description )->setText( actionProperties.description() );
356  mAttributeActionTable->item( row, ShortTitle )->setText( actionProperties.shortTitle() );
357  mAttributeActionTable->item( row, ActionText )->setText( actionProperties.actionText().length() > 30 ? actionProperties.actionText().left( 27 ) + "…" : actionProperties.actionText() );
358  mAttributeActionTable->item( row, ActionText )->setData( Qt::UserRole, actionProperties.actionText() );
359  mAttributeActionTable->item( row, Capture )->setCheckState( actionProperties.capture() ? Qt::Checked : Qt::Unchecked );
360  mAttributeActionTable->item( row, NotificationMessage )->setText( actionProperties.notificationMessage() );
361  mAttributeActionTable->item( row, EnabledOnlyWhenEditable )->setCheckState( actionProperties.isEnabledOnlyWhenEditable() ? Qt::Checked : Qt::Unchecked );
362 
363  QTableWidgetItem *item = mAttributeActionTable->item( row, ActionScopes );
364  QStringList actionScopes = qgis::setToList( actionProperties.actionScopes() );
365  std::sort( actionScopes.begin(), actionScopes.end() );
366  item->setText( actionScopes.join( QLatin1String( ", " ) ) );
367  item->setData( Qt::UserRole, QVariant::fromValue<QSet<QString>>( actionProperties.actionScopes() ) );
368 
369  mAttributeActionTable->verticalHeaderItem( row )->setData( Qt::UserRole, actionProperties.iconPath() );
370  mAttributeActionTable->verticalHeaderItem( row )->setIcon( QIcon( actionProperties.iconPath() ) );
371  }
372 }
373 
374 QString QgsAttributeActionDialog::uniqueName( QString name )
375 {
376  // Make sure that the given name is unique, adding a numerical
377  // suffix if necessary.
378 
379  const int pos = mAttributeActionTable->rowCount();
380  bool unique = true;
381 
382  for ( int i = 0; i < pos; ++i )
383  {
384  if ( mAttributeActionTable->item( i, Description )->text() == name )
385  unique = false;
386  }
387 
388  if ( !unique )
389  {
390  int suffix_num = 1;
391  QString new_name;
392  while ( !unique )
393  {
394  const QString suffix = QString::number( suffix_num );
395  new_name = name + '_' + suffix;
396  unique = true;
397  for ( int i = 0; i < pos; ++i )
398  if ( mAttributeActionTable->item( i, 0 )->text() == new_name )
399  unique = false;
400  ++suffix_num;
401  }
402  name = new_name;
403  }
404  return name;
405 }
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:164
QString name() const
The name of the action. This may be a longer description.
Definition: qgsaction.h:125
QSet< QString > actionScopes() const
The action scopes define where an action will be available.
Definition: qgsaction.cpp:90
QIcon icon() const
The icon.
Definition: qgsaction.h:148
QString iconPath() const
The path to the icon.
Definition: qgsaction.h:145
ActionType type() const
The action type.
Definition: qgsaction.h:167
QString command() const
Returns the command that is executed by this action.
Definition: qgsaction.h:157
QString shortTitle() const
The short title is used to label user interface elements like buttons.
Definition: qgsaction.h:128
bool isEnabledOnlyWhenEditable() const
Returns whether only enabled in editable mode.
Definition: qgsaction.h:174
bool capture() const
Whether to capture output for display when this action is run.
Definition: qgsaction.h:170
QUuid id() const
Returns a unique id for this action.
Definition: qgsaction.h:135
@ GenericPython
Definition: qgsaction.h:40
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.