File indexing completed on 2024-11-24 03:56:26

0001 /*
0002  * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia <dev@dragon.best>
0003  *
0004  * SPDX-License-Identifier: GPL-3.0-or-later
0005  */
0006 
0007 #pragma once
0008 
0009 #include <QQmlEngine>
0010 
0011 #include "app/scripting/script_engine.hpp"
0012 
0013 namespace app::scripting::js {
0014 
0015 class JsContext : public ScriptExecutionContext
0016 {
0017 public:
0018     JsContext(const ScriptEngine* js_engine) : js_engine(js_engine)
0019     {
0020         qml_engine.installExtensions(QJSEngine::ConsoleExtension);
0021     }
0022 
0023     QJSValue convert(const QVariant& val)
0024     {
0025         if ( val.userType() == QMetaType::QVariantMap )
0026         {
0027             auto jsv = qml_engine.newObject();
0028             auto cont = val.toMap();
0029             for ( auto it = cont.begin(); it != cont.end(); ++it )
0030                 jsv.setProperty(it.key(), convert(*it));
0031             return jsv;
0032         }
0033         else if ( val.userType() == QMetaType::QVariantList )
0034         {
0035             auto cont = val.toList();
0036             auto jsv = qml_engine.newArray(cont.size());
0037             for ( int i = 0; i < cont.size(); i++ )
0038                 jsv.setProperty(i, convert(cont[i]));
0039             return jsv;
0040         }
0041         else if ( val.type() == QVariant::Int )
0042         {
0043             return QJSValue(val.toInt());
0044         }
0045         else if ( val.type() == QVariant::String )
0046         {
0047             return QJSValue(val.toString());
0048         }
0049         else if ( val.canConvert<QObject*>() )
0050         {
0051             QObject* obj = val.value<QObject*>();
0052             if ( obj )
0053             {
0054                 auto jsv = qml_engine.newQObject(obj);
0055                 QQmlEngine::setObjectOwnership(obj, QQmlEngine::CppOwnership);
0056                 return jsv;
0057             }
0058         }
0059         return QJSValue{QJSValue::NullValue};
0060     }
0061 
0062     void expose(const QString& name, const QVariant& val) override
0063     {
0064         qml_engine.globalObject().setProperty(name, convert(val));
0065     }
0066 
0067     QString eval_to_string(const QString& code) override
0068     {
0069         return qml_engine.evaluate(code+";").toString();
0070     }
0071 
0072     bool run_from_module (
0073         const QDir& path,       ///< Path containing the module file
0074         const QString& module,  ///< Module name to load
0075         const QString& function,///< Function to call
0076         const QVariantList& args///< Arguments to pass the function
0077     ) override
0078     {
0079         QStringList import_path = qml_engine.importPathList();
0080         qml_engine.addImportPath(path.path());
0081 
0082         QJSValue module_obj = qml_engine.importModule(module);
0083         if ( module_obj.isError() || !module_obj.hasProperty(function) )
0084             return false;
0085 
0086         QJSValue func = module_obj.property(function);
0087         if ( !func.isCallable() )
0088             return false;
0089 
0090         QJSValueList jsargs;
0091         for ( const auto& arg : args )
0092             jsargs.push_back(convert(arg));
0093 
0094         QJSValue ret = func.call(jsargs);
0095         if ( ret.isError() )
0096             throw ScriptError(ret.toString());
0097 
0098         qml_engine.setImportPathList(import_path);
0099         return true;
0100     }
0101 
0102     const ScriptEngine* engine() const override
0103     {
0104         return js_engine;
0105     }
0106 
0107     void app_module(const QString&) override {}
0108 
0109 private:
0110     QQmlEngine qml_engine;
0111     const ScriptEngine* js_engine;
0112 };
0113 
0114 class JsEngine : public ScriptEngine
0115 {
0116 public:
0117     QString slug() const override { return "js"; }
0118     QString label() const override { return "ECMAScript"; }
0119 
0120     ScriptContext create_context() const override
0121     {
0122         return std::make_unique<JsContext>(this);
0123     }
0124 
0125 private:
0126     static Autoregister<JsEngine> autoreg;
0127 };
0128 
0129 } // namespace app::scripting::js