Loading...
Searching...
No Matches
ExecutionHelpers.hpp
1#pragma once
2#include <JS/Qml/QmlObjects.hpp>
3#include <JS/Commands/EditScript.hpp>
4#include <score/command/Dispatchers/CommandDispatcher.hpp>
5#include <score/document/DocumentInterface.hpp>
6#include <Library/LibrarySettings.hpp>
7#include <score/application/ApplicationContext.hpp>
8#include <score/application/GUIApplicationContext.hpp>
9
10#include <ossia/detail/logger.hpp>
11#include <ossia-qt/invoke.hpp>
12#include <ossia-qt/qml_engine_functions.hpp>
13#include <JS/ConsolePanel.hpp>
14
15#include <QDir>
16#include <QFile>
17#include <QQmlComponent>
18#include <QQmlEngine>
19#include <QUrl>
20
21
22namespace JS
23{
24
25inline void connectStateCommit(Script* script, ProcessModel* model)
26{
27 if(!model)
28 return;
29 QObject::connect(
30 script, &Script::commitState, model,
31 [context = QPointer{model}](const QString& key, const QJSValue& value) {
32 auto converted = ossia::qt::value_from_js(value);
33 QMetaObject::invokeMethod(
34 qApp, [context, key, value = std::move(converted)] {
35 if(!context)
36 return;
37 const auto& state = context->state();
38 if(auto it = state.find(key); it != state.end() && it->second == value)
39 return;
40 CommandDispatcher<> dispatcher{
41 score::IDocument::documentContext(*context).commandStack};
42 dispatcher.submit<UpdateStateElement>(*context, key, value);
43 },
44 Qt::QueuedConnection);
45 },
46 Qt::DirectConnection);
47}
48
49
50inline void loadJSObjectFromString(
51 const QString& rootPath, const QByteArray& str, QQmlComponent& comp, bool is_ui)
52{
53 QString path = rootPath;
54 if(is_ui && path.endsWith(".qml"))
55 path.insert(path.size() - 4, ".ui");
56 const auto url = QUrl::fromLocalFile(path);
57 QFile original{path};
58 if(original.open(QIODevice::ReadOnly) && original.readAll() == str)
59 {
60 // Unmodified presets retain Qt's disk compilation cache and their own
61 // import directory. Never merge unrelated addon files in one cache folder.
62 comp.loadUrl(url);
63 }
64 else
65 {
66 // In-memory edits keep the original import base without creating files in
67 // a directory that QQmlTypeLoader may already have cached.
68 comp.setData(str, url);
69 }
70}
71
72inline JS::Script* createJSObject(QQmlComponent& c, QQmlContext* context)
73{
74 const auto& errs = c.errors();
75 if(!errs.empty())
76 {
77 ossia::logger().error(
78 "Uncaught exception at line {} : {}", errs[0].line(),
79 errs[0].toString().toStdString());
80 return nullptr;
81 }
82 else
83 {
84 auto object = c.create(context);
85 auto obj = qobject_cast<JS::Script*>(object);
86 if(obj)
87 return obj;
88 delete object;
89 return nullptr;
90 }
91}
92
93inline JS::Script* createJSObject(
94 const QString& rootPath, const QString& val, QQmlEngine* engine,
95 QQmlContext* context)
96{
97 if(val.trimmed().startsWith("import"))
98 {
99 QQmlComponent c{engine};
100 loadJSObjectFromString(rootPath, val.toUtf8(), c, false);
101 return createJSObject(c, context);
102 }
103 else if(QFile::exists(val))
104 {
105 QQmlComponent c{engine, QUrl::fromLocalFile(val)};
106 return createJSObject(c, context);
107 }
108 return nullptr;
109}
110
111inline void setupExecFuncs(auto* self, QObject* context, ossia::qt::qml_engine_functions* m_execFuncs)
112{
113 QObject::connect(
114 m_execFuncs, &ossia::qt::qml_engine_functions::system, qApp,
115 [](const QString& code) {
116 std::thread{[code] { ::system(code.toStdString().c_str()); }}.detach();
117 }, Qt::QueuedConnection);
118
119 if(auto* js_panel = score::GUIAppContext().findPanel<JS::PanelDelegate>())
120 {
121 QObject::connect(
122 m_execFuncs, &ossia::qt::qml_engine_functions::exec, js_panel,
123 &JS::PanelDelegate::evaluate, Qt::QueuedConnection);
124
125 QObject::connect(
126 m_execFuncs, &ossia::qt::qml_engine_functions::compute, m_execFuncs,
127 [self, context, m_execFuncs, js_panel](const QString& code, const QString& cbname) {
128 // Exec thread
129
130 // Callback ran in UI thread
131 auto cb = [self
132 , context=QPointer{context}
133 , cur = QPointer{self->m_object}
134 , m_execFuncs
135 , cbname] (const QVariant& v) {
136 if(!self)
137 return;
138
139 // Go back to exec thread, we have to go through the normal engine exec ctx
140 ossia::qt::run_async(m_execFuncs, [self, context, cur, v, cbname] {
141 if(!context || !cur)
142 return;
143 if(self->m_object != cur)
144 return;
145
146 auto mo = self->m_object->metaObject();
147 for(int i = 0; i < mo->methodCount(); i++)
148 {
149 if(mo->method(i).name() == cbname)
150 {
151 mo->method(i).invoke(
152 self->m_object, Qt::DirectConnection, QGenericReturnArgument(),
153 QArgument<QVariant>{"v", v});
154 }
155 }
156 });
157 };
158
159 // Go to ui thread
160 ossia::qt::run_async(js_panel, [js_panel, code, cb]() {
161 js_panel->compute(code, cb); // This invokes cb
162 });
163 }, Qt::DirectConnection);
164 }
165}
166}
The CommandDispatcher class.
Definition CommandDispatcher.hpp:13
Definition ConsolePanel.hpp:40
Definition QmlObjects.hpp:849