Skip to content

Commit

Permalink
[FEATURE] layer refresh and trigger actions on provider notification
Browse files Browse the repository at this point in the history
[needs-docs]

In vector layer properties (only usefull for postgres datasources)

**in the rendering tab**

A "Refresh layer on notification" checkbox has been added to refresh layer
on provider notification.

For a postgres datasource, if a `NOTIFY qgis;` command is issued by one of the database clients,
a refresh of the layer will occur.

If the "Only if message is" checkbox is checked, the notification will trigger the refresh only
if the message contend is the one specified, e.g. if the user enters
"refresh" in the box right next to the "Only if message is" checkbox,
then a `NOTIFY qgis, 'refresh';` command in the datatabase will trigger
a layer refresh, but `NOTIFY qgis;` or `NOTIFY qgis, 'something else';`
won't.

**in the actions tab**

A column "On notification" has been added, the action editor widget
a has text field "Execute if notification message matches" to
specify a filter for notification from the provider. The filter is a
Perl-type regex.

Note that, as opposed to the "layer refresh" that

Exemple:
  - QGIS side "Execute if notification message matches" `^trigger my action`
  - Postgres side: `NOTIFY qgis, 'trigger my action'` will trigger the action
  - Postgres side: `NOTIFY qgis, 'trigger my action some additional data'` will trigger the action
  - Postgres side: `NOTIFY qgis, 'do not trigger my action some additional data'` will NOT trigger the action

Please note that if the `^`, which means "starts with",  in `^trigger my action` had been ommited,
the last notification would have triggered the action because the
notification message contains the `trigger my action`

A new qgis variable `notification_message` is available for use in
actions, it holds the contend of the notification message. To continue
with the previous exemple, if the action is of python type with the code:

```python
print('[% @notification_message %]')
```

The three notifictions above will result in two printed lines
```
trigger my action
trigger my action some additional data
```

User Warning:

For postgres providers, if the "Refresh layer on notification" is checked, or if one layer action has
"On notification" specified, a new connection to the database is made to
listen to postgres notifications. This olds even if transaction groups
are enabled at the project level.

Note that once the notification mechanism is started in a QGIS
session, it will not stop, even if there is no more need for it (Refresh
layer on notification" unchecked and no "On notification" in any
action). Consequently the connection listening to notification will
remain open.

IMPLEMENTATION DETAILS:

A notify signal has been added to the abstract QgsVectorDataProvider
along with a setListening function that enables/disble the notification
mechanism.

For the moment only the postgres provider implements the notification.

QgsAction has a notificationMessage member function that holds the regex
to match to trigger action

QgsActionManager becomes a QObject and is doing the filtering and execute actions on
notifications.

The notification notion extends beyond SRGBD servers (postgres and oracle at
least have the notify) and the "watch file" in the delimitedtext
provider could also benefit from this interface.

For the postgres provider a thread is created with a second connection
to the database. This thread is responsible for listening postgres
notifications.

It would be nice to avoid the creation of one listening chanel per
provider in the case transaction groups are enabled.

Please note that when listening starts (a thread and connection is
created in the postgres provider) it cannot be stopped by removing the
connected actions or unchecking the refresh check box. Indeed, since we
don't know who needs the signals, we dont't want to stop the service.

The service will not restart in the next qgis session though.

If this behavior is not deemed appropriate, we could use
```
int QObject::receivers ( const char * signal ) const
```
and have QgsDataProvider::setListening return a bool to tell the caller
if the signal has actually been closed.
  • Loading branch information
vmora committed Sep 22, 2017
1 parent d6d7c6e commit 02e3916
Show file tree
Hide file tree
Showing 32 changed files with 879 additions and 450 deletions.
11 changes: 10 additions & 1 deletion python/core/qgsaction.sip
Expand Up @@ -44,7 +44,7 @@ class QgsAction
\param capture If this is set to true, the output will be captured when an action is run
%End

QgsAction( ActionType type, const QString &description, const QString &action, const QString &icon, bool capture, const QString &shortTitle = QString(), const QSet<QString> &actionScopes = QSet<QString>() );
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() );
%Docstring
Create a new QgsAction

Expand All @@ -55,6 +55,7 @@ class QgsAction
\param capture If this is set to true, the output will be captured when an action is run
\param shortTitle A short string used to label user interface elements like buttons
\param actionScopes A set of scopes in which this action will be available
\param notificationMessage A particular message which reception will trigger the action
%End

QString name() const;
Expand Down Expand Up @@ -103,6 +104,14 @@ The icon
How the content is interpreted depends on the type() and
the actionScope().

.. versionadded:: 3.0
:rtype: str
%End

QString notificationMessage() const;
%Docstring
Returns the notification message that triggers the action

.. versionadded:: 3.0
:rtype: str
%End
Expand Down
2 changes: 1 addition & 1 deletion python/core/qgsactionmanager.sip
Expand Up @@ -12,7 +12,7 @@



class QgsActionManager
class QgsActionManager: QObject
{
%Docstring
Storage and management of actions associated with a layer.
Expand Down
22 changes: 22 additions & 0 deletions python/core/qgsdataprovider.sip
Expand Up @@ -361,6 +361,18 @@ Current time stamp of data source
:rtype: QVariant
%End

virtual void setListening( bool isListening );
%Docstring
Set whether the provider will listen to datasource notifications
If set, the provider will issue notify signals.

The default implementation does nothing.

.. seealso:: notify

.. versionadded:: 3.0
%End

signals:

void fullExtentCalculated();
Expand All @@ -379,6 +391,16 @@ Current time stamp of data source
feature ids should be invalidated.
%End

void notify( const QString &msg ) const;
%Docstring
Emitted when datasource issues a notification

.. seealso:: setListening

.. versionadded:: 3.0
%End


protected:


Expand Down
7 changes: 7 additions & 0 deletions python/core/qgsexpressioncontext.sip
Expand Up @@ -940,6 +940,13 @@ class QgsExpressionContextUtils
:rtype: QgsExpressionContextScope
%End

static QgsExpressionContextScope *notificationScope( const QString &message = QString() ) /Factory/;
%Docstring
Creates a new scope which contains variables and functions relating to provider notifications
\param message the notification message
:rtype: QgsExpressionContextScope
%End

static void registerContextFunctions();
%Docstring
Registers all known core functions provided by QgsExpressionContextScope objects.
Expand Down
34 changes: 34 additions & 0 deletions python/core/qgsmaplayer.sip
Expand Up @@ -953,6 +953,39 @@ Time stamp of data source in the moment when data/metadata were loaded by provid
:rtype: bool
%End

void setRefreshOnNotifyEnabled( bool enabled );
%Docstring
Set whether provider notification is connected to triggerRepaint

.. versionadded:: 3.0
%End

void setRefreshOnNofifyMessage( const QString &message );
%Docstring
Set the notification message that triggers repaine
If refresh on notification is enabled, the notification will triggerRepaint only
if the notification message is equal to \param message

.. versionadded:: 3.0
%End

QString refreshOnNotifyMessage() const;
%Docstring
Returns the message that should be notified by the provider to triggerRepaint

.. versionadded:: 3.0
:rtype: str
%End

bool isRefreshOnNotifyEnabled() const;
%Docstring
Returns true if the refresh on provider nofification is enabled

.. versionadded:: 3.0
:rtype: bool
%End


signals:

void statusChanged( const QString &status );
Expand Down Expand Up @@ -1134,6 +1167,7 @@ Checks whether a new set of dependencies will introduce a cycle
:rtype: bool
%End


};


Expand Down
31 changes: 19 additions & 12 deletions src/app/qgsattributeactiondialog.cpp
Expand Up @@ -145,13 +145,16 @@ void QgsAttributeActionDialog::insertRow( int row, const QgsAction &action )
headerItem->setData( Qt::UserRole, action.iconPath() );
mAttributeActionTable->setVerticalHeaderItem( row, headerItem );

// Notification message
mAttributeActionTable->setItem( row, NotificationMessage, new QTableWidgetItem( action.notificationMessage() ) );

updateButtons();
}

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 )
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 )
{
if ( uniqueName( name ) == name )
insertRow( row, QgsAction( type, name, actionText, iconPath, capture, shortTitle, actionScopes ) );
insertRow( row, QgsAction( type, name, actionText, iconPath, capture, shortTitle, actionScopes, notificationMessage ) );
}

void QgsAttributeActionDialog::moveUp()
Expand Down Expand Up @@ -219,7 +222,9 @@ QgsAction QgsAttributeActionDialog::rowToAction( int row ) const
mAttributeActionTable->verticalHeaderItem( row )->data( Qt::UserRole ).toString(),
mAttributeActionTable->item( row, Capture )->checkState() == Qt::Checked,
mAttributeActionTable->item( row, ShortTitle )->text(),
mAttributeActionTable->item( row, ActionScopes )->data( Qt::UserRole ).value<QSet<QString>>() );
mAttributeActionTable->item( row, ActionScopes )->data( Qt::UserRole ).value<QSet<QString>>(),
mAttributeActionTable->item( row, NotificationMessage )->text()
);
return action;
}

Expand Down Expand Up @@ -273,7 +278,7 @@ void QgsAttributeActionDialog::insert()
{
QString name = uniqueName( dlg.description() );

insertRow( pos, dlg.type(), name, dlg.actionText(), dlg.iconPath(), dlg.capture(), dlg.shortTitle(), dlg.actionScopes() );
insertRow( pos, dlg.type(), name, dlg.actionText(), dlg.iconPath(), dlg.capture(), dlg.shortTitle(), dlg.actionScopes(), dlg.notificationMessage() );
}
}

Expand All @@ -300,14 +305,14 @@ void QgsAttributeActionDialog::updateButtons()
void QgsAttributeActionDialog::addDefaultActions()
{
int pos = 0;
insertRow( pos++, QgsAction::Generic, tr( "Echo attribute's value" ), QStringLiteral( "echo \"[% \"MY_FIELD\" %]\"" ), QLatin1String( "" ), true, tr( "Attribute Value" ), QSet<QString>() << QStringLiteral( "Field" ) );
insertRow( pos++, QgsAction::Generic, tr( "Run an application" ), QStringLiteral( "ogr2ogr -f \"ESRI Shapefile\" \"[% \"OUTPUT_PATH\" %]\" \"[% \"INPUT_FILE\" %]\"" ), QLatin1String( "" ), true, tr( "Run application" ), QSet<QString>() << QStringLiteral( "Feature" ) << QStringLiteral( "Canvas" ) );
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 %]\")" ), QLatin1String( "" ), false, tr( "Feature ID" ), QSet<QString>() << QStringLiteral( "Feature" ) << QStringLiteral( "Canvas" ) );
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\", \"[% @current_field %]\")" ), QLatin1String( "" ), false, tr( "Field Value" ), QSet<QString>() << QStringLiteral( "Field" ) );
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 %])\")" ), QLatin1String( "" ), false, tr( "Clicked Coordinate" ), QSet<QString>() << QStringLiteral( "Canvas" ) );
insertRow( pos++, QgsAction::OpenUrl, tr( "Open file" ), QStringLiteral( "[% \"PATH\" %]" ), QLatin1String( "" ), false, tr( "Open file" ), QSet<QString>() << QStringLiteral( "Feature" ) << QStringLiteral( "Canvas" ) );
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" ) );
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" ) );
insertRow( pos++, QgsAction::Generic, tr( "Echo attribute's value" ), QStringLiteral( "echo \"[% \"MY_FIELD\" %]\"" ), QLatin1String( "" ), true, tr( "Attribute Value" ), QSet<QString>() << QStringLiteral( "Field" ), QString() );
insertRow( pos++, QgsAction::Generic, tr( "Run an application" ), QStringLiteral( "ogr2ogr -f \"ESRI Shapefile\" \"[% \"OUTPUT_PATH\" %]\" \"[% \"INPUT_FILE\" %]\"" ), QLatin1String( "" ), true, tr( "Run application" ), QSet<QString>() << QStringLiteral( "Feature" ) << QStringLiteral( "Canvas" ), QString() );
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 %]\")" ), QLatin1String( "" ), false, tr( "Feature ID" ), QSet<QString>() << QStringLiteral( "Feature" ) << QStringLiteral( "Canvas" ), QString() );
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\", \"[% @current_field %]\")" ), QLatin1String( "" ), false, tr( "Field Value" ), QSet<QString>() << QStringLiteral( "Field" ), QString() );
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 %])\")" ), QLatin1String( "" ), false, tr( "Clicked Coordinate" ), QSet<QString>() << QStringLiteral( "Canvas" ), QString() );
insertRow( pos++, QgsAction::OpenUrl, tr( "Open file" ), QStringLiteral( "[% \"PATH\" %]" ), QLatin1String( "" ), false, tr( "Open file" ), QSet<QString>() << QStringLiteral( "Feature" ) << QStringLiteral( "Canvas" ), QString() );
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() );
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() );
}

void QgsAttributeActionDialog::itemDoubleClicked( QTableWidgetItem *item )
Expand All @@ -322,6 +327,7 @@ void QgsAttributeActionDialog::itemDoubleClicked( QTableWidgetItem *item )
mAttributeActionTable->item( row, ActionText )->text(),
mAttributeActionTable->item( row, Capture )->checkState() == Qt::Checked,
mAttributeActionTable->item( row, ActionScopes )->data( Qt::UserRole ).value<QSet<QString>>(),
mAttributeActionTable->item( row, NotificationMessage )->text(),
mLayer
);

Expand All @@ -335,6 +341,7 @@ void QgsAttributeActionDialog::itemDoubleClicked( QTableWidgetItem *item )
mAttributeActionTable->item( row, ShortTitle )->setText( actionProperties.shortTitle() );
mAttributeActionTable->item( row, ActionText )->setText( actionProperties.actionText() );
mAttributeActionTable->item( row, Capture )->setCheckState( actionProperties.capture() ? Qt::Checked : Qt::Unchecked );
mAttributeActionTable->item( row, NotificationMessage )->setText( actionProperties.notificationMessage() );

QTableWidgetItem *item = mAttributeActionTable->item( row, ActionScopes );
QStringList actionScopes = actionProperties.actionScopes().toList();
Expand Down
5 changes: 3 additions & 2 deletions src/app/qgsattributeactiondialog.h
Expand Up @@ -43,7 +43,8 @@ class APP_EXPORT QgsAttributeActionDialog: public QWidget, private Ui::QgsAttrib
ShortTitle,
ActionText,
Capture,
ActionScopes
ActionScopes,
NotificationMessage
};

public:
Expand All @@ -69,7 +70,7 @@ class APP_EXPORT QgsAttributeActionDialog: public QWidget, private Ui::QgsAttrib

private:
void insertRow( int row, const QgsAction &action );
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 );
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 );
void swapRows( int row1, int row2 );
QgsAction rowToAction( int row ) const;

Expand Down
11 changes: 10 additions & 1 deletion src/app/qgsattributeactionpropertiesdialog.cpp
Expand Up @@ -31,7 +31,7 @@
#include <QFileDialog>
#include <QImageWriter>

QgsAttributeActionPropertiesDialog::QgsAttributeActionPropertiesDialog( QgsAction::ActionType type, const QString &description, const QString &shortTitle, const QString &iconPath, const QString &actionText, bool capture, const QSet<QString> &actionScopes, QgsVectorLayer *layer, QWidget *parent )
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 )
: QDialog( parent )
, mLayer( layer )
{
Expand All @@ -44,6 +44,7 @@ QgsAttributeActionPropertiesDialog::QgsAttributeActionPropertiesDialog( QgsActio
mIconPreview->setPixmap( QPixmap( iconPath ) );
mActionText->setText( actionText );
mCaptureOutput->setChecked( capture );
mNotificationMessage->setText( notificationMessage );

init( actionScopes );
}
Expand Down Expand Up @@ -101,6 +102,12 @@ QSet<QString> QgsAttributeActionPropertiesDialog::actionScopes() const
return actionScopes;
}

QString QgsAttributeActionPropertiesDialog::notificationMessage() const
{
return mNotificationMessage->text();
}


bool QgsAttributeActionPropertiesDialog::capture() const
{
return mCaptureOutput->isChecked();
Expand All @@ -119,6 +126,8 @@ QgsExpressionContext QgsAttributeActionPropertiesDialog::createExpressionContext
}
}

context << QgsExpressionContextUtils::notificationScope();

return context;
}

Expand Down
4 changes: 3 additions & 1 deletion src/app/qgsattributeactionpropertiesdialog.h
Expand Up @@ -28,7 +28,7 @@ class QgsAttributeActionPropertiesDialog: public QDialog, private Ui::QgsAttribu
Q_OBJECT

public:
QgsAttributeActionPropertiesDialog( QgsAction::ActionType type, const QString &description, const QString &shortTitle, const QString &iconPath, const QString &actionText, bool capture, const QSet<QString> &actionScopes, QgsVectorLayer *layer, QWidget *parent = nullptr );
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 = nullptr );

QgsAttributeActionPropertiesDialog( QgsVectorLayer *layer, QWidget *parent = nullptr );

Expand All @@ -44,6 +44,8 @@ class QgsAttributeActionPropertiesDialog: public QDialog, private Ui::QgsAttribu

QSet<QString> actionScopes() const;

QString notificationMessage() const;

bool capture() const;

virtual QgsExpressionContext createExpressionContext() const override;
Expand Down
8 changes: 8 additions & 0 deletions src/app/qgsvectorlayerproperties.cpp
Expand Up @@ -454,6 +454,11 @@ void QgsVectorLayerProperties::syncToLayer()
mRefreshLayerIntervalSpinBox->setEnabled( mLayer->hasAutoRefreshEnabled() );
mRefreshLayerIntervalSpinBox->setValue( mLayer->autoRefreshInterval() / 1000.0 );

mRefreshLayerNotificationCheckBox->setChecked( mLayer->isRefreshOnNotifyEnabled() );
mNotificationMessageCheckBox->setChecked( !mLayer->refreshOnNotifyMessage().isEmpty() );
mNotifyMessagValueLineEdit->setText( mLayer->refreshOnNotifyMessage() );


// load appropriate symbology page (V1 or V2)
updateSymbologyPage();

Expand Down Expand Up @@ -632,6 +637,9 @@ void QgsVectorLayerProperties::apply()
mLayer->setAutoRefreshInterval( mRefreshLayerIntervalSpinBox->value() * 1000.0 );
mLayer->setAutoRefreshEnabled( mRefreshLayerCheckBox->isChecked() );

mLayer->setRefreshOnNotifyEnabled( mRefreshLayerNotificationCheckBox->isChecked() );
mLayer->setRefreshOnNofifyMessage( mNotificationMessageCheckBox->isChecked() ? mNotifyMessagValueLineEdit->text() : QString() );

mOldJoins = mLayer->vectorJoins();

//save variables
Expand Down
1 change: 1 addition & 0 deletions src/core/CMakeLists.txt
Expand Up @@ -562,6 +562,7 @@ ENDIF(NOT MSVC)

SET(QGIS_CORE_MOC_HDRS
qgsapplication.h
qgsactionmanager.h
qgsactionscoperegistry.h
qgsanimatedicon.h
qgsbrowsermodel.h
Expand Down
3 changes: 3 additions & 0 deletions src/core/expression/qgsexpression.cpp
Expand Up @@ -703,6 +703,9 @@ void QgsExpression::initVariableHelp()

//processing variables
sVariableHelpTexts.insert( QStringLiteral( "algorithm_id" ), QCoreApplication::translate( "algorithm_id", "Unique ID for algorithm." ) );

//provider notification
sVariableHelpTexts.insert( QStringLiteral( "notification_message" ), QCoreApplication::translate( "notification_message", "Contend of the notification message sent by the provider (available only for actions triggered by provider notifications)." ) );
}

QString QgsExpression::variableHelpText( const QString &variableName )
Expand Down
2 changes: 2 additions & 0 deletions src/core/qgsaction.cpp
Expand Up @@ -119,6 +119,7 @@ void QgsAction::readXml( const QDomNode &actionNode )
mIcon = actionElement.attributeNode( QStringLiteral( "icon" ) ).value();
mCaptureOutput = actionElement.attributeNode( QStringLiteral( "capture" ) ).value().toInt() != 0;
mShortTitle = actionElement.attributeNode( QStringLiteral( "shortTitle" ) ).value();
mNotificationMessage = actionElement.attributeNode( QStringLiteral( "notificationMessage" ) ).value();
mId = QUuid( actionElement.attributeNode( QStringLiteral( "id" ) ).value() );
if ( mId.isNull() )
mId = QUuid::createUuid();
Expand All @@ -133,6 +134,7 @@ void QgsAction::writeXml( QDomNode &actionsNode ) const
actionSetting.setAttribute( QStringLiteral( "icon" ), mIcon );
actionSetting.setAttribute( QStringLiteral( "action" ), mCommand );
actionSetting.setAttribute( QStringLiteral( "capture" ), mCaptureOutput );
actionSetting.setAttribute( QStringLiteral( "notificationMessage" ), mNotificationMessage );
actionSetting.setAttribute( QStringLiteral( "id" ), mId.toString() );

Q_FOREACH ( const QString &scope, mActionScopes )
Expand Down

0 comments on commit 02e3916

Please sign in to comment.