Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/plugins/score-plugin-js/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ set(HDRS
"${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/QmlProcess.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/TextureSource.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/Utils.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/ViewContext.hpp"

"${CMAKE_CURRENT_SOURCE_DIR}/JS/ApplicationPlugin.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/DocumentPlugin.hpp"
Expand Down Expand Up @@ -83,6 +84,7 @@ set(SRCS
"${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/TextureSource.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/ValueTypes.Qt6.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/Utils.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/Qml/ViewContext.cpp"

"${CMAKE_CURRENT_SOURCE_DIR}/JS/ApplicationPlugin.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/JS/DocumentPlugin.cpp"
Expand Down
4 changes: 4 additions & 0 deletions src/plugins/score-plugin-js/JS/ApplicationPlugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <JS/Qml/DeviceContext.hpp>
#include <JS/Qml/EditContext.hpp>
#include <JS/Qml/Utils.hpp>
#include <JS/Qml/ViewContext.hpp>
#include <Library/LibrarySettings.hpp>
#include <LocalTree/LocalTreeDocumentPlugin.hpp>

Expand Down Expand Up @@ -43,6 +44,7 @@ ApplicationPlugin::ApplicationPlugin(const score::GUIApplicationContext& ctx)
m_consoleEngine.globalObject().setProperty(
"Library", m_consoleEngine.newQObject(new JsLibrary));
m_consoleEngine.globalObject().setProperty("Device", m_consoleEngine.newQObject(new DeviceContext{m_consoleEngine}));
m_consoleEngine.globalObject().setProperty("View", m_consoleEngine.newQObject(new JsViewContext));
connect(&m_consoleEngine, &QQmlEngine::exit, this, [&] {
for(auto& doc : score::GUIAppContext().docManager.documents())
doc->commandStack().markCurrentIndexAsSaved();
Expand Down Expand Up @@ -70,6 +72,8 @@ ApplicationPlugin::ApplicationPlugin(const score::GUIApplicationContext& ctx)
"System", m_scriptProcessUIEngine.newQObject(new JsSystem));
m_scriptProcessUIEngine.globalObject().setProperty(
"Library", m_scriptProcessUIEngine.newQObject(new JsLibrary));
m_scriptProcessUIEngine.globalObject().setProperty(
"View", m_scriptProcessUIEngine.newQObject(new JsViewContext));

// Command-line option parsing
QCommandLineParser parser;
Expand Down
189 changes: 189 additions & 0 deletions src/plugins/score-plugin-js/JS/Qml/ViewContext.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
#include <JS/Qml/ViewContext.hpp>

#include <Process/Dataflow/NodeItem.hpp>
#include <Process/Process.hpp>

#include <Scenario/Document/Interval/IntervalModel.hpp>
#include <Scenario/Document/Interval/IntervalPresenter.hpp>
#include <Scenario/Document/Interval/LayerData.hpp>
#include <Scenario/Document/Interval/SlotPresenter.hpp>
#include <Scenario/Document/ScenarioDocument/ScenarioDocumentPresenter.hpp>
#include <Scenario/Document/ScenarioDocument/ScenarioDocumentView.hpp>
#include <Scenario/Document/ScenarioDocument/SnapshotAction.hpp>

#include <score/application/GUIApplicationContext.hpp>
#include <score/document/DocumentContext.hpp>
#include <score/document/DocumentInterface.hpp>

#include <core/document/Document.hpp>
#include <core/document/DocumentView.hpp>

#include <QApplication>
#include <QGuiApplication>
#include <QPixmap>
#include <QScreen>
#include <QWidget>

#include <wobjectimpl.h>
W_OBJECT_IMPL(JS::JsViewContext)

namespace JS
{

const score::DocumentContext* JsViewContext::ctx()
{
return score::GUIAppContext().currentDocument();
}

Scenario::ScenarioDocumentView* JsViewContext::view()
{
auto doc = ctx();
if(!doc)
return nullptr;
// No GUI (e.g. --no-gui): there is no view.
auto dv = doc->document.view();
if(!dv)
return nullptr;
return qobject_cast<Scenario::ScenarioDocumentView*>(&dv->viewDelegate());
}

Scenario::ScenarioDocumentPresenter* JsViewContext::pres()
{
auto doc = ctx();
if(!doc)
return nullptr;
// Returns nullptr when there is no presenter (e.g. --no-gui).
return score::IDocument::try_presenterDelegate<Scenario::ScenarioDocumentPresenter>(
doc->document);
}

bool JsViewContext::grabScene(QString path)
{
auto v = view();
if(!v)
return false;
return !Scenario::renderSceneToSvg(v->scene(), path).isEmpty();
}

bool JsViewContext::grabMainWindow(QString path)
{
auto w = qApp->activeWindow();
if(!w)
return false;
return w->grab().save(path);
}

bool JsViewContext::grabScreen(QString path)
{
auto screen = QGuiApplication::primaryScreen();
if(!screen)
return false;
return screen->grabWindow(0).save(path);
}

void JsViewContext::zoom(double zx, double zy)
{
if(auto v = view())
v->zoom(zx, zy);
}

void JsViewContext::scroll(double dx, double dy)
{
if(auto v = view())
v->scroll(dx, dy);
}

void JsViewContext::setZoomRatio(double r)
{
if(auto p = pres())
p->setZoomRatio(r);
}

void JsViewContext::centerOn(QObject* process)
{
auto p = pres();
auto v = view();
if(!p || !v)
return;
auto* model = qobject_cast<Process::ProcessModel*>(process);
if(!model)
return;
const auto& target = model->id();

QGraphicsItem* found = nullptr;

// Nodal (dataflow) mode: the process is drawn as a NodeItem in the scene.
for(auto* it : v->scene().items())
{
if(auto* node = dynamic_cast<Process::NodeItem*>(it))
{
if(node->id() == target)
{
found = node;
break;
}
}
}

// Temporal mode: walk the displayed interval's slots/layers.
if(!found)
{
if(auto* itv = p->displayedIntervalPresenter())
{
for(const auto& slot : itv->getSlots())
{
if(auto* ls = slot.getLayerSlot())
{
for(const auto& ld : ls->layers)
{
if(ld.model().id() == target && !ld.layers().empty())
{
found = ld.layers().front().container;
break;
}
}
}
if(found)
break;
}
}
}

if(found)
v->view().centerOn(found);
}

void JsViewContext::goToInterval(QObject* interval)
{
auto p = pres();
if(!p)
return;
if(auto itv = qobject_cast<Scenario::IntervalModel*>(interval))
p->setDisplayedInterval(itv);
}

void JsViewContext::fit()
{
if(auto p = pres())
p->setLargeView();
}

void JsViewContext::recenter()
{
if(auto p = pres())
p->recenter();
}

void JsViewContext::setNodal(bool nodal)
{
if(auto p = pres())
p->setNodalMode(nodal);
}

bool JsViewContext::isNodal()
{
if(auto p = pres())
return p->isNodal();
return false;
}
}
66 changes: 66 additions & 0 deletions src/plugins/score-plugin-js/JS/Qml/ViewContext.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#pragma once
#include <score_plugin_js_export.h>

#include <QObject>
#include <QString>

#include <verdigris>

namespace score
{
struct DocumentContext;
}
namespace Scenario
{
class ScenarioDocumentView;
class ScenarioDocumentPresenter;
}

namespace JS
{
//! JS object exposed as `View`: automation of the main scenario view.
//!
//! Every method degrades gracefully when there is no GUI (`--no-gui`): the
//! view/presenter accessors return nullptr and the methods become no-ops.
class SCORE_PLUGIN_JS_EXPORT JsViewContext : public QObject
{
W_OBJECT(JsViewContext)
public:
// Screenshots
bool grabScene(QString path);
W_SLOT(grabScene)
bool grabMainWindow(QString path);
W_SLOT(grabMainWindow)
bool grabScreen(QString path);
W_SLOT(grabScreen)

// Zoom / scroll
void zoom(double zx, double zy);
W_SLOT(zoom)
void scroll(double dx, double dy);
W_SLOT(scroll)
void setZoomRatio(double r);
W_SLOT(setZoomRatio)

// Navigation / focus
void centerOn(QObject* process);
W_SLOT(centerOn)
void goToInterval(QObject* interval);
W_SLOT(goToInterval)
void fit();
W_SLOT(fit)
void recenter();
W_SLOT(recenter)

// Dataflow / temporal mode
void setNodal(bool nodal);
W_SLOT(setNodal)
bool isNodal();
W_SLOT(isNodal)

private:
const score::DocumentContext* ctx();
Scenario::ScenarioDocumentView* view();
Scenario::ScenarioDocumentPresenter* pres();
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,21 @@ void ScenarioDocumentPresenter::recenterNodal()
ossia::visit(vis, m_centralDisplay);
}

void ScenarioDocumentPresenter::recenter()
{
recenterNodal();
}

void ScenarioDocumentPresenter::setNodalMode(bool nodal)
{
// Route through the toolbar action so its checked state stays in sync:
// toggling it triggers on_timelineModeSwitch() -> switchMode().
if(m_timelineAction)
m_timelineAction->setChecked(!nodal);
else
switchMode(nodal);
}

void ScenarioDocumentPresenter::switchMode(bool nodal)
{
const auto mode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ class SCORE_PLUGIN_SCENARIO_EXPORT ScenarioDocumentPresenter final
void stopTimeBar();

bool isNodal() const noexcept;
// Switch between temporal and dataflow (nodal) mode, keeping the toolbar
// action in sync. Exposed for JS view automation.
void setNodalMode(bool nodal);
// Recenter the view (nodal mode). Exposed for JS view automation.
void recenter();

void setAutoScroll(bool);

Expand Down
Loading
Loading