Skip to content

Commit 5d23bc0

Browse files
authored
Merge pull request #6447 from nyalldawson/backport_action
Backport action API tweaks to 3.0
2 parents 001c80b + b768e6d commit 5d23bc0

31 files changed

+504
-102
lines changed

python/core/qgsaction.sip.in

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ Create a new QgsAction
4242
:param description: A human readable description string
4343
:param command: The action text. Its interpretation depends on the type
4444
:param capture: If this is set to true, the output will be captured when an action is run
45+
:param enabledOnlyWhenEditable: if true then action is only enable in editmode
4546
%End
4647

4748
QgsAction( ActionType type, const QString &description, const QString &action, const QString &icon, bool capture, const QString &shortTitle = QString(), const QSet<QString> &actionScopes = QSet<QString>(), const QString &notificationMessage = QString() );
@@ -56,6 +57,7 @@ Create a new QgsAction
5657
:param shortTitle: A short string used to label user interface elements like buttons
5758
:param actionScopes: A set of scopes in which this action will be available
5859
:param notificationMessage: A particular message which reception will trigger the action
60+
:param enabledOnlyWhenEditable: if true then action is only enable in editmode
5961
%End
6062

6163
QString name() const;
@@ -118,6 +120,13 @@ The action type
118120
Whether to capture output for display when this action is run
119121
%End
120122

123+
124+
bool isEnabledOnlyWhenEditable() const;
125+
%Docstring
126+
Return whether only enabled in editable mode
127+
%End
128+
129+
121130
bool runable() const;
122131
%Docstring
123132
Checks if the action is runable on the current platform

python/gui/qgsactionmenu.sip.in

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@ Change the feature on which actions are performed
7171

7272
:param feature: A feature. Will not take ownership. It's the callers responsibility to keep the feature
7373
as long as the menu is displayed and the action is running.
74+
%End
75+
76+
void setMode( const QgsAttributeForm::Mode mode );
77+
%Docstring
78+
Change the mode of the actions
79+
80+
:param mode: The mode of the attribute form
7481
%End
7582

7683
void setExpressionContextScope( const QgsExpressionContextScope &scope );

python/gui/qgsattributeform.sip.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class QgsAttributeForm : QWidget
2626
MultiEditMode,
2727
SearchMode,
2828
AggregateSearchMode,
29+
IdentifyMode
2930
};
3031

3132
enum FilterType

python/gui/qgsmaplayeractionregistry.sip.in

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,15 @@ An action which can run on map layers
3030
typedef QFlags<QgsMapLayerAction::Target> Targets;
3131

3232

33-
QgsMapLayerAction( const QString &name, QObject *parent /TransferThis/, Targets targets = AllActions, const QIcon &icon = QIcon() );
33+
enum Flag
34+
{
35+
EnabledOnlyWhenEditable,
36+
};
37+
38+
typedef QFlags<QgsMapLayerAction::Flag> Flags;
39+
40+
41+
QgsMapLayerAction( const QString &name, QObject *parent /TransferThis/, Targets targets = AllActions, const QIcon &icon = QIcon(), QgsMapLayerAction::Flags flags = 0 );
3442
%Docstring
3543
Creates a map layer action which can run on any layer
3644

@@ -39,18 +47,25 @@ Creates a map layer action which can run on any layer
3947
using AllActions as a target probably does not make a lot of sense. This default action was settled for API compatibility reasons.
4048
%End
4149

42-
QgsMapLayerAction( const QString &name, QObject *parent /TransferThis/, QgsMapLayer *layer, Targets targets = AllActions, const QIcon &icon = QIcon() );
50+
QgsMapLayerAction( const QString &name, QObject *parent /TransferThis/, QgsMapLayer *layer, Targets targets = AllActions, const QIcon &icon = QIcon(), QgsMapLayerAction::Flags flags = 0 );
4351
%Docstring
4452
Creates a map layer action which can run only on a specific layer
4553
%End
4654

47-
QgsMapLayerAction( const QString &name, QObject *parent /TransferThis/, QgsMapLayer::LayerType layerType, Targets targets = AllActions, const QIcon &icon = QIcon() );
55+
QgsMapLayerAction( const QString &name, QObject *parent /TransferThis/, QgsMapLayer::LayerType layerType, Targets targets = AllActions, const QIcon &icon = QIcon(), QgsMapLayerAction::Flags flags = 0 );
4856
%Docstring
4957
Creates a map layer action which can run on a specific type of layer
5058
%End
5159

5260
~QgsMapLayerAction();
5361

62+
QgsMapLayerAction::Flags flags() const;
63+
%Docstring
64+
Layer behavior flags.
65+
66+
.. versionadded:: 3.0
67+
%End
68+
5469
bool canRunUsingLayer( QgsMapLayer *layer ) const;
5570
%Docstring
5671
True if action can run using the specified layer
@@ -78,6 +93,13 @@ Define the targets of the action
7893
const Targets &targets() const;
7994
%Docstring
8095
Return availibity of action
96+
%End
97+
98+
bool isEnabledOnlyWhenEditable() const;
99+
%Docstring
100+
Returns true if the action is only enabled for layers in editable mode.
101+
102+
.. versionadded:: 3.0
81103
%End
82104

83105
signals:
@@ -158,6 +180,9 @@ Triggered when an action is added or removed from the registry
158180

159181
};
160182

183+
QFlags<QgsMapLayerAction::Flag> operator|(QgsMapLayerAction::Flag f1, QFlags<QgsMapLayerAction::Flag> f2);
184+
185+
161186
/************************************************************************
162187
* This file has been generated automatically from *
163188
* *

resources/qgis_global_settings.ini

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,3 @@ connections-xyz\OpenStreetMap\zmin=0
3434
# for now this is online version of the User Guide for latest (LTR) release
3535
helpSearchPath=https://docs.qgis.org/$qgis_short_version/$qgis_locale/docs/user_manual/
3636

37-
[app]
38-
39-
# If true, the experimental "duplicate feature" actions will be shown in the QGIS UI
40-
tools\showDuplicateFeatureActions=false

src/app/qgisapp.cpp

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6464,6 +6464,9 @@ void QgisApp::refreshFeatureActions()
64646464
QList<QgsAction> actions = vlayer->actions()->actions( QStringLiteral( "Canvas" ) );
64656465
Q_FOREACH ( const QgsAction &action, actions )
64666466
{
6467+
if ( !vlayer->isEditable() && action.isEnabledOnlyWhenEditable() )
6468+
continue;
6469+
64676470
QString actionTitle = !action.shortTitle().isEmpty() ? action.shortTitle() : action.icon().isNull() ? action.name() : QStringLiteral( "" );
64686471
QAction *qAction = new QAction( action.icon(), actionTitle, mFeatureActionMenu );
64696472
qAction->setData( QVariant::fromValue<QgsAction>( action ) );
@@ -7492,31 +7495,27 @@ void QgisApp::setupLayoutManagerConnections()
74927495

74937496
void QgisApp::setupDuplicateFeaturesAction()
74947497
{
7495-
QgsSettings settings;
7496-
if ( settings.value( QStringLiteral( "tools/showDuplicateFeatureActions" ), false, QgsSettings::App ).toBool() )
7497-
{
7498-
mDuplicateFeatureAction.reset( new QgsMapLayerAction( tr( "Duplicate feature" ),
7499-
nullptr, QgsMapLayerAction::SingleFeature,
7500-
QgsApplication::getThemeIcon( QStringLiteral( "/mActionDuplicateFeature.svg" ) ) ) );
7498+
mDuplicateFeatureAction.reset( new QgsMapLayerAction( tr( "Duplicate feature" ),
7499+
nullptr, QgsMapLayerAction::SingleFeature,
7500+
QgsApplication::getThemeIcon( QStringLiteral( "/mActionDuplicateFeature.svg" ) ), QgsMapLayerAction::EnabledOnlyWhenEditable ) );
75017501

7502-
QgsGui::mapLayerActionRegistry()->addMapLayerAction( mDuplicateFeatureAction.get() );
7503-
connect( mDuplicateFeatureAction.get(), &QgsMapLayerAction::triggeredForFeature, this, [this]( QgsMapLayer * layer, const QgsFeature & feat )
7504-
{
7505-
duplicateFeatures( layer, feat );
7506-
}
7507-
);
7502+
QgsGui::mapLayerActionRegistry()->addMapLayerAction( mDuplicateFeatureAction.get() );
7503+
connect( mDuplicateFeatureAction.get(), &QgsMapLayerAction::triggeredForFeature, this, [this]( QgsMapLayer * layer, const QgsFeature & feat )
7504+
{
7505+
duplicateFeatures( layer, feat );
7506+
}
7507+
);
75087508

7509-
mDuplicateFeatureDigitizeAction.reset( new QgsMapLayerAction( tr( "Duplicate feature and digitize" ),
7510-
nullptr, QgsMapLayerAction::SingleFeature,
7511-
QgsApplication::getThemeIcon( QStringLiteral( "/mActionDuplicateFeatureDigitized.svg" ) ) ) );
7509+
mDuplicateFeatureDigitizeAction.reset( new QgsMapLayerAction( tr( "Duplicate feature and digitize" ),
7510+
nullptr, QgsMapLayerAction::SingleFeature,
7511+
QgsApplication::getThemeIcon( QStringLiteral( "/mActionDuplicateFeatureDigitized.svg" ) ), QgsMapLayerAction::EnabledOnlyWhenEditable ) );
75127512

7513-
QgsGui::mapLayerActionRegistry()->addMapLayerAction( mDuplicateFeatureDigitizeAction.get() );
7514-
connect( mDuplicateFeatureDigitizeAction.get(), &QgsMapLayerAction::triggeredForFeature, this, [this]( QgsMapLayer * layer, const QgsFeature & feat )
7515-
{
7516-
duplicateFeatureDigitized( layer, feat );
7517-
}
7518-
);
7513+
QgsGui::mapLayerActionRegistry()->addMapLayerAction( mDuplicateFeatureDigitizeAction.get() );
7514+
connect( mDuplicateFeatureDigitizeAction.get(), &QgsMapLayerAction::triggeredForFeature, this, [this]( QgsMapLayer * layer, const QgsFeature & feat )
7515+
{
7516+
duplicateFeatureDigitized( layer, feat );
75197517
}
7518+
);
75207519
}
75217520

75227521
void QgisApp::setupAtlasMapLayerAction( QgsPrintLayout *layout, bool enableAction )
@@ -13204,7 +13203,13 @@ QgsFeature QgisApp::duplicateFeatures( QgsMapLayer *mlayer, const QgsFeature &fe
1320413203

1320513204
QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( mlayer );
1320613205

13207-
layer->startEditing();
13206+
if ( !layer->isEditable() )
13207+
{
13208+
//should never happen because the action should be disabled
13209+
QString msg = tr( "Cannot duplicate feature in not editable mode on layer %1" ).arg( layer->name() );
13210+
messageBar()->pushMessage( msg, Qgis::Warning, 3 );
13211+
return QgsFeature();
13212+
}
1320813213

1320913214
QgsFeatureList featureList;
1321013215

@@ -13252,7 +13257,13 @@ QgsFeature QgisApp::duplicateFeatureDigitized( QgsMapLayer *mlayer, const QgsFea
1325213257

1325313258
QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( mlayer );
1325413259

13255-
layer->startEditing();
13260+
if ( !layer->isEditable() )
13261+
{
13262+
//should never happen because the action should be disabled
13263+
QString msg = tr( "Cannot duplicate feature in not editable mode on layer %1" ).arg( layer->name() );
13264+
messageBar()->pushMessage( msg, Qgis::Warning, 3 );
13265+
return QgsFeature();
13266+
}
1325613267

1325713268
QgsMapToolDigitizeFeature *digitizeFeature = new QgsMapToolDigitizeFeature( mMapCanvas, mlayer, QgsMapToolCapture::CaptureNone );
1325813269

src/app/qgsattributeactiondialog.cpp

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -148,13 +148,19 @@ void QgsAttributeActionDialog::insertRow( int row, const QgsAction &action )
148148
// Notification message
149149
mAttributeActionTable->setItem( row, NotificationMessage, new QTableWidgetItem( action.notificationMessage() ) );
150150

151+
// EnabledOnlyWhenEditable
152+
item = new QTableWidgetItem();
153+
item->setFlags( item->flags() & ~( Qt::ItemIsEditable ) );
154+
item->setCheckState( action.isEnabledOnlyWhenEditable() ? Qt::Checked : Qt::Unchecked );
155+
mAttributeActionTable->setItem( row, EnabledOnlyWhenEditable, item );
156+
151157
updateButtons();
152158
}
153159

154-
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 )
160+
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 )
155161
{
156162
if ( uniqueName( name ) == name )
157-
insertRow( row, QgsAction( type, name, actionText, iconPath, capture, shortTitle, actionScopes, notificationMessage ) );
163+
insertRow( row, QgsAction( type, name, actionText, iconPath, capture, shortTitle, actionScopes, notificationMessage, isEnabledOnlyWhenEditable ) );
158164
}
159165

160166
void QgsAttributeActionDialog::moveUp()
@@ -223,7 +229,8 @@ QgsAction QgsAttributeActionDialog::rowToAction( int row ) const
223229
mAttributeActionTable->item( row, Capture )->checkState() == Qt::Checked,
224230
mAttributeActionTable->item( row, ShortTitle )->text(),
225231
mAttributeActionTable->item( row, ActionScopes )->data( Qt::UserRole ).value<QSet<QString>>(),
226-
mAttributeActionTable->item( row, NotificationMessage )->text()
232+
mAttributeActionTable->item( row, NotificationMessage )->text(),
233+
mAttributeActionTable->item( row, EnabledOnlyWhenEditable )->checkState() == Qt::Checked
227234
);
228235
return action;
229236
}
@@ -278,7 +285,7 @@ void QgsAttributeActionDialog::insert()
278285
{
279286
QString name = uniqueName( dlg.description() );
280287

281-
insertRow( pos, dlg.type(), name, dlg.actionText(), dlg.iconPath(), dlg.capture(), dlg.shortTitle(), dlg.actionScopes(), dlg.notificationMessage() );
288+
insertRow( pos, dlg.type(), name, dlg.actionText(), dlg.iconPath(), dlg.capture(), dlg.shortTitle(), dlg.actionScopes(), dlg.notificationMessage(), dlg.isEnabledOnlyWhenEditable() );
282289
}
283290
}
284291

@@ -313,7 +320,7 @@ void QgsAttributeActionDialog::addDefaultActions()
313320
insertRow( pos++, QgsAction::OpenUrl, tr( "Open file" ), QStringLiteral( "[% \"PATH\" %]" ), QLatin1String( "" ), false, tr( "Open file" ), QSet<QString>() << QStringLiteral( "Feature" ) << QStringLiteral( "Canvas" ), QString() );
314321
insertRow( pos++, QgsAction::OpenUrl, tr( "Search on web based on attribute's value" ), QStringLiteral( "http://www.google.com/search?q=[% \"ATTRIBUTE\" %]" ), QLatin1String( "" ), false, tr( "Search Web" ), QSet<QString>() << QStringLiteral( "Field" ), QString() );
315322
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]))" ), QLatin1String( "" ), false, tr( "List feature ids" ), QSet<QString>() << QStringLiteral( "Layer" ), QString() );
316-
insertRow( pos++, QgsAction::GenericPython, tr( "Duplicate selected features" ), QStringLiteral( "project = QgsProject.instance()\nlayer = QgsProject.instance().mapLayer('[% @layer_id %]')\nlayer.startEditing()\nfeatures=[]\nif len('[% $id %]')>0:\n features.append( layer.getFeature( [% $id %] ) )\nelse:\n for x in layer.selectedFeatures():\n features.append( x )\nfeature_count=0\nchildren_info=''\nfeatureids=[]\nfor 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) )\nlayer.selectByIds( featureids )\nqgis.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 ) )" ), QLatin1String( "" ), false, tr( "Duplicate selected" ), QSet<QString>() << QStringLiteral( "Layer" ), QString() );
323+
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 ) )" ), QLatin1String( "" ), false, tr( "Duplicate selected" ), QSet<QString>() << QStringLiteral( "Layer" ), QString(), true );
317324

318325
}
319326

@@ -330,6 +337,7 @@ void QgsAttributeActionDialog::itemDoubleClicked( QTableWidgetItem *item )
330337
mAttributeActionTable->item( row, Capture )->checkState() == Qt::Checked,
331338
mAttributeActionTable->item( row, ActionScopes )->data( Qt::UserRole ).value<QSet<QString>>(),
332339
mAttributeActionTable->item( row, NotificationMessage )->text(),
340+
mAttributeActionTable->item( row, EnabledOnlyWhenEditable )->checkState() == Qt::Checked,
333341
mLayer
334342
);
335343

@@ -344,6 +352,7 @@ void QgsAttributeActionDialog::itemDoubleClicked( QTableWidgetItem *item )
344352
mAttributeActionTable->item( row, ActionText )->setText( actionProperties.actionText() );
345353
mAttributeActionTable->item( row, Capture )->setCheckState( actionProperties.capture() ? Qt::Checked : Qt::Unchecked );
346354
mAttributeActionTable->item( row, NotificationMessage )->setText( actionProperties.notificationMessage() );
355+
mAttributeActionTable->item( row, EnabledOnlyWhenEditable )->setCheckState( actionProperties.isEnabledOnlyWhenEditable() ? Qt::Checked : Qt::Unchecked );
347356

348357
QTableWidgetItem *item = mAttributeActionTable->item( row, ActionScopes );
349358
QStringList actionScopes = actionProperties.actionScopes().toList();

src/app/qgsattributeactiondialog.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ class APP_EXPORT QgsAttributeActionDialog: public QWidget, private Ui::QgsAttrib
4444
ActionText,
4545
Capture,
4646
ActionScopes,
47-
NotificationMessage
47+
NotificationMessage,
48+
EnabledOnlyWhenEditable
4849
};
4950

5051
public:
@@ -70,7 +71,7 @@ class APP_EXPORT QgsAttributeActionDialog: public QWidget, private Ui::QgsAttrib
7071

7172
private:
7273
void insertRow( int row, const QgsAction &action );
73-
void 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 );
74+
void 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, const bool isEnabledOnlyWhenEditable = false );
7475
void swapRows( int row1, int row2 );
7576
QgsAction rowToAction( int row ) const;
7677

src/app/qgsattributeactionpropertiesdialog.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
#include <QFileDialog>
3232
#include <QImageWriter>
3333

34-
QgsAttributeActionPropertiesDialog::QgsAttributeActionPropertiesDialog( QgsAction::ActionType type, const QString &description, const QString &shortTitle, const QString &iconPath, const QString &actionText, bool capture, const QSet<QString> &actionScopes, const QString &notificationMessage, QgsVectorLayer *layer, QWidget *parent )
34+
QgsAttributeActionPropertiesDialog::QgsAttributeActionPropertiesDialog( QgsAction::ActionType type, const QString &description, const QString &shortTitle, const QString &iconPath, const QString &actionText, bool capture, const QSet<QString> &actionScopes, const QString &notificationMessage, bool isEnabledOnlyWhenEditable, QgsVectorLayer *layer, QWidget *parent )
3535
: QDialog( parent )
3636
, mLayer( layer )
3737
{
@@ -45,6 +45,7 @@ QgsAttributeActionPropertiesDialog::QgsAttributeActionPropertiesDialog( QgsActio
4545
mActionText->setText( actionText );
4646
mCaptureOutput->setChecked( capture );
4747
mNotificationMessage->setText( notificationMessage );
48+
mIsEnabledOnlyWhenEditable->setChecked( isEnabledOnlyWhenEditable );
4849

4950
init( actionScopes );
5051
}
@@ -107,6 +108,10 @@ QString QgsAttributeActionPropertiesDialog::notificationMessage() const
107108
return mNotificationMessage->text();
108109
}
109110

111+
bool QgsAttributeActionPropertiesDialog::isEnabledOnlyWhenEditable() const
112+
{
113+
return mIsEnabledOnlyWhenEditable->isChecked();
114+
}
110115

111116
bool QgsAttributeActionPropertiesDialog::capture() const
112117
{

0 commit comments

Comments
 (0)