Loading...
Searching...
No Matches
DropOnCable.hpp
1#pragma once
2
3#include <Process/Commands/EditPort.hpp>
4#include <Process/Commands/LoadPresetCommandFactory.hpp>
5#include <Process/Dataflow/CableItem.hpp>
6#include <Process/Dataflow/NodeItem.hpp>
7#include <Process/Drop/ProcessDropHandler.hpp>
8#include <Process/Process.hpp>
9#include <Process/ProcessContext.hpp>
10#include <Process/Dataflow/PortItem.hpp>
11
12#include <Scenario/Commands/CommandAPI.hpp>
13#include <Scenario/Commands/Interval/AddProcessToInterval.hpp>
14#include <Scenario/Document/Interval/IntervalModel.hpp>
15#include <Scenario/Document/ScenarioDocument/ProcessCreation.hpp>
16
17#include <score/application/GUIApplicationContext.hpp>
18#include <score/command/Dispatchers/CommandDispatcher.hpp>
19#include <score/command/Dispatchers/RuntimeDispatcher.hpp>
20#include <score/document/DocumentContext.hpp>
21
22namespace Dataflow
23{
24void replaceCable(
25 const score::DocumentContext& ctx, const Process::Cable& currentCable,
26 const Process::Port& newPort);
27}
28namespace Scenario
29{
30class DropOnCable : public QObject
31{
32 // Only things that make sense to drop: processes and presets (maybe layers?)
33 // Find in which interval we wish to drop.
34 // If both ends are in the same hierarchy level we drop the process in the interval
35
36public:
37 const ScenarioDocumentModel& sm;
38 const Process::Context& m_context;
39 const Dataflow::CableItem& item;
40 const Process::Cable& cable = item.model();
41 const Process::Port& source = cable.source().find(m_context);
42 const Process::Port& sink = cable.sink().find(m_context);
43
44 Scenario::IntervalModel* m_interval{};
45
47 const Dataflow::CableItem& item, const ScenarioDocumentModel& sm,
48 const Process::Context& m_context)
49 : sm{sm}
50 , m_context{m_context}
51 , item{item}
52 {
53 }
54
55 void createPreset(QPointF pos, const QByteArray& presetData)
56 {
57 auto& procs = m_context.app.interfaces<Process::ProcessFactoryList>();
58 if(auto preset = Process::Preset::fromJson(procs, presetData))
59 {
60 Scenario::loadPresetInCable(m_context, sm, *preset, cable);
61 }
62 }
63
64 void createProcess(QPointF pos, const Process::ProcessDropHandler::ProcessDrop& proc)
65 {
66 Scenario::createProcessInCable(
67 m_context, sm, proc.creation, proc.duration, proc.setup, cable);
68 }
69
70 void drop(const QPointF& pos, const QMimeData& mime)
71 {
72 // FIXME put it closer to the source or sink depending on drop position
73 auto source_itv = Scenario::closestParentInterval(&source);
74 //auto sink_itv = Scenario::closestParentInterval(&sink);
75 if(!source_itv)
76 return;
77
78 m_interval = source_itv;
79
80 // Something tricky here is that we have to separate mime data processing and command execution:
81 // mime data processing has to happen synchronously as it gets deleted after the CableItem::dropEvent function returns.
82 // On the other hand, we cannot run the command synchronously as it may delete the cable, which causes
83 // the cableitem to be deleted too and thus it cannot finish the dropEvent without crashing.
84 const auto& handlers = m_context.app.interfaces<Process::ProcessDropHandlerList>();
85
86 if(mime.hasFormat(score::mime::layerdata()))
87 {
88 // TODO
89 }
90 else if(mime.hasFormat(score::mime::processpreset()))
91 {
92 const auto presetData = mime.data(score::mime::processpreset());
93
94 QMetaObject::invokeMethod(this, [pos, presetData, this]() {
95 createPreset(pos, presetData);
96 }, Qt::QueuedConnection);
97 }
98 else if(auto res = handlers.getDrop(mime, m_context); !res.empty())
99 {
100 if(res.size() >= 1)
101 {
102 QMetaObject::invokeMethod(this, [pos, proc = res.front(), this]() {
103 createProcess(pos, proc);
104 }, Qt::QueuedConnection);
105 }
106 }
107 else if(mime.hasFormat(score::mime::port()))
108 {
109 auto base_port = Dataflow::PortItem::clickedPort;
110 if(base_port)
111 {
112 auto& new_port = base_port->port();
113 QMetaObject::invokeMethod(this, [&new_port, this]() {
114 Dataflow::replaceCable(this->m_context, cable, new_port);
115 }, Qt::QueuedConnection);
116 }
117 }
118 else if(mime.hasUrls())
119 {
120 // TODO
121 }
122 else
123 {
124 qDebug()<<mime.formats();
125 }
126 }
127};
128
129class DropOnNode : public QObject
130{
131public:
132 const ScenarioDocumentModel& sm;
133 const Process::Context& m_context;
134 const Process::NodeItem& item;
135
136 Scenario::IntervalModel* m_interval{};
137
139 const Process::NodeItem& item, const ScenarioDocumentModel& sm,
140 const Process::Context& m_context)
141 : sm{sm}
142 , m_context{m_context}
143 , item{item}
144 {
145 }
146
147 void createPreset(const QByteArray& presetData)
148 {
149 auto& old = item.model();
150 auto& procs = m_context.app.interfaces<Process::ProcessFactoryList>();
151 if(auto preset = Process::Preset::fromJson(procs, presetData))
152 {
153 if(preset->key.key == old.concreteKey())
154 {
155 if(old.effect() == preset->data)
156 {
157 // Fast path for loading the preset for e.g. same VSTs, JS, shaders etc.
158 auto& load_preset_ifaces
159 = m_context.app.interfaces<Process::LoadPresetCommandFactoryList>();
160
161 auto cmd = load_preset_ifaces.make(
162 &Process::LoadPresetCommandFactory::make, old, *preset, m_context);
163 CommandDispatcher<> disp{m_context.commandStack};
164 disp.submit(cmd);
165 return;
166 }
167 }
171 if(auto p = m.loadProcessFromPreset(*m_interval, *preset, old.position()))
172 {
173 linkNewProcess(p, m);
174 m.removeProcess(*m_interval, old.id());
175 m.commit();
176 }
177 }
178 }
179
180 void createProcess(const Process::ProcessDropHandler::ProcessDrop& proc)
181 {
182 auto& old = item.model();
186 if(auto p = m.createProcessInNewSlot(*m_interval, proc.creation, old.position()))
187 {
188 if(proc.setup)
189 proc.setup(*p, disp);
190
191 // Give the same connections that the previous process had
192 linkNewProcess(p, m);
193
194 // Remove the previous process
195 m.removeProcess(*m_interval, old.id());
196
197 m.commit();
198 }
199 }
200
201 void linkNewProcess(Process::ProcessModel* p, Scenario::Command::Macro& m)
202 {
203 auto& old = item.model();
204 if(p->inlets().size() > 0)
205 {
206 const auto dst = p->inlets()[0];
207 const auto type = dst->type();
208
209 if(old.inlets().size() > 0)
210 {
211 auto& old_dst = old.inlets()[0];
212 if(old_dst->type() == type && !qobject_cast<Process::ControlInlet*>(dst)
213 && !qobject_cast<Process::ControlInlet*>(old_dst))
214 {
215 for(auto& edge : old.inlets()[0]->cables())
216 {
217 auto& cable = edge.find(m_context);
218 auto& src = cable.source().find(m_context);
219 if(src.type() == type)
220 {
221 m.createCable(sm, src, *dst);
222 }
223 }
224 m.setProperty<Process::Port::p_address>(*dst, old_dst->address());
225 }
226 }
227 }
228
229 if(p->outlets().size() > 0)
230 {
231 const auto src = p->outlets()[0];
232 const auto type = src->type();
233
234 if(old.outlets().size() > 0)
235 {
236 auto& old_src = old.outlets()[0];
237 if(old_src->type() == type && !qobject_cast<Process::ControlInlet*>(src)
238 && !qobject_cast<Process::ControlInlet*>(old_src))
239 {
240 for(auto& edge : old.outlets()[0]->cables())
241 {
242 auto& cable = edge.find(m_context);
243 auto& dst = cable.sink().find(m_context);
244 if(dst.type() == type)
245 {
246 m.createCable(sm, *src, dst);
247 }
248 }
249 m.setProperty<Process::Port::p_address>(*src, old_src->address());
250 if(type == Process::PortType::Audio)
251 {
252 auto old_audio_src = safe_cast<Process::AudioOutlet*>(old_src);
253 auto audio_src = safe_cast<Process::AudioOutlet*>(src);
254 m.setProperty<Process::AudioOutlet::p_propagate>(
255 *audio_src, old_audio_src->propagate());
256 }
257
258 src->setAddress(old_src->address());
259 }
260 }
261 }
262 }
263
264 void drop(const QMimeData& mime)
265 {
266 // FIXME drop in nodal vs drop in scenario
267 auto& model = item.model();
268 m_interval = qobject_cast<Scenario::IntervalModel*>(model.parent());
269 if(!m_interval)
270 return;
271
272 // Something tricky here is that we have to separate mime data processing and command execution:
273 // mime data processing has to happen synchronously as it gets deleted after the NodeItem::dropEvent function returns.
274 // On the other hand, we cannot run the command synchronously as it may delete the cable, which causes
275 // the cableitem to be deleted too and thus it cannot finish the dropEvent without crashing.
276 const auto& handlers = m_context.app.interfaces<Process::ProcessDropHandlerList>();
277
278 if(mime.hasFormat(score::mime::layerdata()))
279 {
280 // TODO
281 }
282 else if(mime.hasFormat(score::mime::processpreset()))
283 {
284 const auto presetData = mime.data(score::mime::processpreset());
285 QMetaObject::invokeMethod(this, [presetData, this]() {
286 createPreset(presetData);
287 }, Qt::QueuedConnection);
288 }
289 else if(auto res = handlers.getDrop(mime, m_context); !res.empty())
290 {
291 if(res.size() >= 1)
292 {
293 QMetaObject::invokeMethod(this, [proc = res.front(), this]() {
294 createProcess(proc);
295 }, Qt::QueuedConnection);
296 }
297 }
298 else if(mime.hasUrls())
299 {
300 // TODO
301 }
302 }
303};
304}
The CommandDispatcher class.
Definition CommandDispatcher.hpp:13
Definition CableItem.hpp:37
Definition Cable.hpp:38
Definition LoadPresetCommandFactory.hpp:32
Definition NodeItem.hpp:40
Definition Port.hpp:103
Definition ProcessDropHandler.hpp:75
Definition ProcessList.hpp:10
The Process class.
Definition score-lib-process/Process/Process.hpp:61
Definition AddProcessToInterval.hpp:72
Definition CommandAPI.hpp:28
Definition DropOnCable.hpp:31
Definition DropOnCable.hpp:130
Definition IntervalModel.hpp:50
Definition ScenarioDocumentModel.hpp:29
auto make(Fun f, Args &&... args) const noexcept
Apply a function on the correct factory according to a set of parameter.
Definition InterfaceList.hpp:189
Main plug-in of score.
Definition score-plugin-dataflow/Dataflow/PortItem.hpp:13
Definition ProcessContext.hpp:12
Definition ProcessDropHandler.hpp:29
Definition RuntimeDispatcher.hpp:14
Definition DocumentContext.hpp:18