1 /***************************************************************************
2   qgsaction.cpp - QgsAction
3 
4  ---------------------
5  begin                : 18.4.2016
6  copyright            : (C) 2016 by Matthias Kuhn
7  email                : matthias@opengis.ch
8  ***************************************************************************
9  *                                                                         *
10  *   This program is free software; you can redistribute it and/or modify  *
11  *   it under the terms of the GNU General Public License as published by  *
12  *   the Free Software Foundation; either version 2 of the License, or     *
13  *   (at your option) any later version.                                   *
14  *                                                                         *
15  ***************************************************************************/
16 
17 #include "qgsaction.h"
18 
19 #include <QDesktopServices>
20 #include <QFileInfo>
21 #include <QUrl>
22 
23 #include "qgspythonrunner.h"
24 #include "qgsrunprocess.h"
25 #include "qgsexpressioncontext.h"
26 #include "qgsvectorlayer.h"
27 #include "qgslogger.h"
28 #include "qgsexpressioncontextutils.h"
29 
runable() const30 bool QgsAction::runable() const
31 {
32   return mType == Generic ||
33          mType == GenericPython ||
34          mType == OpenUrl ||
35 #if defined(Q_OS_WIN)
36          mType == Windows
37 #elif defined(Q_OS_MAC)
38          mType == Mac
39 #else
40          mType == Unix
41 #endif
42          ;
43 }
44 
run(QgsVectorLayer * layer,const QgsFeature & feature,const QgsExpressionContext & expressionContext) const45 void QgsAction::run( QgsVectorLayer *layer, const QgsFeature &feature, const QgsExpressionContext &expressionContext ) const
46 {
47   QgsExpressionContext actionContext( expressionContext );
48 
49   actionContext << QgsExpressionContextUtils::layerScope( layer );
50   actionContext.setFeature( feature );
51 
52   run( actionContext );
53 }
54 
run(const QgsExpressionContext & expressionContext) const55 void QgsAction::run( const QgsExpressionContext &expressionContext ) const
56 {
57   if ( !isValid() )
58   {
59     QgsDebugMsg( QStringLiteral( "Invalid action cannot be run" ) );
60     return;
61   }
62 
63   QgsExpressionContextScope *scope = new QgsExpressionContextScope( mExpressionContextScope );
64   QgsExpressionContext context( expressionContext );
65   context << scope;
66 
67   const QString expandedAction = QgsExpression::replaceExpressionText( mCommand, &context );
68 
69   if ( mType == QgsAction::OpenUrl )
70   {
71     const QFileInfo finfo( expandedAction );
72     if ( finfo.exists() && finfo.isFile() )
73       QDesktopServices::openUrl( QUrl::fromLocalFile( expandedAction ) );
74     else
75       QDesktopServices::openUrl( QUrl( expandedAction, QUrl::TolerantMode ) );
76   }
77   else if ( mType == QgsAction::GenericPython )
78   {
79     // TODO: capture output from QgsPythonRunner (like QgsRunProcess does)
80     QgsPythonRunner::run( expandedAction );
81   }
82   else
83   {
84     // The QgsRunProcess instance created by this static function
85     // deletes itself when no longer needed.
86     QgsRunProcess::create( expandedAction, mCaptureOutput );
87   }
88 }
89 
actionScopes() const90 QSet<QString> QgsAction::actionScopes() const
91 {
92   return mActionScopes;
93 }
94 
setActionScopes(const QSet<QString> & actionScopes)95 void QgsAction::setActionScopes( const QSet<QString> &actionScopes )
96 {
97   mActionScopes = actionScopes;
98 }
99 
readXml(const QDomNode & actionNode)100 void QgsAction::readXml( const QDomNode &actionNode )
101 {
102   QDomElement actionElement = actionNode.toElement();
103   const QDomNodeList actionScopeNodes = actionElement.elementsByTagName( QStringLiteral( "actionScope" ) );
104 
105   if ( actionScopeNodes.isEmpty() )
106   {
107     mActionScopes
108         << QStringLiteral( "Canvas" )
109         << QStringLiteral( "Field" )
110         << QStringLiteral( "Feature" );
111   }
112   else
113   {
114     for ( int j = 0; j < actionScopeNodes.length(); ++j )
115     {
116       const QDomElement actionScopeElem = actionScopeNodes.item( j ).toElement();
117       mActionScopes << actionScopeElem.attribute( QStringLiteral( "id" ) );
118     }
119   }
120 
121   mType = static_cast< QgsAction::ActionType >( actionElement.attributeNode( QStringLiteral( "type" ) ).value().toInt() );
122   mDescription = actionElement.attributeNode( QStringLiteral( "name" ) ).value();
123   mCommand = actionElement.attributeNode( QStringLiteral( "action" ) ).value();
124   mIcon = actionElement.attributeNode( QStringLiteral( "icon" ) ).value();
125   mCaptureOutput = actionElement.attributeNode( QStringLiteral( "capture" ) ).value().toInt() != 0;
126   mShortTitle = actionElement.attributeNode( QStringLiteral( "shortTitle" ) ).value();
127   mNotificationMessage = actionElement.attributeNode( QStringLiteral( "notificationMessage" ) ).value();
128   mIsEnabledOnlyWhenEditable = actionElement.attributeNode( QStringLiteral( "isEnabledOnlyWhenEditable" ) ).value().toInt() != 0;
129   mId = QUuid( actionElement.attributeNode( QStringLiteral( "id" ) ).value() );
130   if ( mId.isNull() )
131     mId = QUuid::createUuid();
132 }
133 
writeXml(QDomNode & actionsNode) const134 void QgsAction::writeXml( QDomNode &actionsNode ) const
135 {
136   QDomElement actionSetting = actionsNode.ownerDocument().createElement( QStringLiteral( "actionsetting" ) );
137   actionSetting.setAttribute( QStringLiteral( "type" ), mType );
138   actionSetting.setAttribute( QStringLiteral( "name" ), mDescription );
139   actionSetting.setAttribute( QStringLiteral( "shortTitle" ), mShortTitle );
140   actionSetting.setAttribute( QStringLiteral( "icon" ), mIcon );
141   actionSetting.setAttribute( QStringLiteral( "action" ), mCommand );
142   actionSetting.setAttribute( QStringLiteral( "capture" ), mCaptureOutput );
143   actionSetting.setAttribute( QStringLiteral( "notificationMessage" ), mNotificationMessage );
144   actionSetting.setAttribute( QStringLiteral( "isEnabledOnlyWhenEditable" ), mIsEnabledOnlyWhenEditable );
145   actionSetting.setAttribute( QStringLiteral( "id" ), mId.toString() );
146 
147   const auto constMActionScopes = mActionScopes;
148   for ( const QString &scope : constMActionScopes )
149   {
150     QDomElement actionScopeElem = actionsNode.ownerDocument().createElement( QStringLiteral( "actionScope" ) );
151     actionScopeElem.setAttribute( QStringLiteral( "id" ), scope );
152     actionSetting.appendChild( actionScopeElem );
153   }
154 
155   actionsNode.appendChild( actionSetting );
156 }
157 
setExpressionContextScope(const QgsExpressionContextScope & scope)158 void QgsAction::setExpressionContextScope( const QgsExpressionContextScope &scope )
159 {
160   mExpressionContextScope = scope;
161 }
162 
expressionContextScope() const163 QgsExpressionContextScope QgsAction::expressionContextScope() const
164 {
165   return mExpressionContextScope;
166 }
167 
html() const168 QString QgsAction::html() const
169 {
170   QString typeText;
171   switch ( mType )
172   {
173     case Generic:
174     {
175       typeText = QObject::tr( "Generic" );
176       break;
177     }
178     case GenericPython:
179     {
180       typeText = QObject::tr( "Generic Python" );
181       break;
182     }
183     case Mac:
184     {
185       typeText = QObject::tr( "Mac" );
186       break;
187     }
188     case Windows:
189     {
190       typeText = QObject::tr( "Windows" );
191       break;
192     }
193     case Unix:
194     {
195       typeText = QObject::tr( "Unix" );
196       break;
197     }
198     case OpenUrl:
199     {
200       typeText = QObject::tr( "Open URL" );
201       break;
202     }
203   }
204   return { QObject::tr( R"html(
205 <h2>Action Details</h2>
206 <p>
207    <b>Description:</b> %1<br>
208    <b>Short title:</b> %2<br>
209    <b>Type:</b> %3<br>
210    <b>Scope:</b> %4<br>
211    <b>Action:</b><br>
212    <pre>%6</pre>
213 </p>
214   )html" ).arg( mDescription, mShortTitle, typeText, actionScopes().values().join( QLatin1String( ", " ) ), mCommand )};
215 };
216