Skip to content

Commit

Permalink
[processing] Port functionality from history dialog to history
Browse files Browse the repository at this point in the history
provider framework
  • Loading branch information
nyalldawson committed Apr 23, 2023
1 parent 363bf0c commit f794b17
Show file tree
Hide file tree
Showing 4 changed files with 259 additions and 0 deletions.
Expand Up @@ -31,6 +31,21 @@ History provider for operations performed through the Processing framework.
Ports the old text log to the history framework.

This should only be called once -- calling multiple times will result in duplicate log entries
%End

virtual QgsHistoryEntryNode *createNodeForEntry( const QgsHistoryEntry &entry, const QgsHistoryWidgetContext &context ) /Factory/;


signals:

void executePython( const QString &commands );
%Docstring
Emitted when the provider needs to execute python ``commands`` in the Processing context.
%End

void createTest( const QString &command );
%Docstring
Emitted when the provider needs to create a Processing test with the given python ``command``.
%End

};
Expand Down
27 changes: 27 additions & 0 deletions python/plugins/processing/ProcessingPlugin.py
Expand Up @@ -57,6 +57,7 @@
from processing.gui.AlgorithmExecutor import execute, execute_in_place
from processing.gui.AlgorithmDialog import AlgorithmDialog
from processing.gui.BatchAlgorithmDialog import BatchAlgorithmDialog
from processing.gui import TestTools
from processing.modeler.ModelerDialog import ModelerDialog
from processing.tools.system import tempHelpFolder
from processing.tools import dataobjects
Expand Down Expand Up @@ -208,6 +209,13 @@ def initGui(self):
self.edit_features_locator_filter = InPlaceAlgorithmLocatorFilter()
iface.registerLocatorFilter(self.edit_features_locator_filter)

QgsGui.historyProviderRegistry().providerById('processing').executePython.connect(
self._execute_history_commands
)
QgsGui.historyProviderRegistry().providerById('processing').createTest.connect(
self.create_test
)

self.toolbox = ProcessingToolbox()
self.iface.addDockWidget(Qt.RightDockWidgetArea, self.toolbox)
self.toolbox.hide()
Expand Down Expand Up @@ -460,6 +468,13 @@ def unload(self):
self.iface.projectMenu().removeAction(self.projectMenuSeparator)
self.projectMenuSeparator = None

QgsGui.historyProviderRegistry().providerById('processing').executePython.disconnect(
self._execute_history_commands
)
QgsGui.historyProviderRegistry().providerById('processing').createTest.disconnect(
self.create_test
)

Processing.deinitialize()

def openToolbox(self, show):
Expand Down Expand Up @@ -492,3 +507,15 @@ def tr(self, message, disambiguation=None, n=-1):

def editSelected(self, enabled):
self.toolbox.set_in_place_edit_mode(enabled)

def _execute_history_commands(self, commands: str):
"""
Executes Python commands from the history provider
"""
exec(commands)

def create_test(self, command: str):
"""
Starts the test creation process given a processing algorithm run command
"""
TestTools.createTest(command)
196 changes: 196 additions & 0 deletions src/gui/processing/qgsprocessinghistoryprovider.cpp
Expand Up @@ -19,11 +19,20 @@
#include "qgsgui.h"
#include "qgshistoryproviderregistry.h"
#include "qgshistoryentry.h"
#include "qgshistoryentrynode.h"
#include "qgsprocessingregistry.h"
#include "qgscodeeditorpython.h"
#include "qgsjsonutils.h"

#include <nlohmann/json.hpp>
#include <QFile>
#include <QTextStream>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
#include <QAction>
#include <QMenu>
#include <QMimeData>
#include <QClipboard>

QgsProcessingHistoryProvider::QgsProcessingHistoryProvider()
{
Expand Down Expand Up @@ -72,8 +81,195 @@ void QgsProcessingHistoryProvider::portOldLog()
}
}


class ProcessingHistoryNode : public QgsHistoryEntryGroup
{
public:

ProcessingHistoryNode( const QgsHistoryEntry &entry, QgsProcessingHistoryProvider *provider )
: mEntry( entry )
, mAlgorithmId( mEntry.entry.value( "algorithm_id" ).toString() )
, mPythonCommand( mEntry.entry.value( "python_command" ).toString() )
, mProcessCommand( mEntry.entry.value( "process_command" ).toString() )
, mProvider( provider )
{

const QVariant parameters = mEntry.entry.value( QStringLiteral( "parameters" ) );
if ( parameters.type() == QVariant::Map )
{
const QVariantMap parametersMap = parameters.toMap();
mInputs = parametersMap.value( QStringLiteral( "inputs" ) ).toMap();
mDescription = QgsProcessingUtils::variantToPythonLiteral( mInputs );
}
else
{
// an older history entry which didn't record inputs
mDescription = mPythonCommand;
}

if ( mDescription.length() > 300 )
{
mDescription = QObject::tr( "%1…" ).arg( mDescription.left( 299 ) );
}
}

QVariant data( int role = Qt::DisplayRole ) const override
{
if ( mAlgorithmInformation.displayName.isEmpty() )
{
mAlgorithmInformation = QgsApplication::processingRegistry()->algorithmInformation( mAlgorithmId );
}

switch ( role )
{
case Qt::DisplayRole:
{
const QString algName = mAlgorithmInformation.displayName;
if ( !mDescription.isEmpty() )
return QStringLiteral( "[%1] %2 - %3" ).arg( mEntry.timestamp.toString( QStringLiteral( "yyyy-MM-dd hh:mm" ) ),
algName,
mDescription );
else
return QStringLiteral( "[%1] %2" ).arg( mEntry.timestamp.toString( QStringLiteral( "yyyy-MM-dd hh:mm" ) ),
algName );
}

case Qt::DecorationRole:
{
return mAlgorithmInformation.icon;
}
}
return QVariant();
}

QWidget *createWidget( const QgsHistoryWidgetContext & ) override
{
QgsCodeEditorPython *codeEditor = new QgsCodeEditorPython( );
codeEditor->setReadOnly( true );
codeEditor->setCaretLineVisible( false );
codeEditor->setLineNumbersVisible( false );
codeEditor->setFoldingVisible( false );
codeEditor->setEdgeMode( QsciScintilla::EdgeNone );
codeEditor->setWrapMode( QsciScintilla::WrapMode::WrapWord );


const QString introText = QStringLiteral( "\"\"\"\n%1\n\"\"\"\n\n " ).arg(
QObject::tr( "Double-click on the history item or paste the command below to re-run the algorithm" ) );
codeEditor->setText( introText + mPythonCommand );

return codeEditor;
}

void doubleClicked( const QgsHistoryWidgetContext & ) override
{
if ( mPythonCommand.isEmpty() )
return;

QString execAlgorithmDialogCommand = mPythonCommand;
execAlgorithmDialogCommand.replace( QStringLiteral( "processing.run(" ), QStringLiteral( "processing.execAlgorithmDialog(" ) );

// adding to this list? Also update the BatchPanel.py imports!!
const QStringList script =
{
QStringLiteral( "import processing" ),
QStringLiteral( "from qgis.core import QgsProcessingOutputLayerDefinition, QgsProcessingFeatureSourceDefinition, QgsProperty, QgsCoordinateReferenceSystem, QgsFeatureRequest" ),
QStringLiteral( "from qgis.PyQt.QtCore import QDate, QTime, QDateTime" ),
QStringLiteral( "from qgis.PyQt.QtGui import QColor" ),
execAlgorithmDialogCommand
};

mProvider->execute( script.join( '\n' ) );
}

void populateContextMenu( QMenu *menu, const QgsHistoryWidgetContext & ) override
{
if ( !mPythonCommand.isEmpty() )
{
QAction *pythonAction = new QAction(
QObject::tr( "Copy as Python Command" ), menu );
pythonAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mIconPythonFile.svg" ) ) );
QObject::connect( pythonAction, &QAction::triggered, menu, [ = ]
{
copyText( mPythonCommand );
} );
menu->addAction( pythonAction );
}
if ( !mProcessCommand.isEmpty() )
{
QAction *processAction = new QAction(
QObject::tr( "Copy as qgis_process Command" ), menu );
processAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionTerminal.svg" ) ) );
QObject::connect( processAction, &QAction::triggered, menu, [ = ]
{
copyText( mProcessCommand );
} );
menu->addAction( processAction );
}
if ( !mInputs.isEmpty() )
{
QAction *inputsAction = new QAction(
QObject::tr( "Copy as JSON" ), menu );
inputsAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionEditCopy.svg" ) ) );
QObject::connect( inputsAction, &QAction::triggered, menu, [ = ]
{
copyText( QString::fromStdString( QgsJsonUtils::jsonFromVariant( mInputs ).dump( 2 ) ) );
} );
menu->addAction( inputsAction );
}

if ( !mPythonCommand.isEmpty() )
{
if ( !menu->isEmpty() )
{
menu->addSeparator();
}

QAction *createTestAction = new QAction(
QObject::tr( "Create Test…" ), menu );
QObject::connect( createTestAction, &QAction::triggered, menu, [ = ]
{
mProvider->emitCreateTest( mPythonCommand );
} );
menu->addAction( createTestAction );
}
}

void copyText( const QString &text )
{
QMimeData *m = new QMimeData();
m->setText( text );
QApplication::clipboard()->setMimeData( m );
}

QgsHistoryEntry mEntry;
QString mAlgorithmId;
QString mPythonCommand;
QString mProcessCommand;
QVariantMap mInputs;
QString mDescription;

mutable QgsProcessingAlgorithmInformation mAlgorithmInformation;
QgsProcessingHistoryProvider *mProvider = nullptr;

};

QgsHistoryEntryNode *QgsProcessingHistoryProvider::createNodeForEntry( const QgsHistoryEntry &entry, const QgsHistoryWidgetContext & )
{
return new ProcessingHistoryNode( entry, this );
}

QString QgsProcessingHistoryProvider::oldLogPath() const
{
const QString userDir = QgsApplication::qgisSettingsDirPath() + QStringLiteral( "/processing" );
return userDir + QStringLiteral( "/processing.log" );
}

void QgsProcessingHistoryProvider::execute( const QString &commands )
{
emit executePython( commands );
}

void QgsProcessingHistoryProvider::emitCreateTest( const QString &command )
{
emit createTest( command );
}
21 changes: 21 additions & 0 deletions src/gui/processing/qgsprocessinghistoryprovider.h
Expand Up @@ -44,11 +44,32 @@ class GUI_EXPORT QgsProcessingHistoryProvider : public QgsAbstractHistoryProvide
*/
void portOldLog();

QgsHistoryEntryNode *createNodeForEntry( const QgsHistoryEntry &entry, const QgsHistoryWidgetContext &context ) override SIP_FACTORY;

signals:

/**
* Emitted when the provider needs to execute python \a commands in the Processing context.
*/
void executePython( const QString &commands );

/**
* Emitted when the provider needs to create a Processing test with the given python \a command.
*/
void createTest( const QString &command );

private:

//! Executes some python commands
void execute( const QString &commands );

void emitCreateTest( const QString &command );

//! Returns the path to the old log file
QString oldLogPath() const;

friend class ProcessingHistoryNode;

};

#endif //QGSHISTORYPROVIDER_H
Expand Down

0 comments on commit f794b17

Please sign in to comment.