mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-15 00:04:00 -04:00
290 lines
9.8 KiB
C++
290 lines
9.8 KiB
C++
/***************************************************************************
|
|
qgsactionmanager.cpp
|
|
|
|
A class that stores and controls the management and execution of actions
|
|
associated. Actions are defined to be external programs that are run
|
|
with user-specified inputs that can depend on the value of layer
|
|
attributes.
|
|
|
|
-------------------
|
|
begin : Oct 24 2004
|
|
copyright : (C) 2004 by Gavin Macaulay
|
|
email : gavin at macaulay dot co dot nz
|
|
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* 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 "qgsactionmanager.h"
|
|
#include "qgspythonrunner.h"
|
|
#include "qgsrunprocess.h"
|
|
#include "qgsvectorlayer.h"
|
|
#include "qgsproject.h"
|
|
#include "qgslogger.h"
|
|
#include "qgsexpression.h"
|
|
#include "qgsdataprovider.h"
|
|
|
|
#include <QList>
|
|
#include <QStringList>
|
|
#include <QDomElement>
|
|
#include <QSettings>
|
|
#include <QDesktopServices>
|
|
#include <QUrl>
|
|
#include <QDir>
|
|
#include <QFileInfo>
|
|
#include <QRegularExpression>
|
|
|
|
|
|
QUuid QgsActionManager::addAction( QgsAction::ActionType type, const QString &name, const QString &command, bool capture )
|
|
{
|
|
QgsAction action( type, name, command, capture );
|
|
addAction( action );
|
|
return action.id();
|
|
}
|
|
|
|
QUuid QgsActionManager::addAction( QgsAction::ActionType type, const QString &name, const QString &command, const QString &icon, bool capture )
|
|
{
|
|
QgsAction action( type, name, command, icon, capture );
|
|
addAction( action );
|
|
return action.id();
|
|
}
|
|
|
|
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;
|
|
for ( const QgsAction &action : qgsAsConst( mActions ) )
|
|
{
|
|
if ( action.id() == actionId )
|
|
{
|
|
mActions.removeAt( i );
|
|
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 )
|
|
{
|
|
QgsExpressionContext context = createExpressionContext();
|
|
QgsExpressionContextScope *actionScope = new QgsExpressionContextScope();
|
|
actionScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "field_index" ), defaultValueIndex, true ) );
|
|
if ( defaultValueIndex >= 0 && defaultValueIndex < feature.fields().size() )
|
|
actionScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "field_name" ), feature.fields().at( defaultValueIndex ).name(), true ) );
|
|
actionScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "field_value" ), feature.attribute( defaultValueIndex ), true ) );
|
|
context << actionScope;
|
|
doAction( actionId, feature, context );
|
|
}
|
|
|
|
void QgsActionManager::doAction( const QUuid &actionId, const QgsFeature &feat, const QgsExpressionContext &context )
|
|
{
|
|
QgsAction act = action( actionId );
|
|
|
|
if ( !act.isValid() || !act.runable() )
|
|
return;
|
|
|
|
QgsExpressionContext actionContext( context );
|
|
|
|
if ( mLayer )
|
|
actionContext << QgsExpressionContextUtils::layerScope( mLayer );
|
|
actionContext.setFeature( feat );
|
|
|
|
QString expandedAction = QgsExpression::replaceExpressionText( act.command(), &actionContext );
|
|
if ( expandedAction.isEmpty() )
|
|
return;
|
|
|
|
QgsAction newAction( act.type(), act.name(), expandedAction, act.capture() );
|
|
runAction( newAction );
|
|
}
|
|
|
|
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
|
|
{
|
|
if ( actionScope.isNull() )
|
|
return mActions;
|
|
else
|
|
{
|
|
QList<QgsAction> actions;
|
|
|
|
for ( const QgsAction &action : qgsAsConst( mActions ) )
|
|
{
|
|
if ( action.actionScopes().contains( actionScope ) )
|
|
actions.append( action );
|
|
}
|
|
|
|
return actions;
|
|
}
|
|
}
|
|
|
|
void QgsActionManager::runAction( const QgsAction &action )
|
|
{
|
|
if ( action.type() == QgsAction::OpenUrl )
|
|
{
|
|
QFileInfo finfo( action.command() );
|
|
if ( finfo.exists() && finfo.isFile() )
|
|
QDesktopServices::openUrl( QUrl::fromLocalFile( action.command() ) );
|
|
else
|
|
QDesktopServices::openUrl( QUrl( action.command(), QUrl::TolerantMode ) );
|
|
}
|
|
else if ( action.type() == QgsAction::GenericPython )
|
|
{
|
|
// TODO: capture output from QgsPythonRunner (like QgsRunProcess does)
|
|
QgsPythonRunner::run( action.command() );
|
|
}
|
|
else
|
|
{
|
|
// The QgsRunProcess instance created by this static function
|
|
// deletes itself when no longer needed.
|
|
QgsRunProcess::create( action.command(), action.capture() );
|
|
}
|
|
}
|
|
|
|
QgsExpressionContext QgsActionManager::createExpressionContext() const
|
|
{
|
|
QgsExpressionContext context;
|
|
context << QgsExpressionContextUtils::globalScope()
|
|
<< QgsExpressionContextUtils::projectScope( QgsProject::instance() );
|
|
if ( mLayer )
|
|
context << QgsExpressionContextUtils::layerScope( mLayer );
|
|
|
|
return context;
|
|
}
|
|
|
|
bool QgsActionManager::writeXml( QDomNode &layer_node ) const
|
|
{
|
|
QDomElement aActions = layer_node.ownerDocument().createElement( QStringLiteral( "attributeactions" ) );
|
|
for ( QMap<QString, QUuid>::const_iterator defaultAction = mDefaultActions.constBegin(); defaultAction != mDefaultActions.constEnd(); ++ defaultAction )
|
|
{
|
|
QDomElement defaultActionElement = layer_node.ownerDocument().createElement( QStringLiteral( "defaultAction" ) );
|
|
defaultActionElement.setAttribute( QStringLiteral( "key" ), defaultAction.key() );
|
|
defaultActionElement.setAttribute( QStringLiteral( "value" ), defaultAction.value().toString() );
|
|
aActions.appendChild( defaultActionElement );
|
|
}
|
|
|
|
for ( const QgsAction &action : qgsAsConst( mActions ) )
|
|
{
|
|
action.writeXml( aActions );
|
|
}
|
|
layer_node.appendChild( aActions );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool QgsActionManager::readXml( const QDomNode &layer_node )
|
|
{
|
|
clearActions();
|
|
|
|
QDomNode aaNode = layer_node.namedItem( QStringLiteral( "attributeactions" ) );
|
|
|
|
if ( !aaNode.isNull() )
|
|
{
|
|
QDomNodeList actionsettings = aaNode.toElement().elementsByTagName( QStringLiteral( "actionsetting" ) );
|
|
for ( int i = 0; i < actionsettings.size(); ++i )
|
|
{
|
|
QgsAction action;
|
|
action.readXml( actionsettings.item( i ) );
|
|
addAction( action );
|
|
}
|
|
|
|
QDomNodeList defaultActionNodes = aaNode.toElement().elementsByTagName( QStringLiteral( "defaultAction" ) );
|
|
|
|
for ( int i = 0; i < defaultActionNodes.size(); ++i )
|
|
{
|
|
QDomElement defaultValueElem = defaultActionNodes.at( i ).toElement();
|
|
mDefaultActions.insert( defaultValueElem.attribute( QStringLiteral( "key" ) ), defaultValueElem.attribute( QStringLiteral( "value" ) ) );
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
QgsAction QgsActionManager::action( const QUuid &id )
|
|
{
|
|
for ( const QgsAction &action : qgsAsConst( mActions ) )
|
|
{
|
|
if ( action.id() == id )
|
|
return action;
|
|
}
|
|
|
|
return QgsAction();
|
|
}
|
|
|
|
void QgsActionManager::setDefaultAction( const QString &actionScope, const QUuid &actionId )
|
|
{
|
|
mDefaultActions[ actionScope ] = actionId;
|
|
}
|
|
|
|
QgsAction QgsActionManager::defaultAction( const QString &actionScope )
|
|
{
|
|
return action( mDefaultActions.value( actionScope ) );
|
|
}
|