19#include <nlohmann/json.hpp>
37#include <QRegularExpression>
38#include <QRegularExpressionMatch>
42#include "moc_qgsprocessinghistoryprovider.cpp"
44using namespace Qt::StringLiterals;
52 return u
"processing"_s;
57 const QString logPath = oldLogPath();
58 if ( !QFile::exists( logPath ) )
61 QFile logFile( logPath );
62 if ( logFile.open( QIODevice::ReadOnly ) )
64 QTextStream in( &logFile );
65 QList<QgsHistoryEntry> entries;
68 const QString line = in.readLine().trimmed();
69 QStringList parts = line.split( u
"|~|"_s );
70 if ( parts.size() <= 1 )
71 parts = line.split(
'|' );
73 if ( parts.size() == 3 && parts.at( 0 ).startsWith(
"ALGORITHM"_L1, Qt::CaseInsensitive ) )
76 details.insert( u
"python_command"_s, parts.at( 2 ) );
78 const thread_local QRegularExpression algIdRegEx( u
"processing\\.run\\(\"(.*?)\""_s );
79 const QRegularExpressionMatch match = algIdRegEx.match( parts.at( 2 ) );
80 if ( match.hasMatch() )
81 details.insert( u
"algorithm_id"_s, match.captured( 1 ) );
83 entries.append(
QgsHistoryEntry(
id(), QDateTime::fromString( parts.at( 1 ), u
"yyyy-MM-d hh:mm:ss"_s ), details ) );
99 , mAlgorithmId( mEntry.entry.value(
"algorithm_id" ).toString() )
100 , mPythonCommand( mEntry.entry.value(
"python_command" ).toString() )
101 , mProcessCommand( mEntry.entry.value(
"process_command" ).toString() )
102 , mProvider( provider )
104 const QVariant parameters = mEntry.entry.value( u
"parameters"_s );
105 if ( parameters.userType() == QMetaType::Type::QVariantMap )
107 const QVariantMap parametersMap = parameters.toMap();
108 mInputs = parametersMap.value( u
"inputs"_s ).toMap();
112 bool doubleClicked(
const QgsHistoryWidgetContext & )
override
114 if ( mPythonCommand.isEmpty() )
119 mProvider->emitShowMessage( QObject::tr(
"Could not find algorithm '%1'. Check if corresponding algorithm provider is enabled." ).arg( mAlgorithmId ) );
123 QString execAlgorithmDialogCommand = mPythonCommand;
124 execAlgorithmDialogCommand.replace(
"processing.run("_L1,
"processing.execAlgorithmDialog("_L1 );
127 const QStringList script = {
128 u
"import processing"_s,
129 u
"from qgis.core import QgsProcessingOutputLayerDefinition, QgsProcessingFeatureSourceDefinition, QgsProperty, QgsCoordinateReferenceSystem, QgsFeatureRequest"_s,
130 u
"from qgis.PyQt.QtCore import QDate, QTime, QDateTime"_s,
131 u
"from qgis.PyQt.QtGui import QColor"_s,
132 execAlgorithmDialogCommand
135 mProvider->emitExecute( script.join(
'\n' ) );
141 if ( !mPythonCommand.isEmpty() )
143 QAction *pythonAction =
new QAction(
144 QObject::tr(
"Copy as Python Command" ), menu
147 QObject::connect( pythonAction, &QAction::triggered, menu, [
this] {
148 copyText( mPythonCommand );
150 menu->addAction( pythonAction );
152 if ( !mProcessCommand.isEmpty() )
154 QAction *processAction =
new QAction(
155 QObject::tr(
"Copy as qgis_process Command" ), menu
158 QObject::connect( processAction, &QAction::triggered, menu, [
this] {
159 copyText( mProcessCommand );
161 menu->addAction( processAction );
163 if ( !mInputs.isEmpty() )
165 QAction *inputsAction =
new QAction(
166 QObject::tr(
"Copy as JSON" ), menu
169 QObject::connect( inputsAction, &QAction::triggered, menu, [
this] {
172 menu->addAction( inputsAction );
175 if ( !mPythonCommand.isEmpty() )
177 if ( !menu->isEmpty() )
179 menu->addSeparator();
182 QAction *createTestAction =
new QAction(
183 QObject::tr(
"Create Test…" ), menu
185 QObject::connect( createTestAction, &QAction::triggered, menu, [
this] {
186 mProvider->emitCreateTest( mPythonCommand );
188 menu->addAction( createTestAction );
192 void copyText(
const QString &text )
194 QMimeData *m =
new QMimeData();
196 QApplication::clipboard()->setMimeData( m );
199 QgsHistoryEntry mEntry;
200 QString mAlgorithmId;
201 QString mPythonCommand;
202 QString mProcessCommand;
205 QgsProcessingHistoryProvider *mProvider =
nullptr;
208class ProcessingHistoryPythonCommandNode :
public ProcessingHistoryBaseNode
211 ProcessingHistoryPythonCommandNode(
const QgsHistoryEntry &entry, QgsProcessingHistoryProvider *provider )
212 : ProcessingHistoryBaseNode( entry, provider )
215 QVariant data(
int role = Qt::DisplayRole )
const override
219 case Qt::DisplayRole:
221 QString display = mPythonCommand;
222 if ( display.length() > 300 )
224 display = QObject::tr(
"%1…" ).arg( display.left( 299 ) );
228 case Qt::DecorationRole:
237 QWidget *createWidget(
const QgsHistoryWidgetContext & )
override
239 QgsCodeEditorPython *codeEditor =
new QgsCodeEditorPython();
240 codeEditor->setReadOnly(
true );
241 codeEditor->setCaretLineVisible(
false );
244 codeEditor->setEdgeMode( QsciScintilla::EdgeNone );
245 codeEditor->setWrapMode( QsciScintilla::WrapMode::WrapWord );
248 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" ) );
249 codeEditor->
setText( introText + mPythonCommand );
255class ProcessingHistoryProcessCommandNode :
public ProcessingHistoryBaseNode
258 ProcessingHistoryProcessCommandNode(
const QgsHistoryEntry &entry, QgsProcessingHistoryProvider *provider )
259 : ProcessingHistoryBaseNode( entry, provider )
262 QVariant data(
int role = Qt::DisplayRole )
const override
266 case Qt::DisplayRole:
268 QString display = mProcessCommand;
269 if ( display.length() > 300 )
271 display = QObject::tr(
"%1…" ).arg( display.left( 299 ) );
275 case Qt::DecorationRole:
284 QWidget *createWidget(
const QgsHistoryWidgetContext & )
override
286 QgsCodeEditorShell *codeEditor =
new QgsCodeEditorShell();
287 codeEditor->setReadOnly(
true );
288 codeEditor->setCaretLineVisible(
false );
291 codeEditor->setEdgeMode( QsciScintilla::EdgeNone );
292 codeEditor->setWrapMode( QsciScintilla::WrapMode::WrapWord );
294 codeEditor->
setText( mProcessCommand );
301class ProcessingHistoryJsonNode :
public ProcessingHistoryBaseNode
304 ProcessingHistoryJsonNode(
const QgsHistoryEntry &entry, QgsProcessingHistoryProvider *provider )
305 : ProcessingHistoryBaseNode( entry, provider )
311 QVariant data(
int role = Qt::DisplayRole )
const override
315 case Qt::DisplayRole:
317 QString display = mJsonSingleLine;
318 if ( display.length() > 300 )
320 display = QObject::tr(
"%1…" ).arg( display.left( 299 ) );
324 case Qt::DecorationRole:
333 QWidget *createWidget(
const QgsHistoryWidgetContext & )
override
335 QgsCodeEditorJson *codeEditor =
new QgsCodeEditorJson();
336 codeEditor->setReadOnly(
true );
337 codeEditor->setCaretLineVisible(
false );
340 codeEditor->setEdgeMode( QsciScintilla::EdgeNone );
341 codeEditor->setWrapMode( QsciScintilla::WrapMode::WrapWord );
349 QString mJsonSingleLine;
353class ProcessingHistoryRootNode :
public ProcessingHistoryBaseNode
356 ProcessingHistoryRootNode(
const QgsHistoryEntry &entry, QgsProcessingHistoryProvider *provider )
357 : ProcessingHistoryBaseNode( entry, provider )
359 const QVariant parameters = mEntry.entry.value( u
"parameters"_s );
360 if ( parameters.type() == QVariant::Map )
362 mDescription = QgsProcessingUtils::variantToPythonLiteral( mInputs );
367 mDescription = mPythonCommand;
370 if ( mDescription.length() > 300 )
372 mDescription = QObject::tr(
"%1…" ).arg( mDescription.left( 299 ) );
375 addChild(
new ProcessingHistoryPythonCommandNode( mEntry, mProvider ) );
376 addChild(
new ProcessingHistoryProcessCommandNode( mEntry, mProvider ) );
377 addChild(
new ProcessingHistoryJsonNode( mEntry, mProvider ) );
380 void setEntry(
const QgsHistoryEntry &entry )
385 QVariant data(
int role = Qt::DisplayRole )
const override
387 if ( mAlgorithmInformation.displayName.isEmpty() )
394 case Qt::DisplayRole:
396 const QString algName = mAlgorithmInformation.displayName.isEmpty() ? mAlgorithmId : mAlgorithmInformation.displayName;
397 if ( !mDescription.isEmpty() )
398 return u
"[%1] %2 - %3"_s.arg( mEntry.timestamp.toString( u
"yyyy-MM-dd hh:mm"_s ), algName, mDescription );
400 return u
"[%1] %2"_s.arg( mEntry.timestamp.toString( u
"yyyy-MM-dd hh:mm"_s ), algName );
403 case Qt::DecorationRole:
414 QString html(
const QgsHistoryWidgetContext & )
const override
416 return mEntry.entry.value( u
"log"_s ).toString();
419 QString mDescription;
420 mutable QgsProcessingAlgorithmInformation mAlgorithmInformation;
427 return new ProcessingHistoryRootNode( entry,
this );
432 if ( ProcessingHistoryRootNode *rootNode =
dynamic_cast<ProcessingHistoryRootNode *
>( node ) )
434 rootNode->setEntry( entry );
438QString QgsProcessingHistoryProvider::oldLogPath()
const
441 return userDir + u
"/processing.log"_s;
444void QgsProcessingHistoryProvider::emitExecute(
const QString &commands )
449void QgsProcessingHistoryProvider::emitCreateTest(
const QString &command )
454void 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.