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().find(m_context);
225 if(src.type() == type)
226 {
227 m.createCable(sm, src, *dst);
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().find(m_context);
250 if(dst.type() == type)
251 {
252 m.createCable(sm, *src, dst);
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
270 void drop(const QMimeData& mime)
271 {
272 // FIXME drop in nodal vs drop in scenario
273 if(!item)
274 return;
275 auto& model = item->model();
276 m_interval = qobject_cast<Scenario::IntervalModel*>(model.parent());
277 if(!m_interval)
278 return;
279
280 // Something tricky here is that we have to separate mime data processing and command execution:
281 // mime data processing has to happen synchronously as it gets deleted after the NodeItem::dropEvent function returns.
282 // On the other hand, we cannot run the command synchronously as it may delete the cable, which causes
283 // the cableitem to be deleted too and thus it cannot finish the dropEvent without crashing.
284 const auto& handlers = m_context.app.interfaces<Process::ProcessDropHandlerList>();
285
286 if(mime.hasFormat(score::mime::layerdata()))
287 {
288 // TODO
289 }
290 else if(mime.hasFormat(score::mime::processpreset()))
291 {
292 const auto presetData = mime.data(score::mime::processpreset());
293 QMetaObject::invokeMethod(this, [presetData, this]() {
294 createPreset(presetData);
295 }, Qt::QueuedConnection);
296 }
297 else if(auto res = handlers.getDrop(mime, m_context); !res.empty())
298 {
299 if(res.size() >= 1)
300 {
301 QMetaObject::invokeMethod(this, [proc = res.front(), this]() {
302 createProcess(proc);
303 }, Qt::QueuedConnection);
304 }
305 }
306 else if(mime.hasUrls())
307 {
308 // TODO
309 }
310 }
311};
312}
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: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