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 // A drag that is moving cable ends has its own drop handling: it must not
111 // relink the cable it happens to pass over.
112 auto base_port = Dataflow::portDragMovedCables().empty()
113 ? Dataflow::PortItem::clickedPort
114 : nullptr;
115 if(base_port)
116 {
117 auto& new_port = base_port->port();
118 QMetaObject::invokeMethod(this, [&new_port, this]() {
119 Dataflow::replaceCable(this->m_context, cable, new_port);
120 }, Qt::QueuedConnection);
121 }
122 }
123 else if(mime.hasUrls())
124 {
125 // TODO
126 }
127 else
128 {
129 qDebug()<<mime.formats();
130 }
131 }
132};
133
134class DropOnNode : public QObject
135{
136public:
137 const ScenarioDocumentModel& sm;
138 const Process::Context& m_context;
139 QPointer<const Process::NodeItem> item;
140
141 Scenario::IntervalModel* m_interval{};
142
144 const Process::NodeItem& item, const ScenarioDocumentModel& sm,
145 const Process::Context& m_context)
146 : sm{sm}
147 , m_context{m_context}
148 , item{&item}
149 {
150 }
151
152 void createPreset(const QByteArray& presetData)
153 {
154 if(!item)
155 return;
156 auto& old = item->model();
157 auto& procs = m_context.app.interfaces<Process::ProcessFactoryList>();
158 if(auto preset = Process::Preset::fromJson(procs, presetData))
159 {
160 if(preset->key.key == old.concreteKey())
161 {
162 if(old.effect() == preset->data)
163 {
164 // Fast path for loading the preset for e.g. same VSTs, JS, shaders etc.
165 auto& load_preset_ifaces
166 = m_context.app.interfaces<Process::LoadPresetCommandFactoryList>();
167
168 auto cmd = load_preset_ifaces.make(
169 &Process::LoadPresetCommandFactory::make, old, *preset, m_context);
170 CommandDispatcher<> disp{m_context.commandStack};
171 disp.submit(cmd);
172 return;
173 }
174 }
178 if(auto p = m.loadProcessFromPreset(*m_interval, *preset, old.position()))
179 {
180 linkNewProcess(p, m);
181 keepUnfolded(p, m);
182 m.removeProcess(*m_interval, old.id());
183 m.commit();
184 }
185 }
186 }
187
188 // A node the user had open stays open, however many ports the replacement
189 // brings: the port-count heuristic is only meant to decide for a node nobody
190 // has an opinion about yet. One-directional -- a folded node does not force
191 // the replacement folded, that is still left to the heuristic.
192 void keepUnfolded(Process::ProcessModel* p, Scenario::Command::Macro& m)
193 {
194 if(!item)
195 return;
196 if(!item->model().folded())
197 m.setProperty<Process::ProcessModel::p_foldMode>(*p, Process::FoldMode::Unfolded);
198 }
199
200 void createProcess(const Process::ProcessDropHandler::ProcessDrop& proc)
201 {
202 if(!item)
203 return;
204 auto& old = item->model();
208 if(auto p = m.createProcessInNewSlot(*m_interval, proc.creation, old.position()))
209 {
210 if(proc.setup)
211 proc.setup(*p, disp);
212
213 // Give the same connections that the previous process had
214 linkNewProcess(p, m);
215 keepUnfolded(p, m);
216
217 // Remove the previous process
218 m.removeProcess(*m_interval, old.id());
219
220 m.commit();
221 }
222 }
223
224 void linkNewProcess(Process::ProcessModel* p, Scenario::Command::Macro& m)
225 {
226 if(!item)
227 return;
228 auto& old = item->model();
229 if(p->inlets().size() > 0)
230 {
231 const auto dst = p->inlets()[0];
232 const auto type = dst->type();
233
234 if(old.inlets().size() > 0)
235 {
236 auto& old_dst = old.inlets()[0];
237 if(old_dst->type() == type && !qobject_cast<Process::ControlInlet*>(dst)
238 && !qobject_cast<Process::ControlInlet*>(old_dst))
239 {
240 for(auto& edge : old.inlets()[0]->cables())
241 {
242 auto& cable = edge.find(m_context);
243 auto* src = cable.source().try_find(m_context);
244 if(src && src->type() == type)
245 {
246 m.createCable(sm, *src, *dst, cable.type());
247 }
248 }
249 m.setProperty<Process::Port::p_address>(*dst, old_dst->address());
250 }
251 }
252 }
253
254 if(p->outlets().size() > 0)
255 {
256 const auto src = p->outlets()[0];
257 const auto type = src->type();
258
259 if(old.outlets().size() > 0)
260 {
261 auto& old_src = old.outlets()[0];
262 if(old_src->type() == type && !qobject_cast<Process::ControlInlet*>(src)
263 && !qobject_cast<Process::ControlInlet*>(old_src))
264 {
265 for(auto& edge : old.outlets()[0]->cables())
266 {
267 auto& cable = edge.find(m_context);
268 auto* dst = cable.sink().try_find(m_context);
269 if(dst && dst->type() == type)
270 {
271 m.createCable(sm, *src, *dst, cable.type());
272 }
273 }
274 m.setProperty<Process::Port::p_address>(*src, old_src->address());
275 if(type == Process::PortType::Audio)
276 {
277 auto old_audio_src = safe_cast<Process::AudioOutlet*>(old_src);
278 auto audio_src = safe_cast<Process::AudioOutlet*>(src);
279 m.setProperty<Process::AudioOutlet::p_propagate>(
280 *audio_src, old_audio_src->propagate());
281 }
282
283 src->setAddress(old_src->address());
284 }
285 }
286 }
287
288 relinkNamedPorts(p, m);
289 }
290
291 // For inlets and outlets beyond index 0, preserve cables and addresses on
292 // ports whose name and type match exactly between old and new process. This
293 // is the preset-replacement use case: e.g. both processes expose "Audio In"
294 // and "MIDI In", we want to keep both wirings, not just the first.
295 void relinkNamedPorts(Process::ProcessModel* p, Scenario::Command::Macro& m)
296 {
297 if(!item)
298 return;
299 auto& old = item->model();
300
301 const auto new_in_count = p->inlets().size();
302 const auto old_in_count = old.inlets().size();
303 for(std::size_t i = 1; i < old_in_count; ++i)
304 {
305 auto* old_in = old.inlets()[i];
306 Process::Inlet* dst = nullptr;
307 for(std::size_t j = 1; j < new_in_count; ++j)
308 {
309 auto* candidate = p->inlets()[j];
310 if(candidate->name() == old_in->name()
311 && candidate->type() == old_in->type())
312 {
313 dst = candidate;
314 break;
315 }
316 }
317 if(!dst)
318 continue;
319
320 const auto type = dst->type();
321 for(auto& edge : old_in->cables())
322 {
323 auto& cable = edge.find(m_context);
324 auto* src = cable.source().try_find(m_context);
325 if(src && src->type() == type)
326 {
327 m.createCable(sm, *src, *dst, cable.type());
328 }
329 }
330 m.setProperty<Process::Port::p_address>(*dst, old_in->address());
331 }
332
333 const auto new_out_count = p->outlets().size();
334 const auto old_out_count = old.outlets().size();
335 for(std::size_t i = 1; i < old_out_count; ++i)
336 {
337 auto* old_out = old.outlets()[i];
338 Process::Outlet* src = nullptr;
339 for(std::size_t j = 1; j < new_out_count; ++j)
340 {
341 auto* candidate = p->outlets()[j];
342 if(candidate->name() == old_out->name()
343 && candidate->type() == old_out->type())
344 {
345 src = candidate;
346 break;
347 }
348 }
349 if(!src)
350 continue;
351
352 const auto type = src->type();
353 for(auto& edge : old_out->cables())
354 {
355 auto& cable = edge.find(m_context);
356 auto* dst = cable.sink().try_find(m_context);
357 if(dst && dst->type() == type)
358 {
359 m.createCable(sm, *src, *dst, cable.type());
360 }
361 }
362 m.setProperty<Process::Port::p_address>(*src, old_out->address());
363 if(type == Process::PortType::Audio)
364 {
365 auto* old_audio = qobject_cast<Process::AudioOutlet*>(old_out);
366 auto* new_audio = qobject_cast<Process::AudioOutlet*>(src);
367 if(old_audio && new_audio)
368 {
369 m.setProperty<Process::AudioOutlet::p_propagate>(
370 *new_audio, old_audio->propagate());
371 }
372 }
373 src->setAddress(old_out->address());
374 }
375 }
376
377 void drop(const QMimeData& mime)
378 {
379 // FIXME drop in nodal vs drop in scenario
380 if(!item)
381 return;
382 auto& model = item->model();
383 m_interval = qobject_cast<Scenario::IntervalModel*>(model.parent());
384 if(!m_interval)
385 return;
386
387 // Something tricky here is that we have to separate mime data processing and command execution:
388 // mime data processing has to happen synchronously as it gets deleted after the NodeItem::dropEvent function returns.
389 // On the other hand, we cannot run the command synchronously as it may delete the cable, which causes
390 // the cableitem to be deleted too and thus it cannot finish the dropEvent without crashing.
391 const auto& handlers = m_context.app.interfaces<Process::ProcessDropHandlerList>();
392
393 if(mime.hasFormat(score::mime::layerdata()))
394 {
395 // TODO
396 }
397 else if(mime.hasFormat(score::mime::processpreset()))
398 {
399 const auto presetData = mime.data(score::mime::processpreset());
400 QMetaObject::invokeMethod(this, [presetData, this]() {
401 createPreset(presetData);
402 }, Qt::QueuedConnection);
403 }
404 else if(auto res = handlers.getDrop(mime, m_context); !res.empty())
405 {
406 if(res.size() >= 1)
407 {
408 QMetaObject::invokeMethod(this, [proc = res.front(), this]() {
409 createProcess(proc);
410 }, Qt::QueuedConnection);
411 }
412 }
413 else if(mime.hasUrls())
414 {
415 // TODO
416 }
417 }
418};
419}
The CommandDispatcher class.
Definition CommandDispatcher.hpp:13
Definition CableItem.hpp:37
Definition Cable.hpp:38
Definition Port.hpp:221
Definition LoadPresetCommandFactory.hpp:35
Definition NodeItem.hpp:47
Definition Port.hpp:329
Definition Port.hpp:106
Definition ProcessDropHandler.hpp:76
Definition ProcessList.hpp:10
The Process class.
Definition score-lib-process/Process/Process.hpp:82
Definition AddProcessToInterval.hpp:72
Definition CommandAPI.hpp:28
Definition DropOnCable.hpp:32
Definition DropOnCable.hpp:135
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 ConnectionsList.hpp:22
Definition ProcessContext.hpp:12
Definition ProcessDropHandler.hpp:30
Definition RuntimeDispatcher.hpp:14
Definition DocumentContext.hpp:18