diff --git a/CMakeLists.txt b/CMakeLists.txt index 7020e31b..a05deb69 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,6 +59,7 @@ message(STATUS "[xTools]CMAKE_CXX_COMPILER_VERSION: ${CMAKE_CXX_COMPILER_VERSION # Qt module list(APPEND X_QT_COMPONENTS Gui) list(APPEND X_QT_COMPONENTS Svg) +list(APPEND X_QT_COMPONENTS Qml) list(APPEND X_QT_COMPONENTS Core) list(APPEND X_QT_COMPONENTS Widgets) list(APPEND X_QT_COMPONENTS Network) @@ -84,6 +85,7 @@ if(QT_VERSION VERSION_GREATER_EQUAL "6.10.0") endif() list(APPEND X_LIBS Qt${QT_VERSION_MAJOR}::Network) list(APPEND X_LIBS Qt${QT_VERSION_MAJOR}::Svg) +list(APPEND X_LIBS Qt${QT_VERSION_MAJOR}::Qml) list(APPEND X_LIBS Qt${QT_VERSION_MAJOR}::Widgets) # -------------------------------------------------------------------------------------------------- @@ -212,7 +214,7 @@ x_add_executable(xTools ${X_SOURCES}) target_link_libraries(xTools PRIVATE ${X_LIBS}) x_output_env(xTools) x_deploy_qt(xTools) -x_deploy_lua(xTools) +x_deploy_resources(xTools) # xTools modules x_generate_module_translations(common ${CMAKE_CURRENT_SOURCE_DIR}/src/common ON) x_generate_module_translations(device ${CMAKE_CURRENT_SOURCE_DIR}/src/device ON) diff --git a/cmake/x.cmake b/cmake/x.cmake index be40fd33..859dd731 100644 --- a/cmake/x.cmake +++ b/cmake/x.cmake @@ -154,3 +154,16 @@ function(x_install_3rd_library target_name dir_name) WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/3rd/${dir_name} COMMENT "Deploy 3rd libraries") endfunction() + +function(x_deploy_resources TARGET) + set(dst_dir $/scripts) + if(APPLE) + set(dst_dir ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/../Resources/scripts) + endif() + make_directory(${dst_dir}) + add_custom_command( + TARGET ${TARGET} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/res/scripts ${dst_dir} + COMMENT "Copy lua scripts to output dir") +endfunction() diff --git a/cmake/x_3rd_lua.cmake b/cmake/x_3rd_lua.cmake index 042e2b58..e2b18901 100644 --- a/cmake/x_3rd_lua.cmake +++ b/cmake/x_3rd_lua.cmake @@ -81,16 +81,3 @@ if(MSVC AND X_ENABLE_LUA_APP) # cmake-format: on endif() endif() - -function(x_deploy_lua TARGET) - set(dst_dir $/scripts) - if(APPLE) - set(dst_dir ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/../Resources/scripts) - endif() - make_directory(${dst_dir}) - add_custom_command( - TARGET ${TARGET} - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/res/scripts ${dst_dir} - COMMENT "Copy lua scripts to output dir") -endfunction() diff --git a/src/page/scripts/scriptbase.cpp b/src/page/scripts/scriptbase.cpp index 7371d9ec..aca13e16 100644 --- a/src/page/scripts/scriptbase.cpp +++ b/src/page/scripts/scriptbase.cpp @@ -20,8 +20,6 @@ ScriptBase::ScriptBase(QWidget *parent) , ui(new Ui::ScriptBase) { ui->setupUi(this); - ui->toolButtonRun->setCheckable(true); - connect(ui->comboBoxFile, qOverload(&QComboBox::currentIndexChanged), this, @@ -136,11 +134,10 @@ void ScriptBase::onScriptComboBoxCurrentIndexChanged() void ScriptBase::onRunButtonClicked(bool checked) { - ui->toolButtonRun->setEnabled(false); - if (checked) { - startRunner(); - } else { + if (m_runner) { stopRunner(); + } else { + startRunner(); } } @@ -150,9 +147,13 @@ void ScriptBase::onNewButtonClicked() tr("New Script"), tr("Please input the script name:"), QLineEdit::Normal, - QString("NewScript") + QString(".") + scriptSuffix(), + QString("NewScript"), nullptr, Qt::WindowCloseButtonHint); + if (txt.isEmpty()) { + return; + } + if (!txt.endsWith('.' + scriptSuffix())) { txt += '.' + scriptSuffix(); } @@ -243,6 +244,8 @@ void ScriptBase::onRunnerStarted() void ScriptBase::onRunnerFinished() { + stopRunner(); + ui->toolButtonRun->setEnabled(true); updateUiEnabled(false); diff --git a/src/page/scripts/scriptrunnerjs.cpp b/src/page/scripts/scriptrunnerjs.cpp index 1c2e70fc..8b6b4af8 100644 --- a/src/page/scripts/scriptrunnerjs.cpp +++ b/src/page/scripts/scriptrunnerjs.cpp @@ -8,10 +8,132 @@ **************************************************************************************************/ #include "scriptrunnerjs.h" +#include +#include +#include +#include + ScriptRunnerJs::ScriptRunnerJs(QObject *parent) : ScriptRunner(parent) {} ScriptRunnerJs::~ScriptRunnerJs() {} -void ScriptRunnerJs::run() {} \ No newline at end of file +void ScriptRunnerJs::onBytesRead(const QByteArray &data) +{ + if (data.isEmpty()) { + return; + } + + if (m_engine == nullptr) { + return; + } + + QJSValue jsFunc = m_engine->globalObject().property("onBytesRead"); + if (jsFunc.isCallable()) { + QJSValueList args; + args << QString::fromUtf8(data); + QJSValue result = jsFunc.call(args); + if (result.isError()) { + QString errorMsg = tr("Uncaught exception at line %1: %2") + .arg(result.property("lineNumber").toInt()) + .arg(result.toString()); + emit logOutput(errorMsg); + } + } +} + +void ScriptRunnerJs::xWriteBytes(const QJSValue &value) +{ + if (value.isString()) { + emit invokeWrite(value.toString().toUtf8()); + } else if (value.isArray()) { + QJSValue lengthValue = value.property("length"); + if (lengthValue.isNumber()) { + int length = lengthValue.toInt(); + QByteArray byteArray; + byteArray.reserve(length); + for (int i = 0; i < length; ++i) { + QJSValue element = value.property(i); + if (element.isNumber()) { + int byte = element.toInt(); + if (byte < 0 || byte > 255) { + emit logOutput(tr("Array element at index %1 is out of byte range: %2") + .arg(i) + .arg(byte)); + return; + } + byteArray.append(static_cast(byte)); + } else { + emit logOutput(tr("Array element at index %1 is not a number").arg(i)); + return; + } + } + emit invokeWrite(byteArray); + } else { + emit logOutput(tr("The 'length' property of the array is not a number")); + } + } else { + emit logOutput(tr("xWriteBytes expects a string or an array of bytes")); + } +} + +bool ScriptRunnerJs::xIsInterruptionRequested() +{ + return isInterruptionRequested(); +} + +void ScriptRunnerJs::xSleep(int ms) +{ + if (ms <= 0) { + ms = 10; + } + + QEventLoop loop; + QTimer timer; + timer.setSingleShot(true); + connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); + timer.start(ms); + loop.exec(); +} + +void ScriptRunnerJs::xPrint(const QString &text) +{ + emit logOutput(text); +} + +void ScriptRunnerJs::run() +{ + QFile scriptFile(m_scriptFile); + if (!scriptFile.open(QIODevice::ReadOnly)) { + emit logOutput(tr("Cannot open script file: %1").arg(m_scriptFile)); + return; + } + + QString script = QString::fromUtf8(scriptFile.readAll()); + scriptFile.close(); + + m_engine = new QJSEngine(); + m_engine->installExtensions(QJSEngine::AllExtensions); + QJSValue obj = m_engine->newQObject(this); + m_engine->globalObject().setProperty("jsRunner", obj); + QStringList exceptionStack; + QJSValue result = m_engine->evaluate(script, m_scriptFile, 1, &exceptionStack); + if (result.isError()) { + QString errorMsg = tr("Uncaught exception at line %1: %2") + .arg(result.property("lineNumber").toInt()) + .arg(result.toString()); + emit logOutput(errorMsg); + if (!exceptionStack.isEmpty()) { + emit logOutput(tr("Stack trace:")); + for (const QString &line : qAsConst(exceptionStack)) { + emit logOutput(line); + } + } + } else { + emit logOutput(tr("Script executed successfully.")); + } + + m_engine->deleteLater(); + m_engine = nullptr; +} \ No newline at end of file diff --git a/src/page/scripts/scriptrunnerjs.h b/src/page/scripts/scriptrunnerjs.h index ecf55428..6b7adf78 100644 --- a/src/page/scripts/scriptrunnerjs.h +++ b/src/page/scripts/scriptrunnerjs.h @@ -8,6 +8,9 @@ **************************************************************************************************/ #pragma once +#include +#include + #include "scriptrunner.h" class ScriptRunnerJs : public ScriptRunner @@ -17,6 +20,16 @@ public: explicit ScriptRunnerJs(QObject *parent = nullptr); ~ScriptRunnerJs(); + void onBytesRead(const QByteArray &data) override; + + Q_INVOKABLE void xWriteBytes(const QJSValue &value); + Q_INVOKABLE bool xIsInterruptionRequested(); + Q_INVOKABLE void xSleep(int ms); + Q_INVOKABLE void xPrint(const QString &text); + protected: void run() override; + +private: + QJSEngine *m_engine{nullptr}; }; diff --git a/src/page/scripts/scriptrunnerlua.cpp b/src/page/scripts/scriptrunnerlua.cpp index 34992045..0bb9a474 100644 --- a/src/page/scripts/scriptrunnerlua.cpp +++ b/src/page/scripts/scriptrunnerlua.cpp @@ -60,7 +60,6 @@ void ScriptRunnerLua::run() lua_pop(m_lua, 1); // Remove error message from the stack } - exec(); lua_close(m_lua); m_lua = nullptr; } diff --git a/src/page/scripts/scriptsmanager.cpp b/src/page/scripts/scriptsmanager.cpp index 97e63ea7..62a49d20 100644 --- a/src/page/scripts/scriptsmanager.cpp +++ b/src/page/scripts/scriptsmanager.cpp @@ -39,6 +39,12 @@ void ScriptsManager::load(const QJsonObject &obj) ScriptsManagerParameterKeys keys; m_lua->load(obj.value(keys.lua).toObject(QJsonObject())); m_js->load(obj.value(keys.js).toObject(QJsonObject())); + + int index = obj.value(keys.tabIndex).toInt(0); + if (index < 0 || index >= ui->tabWidget->count()) { + index = 0; + } + ui->tabWidget->setCurrentIndex(index); } QJsonObject ScriptsManager::save() @@ -47,6 +53,7 @@ QJsonObject ScriptsManager::save() QJsonObject obj; obj.insert(keys.lua, m_lua->save()); obj.insert(keys.js, m_js->save()); + obj.insert(keys.tabIndex, ui->tabWidget->currentIndex()); return obj; } diff --git a/src/page/scripts/scriptsmanager.h b/src/page/scripts/scriptsmanager.h index a9257e86..80fac708 100644 --- a/src/page/scripts/scriptsmanager.h +++ b/src/page/scripts/scriptsmanager.h @@ -18,8 +18,9 @@ class ScriptsManager; struct ScriptsManagerParameterKeys { - const QString lua = QStringLiteral("lua"); - const QString js = QStringLiteral("js"); + const QString tabIndex{"tabIndex"}; + const QString lua{"lua"}; + const QString js{"js"}; }; class ScriptLua;