Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgsactionmanager.cpp
3 : :
4 : : A class that stores and controls the management and execution of actions
5 : : associated. Actions are defined to be external programs that are run
6 : : with user-specified inputs that can depend on the value of layer
7 : : attributes.
8 : :
9 : : -------------------
10 : : begin : Oct 24 2004
11 : : copyright : (C) 2004 by Gavin Macaulay
12 : : email : gavin at macaulay dot co dot nz
13 : :
14 : : ***************************************************************************/
15 : :
16 : : /***************************************************************************
17 : : * *
18 : : * This program is free software; you can redistribute it and/or modify *
19 : : * it under the terms of the GNU General Public License as published by *
20 : : * the Free Software Foundation; either version 2 of the License, or *
21 : : * (at your option) any later version. *
22 : : * *
23 : : ***************************************************************************/
24 : :
25 : : #include "qgsactionmanager.h"
26 : : #include "qgspythonrunner.h"
27 : : #include "qgsrunprocess.h"
28 : : #include "qgsvectorlayer.h"
29 : : #include "qgsproject.h"
30 : : #include "qgslogger.h"
31 : : #include "qgsexpression.h"
32 : : #include "qgsdataprovider.h"
33 : : #include "qgsexpressioncontextutils.h"
34 : :
35 : : #include <QList>
36 : : #include <QStringList>
37 : : #include <QDomElement>
38 : : #include <QSettings>
39 : : #include <QDesktopServices>
40 : : #include <QUrl>
41 : : #include <QDir>
42 : : #include <QFileInfo>
43 : : #include <QRegularExpression>
44 : :
45 : :
46 : 0 : QUuid QgsActionManager::addAction( QgsAction::ActionType type, const QString &name, const QString &command, bool capture )
47 : : {
48 : 0 : QgsAction action( type, name, command, capture );
49 : 0 : addAction( action );
50 : 0 : return action.id();
51 : 0 : }
52 : :
53 : 0 : QUuid QgsActionManager::addAction( QgsAction::ActionType type, const QString &name, const QString &command, const QString &icon, bool capture )
54 : : {
55 : 0 : QgsAction action( type, name, command, icon, capture );
56 : 0 : addAction( action );
57 : 0 : return action.id();
58 : 0 : }
59 : :
60 : 0 : void QgsActionManager::addAction( const QgsAction &action )
61 : : {
62 : 0 : QgsDebugMsg( "add action " + action.name() );
63 : 0 : mActions.append( action );
64 : 0 : if ( mLayer && mLayer->dataProvider() && !action.notificationMessage().isEmpty() )
65 : : {
66 : 0 : mLayer->dataProvider()->setListening( true );
67 : 0 : if ( !mOnNotifyConnected )
68 : : {
69 : 0 : QgsDebugMsg( QStringLiteral( "connecting to notify" ) );
70 : 0 : connect( mLayer->dataProvider(), &QgsDataProvider::notify, this, &QgsActionManager::onNotifyRunActions );
71 : 0 : mOnNotifyConnected = true;
72 : 0 : }
73 : 0 : }
74 : 0 : }
75 : :
76 : 0 : void QgsActionManager::onNotifyRunActions( const QString &message )
77 : : {
78 : 0 : for ( const QgsAction &act : std::as_const( mActions ) )
79 : : {
80 : 0 : if ( !act.notificationMessage().isEmpty() && QRegularExpression( act.notificationMessage() ).match( message ).hasMatch() )
81 : : {
82 : 0 : if ( !act.isValid() || !act.runable() )
83 : 0 : continue;
84 : :
85 : 0 : QgsExpressionContext context = createExpressionContext();
86 : :
87 : : Q_ASSERT( mLayer ); // if there is no layer, then where is the notification coming from ?
88 : 0 : context << QgsExpressionContextUtils::layerScope( mLayer );
89 : 0 : context << QgsExpressionContextUtils::notificationScope( message );
90 : :
91 : 0 : QString expandedAction = QgsExpression::replaceExpressionText( act.command(), &context );
92 : 0 : if ( expandedAction.isEmpty() )
93 : 0 : continue;
94 : 0 : runAction( QgsAction( act.type(), act.name(), expandedAction, act.capture() ) );
95 : 0 : }
96 : : }
97 : 0 : }
98 : :
99 : 0 : void QgsActionManager::removeAction( QUuid actionId )
100 : : {
101 : 0 : int i = 0;
102 : 0 : for ( const QgsAction &action : std::as_const( mActions ) )
103 : : {
104 : 0 : if ( action.id() == actionId )
105 : : {
106 : 0 : mActions.removeAt( i );
107 : 0 : break;
108 : : }
109 : 0 : ++i;
110 : : }
111 : :
112 : 0 : if ( mOnNotifyConnected )
113 : : {
114 : 0 : bool hasActionOnNotify = false;
115 : 0 : for ( const QgsAction &action : std::as_const( mActions ) )
116 : 0 : hasActionOnNotify |= !action.notificationMessage().isEmpty();
117 : 0 : if ( !hasActionOnNotify && mLayer && mLayer->dataProvider() )
118 : : {
119 : : // note that there is no way of knowing if the provider is listening only because
120 : : // this class has hasked it to, so we do not reset the provider listening state here
121 : 0 : disconnect( mLayer->dataProvider(), &QgsDataProvider::notify, this, &QgsActionManager::onNotifyRunActions );
122 : 0 : mOnNotifyConnected = false;
123 : 0 : }
124 : 0 : }
125 : 0 : }
126 : :
127 : 0 : void QgsActionManager::doAction( QUuid actionId, const QgsFeature &feature, int defaultValueIndex, const QgsExpressionContextScope &scope )
128 : : {
129 : 0 : QgsExpressionContext context = createExpressionContext();
130 : 0 : QgsExpressionContextScope *actionScope = new QgsExpressionContextScope( scope );
131 : 0 : actionScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "field_index" ), defaultValueIndex, true ) );
132 : 0 : if ( defaultValueIndex >= 0 && defaultValueIndex < feature.fields().size() )
133 : 0 : actionScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "field_name" ), feature.fields().at( defaultValueIndex ).name(), true ) );
134 : 0 : actionScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "field_value" ), feature.attribute( defaultValueIndex ), true ) );
135 : 0 : context << actionScope;
136 : 0 : doAction( actionId, feature, context );
137 : 0 : }
138 : :
139 : 0 : void QgsActionManager::doAction( QUuid actionId, const QgsFeature &feat, const QgsExpressionContext &context )
140 : : {
141 : 0 : QgsAction act = action( actionId );
142 : :
143 : 0 : if ( !act.isValid() || !act.runable() )
144 : 0 : return;
145 : :
146 : 0 : QgsExpressionContext actionContext( context );
147 : :
148 : 0 : if ( mLayer )
149 : 0 : actionContext << QgsExpressionContextUtils::layerScope( mLayer );
150 : 0 : actionContext.setFeature( feat );
151 : :
152 : 0 : QString expandedAction = QgsExpression::replaceExpressionText( act.command(), &actionContext );
153 : 0 : if ( expandedAction.isEmpty() )
154 : 0 : return;
155 : :
156 : 0 : QgsAction newAction( act.type(), act.name(), expandedAction, act.capture() );
157 : 0 : runAction( newAction );
158 : 0 : }
159 : :
160 : 0 : void QgsActionManager::clearActions()
161 : : {
162 : 0 : mActions.clear();
163 : 0 : if ( mOnNotifyConnected && mLayer && mLayer->dataProvider() )
164 : : {
165 : : // note that there is no way of knowing if the provider is listening only because
166 : : // this class has hasked it to, so we do not reset the provider listening state here
167 : 0 : disconnect( mLayer->dataProvider(), &QgsDataProvider::notify, this, &QgsActionManager::onNotifyRunActions );
168 : 0 : mOnNotifyConnected = false;
169 : 0 : }
170 : 0 : }
171 : :
172 : 0 : QList<QgsAction> QgsActionManager::actions( const QString &actionScope ) const
173 : : {
174 : 0 : if ( actionScope.isNull() )
175 : 0 : return mActions;
176 : : else
177 : : {
178 : 0 : QList<QgsAction> actions;
179 : :
180 : 0 : for ( const QgsAction &action : std::as_const( mActions ) )
181 : : {
182 : 0 : if ( action.actionScopes().contains( actionScope ) )
183 : 0 : actions.append( action );
184 : : }
185 : :
186 : 0 : return actions;
187 : 0 : }
188 : 0 : }
189 : :
190 : 0 : void QgsActionManager::runAction( const QgsAction &action )
191 : : {
192 : 0 : if ( action.type() == QgsAction::OpenUrl )
193 : : {
194 : 0 : QFileInfo finfo( action.command() );
195 : 0 : if ( finfo.exists() && finfo.isFile() )
196 : 0 : QDesktopServices::openUrl( QUrl::fromLocalFile( action.command() ) );
197 : : else
198 : 0 : QDesktopServices::openUrl( QUrl( action.command(), QUrl::TolerantMode ) );
199 : 0 : }
200 : 0 : else if ( action.type() == QgsAction::GenericPython )
201 : : {
202 : : // TODO: capture output from QgsPythonRunner (like QgsRunProcess does)
203 : 0 : QgsPythonRunner::run( action.command() );
204 : 0 : }
205 : : else
206 : : {
207 : : // The QgsRunProcess instance created by this static function
208 : : // deletes itself when no longer needed.
209 : 0 : QgsRunProcess::create( action.command(), action.capture() );
210 : : }
211 : 0 : }
212 : :
213 : 0 : QgsExpressionContext QgsActionManager::createExpressionContext() const
214 : : {
215 : 0 : QgsExpressionContext context;
216 : 0 : context << QgsExpressionContextUtils::globalScope()
217 : 0 : << QgsExpressionContextUtils::projectScope( QgsProject::instance() );
218 : 0 : if ( mLayer )
219 : 0 : context << QgsExpressionContextUtils::layerScope( mLayer );
220 : :
221 : 0 : return context;
222 : 0 : }
223 : :
224 : 0 : bool QgsActionManager::writeXml( QDomNode &layer_node ) const
225 : : {
226 : 0 : QDomElement aActions = layer_node.ownerDocument().createElement( QStringLiteral( "attributeactions" ) );
227 : 0 : for ( QMap<QString, QUuid>::const_iterator defaultAction = mDefaultActions.constBegin(); defaultAction != mDefaultActions.constEnd(); ++ defaultAction )
228 : : {
229 : 0 : QDomElement defaultActionElement = layer_node.ownerDocument().createElement( QStringLiteral( "defaultAction" ) );
230 : 0 : defaultActionElement.setAttribute( QStringLiteral( "key" ), defaultAction.key() );
231 : 0 : defaultActionElement.setAttribute( QStringLiteral( "value" ), defaultAction.value().toString() );
232 : 0 : aActions.appendChild( defaultActionElement );
233 : 0 : }
234 : :
235 : 0 : for ( const QgsAction &action : std::as_const( mActions ) )
236 : : {
237 : 0 : action.writeXml( aActions );
238 : : }
239 : 0 : layer_node.appendChild( aActions );
240 : :
241 : : return true;
242 : 0 : }
243 : :
244 : 0 : bool QgsActionManager::readXml( const QDomNode &layer_node )
245 : : {
246 : 0 : clearActions();
247 : :
248 : 0 : QDomNode aaNode = layer_node.namedItem( QStringLiteral( "attributeactions" ) );
249 : :
250 : 0 : if ( !aaNode.isNull() )
251 : : {
252 : 0 : QDomNodeList actionsettings = aaNode.toElement().elementsByTagName( QStringLiteral( "actionsetting" ) );
253 : 0 : for ( int i = 0; i < actionsettings.size(); ++i )
254 : : {
255 : 0 : QgsAction action;
256 : 0 : action.readXml( actionsettings.item( i ) );
257 : 0 : addAction( action );
258 : 0 : }
259 : :
260 : 0 : QDomNodeList defaultActionNodes = aaNode.toElement().elementsByTagName( QStringLiteral( "defaultAction" ) );
261 : :
262 : 0 : for ( int i = 0; i < defaultActionNodes.size(); ++i )
263 : : {
264 : 0 : QDomElement defaultValueElem = defaultActionNodes.at( i ).toElement();
265 : 0 : mDefaultActions.insert( defaultValueElem.attribute( QStringLiteral( "key" ) ), QUuid( defaultValueElem.attribute( QStringLiteral( "value" ) ) ) );
266 : 0 : }
267 : 0 : }
268 : : return true;
269 : 0 : }
270 : :
271 : 0 : QgsAction QgsActionManager::action( QUuid id )
272 : : {
273 : 0 : for ( const QgsAction &action : std::as_const( mActions ) )
274 : : {
275 : 0 : if ( action.id() == id )
276 : 0 : return action;
277 : : }
278 : :
279 : 0 : return QgsAction();
280 : 0 : }
281 : :
282 : 0 : void QgsActionManager::setDefaultAction( const QString &actionScope, QUuid actionId )
283 : : {
284 : 0 : mDefaultActions[ actionScope ] = actionId;
285 : 0 : }
286 : :
287 : 0 : QgsAction QgsActionManager::defaultAction( const QString &actionScope )
288 : : {
289 : 0 : return action( mDefaultActions.value( actionScope ) );
290 : : }
|