Skip to content

Commit

Permalink
Merge pull request #5179 from vmora/listen_notify
Browse files Browse the repository at this point in the history
[FEATURE] layer refresh and trigger actions on provider notification
  • Loading branch information
m-kuhn committed Sep 22, 2017
2 parents 782ed47 + 02e3916 commit 6dfe44f
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 6dfe44f

Please sign in to comment.