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 QPointer<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 if(!item)
150 return;
151 auto& old = item->model();
152 auto& procs = m_context.app.interfaces<Process::ProcessFactoryList>();
153 if(auto preset = Process::Preset::fromJson(procs, presetData))
154 {
155 if(preset->key.key == old.concreteKey())
156 {
157 if(old.effect() == preset->data)
158 {
159 // Fast path for loading the preset for e.g. same VSTs, JS, shaders etc.
160 auto& load_preset_ifaces
161 = m_context.app.interfaces<Process::LoadPresetCommandFactoryList>();
162
163 auto cmd = load_preset_ifaces.make(
164 &Process::LoadPresetCommandFactory::make, old, *preset, m_context);
165 CommandDispatcher<> disp{m_context.commandStack};
166 disp.submit(cmd);
167 return;
168 }
169 }
173 if(auto p = m.loadProcessFromPreset(*m_interval, *preset, old.position()))
174 {
175 linkNewProcess(p, m);
176 m.removeProcess(*m_interval, old.id());
177 m.commit();
178 }
179 }
180 }
181
182 void createProcess(const Process::ProcessDropHandler::ProcessDrop& proc)
183 {
184 if(!item)
185 return;
186 auto& old = item->model();
190 if(auto p = m.createProcessInNewSlot(*m_interval, proc.creation, old.position()))
191 {
192 if(proc.setup)
193 proc.setup(*p, disp);
194
195 // Give the same connections that the previous process had
196 linkNewProcess(p, m);
197
198 // Remove the previous process
199 m.removeProcess(*m_interval, old.id());
200
201 m.commit();
202 }
203 }
204
205 void linkNewProcess(Process::ProcessModel* p, Scenario::Command::Macro& m)
206 {
207 if(!item)
208 return;
209 auto& old = item->model();
210 if(p->inlets().size() > 0)
211 {
212 const auto dst = p->inlets()[0];
213 const auto type = dst->type();
214
215 if(old.inlets().size() > 0)
216 {
217 auto& old_dst = old.inlets()[0];
218 if(old_dst->type() == type && !qobject_cast<Process::ControlInlet*>(dst)
219 && !qobject_cast<Process::ControlInlet*>(old_dst))
220 {
221 for(auto& edge : old.inlets()[0]->cables())
222 {
223 auto& cable = edge.find(m_context);
224 auto* src = cable.source().try_find(m_context);
225 if(src && src->type() == type)
226 {
227 m.createCable(sm, *src, *dst, cable.type());
228 }
229 }
230 m.setProperty<Process::Port::p_address>(*dst, old_dst->address());
231 }
232 }
233 }
234
235 if(p->outlets().size() > 0)
236 {
237 const auto src = p->outlets()[0];
238 const auto type = src->type();
239
240 if(old.outlets().size() > 0)
241 {
242 auto& old_src = old.outlets()[0];
243 if(old_src->type() == type && !qobject_cast<Process::ControlInlet*>(src)
244 && !qobject_cast<Process::ControlInlet*>(old_src))
245 {
246 for(auto& edge : old.outlets()[0]->cables())
247 {
248 auto& cable = edge.find(m_context);
249 auto* dst = cable.sink().try_find(m_context);
250 if(dst && dst->type() == type)
251 {
252 m.createCable(sm, *src, *dst, cable.type());
253 }
254 }
255 m.setProperty<Process::Port::p_address>(*src, old_src->address());
256 if(type == Process::PortType::Audio)
257 {
258 auto old_audio_src = safe_cast<Process::AudioOutlet*>(old_src);
259 auto audio_src = safe_cast<Process::AudioOutlet*>(src);
260 m.setProperty<Process::AudioOutlet::p_propagate>(
261 *audio_src, old_audio_src->propagate());
262 }
263
264 src->setAddress(old_src->address());
265 }
266 }
267 }
268
269 relinkNamedPorts(p, m);
270 }
271
272 // For inlets and outlets beyond index 0, preserve cables and addresses on
273 // ports whose name and type match exactly between old and new process. This
274 // is the preset-replacement use case: e.g. both processes expose "Audio In"
275 // and "MIDI In", we want to keep both wirings, not just the first.
276 void relinkNamedPorts(Process::ProcessModel* p, Scenario::Command::Macro& m)
277 {
278 if(!item)
279 return;
280 auto& old = item->model();
281
282 const auto new_in_count = p->inlets().size();
283 const auto old_in_count = old.inlets().size();
284 for(std::size_t i = 1; i < old_in_count; ++i)
285 {
286 auto* old_in = old.inlets()[i];
287 Process::Inlet* dst = nullptr;
288 for(std::size_t j = 1; j < new_in_count; ++j)
289 {
290 auto* candidate = p->inlets()[j];
291 if(candidate->name() == old_in->name()
292 && candidate->type() == old_in->type())
293 {
294 dst = candidate;
295 break;
296 }
297 }
298 if(!dst)
299 continue;
300
301 const auto type = dst->type();
302 for(auto& edge : old_in->cables())
303 {
304 auto& cable = edge.find(m_context);
305 auto* src = cable.source().try_find(m_context);
306 if(src && src->type() == type)
307 {
308 m.createCable(sm, *src, *dst, cable.type());
309 }
310 }
311 m.setProperty<Process::Port::p_address>(*dst, old_in->address());
312 }
313
314 const auto new_out_count = p->outlets().size();
315 const auto old_out_count = old.outlets().size();
316 for(std::size_t i = 1; i < old_out_count; ++i)
317 {
318 auto* old_out = old.outlets()[i];
319 Process::Outlet* src = nullptr;
320 for(std::size_t j = 1; j < new_out_count; ++j)
321 {
322 auto* candidate = p->outlets()[j];
323 if(candidate->name() == old_out->name()
324 && candidate->type() == old_out->type())
325 {
326 src = candidate;
327 break;
328 }
329 }
330 if(!src)
331 continue;
332
333 const auto type = src->type();
334 for(auto& edge : old_out->cables())
335 {
336 auto& cable = edge.find(m_context);
337 auto* dst = cable.sink().try_find(m_context);
338 if(dst && dst->type() == type)
339 {
340 m.createCable(sm, *src, *dst, cable.type());
341 }
342 }
343 m.setProperty<Process::Port::p_address>(*src, old_out->address());
344 if(type == Process::PortType::Audio)
345 {
346 auto* old_audio = qobject_cast<Process::AudioOutlet*>(old_out);
347 auto* new_audio = qobject_cast<Process::AudioOutlet*>(src);
348 if(old_audio && new_audio)
349 {
350 m.setProperty<Process::AudioOutlet::p_propagate>(
351 *new_audio, old_audio->propagate());
352 }
353 }
354 src->setAddress(old_out->address());
355 }
356 }
357
358 void drop(const QMimeData& mime)
359 {
360 // FIXME drop in nodal vs drop in scenario
361 if(!item)
362 return;
363 auto& model = item->model();
364 m_interval = qobject_cast<Scenario::IntervalModel*>(model.parent());
365 if(!m_interval)
366 return;
367
368 // Something tricky here is that we have to separate mime data processing and command execution:
369 // mime data processing has to happen synchronously as it gets deleted after the NodeItem::dropEvent function returns.
370 // On the other hand, we cannot run the command synchronously as it may delete the cable, which causes
371 // the cableitem to be deleted too and thus it cannot finish the dropEvent without crashing.
372 const auto& handlers = m_context.app.interfaces<Process::ProcessDropHandlerList>();
373
374 if(mime.hasFormat(score::mime::layerdata()))
375 {
376 // TODO
377 }
378 else if(mime.hasFormat(score::mime::processpreset()))
379 {
380 const auto presetData = mime.data(score::mime::processpreset());
381 QMetaObject::invokeMethod(this, [presetData, this]() {
382 createPreset(presetData);
383 }, Qt::QueuedConnection);
384 }
385 else if(auto res = handlers.getDrop(mime, m_context); !res.empty())
386 {
387 if(res.size() >= 1)
388 {
389 QMetaObject::invokeMethod(this, [proc = res.front(), this]() {
390 createProcess(proc);
391 }, Qt::QueuedConnection);
392 }
393 }
394 else if(mime.hasUrls())
395 {
396 // TODO
397 }
398 }
399};
400}
The CommandDispatcher class.
Definition CommandDispatcher.hpp:13
Definition CableItem.hpp:37
Definition Cable.hpp:38
Definition Port.hpp:180
Definition LoadPresetCommandFactory.hpp:32
Definition NodeItem.hpp:40
Definition Port.hpp:275
Definition Port.hpp:103
Definition ProcessDropHandler.hpp:75
Definition ProcessList.hpp:10
The Process class.
Definition score-lib-process/Process/Process.hpp:62
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