1 | /* This file is part of the KDE project |
2 | Copyright (C) 2007 Matthias Kretz <kretz@kde.org> |
3 | |
4 | This library is free software; you can redistribute it and/or |
5 | modify it under the terms of the GNU Lesser General Public |
6 | License as published by the Free Software Foundation; either |
7 | version 2.1 of the License, or (at your option) version 3, or any |
8 | later version accepted by the membership of KDE e.V. (or its |
9 | successor approved by the membership of KDE e.V.), Nokia Corporation |
10 | (or its successors, if any) and the KDE Free Qt Foundation, which shall |
11 | act as a proxy defined in Section 6 of version 3 of the license. |
12 | |
13 | This library is distributed in the hope that it will be useful, |
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
16 | Lesser General Public License for more details. |
17 | |
18 | You should have received a copy of the GNU Lesser General Public |
19 | License along with this library. If not, see <http://www.gnu.org/licenses/>. |
20 | |
21 | */ |
22 | |
23 | #include "path.h" |
24 | #include "path_p.h" |
25 | |
26 | #include "phononnamespace_p.h" |
27 | #include "backendinterface.h" |
28 | #include "factory_p.h" |
29 | #include "medianode.h" |
30 | #include "medianode_p.h" |
31 | |
32 | namespace Phonon |
33 | { |
34 | |
35 | class ConnectionTransaction |
36 | { |
37 | public: |
38 | ConnectionTransaction(BackendInterface *b, const QSet<QObject*> &x) : backend(b), list(x) |
39 | { |
40 | success = backend->startConnectionChange(list); |
41 | } |
42 | ~ConnectionTransaction() |
43 | { |
44 | backend->endConnectionChange(list); |
45 | } |
46 | operator bool() |
47 | { |
48 | return success; |
49 | } |
50 | private: |
51 | bool success; |
52 | BackendInterface *const backend; |
53 | const QSet<QObject*> list; |
54 | }; |
55 | |
56 | PathPrivate::~PathPrivate() |
57 | { |
58 | #ifndef QT_NO_PHONON_EFFECT |
59 | for (int i = 0; i < effects.count(); ++i) { |
60 | effects.at(i)->k_ptr->removeDestructionHandler(handler: this); |
61 | } |
62 | delete effectsParent; |
63 | #endif |
64 | } |
65 | |
66 | Path::~Path() |
67 | { |
68 | } |
69 | |
70 | Path::Path() |
71 | : d(new PathPrivate) |
72 | { |
73 | } |
74 | |
75 | Path::Path(const Path &rhs) |
76 | : d(rhs.d) |
77 | { |
78 | } |
79 | |
80 | bool Path::isValid() const |
81 | { |
82 | return d->sourceNode != nullptr && d->sinkNode != nullptr; |
83 | } |
84 | |
85 | #ifndef QT_NO_PHONON_EFFECT |
86 | Effect *Path::insertEffect(const EffectDescription &desc, Effect *insertBefore) |
87 | { |
88 | if (!d->effectsParent) { |
89 | d->effectsParent = new QObject; |
90 | } |
91 | Effect *e = new Effect(desc, d->effectsParent); |
92 | if (!e->isValid()) { |
93 | delete e; |
94 | return nullptr; |
95 | } |
96 | bool success = insertEffect(newEffect: e, insertBefore); |
97 | if (!success) { |
98 | delete e; |
99 | return nullptr; |
100 | } |
101 | return e; |
102 | } |
103 | |
104 | bool Path::insertEffect(Effect *newEffect, Effect *insertBefore) |
105 | { |
106 | QObject *newEffectBackend = newEffect ? newEffect->k_ptr->backendObject() : nullptr; |
107 | if (!isValid() || !newEffectBackend || d->effects.contains(t: newEffect) || |
108 | (insertBefore && (!d->effects.contains(t: insertBefore) || !insertBefore->k_ptr->backendObject()))) { |
109 | return false; |
110 | } |
111 | QObject *leftNode = nullptr; |
112 | QObject *rightNode = nullptr; |
113 | const int insertIndex = insertBefore ? d->effects.indexOf(t: insertBefore) : d->effects.size(); |
114 | if (insertIndex == 0) { |
115 | //prepend |
116 | leftNode = d->sourceNode->k_ptr->backendObject(); |
117 | } else { |
118 | leftNode = d->effects[insertIndex - 1]->k_ptr->backendObject(); |
119 | } |
120 | |
121 | if (insertIndex == d->effects.size()) { |
122 | //append |
123 | rightNode = d->sinkNode->k_ptr->backendObject(); |
124 | } else { |
125 | Q_ASSERT(insertBefore); |
126 | rightNode = insertBefore->k_ptr->backendObject(); |
127 | } |
128 | |
129 | QList<QObjectPair> disconnections, connections; |
130 | disconnections << QObjectPair(leftNode, rightNode); |
131 | connections << QObjectPair(leftNode, newEffectBackend) |
132 | << QObjectPair(newEffectBackend, rightNode); |
133 | |
134 | if (d->executeTransaction(disconnections, connections)) { |
135 | newEffect->k_ptr->addDestructionHandler(handler: d.data()); |
136 | d->effects.insert(i: insertIndex, t: newEffect); |
137 | return true; |
138 | } else { |
139 | return false; |
140 | } |
141 | } |
142 | |
143 | bool Path::removeEffect(Effect *effect) |
144 | { |
145 | return d->removeEffect(effect); |
146 | } |
147 | |
148 | QList<Effect *> Path::effects() const |
149 | { |
150 | return d->effects; |
151 | } |
152 | #endif //QT_NO_PHONON_EFFECT |
153 | |
154 | bool Path::reconnect(MediaNode *source, MediaNode *sink) |
155 | { |
156 | if (!source || !sink || !source->k_ptr->backendObject() || !sink->k_ptr->backendObject()) { |
157 | return false; |
158 | } |
159 | |
160 | QList<QObjectPair> disconnections, connections; |
161 | |
162 | //backend objects |
163 | QObject *bnewSource = source->k_ptr->backendObject(); |
164 | QObject *bnewSink = sink->k_ptr->backendObject(); |
165 | QObject *bcurrentSource = d->sourceNode ? d->sourceNode->k_ptr->backendObject() : nullptr; |
166 | QObject *bcurrentSink = d->sinkNode ? d->sinkNode->k_ptr->backendObject() : nullptr; |
167 | |
168 | if (bnewSource != bcurrentSource) { |
169 | //we need to change the source |
170 | #ifndef QT_NO_PHONON_EFFECT |
171 | MediaNode *next = d->effects.isEmpty() ? sink : d->effects.first(); |
172 | #else |
173 | MediaNode *next = sink; |
174 | #endif //QT_NO_PHONON_EFFECT |
175 | QObject *bnext = next->k_ptr->backendObject(); |
176 | if (bcurrentSource) |
177 | disconnections << QObjectPair(bcurrentSource, bnext); |
178 | connections << QObjectPair(bnewSource, bnext); |
179 | } |
180 | |
181 | if (bnewSink != bcurrentSink) { |
182 | #ifndef QT_NO_PHONON_EFFECT |
183 | MediaNode *previous = d->effects.isEmpty() ? source : d->effects.last(); |
184 | #else |
185 | MediaNode *previous = source; |
186 | #endif //QT_NO_PHONON_EFFECT |
187 | QObject *bprevious = previous->k_ptr->backendObject(); |
188 | if (bcurrentSink) |
189 | disconnections << QObjectPair(bprevious, bcurrentSink); |
190 | QObjectPair pair(bprevious, bnewSink); |
191 | if (!connections.contains(t: pair)) //avoid connecting twice |
192 | connections << pair; |
193 | } |
194 | |
195 | if (d->executeTransaction(disconnections, connections)) { |
196 | |
197 | //everything went well: let's update the path and the sink node |
198 | if (d->sinkNode != sink) { |
199 | if (d->sinkNode) { |
200 | d->sinkNode->k_ptr->removeInputPath(*this); |
201 | d->sinkNode->k_ptr->removeDestructionHandler(handler: d.data()); |
202 | } |
203 | sink->k_ptr->addInputPath(*this); |
204 | d->sinkNode = sink; |
205 | d->sinkNode->k_ptr->addDestructionHandler(handler: d.data()); |
206 | } |
207 | |
208 | //everything went well: let's update the path and the source node |
209 | if (d->sourceNode != source) { |
210 | source->k_ptr->addOutputPath(*this); |
211 | if (d->sourceNode) { |
212 | d->sourceNode->k_ptr->removeOutputPath(*this); |
213 | d->sourceNode->k_ptr->removeDestructionHandler(handler: d.data()); |
214 | } |
215 | d->sourceNode = source; |
216 | d->sourceNode->k_ptr->addDestructionHandler(handler: d.data()); |
217 | } |
218 | return true; |
219 | } else { |
220 | return false; |
221 | } |
222 | } |
223 | |
224 | bool Path::disconnect() |
225 | { |
226 | if (!isValid()) { |
227 | return false; |
228 | } |
229 | |
230 | QObjectList list; |
231 | if (d->sourceNode) |
232 | list << d->sourceNode->k_ptr->backendObject(); |
233 | #ifndef QT_NO_PHONON_EFFECT |
234 | for (int i = 0; i < d->effects.count(); ++i) { |
235 | list << d->effects.at(i)->k_ptr->backendObject(); |
236 | } |
237 | #endif |
238 | if (d->sinkNode) { |
239 | list << d->sinkNode->k_ptr->backendObject(); |
240 | } |
241 | |
242 | //lets build the disconnection list |
243 | QList<QObjectPair> disco; |
244 | if (list.count() >=2 ) { |
245 | QObjectList::const_iterator it = list.constBegin(); |
246 | for(;it+1 != list.constEnd();++it) { |
247 | disco << QObjectPair(*it, *(it+1)); |
248 | } |
249 | } |
250 | |
251 | if (d->executeTransaction(disconnections: disco, connections: QList<QObjectPair>())) { |
252 | //everything went well, let's remove the reference |
253 | //to the paths from the source and sink |
254 | if (d->sourceNode) { |
255 | d->sourceNode->k_ptr->removeOutputPath(*this); |
256 | d->sourceNode->k_ptr->removeDestructionHandler(handler: d.data()); |
257 | } |
258 | d->sourceNode = nullptr; |
259 | |
260 | #ifndef QT_NO_PHONON_EFFECT |
261 | for (int i = 0; i < d->effects.count(); ++i) { |
262 | d->effects.at(i)->k_ptr->removeDestructionHandler(handler: d.data()); |
263 | } |
264 | d->effects.clear(); |
265 | #endif |
266 | |
267 | if (d->sinkNode) { |
268 | d->sinkNode->k_ptr->removeInputPath(*this); |
269 | d->sinkNode->k_ptr->removeDestructionHandler(handler: d.data()); |
270 | } |
271 | d->sinkNode = nullptr; |
272 | return true; |
273 | } else { |
274 | return false; |
275 | } |
276 | } |
277 | |
278 | MediaNode *Path::source() const |
279 | { |
280 | return d->sourceNode; |
281 | } |
282 | |
283 | MediaNode *Path::sink() const |
284 | { |
285 | return d->sinkNode; |
286 | } |
287 | |
288 | |
289 | |
290 | bool PathPrivate::executeTransaction( const QList<QObjectPair> &disconnections, const QList<QObjectPair> &connections) |
291 | { |
292 | QSet<QObject*> nodesForTransaction; |
293 | for (int i = 0; i < disconnections.count(); ++i) { |
294 | const QObjectPair &pair = disconnections.at(i); |
295 | nodesForTransaction << pair.first; |
296 | nodesForTransaction << pair.second; |
297 | } |
298 | for (int i = 0; i < connections.count(); ++i) { |
299 | const QObjectPair &pair = connections.at(i); |
300 | nodesForTransaction << pair.first; |
301 | nodesForTransaction << pair.second; |
302 | } |
303 | BackendInterface *backend = qobject_cast<BackendInterface *>(object: Factory::backend()); |
304 | if (!backend) |
305 | return false; |
306 | |
307 | ConnectionTransaction transaction(backend, nodesForTransaction); |
308 | if (!transaction) |
309 | return false; |
310 | |
311 | QList<QObjectPair>::const_iterator it = disconnections.begin(); |
312 | for(;it != disconnections.end();++it) { |
313 | const QObjectPair &pair = *it; |
314 | if (!backend->disconnectNodes(pair.first, pair.second)) { |
315 | |
316 | //Error: a disconnection failed |
317 | QList<QObjectPair>::const_iterator it2 = disconnections.begin(); |
318 | for(; it2 != it; ++it2) { |
319 | const QObjectPair &pair = *it2; |
320 | bool success = backend->connectNodes(pair.first, pair.second); |
321 | Q_ASSERT(success); //a failure here means it is impossible to reestablish the connection |
322 | Q_UNUSED(success); |
323 | } |
324 | return false; |
325 | } |
326 | } |
327 | |
328 | for(it = connections.begin(); it != connections.end();++it) { |
329 | const QObjectPair &pair = *it; |
330 | if (!backend->connectNodes(pair.first, pair.second)) { |
331 | //Error: a connection failed |
332 | QList<QObjectPair>::const_iterator it2 = connections.begin(); |
333 | for(; it2 != it; ++it2) { |
334 | const QObjectPair &pair = *it2; |
335 | bool success = backend->disconnectNodes(pair.first, pair.second); |
336 | Q_ASSERT(success); //a failure here means it is impossible to reestablish the connection |
337 | Q_UNUSED(success); |
338 | } |
339 | |
340 | //and now let's reconnect the nodes that were disconnected: rollback |
341 | for (int i = 0; i < disconnections.count(); ++i) { |
342 | const QObjectPair &pair = disconnections.at(i); |
343 | bool success = backend->connectNodes(pair.first, pair.second); |
344 | Q_ASSERT(success); //a failure here means it is impossible to reestablish the connection |
345 | Q_UNUSED(success); |
346 | } |
347 | |
348 | return false; |
349 | |
350 | } |
351 | } |
352 | return true; |
353 | } |
354 | |
355 | #ifndef QT_NO_PHONON_EFFECT |
356 | bool PathPrivate::removeEffect(Effect *effect) |
357 | { |
358 | if (!effects.contains(t: effect)) |
359 | return false; |
360 | |
361 | QObject *leftNode = nullptr; |
362 | QObject *rightNode = nullptr; |
363 | const int index = effects.indexOf(t: effect); |
364 | if (index == 0) { |
365 | leftNode = sourceNode->k_ptr->backendObject(); //append |
366 | } else { |
367 | leftNode = effects[index - 1]->k_ptr->backendObject(); |
368 | } |
369 | if (index == effects.size()-1) { |
370 | rightNode = sinkNode->k_ptr->backendObject(); //prepend |
371 | } else { |
372 | rightNode = effects[index + 1]->k_ptr->backendObject(); |
373 | } |
374 | |
375 | QList<QObjectPair> disconnections, connections; |
376 | QObject *beffect = effect->k_ptr->backendObject(); |
377 | disconnections << QObjectPair(leftNode, beffect) << QObjectPair(beffect, rightNode); |
378 | connections << QObjectPair(leftNode, rightNode); |
379 | |
380 | if (executeTransaction(disconnections, connections)) { |
381 | effect->k_ptr->removeDestructionHandler(handler: this); |
382 | effects.removeAt(i: index); |
383 | return true; |
384 | } |
385 | return false; |
386 | } |
387 | #endif //QT_NO_PHONON_EFFECT |
388 | |
389 | |
390 | void PathPrivate::phononObjectDestroyed(MediaNodePrivate *mediaNodePrivate) |
391 | { |
392 | Q_ASSERT(mediaNodePrivate); |
393 | if (mediaNodePrivate == sinkNode->k_ptr || mediaNodePrivate == sourceNode->k_ptr) { |
394 | //let's first disconnectq the path from its source and sink |
395 | QObject *bsink = sinkNode->k_ptr->backendObject(); |
396 | QObject *bsource = sourceNode->k_ptr->backendObject(); |
397 | QList<QObjectPair> disconnections; |
398 | #ifndef QT_NO_PHONON_EFFECT |
399 | disconnections << QObjectPair(bsource, effects.isEmpty() ? bsink : effects.first()->k_ptr->backendObject()); |
400 | if (!effects.isEmpty()) |
401 | disconnections << QObjectPair(effects.last()->k_ptr->backendObject(), bsink); |
402 | #else |
403 | disconnections << QObjectPair(bsource, bsink); |
404 | #endif //QT_NO_PHONON_EFFECT |
405 | |
406 | executeTransaction(disconnections, connections: QList<QObjectPair>()); |
407 | |
408 | Path p; //temporary path |
409 | p.d = this; |
410 | if (mediaNodePrivate == sinkNode->k_ptr) { |
411 | sourceNode->k_ptr->removeOutputPath(p); |
412 | sourceNode->k_ptr->removeDestructionHandler(handler: this); |
413 | } else { |
414 | sinkNode->k_ptr->removeInputPath(p); |
415 | sinkNode->k_ptr->removeDestructionHandler(handler: this); |
416 | } |
417 | sourceNode = nullptr; |
418 | sinkNode = nullptr; |
419 | } else { |
420 | #ifndef QT_NO_PHONON_EFFECT |
421 | for (int i = 0; i < effects.count(); ++i) { |
422 | Effect *e = effects.at(i); |
423 | if (e->k_ptr == mediaNodePrivate) { |
424 | removeEffect(effect: e); |
425 | } |
426 | } |
427 | #endif //QT_NO_PHONON_EFFECT |
428 | } |
429 | } |
430 | |
431 | Path createPath(MediaNode *source, MediaNode *sink) |
432 | { |
433 | Path p; |
434 | if (!p.reconnect(source, sink)) { |
435 | const QObject *const src = source ? (source->k_ptr->qObject() |
436 | #ifndef QT_NO_DYNAMIC_CAST |
437 | ? source->k_ptr->qObject() : dynamic_cast<QObject *>(source) |
438 | #endif |
439 | ) : nullptr; |
440 | const QObject *const snk = sink ? (sink->k_ptr->qObject() |
441 | #ifndef QT_NO_DYNAMIC_CAST |
442 | ? sink->k_ptr->qObject() : dynamic_cast<QObject *>(sink) |
443 | #endif |
444 | ) : nullptr; |
445 | pWarning() << "Phonon::createPath: Cannot connect " |
446 | << (src ? src->metaObject()->className() : "" ) |
447 | << '(' << (src ? (src->objectName().isEmpty() ? "no objectName" : qPrintable(src->objectName())) : "null" ) << ") to " |
448 | << (snk ? snk->metaObject()->className() : "" ) |
449 | << '(' << (snk ? (snk->objectName().isEmpty() ? "no objectName" : qPrintable(snk->objectName())) : "null" ) |
450 | << ")." ; |
451 | } |
452 | return p; |
453 | } |
454 | |
455 | |
456 | Path & Path::operator=(const Path &other) |
457 | { |
458 | d = other.d; |
459 | return *this; |
460 | } |
461 | |
462 | bool Path::operator==(const Path &other) const |
463 | { |
464 | return d == other.d; |
465 | } |
466 | |
467 | bool Path::operator!=(const Path &other) const |
468 | { |
469 | return !operator==(other); |
470 | } |
471 | |
472 | } // namespace Phonon |
473 | |