diff --git a/src/lib/core/application/ApplicationSettings.cpp b/src/lib/core/application/ApplicationSettings.cpp index 8dc97552a6..fb434f5186 100644 --- a/src/lib/core/application/ApplicationSettings.cpp +++ b/src/lib/core/application/ApplicationSettings.cpp @@ -57,6 +57,11 @@ void ApplicationSettings::parse(QStringList cargs, int& argc, char** argv) "N", "0"); parser.addOption(waitLoadOpt); + QCommandLineOption uiOpt( + "ui", QCoreApplication::translate("main", "Specify an UI file to load."), "file", + ""); + parser.addOption(uiOpt); + #if defined(__APPLE__) // Bogus macOS gatekeeper BS: // https://stackoverflow.com/questions/55562155/qt-application-for-mac-not-being-launched @@ -106,6 +111,12 @@ void ApplicationSettings::parse(QStringList cargs, int& argc, char** argv) tryToRestore = !parser.isSet(noRestore); this->forceRestore = parser.isSet(forceRestore); gui = !parser.isSet(noGUI); + + if(parser.isSet(uiOpt)) + { + gui = false; + ui = parser.value(uiOpt); + } if(parser.isSet(GL)) opengl = true; if(parser.isSet(noGL)) diff --git a/src/lib/core/application/ApplicationSettings.hpp b/src/lib/core/application/ApplicationSettings.hpp index b81ed576d4..5284354083 100644 --- a/src/lib/core/application/ApplicationSettings.hpp +++ b/src/lib/core/application/ApplicationSettings.hpp @@ -36,6 +36,9 @@ struct SCORE_LIB_BASE_EXPORT ApplicationSettings //! List of scenarios that should be loaded QStringList loadList; + //! UI file to be loaded + QString ui; + //! Seconds to wait before playing int waitAfterLoad = 0; diff --git a/src/plugins/score-plugin-js/CMakeLists.txt b/src/plugins/score-plugin-js/CMakeLists.txt index 7682bb5c25..15d29a71c6 100644 --- a/src/plugins/score-plugin-js/CMakeLists.txt +++ b/src/plugins/score-plugin-js/CMakeLists.txt @@ -29,17 +29,24 @@ set(HDRS "${CMAKE_CURRENT_SOURCE_DIR}/JS/Executor/GPUNode.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/JS/Executor/JSAPIWrapper.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/AddressItem.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/DeviceContext.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/EditContext.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/QmlObjects.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/ValueTypes.${QT_PREFIX}.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/Metatypes.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/PortSource.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/Utils.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/JS/ApplicationPlugin.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/JS/ConsolePanel.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/JS/DropHandler.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/JS/JSProcessFactory.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/JS/JSProcessMetadata.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/JS/JSProcessModel.hpp" - "${CMAKE_CURRENT_SOURCE_DIR}/JS/ConsolePanel.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/JS/LibraryHandler.hpp" ) + set(SRCS "${CMAKE_CURRENT_SOURCE_DIR}/JS/JSProcessModel.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/JS/JSProcessModelSerialization.cpp" @@ -51,6 +58,8 @@ set(SRCS "${CMAKE_CURRENT_SOURCE_DIR}/JS/Commands/JSCommandFactory.cpp" +"${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/AddressItem.cpp" +"${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/DeviceContext.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/EditContext.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/EditContext.curve.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/EditContext.device.cpp" @@ -59,10 +68,12 @@ set(SRCS "${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/EditContext.port.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/EditContext.scenario.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/EditContext.ui.cpp" +"${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/PortSource.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/QmlObjects.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/ValueTypes.${QT_PREFIX}.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/Utils.cpp" +"${CMAKE_CURRENT_SOURCE_DIR}/JS/ApplicationPlugin.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/JS/ConsolePanel.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/score_plugin_js.cpp" ) diff --git a/src/plugins/score-plugin-js/JS/ApplicationPlugin.cpp b/src/plugins/score-plugin-js/JS/ApplicationPlugin.cpp new file mode 100644 index 0000000000..e8b8e1d8f5 --- /dev/null +++ b/src/plugins/score-plugin-js/JS/ApplicationPlugin.cpp @@ -0,0 +1,46 @@ +#include "ApplicationPlugin.hpp" + +#include +#include +#include + +namespace JS +{ +ApplicationPlugin::ApplicationPlugin(const score::GUIApplicationContext& ctx) + : score::GUIApplicationPlugin{ctx} +{ + m_engine.globalObject().setProperty("Score", m_engine.newQObject(new EditJsContext)); + m_engine.globalObject().setProperty("Util", m_engine.newQObject(new JsUtils)); + m_engine.globalObject().setProperty("Device", m_engine.newQObject(new DeviceContext)); +} + +ApplicationPlugin::~ApplicationPlugin() { } + +bool ApplicationPlugin::handleStartup() +{ + if(QFileInfo f{context.applicationSettings.ui}; f.isFile()) + { + m_comp = new QQmlComponent{&m_engine, f.absoluteFilePath(), this}; + + if(auto obj = m_comp->create()) + { + if(auto item = qobject_cast(obj)) + { + m_window = new QQuickWindow{}; + m_window->setWidth(640); + m_window->setHeight(480); + item->setParentItem(m_window->contentItem()); + m_window->show(); + return true; + } + } + else + { + qDebug() << m_comp->errorString(); + qGuiApp->exit(1); + } + delete m_comp; + } + return false; +} +} diff --git a/src/plugins/score-plugin-js/JS/ApplicationPlugin.hpp b/src/plugins/score-plugin-js/JS/ApplicationPlugin.hpp new file mode 100644 index 0000000000..085dba0e7e --- /dev/null +++ b/src/plugins/score-plugin-js/JS/ApplicationPlugin.hpp @@ -0,0 +1,28 @@ +#pragma once +#include + +#include + +#include +#include +#include +#include +#include + +namespace JS +{ +class ApplicationPlugin final + : public QObject + , public score::GUIApplicationPlugin +{ +public: + explicit ApplicationPlugin(const score::GUIApplicationContext& ctx); + + ~ApplicationPlugin() override; + bool handleStartup() override; + + QQmlEngine m_engine; + QQmlComponent* m_comp{}; + QQuickWindow* m_window{}; +}; +} diff --git a/src/plugins/score-plugin-js/JS/ConsolePanel.cpp b/src/plugins/score-plugin-js/JS/ConsolePanel.cpp index 044d9fda7d..0aa7482d06 100644 --- a/src/plugins/score-plugin-js/JS/ConsolePanel.cpp +++ b/src/plugins/score-plugin-js/JS/ConsolePanel.cpp @@ -1,23 +1,24 @@ #include "ConsolePanel.hpp" -#include -#include +#include #include #include +#include +#include namespace JS { PanelDelegate::PanelDelegate(const score::GUIApplicationContext& ctx) : score::PanelDelegate{ctx} + , m_engine{ctx.guiApplicationPlugin().m_engine} , m_widget{new QWidget} { m_engine.installExtensions(QJSEngine::ConsoleExtension); - m_engine.globalObject().setProperty("Score", m_engine.newQObject(new EditJsContext)); - m_engine.globalObject().setProperty("Util", m_engine.newQObject(new JsUtils)); m_engine.globalObject().setProperty( "ActionContext", m_engine.newQObject(new ActionContext)); + auto lay = new QVBoxLayout; m_widget->setLayout(lay); m_widget->setStatusTip( diff --git a/src/plugins/score-plugin-js/JS/ConsolePanel.hpp b/src/plugins/score-plugin-js/JS/ConsolePanel.hpp index b24a9fe0eb..a30354f09e 100644 --- a/src/plugins/score-plugin-js/JS/ConsolePanel.hpp +++ b/src/plugins/score-plugin-js/JS/ConsolePanel.hpp @@ -9,8 +9,6 @@ #include -#include -#include #include #include #include @@ -20,6 +18,8 @@ #include #include +class QQmlEngine; +class QJSEngine; namespace JS { @@ -50,10 +50,10 @@ class SCORE_PLUGIN_JS_EXPORT PanelDelegate final const score::PanelStatus& defaultPanelStatus() const override; + QQmlEngine& m_engine; QWidget* m_widget{}; QPlainTextEdit* m_edit{}; QLineEdit* m_lineEdit{}; - QJSEngine m_engine; }; class PanelDelegateFactory final : public score::PanelDelegateFactory diff --git a/src/plugins/score-plugin-js/JS/Executor/CPUNode.cpp b/src/plugins/score-plugin-js/JS/Executor/CPUNode.cpp index 9d2345df7c..7d041910c4 100644 --- a/src/plugins/score-plugin-js/JS/Executor/CPUNode.cpp +++ b/src/plugins/score-plugin-js/JS/Executor/CPUNode.cpp @@ -6,6 +6,8 @@ #include #include +#include + #include #include #include @@ -80,7 +82,10 @@ void js_node::setScript(const QString& val) if(!m_engine) { m_engine = new QQmlEngine; - m_execFuncs = new ExecStateWrapper{m_st, m_engine}; + m_execFuncs = new ExecStateWrapper{ + m_st.exec_devices(), [&](Args&&... args) { + m_st.insert(std::forward(args)...); + }, m_engine}; m_engine->rootContext()->setContextProperty("Device", m_execFuncs); QObject::connect( diff --git a/src/plugins/score-plugin-js/JS/Executor/JSAPIWrapper.cpp b/src/plugins/score-plugin-js/JS/Executor/JSAPIWrapper.cpp index dd8737885a..d468d695ff 100644 --- a/src/plugins/score-plugin-js/JS/Executor/JSAPIWrapper.cpp +++ b/src/plugins/score-plugin-js/JS/Executor/JSAPIWrapper.cpp @@ -43,7 +43,7 @@ const ossia::destination_t& ExecStateWrapper::find_address(const QString& str) { // Address looks like '/foo/bar' // Try to find automatically in all devices - auto node = devices.find_node(str.toStdString()); + auto node = find_node(devices, str.toStdString()); if(node) { if(auto addr = node->get_parameter()) @@ -56,11 +56,11 @@ const ossia::destination_t& ExecStateWrapper::find_address(const QString& str) // Split in devices auto dev = ossia::find_if( - devices.exec_devices(), [devname = str.mid(0, d).toStdString()](const auto& dev) { - return dev->get_name() == devname; - }); + devices, [devname = str.mid(0, d).toStdString()](const auto& dev) { + return dev->get_name() == devname; + }); - if(dev != devices.exec_devices().end()) + if(dev != devices.end()) { if(d == str.size() - 1) { @@ -89,7 +89,7 @@ const ossia::destination_t& ExecStateWrapper::find_address(const QString& str) return it->second; } - static ossia::destination_t bad_dest; + static const ossia::destination_t bad_dest; return bad_dest; } @@ -101,8 +101,7 @@ QVariant ExecStateWrapper::read(const QString& address) QVariantMap mv; bool unique = ossia::apply_to_destination( - addr, devices.exec_devices(), - [&](ossia::net::parameter_base* addr, bool unique) { + addr, devices, [&](ossia::net::parameter_base* addr, bool unique) { if(unique) { var = addr->value().apply(ossia::qt::ossia_to_qvariant{}); @@ -112,8 +111,7 @@ QVariant ExecStateWrapper::read(const QString& address) mv[QString::fromStdString(addr->get_node().osc_address())] = addr->value().apply(ossia::qt::ossia_to_qvariant{}); } - }, - ossia::do_nothing_for_nodes{}); + }, ossia::do_nothing_for_nodes{}); if(unique) return var; else @@ -130,12 +128,20 @@ void ExecStateWrapper::write(const QString& address, const QVariant& value) m_port_cache.get_data().emplace_back(ossia::qt::qt_to_ossia{}(value)); ossia::apply_to_destination( - addr, devices.exec_devices(), - [&](ossia::net::parameter_base* addr, bool unique) { - devices.insert(*addr, m_port_cache); - }, - ossia::do_nothing_for_nodes{}); + addr, devices, [&](ossia::net::parameter_base* addr, bool unique) { + on_push(*addr, m_port_cache); + }, ossia::do_nothing_for_nodes{}); } } +ossia::net::node_base* +ExecStateWrapper::find_node(DeviceCache& devices, std::string_view name) +{ + for(auto dev : devices) + { + if(auto res = ossia::net::find_node(dev->get_root_node(), name)) + return res; + } + return nullptr; +} } diff --git a/src/plugins/score-plugin-js/JS/Executor/JSAPIWrapper.hpp b/src/plugins/score-plugin-js/JS/Executor/JSAPIWrapper.hpp index 028516b464..a7afbbb8a9 100644 --- a/src/plugins/score-plugin-js/JS/Executor/JSAPIWrapper.hpp +++ b/src/plugins/score-plugin-js/JS/Executor/JSAPIWrapper.hpp @@ -9,19 +9,32 @@ #include #include +namespace Device +{ +class DeviceList; +} namespace ossia { struct execution_state; +namespace net +{ +class device_base; +} } namespace JS { +using DeviceCache = ossia::small_vector; +using DevicePushFunction + = smallfun::function; + class ExecStateWrapper : public QObject { W_OBJECT(ExecStateWrapper) public: - ExecStateWrapper(ossia::execution_state& state, QObject* parent) + ExecStateWrapper(const DeviceCache& state, DevicePushFunction push, QObject* parent) : QObject{parent} , devices{state} + , on_push{std::move(push)} { } ~ExecStateWrapper() override; @@ -35,12 +48,43 @@ class ExecStateWrapper : public QObject void system(const QString& code) W_SIGNAL(system, code); + static ossia::net::node_base* find_node(DeviceCache& devices, std::string_view name); + const ossia::destination_t& find_address(const QString&); + + DeviceCache devices; + +private: + DevicePushFunction on_push; + + ossia::hash_map m_address_cache; + ossia::value_port m_port_cache; + // TODO share cache +}; + +/* +class EditStateWrapper : public QObject +{ + W_OBJECT(EditStateWrapper) +public: + EditStateWrapper(Device::DeviceList& state, QObject* parent) + : QObject{parent} + , devices{state} + { + } + ~EditStateWrapper() override; + + QVariant read(const QString& address); + W_SLOT(read); + void write(const QString& address, const QVariant& value); + W_SLOT(write); + private: - ossia::execution_state& devices; + Device::DeviceList& devices; const ossia::destination_t& find_address(const QString&); ossia::hash_map m_address_cache; ossia::value_port m_port_cache; - // TODO share cash across + // TODO share cache }; +*/ } diff --git a/src/plugins/score-plugin-js/JS/Qml/AddressItem.cpp b/src/plugins/score-plugin-js/JS/Qml/AddressItem.cpp new file mode 100644 index 0000000000..30f5213778 --- /dev/null +++ b/src/plugins/score-plugin-js/JS/Qml/AddressItem.cpp @@ -0,0 +1,193 @@ +#include "AddressItem.hpp" + +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#include + +W_OBJECT_IMPL(JS::AddressItem) +W_OBJECT_IMPL(JS::AddressSource) + +namespace JS +{ + +AddressItem::AddressItem(QQuickItem* parent) + : QQuickItem{parent} +{ + connect(this, &QQuickItem::parentChanged, this, [this](QQuickItem* parent) { + if(!parent) + return; + auto ctx = qmlContext(parent); + m_devices = qobject_cast( + ctx->engine()->globalObject().property("Device").toQObject()); + on_addressChanged(m_address); + }); + + connect(this, &AddressItem::addressChanged, this, &AddressItem::on_addressChanged); +} + +void AddressItem::on_addressChanged(const QString& addr) +{ + if(!m_devices) + return; + auto node = m_devices->find(addr); + if(!node) + return; + auto param = node->get_parameter(); + if(!param) + return; + switch(param->get_value_type()) + { + case ossia::val_type::FLOAT: + break; + } +} + +AddressItem::~AddressItem() { } + +AddressSource::AddressSource(QObject* parent) + : QObject{parent} +{ + connect(this, &AddressSource::addressChanged, this, &AddressSource::on_addressChanged); + connect(this, &AddressSource::sendUpdatesChanged, this, [this] { + clearQMLCallbacks(); + rebuild(); + }); + connect(this, &AddressSource::receiveUpdatesChanged, this, [this] { + on_addressChanged(address()); + }); +} + +AddressSource::~AddressSource() +{ + clearNetworkCallbacks(); +} + +void AddressSource::init() +{ + if(m_devices) + return; + if(!parent()) + return; + auto ctx = qmlContext(parent()); + m_devices = qobject_cast( + ctx->engine()->globalObject().property("Device").toQObject()); +} + +void AddressSource::on_addressChanged(const QString& addr) +{ + if(!m_devices) + { + init(); + if(!m_devices) + return; + } + + clearNetworkCallbacks(); + + // Set new callback + // First find the node + auto node = m_devices->find(addr); + if(!node) + return; + m_param = node->get_parameter(); + if(!m_param) + return; + + // Connect to it + if(m_receiveUpdates) + { + m_callback = m_param->add_callback([ptr = QPointer{this}](const ossia::value& v) { + if(!ptr) + return; + + ossia::qt::run_async(QCoreApplication::instance(), [ptr, v]() mutable { + if(ptr) + { + ptr->on_newNetworkValue(std::move(v)); + } + }); + }); + } + + // Get a notification if the node gets removed + m_device = &node->get_device(); + m_device->on_parameter_removing.connect<&AddressSource::on_parameterRemoving>(this); +} + +void AddressSource::on_parameterRemoving(const ossia::net::parameter_base& v) +{ + clearNetworkCallbacks(); +} + +void AddressSource::on_newUIValue() +{ + // Avoir signal loops + if(m_writingValue) + return; + if(m_param) + { + m_param->push_value(ossia::qt::qt_to_ossia{}(m_targetProperty.read())); + } +} + +void AddressSource::on_newNetworkValue(const ossia::value& v) +{ + auto vv = v.apply(ossia::qt::ossia_to_qvariant{}); + m_writingValue = true; + m_targetProperty.write(vv); + m_writingValue = false; +} + +void AddressSource::setTarget(const QQmlProperty& prop) +{ + clearQMLCallbacks(); + + m_targetProperty = prop; + + rebuild(); +} + +void AddressSource::clearQMLCallbacks() +{ + if(auto obj = m_targetProperty.object()) + obj->disconnect(this); +} + +void AddressSource::rebuild() +{ + if(m_sendUpdates) + { + if(m_targetProperty.hasNotifySignal()) + { + m_targetProperty.connectNotifySignal(this, SLOT(on_newUIValue())); + } + } + on_addressChanged(m_address); +} + +void AddressSource::clearNetworkCallbacks() +{ + // Remove old callback + if(m_callback && m_device && m_param) + { + m_param->remove_callback(*m_callback); + m_callback.reset(); + + m_device->on_parameter_removing.disconnect<&AddressSource::on_parameterRemoving>( + *this); + } + + m_device = nullptr; + m_param = nullptr; +} +} diff --git a/src/plugins/score-plugin-js/JS/Qml/AddressItem.hpp b/src/plugins/score-plugin-js/JS/Qml/AddressItem.hpp new file mode 100644 index 0000000000..5396c7cd33 --- /dev/null +++ b/src/plugins/score-plugin-js/JS/Qml/AddressItem.hpp @@ -0,0 +1,83 @@ +#pragma once +#include +#include + +#include +#include +#include + +#include + +#include +namespace ossia::net +{ +class device_base; +class parameter_base; +} +namespace JS +{ +class DeviceContext; +struct AddressItem : public QQuickItem +{ + W_OBJECT(AddressItem) +public: + explicit AddressItem(QQuickItem* parent = nullptr); + ~AddressItem(); + + void on_addressChanged(const QString& addr); + + INLINE_PROPERTY_CREF(QString, address, = "", address, setAddress, addressChanged) + +private: + JS::DeviceContext* m_devices{}; +}; + +struct AddressSource + : public QObject + , public QQmlPropertyValueSource + , public Nano::Observer +{ + W_OBJECT(AddressSource) + W_INTERFACE(QQmlPropertyValueSource) + QML_ELEMENT + +public: + explicit AddressSource(QObject* parent = nullptr); + ~AddressSource(); + + void init(); + + void setTarget(const QQmlProperty& prop) override; + + void clearNetworkCallbacks(); + void clearQMLCallbacks(); + void on_addressChanged(const QString& addr); + void on_newNetworkValue(const ossia::value& v); + void on_newUIValue(); + W_SLOT(on_newUIValue); + + void on_parameterRemoving(const ossia::net::parameter_base& v); + + INLINE_PROPERTY_CREF(QString, address, = "", address, setAddress, addressChanged) + + // Send updates from the GUI to the network + INLINE_PROPERTY_CREF( + bool, sendUpdates, = true, sendUpdates, setSendUpdates, sendUpdatesChanged) + + // Receive updates from the network to the GUI + INLINE_PROPERTY_CREF( + bool, receiveUpdates, = true, receiveUpdates, setReceiveUpdates, + receiveUpdatesChanged) + +private: + void rebuild(); + + QQmlProperty m_targetProperty; + JS::DeviceContext* m_devices{}; + + ossia::net::device_base* m_device{}; + ossia::net::parameter_base* m_param{}; + std::optional::iterator> m_callback; + bool m_writingValue{}; +}; +} diff --git a/src/plugins/score-plugin-js/JS/Qml/DeviceContext.cpp b/src/plugins/score-plugin-js/JS/Qml/DeviceContext.cpp new file mode 100644 index 0000000000..ed20da195e --- /dev/null +++ b/src/plugins/score-plugin-js/JS/Qml/DeviceContext.cpp @@ -0,0 +1,97 @@ +#include "DeviceContext.hpp" + +#include + +#include + +#include + +#include + +#include + +W_OBJECT_IMPL(JS::DeviceContext) + +namespace JS +{ +DeviceContext::DeviceContext(QObject* parent) + : QObject{parent} +{ +} + +DeviceContext::~DeviceContext() { } + +bool DeviceContext::init() +{ + if(!m_impl) + { + [[unlikely]]; + + auto& ctx = score::GUIAppContext(); + auto doc = ctx.currentDocument(); + if(!doc) + return false; + + auto m_devices = doc->findPlugin(); + if(!m_devices) + return false; + + DeviceCache cache; + m_devices->list().apply([&cache](Device::DeviceInterface& iface) { + if(auto ossia = iface.getDevice()) + cache.push_back(ossia); + }); + + m_impl = new ExecStateWrapper{ + cache, [](ossia::net::parameter_base& param, const ossia::value_port& v) { + if(v.get_data().empty()) + return; + auto& last = v.get_data().back().value; + param.push_value(last); + }, this}; + } + return true; +} + +QVariant DeviceContext::read(const QString& address) +{ + if(!init()) + { + [[unlikely]]; + return {}; + } + return m_impl->read(address); +} + +void DeviceContext::write(const QString& address, const QVariant& value) +{ + if(!init()) + { + [[unlikely]]; + return; + } + return m_impl->write(address, value); +} + +ossia::net::node_base* DeviceContext::find(const QString& addr) +{ + if(!init()) + { + [[unlikely]]; + return nullptr; + } + auto res = m_impl->find_address(addr); + if(auto p = res.target()) + { + return &(*p)->get_node(); + } + else if(auto n = res.target()) + { + return *n; + } + else + { + return nullptr; + } +} +} diff --git a/src/plugins/score-plugin-js/JS/Qml/DeviceContext.hpp b/src/plugins/score-plugin-js/JS/Qml/DeviceContext.hpp new file mode 100644 index 0000000000..6756a6fab8 --- /dev/null +++ b/src/plugins/score-plugin-js/JS/Qml/DeviceContext.hpp @@ -0,0 +1,33 @@ +#pragma once +#include + +#include + +namespace ossia::net +{ +class node_base; +} + +namespace JS +{ +class ExecStateWrapper; +class DeviceContext : public QObject +{ + W_OBJECT(DeviceContext) + +public: + explicit DeviceContext(QObject* parent = nullptr); + ~DeviceContext(); + + bool init(); + ossia::net::node_base* find(const QString& addr); + + QVariant read(const QString& address); + W_SLOT(read); + + void write(const QString& address, const QVariant& value); + W_SLOT(write); + + ExecStateWrapper* m_impl{}; +}; +} diff --git a/src/plugins/score-plugin-js/JS/Qml/EditContext.cpp b/src/plugins/score-plugin-js/JS/Qml/EditContext.cpp index 55bdc1df55..a673973587 100644 --- a/src/plugins/score-plugin-js/JS/Qml/EditContext.cpp +++ b/src/plugins/score-plugin-js/JS/Qml/EditContext.cpp @@ -55,6 +55,20 @@ QObject* EditJsContext::find(QString p) return nullptr; } +QObject* EditJsContext::findByLabel(QString p) +{ + auto doc = document(); + const auto meta = doc->findChildren(); + for(auto m : meta) + { + if(m->getLabel() == p) + { + return m->parent(); + } + } + return nullptr; +} + QObject* EditJsContext::document() { return score::GUIAppContext().documents.currentDocument(); diff --git a/src/plugins/score-plugin-js/JS/Qml/EditContext.hpp b/src/plugins/score-plugin-js/JS/Qml/EditContext.hpp index ae9d35c236..4bc268795a 100644 --- a/src/plugins/score-plugin-js/JS/Qml/EditContext.hpp +++ b/src/plugins/score-plugin-js/JS/Qml/EditContext.hpp @@ -179,12 +179,18 @@ class EditJsContext : public QObject QObject* find(QString p); W_SLOT(find) + QObject* findByLabel(QString p); + W_SLOT(findByLabel) + QObject* document(); W_SLOT(document) /// Execution /// + void play(); + W_SLOT(play, ()) + void play(QObject* obj); - W_SLOT(play) + W_SLOT(play, (QObject*)) void stop(); W_SLOT(stop) diff --git a/src/plugins/score-plugin-js/JS/Qml/EditContext.ossia.cpp b/src/plugins/score-plugin-js/JS/Qml/EditContext.ossia.cpp index 8a13ae753f..75698c2c91 100644 --- a/src/plugins/score-plugin-js/JS/Qml/EditContext.ossia.cpp +++ b/src/plugins/score-plugin-js/JS/Qml/EditContext.ossia.cpp @@ -32,6 +32,14 @@ QString EditJsContext::valueType(QObject* obj) return ret; } +void EditJsContext::play() +{ + auto plug + = score::GUIAppContext().findGuiApplicationPlugin(); + if(plug) + plug->execution().request_play_global(true); +} + void EditJsContext::play(QObject* obj) { auto plug = score::GUIAppContext() diff --git a/src/plugins/score-plugin-js/JS/Qml/PortSource.cpp b/src/plugins/score-plugin-js/JS/Qml/PortSource.cpp new file mode 100644 index 0000000000..b00ebc6187 --- /dev/null +++ b/src/plugins/score-plugin-js/JS/Qml/PortSource.cpp @@ -0,0 +1,117 @@ +#include "PortSource.hpp" + +#include +#include + +#include + +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#include + +W_OBJECT_IMPL(JS::PortSource) +namespace JS +{ + +PortSource::PortSource(QObject* parent) + : QObject{parent} +{ + connect(this, &PortSource::processChanged, this, [this] { rebuild(); }); + connect(this, &PortSource::portChanged, this, [this] { rebuild(); }); +} + +PortSource::~PortSource() { } + +void PortSource::setTarget(const QQmlProperty& prop) +{ + m_targetProperty = prop; + if(m_targetProperty.hasNotifySignal()) + { + m_targetProperty.connectNotifySignal(this, SLOT(on_newUIValue())); + } +} + +void PortSource::on_newUIValue() +{ + // Avoir signal loops + // if(m_writingValue) + // return; + if(m_inlet) + { + qDebug() << m_inlet->name() << m_targetProperty.read(); + m_inlet->setValue(ossia::qt::qt_to_ossia{}(m_targetProperty.read())); + } +} + +void PortSource::rebuild() +{ + if(m_inlet) + { + // TODO + } + + m_inlet = nullptr; + + if(!parent()) + return; + + auto doc = score::GUIAppContext().documents.currentDocument(); + if(!doc) + return; + + auto& model = doc->model().modelDelegate(); + auto processes + = model.findChildren(Qt::FindChildrenRecursively); + + Process::ProcessModel* process = nullptr; + for(auto proc : processes) + { + if(proc->metadata().getName() == m_process) + { + process = proc; + break; + } + } + if(!process) + return; + + if(m_port.typeId() == QMetaType::Type::QString) + { + auto port_name = m_port.value(); + m_inlet = process->findChild(port_name); + if(!m_inlet) + { + auto& inls = process->inlets(); + for(auto& inl : inls) + { + qDebug() << inl->name() << " == " << port_name; + if(inl->name() == port_name) + { + m_inlet = qobject_cast(inl); + break; + } + } + } + } + else if(m_port.typeId() == QMetaType::Type::Int) + { + int i = m_port.toInt(); + auto& inls = process->inlets(); + if(i >= 0 && i < inls.size()) + m_inlet = qobject_cast(inls[i]); + } + + if(!m_inlet) + return; +} +} diff --git a/src/plugins/score-plugin-js/JS/Qml/PortSource.hpp b/src/plugins/score-plugin-js/JS/Qml/PortSource.hpp new file mode 100644 index 0000000000..ff8f362c3e --- /dev/null +++ b/src/plugins/score-plugin-js/JS/Qml/PortSource.hpp @@ -0,0 +1,41 @@ +#pragma once +#include +#include +#include + +#include + +namespace Process +{ +class ControlInlet; +} + +namespace JS +{ + +struct PortSource + : public QObject + , public QQmlPropertyValueSource +{ + W_OBJECT(PortSource) + W_INTERFACE(QQmlPropertyValueSource) + QML_ELEMENT + +public: + explicit PortSource(QObject* parent = nullptr); + ~PortSource(); + + void setTarget(const QQmlProperty& prop) override; + void on_newUIValue(); + W_SLOT(on_newUIValue); + + INLINE_PROPERTY_CREF(QString, process, = "", process, setProcess, processChanged) + INLINE_PROPERTY_CREF(QVariant, port, = "", port, setPort, portChanged) + +private: + void rebuild(); + QQmlProperty m_targetProperty; + QPointer m_inlet{}; +}; + +} diff --git a/src/plugins/score-plugin-js/score_plugin_js.cpp b/src/plugins/score-plugin-js/score_plugin_js.cpp index 5e903d3e3f..5633b8357e 100644 --- a/src/plugins/score-plugin-js/score_plugin_js.cpp +++ b/src/plugins/score-plugin-js/score_plugin_js.cpp @@ -2,17 +2,20 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include "score_plugin_js.hpp" -#include "JS/Commands/JSCommandFactory.hpp" - #include #include +#include +#include #include #include #include #include #include +#include +#include #include +#include #include #include @@ -20,9 +23,7 @@ #include #include -#include -#include -#include +#include #include #include @@ -73,6 +74,10 @@ score_plugin_js::score_plugin_js() qmlRegisterType("Score", 1, 0, "LineEdit"); qmlRegisterType("Score", 1, 0, "Script"); + qmlRegisterType("Score", 1, 0, "AddressItem"); + qmlRegisterType("Score", 1, 0, "AddressSource"); + qmlRegisterType("Score", 1, 0, "PortSource"); + qRegisterMetaType>(); qRegisterMetaType(); @@ -95,6 +100,12 @@ std::vector score_plugin_js::factories( FW>(ctx, key); } +score::GUIApplicationPlugin* +score_plugin_js::make_guiApplicationPlugin(const score::GUIApplicationContext& app) +{ + return new JS::ApplicationPlugin{app}; +} + std::pair score_plugin_js::make_commands() { using namespace JS; diff --git a/src/plugins/score-plugin-js/score_plugin_js.hpp b/src/plugins/score-plugin-js/score_plugin_js.hpp index 4280bc0353..88fba2292d 100644 --- a/src/plugins/score-plugin-js/score_plugin_js.hpp +++ b/src/plugins/score-plugin-js/score_plugin_js.hpp @@ -14,6 +14,7 @@ class score_plugin_js final : public score::Plugin_QtInterface + , public score::ApplicationPlugin_QtInterface , public score::FactoryInterface_QtInterface , public score::CommandFactory_QtInterface { @@ -23,6 +24,9 @@ class score_plugin_js final virtual ~score_plugin_js(); private: + score::GUIApplicationPlugin* + make_guiApplicationPlugin(const score::GUIApplicationContext& app) override; + // Process & inspector std::vector factories( const score::ApplicationContext& ctx, diff --git a/src/plugins/score-plugin-remotecontrol/RemoteControl/DocumentPlugin.cpp b/src/plugins/score-plugin-remotecontrol/RemoteControl/DocumentPlugin.cpp index cefca62806..0a91a44588 100644 --- a/src/plugins/score-plugin-remotecontrol/RemoteControl/DocumentPlugin.cpp +++ b/src/plugins/score-plugin-remotecontrol/RemoteControl/DocumentPlugin.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include