/**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the Qt Mobility Components. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qvaluespacesubscriber.h" #include "qvaluespace_p.h" #include "qvaluespacemanager_p.h" #include #include #include #include #include QTM_BEGIN_NAMESPACE /*! \class QValueSpaceSubscriber \brief The QValueSpaceSubscriber class allows applications to read and subscribe to Value Space paths. \inmodule QtPublishSubscribe \ingroup publishsubscribe \since 1.0 By default QValueSpaceSubscriber can read values from and report change notifications for all available Value Space layers. Only data from the layer with the highest order and that contains the specific key is returned by this class. The layers that QValueSpaceSubscriber accesses can be limited by specifying either a \l {QValueSpace::LayerOptions}{filter} or a QUuid at construction time. Applications subscribe to a particular path in the Value Space. If anything under that path changes the contextChanged() signal is emitted. For example given the schema: \code /Device/Buttons = 3 /Device/Buttons/1/Name = Menu /Device/Buttons/1/Usable = true /Device/Buttons/2/Name = Select /Device/Buttons/2/Usable = false /Device/Buttons/3/Name = Back /Device/Buttons/3/Usable = true \endcode The code: \code QValueSpaceSubscriber *buttons = new QValueSpaceSubscriber("/Device/Buttons"); QObject::connect(buttons, SIGNAL(contentsChanged()), this, SLOT(buttonInfoChanged())); \endcode will invoke the \c {buttonInfoChanged()} slot whenever any value under \c {/Device/Buttons} changes. This includes the value of \c {/Device/Buttons} itself, a change of a subpath such as \c {/Device/Buttons/2/Name} or the creation or removal of a subpath. \since 1.0 */ /*! \fn QValueSpaceSubscriber::contentsChanged() Emitted whenever any value under the current path changes. \bold {Note:} that if a value changes multiple times in quick succession, only the most recent value may be accessible via the value() function. \since 1.0 */ /*! \property QValueSpaceSubscriber::path This property holds the current path that the QValueSpaceSubscriber refers to. Settings this property causes the QValueSpaceSubscriber to disconnect and reconnect to the Value Space with the new path. As a result all signal/slot connections are disconnected. \since 1.0 */ /*! \property QValueSpaceSubscriber::value This property holds the value of the path that this QValueSpaceSubscriber refers to. \since 1.0 */ class QValueSpaceSubscriberPrivateProxy : public QObject { Q_OBJECT signals: void changed(); public slots: void handleChanged(quintptr handle) { QAbstractValueSpaceLayer *layer = qobject_cast(sender()); for (int i = 0; i < readers.count(); ++i) { if (readers.at(i).first == layer && readers.at(i).second == handle) { emit changed(); return; } } } public: QList > readers; QHash connections; }; typedef QList > LayerList; class QValueSpaceSubscriberPrivate : public QSharedData { public: QValueSpaceSubscriberPrivate(const QString &_path, QValueSpace::LayerOptions filter = QValueSpace::UnspecifiedLayer); QValueSpaceSubscriberPrivate(const QString &_path, const QUuid &uuid); ~QValueSpaceSubscriberPrivate(); void connect(const QValueSpaceSubscriber *space) const; bool disconnect(QValueSpaceSubscriber * space); const QString path; #ifdef QT_SIMULATOR const QString originalPath; #endif const LayerList readers; mutable QMutex lock; mutable QValueSpaceSubscriberPrivateProxy *connections; }; static LayerList matchLayers(const QString &path, QValueSpace::LayerOptions filter) { LayerList list; QValueSpaceManager *manager = QValueSpaceManager::instance(); if (!manager) return list; // Invalid filter combination. if ((filter & QValueSpace::PermanentLayer && filter & QValueSpace::TransientLayer) || (filter & QValueSpace::WritableLayer && filter & QValueSpace::ReadOnlyLayer)) { return list; } const QList &readerList = manager->getLayers(); for (int ii = 0; ii < readerList.count(); ++ii) { QAbstractValueSpaceLayer *read = readerList.at(ii); if (filter != QValueSpace::UnspecifiedLayer && !(read->layerOptions() & filter)) { continue; } QAbstractValueSpaceLayer::Handle handle = read->item(QAbstractValueSpaceLayer::InvalidHandle, path); if (QAbstractValueSpaceLayer::InvalidHandle != handle) { list.append(qMakePair(read, handle)); read->notifyInterest(handle, true); } } return list; } QValueSpaceSubscriberPrivate::QValueSpaceSubscriberPrivate(const QString &path, QValueSpace::LayerOptions filter) #ifndef QT_SIMULATOR : path(qCanonicalPath(path)), #else : path(qAddSimulatorPrefix(qCanonicalPath(path))), originalPath(qCanonicalPath(path)), #endif readers(matchLayers(this->path, filter)), connections(0) { } static LayerList matchLayers(const QString &path, const QUuid &uuid) { LayerList list; QValueSpaceManager *manager = QValueSpaceManager::instance(); if (!manager) return list; const QList &readerList = manager->getLayers(); for (int ii = 0; ii < readerList.count(); ++ii) { QAbstractValueSpaceLayer *read = readerList.at(ii); if (read->id() != uuid) continue; QAbstractValueSpaceLayer::Handle handle = read->item(QAbstractValueSpaceLayer::InvalidHandle, path); if (QAbstractValueSpaceLayer::InvalidHandle != handle) { list.append(qMakePair(read, handle)); read->notifyInterest(handle, true); } } return list; } QValueSpaceSubscriberPrivate::QValueSpaceSubscriberPrivate(const QString &path, const QUuid &uuid) #ifndef QT_SIMULATOR : path(qCanonicalPath(path)), #else : path(qAddSimulatorPrefix(qCanonicalPath(path))), originalPath(qCanonicalPath(path)), #endif readers(matchLayers(this->path, uuid)), connections(0) { } QValueSpaceSubscriberPrivate::~QValueSpaceSubscriberPrivate() { for (int ii = 0; ii < readers.count(); ++ii) { readers[ii].first->notifyInterest(readers[ii].second, false); readers[ii].first->removeHandle(readers[ii].second); } if (connections) delete connections; } void QValueSpaceSubscriberPrivate::connect(const QValueSpaceSubscriber *space) const { QMutexLocker locker(&lock); if (!connections) { qRegisterMetaType("quintptr"); connections = new QValueSpaceSubscriberPrivateProxy; connections->readers = readers; connections->connections.insert(space,1); QObject::connect(connections, SIGNAL(changed()), space, SIGNAL(contentsChanged())); for (int ii = 0; ii < readers.count(); ++ii) { readers.at(ii).first->setProperty(readers.at(ii).second, QAbstractValueSpaceLayer::Publish); QObject::connect(readers.at(ii).first, SIGNAL(handleChanged(quintptr)), connections, SLOT(handleChanged(quintptr))); } } else if (!connections->connections.contains(space)) { connections->connections[space] = 1; QObject::connect(connections, SIGNAL(changed()), space, SIGNAL(contentsChanged())); } else { ++connections->connections[space]; } } bool QValueSpaceSubscriberPrivate::disconnect(QValueSpaceSubscriber * space) { QMutexLocker locker(&lock); if (connections) { QHash::Iterator iter = connections->connections.find(space); if (iter != connections->connections.end()) { --(*iter); if (!*iter) { QObject::disconnect(connections, SIGNAL(changed()), space, SIGNAL(contentsChanged())); connections->connections.erase(iter); } return true; } } return false; } /*! Constructs a QValueSpaceSubscriber with the specified \a parent that refers to the root path. The constructed Value Space subscriber will access all available layers. \since 1.0 */ QValueSpaceSubscriber::QValueSpaceSubscriber(QObject *parent) : QObject(parent) { d = new QValueSpaceSubscriberPrivate(QLatin1String("/")); } /*! Constructs a QValueSpaceSubscriber with the specified \a parent that refers to \a path. The constructed Value Space subscriber will access all available layers. \since 1.0 */ QValueSpaceSubscriber::QValueSpaceSubscriber(const QString &path, QObject *parent) : QObject(parent) { d = new QValueSpaceSubscriberPrivate(path); } /*! Constructs a QValueSpaceSubscriber with the specified \a parent that refers to \a path. The \a filter parameter is used to limit which layers this QValueSpaceSubscriber will access. If a layer matching \a filter is not found, the constructed QValueSpaceSubscriber will be unconnected. \sa isConnected() \since 1.0 */ QValueSpaceSubscriber::QValueSpaceSubscriber(QValueSpace::LayerOptions filter, const QString &path, QObject *parent) : QObject(parent) { d = new QValueSpaceSubscriberPrivate(path, filter); } /*! Constructs a QValueSpaceSubscriber with the specified \a parent that refers to \a path. This QValueSpaceSubscriber will only use the layer identified by \a uuid. Use of this constructor is not platform agnostic. If possible use one of the constructors that take a QValueSpace::LayerOptions parameter instead. If a layer with a matching \a uuid is not found, the constructed QValueSpaceSubscriber will be unconnected. \sa QValueSpace, isConnected() \since 1.0 */ QValueSpaceSubscriber::QValueSpaceSubscriber(const QUuid &uuid, const QString &path, QObject *parent) : QObject(parent) { d = new QValueSpaceSubscriberPrivate(path, uuid); } /*! Destroys the QValueSpaceSubscriber. \since 1.0 */ QValueSpaceSubscriber::~QValueSpaceSubscriber() { d->disconnect(this); } /*! Sets the path to \a path. Calling this function causes the QValueSpaceSubscriber to disconnect and reconnect to the value space with the new \a path. Calling this function disconnects all signal/slot connections. \since 1.0 */ void QValueSpaceSubscriber::setPath(const QString &path) { if (this->path() == path) return; d->disconnect(this); disconnect(); d = new QValueSpaceSubscriberPrivate(path); } /*! Sets the path to the same path as \a subscriber. Calling this function causes the QValueSpaceSubscriber to disconnect and reconnect to the value space with the specified \a path. Calling this function disconnects all signal/slot connections. \since 1.0 */ void QValueSpaceSubscriber::setPath(QValueSpaceSubscriber *subscriber) { d->disconnect(this); disconnect(); d = subscriber->d; } QString QValueSpaceSubscriber::path() const { #ifndef QT_SIMULATOR return d->path; #else return d->originalPath; #endif } /*! Changes the path to the absolute path if \a path starts with a '/'; otherwise changes to the sub path of the current path. \since 1.0 */ void QValueSpaceSubscriber::cd(const QString &path) { if (path.startsWith(QLatin1Char('/'))) setPath(path); else setPath(this->path() + QLatin1Char('/') + path); } /*! Sets the path to parent of the current path. \since 1.0 */ void QValueSpaceSubscriber::cdUp() { if (path() == QLatin1String("/")) return; QString p(path()); int index = p.lastIndexOf(QLatin1Char('/')); p.truncate(index); setPath(p); } /*! Returns true if this QValueSpaceSubscriber is connected to at least one available layer; otherwise returns false. An unconnected QValueSpaceSubscriber is constructed if the filtering parameters passed to the constructor eliminate all available layers. \since 1.0 */ bool QValueSpaceSubscriber::isConnected() const { return !d->readers.isEmpty(); } /*! Returns the value of the \a subPath under this subscriber path, or the value of this subscriber path if \a subPath is empty. If the value does not exists \a def is returned. The following code shows how the subscriber path and \a subPath relate. \code QValueSpaceSubscriber base("/Settings"); QValueSpaceSubscriber equiv("/Settings/Nokia/General/Mappings"); // Is true equiv.value() == base.value("Nokia/General/Mappings"); \endcode \since 1.0 */ QVariant QValueSpaceSubscriber::value(const QString & subPath, const QVariant &def) const { QVariant value; if (subPath.isEmpty()) { for (int ii = d->readers.count(); ii > 0; --ii) { if (d->readers[ii - 1].first->value(d->readers[ii - 1].second, &value)) return value; } } else { const QString vpath(qCanonicalPath(subPath)); for (int ii = d->readers.count(); ii > 0; --ii) { if (d->readers[ii - 1].first->value(d->readers[ii - 1].second, vpath, &value)) return value; } } return def; } QVariant QValueSpaceSubscriber::valuex(const QVariant &def) const { QMutexLocker locker(&d->lock); if (!d->connections || d->connections->connections.value(this) == 0) { locker.unlock(); d->connect(this); } return value(QString(), def); } /*! \internal Registers for change notifications in response to connection to the contentsChanged() \a signal. \since 1.0 */ void QValueSpaceSubscriber::connectNotify(const char *signal) { if (QLatin1String(signal) == SIGNAL(contentsChanged())) d->connect(this); else QObject::connectNotify(signal); } /*! \internal Unregisters for change notifications in response to disconnection from the contentsChanged() \a signal. \since 1.0 */ void QValueSpaceSubscriber::disconnectNotify(const char *signal) { if (QLatin1String(signal) == SIGNAL(contentsChanged())) d->disconnect(this); else QObject::disconnectNotify(signal); } /*! Returns a list of sub-paths under the current path. For example, given a Value Space tree containing: \code /Settings/Nokia/Device /Settings/Nokia/Other /Settings/Qt /Device/Buttons \endcode \c { QValueSpaceSubscriber("/Settings").subPaths() } will return a list containing \c { { Nokia, Qt } } in no particular order. \since 1.0 */ QStringList QValueSpaceSubscriber::subPaths() const { QSet rv; for (int ii = 0; ii < d->readers.count(); ++ii) rv.unite(d->readers[ii].first->children(d->readers[ii].second)); QStringList rvs; for (QSet::ConstIterator iter = rv.begin(); iter != rv.end(); ++iter) rvs.append(*iter); return rvs; } #include "moc_qvaluespacesubscriber.cpp" #include "qvaluespacesubscriber.moc" QTM_END_NAMESPACE