-
Notifications
You must be signed in to change notification settings - Fork 281
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
App: introduce DialogExecScript to let user control script execution
- Loading branch information
1 parent
a392fbd
commit 073bea6
Showing
9 changed files
with
427 additions
and
62 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
/**************************************************************************** | ||
** Copyright (c) 2024, Fougue Ltd. <https://www.fougue.pro> | ||
** All rights reserved. | ||
** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt | ||
****************************************************************************/ | ||
|
||
#include "dialog_exec_script.h" | ||
|
||
#include "qtwidgets_utils.h" | ||
#include "../qtcommon/filepath_conv.h" | ||
#include "../qtcommon/log_message_handler.h" | ||
#include "../qtscripting/script_global.h" | ||
#include "ui_dialog_exec_script.h" | ||
|
||
#include <QtCore/QDir> | ||
#include <QtCore/QFile> | ||
#include <QtCore/QFileinfo> | ||
#include <QtCore/QTimer> | ||
#include <QtQml/QJSEngine> | ||
#include <gsl/util> | ||
|
||
namespace Mayo { | ||
|
||
namespace { | ||
|
||
QString scriptProgram(const QString& strFilepath) | ||
{ | ||
QFile file(strFilepath); | ||
if (file.open(QIODevice::ReadOnly)) | ||
return file.readAll(); | ||
|
||
return {}; | ||
} | ||
|
||
} // namespace | ||
|
||
DialogExecScript::DialogExecScript(QWidget* parent) | ||
: QDialog(parent), | ||
m_ui(new Ui_DialogExecScript) | ||
{ | ||
m_ui->setupUi(this); | ||
// TODO Don't forget to disconnect those slots in destructor(maybe not needed as m_taskMgr will be out of scope) | ||
m_taskMgr.signalStarted.connectSlot(&DialogExecScript::onTaskStarted, this); | ||
m_taskMgr.signalEnded.connectSlot(&DialogExecScript::onTaskEnded, this); | ||
QObject::connect( | ||
m_ui->btn_restartStop, &QAbstractButton::clicked, this, &DialogExecScript::restartOrStopScriptExec | ||
); | ||
QObject::connect( | ||
m_ui->buttonBox->button(QDialogButtonBox::Close), &QAbstractButton::clicked, this, &QDialog::accept | ||
); | ||
} | ||
|
||
DialogExecScript::~DialogExecScript() | ||
{ | ||
delete m_ui; | ||
if (m_jsEngine) | ||
m_jsEngine->deleteLater(); | ||
} | ||
|
||
void DialogExecScript::setScriptEngineCreator(ScriptEngineCreator fn) | ||
{ | ||
m_fnScriptEngineCreator = std::move(fn); | ||
} | ||
|
||
void DialogExecScript::setScriptFilePath(const FilePath& scriptFilePath) | ||
{ | ||
m_scriptFilePath = QDir::toNativeSeparators(filepathTo<QString>(scriptFilePath)); | ||
} | ||
|
||
void DialogExecScript::startScript() | ||
{ | ||
m_scriptExecTaskId = m_taskMgr.newTask([=](TaskProgress*) { | ||
// Override the "console output" handler of LogMessageHandler, first keep the current handler | ||
// so it can be restored before exiting | ||
auto& logMsgHandler = LogMessageHandler::instance(); | ||
auto onEntryJsConsoleOutputHandler = logMsgHandler.jsConsoleOutputHandler(); | ||
auto _ = gsl::finally([&]{ | ||
logMsgHandler.setJsConsoleOutputHandler(onEntryJsConsoleOutputHandler); | ||
}); | ||
auto fnAddConsoleOutput = [=](QtMsgType type, const QString& text, const QString& file, int line) { | ||
const Message msg = { type, text, file, line }; | ||
QTimer::singleShot(0, this, [=]{ this->addConsoleOutput(msg); }); | ||
}; | ||
logMsgHandler.setJsConsoleOutputHandler( | ||
[=](QtMsgType type, const QMessageLogContext& context, const QString& text) { | ||
const QString strFile = context.file ? context.file : ""; | ||
fnAddConsoleOutput(type, text, strFile, context.line); | ||
} | ||
); | ||
|
||
// Evaluate script program | ||
this->recreateScriptEngine(); | ||
auto jsVal = m_jsEngine->evaluate(scriptProgram(m_scriptFilePath), m_scriptFilePath); | ||
if (jsVal.isError()) { | ||
const QString name = jsVal.property("name").toString(); | ||
const QString message = jsVal.property("message").toString(); | ||
fnAddConsoleOutput( | ||
QtCriticalMsg, | ||
tr("%1: %2").arg(name, message), | ||
jsVal.property("fileName").toString(), | ||
jsVal.property("lineNumber").toInt() | ||
); | ||
} | ||
}); | ||
m_taskMgr.run(m_scriptExecTaskId); | ||
} | ||
|
||
void DialogExecScript::done(int resultCode) | ||
{ | ||
if (m_scriptExecIsRunning) { | ||
// TODO Ask if script execution should be stopped | ||
} | ||
else { | ||
QDialog::done(resultCode); | ||
} | ||
} | ||
|
||
void DialogExecScript::onTaskStarted(TaskId taskId) | ||
{ | ||
if (m_scriptExecTaskId != taskId) | ||
return; | ||
|
||
m_scriptExecIsRunning = true; | ||
m_wasScriptExecInterrupted = false; | ||
m_ui->label_Status->setText(tr("Executing '%1'...").arg(m_scriptFilePath)); | ||
m_ui->btn_restartStop->setText(tr("Stop")); | ||
m_ui->progressBar_Execution->setRange(0, 0); | ||
m_ui->progressBar_Execution->setValue(-1); | ||
m_ui->treeWidget_Output->clear(); | ||
} | ||
|
||
void DialogExecScript::onTaskEnded(TaskId taskId) | ||
{ | ||
if (m_scriptExecTaskId != taskId) | ||
return; | ||
|
||
m_scriptExecIsRunning = false; | ||
if (!m_wasScriptExecInterrupted) | ||
m_ui->label_Status->setText(tr("Finished '%1'").arg(m_scriptFilePath)); | ||
else | ||
m_ui->label_Status->setText(tr("Stopped '%1'").arg(m_scriptFilePath)); | ||
|
||
m_ui->btn_restartStop->setText(tr("Restart")); | ||
m_ui->progressBar_Execution->setRange(0, 100); | ||
m_ui->progressBar_Execution->setValue(!m_wasScriptExecInterrupted ? 100 : 0); | ||
m_jsEngine->setInterrupted(false); | ||
|
||
for (int col = 0; col < m_ui->treeWidget_Output->columnCount(); ++col) | ||
m_ui->treeWidget_Output->resizeColumnToContents(col); | ||
} | ||
|
||
void DialogExecScript::restartOrStopScriptExec() | ||
{ | ||
if (m_scriptExecIsRunning) { | ||
m_wasScriptExecInterrupted = true; | ||
m_jsEngine->setInterrupted(true); | ||
} | ||
else { | ||
this->startScript(); | ||
} | ||
} | ||
|
||
void DialogExecScript::recreateScriptEngine() | ||
{ | ||
delete m_jsEngine; | ||
m_jsEngine = m_fnScriptEngineCreator(nullptr); | ||
} | ||
|
||
void DialogExecScript::addConsoleOutput(const Message& msg) | ||
{ | ||
auto fnStrMsgType = [](QtMsgType type) -> QString { | ||
switch (type) { | ||
case QtDebugMsg: return tr("debug"); | ||
case QtWarningMsg: return tr("warning"); | ||
case QtCriticalMsg: return tr("critical"); | ||
case QtFatalMsg: return tr("fatal"); | ||
case QtInfoMsg: return tr("info"); | ||
default: return tr("?"); | ||
} | ||
}; | ||
|
||
auto item = new QTreeWidgetItem; | ||
item->setText(0, fnStrMsgType(msg.type)); | ||
item->setText(1, msg.text); | ||
item->setText(2, QFileInfo(msg.contextFile).fileName()); | ||
item->setText(3, QString::number(msg.contextLine)); | ||
m_ui->treeWidget_Output->addTopLevelItem(item); | ||
} | ||
|
||
} // namespace Mayo |
Oops, something went wrong.