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/Commands/Properties.hpp>
6#include <Process/Dataflow/CableItem.hpp>
7#include <Process/Dataflow/NodeItem.hpp>
8#include <Process/Drop/ProcessDropHandler.hpp>
9#include <Process/Process.hpp>
10#include <Process/ProcessContext.hpp>
11#include <Process/Dataflow/PortItem.hpp>
12
13#include <Scenario/Commands/CommandAPI.hpp>
14#include <Scenario/Commands/Interval/AddProcessToInterval.hpp>
15#include <Scenario/Document/Interval/IntervalModel.hpp>
16#include <Scenario/Document/ScenarioDocument/ProcessCreation.hpp>
17
18#include <score/application/GUIApplicationContext.hpp>
19#include <score/command/Dispatchers/CommandDispatcher.hpp>
20#include <score/command/Dispatchers/RuntimeDispatcher.hpp>
21#include <score/document/DocumentContext.hpp>
22
23namespace Dataflow
24{
25void replaceCable(
26 const score::DocumentContext& ctx, const Process::Cable& currentCable,
27 const Process::Port& newPort);
28}
29namespace Scenario
30{
31class DropOnCable : public QObject
32{
33 // Only things that make sense to drop: processes and presets (maybe layers?)
34 // Find in which interval we wish to drop.
35 // If both ends are in the same hierarchy level we drop the process in the interval
36
37public:
38 const ScenarioDocumentModel& sm;
39 const Process::Context& m_context;
40 const Dataflow::CableItem& item;
41 const Process::Cable& cable = item.model();
42 const Process::Port& source = cable.source().find(m_context);
43 const Process::Port& sink = cable.sink().find(m_context);
44
45 Scenario::IntervalModel* m_interval{};
46
48 const Dataflow::CableItem& item, const ScenarioDocumentModel& sm,
49 const Process::Context& m_context)
50 : sm{sm}
51 , m_context{m_context}
52 , item{item}
53 {
54 }
55
56 void createPreset(QPointF pos, const QByteArray& presetData)
57 {
58 auto& procs = m_context.app.interfaces<Process::ProcessFactoryList>();
59 if(auto preset = Process::Preset::fromJson(procs, presetData))
60 {
61 Scenario::loadPresetInCable(m_context, sm, *preset, cable);
62 }
63 }
64
65 void createProcess(QPointF pos, const Process::ProcessDropHandler::ProcessDrop& proc)
66 {
67 Scenario::createProcessInCable(
68 m_context, sm, proc.creation, proc.duration, proc.setup, cable);
69 }
70
71 void drop(const QPointF& pos, const QMimeData& mime)
72 {
73 // FIXME put it closer to the source or sink depending on drop position
74 auto source_itv = Scenario::closestParentInterval(&source);
75 //auto sink_itv = Scenario::closestParentInterval(&sink);
76 if(!source_itv)
77 return;
78
79 m_interval = source_itv;
80
81 // Something tricky here is that we have to separate mime data processing and command execution:
82 // mime data processing has to happen synchronously as it gets deleted after the CableItem::dropEvent function returns.
83 // On the other hand, we cannot run the command synchronously as it may delete the cable, which causes
84 // the cableitem to be deleted too and thus it cannot finish the dropEvent without crashing.
85 const auto& handlers = m_context.app.interfaces<Process::ProcessDropHandlerList>();
86
87 if(mime.hasFormat(score::mime::layerdata()))
88 {
89 // TODO
90 }
91 else if(mime.hasFormat(score::mime::processpreset()))
92 {
93 const auto presetData = mime.data(score::mime::processpreset());
94
95 QMetaObject::invokeMethod(this, [pos, presetData, this]() {
96 createPreset(pos, presetData);
97 }, Qt::QueuedConnection);
98 }
99 else if(auto res = handlers.getDrop(mime, m_context); !res.empty())
100 {
101 if(res.size() >= 1)
102 {
103 QMetaObject::invokeMethod(this, [pos, proc = res.front(), this]() {
104 createProcess(pos, proc);
105 }, Qt::QueuedConnection);
106 }
107 }
108 else if(mime.hasFormat(score::mime::port()))
109 {
110 auto base_port = Dataflow::PortItem::clickedPort;
111 if(base_port)
112 {
113 auto& new_port = base_port->port();
114 QMetaObject::invokeMethod(this, [&new_port, this]() {
115 Dataflow::replaceCable(this->m_context, cable, new_port);
116 }, Qt::QueuedConnection);
117 }
118 }
119 else if(mime.hasUrls())
120 {
121 // TODO
122 }
123 else
124 {
125 qDebug()<<mime.formats();
126 }
127 }
128};
129
130class DropOnNode : public QObject
131{
132public:
133 const ScenarioDocumentModel& sm;
134 const Process::Context& m_context;
135 QPointer<const Process::NodeItem> item;
136
137 Scenario::IntervalModel* m_interval{};
138
140 const Process::NodeItem& item, const ScenarioDocumentModel& sm,
141 const Process::Context& m_context)
142 : sm{sm}
143 , m_context{m_context}
144 , item{&item}
145 {
146 }
147
148 void createPreset(const QByteArray& presetData)
149 {
150 if(!item)
151 return;
152 auto& old = item->model();
153 auto& procs = m_context.app.interfaces<Process::ProcessFactoryList>();
154 if(auto preset = Process::Preset::fromJson(procs, presetData))
155 {
156 if(preset->key.key == old.concreteKey())
157 {
158 if(old.effect() == preset->data)
159 {
160 // Fast path for loading the preset for e.g. same VSTs, JS, shaders etc.
161 auto& load_preset_ifaces
162 = m_context.app.interfaces<Process::LoadPresetCommandFactoryList>();
163
164 auto cmd = load_preset_ifaces.make(
165 &Process::LoadPresetCommandFactory::make, old, *preset, m_context);
166 CommandDispatcher<> disp{m_context.commandStack};
167 disp.submit(cmd);
168 return;
169 }
170 }
174 if(auto p = m.loadProcessFromPreset(*m_interval, *preset, old.position()))
175 {
176 linkNewProcess(p, m);
177 keepUnfolded(p, m);
178 m.removeProcess(*m_interval, old.id());
179 m.commit();
180 }
181 }
182 }
183
184 // A node the user had open stays open, however many ports the replacement
185 // brings: the port-count heuristic is only meant to decide for a node nobody
186 // has an opinion about yet. One-directional -- a folded node does not force
187 // the replacement folded, that is still left to the heuristic.
188 void keepUnfolded(Process::ProcessModel* p, Scenario::Command::Macro& m)
189 {
190 if(!item)
191 return;
192 if(!item->model().folded())
193 m.setProperty<Process::ProcessModel::p_foldMode>(*p, Process::FoldMode::Unfolded);
194 }
195
196 void createProcess(const Process::ProcessDropHandler::ProcessDrop& proc)
197 {
198 if(!item)
199 return;
200 auto& old = item->model();
204 if(auto p = m.createProcessInNewSlot(*m_interval, proc.creation, old.position()))
205 {
206 if(proc.setup)
207 proc.setup(*p, disp);
208
209 // Give the same connections that the previous process had
210 linkNewProcess(p, m);
211 keepUnfolded(p, m);
212
213 // Remove the previous process
214 m.removeProcess(*m_interval, old.id());
215
216 m.commit();
217 }
218 }
219
220 void linkNewProcess(Process::ProcessModel* p, Scenario::Command::Macro& m)
221 {
222 if(!item)
223 return;
224 auto& old = item->model();
225 if(p->inlets().size() > 0)
226 {
227 const auto dst = p->inlets()[0];
228 const auto type = dst->type();
229
230 if(old.inlets().size() > 0)
231 {
232 auto& old_dst = old.inlets()[0];
233 if(old_dst->type() == type && !qobject_cast<Process::ControlInlet*>(dst)
234 && !qobject_cast<Process::ControlInlet*>(old_dst))
235 {
236 for(auto& edge : old.inlets()[0]->cables())
237 {
238 auto& cable = edge.find(m_context);
239 auto* src = cable.source().try_find(m_context);
240 if(src && src->type() == type)
241 {
242 m.createCable(sm, *src, *dst, cable.type());
243 }
244 }
245 m.setProperty<Process::Port::p_address>(*dst, old_dst->address());
246 }
247 }
248 }
249
250 if(p->outlets().size() > 0)
251 {
252 const auto src = p->outlets()[0];
253 const auto type = src->type();
254
255 if(old.outlets().size() > 0)
256 {
257 auto& old_src = old.outlets()[0];
258 if(old_src->type() == type && !qobject_cast<Process::ControlInlet*>(src)
259 && !qobject_cast<Process::ControlInlet*>(old_src))
260 {
261 for(auto& edge : old.outlets()[0]->cables())
262 {
263 auto& cable = edge.find(m_context);
264 auto* dst = cable.sink().try_find(m_context);
265 if(dst && dst->type() == type)
266 {
267 m.createCable(sm, *src, *dst, cable.type());
268 }
269 }
270 m.setProperty<Process::Port::p_address>(*src, old_src->address());
271 if(type == Process::PortType::Audio)
272 {
273 auto old_audio_src = safe_cast<Process::AudioOutlet*>(old_src);
274 auto audio_src = safe_cast<Process::AudioOutlet*>(src);
275 m.setProperty<Process::AudioOutlet::p_propagate>(
276 *audio_src, old_audio_src->propagate());
277 }
278
279 src->setAddress(old_src->address());
280 }
281 }
282 }
283
284 relinkNamedPorts(p, m);
285 }
286
287 // For inlets and outlets beyond index 0, preserve cables and addresses on
288 // ports whose name and type match exactly between old and new process. This
289 // is the preset-replacement use case: e.g. both processes expose "Audio In"
290 // and "MIDI In", we want to keep both wirings, not just the first.
291 void relinkNamedPorts(Process::ProcessModel* p, Scenario::Command::Macro& m)
292 {
293 if(!item)
294 return;
295 auto& old = item->model();
296
297 const auto new_in_count = p->inlets().size();
298 const auto old_in_count = old.inlets().size();
299 for(std::size_t i = 1; i < old_in_count; ++i)
300 {
301 auto* old_in = old.inlets()[i];
302 Process::Inlet* dst = nullptr;
303 for(std::size_t j = 1; j < new_in_count; ++j)
304 {
305 auto* candidate = p->inlets()[j];
306 if(candidate->name() == old_in->name()
307 && candidate->type() == old_in->type())
308 {
309 dst = candidate;
310 break;
311 }
312 }
313 if(!dst)
314 continue;
315
316 const auto type = dst->type();
317 for(auto& edge : old_in->cables())
318 {
319 auto& cable = edge.find(m_context);
320 auto* src = cable.source().try_find(m_context);
321 if(src && src->type() == type)
322 {
323 m.createCable(sm, *src, *dst, cable.type());
324 }
325 }
326 m.setProperty<Process::Port::p_address>(*dst, old_in->address());
327 }
328
329 const auto new_out_count = p->outlets().size();
330 const auto old_out_count = old.outlets().size();
331 for(std::size_t i = 1; i < old_out_count; ++i)
332 {
333 auto* old_out = old.outlets()[i];
334 Process::Outlet* src = nullptr;
335 for(std::size_t j = 1; j < new_out_count; ++j)
336 {
337 auto* candidate = p->outlets()[j];
338 if(candidate->name() == old_out->name()
339 && candidate->type() == old_out->type())
340 {
341 src = candidate;
342 break;
343 }
344 }
345 if(!src)
346 continue;
347
348 const auto type = src->type();
349 for(auto& edge : old_out->cables())
350 {
351 auto& cable = edge.find(m_context);
352 auto* dst = cable.sink().try_find(m_context);
353 if(dst && dst->type() == type)
354 {
355 m.createCable(sm, *src, *dst, cable.type());
356 }
357 }
358 m.setProperty<Process::Port::p_address>(*src, old_out->address());
359 if(type == Process::PortType::Audio)
360 {
361 auto* old_audio = qobject_cast<Process::AudioOutlet*>(old_out);
362 auto* new_audio = qobject_cast<Process::AudioOutlet*>(src);
363 if(old_audio && new_audio)
364 {
365 m.setProperty<Process::AudioOutlet::p_propagate>(
366 *new_audio, old_audio->propagate());
367 }
368 }
369 src->setAddress(old_out->address());
370 }
371 }
372
373 void drop(const QMimeData& mime)
374 {
375 // FIXME drop in nodal vs drop in scenario
376 if(!item)
377 return;
378 auto& model = item->model();
379 m_interval = qobject_cast<Scenario::IntervalModel*>(model.parent());
380 if(!m_interval)
381 return;
382
383 // Something tricky here is that we have to separate mime data processing and command execution:
384 // mime data processing has to happen synchronously as it gets deleted after the NodeItem::dropEvent function returns.
385 // On the other hand, we cannot run the command synchronously as it may delete the cable, which causes
386 // the cableitem to be deleted too and thus it cannot finish the dropEvent without crashing.
387 const auto& handlers = m_context.app.interfaces<Process::ProcessDropHandlerList>();
388
389 if(mime.hasFormat(score::mime::layerdata()))
390 {
391 // TODO
392 }
393 else if(mime.hasFormat(score::mime::processpreset()))
394 {
395 const auto presetData = mime.data(score::mime::processpreset());
396 QMetaObject::invokeMethod(this, [presetData, this]() {
397 createPreset(presetData);
398 }, Qt::QueuedConnection);
399 }
400 else if(auto res = handlers.getDrop(mime, m_context); !res.empty())
401 {
402 if(res.size() >= 1)
403 {
404 QMetaObject::invokeMethod(this, [proc = res.front(), this]() {
405 createProcess(proc);
406 }, Qt::QueuedConnection);
407 }
408 }
409 else if(mime.hasUrls())
410 {
411 // TODO
412 }
413 }
414};
415}
The CommandDispatcher class.
Definition CommandDispatcher.hpp:13
Definition CableItem.hpp:37
Definition Cable.hpp:38
Definition Port.hpp:192
Definition LoadPresetCommandFactory.hpp:32
Definition NodeItem.hpp:40
Definition Port.hpp:300
Definition Port.hpp:104
Definition ProcessDropHandler.hpp:75
Definition ProcessList.hpp:10
The Process class.
Definition score-lib-process/Process/Process.hpp:75
Definition AddProcessToInterval.hpp:72
Definition CommandAPI.hpp:28
Definition DropOnCable.hpp:32
Definition DropOnCable.hpp:131
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