19#include <nlohmann/json.hpp>
37#include <QRegularExpression>
38#include <QRegularExpressionMatch>
42#include "moc_qgsprocessinghistoryprovider.cpp"
44using namespace Qt::StringLiterals;
51 return u
"processing"_s;
56 const QString logPath = oldLogPath();
57 if ( !QFile::exists( logPath ) )
60 QFile logFile( logPath );
61 if ( logFile.open( QIODevice::ReadOnly ) )
63 QTextStream in( &logFile );
64 QList<QgsHistoryEntry> entries;
67 const QString line = in.readLine().trimmed();
68 QStringList parts = line.split( u
"|~|"_s );
69 if ( parts.size() <= 1 )
70 parts = line.split(
'|' );
72 if ( parts.size() == 3 && parts.at( 0 ).startsWith(
"ALGORITHM"_L1, Qt::CaseInsensitive ) )
75 details.insert( u
"python_command"_s, parts.at( 2 ) );
77 const thread_local QRegularExpression algIdRegEx( u
"processing\\.run\\(\"(.*?)\""_s );
78 const QRegularExpressionMatch match = algIdRegEx.match( parts.at( 2 ) );
79 if ( match.hasMatch() )
80 details.insert( u
"algorithm_id"_s, match.captured( 1 ) );
82 entries.append(
QgsHistoryEntry(
id(), QDateTime::fromString( parts.at( 1 ), u
"yyyy-MM-d hh:mm:ss"_s ), details ) );
98 , mAlgorithmId( mEntry.entry.value(
"algorithm_id" ).toString() )
99 , mPythonCommand( mEntry.entry.value(
"python_command" ).toString() )
100 , mProcessCommand( mEntry.entry.value(
"process_command" ).toString() )
101 , mProvider( provider )
103 const QVariant parameters = mEntry.entry.value( u
"parameters"_s );
104 if ( parameters.userType() == QMetaType::Type::QVariantMap )
106 const QVariantMap parametersMap = parameters.toMap();
107 mInputs = parametersMap.value( u
"inputs"_s ).toMap();
111 bool doubleClicked(
const QgsHistoryWidgetContext & )
override
113 if ( mPythonCommand.isEmpty() )
118 mProvider->emitShowMessage( QObject::tr(
"Could not find algorithm '%1'. Check if corresponding algorithm provider is enabled." ).arg( mAlgorithmId ) );
122 QString execAlgorithmDialogCommand = mPythonCommand;
123 execAlgorithmDialogCommand.replace(
"processing.run("_L1,
"processing.execAlgorithmDialog("_L1 );
126 const QStringList script = {
127 u
"import processing"_s,
128 u
"from qgis.core import QgsProcessingOutputLayerDefinition, QgsProcessingFeatureSourceDefinition, QgsProperty, QgsCoordinateReferenceSystem, QgsFeatureRequest"_s,
129 u
"from qgis.PyQt.QtCore import QDate, QTime, QDateTime"_s,
130 u
"from qgis.PyQt.QtGui import QColor"_s,
131 execAlgorithmDialogCommand
134 mProvider->emitExecute( script.join(
'\n' ) );
140 if ( !mPythonCommand.isEmpty() )
142 QAction *pythonAction =
new QAction( QObject::tr(
"Copy as Python Command" ), menu );
144 QObject::connect( pythonAction, &QAction::triggered, menu, [
this] { copyText( mPythonCommand ); } );
145 menu->addAction( pythonAction );
147 if ( !mProcessCommand.isEmpty() )
149 QAction *processAction =
new QAction( QObject::tr(
"Copy as qgis_process Command" ), menu );
151 QObject::connect( processAction, &QAction::triggered, menu, [
this] { copyText( mProcessCommand ); } );
152 menu->addAction( processAction );
154 if ( !mInputs.isEmpty() )
156 QAction *inputsAction =
new QAction( QObject::tr(
"Copy as JSON" ), menu );
158 QObject::connect( inputsAction, &QAction::triggered, menu, [
this] { copyText( QString::fromStdString(
QgsJsonUtils::jsonFromVariant( mInputs ).dump( 2 ) ) ); } );
159 menu->addAction( inputsAction );
162 if ( !mPythonCommand.isEmpty() )
164 if ( !menu->isEmpty() )
166 menu->addSeparator();
169 QAction *createTestAction =
new QAction( QObject::tr(
"Create Test…" ), menu );
170 QObject::connect( createTestAction, &QAction::triggered, menu, [
this] { mProvider->emitCreateTest( mPythonCommand ); } );
171 menu->addAction( createTestAction );
175 void copyText(
const QString &text )
177 QMimeData *m =
new QMimeData();
179 QApplication::clipboard()->setMimeData( m );
182 QgsHistoryEntry mEntry;
183 QString mAlgorithmId;
184 QString mPythonCommand;
185 QString mProcessCommand;
188 QgsProcessingHistoryProvider *mProvider =
nullptr;
191class ProcessingHistoryPythonCommandNode :
public ProcessingHistoryBaseNode
194 ProcessingHistoryPythonCommandNode(
const QgsHistoryEntry &entry, QgsProcessingHistoryProvider *provider )
195 : ProcessingHistoryBaseNode( entry, provider )
198 QVariant data(
int role = Qt::DisplayRole )
const override
202 case Qt::DisplayRole:
204 QString display = mPythonCommand;
205 if ( display.length() > 300 )
207 display = QObject::tr(
"%1…" ).arg( display.left( 299 ) );
211 case Qt::DecorationRole:
220 QWidget *createWidget(
const QgsHistoryWidgetContext & )
override
222 QgsCodeEditorPython *codeEditor =
new QgsCodeEditorPython();
223 codeEditor->setReadOnly(
true );
224 codeEditor->setCaretLineVisible(
false );
227 codeEditor->setEdgeMode( QsciScintilla::EdgeNone );
228 codeEditor->setWrapMode( QsciScintilla::WrapMode::WrapWord );
231 const QString introText = u
"\"\"\"\n%1\n\"\"\"\n\n "_s.arg( QObject::tr(
"Double-click on the history item or paste the command below to re-run the algorithm" ) );
232 codeEditor->
setText( introText + mPythonCommand );
238class ProcessingHistoryProcessCommandNode :
public ProcessingHistoryBaseNode
241 ProcessingHistoryProcessCommandNode(
const QgsHistoryEntry &entry, QgsProcessingHistoryProvider *provider )
242 : ProcessingHistoryBaseNode( entry, provider )
245 QVariant data(
int role = Qt::DisplayRole )
const override
249 case Qt::DisplayRole:
251 QString display = mProcessCommand;
252 if ( display.length() > 300 )
254 display = QObject::tr(
"%1…" ).arg( display.left( 299 ) );
258 case Qt::DecorationRole:
267 QWidget *createWidget(
const QgsHistoryWidgetContext & )
override
269 QgsCodeEditorShell *codeEditor =
new QgsCodeEditorShell();
270 codeEditor->setReadOnly(
true );
271 codeEditor->setCaretLineVisible(
false );
274 codeEditor->setEdgeMode( QsciScintilla::EdgeNone );
275 codeEditor->setWrapMode( QsciScintilla::WrapMode::WrapWord );
277 codeEditor->
setText( mProcessCommand );
284class ProcessingHistoryJsonNode :
public ProcessingHistoryBaseNode
287 ProcessingHistoryJsonNode(
const QgsHistoryEntry &entry, QgsProcessingHistoryProvider *provider )
288 : ProcessingHistoryBaseNode( entry, provider )
294 QVariant data(
int role = Qt::DisplayRole )
const override
298 case Qt::DisplayRole:
300 QString display = mJsonSingleLine;
301 if ( display.length() > 300 )
303 display = QObject::tr(
"%1…" ).arg( display.left( 299 ) );
307 case Qt::DecorationRole:
316 QWidget *createWidget(
const QgsHistoryWidgetContext & )
override
318 QgsCodeEditorJson *codeEditor =
new QgsCodeEditorJson();
319 codeEditor->setReadOnly(
true );
320 codeEditor->setCaretLineVisible(
false );
323 codeEditor->setEdgeMode( QsciScintilla::EdgeNone );
324 codeEditor->setWrapMode( QsciScintilla::WrapMode::WrapWord );
332 QString mJsonSingleLine;
336class ProcessingHistoryRootNode :
public ProcessingHistoryBaseNode
339 ProcessingHistoryRootNode(
const QgsHistoryEntry &entry, QgsProcessingHistoryProvider *provider )
340 : ProcessingHistoryBaseNode( entry, provider )
342 const QVariant parameters = mEntry.entry.value( u
"parameters"_s );
343 if ( parameters.type() == QVariant::Map )
345 mDescription = QgsProcessingUtils::variantToPythonLiteral( mInputs );
350 mDescription = mPythonCommand;
353 if ( mDescription.length() > 300 )
355 mDescription = QObject::tr(
"%1…" ).arg( mDescription.left( 299 ) );
358 addChild(
new ProcessingHistoryPythonCommandNode( mEntry, mProvider ) );
359 addChild(
new ProcessingHistoryProcessCommandNode( mEntry, mProvider ) );
360 addChild(
new ProcessingHistoryJsonNode( mEntry, mProvider ) );
363 void setEntry(
const QgsHistoryEntry &entry ) { mEntry = entry; }
365 QVariant data(
int role = Qt::DisplayRole )
const override
367 if ( mAlgorithmInformation.displayName.isEmpty() )
374 case Qt::DisplayRole:
376 const QString algName = mAlgorithmInformation.displayName.isEmpty() ? mAlgorithmId : mAlgorithmInformation.displayName;
377 if ( !mDescription.isEmpty() )
378 return u
"[%1] %2 - %3"_s.arg( mEntry.timestamp.toString( u
"yyyy-MM-dd hh:mm"_s ), algName, mDescription );
380 return u
"[%1] %2"_s.arg( mEntry.timestamp.toString( u
"yyyy-MM-dd hh:mm"_s ), algName );
383 case Qt::DecorationRole:
394 QString html(
const QgsHistoryWidgetContext & )
const override {
return mEntry.entry.value( u
"log"_s ).toString(); }
396 QString mDescription;
397 mutable QgsProcessingAlgorithmInformation mAlgorithmInformation;
404 return new ProcessingHistoryRootNode( entry,
this );
409 if ( ProcessingHistoryRootNode *rootNode =
dynamic_cast<ProcessingHistoryRootNode *
>( node ) )
411 rootNode->setEntry( entry );
415QString QgsProcessingHistoryProvider::oldLogPath()
const
418 return userDir + u
"/processing.log"_s;
421void QgsProcessingHistoryProvider::emitExecute(
const QString &commands )
426void QgsProcessingHistoryProvider::emitCreateTest(
const QString &command )
431void QgsProcessingHistoryProvider::emitShowMessage(
const QString &message )
static QgsProcessingRegistry * processingRegistry()
Returns the application's processing registry, used for managing processing providers,...
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QString qgisSettingsDirPath()
Returns the path to the settings directory in user's home dir.
void setText(const QString &text) override
void setFoldingVisible(bool folding)
Set whether the folding controls are visible in the editor.
void setLineNumbersVisible(bool visible)
Sets whether line numbers should be visible in the editor.
static QgsHistoryProviderRegistry * historyProviderRegistry()
Returns the global history provider registry, used for tracking history providers.
Base class for history entry "group" nodes, which contain children of their own.
Base class for nodes representing a QgsHistoryEntry.
virtual void populateContextMenu(QMenu *menu, const QgsHistoryWidgetContext &context)
Allows the node to populate a context menu before display to the user.
virtual bool doubleClicked(const QgsHistoryWidgetContext &context)
Called when the node is double-clicked.
Encapsulates a history entry.
bool addEntries(const QList< QgsHistoryEntry > &entries, QgsHistoryProviderRegistry::HistoryEntryOptions options=QgsHistoryProviderRegistry::HistoryEntryOptions())
Adds a list of entries to the history logs.
Contains settings which reflect the context in which a history widget is shown, e....
static json jsonFromVariant(const QVariant &v)
Converts a QVariant v to a json object.
History provider for operations performed through the Processing framework.
void showMessage(const QString &message)
Emitted when the provider needs to display a message.
void updateNodeForEntry(QgsHistoryEntryNode *node, const QgsHistoryEntry &entry, const QgsHistoryWidgetContext &context) override
Updates an existing history node for the given entry.
QString id() const override
Returns the provider's unique id, which is used to associate existing history entries with the provid...
void executePython(const QString &commands)
Emitted when the provider needs to execute python commands in the Processing context.
QgsHistoryEntryNode * createNodeForEntry(const QgsHistoryEntry &entry, const QgsHistoryWidgetContext &context) override
Creates a new history node for the given entry.
void createTest(const QString &command)
Emitted when the provider needs to create a Processing test with the given python command.
void portOldLog()
Ports the old text log to the history framework.
QgsProcessingHistoryProvider()
QgsProcessingAlgorithmInformation algorithmInformation(const QString &id) const
Returns basic algorithm information for the algorithm with matching ID.