mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-15 00:04:00 -04:00
[FEATURE] layer refresh and trigger actions on provider notification
[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.
This commit is contained in:
parent
d6d7c6e499
commit
02e3916bf5
@ -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 ¬ificationMessage = QString() );
|
||||
%Docstring
|
||||
Create a new QgsAction
|
||||
|
||||
@ -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;
|
||||
@ -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
|
||||
|
@ -12,7 +12,7 @@
|
||||
|
||||
|
||||
|
||||
class QgsActionManager
|
||||
class QgsActionManager: QObject
|
||||
{
|
||||
%Docstring
|
||||
Storage and management of actions associated with a layer.
|
||||
|
@ -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();
|
||||
@ -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:
|
||||
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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 );
|
||||
@ -1134,6 +1167,7 @@ Checks whether a new set of dependencies will introduce a cycle
|
||||
:rtype: bool
|
||||
%End
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
@ -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 ¬ificationMessage )
|
||||
{
|
||||
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()
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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() );
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 )
|
||||
@ -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
|
||||
);
|
||||
|
||||
@ -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();
|
||||
|
@ -43,7 +43,8 @@ class APP_EXPORT QgsAttributeActionDialog: public QWidget, private Ui::QgsAttrib
|
||||
ShortTitle,
|
||||
ActionText,
|
||||
Capture,
|
||||
ActionScopes
|
||||
ActionScopes,
|
||||
NotificationMessage
|
||||
};
|
||||
|
||||
public:
|
||||
@ -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 ¬ificationMessage );
|
||||
void swapRows( int row1, int row2 );
|
||||
QgsAction rowToAction( int row ) const;
|
||||
|
||||
|
@ -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 ¬ificationMessage, QgsVectorLayer *layer, QWidget *parent )
|
||||
: QDialog( parent )
|
||||
, mLayer( layer )
|
||||
{
|
||||
@ -44,6 +44,7 @@ QgsAttributeActionPropertiesDialog::QgsAttributeActionPropertiesDialog( QgsActio
|
||||
mIconPreview->setPixmap( QPixmap( iconPath ) );
|
||||
mActionText->setText( actionText );
|
||||
mCaptureOutput->setChecked( capture );
|
||||
mNotificationMessage->setText( notificationMessage );
|
||||
|
||||
init( actionScopes );
|
||||
}
|
||||
@ -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();
|
||||
@ -119,6 +126,8 @@ QgsExpressionContext QgsAttributeActionPropertiesDialog::createExpressionContext
|
||||
}
|
||||
}
|
||||
|
||||
context << QgsExpressionContextUtils::notificationScope();
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
|
@ -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 ¬ificationMessage, QgsVectorLayer *layer, QWidget *parent = nullptr );
|
||||
|
||||
QgsAttributeActionPropertiesDialog( QgsVectorLayer *layer, QWidget *parent = nullptr );
|
||||
|
||||
@ -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;
|
||||
|
@ -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();
|
||||
|
||||
@ -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
|
||||
|
@ -562,6 +562,7 @@ ENDIF(NOT MSVC)
|
||||
|
||||
SET(QGIS_CORE_MOC_HDRS
|
||||
qgsapplication.h
|
||||
qgsactionmanager.h
|
||||
qgsactionscoperegistry.h
|
||||
qgsanimatedicon.h
|
||||
qgsbrowsermodel.h
|
||||
|
@ -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 )
|
||||
|
@ -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();
|
||||
@ -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 )
|
||||
|
@ -77,8 +77,9 @@ class CORE_EXPORT 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
|
||||
*/
|
||||
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 ¬ificationMessage = QString() )
|
||||
: mType( type )
|
||||
, mDescription( description )
|
||||
, mShortTitle( shortTitle )
|
||||
@ -86,6 +87,7 @@ class CORE_EXPORT QgsAction
|
||||
, mCommand( action )
|
||||
, mCaptureOutput( capture )
|
||||
, mActionScopes( actionScopes )
|
||||
, mNotificationMessage( notificationMessage )
|
||||
, mId( QUuid::createUuid() )
|
||||
{}
|
||||
|
||||
@ -124,6 +126,13 @@ class CORE_EXPORT QgsAction
|
||||
*/
|
||||
QString command() const { return mCommand; }
|
||||
|
||||
/**
|
||||
* Returns the notification message that triggers the action
|
||||
*
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
QString notificationMessage() const { return mNotificationMessage; }
|
||||
|
||||
//! The action type
|
||||
ActionType type() const { return mType; }
|
||||
|
||||
@ -190,6 +199,7 @@ class CORE_EXPORT QgsAction
|
||||
QString mCommand;
|
||||
bool mCaptureOutput = false;
|
||||
QSet<QString> mActionScopes;
|
||||
QString mNotificationMessage;
|
||||
mutable std::shared_ptr<QAction> mAction;
|
||||
QUuid mId;
|
||||
};
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "qgsproject.h"
|
||||
#include "qgslogger.h"
|
||||
#include "qgsexpression.h"
|
||||
#include "qgsdataprovider.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QStringList>
|
||||
@ -38,6 +39,7 @@
|
||||
#include <QUrl>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QRegularExpression>
|
||||
|
||||
|
||||
QUuid QgsActionManager::addAction( QgsAction::ActionType type, const QString &name, const QString &command, bool capture )
|
||||
@ -56,21 +58,69 @@ QUuid QgsActionManager::addAction( QgsAction::ActionType type, const QString &na
|
||||
|
||||
void QgsActionManager::addAction( const QgsAction &action )
|
||||
{
|
||||
QgsDebugMsg( "add action " + action.name() );
|
||||
mActions.append( action );
|
||||
if ( mLayer && mLayer->dataProvider() && !action.notificationMessage().isEmpty() )
|
||||
{
|
||||
mLayer->dataProvider()->setListening( true );
|
||||
if ( !mOnNotifyConnected )
|
||||
{
|
||||
QgsDebugMsg( "connecting to notify" );
|
||||
connect( mLayer->dataProvider(), &QgsDataProvider::notify, this, &QgsActionManager::onNotifyRunActions );
|
||||
mOnNotifyConnected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QgsActionManager::onNotifyRunActions( const QString &message )
|
||||
{
|
||||
for ( const QgsAction &act : qgsAsConst( mActions ) )
|
||||
{
|
||||
if ( !act.notificationMessage().isEmpty() && QRegularExpression( act.notificationMessage() ).match( message ).hasMatch() )
|
||||
{
|
||||
if ( !act.isValid() || !act.runable() )
|
||||
continue;
|
||||
|
||||
QgsExpressionContext context = createExpressionContext();
|
||||
|
||||
Q_ASSERT( mLayer ); // if there is no layer, then where is the notification coming from ?
|
||||
context << QgsExpressionContextUtils::layerScope( mLayer );
|
||||
context << QgsExpressionContextUtils::notificationScope( message );
|
||||
|
||||
QString expandedAction = QgsExpression::replaceExpressionText( act.command(), &context );
|
||||
if ( expandedAction.isEmpty() )
|
||||
continue;
|
||||
runAction( QgsAction( act.type(), act.name(), expandedAction, act.capture() ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QgsActionManager::removeAction( const QUuid &actionId )
|
||||
{
|
||||
int i = 0;
|
||||
Q_FOREACH ( const QgsAction &action, mActions )
|
||||
for ( const QgsAction &action : qgsAsConst( mActions ) )
|
||||
{
|
||||
if ( action.id() == actionId )
|
||||
{
|
||||
mActions.removeAt( i );
|
||||
return;
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
|
||||
if ( mOnNotifyConnected )
|
||||
{
|
||||
bool hasActionOnNotify = false;
|
||||
for ( const QgsAction &action : qgsAsConst( mActions ) )
|
||||
hasActionOnNotify |= !action.notificationMessage().isEmpty();
|
||||
if ( !hasActionOnNotify && mLayer && mLayer->dataProvider() )
|
||||
{
|
||||
// note that there is no way of knowing if the provider is listening only because
|
||||
// this class has hasked it to, so we do not reset the provider listening state here
|
||||
disconnect( mLayer->dataProvider(), &QgsDataProvider::notify, this, &QgsActionManager::onNotifyRunActions );
|
||||
mOnNotifyConnected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QgsActionManager::doAction( const QUuid &actionId, const QgsFeature &feature, int defaultValueIndex )
|
||||
@ -109,6 +159,13 @@ void QgsActionManager::doAction( const QUuid &actionId, const QgsFeature &feat,
|
||||
void QgsActionManager::clearActions()
|
||||
{
|
||||
mActions.clear();
|
||||
if ( mOnNotifyConnected && mLayer && mLayer->dataProvider() )
|
||||
{
|
||||
// note that there is no way of knowing if the provider is listening only because
|
||||
// this class has hasked it to, so we do not reset the provider listening state here
|
||||
disconnect( mLayer->dataProvider(), &QgsDataProvider::notify, this, &QgsActionManager::onNotifyRunActions );
|
||||
mOnNotifyConnected = false;
|
||||
}
|
||||
}
|
||||
|
||||
QList<QgsAction> QgsActionManager::actions( const QString &actionScope ) const
|
||||
@ -119,7 +176,7 @@ QList<QgsAction> QgsActionManager::actions( const QString &actionScope ) const
|
||||
{
|
||||
QList<QgsAction> actions;
|
||||
|
||||
Q_FOREACH ( const QgsAction &action, mActions )
|
||||
for ( const QgsAction &action : qgsAsConst( mActions ) )
|
||||
{
|
||||
if ( action.actionScopes().contains( actionScope ) )
|
||||
actions.append( action );
|
||||
@ -174,7 +231,7 @@ bool QgsActionManager::writeXml( QDomNode &layer_node ) const
|
||||
aActions.appendChild( defaultActionElement );
|
||||
}
|
||||
|
||||
Q_FOREACH ( const QgsAction &action, mActions )
|
||||
for ( const QgsAction &action : qgsAsConst( mActions ) )
|
||||
{
|
||||
action.writeXml( aActions );
|
||||
}
|
||||
@ -185,7 +242,7 @@ bool QgsActionManager::writeXml( QDomNode &layer_node ) const
|
||||
|
||||
bool QgsActionManager::readXml( const QDomNode &layer_node )
|
||||
{
|
||||
mActions.clear();
|
||||
clearActions();
|
||||
|
||||
QDomNode aaNode = layer_node.namedItem( QStringLiteral( "attributeactions" ) );
|
||||
|
||||
@ -196,7 +253,7 @@ bool QgsActionManager::readXml( const QDomNode &layer_node )
|
||||
{
|
||||
QgsAction action;
|
||||
action.readXml( actionsettings.item( i ) );
|
||||
mActions.append( action );
|
||||
addAction( action );
|
||||
}
|
||||
|
||||
QDomNodeList defaultActionNodes = aaNode.toElement().elementsByTagName( "defaultAction" );
|
||||
@ -212,7 +269,7 @@ bool QgsActionManager::readXml( const QDomNode &layer_node )
|
||||
|
||||
QgsAction QgsActionManager::action( const QUuid &id )
|
||||
{
|
||||
Q_FOREACH ( const QgsAction &action, mActions )
|
||||
for ( const QgsAction &action : qgsAsConst( mActions ) )
|
||||
{
|
||||
if ( action.id() == id )
|
||||
return action;
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "qgis_core.h"
|
||||
#include <QString>
|
||||
#include <QIcon>
|
||||
#include <QObject>
|
||||
|
||||
#include "qgsaction.h"
|
||||
#include "qgsfeature.h"
|
||||
@ -46,8 +47,10 @@ class QgsExpressionContext;
|
||||
* based on attributes of a given feature.
|
||||
*/
|
||||
|
||||
class CORE_EXPORT QgsActionManager
|
||||
class CORE_EXPORT QgsActionManager: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
//! Constructor
|
||||
QgsActionManager( QgsVectorLayer *layer )
|
||||
@ -148,7 +151,12 @@ class CORE_EXPORT QgsActionManager
|
||||
|
||||
QMap<QString, QUuid> mDefaultActions;
|
||||
|
||||
bool mOnNotifyConnected = false;
|
||||
|
||||
QgsExpressionContext createExpressionContext() const;
|
||||
|
||||
private slots:
|
||||
void onNotifyRunActions( const QString &message );
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -36,3 +36,8 @@ QVariant QgsDataProvider::providerProperty( int property, const QVariant &defaul
|
||||
return mProviderProperties.value( property, defaultValue );
|
||||
}
|
||||
|
||||
void QgsDataProvider::setListening( bool isListening )
|
||||
{
|
||||
Q_UNUSED( isListening );
|
||||
}
|
||||
|
||||
|
@ -429,6 +429,18 @@ class CORE_EXPORT QgsDataProvider : public QObject
|
||||
*/
|
||||
QVariant providerProperty( int property, const QVariant &defaultValue ) const; // SIP_SKIP
|
||||
|
||||
/**
|
||||
* Set whether the provider will listen to datasource notifications
|
||||
* If set, the provider will issue notify signals.
|
||||
*
|
||||
* The default implementation does nothing.
|
||||
*
|
||||
* \see notify
|
||||
*
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
virtual void setListening( bool isListening );
|
||||
|
||||
signals:
|
||||
|
||||
/**
|
||||
@ -447,6 +459,16 @@ class CORE_EXPORT QgsDataProvider : public QObject
|
||||
*/
|
||||
void dataChanged();
|
||||
|
||||
/**
|
||||
* Emitted when datasource issues a notification
|
||||
*
|
||||
* \see setListening
|
||||
*
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
void notify( const QString &msg ) const;
|
||||
|
||||
|
||||
protected:
|
||||
|
||||
/**
|
||||
|
@ -1230,6 +1230,13 @@ QgsExpressionContextScope *QgsExpressionContextUtils::processingAlgorithmScope(
|
||||
return scope.release();
|
||||
}
|
||||
|
||||
QgsExpressionContextScope *QgsExpressionContextUtils::notificationScope( const QString &message )
|
||||
{
|
||||
std::unique_ptr< QgsExpressionContextScope > scope( new QgsExpressionContextScope() );
|
||||
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "notification_message" ), message, true ) );
|
||||
return scope.release();
|
||||
}
|
||||
|
||||
void QgsExpressionContextUtils::registerContextFunctions()
|
||||
{
|
||||
QgsExpression::registerFunction( new GetNamedProjectColor( nullptr ) );
|
||||
|
@ -861,6 +861,12 @@ class CORE_EXPORT QgsExpressionContextUtils
|
||||
*/
|
||||
static QgsExpressionContextScope *processingAlgorithmScope( const QgsProcessingAlgorithm *algorithm, const QVariantMap ¶meters, QgsProcessingContext &context ) SIP_FACTORY;
|
||||
|
||||
/**
|
||||
* Creates a new scope which contains variables and functions relating to provider notifications
|
||||
* \param message the notification message
|
||||
*/
|
||||
static QgsExpressionContextScope *notificationScope( const QString &message = QString() ) SIP_FACTORY;
|
||||
|
||||
/** Registers all known core functions provided by QgsExpressionContextScope objects.
|
||||
*/
|
||||
static void registerContextFunctions();
|
||||
|
@ -472,6 +472,9 @@ bool QgsMapLayer::readLayerXml( const QDomElement &layerElement, const QgsReadWr
|
||||
|
||||
setAutoRefreshInterval( layerElement.attribute( QStringLiteral( "autoRefreshTime" ), 0 ).toInt() );
|
||||
setAutoRefreshEnabled( layerElement.attribute( QStringLiteral( "autoRefreshEnabled" ), QStringLiteral( "0" ) ).toInt() );
|
||||
setRefreshOnNofifyMessage( layerElement.attribute( QStringLiteral( "refreshOnNotifyMessage" ), QString() ) );
|
||||
setRefreshOnNotifyEnabled( layerElement.attribute( QStringLiteral( "refreshOnNotifyEnabled" ), QStringLiteral( "0" ) ).toInt() );
|
||||
|
||||
|
||||
// set name
|
||||
mnl = layerElement.namedItem( QStringLiteral( "layername" ) );
|
||||
@ -577,6 +580,9 @@ bool QgsMapLayer::writeLayerXml( QDomElement &layerElement, QDomDocument &docume
|
||||
|
||||
layerElement.setAttribute( QStringLiteral( "autoRefreshTime" ), QString::number( mRefreshTimer.interval() ) );
|
||||
layerElement.setAttribute( QStringLiteral( "autoRefreshEnabled" ), mRefreshTimer.isActive() ? 1 : 0 );
|
||||
layerElement.setAttribute( QStringLiteral( "refreshOnNotifyEnabled" ), mIsRefreshOnNofifyEnabled ? 1 : 0 );
|
||||
layerElement.setAttribute( QStringLiteral( "refreshOnNotifyMessage" ), mRefreshOnNofifyMessage );
|
||||
|
||||
|
||||
// ID
|
||||
QDomElement layerId = document.createElement( QStringLiteral( "id" ) );
|
||||
@ -1775,3 +1781,28 @@ bool QgsMapLayer::setDependencies( const QSet<QgsMapLayerDependency> &oDeps )
|
||||
emit dependenciesChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
void QgsMapLayer::setRefreshOnNotifyEnabled( bool enabled )
|
||||
{
|
||||
if ( !dataProvider() )
|
||||
return;
|
||||
|
||||
if ( enabled && !isRefreshOnNotifyEnabled() )
|
||||
{
|
||||
dataProvider()->setListening( enabled );
|
||||
connect( dataProvider(), &QgsVectorDataProvider::notify, this, &QgsMapLayer::onNotifiedTriggerRepaint );
|
||||
}
|
||||
else if ( !enabled && isRefreshOnNotifyEnabled() )
|
||||
{
|
||||
// we don't want to disable provider listening because someone else could need it (e.g. actions)
|
||||
disconnect( dataProvider(), &QgsVectorDataProvider::notify, this, &QgsMapLayer::onNotifiedTriggerRepaint );
|
||||
}
|
||||
mIsRefreshOnNofifyEnabled = enabled;
|
||||
}
|
||||
|
||||
void QgsMapLayer::onNotifiedTriggerRepaint( const QString &message )
|
||||
{
|
||||
if ( refreshOnNotifyMessage().isEmpty() || refreshOnNotifyMessage() == message )
|
||||
triggerRepaint();
|
||||
}
|
||||
|
||||
|
@ -847,6 +847,37 @@ class CORE_EXPORT QgsMapLayer : public QObject
|
||||
*/
|
||||
virtual bool setDependencies( const QSet<QgsMapLayerDependency> &layers );
|
||||
|
||||
/**
|
||||
* Set whether provider notification is connected to triggerRepaint
|
||||
*
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
void setRefreshOnNotifyEnabled( bool enabled );
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
void setRefreshOnNofifyMessage( const QString &message ) { mRefreshOnNofifyMessage = message; }
|
||||
|
||||
/**
|
||||
* Returns the message that should be notified by the provider to triggerRepaint
|
||||
*
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
QString refreshOnNotifyMessage() const { return mRefreshOnNofifyMessage; }
|
||||
|
||||
/**
|
||||
* Returns true if the refresh on provider nofification is enabled
|
||||
*
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
bool isRefreshOnNotifyEnabled() const { return mIsRefreshOnNofifyEnabled; }
|
||||
|
||||
|
||||
signals:
|
||||
|
||||
//! Emit a signal with status (e.g. to be caught by QgisApp and display a msg on status bar)
|
||||
@ -931,6 +962,10 @@ class CORE_EXPORT QgsMapLayer : public QObject
|
||||
*/
|
||||
void metadataChanged();
|
||||
|
||||
private slots:
|
||||
|
||||
void onNotifiedTriggerRepaint( const QString &message );
|
||||
|
||||
protected:
|
||||
|
||||
/** Copies attributes like name, short name, ... into another layer.
|
||||
@ -1030,6 +1065,9 @@ class CORE_EXPORT QgsMapLayer : public QObject
|
||||
//! Checks whether a new set of dependencies will introduce a cycle
|
||||
bool hasDependencyCycle( const QSet<QgsMapLayerDependency> &layers ) const;
|
||||
|
||||
bool mIsRefreshOnNofifyEnabled = false;
|
||||
QString mRefreshOnNofifyMessage;
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
|
@ -4434,3 +4434,4 @@ bool QgsVectorLayer::readExtentFromXml() const
|
||||
{
|
||||
return mReadExtentFromXml;
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@ SET(PG_SRCS
|
||||
qgspgtablemodel.cpp
|
||||
qgscolumntypethread.cpp
|
||||
qgspostgresexpressioncompiler.cpp
|
||||
qgspostgreslistener.cpp
|
||||
)
|
||||
|
||||
SET(PG_MOC_HDRS
|
||||
@ -21,7 +22,7 @@ SET(PG_MOC_HDRS
|
||||
qgspostgresdataitems.h
|
||||
qgspostgresprovider.h
|
||||
qgspostgrestransaction.h
|
||||
|
||||
qgspostgreslistener.h
|
||||
)
|
||||
|
||||
IF (WITH_GUI)
|
||||
|
127
src/providers/postgres/qgspostgreslistener.cpp
Normal file
127
src/providers/postgres/qgspostgreslistener.cpp
Normal file
@ -0,0 +1,127 @@
|
||||
/***************************************************************************
|
||||
qgspostgreslistener.cpp - Listen to postgres NOTIFY
|
||||
-------------------
|
||||
begin : Sept 11, 2017
|
||||
copyright : (C) 2017 by Vincent Mora
|
||||
email : vincent dor mora at oslandia dot com
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "qgspostgreslistener.h"
|
||||
|
||||
#include "qgslogger.h"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <winsock.h>
|
||||
#else
|
||||
#include <sys/select.h>
|
||||
#endif
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include <libpq-fe.h>
|
||||
}
|
||||
|
||||
std::unique_ptr< QgsPostgresListener > QgsPostgresListener::create( const QString &connString )
|
||||
{
|
||||
std::unique_ptr< QgsPostgresListener > res( new QgsPostgresListener( connString ) );
|
||||
QgsDebugMsg( "starting notification listener" );
|
||||
res->start();
|
||||
res->mMutex.lock();
|
||||
res->mIsReadyCondition.wait( &res->mMutex );
|
||||
res->mMutex.unlock();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
QgsPostgresListener::QgsPostgresListener( const QString &connString )
|
||||
: mConnString( connString )
|
||||
{
|
||||
}
|
||||
|
||||
QgsPostgresListener::~QgsPostgresListener()
|
||||
{
|
||||
mStop = true;
|
||||
QgsDebugMsg( "stopping the loop" );
|
||||
wait();
|
||||
QgsDebugMsg( "notification listener stopped" );
|
||||
}
|
||||
|
||||
void QgsPostgresListener::run()
|
||||
{
|
||||
PGconn *conn;
|
||||
conn = PQconnectdb( mConnString.toLocal8Bit() );
|
||||
|
||||
PGresult *res = PQexec( conn, "LISTEN qgis" );
|
||||
if ( PQresultStatus( res ) != PGRES_COMMAND_OK )
|
||||
{
|
||||
QgsDebugMsg( "error in listen" );
|
||||
PQclear( res );
|
||||
PQfinish( conn );
|
||||
mMutex.lock();
|
||||
mIsReadyCondition.wakeOne();
|
||||
mMutex.unlock();
|
||||
return;
|
||||
}
|
||||
PQclear( res );
|
||||
mMutex.lock();
|
||||
mIsReadyCondition.wakeOne();
|
||||
mMutex.unlock();
|
||||
|
||||
const int sock = PQsocket( conn );
|
||||
if ( sock < 0 )
|
||||
{
|
||||
QgsDebugMsg( "error in socket" );
|
||||
PQfinish( conn );
|
||||
return;
|
||||
}
|
||||
|
||||
forever
|
||||
{
|
||||
fd_set input_mask;
|
||||
FD_ZERO( &input_mask );
|
||||
FD_SET( sock, &input_mask );
|
||||
|
||||
timeval timeout;
|
||||
timeout.tv_sec = 1;
|
||||
timeout.tv_usec = 0;
|
||||
|
||||
QgsDebugMsg( "select in the loop" );
|
||||
if ( select( sock + 1, &input_mask, nullptr, nullptr, &timeout ) < 0 )
|
||||
{
|
||||
QgsDebugMsg( "error in select" );
|
||||
break;
|
||||
}
|
||||
|
||||
PQconsumeInput( conn );
|
||||
PGnotify *n = PQnotifies( conn );
|
||||
if ( n )
|
||||
{
|
||||
const QString msg( n->extra );
|
||||
emit notify( msg );
|
||||
QgsDebugMsg( "notify " + msg );
|
||||
PQfreemem( n );
|
||||
}
|
||||
else
|
||||
{
|
||||
QgsDebugMsg( "not a notify" );
|
||||
}
|
||||
|
||||
if ( mStop )
|
||||
{
|
||||
QgsDebugMsg( "stop from main thread" );
|
||||
break;
|
||||
}
|
||||
}
|
||||
PQfinish( conn );
|
||||
}
|
||||
|
||||
|
65
src/providers/postgres/qgspostgreslistener.h
Normal file
65
src/providers/postgres/qgspostgreslistener.h
Normal file
@ -0,0 +1,65 @@
|
||||
/***************************************************************************
|
||||
qgspostgreslistener.h - Listen to postgres NOTIFY
|
||||
-------------------
|
||||
begin : Sept 11, 2017
|
||||
copyright : (C) 2017 by Vincent Mora
|
||||
email : vincent dor mora at oslandia dot com
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef QGSPOSTGRESLISTENER_H
|
||||
#define QGSPOSTGRESLISTENER_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QThread>
|
||||
#include <QWaitCondition>
|
||||
#include <QMutex>
|
||||
|
||||
/**
|
||||
* \class QgsPostgresListener
|
||||
* \brief Launch a thread to listen on postgres notifications on the "qgis" channel, the notify signal is emitted on postgres notify.
|
||||
*
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
|
||||
class QgsPostgresListener : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* create an instance if possible and starts the associated thread
|
||||
* /returns nullptr on error
|
||||
*/
|
||||
static std::unique_ptr< QgsPostgresListener > create( const QString &connString );
|
||||
|
||||
~QgsPostgresListener();
|
||||
|
||||
void run() override;
|
||||
|
||||
signals:
|
||||
void notify( QString message );
|
||||
|
||||
private:
|
||||
volatile bool mStop = false;
|
||||
const QString mConnString;
|
||||
QWaitCondition mIsReadyCondition;
|
||||
QMutex mMutex;
|
||||
|
||||
QgsPostgresListener( const QString &connString );
|
||||
|
||||
Q_DISABLE_COPY( QgsPostgresListener )
|
||||
|
||||
};
|
||||
|
||||
#endif // QGSPOSTGRESLISTENER_H
|
@ -35,6 +35,7 @@
|
||||
#include "qgspostgresdataitems.h"
|
||||
#include "qgspostgresfeatureiterator.h"
|
||||
#include "qgspostgrestransaction.h"
|
||||
#include "qgspostgreslistener.h"
|
||||
#include "qgslogger.h"
|
||||
#include "qgsfeedback.h"
|
||||
#include "qgssettings.h"
|
||||
@ -309,6 +310,20 @@ QgsPostgresConn *QgsPostgresProvider::connectionRO() const
|
||||
return mTransaction ? mTransaction->connection() : mConnectionRO;
|
||||
}
|
||||
|
||||
void QgsPostgresProvider::setListening( bool isListening )
|
||||
{
|
||||
if ( isListening && !mListener )
|
||||
{
|
||||
mListener.reset( QgsPostgresListener::create( mUri.connectionInfo( false ) ).release() );
|
||||
connect( mListener.get(), &QgsPostgresListener::notify, this, &QgsPostgresProvider::notify );
|
||||
}
|
||||
else if ( !isListening && mListener )
|
||||
{
|
||||
disconnect( mListener.get(), &QgsPostgresListener::notify, this, &QgsPostgresProvider::notify );
|
||||
mListener.reset();
|
||||
}
|
||||
}
|
||||
|
||||
QgsPostgresConn *QgsPostgresProvider::connectionRW()
|
||||
{
|
||||
if ( mTransaction )
|
||||
|
@ -32,6 +32,7 @@ class QgsGeometry;
|
||||
class QgsPostgresFeatureIterator;
|
||||
class QgsPostgresSharedData;
|
||||
class QgsPostgresTransaction;
|
||||
class QgsPostgresListener;
|
||||
|
||||
#include "qgsdatasourceuri.h"
|
||||
|
||||
@ -208,6 +209,14 @@ class QgsPostgresProvider : public QgsVectorDataProvider
|
||||
*/
|
||||
virtual bool hasMetadata() const override;
|
||||
|
||||
/**
|
||||
* Launch a listening thead to listen to postgres NOTIFY on "qgis" channel
|
||||
* the notification is transformed into a Qt signal.
|
||||
*
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
void setListening( bool isListening ) override;
|
||||
|
||||
signals:
|
||||
|
||||
/**
|
||||
@ -434,6 +443,8 @@ class QgsPostgresProvider : public QgsVectorDataProvider
|
||||
QHash<int, QString> mDefaultValues;
|
||||
|
||||
bool mCheckPrimaryKeyUnicity = true;
|
||||
|
||||
std::unique_ptr< QgsPostgresListener > mListener;
|
||||
};
|
||||
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>653</width>
|
||||
<width>948</width>
|
||||
<height>731</height>
|
||||
</rect>
|
||||
</property>
|
||||
@ -44,7 +44,7 @@
|
||||
<property name="title">
|
||||
<string>Action list</string>
|
||||
</property>
|
||||
<property name="syncGroup" stdset="0">
|
||||
<property name="syncGroup">
|
||||
<string notr="true">actiongroup</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
@ -146,7 +146,7 @@
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<property name="columnCount">
|
||||
<number>6</number>
|
||||
<number>7</number>
|
||||
</property>
|
||||
<column>
|
||||
<property name="text">
|
||||
@ -178,6 +178,14 @@
|
||||
<string>Action Scopes</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>On Notification</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>If not empty, this will enable providr notification listening and the action will be executed when hte notification message matched the specified value. </p></body></html></string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
|
@ -27,100 +27,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1" colspan="3">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QLabel" name="mIconPreview">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>24</horstretch>
|
||||
<verstretch>24</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>24</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="mActionIcon"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="mChooseIconButton">
|
||||
<property name="text">
|
||||
<string>…</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../images/images.qrc">
|
||||
<normaloff>:/images/themes/default/mActionFileOpen.svg</normaloff>:/images/themes/default/mActionFileOpen.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="6" column="0" colspan="4">
|
||||
<widget class="QDialogButtonBox" name="mButtonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Type</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>mActionType</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="mActionType">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Generic</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Python</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Mac</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Windows</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Unix</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Open</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="4">
|
||||
<widget class="QGroupBox" name="mActionGroupBox">
|
||||
<property name="sizePolicy">
|
||||
@ -136,7 +42,7 @@
|
||||
<item row="1" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QgsFieldExpressionWidget" name="mFieldExpression" native="true">
|
||||
<widget class="QgsFieldExpressionWidget" name="mFieldExpression">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::TabFocus</enum>
|
||||
</property>
|
||||
@ -204,9 +110,156 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_11">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Execute if notification matches</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="mNotificationMessage">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>If specified, listen to data source notification and performs action if notification message matches the specified value.</p><p>E.g. to match messag beginning with <span style=" font-weight:600;">wathever </span>use <span style=" font-weight:600;">^whatever</span></p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0" colspan="4">
|
||||
<widget class="QDialogButtonBox" name="mButtonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Icon</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1" colspan="3">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QLabel" name="mIconPreview">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>24</horstretch>
|
||||
<verstretch>24</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>24</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="mActionIcon"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="mChooseIconButton">
|
||||
<property name="text">
|
||||
<string>…</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../images/images.qrc">
|
||||
<normaloff>:/images/themes/default/mActionFileOpen.svg</normaloff>:/images/themes/default/mActionFileOpen.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Type</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>mActionType</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="mActionType">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Generic</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Python</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Mac</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Windows</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Unix</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Open</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="3">
|
||||
<widget class="QGroupBox" name="mActionScopesGroupBox">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Action Scopes</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" colspan="3">
|
||||
<widget class="QLineEdit" name="mActionName">
|
||||
<property name="toolTip">
|
||||
<string>Enter the action name here</string>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string>Enter the name of an action here. The name should be unique (QGIS will make it unique if necessary).</string>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Mandatory description</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
@ -234,37 +287,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Icon</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" colspan="3">
|
||||
<widget class="QLineEdit" name="mActionName">
|
||||
<property name="toolTip">
|
||||
<string>Enter the action name here</string>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string>Enter the name of an action here. The name should be unique (QGIS will make it unique if necessary).</string>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Mandatory description</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="3">
|
||||
<widget class="QGroupBox" name="mActionScopesGroupBox">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Action Scopes</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout"/>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
|
@ -46,16 +46,7 @@
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -282,16 +273,7 @@
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -303,20 +285,11 @@
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>13</number>
|
||||
<number>5</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="mOptsPage_Information">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -328,16 +301,7 @@
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -354,16 +318,7 @@
|
||||
</widget>
|
||||
<widget class="QWidget" name="mOptsPage_Source">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -379,21 +334,12 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>338</width>
|
||||
<height>401</height>
|
||||
<width>651</width>
|
||||
<height>537</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_9">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -448,16 +394,7 @@ border-radius: 2px;</string>
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -506,7 +443,7 @@ border-radius: 2px;</string>
|
||||
<property name="checkable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="syncGroup" stdset="0">
|
||||
<property name="syncGroup">
|
||||
<string notr="true">vectorgeneral</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_28">
|
||||
@ -521,7 +458,7 @@ border-radius: 2px;</string>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QgsProjectionSelectionWidget" name="mCrsSelector" native="true">
|
||||
<widget class="QgsProjectionSelectionWidget" name="mCrsSelector">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
@ -629,16 +566,7 @@ border-radius: 2px;</string>
|
||||
</widget>
|
||||
<widget class="QWidget" name="mOptsPage_Style">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_11">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -654,21 +582,12 @@ border-radius: 2px;</string>
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>100</width>
|
||||
<height>30</height>
|
||||
<width>651</width>
|
||||
<height>537</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_18">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -698,16 +617,7 @@ border-radius: 2px;</string>
|
||||
</widget>
|
||||
<widget class="QWidget" name="mOptsPage_Labels">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_12">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -730,16 +640,7 @@ border-radius: 2px;</string>
|
||||
</widget>
|
||||
<widget class="QWidget" name="mOptsPage_Fields">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_15">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -755,21 +656,12 @@ border-radius: 2px;</string>
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>100</width>
|
||||
<height>30</height>
|
||||
<width>651</width>
|
||||
<height>537</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_20">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -790,16 +682,7 @@ border-radius: 2px;</string>
|
||||
</widget>
|
||||
<widget class="QWidget" name="mOptsPage_Rendering">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_19">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -815,21 +698,12 @@ border-radius: 2px;</string>
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>673</width>
|
||||
<height>317</height>
|
||||
<width>651</width>
|
||||
<height>537</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_32">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -842,7 +716,7 @@ border-radius: 2px;</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_6">
|
||||
<item row="0" column="0">
|
||||
<widget class="QgsScaleRangeWidget" name="mScaleRangeWidget" native="true">
|
||||
<widget class="QgsScaleRangeWidget" name="mScaleRangeWidget">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
@ -1021,6 +895,70 @@ border-radius: 2px;</string>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_14">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="mRefreshLayerNotificationCheckBox">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Some data providers can notify QGIS (e.g. PostgreSQL) with a message. If this is the case for this layer's data provider, notification will refresh the layer. </p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Refresh layer on notification</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="mNotificationFrame">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="mNotificationMessageCheckBox">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Check if only a specific message must refresh the layer (i.e. not all data source notifications)</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Only if message is</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="mNotifyMessagValueLineEdit">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Notification message that will refresh the layer.</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
@ -1042,16 +980,7 @@ border-radius: 2px;</string>
|
||||
</widget>
|
||||
<widget class="QWidget" name="mOptsPage_Display">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_25">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -1060,16 +989,7 @@ border-radius: 2px;</string>
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_10">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
@ -1080,7 +1000,7 @@ border-radius: 2px;</string>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QgsFieldExpressionWidget" name="mDisplayExpressionWidget" native="true">
|
||||
<widget class="QgsFieldExpressionWidget" name="mDisplayExpressionWidget">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
@ -1131,7 +1051,7 @@ border-radius: 2px;</string>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QgsFieldExpressionWidget" name="mMapTipExpressionFieldWidget" native="true">
|
||||
<widget class="QgsFieldExpressionWidget" name="mMapTipExpressionFieldWidget">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
@ -1150,16 +1070,7 @@ border-radius: 2px;</string>
|
||||
</widget>
|
||||
<widget class="QWidget" name="mOptsPage_Actions">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_16">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -1180,16 +1091,7 @@ border-radius: 2px;</string>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_21">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -1216,16 +1118,7 @@ border-radius: 2px;</string>
|
||||
</widget>
|
||||
<widget class="QWidget" name="mOptsPage_Joins">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_17">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -1241,21 +1134,12 @@ border-radius: 2px;</string>
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>199</width>
|
||||
<height>123</height>
|
||||
<width>128</width>
|
||||
<height>116</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_23">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -1339,16 +1223,7 @@ border-radius: 2px;</string>
|
||||
</widget>
|
||||
<widget class="QWidget" name="mOptsPage_Diagrams">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_10">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -1371,16 +1246,7 @@ border-radius: 2px;</string>
|
||||
</widget>
|
||||
<widget class="QWidget" name="mOptsPage_Variables">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -1403,16 +1269,7 @@ border-radius: 2px;</string>
|
||||
</widget>
|
||||
<widget class="QWidget" name="mOptsPage_Legend">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_8">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -1431,16 +1288,7 @@ border-radius: 2px;</string>
|
||||
</widget>
|
||||
<widget class="QWidget" name="mOptsPage_DataDependencies">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_29">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -1496,16 +1344,7 @@ border-radius: 2px;</string>
|
||||
</widget>
|
||||
<widget class="QWidget" name="mOptsPage_Server">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_14">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -1521,21 +1360,12 @@ border-radius: 2px;</string>
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>629</width>
|
||||
<height>527</height>
|
||||
<width>651</width>
|
||||
<height>537</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_13">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -1543,7 +1373,7 @@ border-radius: 2px;</string>
|
||||
<property name="title">
|
||||
<string>Description</string>
|
||||
</property>
|
||||
<property name="syncGroup" stdset="0">
|
||||
<property name="syncGroup">
|
||||
<string notr="true">vectormeta</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
@ -1685,7 +1515,7 @@ border-radius: 2px;</string>
|
||||
<property name="title">
|
||||
<string>Attribution</string>
|
||||
</property>
|
||||
<property name="syncGroup" stdset="0">
|
||||
<property name="syncGroup">
|
||||
<string notr="true">vectormeta</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_7">
|
||||
@ -1731,7 +1561,7 @@ border-radius: 2px;</string>
|
||||
<property name="title">
|
||||
<string>MetadataUrl</string>
|
||||
</property>
|
||||
<property name="syncGroup" stdset="0">
|
||||
<property name="syncGroup">
|
||||
<string notr="true">vectormeta</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_9">
|
||||
@ -1929,16 +1759,7 @@ border-radius: 2px;</string>
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_btnbox">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="2" column="1" colspan="4">
|
||||
@ -1964,9 +1785,9 @@ border-radius: 2px;</string>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>QgsProjectionSelectionWidget</class>
|
||||
<class>QgsFieldExpressionWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>qgsprojectionselectionwidget.h</header>
|
||||
<header>qgsfieldexpressionwidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
@ -1974,6 +1795,12 @@ border-radius: 2px;</string>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>qgsfilterlineedit.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>QgsProjectionSelectionWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>qgsprojectionselectionwidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>QgsScaleRangeWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
@ -1991,12 +1818,6 @@ border-radius: 2px;</string>
|
||||
<header>qgslayertreeembeddedconfigwidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>QgsFieldExpressionWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>qgsfieldexpressionwidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>QgsLayerTreeView</class>
|
||||
<extends>QTreeView</extends>
|
||||
@ -2090,5 +1911,37 @@ border-radius: 2px;</string>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>mRefreshLayerNotificationCheckBox</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>mNotificationFrame</receiver>
|
||||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>260</x>
|
||||
<y>363</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>587</x>
|
||||
<y>364</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>mNotificationMessageCheckBox</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>mNotifyMessagValueLineEdit</receiver>
|
||||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>426</x>
|
||||
<y>363</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>652</x>
|
||||
<y>364</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
|
@ -17,6 +17,7 @@ import qgis # NOQA
|
||||
import psycopg2
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
from qgis.core import (
|
||||
QgsVectorLayer,
|
||||
@ -33,13 +34,13 @@ from qgis.core import (
|
||||
QgsRectangle
|
||||
)
|
||||
from qgis.gui import QgsGui
|
||||
from qgis.PyQt.QtCore import QDate, QTime, QDateTime, QVariant, QDir
|
||||
from qgis.PyQt.QtCore import QDate, QTime, QDateTime, QVariant, QDir, QObject
|
||||
from qgis.testing import start_app, unittest
|
||||
from qgis.PyQt.QtXml import QDomDocument
|
||||
from utilities import unitTestDataPath
|
||||
from providertestbase import ProviderTestCase
|
||||
|
||||
start_app()
|
||||
QGISAPP = start_app()
|
||||
TEST_DATA_DIR = unitTestDataPath()
|
||||
|
||||
|
||||
@ -809,6 +810,42 @@ class TestPyQgsPostgresProvider(unittest.TestCase, ProviderTestCase):
|
||||
|
||||
self.assertEqual(vl2.extent(), originalExtent)
|
||||
|
||||
def testNotify(self):
|
||||
vl0 = QgsVectorLayer(self.dbconn + ' sslmode=disable key=\'pk\' srid=4326 type=POLYGON table="qgis_test"."some_poly_data" (geom) sql=', 'test', 'postgres')
|
||||
vl0.dataProvider().setListening(True)
|
||||
|
||||
class Notified(QObject):
|
||||
|
||||
def __init__(self):
|
||||
super(Notified, self).__init__()
|
||||
self.received = ""
|
||||
|
||||
def receive(self, msg):
|
||||
self.received = msg
|
||||
|
||||
notified = Notified()
|
||||
vl0.dataProvider().notify.connect(notified.receive)
|
||||
|
||||
vl0.dataProvider().setListening(True)
|
||||
|
||||
cur = self.con.cursor()
|
||||
ok = False
|
||||
start = time.time()
|
||||
while True:
|
||||
cur.execute("NOTIFY qgis, 'my message'")
|
||||
self.con.commit()
|
||||
QGISAPP.processEvents()
|
||||
if notified.received == "my message":
|
||||
ok = True
|
||||
break
|
||||
if (time.time() - start) > 5: # timeout
|
||||
break
|
||||
|
||||
vl0.dataProvider().notify.disconnect(notified.receive)
|
||||
vl0.dataProvider().setListening(False)
|
||||
|
||||
self.assertTrue(ok)
|
||||
|
||||
|
||||
class TestPyQgsPostgresProviderCompoundKey(unittest.TestCase, ProviderTestCase):
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user