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
addAction(QgsAction::ActionType type,const QString & name,const QString & command,bool capture)46 QUuid QgsActionManager::addAction( QgsAction::ActionType type, const QString &name, const QString &command, bool capture )
47 {
48 QgsAction action( type, name, command, capture );
49 addAction( action );
50 return action.id();
51 }
52
addAction(QgsAction::ActionType type,const QString & name,const QString & command,const QString & icon,bool capture)53 QUuid QgsActionManager::addAction( QgsAction::ActionType type, const QString &name, const QString &command, const QString &icon, bool capture )
54 {
55 QgsAction action( type, name, command, icon, capture );
56 addAction( action );
57 return action.id();
58 }
59
addAction(const QgsAction & action)60 void QgsActionManager::addAction( const QgsAction &action )
61 {
62 QgsDebugMsg( "add action " + action.name() );
63 mActions.append( action );
64 if ( mLayer && mLayer->dataProvider() && !action.notificationMessage().isEmpty() )
65 {
66 mLayer->dataProvider()->setListening( true );
67 if ( !mOnNotifyConnected )
68 {
69 QgsDebugMsg( QStringLiteral( "connecting to notify" ) );
70 connect( mLayer->dataProvider(), &QgsDataProvider::notify, this, &QgsActionManager::onNotifyRunActions );
71 mOnNotifyConnected = true;
72 }
73 }
74 }
75
onNotifyRunActions(const QString & message)76 void QgsActionManager::onNotifyRunActions( const QString &message )
77 {
78 for ( const QgsAction &act : std::as_const( mActions ) )
79 {
80 if ( !act.notificationMessage().isEmpty() && QRegularExpression( act.notificationMessage() ).match( message ).hasMatch() )
81 {
82 if ( !act.isValid() || !act.runable() )
83 continue;
84
85 QgsExpressionContext context = createExpressionContext();
86
87 Q_ASSERT( mLayer ); // if there is no layer, then where is the notification coming from ?
88 context << QgsExpressionContextUtils::layerScope( mLayer );
89 context << QgsExpressionContextUtils::notificationScope( message );
90
91 QString expandedAction = QgsExpression::replaceExpressionText( act.command(), &context );
92 if ( expandedAction.isEmpty() )
93 continue;
94 runAction( QgsAction( act.type(), act.name(), expandedAction, act.capture() ) );
95 }
96 }
97 }
98
removeAction(QUuid actionId)99 void QgsActionManager::removeAction( QUuid actionId )
100 {
101 int i = 0;
102 for ( const QgsAction &action : std::as_const( mActions ) )
103 {
104 if ( action.id() == actionId )
105 {
106 mActions.removeAt( i );
107 break;
108 }
109 ++i;
110 }
111
112 if ( mOnNotifyConnected )
113 {
114 bool hasActionOnNotify = false;
115 for ( const QgsAction &action : std::as_const( mActions ) )
116 hasActionOnNotify |= !action.notificationMessage().isEmpty();
117 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 disconnect( mLayer->dataProvider(), &QgsDataProvider::notify, this, &QgsActionManager::onNotifyRunActions );
122 mOnNotifyConnected = false;
123 }
124 }
125 }
126
doAction(QUuid actionId,const QgsFeature & feature,int defaultValueIndex,const QgsExpressionContextScope & scope)127 void QgsActionManager::doAction( QUuid actionId, const QgsFeature &feature, int defaultValueIndex, const QgsExpressionContextScope &scope )
128 {
129 QgsExpressionContext context = createExpressionContext();
130 QgsExpressionContextScope *actionScope = new QgsExpressionContextScope( scope );
131 actionScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "field_index" ), defaultValueIndex, true ) );
132 if ( defaultValueIndex >= 0 && defaultValueIndex < feature.fields().size() )
133 actionScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "field_name" ), feature.fields().at( defaultValueIndex ).name(), true ) );
134 actionScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "field_value" ), feature.attribute( defaultValueIndex ), true ) );
135 context << actionScope;
136 doAction( actionId, feature, context );
137 }
138
doAction(QUuid actionId,const QgsFeature & feat,const QgsExpressionContext & context)139 void QgsActionManager::doAction( QUuid actionId, const QgsFeature &feat, const QgsExpressionContext &context )
140 {
141 QgsAction act = action( actionId );
142
143 if ( !act.isValid() || !act.runable() )
144 return;
145
146 QgsExpressionContext actionContext( context );
147
148 if ( mLayer )
149 actionContext << QgsExpressionContextUtils::layerScope( mLayer );
150 actionContext.setFeature( feat );
151
152 QString expandedAction = QgsExpression::replaceExpressionText( act.command(), &actionContext );
153 if ( expandedAction.isEmpty() )
154 return;
155
156 QgsAction newAction( act.type(), act.name(), expandedAction, act.capture() );
157 runAction( newAction );
158 }
159
clearActions()160 void QgsActionManager::clearActions()
161 {
162 mActions.clear();
163 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 disconnect( mLayer->dataProvider(), &QgsDataProvider::notify, this, &QgsActionManager::onNotifyRunActions );
168 mOnNotifyConnected = false;
169 }
170 }
171
actions(const QString & actionScope) const172 QList<QgsAction> QgsActionManager::actions( const QString &actionScope ) const
173 {
174 if ( actionScope.isNull() )
175 return mActions;
176 else
177 {
178 QList<QgsAction> actions;
179
180 for ( const QgsAction &action : std::as_const( mActions ) )
181 {
182 if ( action.actionScopes().contains( actionScope ) )
183 actions.append( action );
184 }
185
186 return actions;
187 }
188 }
189
runAction(const QgsAction & action)190 void QgsActionManager::runAction( const QgsAction &action )
191 {
192 if ( action.type() == QgsAction::OpenUrl )
193 {
194 QFileInfo finfo( action.command() );
195 if ( finfo.exists() && finfo.isFile() )
196 QDesktopServices::openUrl( QUrl::fromLocalFile( action.command() ) );
197 else
198 QDesktopServices::openUrl( QUrl( action.command(), QUrl::TolerantMode ) );
199 }
200 else if ( action.type() == QgsAction::GenericPython )
201 {
202 // TODO: capture output from QgsPythonRunner (like QgsRunProcess does)
203 QgsPythonRunner::run( action.command() );
204 }
205 else
206 {
207 // The QgsRunProcess instance created by this static function
208 // deletes itself when no longer needed.
209 QgsRunProcess::create( action.command(), action.capture() );
210 }
211 }
212
createExpressionContext() const213 QgsExpressionContext QgsActionManager::createExpressionContext() const
214 {
215 QgsExpressionContext context;
216 context << QgsExpressionContextUtils::globalScope()
217 << QgsExpressionContextUtils::projectScope( QgsProject::instance() );
218 if ( mLayer )
219 context << QgsExpressionContextUtils::layerScope( mLayer );
220
221 return context;
222 }
223
writeXml(QDomNode & layer_node) const224 bool QgsActionManager::writeXml( QDomNode &layer_node ) const
225 {
226 QDomElement aActions = layer_node.ownerDocument().createElement( QStringLiteral( "attributeactions" ) );
227 for ( QMap<QString, QUuid>::const_iterator defaultAction = mDefaultActions.constBegin(); defaultAction != mDefaultActions.constEnd(); ++ defaultAction )
228 {
229 QDomElement defaultActionElement = layer_node.ownerDocument().createElement( QStringLiteral( "defaultAction" ) );
230 defaultActionElement.setAttribute( QStringLiteral( "key" ), defaultAction.key() );
231 defaultActionElement.setAttribute( QStringLiteral( "value" ), defaultAction.value().toString() );
232 aActions.appendChild( defaultActionElement );
233 }
234
235 for ( const QgsAction &action : std::as_const( mActions ) )
236 {
237 action.writeXml( aActions );
238 }
239 layer_node.appendChild( aActions );
240
241 return true;
242 }
243
readXml(const QDomNode & layer_node)244 bool QgsActionManager::readXml( const QDomNode &layer_node )
245 {
246 clearActions();
247
248 QDomNode aaNode = layer_node.namedItem( QStringLiteral( "attributeactions" ) );
249
250 if ( !aaNode.isNull() )
251 {
252 QDomNodeList actionsettings = aaNode.toElement().elementsByTagName( QStringLiteral( "actionsetting" ) );
253 for ( int i = 0; i < actionsettings.size(); ++i )
254 {
255 QgsAction action;
256 action.readXml( actionsettings.item( i ) );
257 addAction( action );
258 }
259
260 QDomNodeList defaultActionNodes = aaNode.toElement().elementsByTagName( QStringLiteral( "defaultAction" ) );
261
262 for ( int i = 0; i < defaultActionNodes.size(); ++i )
263 {
264 QDomElement defaultValueElem = defaultActionNodes.at( i ).toElement();
265 mDefaultActions.insert( defaultValueElem.attribute( QStringLiteral( "key" ) ), QUuid( defaultValueElem.attribute( QStringLiteral( "value" ) ) ) );
266 }
267 }
268 return true;
269 }
270
action(QUuid id) const271 QgsAction QgsActionManager::action( QUuid id ) const
272 {
273 for ( const QgsAction &action : std::as_const( mActions ) )
274 {
275 if ( action.id() == id )
276 return action;
277 }
278
279 return QgsAction();
280 }
281
setDefaultAction(const QString & actionScope,QUuid actionId)282 void QgsActionManager::setDefaultAction( const QString &actionScope, QUuid actionId )
283 {
284 mDefaultActions[ actionScope ] = actionId;
285 }
286
defaultAction(const QString & actionScope)287 QgsAction QgsActionManager::defaultAction( const QString &actionScope )
288 {
289 return action( mDefaultActions.value( actionScope ) );
290 }
291