/* This file is part of the KDE project | |
Copyright (C) 2007 Matthias Kretz <kretz@kde.org> | |
This library is free software; you can redistribute it and/or | |
modify it under the terms of the GNU Lesser General Public | |
License as published by the Free Software Foundation; either | |
version 2.1 of the License, or (at your option) version 3, or any | |
later version accepted by the membership of KDE e.V. (or its | |
successor approved by the membership of KDE e.V.), Nokia Corporation | |
(or its successors, if any) and the KDE Free Qt Foundation, which shall | |
act as a proxy defined in Section 6 of version 3 of the license. | |
This library is distributed in the hope that it will be useful, | |
but WITHOUT ANY WARRANTY; without even the implied warranty of | |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
Lesser General Public License for more details. | |
You should have received a copy of the GNU Lesser General Public | |
License along with this library. If not, see <http://www.gnu.org/licenses/>. | |
*/ | |
#include "path.h" | |
#include "path_p.h" | |
#include "phononnamespace_p.h" | |
#include "backendinterface.h" | |
#include "factory_p.h" | |
#include "medianode.h" | |
#include "medianode_p.h" | |
QT_BEGIN_NAMESPACE | |
namespace Phonon | |
{ | |
class ConnectionTransaction | |
{ | |
public: | |
ConnectionTransaction(BackendInterface *b, const QSet<QObject*> &x) : backend(b), list(x) | |
{ | |
success = backend->startConnectionChange(list); | |
} | |
~ConnectionTransaction() | |
{ | |
backend->endConnectionChange(list); | |
} | |
operator bool() | |
{ | |
return success; | |
} | |
private: | |
bool success; | |
BackendInterface *const backend; | |
const QSet<QObject*> list; | |
}; | |
PathPrivate::~PathPrivate() | |
{ | |
#ifndef QT_NO_PHONON_EFFECT | |
for (int i = 0; i < effects.count(); ++i) { | |
effects.at(i)->k_ptr->removeDestructionHandler(this); | |
} | |
delete effectsParent; | |
#endif | |
} | |
Path::~Path() | |
{ | |
} | |
Path::Path() | |
: d(new PathPrivate) | |
{ | |
} | |
Path::Path(const Path &rhs) | |
: d(rhs.d) | |
{ | |
} | |
bool Path::isValid() const | |
{ | |
return d->sourceNode != 0 && d->sinkNode != 0; | |
} | |
#ifndef QT_NO_PHONON_EFFECT | |
Effect *Path::insertEffect(const EffectDescription &desc, Effect *insertBefore) | |
{ | |
if (!d->effectsParent) { | |
d->effectsParent = new QObject; | |
} | |
Effect *e = new Effect(desc, d->effectsParent); | |
if (!e->isValid()) { | |
delete e; | |
return 0; | |
} | |
bool success = insertEffect(e, insertBefore); | |
if (!success) { | |
delete e; | |
return 0; | |
} | |
return e; | |
} | |
bool Path::insertEffect(Effect *newEffect, Effect *insertBefore) | |
{ | |
QObject *newEffectBackend = newEffect ? newEffect->k_ptr->backendObject() : 0; | |
if (!isValid() || !newEffectBackend || d->effects.contains(newEffect) || | |
(insertBefore && (!d->effects.contains(insertBefore) || !insertBefore->k_ptr->backendObject()))) { | |
return false; | |
} | |
QObject *leftNode = 0; | |
QObject *rightNode = 0; | |
const int insertIndex = insertBefore ? d->effects.indexOf(insertBefore) : d->effects.size(); | |
if (insertIndex == 0) { | |
//prepend | |
leftNode = d->sourceNode->k_ptr->backendObject(); | |
} else { | |
leftNode = d->effects[insertIndex - 1]->k_ptr->backendObject(); | |
} | |
if (insertIndex == d->effects.size()) { | |
//append | |
rightNode = d->sinkNode->k_ptr->backendObject(); | |
} else { | |
Q_ASSERT(insertBefore); | |
rightNode = insertBefore->k_ptr->backendObject(); | |
} | |
QList<QObjectPair> disconnections, connections; | |
disconnections << QObjectPair(leftNode, rightNode); | |
connections << QObjectPair(leftNode, newEffectBackend) | |
<< QObjectPair(newEffectBackend, rightNode); | |
if (d->executeTransaction(disconnections, connections)) { | |
newEffect->k_ptr->addDestructionHandler(d.data()); | |
d->effects.insert(insertIndex, newEffect); | |
return true; | |
} else { | |
return false; | |
} | |
} | |
bool Path::removeEffect(Effect *effect) | |
{ | |
return d->removeEffect(effect); | |
} | |
QList<Effect *> Path::effects() const | |
{ | |
return d->effects; | |
} | |
#endif //QT_NO_PHONON_EFFECT | |
bool Path::reconnect(MediaNode *source, MediaNode *sink) | |
{ | |
if (!source || !sink || !source->k_ptr->backendObject() || !sink->k_ptr->backendObject()) { | |
return false; | |
} | |
QList<QObjectPair> disconnections, connections; | |
//backend objects | |
QObject *bnewSource = source->k_ptr->backendObject(); | |
QObject *bnewSink = sink->k_ptr->backendObject(); | |
QObject *bcurrentSource = d->sourceNode ? d->sourceNode->k_ptr->backendObject() : 0; | |
QObject *bcurrentSink = d->sinkNode ? d->sinkNode->k_ptr->backendObject() : 0; | |
if (bnewSource != bcurrentSource) { | |
//we need to change the source | |
#ifndef QT_NO_PHONON_EFFECT | |
MediaNode *next = d->effects.isEmpty() ? sink : d->effects.first(); | |
#else | |
MediaNode *next = sink; | |
#endif //QT_NO_PHONON_EFFECT | |
QObject *bnext = next->k_ptr->backendObject(); | |
if (bcurrentSource) | |
disconnections << QObjectPair(bcurrentSource, bnext); | |
connections << QObjectPair(bnewSource, bnext); | |
} | |
if (bnewSink != bcurrentSink) { | |
#ifndef QT_NO_PHONON_EFFECT | |
MediaNode *previous = d->effects.isEmpty() ? source : d->effects.last(); | |
#else | |
MediaNode *previous = source; | |
#endif //QT_NO_PHONON_EFFECT | |
QObject *bprevious = previous->k_ptr->backendObject(); | |
if (bcurrentSink) | |
disconnections << QObjectPair(bprevious, bcurrentSink); | |
QObjectPair pair(bprevious, bnewSink); | |
if (!connections.contains(pair)) //avoid connecting twice | |
connections << pair; | |
} | |
if (d->executeTransaction(disconnections, connections)) { | |
//everything went well: let's update the path and the sink node | |
if (d->sinkNode != sink) { | |
if (d->sinkNode) { | |
d->sinkNode->k_ptr->removeInputPath(*this); | |
d->sinkNode->k_ptr->removeDestructionHandler(d.data()); | |
} | |
sink->k_ptr->addInputPath(*this); | |
d->sinkNode = sink; | |
d->sinkNode->k_ptr->addDestructionHandler(d.data()); | |
} | |
//everything went well: let's update the path and the source node | |
if (d->sourceNode != source) { | |
source->k_ptr->addOutputPath(*this); | |
if (d->sourceNode) { | |
d->sourceNode->k_ptr->removeOutputPath(*this); | |
d->sourceNode->k_ptr->removeDestructionHandler(d.data()); | |
} | |
d->sourceNode = source; | |
d->sourceNode->k_ptr->addDestructionHandler(d.data()); | |
} | |
return true; | |
} else { | |
return false; | |
} | |
} | |
bool Path::disconnect() | |
{ | |
if (!isValid()) { | |
return false; | |
} | |
QObjectList list; | |
if (d->sourceNode) | |
list << d->sourceNode->k_ptr->backendObject(); | |
#ifndef QT_NO_PHONON_EFFECT | |
for (int i = 0; i < d->effects.count(); ++i) { | |
list << d->effects.at(i)->k_ptr->backendObject(); | |
} | |
#endif | |
if (d->sinkNode) { | |
list << d->sinkNode->k_ptr->backendObject(); | |
} | |
//lets build the disconnection list | |
QList<QObjectPair> disco; | |
if (list.count() >=2 ) { | |
QObjectList::const_iterator it = list.constBegin(); | |
for(;it+1 != list.constEnd();++it) { | |
disco << QObjectPair(*it, *(it+1)); | |
} | |
} | |
if (d->executeTransaction(disco, QList<QObjectPair>())) { | |
//everything went well, let's remove the reference | |
//to the paths from the source and sink | |
if (d->sourceNode) { | |
d->sourceNode->k_ptr->removeOutputPath(*this); | |
d->sourceNode->k_ptr->removeDestructionHandler(d.data()); | |
} | |
d->sourceNode = 0; | |
#ifndef QT_NO_PHONON_EFFECT | |
for (int i = 0; i < d->effects.count(); ++i) { | |
d->effects.at(i)->k_ptr->removeDestructionHandler(d.data()); | |
} | |
d->effects.clear(); | |
#endif | |
if (d->sinkNode) { | |
d->sinkNode->k_ptr->removeInputPath(*this); | |
d->sinkNode->k_ptr->removeDestructionHandler(d.data()); | |
} | |
d->sinkNode = 0; | |
return true; | |
} else { | |
return false; | |
} | |
} | |
MediaNode *Path::source() const | |
{ | |
return d->sourceNode; | |
} | |
MediaNode *Path::sink() const | |
{ | |
return d->sinkNode; | |
} | |
bool PathPrivate::executeTransaction( const QList<QObjectPair> &disconnections, const QList<QObjectPair> &connections) | |
{ | |
QSet<QObject*> nodesForTransaction; | |
for (int i = 0; i < disconnections.count(); ++i) { | |
const QObjectPair &pair = disconnections.at(i); | |
nodesForTransaction << pair.first; | |
nodesForTransaction << pair.second; | |
} | |
for (int i = 0; i < connections.count(); ++i) { | |
const QObjectPair &pair = connections.at(i); | |
nodesForTransaction << pair.first; | |
nodesForTransaction << pair.second; | |
} | |
BackendInterface *backend = qobject_cast<BackendInterface *>(Factory::backend()); | |
if (!backend) | |
return false; | |
ConnectionTransaction transaction(backend, nodesForTransaction); | |
if (!transaction) | |
return false; | |
QList<QObjectPair>::const_iterator it = disconnections.begin(); | |
for(;it != disconnections.end();++it) { | |
const QObjectPair &pair = *it; | |
if (!backend->disconnectNodes(pair.first, pair.second)) { | |
//Error: a disconnection failed | |
QList<QObjectPair>::const_iterator it2 = disconnections.begin(); | |
for(; it2 != it; ++it2) { | |
const QObjectPair &pair = *it2; | |
bool success = backend->connectNodes(pair.first, pair.second); | |
Q_ASSERT(success); //a failure here means it is impossible to reestablish the connection | |
Q_UNUSED(success); | |
} | |
return false; | |
} | |
} | |
for(it = connections.begin(); it != connections.end();++it) { | |
const QObjectPair &pair = *it; | |
if (!backend->connectNodes(pair.first, pair.second)) { | |
//Error: a connection failed | |
QList<QObjectPair>::const_iterator it2 = connections.begin(); | |
for(; it2 != it; ++it2) { | |
const QObjectPair &pair = *it2; | |
bool success = backend->disconnectNodes(pair.first, pair.second); | |
Q_ASSERT(success); //a failure here means it is impossible to reestablish the connection | |
Q_UNUSED(success); | |
} | |
//and now let's reconnect the nodes that were disconnected: rollback | |
for (int i = 0; i < disconnections.count(); ++i) { | |
const QObjectPair &pair = disconnections.at(i); | |
bool success = backend->connectNodes(pair.first, pair.second); | |
Q_ASSERT(success); //a failure here means it is impossible to reestablish the connection | |
Q_UNUSED(success); | |
} | |
return false; | |
} | |
} | |
return true; | |
} | |
#ifndef QT_NO_PHONON_EFFECT | |
bool PathPrivate::removeEffect(Effect *effect) | |
{ | |
if (!effects.contains(effect)) | |
return false; | |
QObject *leftNode = 0; | |
QObject *rightNode = 0; | |
const int index = effects.indexOf(effect); | |
if (index == 0) { | |
leftNode = sourceNode->k_ptr->backendObject(); //append | |
} else { | |
leftNode = effects[index - 1]->k_ptr->backendObject(); | |
} | |
if (index == effects.size()-1) { | |
rightNode = sinkNode->k_ptr->backendObject(); //prepend | |
} else { | |
rightNode = effects[index + 1]->k_ptr->backendObject(); | |
} | |
QList<QObjectPair> disconnections, connections; | |
QObject *beffect = effect->k_ptr->backendObject(); | |
disconnections << QObjectPair(leftNode, beffect) << QObjectPair(beffect, rightNode); | |
connections << QObjectPair(leftNode, rightNode); | |
if (executeTransaction(disconnections, connections)) { | |
effect->k_ptr->removeDestructionHandler(this); | |
effects.removeAt(index); | |
return true; | |
} | |
return false; | |
} | |
#endif //QT_NO_PHONON_EFFECT | |
void PathPrivate::phononObjectDestroyed(MediaNodePrivate *mediaNodePrivate) | |
{ | |
Q_ASSERT(mediaNodePrivate); | |
if (mediaNodePrivate == sinkNode->k_ptr || mediaNodePrivate == sourceNode->k_ptr) { | |
//let's first disconnectq the path from its source and sink | |
QObject *bsink = sinkNode->k_ptr->backendObject(); | |
QObject *bsource = sourceNode->k_ptr->backendObject(); | |
QList<QObjectPair> disconnections; | |
#ifndef QT_NO_PHONON_EFFECT | |
disconnections << QObjectPair(bsource, effects.isEmpty() ? bsink : effects.first()->k_ptr->backendObject()); | |
if (!effects.isEmpty()) | |
disconnections << QObjectPair(effects.last()->k_ptr->backendObject(), bsink); | |
#else | |
disconnections << QObjectPair(bsource, bsink); | |
#endif //QT_NO_PHONON_EFFECT | |
executeTransaction(disconnections, QList<QObjectPair>()); | |
Path p; //temporary path | |
p.d = this; | |
if (mediaNodePrivate == sinkNode->k_ptr) { | |
sourceNode->k_ptr->removeOutputPath(p); | |
sourceNode->k_ptr->removeDestructionHandler(this); | |
} else { | |
sinkNode->k_ptr->removeInputPath(p); | |
sinkNode->k_ptr->removeDestructionHandler(this); | |
} | |
sourceNode = 0; | |
sinkNode = 0; | |
} else { | |
#ifndef QT_NO_PHONON_EFFECT | |
for (int i = 0; i < effects.count(); ++i) { | |
Effect *e = effects.at(i); | |
if (e->k_ptr == mediaNodePrivate) { | |
removeEffect(e); | |
} | |
} | |
#endif //QT_NO_PHONON_EFFECT | |
} | |
} | |
Path createPath(MediaNode *source, MediaNode *sink) | |
{ | |
Path p; | |
if (!p.reconnect(source, sink)) { | |
const QObject *const src = source ? (source->k_ptr->qObject() | |
#ifndef QT_NO_DYNAMIC_CAST | |
? source->k_ptr->qObject() : dynamic_cast<QObject *>(source) | |
#endif | |
) : 0; | |
const QObject *const snk = sink ? (sink->k_ptr->qObject() | |
#ifndef QT_NO_DYNAMIC_CAST | |
? sink->k_ptr->qObject() : dynamic_cast<QObject *>(sink) | |
#endif | |
) : 0; | |
pWarning() << "Phonon::createPath: Cannot connect " | |
<< (src ? src->metaObject()->className() : "") | |
<< '(' << (src ? (src->objectName().isEmpty() ? "no objectName" : qPrintable(src->objectName())) : "null") << ") to " | |
<< (snk ? snk->metaObject()->className() : "") | |
<< '(' << (snk ? (snk->objectName().isEmpty() ? "no objectName" : qPrintable(snk->objectName())) : "null") | |
<< ")."; | |
} | |
return p; | |
} | |
Path & Path::operator=(const Path &other) | |
{ | |
d = other.d; | |
return *this; | |
} | |
bool Path::operator==(const Path &other) const | |
{ | |
return d == other.d; | |
} | |
bool Path::operator!=(const Path &other) const | |
{ | |
return !operator==(other); | |
} | |
} // namespace Phonon | |
QT_END_NAMESPACE |