1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2015 The Qt Company Ltd and/or its subsidiary(-ies). |
4 | ** Contact: http://www.qt-project.org/legal |
5 | ** |
6 | ** This file is part of the QtSystems module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL21$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see http://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at http://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 2.1 or version 3 as published by the Free |
20 | ** Software Foundation and appearing in the file LICENSE.LGPLv21 and |
21 | ** LICENSE.LGPLv3 included in the packaging of this file. Please review the |
22 | ** following information to ensure the GNU Lesser General Public License |
23 | ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and |
24 | ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
25 | ** |
26 | ** As a special exception, The Qt Company gives you certain additional |
27 | ** rights. These rights are described in The Qt Company LGPL Exception |
28 | ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
29 | ** |
30 | ** $QT_END_LICENSE$ |
31 | ** |
32 | ****************************************************************************/ |
33 | |
34 | #include <qvaluespacesubscriber.h> |
35 | |
36 | #include "qvaluespacemanager_p.h" |
37 | #include "qvaluespacesubscriber_p.h" |
38 | |
39 | #include <QtCore/qmetaobject.h> |
40 | #include <QtCore/qset.h> |
41 | #include <QtCore/qstringlist.h> |
42 | |
43 | QT_BEGIN_NAMESPACE |
44 | |
45 | /*! |
46 | \class QValueSpaceSubscriber |
47 | \brief The QValueSpaceSubscriber class allows applications to read and subscribe to Value Space paths. |
48 | \inmodule QtPublishSubscribe |
49 | \ingroup publishsubscribe |
50 | |
51 | By default QValueSpaceSubscriber can read values from and report change notifications for all |
52 | available Value Space layers. Only data from the layer with the highest priority and that |
53 | contains the specific key is returned by this class. |
54 | |
55 | The layers that QValueSpaceSubscriber accesses can be limited by specifying either a |
56 | \l {QValueSpace::LayerOptions}{filter} or a QUuid at construction time. |
57 | |
58 | Applications subscribe to a particular path in the Value Space. If anything under that path |
59 | changes the contextChanged() signal is emitted. For example given the schema: |
60 | |
61 | \code |
62 | /Device/Buttons = 3 |
63 | /Device/Buttons/1/Name = Menu |
64 | /Device/Buttons/1/Usable = true |
65 | /Device/Buttons/2/Name = Select |
66 | /Device/Buttons/2/Usable = false |
67 | /Device/Buttons/3/Name = Back |
68 | /Device/Buttons/3/Usable = true |
69 | \endcode |
70 | |
71 | The code: |
72 | |
73 | \code |
74 | QValueSpaceSubscriber *buttons = new QValueSpaceSubscriber("/Device/Buttons"); |
75 | QObject::connect(buttons, SIGNAL(contentsChanged()), this, SLOT(buttonInfoChanged())); |
76 | \endcode |
77 | |
78 | will invoke the \c {buttonInfoChanged()} slot whenever any value under \c {/Device/Buttons} |
79 | changes. This includes the value of \c {/Device/Buttons} itself, a change of a subpath such as |
80 | \c {/Device/Buttons/2/Name} or the creation or removal of a subpath. |
81 | */ |
82 | |
83 | /*! |
84 | \fn QValueSpaceSubscriber::contentsChanged() |
85 | |
86 | Emitted whenever any value under the current path changes. |
87 | |
88 | \b {Note:} that if a value changes multiple times in quick succession, only the most recent |
89 | value may be accessible via the value() function. |
90 | */ |
91 | |
92 | void QValueSpaceSubscriberPrivateProxy::handleChanged(quintptr handle) |
93 | { |
94 | QAbstractValueSpaceLayer *layer = qobject_cast<QAbstractValueSpaceLayer *>(object: sender()); |
95 | |
96 | for (int i = 0; i < readers.count(); ++i) { |
97 | if (readers.at(i).first == layer && readers.at(i).second == handle) { |
98 | emit changed(); |
99 | return; |
100 | } |
101 | } |
102 | } |
103 | |
104 | static LayerList matchLayers(const QString &path, QValueSpace::LayerOptions filter) |
105 | { |
106 | LayerList list; |
107 | |
108 | QValueSpaceManager *manager = QValueSpaceManager::instance(); |
109 | if (!manager) |
110 | return list; |
111 | |
112 | // Invalid filter combination. |
113 | if ((filter & QValueSpace::PermanentLayer && filter & QValueSpace::TransientLayer) |
114 | || (filter & QValueSpace::WritableLayer && filter & QValueSpace::ReadOnlyLayer)) { |
115 | return list; |
116 | } |
117 | |
118 | const QList<QAbstractValueSpaceLayer *> &readerList = manager->getLayers(); |
119 | |
120 | for (int ii = 0; ii < readerList.count(); ++ii) { |
121 | QAbstractValueSpaceLayer *read = readerList.at(i: ii); |
122 | if (filter != QValueSpace::UnspecifiedLayer && |
123 | !(read->layerOptions() & filter)) { |
124 | continue; |
125 | } |
126 | |
127 | QAbstractValueSpaceLayer::Handle handle = |
128 | read->item(parent: QAbstractValueSpaceLayer::InvalidHandle, subPath: path); |
129 | if (QAbstractValueSpaceLayer::InvalidHandle != handle) { |
130 | list.append(t: qMakePair(x: read, y: handle)); |
131 | |
132 | read->notifyInterest(handle, interested: true); |
133 | } |
134 | } |
135 | |
136 | return list; |
137 | } |
138 | |
139 | QValueSpaceSubscriberPrivate::QValueSpaceSubscriberPrivate(const QString &path, QValueSpace::LayerOptions filter) |
140 | : path(qCanonicalPath(path)) |
141 | , readers(matchLayers(path: this->path, filter)) |
142 | , connections(0) |
143 | { |
144 | } |
145 | |
146 | static LayerList matchLayers(const QString &path, const QUuid &uuid) |
147 | { |
148 | LayerList list; |
149 | |
150 | QValueSpaceManager *manager = QValueSpaceManager::instance(); |
151 | if (!manager) |
152 | return list; |
153 | |
154 | const QList<QAbstractValueSpaceLayer *> &readerList = manager->getLayers(); |
155 | |
156 | for (int ii = 0; ii < readerList.count(); ++ii) { |
157 | QAbstractValueSpaceLayer *read = readerList.at(i: ii); |
158 | if (read->id() != uuid) |
159 | continue; |
160 | |
161 | QAbstractValueSpaceLayer::Handle handle = |
162 | read->item(parent: QAbstractValueSpaceLayer::InvalidHandle, subPath: path); |
163 | if (QAbstractValueSpaceLayer::InvalidHandle != handle) { |
164 | list.append(t: qMakePair(x: read, y: handle)); |
165 | |
166 | read->notifyInterest(handle, interested: true); |
167 | } |
168 | } |
169 | |
170 | return list; |
171 | } |
172 | |
173 | QValueSpaceSubscriberPrivate::QValueSpaceSubscriberPrivate(const QString &path, const QUuid &uuid) |
174 | : path(qCanonicalPath(path)) |
175 | , readers(matchLayers(path: this->path, uuid)) |
176 | , connections(0) |
177 | { |
178 | } |
179 | |
180 | QValueSpaceSubscriberPrivate::~QValueSpaceSubscriberPrivate() |
181 | { |
182 | for (int ii = 0; ii < readers.count(); ++ii) { |
183 | readers[ii].first->notifyInterest(handle: readers[ii].second, interested: false); |
184 | readers[ii].first->removeHandle(handle: readers[ii].second); |
185 | } |
186 | |
187 | if (connections) |
188 | delete connections; |
189 | } |
190 | |
191 | void QValueSpaceSubscriberPrivate::connect(const QValueSpaceSubscriber *space) const |
192 | { |
193 | QMutexLocker locker(&lock); |
194 | |
195 | if (!connections) { |
196 | qRegisterMetaType<quintptr>(typeName: "quintptr" ); |
197 | connections = new QValueSpaceSubscriberPrivateProxy; |
198 | connections->readers = readers; |
199 | connections->connections.insert(akey: space,avalue: 1); |
200 | QObject::connect(sender: connections, SIGNAL(changed()), |
201 | receiver: space, SIGNAL(contentsChanged())); |
202 | for (int ii = 0; ii < readers.count(); ++ii) { |
203 | readers.at(i: ii).first->setProperty(handle: readers.at(i: ii).second, properties: QAbstractValueSpaceLayer::Publish); |
204 | QObject::connect(sender: readers.at(i: ii).first, SIGNAL(handleChanged(quintptr)), receiver: connections, SLOT(handleChanged(quintptr))); |
205 | } |
206 | } else if (!connections->connections.contains(akey: space)) { |
207 | connections->connections[space] = 1; |
208 | |
209 | QObject::connect(sender: connections, SIGNAL(changed()), receiver: space, SIGNAL(contentsChanged())); |
210 | } else { |
211 | ++connections->connections[space]; |
212 | } |
213 | } |
214 | |
215 | bool QValueSpaceSubscriberPrivate::disconnect(QValueSpaceSubscriber * space) |
216 | { |
217 | QMutexLocker locker(&lock); |
218 | |
219 | if (connections) { |
220 | QHash<const QValueSpaceSubscriber *, int>::Iterator iter = connections->connections.find(akey: space); |
221 | if (iter != connections->connections.end()) { |
222 | --(*iter); |
223 | if (!*iter) { |
224 | QObject::disconnect(sender: connections, SIGNAL(changed()), receiver: space, SIGNAL(contentsChanged())); |
225 | connections->connections.erase(it: iter); |
226 | } |
227 | return true; |
228 | } |
229 | } |
230 | return false; |
231 | } |
232 | |
233 | /*! |
234 | Constructs a QValueSpaceSubscriber with the specified \a parent that refers to the root path. |
235 | |
236 | The constructed Value Space subscriber will access all available layers. |
237 | */ |
238 | QValueSpaceSubscriber::QValueSpaceSubscriber(QObject *parent) |
239 | : QObject(parent) |
240 | { |
241 | d = new QValueSpaceSubscriberPrivate(QLatin1String("/" )); |
242 | } |
243 | |
244 | /*! |
245 | Constructs a QValueSpaceSubscriber with the specified \a parent that refers to \a path. |
246 | |
247 | The constructed Value Space subscriber will access all available layers. |
248 | */ |
249 | QValueSpaceSubscriber::QValueSpaceSubscriber(const QString &path, QObject *parent) |
250 | : QObject(parent) |
251 | { |
252 | d = new QValueSpaceSubscriberPrivate(path); |
253 | } |
254 | |
255 | /*! |
256 | Constructs a QValueSpaceSubscriber with the specified \a parent that refers to \a path. The |
257 | \a filter parameter is used to limit which layers this QValueSpaceSubscriber will access. |
258 | |
259 | If a layer matching \a filter is not found, the constructed QValueSpaceSubscriber will be |
260 | unconnected. |
261 | |
262 | \sa isConnected() |
263 | */ |
264 | QValueSpaceSubscriber::QValueSpaceSubscriber(QValueSpace::LayerOptions filter, const QString &path, QObject *parent) |
265 | : QObject(parent) |
266 | { |
267 | d = new QValueSpaceSubscriberPrivate(path, filter); |
268 | } |
269 | |
270 | /*! |
271 | Constructs a QValueSpaceSubscriber with the specified \a parent that refers to \a path. This |
272 | QValueSpaceSubscriber will only use the layer identified by \a uuid. |
273 | |
274 | Use of this constructor is not platform agnostic. If possible use one of the constructors that |
275 | take a QValueSpace::LayerOptions parameter instead. |
276 | |
277 | If a layer with a matching \a uuid is not found, the constructed QValueSpaceSubscriber will be |
278 | unconnected. |
279 | |
280 | \sa QValueSpace, isConnected() |
281 | */ |
282 | QValueSpaceSubscriber::QValueSpaceSubscriber(const QUuid &uuid, const QString &path, QObject *parent) |
283 | : QObject(parent) |
284 | { |
285 | d = new QValueSpaceSubscriberPrivate(path, uuid); |
286 | } |
287 | |
288 | /*! |
289 | Destroys the QValueSpaceSubscriber. |
290 | */ |
291 | QValueSpaceSubscriber::~QValueSpaceSubscriber() |
292 | { |
293 | if (!isConnected()) |
294 | return; |
295 | |
296 | d->disconnect(space: this); |
297 | } |
298 | |
299 | /*! |
300 | \property QValueSpaceSubscriber::path |
301 | |
302 | This property holds the current path that the QValueSpaceSubscriber refers to. |
303 | |
304 | Settings this property causes the QValueSpaceSubscriber to disconnect and reconnect to the |
305 | Value Space with the new path. As a result all signal/slot connections are disconnected. |
306 | */ |
307 | void QValueSpaceSubscriber::setPath(const QString &path) |
308 | { |
309 | if (!isConnected()) { |
310 | qWarning(msg: "setPath called on unconnected QValueSpaceSubscriber." ); |
311 | return; |
312 | } |
313 | |
314 | if (this->path() == path) |
315 | return; |
316 | |
317 | d->disconnect(space: this); |
318 | |
319 | disconnect(); |
320 | |
321 | d = new QValueSpaceSubscriberPrivate(path); |
322 | } |
323 | |
324 | QString QValueSpaceSubscriber::path() const |
325 | { |
326 | return d->path; |
327 | } |
328 | |
329 | /*! |
330 | Sets the path to the same path as \a subscriber. |
331 | |
332 | Calling this function causes the QValueSpaceSubscriber to disconnect and reconnect to the value |
333 | space with the specified \a path. |
334 | |
335 | Calling this function disconnects all signal/slot connections. |
336 | */ |
337 | void QValueSpaceSubscriber::setPath(QValueSpaceSubscriber *subscriber) |
338 | { |
339 | if (!isConnected()) { |
340 | qWarning(msg: "setPath called on unconnected QValueSpaceSubscriber." ); |
341 | return; |
342 | } |
343 | |
344 | d->disconnect(space: this); |
345 | |
346 | disconnect(); |
347 | |
348 | d = subscriber->d; |
349 | } |
350 | |
351 | /*! |
352 | Changes the path to the absolute path if \a path starts with a '/'; otherwise changes to the |
353 | sub path of the current path. |
354 | */ |
355 | void QValueSpaceSubscriber::cd(const QString &path) |
356 | { |
357 | if (!isConnected()) { |
358 | qWarning(msg: "cd called on unconnected QValueSpaceSubscriber." ); |
359 | return; |
360 | } |
361 | |
362 | if (path.startsWith(c: QLatin1Char('/'))) |
363 | setPath(path); |
364 | else |
365 | setPath(this->path() + QLatin1Char('/') + path); |
366 | } |
367 | |
368 | /*! |
369 | Sets the path to parent of the current path. |
370 | */ |
371 | void QValueSpaceSubscriber::cdUp() |
372 | { |
373 | if (!isConnected()) { |
374 | qWarning(msg: "cdUp called on unconnected QValueSpaceSubscriber." ); |
375 | return; |
376 | } |
377 | |
378 | if (path() == QLatin1String("/" )) |
379 | return; |
380 | |
381 | QString p(path()); |
382 | |
383 | int index = p.lastIndexOf(c: QLatin1Char('/')); |
384 | |
385 | p.truncate(pos: index); |
386 | |
387 | setPath(p); |
388 | } |
389 | |
390 | /*! |
391 | Returns true if this QValueSpaceSubscriber is connected to at least one available layer; |
392 | otherwise returns false. An unconnected QValueSpaceSubscriber is constructed if the filtering |
393 | parameters passed to the constructor eliminate all available layers. |
394 | */ |
395 | bool QValueSpaceSubscriber::isConnected() const |
396 | { |
397 | return !d->readers.isEmpty(); |
398 | } |
399 | |
400 | /*! |
401 | Returns the value of the \a subPath under this subscriber path, or the value of this subscriber |
402 | path if \a subPath is empty. If the value does not exists \a def is returned. |
403 | |
404 | The following code shows how the subscriber path and \a subPath relate. |
405 | |
406 | \code |
407 | QValueSpaceSubscriber base("/Settings"); |
408 | QValueSpaceSubscriber equiv("/Settings/QtProject/General/Mappings"); |
409 | |
410 | // Is true |
411 | equiv.value() == base.value("QtProject/General/Mappings"); |
412 | \endcode |
413 | */ |
414 | QVariant QValueSpaceSubscriber::value(const QString & subPath, const QVariant &def) const |
415 | { |
416 | if (!isConnected()) { |
417 | qWarning(msg: "value called on unconnected QValueSpaceSubscriber." ); |
418 | return QVariant(); |
419 | } |
420 | |
421 | QVariant value; |
422 | if (subPath.isEmpty()) { |
423 | for (int ii = d->readers.count(); ii > 0; --ii) { |
424 | if (d->readers[ii - 1].first->value(handle: d->readers[ii - 1].second, data: &value)) |
425 | return value; |
426 | } |
427 | } else { |
428 | const QString vpath(qCanonicalPath(path: subPath)); |
429 | for (int ii = d->readers.count(); ii > 0; --ii) { |
430 | if (d->readers[ii - 1].first->value(handle: d->readers[ii - 1].second, subPath: vpath, data: &value)) |
431 | return value; |
432 | } |
433 | } |
434 | return def; |
435 | } |
436 | |
437 | /*! |
438 | \property QValueSpaceSubscriber::value |
439 | |
440 | This property holds the value of the path that this QValueSpaceSubscriber refers to. |
441 | */ |
442 | QVariant QValueSpaceSubscriber::valuex(const QVariant &def) const |
443 | { |
444 | QMutexLocker locker(&d->lock); |
445 | |
446 | if (!d->connections || d->connections->connections.value(akey: this) == 0) { |
447 | locker.unlock(); |
448 | d->connect(space: this); |
449 | } |
450 | |
451 | return value(subPath: QString(), def); |
452 | } |
453 | |
454 | /*! |
455 | \reimp |
456 | */ |
457 | void QValueSpaceSubscriber::connectNotify(const QMetaMethod &signal) |
458 | { |
459 | static const QMetaMethod contentsChangedSignal = QMetaMethod::fromSignal(signal: &QValueSpaceSubscriber::contentsChanged); |
460 | if (isConnected() && signal == contentsChangedSignal) |
461 | d->connect(space: this); |
462 | } |
463 | |
464 | /*! |
465 | \reimp |
466 | */ |
467 | void QValueSpaceSubscriber::disconnectNotify(const QMetaMethod &signal) |
468 | { |
469 | static const QMetaMethod contentsChangedSignal = QMetaMethod::fromSignal(signal: &QValueSpaceSubscriber::contentsChanged); |
470 | if (isConnected() && signal == contentsChangedSignal) |
471 | d->disconnect(space: this); |
472 | } |
473 | |
474 | /*! |
475 | Returns a list of sub-paths under the current path. For example, given a Value Space tree |
476 | containing: |
477 | |
478 | \code |
479 | /Settings/QtProject/Device |
480 | /Settings/QtProject/Other |
481 | /Settings/Qt |
482 | /Device/Buttons |
483 | \endcode |
484 | |
485 | \c { QValueSpaceSubscriber("/Settings").subPaths() } will return a list containing |
486 | \c { { QtProject, Qt } } in no particular order. |
487 | */ |
488 | QStringList QValueSpaceSubscriber::subPaths() const |
489 | { |
490 | if (!isConnected()) { |
491 | qWarning(msg: "subPaths called on unconnected QValueSpaceSubscriber." ); |
492 | return QStringList(); |
493 | } |
494 | |
495 | QSet<QString> rv; |
496 | for (int ii = 0; ii < d->readers.count(); ++ii) |
497 | rv.unite(other: d->readers[ii].first->children(handle: d->readers[ii].second)); |
498 | |
499 | QStringList rvs; |
500 | for (QSet<QString>::ConstIterator iter = rv.begin(); iter != rv.end(); ++iter) |
501 | rvs.append(t: *iter); |
502 | |
503 | return rvs; |
504 | } |
505 | |
506 | QT_END_NAMESPACE |
507 | |