1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
|
/*
* Copyright (C) 2008, 2009 Nokia Corporation.
*
* Contact: Marius Vollmer <marius.vollmer@nokia.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
*/
/*!
\mainpage Context Properties
\brief The Context Framework allows you to access system- and
session-wide named values. Examples are context properties like the
current geographical location. You can receive notifications about
changes to these values, and you can also easily subscribe and
unsubscribe from change notifications to help with managing power
consumption.
\section Overview
The Context Properties are key/value pairs. The keys are
strings and the values are QVariants.
Key are arranged in a hierarchical namespace like in this example
of two contextual properties
\code
Screen.TopEdge = "left"
Screen.IsCovered = false
\endcode
Although the key names can be considered to form a tree (with
"Screen" at the root) there is no semantic relationship between
parent and child nodes in the tree: the key "Screen" is unrelated
to "Screen.TopEdge". In particular, change notifications do not
travel up the tree.
The \ref Introspection section describes in detail how to get a list of
existing keys and examine their capabilities.
Programmers access the key/value pairs through instances of the
ContextProperty class. These instances allow applications to
access the values and receive change notifications.
Example:
\code
ContextProperty *topEdge = new ContextProperty("Screen.TopEdge");
QObject::connect(topEdge, SIGNAL(valueChanged()),
this, SLOT(topEdgeChanged()));
\endcode
In your edgeUpChanged method you are able to retrieve the value of the property:
\code
qWarning() << "The edge " << topEdge->value() << " is up";
\endcode
Creating a ContextProperty instance for a key causes the program
to 'subscribe' to this key. The values for some keys might be
expensive to determine, so you should only subscribe to those keys
that you are currently interested in. You can temporarily
unsubscribe from a key without destroying the ContextProperty
instance by using the unsubscribe() member function. Later, you
can resume the subscription by calling the subscribe() member
function.
\code
void onScreenBlank ()
{
topEdge->unsubscribe();
}
void onScreenUnblank ()
{
topEdge->subscribe();
}
\endcode
All the context properties can be used anytime, not depending on
whether the provider of the property is installed or running. If
the system/provider cannot provide you with a value, the value of
the context property will be null. If for some reason you are
interested in property metadata (such as a key's current provider,
availability, etc.) you should consult the \ref Introspection API.
*/
#include "contextproperty.h"
#include "propertyhandle.h"
#include "sconnect.h"
#include "logging.h"
#include "loggingfeatures.h"
#include <QCoreApplication>
#include <QThread>
using namespace ContextSubscriber;
/*!
\class ContextPropertyPrivate
\brief The private parts of the ContextProperty class.
*/
struct ContextPropertyPrivate
{
PropertyHandle *handle; ///< The common handle behind this context property
bool subscribed; ///< True, if we are subscribed to the handle behind us
QVariant value; ///< Our knowledge of the value. Needed because several
/// valueChanged signals might be emitted (queued) by the
/// PropertyHandle, without us handling them.
};
/*!
\class ContextProperty
\brief The ContextProperty class allows access to keys and their
values.
The value is available with the value() member function and change
notifications are delivered via the valueChanged() signal.
You can explicity subscribe and unsubscribe using the subscribe()
and unsubscribe() member functions. A ContextProperty is
initially subscribed.
When a ContextProperty is in the unsubscribed state, it usually
keeps its last value. This is not guaranteed however: more than
one ContextProperty might exist in your process for the same key,
and as long as one of them is subscribed, all of them might receive
new values. The valueChanged() signal is never emitted if the
property is unsubscribed.
A ContextProperty is generally asynchronous and relies on a running
event loop. Subscriptions and unsubcriptions are only handled and
new values are only received when your program enters the event
loop.
ContextProperty objects can be created only after the
Q(Core)Application is constructed.
When a ContextProperty is first created or goes from the
unsubcribed to the subscribed state later on, it is temporarily in
an intermediate 'subscribing' state. This state lasts until the
negotiations with the provider of the key are over (or an error
occurs) and the key's current value is known to the
ContextProperty.
Thus, there is a time after creating a ContextProperty (or
subscribing it again) where value() might be out of sync with the
provider of the key. If you need to wait for this time to be over,
you can not rely on the valueChanged() signal being emitted.
This signal is only emitted when the value actually changes, which
might not happen when subscription is over.
Instead, you can use the waitForSubscription() member function.
This function runs a recursive event loop, if necessary, until the
ContextProperty is fully subscribed.
Thus, the recommended way is to first create all ContextProperty
instances that your program needs and QObject::connect their
valueChanged() signals, then to call waitForSubscription() on those values
that are needed to create the initial user interface.
It is important to create all needed ContextProperty instances
before calling waitForSubscription() on any of them. Subscriptions
are usually bundled together behind the scenes so that they can all
be done with a single round trip to the provider. Interleaving
creation of ContextProperties with calls to waitForSubscription()
would prevent this optimization.
\note The \c ContextProperty class follows the usual QObject rules
for non-GUI classes in multi-threaded programs. In Qt terminology,
the ContextProperty class is reentrant but not thread-safe. This
means that you can create ContextProperty instances in any thread
and then freely use these instance in their threads, but you can
not use a single instance concurrently from multiple threads.
\note Please pay special attention to how signals and slots work in
a multi-threaded program: by default, a slot is emitted in the
thread that called QObject::connect(). For this to happen
reliably, the thread needs to run a event loop.
\note See the Qt documentation for \c QThread and related classes
for more details.
*/
/// Constructs a new ContextProperty for \a key and subscribes to it.
ContextProperty::ContextProperty(const QString &key, QObject* parent)
: QObject(parent), priv(0)
{
priv = new ContextPropertyPrivate;
priv->handle = PropertyHandle::instance(key);
priv->subscribed = false;
subscribe();
}
/// Unsubscribes from the ContextProperty and destroys it.
ContextProperty::~ContextProperty()
{
unsubscribe();
delete priv;
}
/// Returns the key.
QString ContextProperty::key() const
{
return priv->handle->key();
}
/// Returns the current value.
QVariant ContextProperty::value() const
{
return priv->handle->value();
}
/// Returns the current value, or the value \a def if the current
/// value is \c null.
QVariant ContextProperty::value(const QVariant &def) const
{
QVariant val = priv->handle->value();
if (val.isNull())
return def;
else
return val;
}
// A safety measure to avoid emitting unnecessary signals, see
// ContextPropertyPrivate::value.
void ContextProperty::onValueChanged()
{
QVariant newValue = priv->handle->value();
if (priv->value != newValue ||
priv->value.isNull() != newValue.isNull() ||
priv->value.type() != newValue.type())
{
priv->value = newValue;
Q_EMIT valueChanged();
}
}
/// Starts subscribtion to the context property, if it isn't
/// subscribed already. If you need to wait for it to be complete, use
/// waitForSubscription().
void ContextProperty::subscribe() const
{
if (priv->subscribed)
return;
// We create a queued connection, because otherwise we run
// the users' valueChanged() handlers with locks and if they do
// something fancy (for example unsubscribe) it can cause a
// deadlock.
sconnect(priv->handle, SIGNAL(valueChanged()), this, SLOT(onValueChanged()), Qt::QueuedConnection);
priv->handle->subscribe();
priv->subscribed = true;
}
/// Unsubscribes from the context property, if it is currently
/// subscribed. Unsubscribing informs the rest of the system that no
/// effort needs to be spent to keep the value up-to-date. However,
/// the value might still change when it can happen 'for free'. In
/// this case the valueChanged() signal won't be emitted.
void ContextProperty::unsubscribe() const
{
if (!priv->subscribed)
return;
QObject::disconnect(priv->handle, SIGNAL(valueChanged()), this, SIGNAL(valueChanged()));
priv->handle->unsubscribe();
priv->subscribed = false;
}
/// Suspends the execution of the current thread until subcription is
/// complete for this context property. This might cause the main
/// event loop of your program to run and consequently signals might
/// get emitted (including the valueChanged() signal of this
/// property). Calling this function while the subscription is not in
/// progress (because it has completed already or because the property
/// is currently unsubscribed) does nothing. Calling this function
/// from a thread which is not the main thread results in busy looping.
void ContextProperty::waitForSubscription() const
{
if (!priv->subscribed)
return;
while (priv->handle->isSubscribePending()) {
if (QThread::currentThread() == QCoreApplication::instance()->thread()) {
// This is not a busy loop, since the QEventLoop::WaitForMoreEvents flag
QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents);
} else {
usleep(100000); // 0.1 second
}
}
}
/// Sets all of the ContextProperty instances immune to 'external
/// commanding'. This is only intended to be used by the Context
/// Commander itself, so that it can use ContextProperties without
/// tripping over itself. Don't use this.
void ContextProperty::ignoreCommander()
{
PropertyHandle::ignoreCommander();
}
/// Returns the metadata about this property, please refer to \ref
/// Introspection for details.
const ContextPropertyInfo* ContextProperty::info() const
{
return priv->handle->info();
}
/// Enables or disables all of the ContextProperty instances'
/// type-check feature. If it is enabled and the received value from
/// the provider doesn't match the expected type, you will get an
/// error message on the stderr and the value won't be updated. If you
/// use this method, you have to use it before starting any threads.
void ContextProperty::setTypeCheck(bool newTypeCheck)
{
PropertyHandle::setTypeCheck(newTypeCheck);
}
|