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