Merge lp:~zsombi/ubuntu-ui-toolkit/haptics-feedback-singleton into lp:ubuntu-ui-toolkit/rtm

Proposed by Zsombor Egri
Status: Merged
Approved by: Zoltan Balogh
Approved revision: 1143
Merged at revision: 1143
Proposed branch: lp:~zsombi/ubuntu-ui-toolkit/haptics-feedback-singleton
Merge into: lp:ubuntu-ui-toolkit/rtm
Prerequisite: lp:~zsombi/ubuntu-ui-toolkit/other-vibrations
Diff against target: 497 lines (+339/-19)
9 files modified
components.api (+78/-0)
modules/Ubuntu/Components/11/Haptics.qml (+181/-0)
modules/Ubuntu/Components/AbstractButton.qml (+6/-12)
modules/Ubuntu/Components/plugin/adapters/dbuspropertywatcher_p.cpp (+3/-3)
modules/Ubuntu/Components/plugin/ucserviceproperties.cpp (+14/-4)
modules/Ubuntu/Components/plugin/ucserviceproperties_p.h (+1/-0)
modules/Ubuntu/Components/qmldir (+3/-0)
tests/unit/runtest.sh (+1/-0)
tests/unit/tst_components/tst_haptics.qml (+52/-0)
To merge this branch: bzr merge lp:~zsombi/ubuntu-ui-toolkit/haptics-feedback-singleton
Reviewer Review Type Date Requested Status
Cris Dywan Approve
Review via email: mp+243010@code.launchpad.net

Commit message

Haptics singleton component to provide toolkit wide default and custom feedback functionaity controlled by system settings.

Description of the change

*** MUST BE LANDED TOGETHER OR AFTER GSETTINGS BRANCH BELOW IS LANDED ***
https://code.launchpad.net/~jonas-drange/gsettings-ubuntu-touch-schemas/other-vibrations/+merge/239734

To post a comment you must log in.
Revision history for this message
Tim Peeters (tpeeters) wrote :

why do we have so much stuff added to components.api here? Only Haptics should be added.

Revision history for this message
Zsombor Egri (zsombi) wrote :

The Haptics singleton has to be added to 0.1 and 1.0 versions, otherwise apps made with 0.1 and 1.0 will no longer work. At least UITK gallery doesn't load anymore.

Revision history for this message
Cris Dywan (kalikiana) wrote :

224 + function playCustomEffect(customProperties) {

How about making this an optional argument to play(), without the extra function? So no argument would just mean defaults.

153 + onClicked: Haptics.play()

I wonder, should/ can we do anything here to be prepared for the future where a theme may want to use the name of the component playing the effect to change it?

Can we somehow get the name of the component from the function call? Or would we need to pass "Button" everywhere?

review: Needs Fixing
Revision history for this message
Cris Dywan (kalikiana) wrote :

Nice. As discussed, and reflected in the FIXME, this will be supported by theming in a follow-up change.

review: Approve
Revision history for this message
Zsombor Egri (zsombi) wrote :

> 224 + function playCustomEffect(customProperties) {
>
> How about making this an optional argument to play(), without the extra
> function? So no argument would just mean defaults.

Hmm, not bad.

>
> 153 + onClicked: Haptics.play()
>
> I wonder, should/ can we do anything here to be prepared for the future where
> a theme may want to use the name of the component playing the effect to change
> it?
>
> Can we somehow get the name of the component from the function call? Or would
> we need to pass "Button" everywhere?

As discussed, we should move this into teh style and have styleHint driving it.

Revision history for this message
Cris Dywan (kalikiana) wrote :

A way to overcome the testing dependency on the real service:

https://pypi.python.org/pypi/python-dbusmock/0.9.1

1141. By Zsombor Egri

rtm merge

1142. By Zsombor Egri

small typo fix

1143. By Zsombor Egri

suppress ServiceProperties element warnings if SUPPRESS_SERVICEPROPERTIES_WARNINGS is set to '1' or 'yes'

1144. By Zsombor Egri

RTM sync

1145. By Zsombor Egri

prereq merge

1146. By Zsombor Egri

guard crashing if ServiceProperties cannot connect to the DBus on CI

1147. By Zsombor Egri

no need to wait for service readyness which is behind the scene

1148. By Zsombor Egri

initialize Haptics singleton early enough so its settings are synched when the singleton is invoked

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'components.api'
2--- components.api 2015-02-09 12:55:39 +0000
3+++ components.api 2015-02-09 12:55:39 +0000
4@@ -62,6 +62,11 @@
5 property string fadeStyle
6 readonly property bool running
7 readonly property int status
8+Haptics 0.1 1.0
9+Object
10+ readonly property bool enabled
11+ property HapticsEffect effect
12+ function play(customEffect)
13 Header 0.1 1.0
14 AppHeader
15 property string _for_autopilot
16@@ -701,6 +706,64 @@
17 name: "QAbstractProxyModel"
18 prototype: "QAbstractItemModel"
19 Property { name: "sourceModel"; type: "QAbstractItemModel"; isPointer: true }
20+ name: "QDeclarativeFeedbackActuator"
21+ prototype: "QObject"
22+ exports: ["QtFeedback/Actuator 5.0"]
23+ name: "Capability"
24+ name: "State"
25+ Property { name: "actuatorId"; type: "int"; isReadonly: true }
26+ Property { name: "name"; type: "string"; isReadonly: true }
27+ Property { name: "state"; type: "State"; isReadonly: true }
28+ Property { name: "valid"; type: "bool"; isReadonly: true }
29+ Property { name: "enabled"; type: "bool" }
30+ Method {
31+ name: "isCapabilitySupported"
32+ Parameter { name: "capability"; type: "Capability" }
33+ name: "QDeclarativeFeedbackEffect"
34+ prototype: "QObject"
35+ exports: ["QtFeedback/Feedback 5.0", "QtFeedback/FeedbackEffect 5.0"]
36+ name: "Duration"
37+ name: "State"
38+ name: "ErrorType"
39+ Property { name: "running"; type: "bool" }
40+ Property { name: "paused"; type: "bool" }
41+ Property { name: "duration"; type: "int" }
42+ Property { name: "state"; type: "State" }
43+ Property { name: "error"; type: "ErrorType"; isReadonly: true }
44+ Method { name: "updateState" }
45+ Method { name: "start" }
46+ Method { name: "stop" }
47+ Method { name: "pause" }
48+ name: "QDeclarativeFileEffect"
49+ prototype: "QDeclarativeFeedbackEffect"
50+ exports: ["QtFeedback/FileEffect 5.0"]
51+ Property { name: "loaded"; type: "bool" }
52+ Property { name: "source"; type: "QUrl" }
53+ Property { name: "supportedMimeTypes"; type: "QStringList"; isReadonly: true }
54+ Method { name: "load" }
55+ Method { name: "unload" }
56+ name: "QDeclarativeHapticsEffect"
57+ prototype: "QDeclarativeFeedbackEffect"
58+ exports: ["QtFeedback/HapticsEffect 5.0"]
59+ Property {
60+ name: "availableActuators"
61+ Property { name: "intensity"; type: "double" }
62+ Property { name: "attackTime"; type: "int" }
63+ Property { name: "attackIntensity"; type: "double" }
64+ Property { name: "fadeTime"; type: "int" }
65+ Property { name: "fadeIntensity"; type: "double" }
66+ Property { name: "period"; type: "int" }
67+ Property { name: "actuator"; type: "QDeclarativeFeedbackActuator"; isPointer: true }
68+ name: "QDeclarativeThemeEffect"
69+ prototype: "QObject"
70+ exports: ["QtFeedback/EffectPlayer 5.0", "QtFeedback/ThemeEffect 5.0"]
71+ name: "Effect"
72+ Property { name: "supported"; type: "bool"; isReadonly: true }
73+ Property { name: "effect"; type: "Effect" }
74+ Method { name: "play" }
75+ Method {
76+ name: "play"
77+ Parameter { name: "effect"; type: "Effect" }
78 name: "QSortFilterProxyModel"
79 prototype: "QAbstractProxyModel"
80 Property { name: "filterRegExp"; type: "QRegExp" }
81@@ -995,6 +1058,21 @@
82 Parameter { name: "singular"; type: "string" }
83 Parameter { name: "plural"; type: "string" }
84 Parameter { name: "n"; type: "int" }
85+ prototype: "QObject"
86+ name: "Haptics"
87+ exports: ["Haptics -1.-1"]
88+ Property { name: "enabled"; type: "bool"; isReadonly: true }
89+ Property { name: "effect"; type: "QDeclarativeHapticsEffect"; isReadonly: true; isPointer: true }
90+ Method {
91+ name: "play"
92+ Parameter { name: "customEffect"; type: "QVariant" }
93+ Property { name: "__defaultPropertyFix"; type: "QObject"; isList: true; isReadonly: true }
94+ Property { name: "children"; type: "QObject"; isList: true; isReadonly: true }
95+ prototype: "QObject"
96+ name: "Object"
97+ exports: ["Object -1.-1"]
98+ Property { name: "__defaultPropertyFix"; type: "QObject"; isList: true; isReadonly: true }
99+ Property { name: "children"; type: "QObject"; isList: true; isReadonly: true }
100 name: "ULConditionalLayout"
101 prototype: "QObject"
102 exports: ["ConditionalLayout 0.1", "ConditionalLayout 1.0"]
103
104=== added file 'modules/Ubuntu/Components/11/Haptics.qml'
105--- modules/Ubuntu/Components/11/Haptics.qml 1970-01-01 00:00:00 +0000
106+++ modules/Ubuntu/Components/11/Haptics.qml 2015-02-09 12:55:39 +0000
107@@ -0,0 +1,181 @@
108+/*
109+ * Copyright 2014 Canonical Ltd.
110+ *
111+ * This program is free software; you can redistribute it and/or modify
112+ * it under the terms of the GNU Lesser General Public License as published by
113+ * the Free Software Foundation; version 3.
114+ *
115+ * This program is distributed in the hope that it will be useful,
116+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
117+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
118+ * GNU Lesser General Public License for more details.
119+ *
120+ * You should have received a copy of the GNU Lesser General Public License
121+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
122+ */
123+
124+pragma Singleton
125+import QtQuick 2.0
126+import QtFeedback 5.0
127+import Ubuntu.Components 1.1
128+
129+/*!
130+ \qmltype Haptics
131+ \inqmlmodule Ubuntu.Components 1.1
132+ \ingroup ubuntu-services
133+ \brief Singleton defining the haptics feedback used in components, where execution
134+ of the feedback is controlled by the system settings.
135+
136+ Supports global feedback as well as custom feedback. Global feedback can be
137+ configured through its properties, and \l play function will play the default
138+ configuration, or a custom one if parameter is given.
139+
140+ Example of using Haptics:
141+ \qml
142+ import QtQuick 2.3
143+ import Ubuntu.Components 1.1
144+
145+ Item {
146+ implicitWidth: units.gu(20)
147+ implicitHeight: units.gu(5)
148+
149+ Label {
150+ text: "Press me"
151+ anchors.fill: parent
152+ horizontalAlignment: Text.AlignHCenter
153+ verticalAlignment: Text.AlignVCenter
154+ }
155+ MouseArea {
156+ anchors.fill: parent
157+ onClicked: Haptics.play()
158+ }
159+ }
160+ \endqml
161+
162+ Custom effects can be played as follows:
163+ \qml
164+ import QtQuick 2.3
165+ import Ubuntu.Components 1.1
166+
167+ Item {
168+ implicitWidth: units.gu(20)
169+ implicitHeight: units.gu(5)
170+
171+ Label {
172+ text: "Press me"
173+ anchors.fill: parent
174+ horizontalAlignment: Text.AlignHCenter
175+ verticalAlignment: Text.AlignVCenter
176+ }
177+ MouseArea {
178+ anchors.fill: parent
179+ onClicked: Haptics.play({duration: 25, attackIntensity: 0.7})
180+ }
181+ }
182+ \endqml
183+
184+ \note Though the \l effect property exposes \c start, \c stop and \c pause
185+ functions, use those only if you want to have feedback independent on what the
186+ system setting is.
187+ */
188+Object {
189+
190+ /*!
191+ \qmlproperty bool enabled
192+ \readonly
193+ The property specifies whether the haptics feedback is enabled or not by the system.
194+ */
195+ readonly property alias enabled: vibra.otherVibrate
196+
197+ /*!
198+ \qmlproperty HapticsEffect effect
199+ The property defines the settings of the haptics effect used by the component.
200+ The default setting is a haptics effect with a duration of 10 milliseconds
201+ with an intensity of 1.0, having fading time of 50 millisecods and fading
202+ intensity 0.0, and attack time of 50 milliseconds and with an intensity of
203+ 0.0.
204+ */
205+ property alias effect: effect
206+
207+ /*!
208+ \qmlmethod play([customEffect])
209+ The function plays the feedback with the configuration specified in \l effect
210+ if no parameter is given. Custom effect can be played by specifying the effect
211+ properties in a JSON object in \c customEffect.
212+
213+ The function will exit unconditionaly if playing the effects is blocked by
214+ system settings.
215+
216+ The function will not stop any ongoing haptics effect played, if that one
217+ was a default haptics effect. In case of custom effects, the previous effect
218+ will be stopped, and settings will be restored before the new haptics will
219+ be played. The custom settings properties (the ones which are required to
220+ be different from the ones defined in the \l effect) must be specified in
221+ the parameter in a JSON object.
222+ */
223+ function play(customEffect) {
224+ if (!vibra.otherVibrate) {
225+ return;
226+ }
227+ if (effectData.data) {
228+ // we have a custom effect playing, stop it
229+ effect.stop();
230+ }
231+ if (effect.running) {
232+ // this is a global effect, leave
233+ return;
234+ }
235+ if (customEffect) {
236+ effectData.backup(customEffect);
237+ }
238+ effect.start();
239+ }
240+
241+ QtObject {
242+ id: effectData
243+ property var data
244+
245+ function backup(customEffect) {
246+ data = customEffect;
247+ for (var p in data) {
248+ var value = data[p];
249+ data[p] = effect[p];
250+ effect[p] = value;
251+ }
252+ }
253+ function restore() {
254+ for (var p in data) {
255+ effect[p] = data[p];
256+ }
257+ data = undefined;
258+ }
259+ }
260+
261+ // local feedback component used to play feedback
262+ HapticsEffect {
263+ id: effect
264+ attackIntensity: 0.0
265+ attackTime: 50
266+ intensity: 1.0
267+ duration: 10
268+ fadeTime: 50
269+ fadeIntensity: 0.0
270+
271+ onStateChanged: {
272+ if (state == HapticsEffect.Stopped) {
273+ effectData.restore();
274+ }
275+ }
276+ }
277+
278+ // watch system settings for otherVibrate
279+ ServiceProperties {
280+ objectName: "system_effect_settings"
281+ id: vibra
282+ service: "org.freedesktop.Accounts"
283+ serviceInterface: "org.freedesktop.Accounts"
284+ path: "/org/freedesktop/Accounts"
285+ adaptorInterface: "com.ubuntu.touch.AccountsService.Sound"
286+ property bool otherVibrate: true
287+ }
288+}
289
290=== modified file 'modules/Ubuntu/Components/AbstractButton.qml'
291--- modules/Ubuntu/Components/AbstractButton.qml 2015-02-09 12:55:39 +0000
292+++ modules/Ubuntu/Components/AbstractButton.qml 2015-02-09 12:55:39 +0000
293@@ -15,7 +15,6 @@
294 */
295
296 import QtQuick 2.0
297-import QtFeedback 5.0
298 import Ubuntu.Components 1.1
299
300 /*!
301@@ -85,16 +84,6 @@
302
303 activeFocusOnPress: true
304
305- HapticsEffect {
306- id: pressEffect
307- attackIntensity: 0.0
308- attackTime: 50
309- intensity: 1.0
310- duration: 10
311- fadeTime: 50
312- fadeIntensity: 0.0
313- }
314-
315 MouseArea {
316 id: mouseArea
317 anchors.fill: parent
318@@ -102,9 +91,14 @@
319 // as it might occlude the newly assigned mouse area.
320 hoverEnabled: true
321
322+ // invoke Haptics singleton earlier than we press the button,
323+ // so we give some time for the singleton to sync settings with the service
324+ property bool hapticsEnabled: Haptics.enabled
325+
326 onClicked: {
327 if (button.__acceptEvents) {
328- pressEffect.start();
329+ // FIXME (Vivid) call this in the style rather than from AbstractButton
330+ Haptics.play();
331 button.clicked()
332 }
333 }
334
335=== modified file 'modules/Ubuntu/Components/plugin/adapters/dbuspropertywatcher_p.cpp'
336--- modules/Ubuntu/Components/plugin/adapters/dbuspropertywatcher_p.cpp 2015-02-09 12:55:39 +0000
337+++ modules/Ubuntu/Components/plugin/adapters/dbuspropertywatcher_p.cpp 2015-02-09 12:55:39 +0000
338@@ -133,13 +133,13 @@
339 if (!readIFace.isValid()) {
340 // report invalid interface only if the property's first letter was with capital one!
341 if (property[0].isUpper()) {
342- qmlInfo(q) << readIFace.lastError().message();
343+ warning(readIFace.lastError().message());
344 }
345 return false;
346 }
347 QDBusPendingCall pending = readIFace.asyncCall("Get", adaptor, property);
348 if (pending.isError()) {
349- qmlInfo(q) << pending.error().message();
350+ warning(pending.error().message());
351 return false;
352 }
353 QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(pending, q);
354@@ -182,7 +182,7 @@
355 properties.removeAll(property);
356 if (property[0].isUpper()) {
357 // report error!
358- qmlInfo(q) << reply.error().message();
359+ warning(reply.error().message());
360 }
361 } else {
362 // update watched property value
363
364=== modified file 'modules/Ubuntu/Components/plugin/ucserviceproperties.cpp'
365--- modules/Ubuntu/Components/plugin/ucserviceproperties.cpp 2015-02-09 12:55:39 +0000
366+++ modules/Ubuntu/Components/plugin/ucserviceproperties.cpp 2015-02-09 12:55:39 +0000
367@@ -39,6 +39,15 @@
368 return service->d_func();
369 }
370
371+void UCServicePropertiesPrivate::warning(const QString &message)
372+{
373+ QByteArray suppressWarnings = qgetenv("SUPPRESS_SERVICEPROPERTIES_WARNINGS");
374+ if ((suppressWarnings == "yes") || (suppressWarnings == "1")) {
375+ return;
376+ }
377+ qmlInfo(q_ptr) << message;
378+}
379+
380 void UCServicePropertiesPrivate::setError(const QString &msg)
381 {
382 if (error == msg) {
383@@ -59,7 +68,8 @@
384
385 void printLocked(UCServiceProperties *owner)
386 {
387- qmlInfo(owner) << UbuntuI18n::instance().tr("Changing connection parameters forbidden.");
388+ UCServicePropertiesPrivate::get(owner)->
389+ warning(UbuntuI18n::instance().tr("Changing connection parameters forbidden."));
390 }
391
392 /*!
393@@ -141,9 +151,9 @@
394 // check the binding on the property and warn if there is one.
395 QQmlProperty qmlProperty(this, property);
396 if (QQmlPropertyPrivate::binding(qmlProperty)) {
397- qmlInfo(this) << UbuntuI18n::instance().
398- tr("Binding detected on property '%1' will be removed by the service updates.").
399- arg(property);
400+ d->warning(UbuntuI18n::instance().
401+ tr("Binding detected on property '%1' will be removed by the service updates.").
402+ arg(property));
403 }
404 // insert both the declared and capitalized first character properties
405 d->properties << property;
406
407=== modified file 'modules/Ubuntu/Components/plugin/ucserviceproperties_p.h'
408--- modules/Ubuntu/Components/plugin/ucserviceproperties_p.h 2015-02-09 12:55:39 +0000
409+++ modules/Ubuntu/Components/plugin/ucserviceproperties_p.h 2015-02-09 12:55:39 +0000
410@@ -27,6 +27,7 @@
411 virtual ~UCServicePropertiesPrivate();
412
413 static UCServicePropertiesPrivate *get(UCServiceProperties *service);
414+ void warning(const QString &message);
415 void setError(const QString &msg);
416 void setStatus(UCServiceProperties::Status status);
417
418
419=== modified file 'modules/Ubuntu/Components/qmldir'
420--- modules/Ubuntu/Components/qmldir 2014-09-07 16:42:47 +0000
421+++ modules/Ubuntu/Components/qmldir 2015-02-09 12:55:39 +0000
422@@ -104,3 +104,6 @@
423 Icon 1.1 Icon11.qml
424 StyledItem 1.1 StyledItem.qml
425 singleton UbuntuColors 1.1 11/UbuntuColors.qml
426+
427+singleton Haptics 0.1 11/Haptics.qml
428+singleton Haptics 1.0 11/Haptics.qml
429
430=== modified file 'tests/unit/runtest.sh'
431--- tests/unit/runtest.sh 2015-02-09 12:55:39 +0000
432+++ tests/unit/runtest.sh 2015-02-09 12:55:39 +0000
433@@ -46,6 +46,7 @@
434 # https://bugreports.qt-project.org/browse/QTBUG-36243
435 QML2_IMPORT_PATH=../../../modules:$QML2_IMPORT_PATH UBUNTU_UI_TOOLKIT_THEMES_PATH=../../../modules \
436 ALARM_BACKEND=memory \
437+ SUPPRESS_SERVICEPROPERTIES_WARNINGS=yes \
438 $_CMD $_ARGS 2>&1 | grep -v 'QFontDatabase: Cannot find font directory'
439 # Note: Get first command before the pipe, $? would be ambiguous
440 RESULT=${PIPESTATUS[0]}
441
442=== added file 'tests/unit/tst_components/tst_haptics.qml'
443--- tests/unit/tst_components/tst_haptics.qml 1970-01-01 00:00:00 +0000
444+++ tests/unit/tst_components/tst_haptics.qml 2015-02-09 12:55:39 +0000
445@@ -0,0 +1,52 @@
446+/*
447+ * Copyright 2014 Canonical Ltd.
448+ *
449+ * This program is free software; you can redistribute it and/or modify
450+ * it under the terms of the GNU Lesser General Public License as published by
451+ * the Free Software Foundation; version 3.
452+ *
453+ * This program is distributed in the hope that it will be useful,
454+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
455+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
456+ * GNU Lesser General Public License for more details.
457+ *
458+ * You should have received a copy of the GNU Lesser General Public License
459+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
460+ */
461+
462+import QtQuick 2.3
463+import QtTest 1.0
464+import Ubuntu.Components 1.1
465+import Ubuntu.Test 1.0
466+import QtFeedback 5.0
467+
468+UbuntuTestCase {
469+ name: "HapticsAPI"
470+
471+ function waitForHapticsCompleted() {
472+ tryCompareFunction(function() { return Haptics.effect.state; }, HapticsEffect.Stopped, 1000);
473+ }
474+
475+ function test_0_defaults() {
476+ verify(Haptics.hasOwnProperty("enabled"), "missing property 'enabled'");
477+ verify(Haptics.hasOwnProperty("effect"), "missing property 'effect'");
478+ verify(Haptics.hasOwnProperty("play"), "missing function 'play'");
479+ }
480+
481+ function test_play() {
482+ Haptics.play();
483+ if (Haptics.enabled) {
484+ waitForHapticsCompleted();
485+ }
486+ }
487+
488+ function test_custom_play() {
489+ Haptics.play({attackTime: 10, attackIntensity: 0.5, duration: 1200});
490+ if (Haptics.enabled && Haptics.effect.running) {
491+ compare(Haptics.effect.attackTime, 10, "attack time not modified");
492+ compare(Haptics.effect.attackIntensity, 0.5, "attack intensity not modified");
493+ compare(Haptics.effect.duration, 400, "duration not modified");
494+ waitForHapticsCompleted();
495+ }
496+ }
497+}

Subscribers

People subscribed via source and target branches

to all changes: