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