on xenial help sign in
merge trunk
commit69850dd5c06b51b09a0ceca898a12b2a993d73c1 authorMichael Zanetti <michael.zanetti@canonical.com> dateTue 27 Aug 2013, 08:59 (13 years ago) parent3dcad5a5ff94b9
55 files changed+1376−297
Dash/GenericScopeView.qml+6 Launcher/FoldingLauncherDelegate.qml+35−24 Launcher/Launcher.qml+1−1 Launcher/LauncherDelegate.qml+2−2 Launcher/LauncherPanel.qml+43−17 Launcher/QuicklistForegroundStyle.qml+16 build+2−1 debian/changelog+98 main.cpp−1 plugins/HudClient/qmldir+1 plugins/LightDM/plugin.cpp+1−1 plugins/Powerd/plugin.cpp+1−1 plugins/Ubuntu/DownloadDaemonListener/plugin.cpp+1−1 plugins/Unity/Indicators/Network/plugin.cpp+1−1 plugins/Unity/Indicators/Network/qml/AccessPoint.qml+18−8 plugins/Unity/Indicators/Network/qml/PasswordPage.qml+1−1 plugins/Unity/Indicators/Network/qml/WifiSection.qml−28 plugins/Unity/Indicators/Network/qml/qmldir+1−2 plugins/Unity/Indicators/plugin.cpp+1−1 plugins/Unity/Indicators/qml/BaseMenuItem.qml−4 plugins/Unity/Indicators/qml/ButtonMenuItem.qml+4−9 plugins/Unity/Indicators/qml/FramedMenuItem.qml−2 plugins/Unity/Indicators/qml/IndicatorPage.qml+4−9 plugins/Unity/Indicators/qml/MenuAction.qml−59 plugins/Unity/Indicators/qml/MenuItemFactory.qml+124−7 plugins/Unity/Indicators/qml/ProgressMenuItem.qml+1−8 plugins/Unity/Indicators/qml/SectionMenuItem.qml+3−3 plugins/Unity/Indicators/qml/SliderMenuItem.qml+8−23 plugins/Unity/Indicators/qml/StandardMenuItem.qml+5−10 plugins/Unity/Indicators/qml/SwitchMenuItem.qml+3−8 plugins/Unity/Indicators/qml/qmldir−1 plugins/Unity/IndicatorsLegacy/Network/plugin.cpp+1−1 plugins/Unity/IndicatorsLegacy/plugin.cpp+1−1 plugins/Unity/Launcher/launchermodel.cpp+34 plugins/Unity/Launcher/launchermodel.h+5 plugins/Unity/Launcher/plugin.cpp+1−1 plugins/Unity/genericoptionsmodel.cpp+2−2 plugins/Unity/genericoptionsmodel.h+1−1 plugins/Unity/plugin.cpp+4−1 plugins/Utils/plugin.cpp+1−1 po/fi.po+13−13 tests/autopilot/unity8/shell/emulators/create_interactive_notification.py+102 tests/autopilot/unity8/shell/tests/__init__.py+20−20 tests/autopilot/unity8/shell/tests/test_notifications.py+737 tests/mocks/GSettings.1.0/fake_gsettings.h+1−1 tests/mocks/GSettings.1.0/plugin.cpp+1−1 tests/mocks/Powerd/plugin.cpp+1−1 tests/mocks/Ubuntu/DownloadDaemonListener/plugin.cpp−1 tests/mocks/Unity/Indicators/fakeplugin.cpp+1−1 tests/mocks/Unity/Launcher/MockLauncherModel.cpp+6 tests/mocks/Unity/Launcher/MockLauncherModel.h+1 tests/mocks/Unity/fake_unity_plugin.cpp+2−1 tests/qmltests/Launcher/tst_Launcher.qml+38−14 tests/utils/modules/Unity/Test/TouchEventSequenceWrapper.h+1−1 tests/utils/modules/Unity/Test/UnityTestCase.qml+21−2
Dash/GenericScopeView.qml+6
@@ -80,6 +80,12 @@ ScopeView {
8080 item.model = results
8181 }
8282 item.objectName = categoryId
83+ if (item.expandable) {
84+ var shouldFilter = index != categoryView.expandedIndex;
85+ if (shouldFilter != item.filter) {
86+ item.filter = shouldFilter;
87+ }
88+ }
8389 }
8490
8591 Connections {
Launcher/FoldingLauncherDelegate.qml+35−24
@@ -26,21 +26,21 @@ LauncherDelegate {
2626 if (index == 0 || index == priv.listView.count-1) {
2727 if (priv.distanceFromEdge < 0) {
2828 // proportion equation: distanceFromTopEdge : angle = totalUnfoldedHeight/2 : maxAngle
29− return Math.max(-maxAngle, priv.distanceFromEdge * maxAngle / (priv.listView.foldingAreaHeight)) * priv.orientationFlag
29+ return Math.max(-maxAngle, priv.distanceFromEdge * maxAngle / (priv.foldingAreaHeight)) * priv.orientationFlag
3030 }
3131 return 0; // Don't fold first/last item as long as inside the view
3232 }
3333
34− // Are we in the already completely outside the flickable? Fold for the last 5 degrees
35− if (priv.distanceFromEdge < 0) {
36− // proportion equation: -distanceFromTopEdge : angle = totalUnfoldedHeight : 5
37− return Math.max(-maxAngle, (priv.distanceFromEdge * 5 / priv.listView.foldingAreaHeight) - (maxAngle-5)) * priv.orientationFlag
34+ // We reached the folded area... fold the last 5 degrees
35+ if (priv.overlapWithFoldedArea > 0) {
36+ // proportion equation: overlap : x = height : 5
37+ return ((maxAngle - 5) + (priv.overlapWithFoldedArea * 5 / priv.foldingAreaHeight)) * -priv.orientationFlag
3838 }
3939
4040 // We are overlapping with the folding area, fold the icon to maxAngle - 5 degrees
4141 if (priv.overlapWithFoldingArea > 0) {
4242 // proportion equation: overlap: totalHeight = angle : (maxAngle - 5)
43− return -priv.overlapWithFoldingArea * (maxAngle -5) / priv.listView.foldingAreaHeight * priv.orientationFlag;
43+ return -priv.overlapWithFoldingArea * (maxAngle -5) / priv.foldingAreaHeight * priv.orientationFlag;
4444 }
4545 return 0;
4646 }
@@ -61,9 +61,9 @@ LauncherDelegate {
6161 return (-priv.distanceFromEdge - (root.height - effectiveHeight)) * priv.orientationFlag;
6262 }
6363
64− // We're touching the edge, move slower than the actual flicking speed.
65− if (priv.distanceFromEdge < 0) {
66− return (Math.abs(priv.distanceFromEdge) * priv.totalEffectiveHeight / priv.totalUnfoldedHeight) * priv.orientationFlag
64+ // We stopped folding, move slower than the actual flicking speed.
65+ if (priv.overlapWithFoldedArea > 0) {
66+ return (priv.overlapWithFoldedArea * priv.totalEffectiveHeight / (priv.totalUnfoldedHeight + priv.listView.foldingStopHeight)) * priv.orientationFlag;
6767 }
6868 return 0;
6969 }
@@ -72,22 +72,23 @@ LauncherDelegate {
7272 // First/last items are special
7373 if (index == 0 || index == priv.listView.count-1) {
7474 if (priv.distanceFromEdge < 0) {
75− // Fade from 1 to 0 in the distance of 3 * foldingAreaHeight (which is when the next item reaches the edge)
76− return 1.0 - (-priv.distanceFromEdge / (priv.listView.foldingAreaHeight * 3))
75+ // Fade from 1 to 0 while traversing a distance of 2*foldingAreaHeight
76+ // proportion equation: 0.5 : x = -2*foldingAreaHeight : distance
77+ return 1 + (priv.distanceFromEdge / (priv.foldingAreaHeight * 2))
7778 }
7879 return 1; // Don't make first/last item transparent as long as inside the view
7980 }
8081
81− // Are we already completely outside the flickable? Fade from 0.75 to 0 in 2 items height
82− if (priv.distanceFromEdge < 0) {
83− // proportion equation: -distanceFromEdge : 1-opacity = totalUnfoldedHeight : 0.75
84− return 0.75 - (-priv.distanceFromEdge * 0.75 / (priv.totalUnfoldedHeight*2))
82+ // Did we stop folding? Fade out to 0 in 2*foldingAreaHeight
83+ if (priv.overlapWithFoldedArea > 0) {
84+ // overlap : foldingAreaHeight = opacity : 0.75
85+ return 0.75 - (priv.overlapWithFoldedArea * 0.75 / (priv.foldingAreaHeight * 2));
8586 }
8687
87− // We are overlapping with the folding area, fade out to 0.75
88+ // We are overlapping with the folding area, fade out to 0.75 transparency
8889 if (priv.overlapWithFoldingArea > 0) {
8990 // proportion equation: overlap : totalHeight = 1-opacity : 0.25
90− return 1 - (priv.overlapWithFoldingArea * 0.25 / priv.listView.foldingAreaHeight)
91+ return 1 - (priv.overlapWithFoldingArea * 0.25 / priv.foldingAreaHeight)
9192 }
9293 return 1;
9394 }
@@ -95,19 +96,27 @@ LauncherDelegate {
9596 brightness: {
9697 // First/last items are special
9798 if (index == 0 || index == priv.listView.count-1) {
99+
100+ // Traversed one foldingAreaHeight. Stop at 0.3
101+ if (priv.distanceFromEdge < -priv.foldingAreaHeight) {
102+ return -0.3
103+ }
104+
105+ // We started moving, fade to 0.3
98106 if (priv.distanceFromEdge < 0) {
99− return -(-priv.distanceFromEdge / (priv.listView.foldingAreaHeight * 3))
107+ return -0.3 * (-priv.distanceFromEdge / (priv.foldingAreaHeight))
100108 }
101109 return 0;
102110 }
103− // Are we already completely outside the flickable? Fade from 0.7 to 0 in 2 items height
104− if (priv.distanceFromEdge < 0) {
105− return -0.3 - (-priv.distanceFromEdge * 0.1 / (priv.totalUnfoldedHeight*2))
111+
112+ // We stopped folding? Stop brightness change at 0.3
113+ if (priv.overlapWithFoldedArea > 0) {
114+ return -0.3;
106115 }
107116
108− // We are overlapping with the folding area, fade out to 0.7
117+ // We are overlapping with the folding area, fade out to 0.3
109118 if (priv.overlapWithFoldingArea > 0) {
110− return - (priv.overlapWithFoldingArea * 0.3 / priv.listView.foldingAreaHeight)
119+ return - (priv.overlapWithFoldingArea * 0.3 / priv.listView.foldingStartHeight);
111120 }
112121 return 0;
113122 }
@@ -127,7 +136,9 @@ LauncherDelegate {
127136 property real distanceFromEdge: Math.abs(distanceFromBottomEdge) < Math.abs(distanceFromTopEdge) ? distanceFromBottomEdge : distanceFromTopEdge
128137 property real orientationFlag: Math.abs(distanceFromBottomEdge) < Math.abs(distanceFromTopEdge) ? -1 : 1
129138
130− property real overlapWithFoldingArea: listView.foldingAreaHeight - distanceFromEdge
139+ property real overlapWithFoldingArea: listView.foldingStartHeight - distanceFromEdge
140+ property real overlapWithFoldedArea: listView.foldingStopHeight - distanceFromEdge
141+ property real foldingAreaHeight: listView.foldingStartHeight - listView.foldingStopHeight
131142 }
132143
133144}
Launcher/Launcher.qml+1−1
@@ -26,7 +26,7 @@ Item {
2626
2727 property bool available: true // can be used to disable all interactions
2828
29− property int panelWidth: units.gu(8.5)
29+ property int panelWidth: units.gu(8)
3030 property int dragAreaWidth: units.gu(1)
3131 property real progress: dragArea.dragging && dragArea.touchX > panel.width ? dragArea.touchX : 0
3232
Launcher/LauncherDelegate.qml+2−2
@@ -80,7 +80,7 @@ Item {
8080 top: parent.top
8181 margins: -units.dp(1)
8282 }
83− width: Math.min(units.gu(7.5), Math.max(units.gu(3), countLabel.implicitWidth + units.gu(2.5)))
83+ width: Math.min(root.itemWidth, Math.max(units.gu(3), countLabel.implicitWidth + units.gu(2.5)))
8484 height: units.gu(3)
8585 source: "graphics/notification.sci"
8686 visible: root.count > 0
@@ -89,7 +89,7 @@ Item {
8989 id: countLabel
9090 text: root.count
9191 anchors.centerIn: parent
92− width: units.gu(6)
92+ width: root.itemWidth - units.gu(1)
9393 horizontalAlignment: Text.AlignHCenter
9494 elide: Text.ElideRight
9595 color: "white"
Launcher/LauncherPanel.qml+43−17
@@ -62,7 +62,7 @@ Item {
6262 z: 1
6363 Image {
6464 objectName: "dashItem"
65− width: units.gu(5.5)
65+ width: units.gu(5)
6666 height: width
6767 anchors.centerIn: parent
6868 source: "graphics/home.png"
@@ -113,14 +113,22 @@ Item {
113113 property int extensionSize: itemHeight * 3
114114
115115 // The height of the area where icons start getting folded
116− property int foldingAreaHeight: itemHeight * 0.75
117− property int itemWidth: width
118− property int itemHeight: width * 7.5 / 8
116+ property int foldingStartHeight: units.gu(6.5)
117+ // The height of the area where the items reach the final folding angle
118+ property int foldingStopHeight: foldingStartHeight - itemHeight - spacing
119+ property int itemWidth: units.gu(7)
120+ property int itemHeight: units.gu(6.5)
119121 property int clickFlickSpeed: units.gu(60)
120122 property int draggedIndex: dndArea.draggedIndex
121123 property real realContentY: contentY - originY + topMargin
122124 property int realItemHeight: itemHeight + spacing
123125
126+ // In case the start dragging transition is running, we need to delay the
127+ // move because the displaced transition would clash with it and cause items
128+ // to be moved to wrong places
129+ property bool draggingTransitionRunning: false
130+ property int scheduledMoveTo: -1
131+
124132 displaced: Transition {
125133 NumberAnimation { properties: "x,y"; duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing }
126134 }
@@ -136,9 +144,8 @@ Item {
136144 count: model.count
137145 progress: model.progress
138146 inverted: root.inverted
139− highlighted: dragging && index === root.highlightIndex
140147 z: -Math.abs(offset)
141− maxAngle: 60
148+ maxAngle: 55
142149 property bool dragging: false
143150
144151 ThinDivider {
@@ -202,10 +209,26 @@ Item {
202209 UbuntuNumberAnimation { properties: "angle,offset" }
203210 },
204211 Transition {
212+ id: draggingTransition
205213 from: "selected"
206214 to: "dragging"
207− UbuntuNumberAnimation { properties: "height" }
208− NumberAnimation { target: dropIndicator; properties: "opacity"; duration: UbuntuAnimation.FastDuration }
215+ SequentialAnimation {
216+ PropertyAction { target: launcherListView; property: "draggingTransitionRunning"; value: true }
217+ ParallelAnimation {
218+ UbuntuNumberAnimation { properties: "height" }
219+ NumberAnimation { target: dropIndicator; properties: "opacity"; duration: UbuntuAnimation.FastDuration }
220+ }
221+ ScriptAction {
222+ script: {
223+ if (launcherListView.scheduledMoveTo > -1) {
224+ launcherListView.model.move(dndArea.draggedIndex, launcherListView.scheduledMoveTo)
225+ dndArea.draggedIndex = launcherListView.scheduledMoveTo
226+ launcherListView.scheduledMoveTo = -1
227+ }
228+ }
229+ }
230+ PropertyAction { target: launcherListView; property: "draggingTransitionRunning"; value: false }
231+ }
209232 },
210233 Transition {
211234 from: "dragging"
@@ -213,7 +236,9 @@ Item {
213236 NumberAnimation { target: dropIndicator; properties: "opacity"; duration: UbuntuAnimation.FastDuration }
214237 NumberAnimation { properties: "itemOpacity"; duration: UbuntuAnimation.BriskDuration }
215238 SequentialAnimation {
239+ ScriptAction { script: if (index == launcherListView.count-1) launcherListView.flick(0, -launcherListView.clickFlickSpeed); }
216240 UbuntuNumberAnimation { properties: "height" }
241+ ScriptAction { script: if (index == launcherListView.count-1) launcherListView.flick(0, -launcherListView.clickFlickSpeed); }
217242 PropertyAction { target: dndArea; property: "postDragging"; value: false }
218243 PropertyAction { target: dndArea; property: "draggedIndex"; value: -1 }
219244 }
@@ -243,9 +268,6 @@ Item {
243268
244269 onPressed: {
245270 selectedItem = launcherListView.itemAt(mouseX, mouseY + launcherListView.realContentY)
246− if (selectedItem !== null) {
247− selectedItem.highlighted = true
248− }
249271 }
250272
251273 onClicked: {
@@ -280,7 +302,6 @@ Item {
280302 }
281303
282304 onCanceled: {
283− selectedItem.highlighted = false
284305 selectedItem = undefined;
285306 preDragging = false;
286307 postDragging = false;
@@ -298,7 +319,6 @@ Item {
298319 return;
299320 }
300321
301− selectedItem.highlighted = false
302322 selectedItem.dragging = false;
303323 selectedItem = undefined;
304324 preDragging = false;
@@ -310,6 +330,9 @@ Item {
310330 if (droppedIndex >= launcherListView.count - 2 && postDragging) {
311331 launcherListView.flick(0, -launcherListView.clickFlickSpeed);
312332 }
333+ if (droppedIndex == 0 && postDragging) {
334+ launcherListView.flick(0, launcherListView.clickFlickSpeed);
335+ }
313336 }
314337
315338 onPressAndHold: {
@@ -387,8 +410,12 @@ Item {
387410 }
388411
389412 if (newIndex >= 0 && newIndex < launcherListView.count) {
390− launcherListView.model.move(draggedIndex, newIndex)
391− draggedIndex = newIndex
413+ if (launcherListView.draggingTransitionRunning) {
414+ launcherListView.scheduledMoveTo = newIndex
415+ } else {
416+ launcherListView.model.move(draggedIndex, newIndex)
417+ draggedIndex = newIndex
418+ }
392419 }
393420 }
394421 }
@@ -425,8 +452,7 @@ Item {
425452 height: itemHeight
426453 width: itemWidth
427454 rotation: root.rotation
428− highlighted: true
429− itemOpacity: 0.8
455+ itemOpacity: 0.9
430456
431457 function flatten() {
432458 fakeDragItemAnimation.start();
Launcher/QuicklistForegroundStyle.qml+16
@@ -1,3 +1,19 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
117import Ubuntu.Components.Themes.Ambiance 0.1
218
319PopoverForegroundStyle {
build+2−1
@@ -59,7 +59,8 @@ mk_build_deps() {
5959
6060if [ -f "/usr/bin/ninja" ] ; then
6161 GENERATOR="-G Ninja"
62− BUILD_COMMAND="ninja -j$NUM_JOBS"
62+ # Ninja does not need -j, it parallelizes automatically.
63+ BUILD_COMMAND="ninja"
6364else
6465 GENERATOR=
6566 BUILD_COMMAND="make -j$NUM_JOBS"
debian/changelog+98
@@ -1,3 +1,101 @@
1+unity8 (7.81.3+13.10.20130827-0ubuntu1) saucy; urgency=low
2+
3+ [ Michael Zanetti ]
4+ * Theme the Quicklist Popover.
5+
6+ [ Albert Astals ]
7+ * Apply expandedIndex on delegate creation bug #1213033. (LP:
8+ #1213033)
9+
10+ [ Ubuntu daily release ]
11+ * Automatic snapshot from revision 254
12+
13+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Tue, 27 Aug 2013 02:10:43 +0000
14+
15+unity8 (7.81.3+13.10.20130826.5-0ubuntu1) saucy; urgency=low
16+
17+ [ Michael Zanetti ]
18+ * tweak launcher folding and visuals according to feedback from
19+ design: - increase foldingStartHeight - Introduces a
20+ foldingStopHeight. Folding only happens between the
21+ foldingStartHeight and the foldingStopHeight. - Only change
22+ brightness while folding - remove highlight of pressed icon -
23+ decrease launcher's width by half a grid unit.
24+
25+ [ Ubuntu daily release ]
26+ * Automatic snapshot from revision 251
27+
28+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Mon, 26 Aug 2013 22:09:43 +0000
29+
30+unity8 (7.81.3+13.10.20130826.4-0ubuntu1) saucy; urgency=low
31+
32+ [ Michał Sawicz ]
33+ * Add module entry in HudClient's qmldir.
34+
35+ [ Jussi Pakkanen ]
36+ * Let Ninja parallelize itself.
37+
38+ [ Nick Dedekind ]
39+ * Abstraction of indicator menu item properties prior to move into
40+ common components library.
41+
42+ [ Christopher Lee ]
43+ * Added autopilot-tests for ephemeral, interactive and snap-decision
44+ notifications.
45+
46+ [ Mirco Müller ]
47+ * Added autopilot-tests for ephemeral, interactive and snap-decision
48+ notifications.
49+
50+ [ Michael Zanetti ]
51+ * only search visible children in findChild, add findInvisibleChild
52+ for others.
53+ * Added autopilot-tests for ephemeral, interactive and snap-decision
54+ notifications.
55+
56+ [ Thomi Richards ]
57+ * Added autopilot-tests for ephemeral, interactive and snap-decision
58+ notifications.
59+
60+ [ Ubuntu daily release ]
61+ * Automatic snapshot from revision 249
62+
63+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Mon, 26 Aug 2013 18:34:44 +0000
64+
65+unity8 (7.81.3+13.10.20130826.2-0ubuntu1) saucy; urgency=low
66+
67+ [ Albert Astals ]
68+ * Don't include QtQML It includes LOTS of files we don't need.
69+
70+ [ Ubuntu daily release ]
71+ * Automatic snapshot from revision 243
72+
73+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Mon, 26 Aug 2013 10:08:52 +0000
74+
75+unity8 (7.81.3+13.10.20130826.1-0ubuntu1) saucy; urgency=low
76+
77+ * Automatic snapshot from revision 241
78+
79+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Mon, 26 Aug 2013 06:08:33 +0000
80+
81+unity8 (7.81.3+13.10.20130826-0ubuntu1) saucy; urgency=low
82+
83+ [ Michael Zanetti ]
84+ * delay move operations if the start dragging operation is running
85+ This prevents items to left in wrong places when transitions clash.
86+ * add empty setUser to allow compiling in jenkins again until the
87+ branch that matches unity-api is ready to land. .
88+ * add count and progress overlay information to real model too.
89+
90+ [ Pawel Stolowski ]
91+ * Changed the type of setActive argument from
92+ QVector<AbstractFilterOption *>::size_type to unsigned int.
93+
94+ [ Ubuntu daily release ]
95+ * Automatic snapshot from revision 240
96+
97+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Mon, 26 Aug 2013 02:31:42 +0000
98+
199unity8 (7.81.3+13.10.20130821.2-0ubuntu1) saucy; urgency=low
2100
3101 [ Michael Terry ]
main.cpp−1
@@ -18,7 +18,6 @@
1818 */
1919
2020// Qt
21−#include <QtQml>
2221#include <QtQuick/QQuickView>
2322#include <QtGui/QIcon>
2423#include <QtGui/QGuiApplication>
plugins/HudClient/qmldir+1
@@ -1,2 +1,3 @@
1+module HudClient
12plugin HudClientQml
23typeinfo plugin.qmltypes
plugins/LightDM/plugin.cpp+1−1
@@ -25,7 +25,7 @@
2525#include <QLightDM/UsersModel>
2626
2727#include <QtCore/QAbstractItemModel>
28−#include <QtQml>
28+#include <QtQml/qqml.h>
2929
3030static QObject *greeter_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
3131{
plugins/Powerd/plugin.cpp+1−1
@@ -20,7 +20,7 @@
2020#include "plugin.h"
2121#include "Powerd.h"
2222
23−#include <QtQml/QtQml>
23+#include <QtQml/qqml.h>
2424
2525static QObject *powerd_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
2626{
plugins/Ubuntu/DownloadDaemonListener/plugin.cpp+1−1
@@ -22,7 +22,7 @@
2222#include "plugin.h"
2323#include "DownloadTracker.h"
2424
25−#include <QtQml>
25+#include <QtQml/qqml.h>
2626#include <QtQml/QQmlContext>
2727
2828
plugins/Unity/Indicators/Network/plugin.cpp+1−1
@@ -17,7 +17,7 @@
1717 */
1818
1919// Qt
20−#include <QtQml>
20+#include <QtQml/qqml.h>
2121
2222// self
2323#include "plugin.h"
plugins/Unity/Indicators/Network/qml/AccessPoint.qml+18−8
@@ -25,29 +25,39 @@ import Unity.Indicators 0.1 as Indicators
2525Indicators.FramedMenuItem {
2626 id: accessPoint
2727
28− readonly property bool checked: menu ? menu.isToggled : false
28+ property bool checked: false
29+ property bool secure: false
30+ property bool adHoc: false
31+ property int signalStrength: 0
32+
33+ signal activate()
2934
3035 onCheckedChanged: {
3136 // Can't rely on binding. Checked is assigned on click.
3237 checkBoxActive.checked = checked;
3338 }
3439
35− // FIXME - we need to get the strength from menu.ext.xCanonicalWifiApStrengthAction
36− // but UnityMenuModel doesnt support fetching actions not attached to menu item.
37− function getNetworkIcon(data) {
40+ icon: {
3841 var imageName = "nm-signal-100"
3942
40− if (data.ext.xCanonicalWifiApIsAdhoc) {
43+ if (adHoc) {
4144 imageName = "nm-adhoc";
45+ } else if (signalStrength == 0) {
46+ imageName = "nm-signal-00";
47+ } else if (signalStrength <= 25) {
48+ imageName = "nm-signal-25";
49+ } else if (signalStrength <= 50) {
50+ imageName = "nm-signal-50";
51+ } else if (signalStrength <= 75) {
52+ imageName = "nm-signal-75";
4253 }
43− if (data.ext.xCanonicalWifiApIsSecure) {
54+
55+ if (secure) {
4456 imageName += "-secure";
4557 }
46−
4758 return "image://gicon/" + imageName;
4859 }
4960
50− icon: menu ? getNetworkIcon(menu) : "image://gicon/wifi-none"
5161 iconFrame: false
5262 control: CheckBox {
5363 id: checkBoxActive
plugins/Unity/Indicators/Network/qml/PasswordPage.qml+1−1
@@ -38,7 +38,7 @@ Page {
3838 }
3939
4040 Indicators.SectionMenuItem {
41− label: "Authentication"
41+ text: "Authentication"
4242 }
4343
4444 Indicators.FramedMenuItem {
plugins/Unity/Indicators/Network/qml/WifiSection.qmldeleted−28
@@ -1,28 +0,0 @@
1−/*
2− * Copyright 2013 Canonical Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU Lesser General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU Lesser General Public License for more details.
12− *
13− * You should have received a copy of the GNU Lesser General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Authors:
17− * Renato Araujo Oliveira Filho <renato@canonical.com>
18− */
19−
20−import QtQuick 2.0
21−import Ubuntu.Components 0.1
22−import Unity.Indicators 0.1 as Indicators
23−
24−Indicators.SectionMenuItem {
25− id: wifiSectionMenu
26−
27− busy: menu ? menu.ext.xCanonicalBusyAction : false
28−}
plugins/Unity/Indicators/Network/qml/qmldir+1−2
@@ -5,5 +5,4 @@ typeinfo plugin.qmltypes
55NetworkPage 0.1 NetworkPage.qml
66AccessPoint 0.1 AccessPoint.qml
77PasswordPage 0.1 PasswordPage.qml
8−PasswordTextField 0.1 PasswordTextField.qml
9−WifiSection 0.1 WifiSection.qml
8+PasswordTextField 0.1 PasswordTextField.qml
plugins/Unity/Indicators/plugin.cpp+1−1
@@ -18,7 +18,7 @@
1818 */
1919
2020// Qt
21−#include <QtQml>
21+#include <QtQml/qqml.h>
2222
2323// self
2424#include "plugin.h"
plugins/Unity/Indicators/qml/BaseMenuItem.qml−4
@@ -25,14 +25,10 @@ ListItem.Standard {
2525 id: baseMenu
2626
2727 property bool menuActivated: false
28− property QtObject menu
2928
3029 signal selectMenu()
3130 signal deselectMenu()
3231
33− signal activate()
34− signal changeState(var value)
35−
3632 showDivider: false
3733 __foregroundColor: "#e8e1d0" // FIXME not in palette yet
3834
plugins/Unity/Indicators/qml/ButtonMenuItem.qml+4−9
@@ -24,14 +24,14 @@ import Unity.Indicators 0.1 as Indicators
2424
2525BaseMenuItem {
2626 id: menuItem
27− objectName: menuAction.name
2827 implicitHeight: units.gu(7)
29− enabled: menuAction.active
28+
29+ property alias text: button.text
30+
31+ signal activate()
3032
3133 Button {
3234 id: button
33− enabled: menuAction.enabled
34− text: menu ? menu.label : ""
3535 anchors.centerIn: parent
3636 height: units.gu(4)
3737 width: units.gu(16)
@@ -41,9 +41,4 @@ BaseMenuItem {
4141 menuItem.activate();
4242 }
4343 }
44−
45− Indicators.MenuAction {
46− id: menuAction
47− menu: menuItem.menu
48− }
4944}
plugins/Unity/Indicators/qml/FramedMenuItem.qml−2
@@ -23,10 +23,8 @@ import Ubuntu.Components 0.1
2323import Ubuntu.Components.ListItems 0.1 as ListItem
2424
2525BaseMenuItem {
26− text: menu && menu.label ? menu.label : ""
2726 implicitHeight: units.gu(7)
2827
29− icon: menu ? menu.icon : ""
3028 iconFrame: false
3129
3230 HLine {
plugins/Unity/Indicators/qml/IndicatorPage.qml+4−9
@@ -96,18 +96,13 @@ IndicatorBase {
9696 }
9797 if (item.hasOwnProperty("menu")) {
9898 item.menu = Qt.binding(function() { return model; });
99−
100− item.activate.connect(onActivateMenu);
101− item.changeState.connect(onChangeState);
10299 }
103100 }
104101
105− function onActivateMenu(value) {
106− mainMenu.model.activate(index, value);
107− }
108−
109− function onChangeState(value) {
110− mainMenu.model.changeState(index, value);
102+ Binding {
103+ target: item ? item : null
104+ property: "objectName"
105+ value: model.action
111106 }
112107 }
113108 }
plugins/Unity/Indicators/qml/MenuAction.qmldeleted−59
@@ -1,59 +0,0 @@
1−/*
2− * Copyright 2013 Canonical Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU Lesser General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU Lesser General Public License for more details.
12− *
13− * You should have received a copy of the GNU Lesser General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Authors:
17− * Renato Araujo Oliveira Filho <renato@canonical.com>
18− * Nick Dedekind <nick.dedekind@canonical.com>
19− */
20−
21−import QtQuick 2.0
22−
23−/*!
24− \qmltype MenuAction
25− \inqmlmodule Indicators 0.1
26− \brief Helper class to connect to a qmenumodel action
27−
28− Example:
29− \qml
30− BaseMenuItem {
31− id: menuItem
32−
33− Switch {
34− checked: menuAction.state
35− }
36−
37− Indicators.MenuAction {
38− id: menuAction
39− menu: menuItem.menu
40− }
41− }
42− \endqml
43−*/
44−
45−Item {
46− id: menuAction
47−
48− /*!
49− \preliminary
50− The dbus action group object
51− */
52− readonly property string name: menu ? menu.action : ""
53−
54− property QtObject menu: null
55−
56− readonly property var state: menu ? menu.actionState : undefined
57−
58− readonly property bool active: menu ? menu.sensitive : false
59−}
plugins/Unity/Indicators/qml/MenuItemFactory.qml+124−7
@@ -20,6 +20,7 @@
2020import QtQuick 2.0
2121import Unity.Indicators 0.1 as Indicators
2222import Unity.Indicators.Network 0.1 as ICNetwork
23+import QMenuModel 0.1 as QMenuModel
2324
2425Item {
2526 id: menuFactory
@@ -47,35 +48,151 @@ Item {
4748 Component {
4849 id: sliderMenu;
4950 Indicators.SliderMenuItem {
51+ property QtObject menu: null
52+
53+ text: menu && menu.label ? menu.label : ""
54+ icon: menu ? menu.icon : ""
55+ minIcon: menu.ext.minIcon
56+ maxIcon: menu.ext.maxIcon
57+
58+ minimumValue: menu.ext.minValue ? menu.ext.minValue : 0.0
59+ maximumValue: {
60+ var maximum = menu.ext.maxValue ? menu.ext.maxValue : 1.0
61+ if (maximum <= minimumValue) {
62+ return minimumValue + 1;
63+ }
64+ return maximum;
65+ }
66+ value: menu ? menu.actionState : 0.0
67+ enabled: menu ? menu.sensitive : false
68+
5069 Component.onCompleted: {
5170 model.loadExtendedAttributes(modelIndex, {'min-value': 'double',
5271 'max-value': 'double',
5372 'min-icon': 'icon',
5473 'max-icon': 'icon'});
5574 }
75+
76+ // FIXME: The interval should be [0.0 - 1.0]. Unfortunately, when
77+ // reaching the boundaries (0.0 or 1.0), the value is converted
78+ // to an integer when automatically wrapped in a variant when
79+ // passed to QStateAction::updateState(…). The server chokes on
80+ // those values, complaining that they’re not of the right type…
81+ onChangeState: {
82+ if (value == Math.round(value)) {
83+ if (value >= maximumValue) {
84+ model.changeState(modelIndex, maximumValue - 0.000001);
85+ } else if (value <= minimumValue) {
86+ model.changeState(modelIndex, minimumValue + 0.000001);
87+ } else {
88+ model.changeState(modelIndex, value * 1.000001);
89+ }
90+ } else {
91+ model.changeState(modelIndex, value);
92+ }
93+ }
5694 }
5795 }
58− Component { id: buttonMenu; Indicators.ButtonMenuItem {} }
96+
5997 Component { id: divMenu; Indicators.DivMenuItem {} }
60− Component { id: sectionMenu; Indicators.SectionMenuItem {} }
61− Component { id: progressMenu; Indicators.ProgressMenuItem {} }
62− Component { id: standardMenu; Indicators.StandardMenuItem {} }
63− Component { id: switchMenu; Indicators.SwitchMenuItem {} }
98+
99+ Component {
100+ id: buttonMenu;
101+ Indicators.ButtonMenuItem {
102+ property QtObject menu: null
103+
104+ text: menu && menu.label ? menu.label : ""
105+ enabled: menu ? menu.sensitive : false
106+
107+ onActivate: model.activate(modelIndex);
108+ }
109+ }
110+ Component {
111+ id: sectionMenu;
112+ Indicators.SectionMenuItem {
113+ property QtObject menu: null
114+
115+ text: menu && menu.label ? menu.label : ""
116+ }
117+ }
118+
119+ Component {
120+ id: progressMenu;
121+ Indicators.ProgressMenuItem {
122+ property QtObject menu: null
123+
124+ text: menu && menu.label ? menu.label : ""
125+ icon: menu ? menu.icon : ""
126+ value : menu ? menu.actionState : 0.0
127+ }
128+ }
129+
130+ Component {
131+ id: standardMenu;
132+ Indicators.StandardMenuItem {
133+ property QtObject menu: null
134+
135+ text: menu && menu.label ? menu.label : ""
136+ icon: menu ? menu.icon : ""
137+ checkable: menu ? (menu.isCheck || menu.isRadio) : false
138+ checked: checkable ? menu.isToggled : false
139+ enabled: menu ? menu.sensitive : false
140+
141+ onActivate: model.activate(modelIndex);
142+ }
143+ }
144+
145+ Component {
146+ id: switchMenu;
147+ Indicators.SwitchMenuItem {
148+ property QtObject menu: null
149+
150+ text: menu && menu.label ? menu.label : ""
151+ icon: menu ? menu.icon : ""
152+ checked: menu ? menu.isToggled : false
153+ enabled: menu ? menu.sensitive : false
154+
155+ onActivate: model.activate(modelIndex);
156+ }
157+ }
158+
64159 Component {
65160 id: wifiSection;
66− ICNetwork.WifiSection {
161+ Indicators.SectionMenuItem {
162+ property QtObject menu: null
163+
164+ text: menu && menu.label ? menu.label : ""
165+ busy: menu ? menu.ext.xCanonicalBusyAction : false
166+
67167 Component.onCompleted: {
68168 model.loadExtendedAttributes(modelIndex, {'x-canonical-busy-action': 'bool'});
69169 }
70170 }
71171 }
172+
72173 Component {
73174 id: accessPoint;
74175 ICNetwork.AccessPoint {
176+ property QtObject menu: null
177+// property var strenthAction: QMenuModel.UnityMenuAction {
178+// model: menuFactory.model ? menuFactory.model : null
179+// name: menu ? menu.ext.xCanonicalWifiApStrengthAction : ""
180+// }
181+
182+ text: menu && menu.label ? menu.label : ""
183+ icon: menu ? menu.icon : ""
184+ secure: menu ? menu.ext.xCanonicalWifiApIsSecure : false
185+ adHoc: menu ? menu.ext.xCanonicalWifiApIsAdhoc : false
186+ checked: menu ? menu.isToggled : false
187+// signalStrength: strenthAction.valid ? strenthAction.state : 0
188+ enabled: menu ? menu.sensitive : false
189+
75190 Component.onCompleted: {
76191 model.loadExtendedAttributes(modelIndex, {'x-canonical-wifi-ap-is-adhoc': 'bool',
77− 'x-canonical-wifi-ap-is-secure': 'bool'});
192+ 'x-canonical-wifi-ap-is-secure': 'bool',
193+ 'x-canonical-wifi-ap-strength-action': 'string'});
78194 }
195+ onActivate: model.activate(modelIndex);
79196 }
80197 }
81198
plugins/Unity/Indicators/qml/ProgressMenuItem.qml+1−8
@@ -23,10 +23,8 @@ import Unity.Indicators 0.1 as Indicators
2323
2424FramedMenuItem {
2525 id: menuItem
26− objectName: menuAction.name
27− enabled: menuAction.active
2826
29− property int value : menuAction.state
27+ property int value : 0.0
3028
3129 control: Label {
3230 id: progress
@@ -37,9 +35,4 @@ FramedMenuItem {
3735 fontSize: "medium"
3836 color: "#e8e1d0"
3937 }
40−
41− Indicators.MenuAction {
42− id: menuAction
43− menu: menuItem.menu
44− }
4538}
plugins/Unity/Indicators/qml/SectionMenuItem.qml+3−3
@@ -22,15 +22,15 @@ import Ubuntu.Components 0.1
2222import Ubuntu.Components.ListItems 0.1 as ListItem
2323
2424BaseMenuItem {
25− property string label: menu && menu.label ? menu.label : ""
25+ id: menuItem
26+ property alias text: header.text
2627 property bool busy: false
2728
28− implicitHeight: label !== "" ? header.height : 0
29+ implicitHeight: text !== "" ? header.height : 0
2930
3031 ListItem.Header {
3132 id: header
3233
33− text: label
3434 height: units.gu(4)
3535 anchors {
3636 left: parent.left
plugins/Unity/Indicators/qml/SliderMenuItem.qml+8−23
@@ -24,17 +24,20 @@ import Unity.Indicators 0.1 as Indicators
2424
2525FramedMenuItem {
2626 id: menuItem
27− objectName: menuAction.name
28− enabled: menuAction.active
2927
3028 property alias minimumValue: slider.minimumValue
3129 property alias maximumValue: slider.maximumValue
32− readonly property double value: menu ? menuAction.state : 0.0
30+ property double value: 0.0
31+
32+ property alias minIcon: leftImage.source
33+ property alias maxIcon: rightImage.source
3334
3435 property QtObject d: QtObject {
3536 property bool enableValueConnection: true
3637 }
3738
39+ signal changeState(real value)
40+
3841 onValueChanged: {
3942 // TODO: look into adding a component to manage bi-directional bindings.
4043 var oldEnable = d.enableValueConnection
@@ -58,7 +61,6 @@ FramedMenuItem {
5861 anchors.verticalCenter: row.verticalCenter
5962 height: units.gu(4)
6063 width: height
61− source: menu.ext.minIcon
6264 }
6365
6466 Components.Slider {
@@ -75,19 +77,8 @@ FramedMenuItem {
7577 value = menuItem.value
7678 }
7779
78− // FIXME: The interval should be [0.0 - 1.0]. Unfortunately, when
79− // reaching the boundaries (0.0 or 1.0), the value is converted
80− // to an integer when automatically wrapped in a variant when
81− // passed to QStateAction::updateState(…). The server chokes on
82− // those values, complaining that they’re not of the right type…
83− minimumValue: menu.ext.minValue ? menu.ext.minValue * 1.000001 : 0.0000001
84− maximumValue: {
85− var maximum = menu.ext.maxValue ? menu.ext.maxValue * 1.000001 : 0.9999999
86− if (maximum <= minimumValue) {
87− return minimumValue + 1;
88− }
89− return maximum;
90− }
80+ minimumValue: 0.0
81+ maximumValue: 0.1
9182
9283 // FIXME - to be deprecated in Ubuntu.Components.
9384 // Use this to disable the label, since there is not way to do it on the component.
@@ -109,12 +100,6 @@ FramedMenuItem {
109100 anchors.verticalCenter: row.verticalCenter
110101 height: units.gu(4)
111102 width: height
112− source: menu.ext.maxIcon
113103 }
114104 }
115−
116− Indicators.MenuAction {
117− id: menuAction
118− menu: menuItem.menu
119− }
120105}
plugins/Unity/Indicators/qml/StandardMenuItem.qml+5−10
@@ -23,16 +23,16 @@ import Unity.Indicators 0.1 as Indicators
2323
2424FramedMenuItem {
2525 id: menuItem
26− objectName: menuAction.name
27− enabled: menuAction.active
2826
29− readonly property bool checkable: menu ? (menu.isCheck || menu.isRadio) : false
30− readonly property bool checked: checkable ? menu.isToggled : false
27+ property bool checkable: false
28+ property bool checked: false
29+
30+ signal activate()
3131
3232 // FIXME : need a radio button from sdk
3333 // https://bugs.launchpad.net/ubuntu-ui-toolkit/+bug/1211866
3434 onCheckableChanged: {
35− if (menu && checkable) {
35+ if (checkable) {
3636 icon = checkComponent.createObject(menuItem);
3737 }
3838 }
@@ -68,9 +68,4 @@ FramedMenuItem {
6868 }
6969 }
7070 }
71−
72− Indicators.MenuAction {
73− id: menuAction
74− menu: menuItem.menu
75− }
7671}
plugins/Unity/Indicators/qml/SwitchMenuItem.qml+3−8
@@ -24,10 +24,10 @@ import Unity.Indicators 0.1 as Indicators
2424
2525FramedMenuItem {
2626 id: menuItem
27− objectName: menuAction.name
28− enabled: menuAction.enabled
2927
30− readonly property bool checked: menu ? menu.isToggled : false
28+ property bool checked: false
29+
30+ signal activate()
3131
3232 onCheckedChanged: {
3333 // Can't rely on binding. Checked is assigned on click.
@@ -50,9 +50,4 @@ FramedMenuItem {
5050 menuItem.activate();
5151 }
5252 }
53−
54− Indicators.MenuAction {
55− id: menuAction
56− menu: menuItem.menu
57− }
5853}
plugins/Unity/Indicators/qml/qmldir−1
@@ -11,7 +11,6 @@ HLine 0.1 HLine.qml
1111IndicatorBase 0.1 IndicatorBase.qml
1212IndicatorPage 0.1 IndicatorPage.qml
1313IndicatorWidget 0.1 IndicatorWidget.qml
14−MenuAction 0.1 MenuAction.qml
1514MenuActionBinding 0.1 MenuActionBinding.qml
1615MenuItemFactory 0.1 MenuItemFactory.qml
1716ProgressMenuItem 0.1 ProgressMenuItem.qml
plugins/Unity/IndicatorsLegacy/Network/plugin.cpp+1−1
@@ -17,7 +17,7 @@
1717 */
1818
1919// Qt
20−#include <QtQml>
20+#include <QtQml/qqml.h>
2121
2222// self
2323#include "plugin.h"
plugins/Unity/IndicatorsLegacy/plugin.cpp+1−1
@@ -17,7 +17,7 @@
1717 */
1818
1919// Qt
20−#include <QtQml>
20+#include <QtQml/qqml.h>
2121
2222// self
2323#include "plugin.h"
plugins/Unity/Launcher/launchermodel.cpp+34
@@ -25,6 +25,9 @@ LauncherModel::LauncherModel(QObject *parent):
2525 LauncherModelInterface(parent),
2626 m_backend(new LauncherBackend(this))
2727{
28+ connect(m_backend, SIGNAL(countChanged(QString,int)), SLOT(countChanged(QString,int)));
29+ connect(m_backend, SIGNAL(progressChanged(QString,int)), SLOT(progressChanged(QString,int)));
30+
2831 Q_FOREACH (const QString &entry, m_backend->storedApplications()) {
2932 LauncherItem *item = new LauncherItem(entry,
3033 m_backend->desktopFile(entry),
@@ -67,6 +70,10 @@ QVariant LauncherModel::data(const QModelIndex &index, int role) const
6770 return item->icon();
6871 case RolePinned:
6972 return item->pinned();
73+ case RoleCount:
74+ return item->count();
75+ case RoleProgress:
76+ return item->progress();
7077 }
7178
7279 return QVariant();
@@ -177,6 +184,12 @@ void LauncherModel::quickListActionInvoked(const QString &appId, int actionIndex
177184 }
178185}
179186
187+void LauncherModel::setUser(const QString &username)
188+{
189+ Q_UNUSED(username)
190+ // TODO: Implement this...
191+}
192+
180193void LauncherModel::storeAppList()
181194{
182195 QStringList appIds;
@@ -198,3 +211,24 @@ int LauncherModel::findApplication(const QString &appId)
198211 }
199212 return -1;
200213}
214+
215+void LauncherModel::progressChanged(const QString &appId, int progress)
216+{
217+ int idx = findApplication(appId);
218+ if (idx >= 0) {
219+ LauncherItem *item = m_list.at(idx);
220+ item->setProgress(progress);
221+ Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleProgress);
222+ }
223+}
224+
225+
226+void LauncherModel::countChanged(const QString &appId, int count)
227+{
228+ int idx = findApplication(appId);
229+ if (idx >= 0) {
230+ LauncherItem *item = m_list.at(idx);
231+ item->setCount(count);
232+ Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleCount);
233+ }
234+}
plugins/Unity/Launcher/launchermodel.h+5
@@ -48,11 +48,16 @@ public:
4848 Q_INVOKABLE void pin(const QString &appId, int index = -1);
4949 Q_INVOKABLE void requestRemove(const QString &appId);
5050 Q_INVOKABLE void quickListActionInvoked(const QString &appId, int actionIndex);
51+ Q_INVOKABLE void setUser(const QString &username);
5152
5253private:
5354 void storeAppList();
5455 int findApplication(const QString &appId);
5556
57+private Q_SLOTS:
58+ void progressChanged(const QString &appId, int progress);
59+ void countChanged(const QString &appId, int count);
60+
5661private:
5762 QList<LauncherItem*> m_list;
5863 LauncherBackend *m_backend;
plugins/Unity/Launcher/plugin.cpp+1−1
@@ -18,7 +18,7 @@
1818 */
1919
2020// Qt
21−#include <QtQml>
21+#include <QtQml/qqml.h>
2222
2323// self
2424#include "plugin.h"
plugins/Unity/genericoptionsmodel.cpp+2−2
@@ -76,9 +76,9 @@ int GenericOptionsModel::rowCount(const QModelIndex& /* parent */) const
7676 return m_options.size ();
7777}
7878
79−void GenericOptionsModel::setActive(QVector<AbstractFilterOption *>::size_type idx, bool value)
79+void GenericOptionsModel::setActive(int idx, bool value)
8080{
81− if (idx < m_options.size())
81+ if (idx >= 0 && idx < m_options.size())
8282 {
8383 m_options[idx]->setActive(value);
8484 }
plugins/Unity/genericoptionsmodel.h+1−1
@@ -49,7 +49,7 @@ public:
4949 QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
5050 int rowCount(const QModelIndex& parent = QModelIndex()) const override;
5151
52− Q_INVOKABLE void setActive(QVector<AbstractFilterOption *>::size_type idx, bool value);
52+ Q_INVOKABLE void setActive(int idx, bool value);
5353 Q_INVOKABLE void setActive(const QString &id, bool value);
5454
5555 AbstractFilterOption* getRawOption(QVector<AbstractFilterOption *>::size_type idx) const;
plugins/Unity/plugin.cpp+4−1
@@ -17,8 +17,9 @@
1717 */
1818
1919// Qt
20−#include <QtQml>
2120#include <QDBusConnection>
21+#include <QQmlContext>
22+#include <qqml.h>
2223
2324// self
2425#include "plugin.h"
@@ -33,6 +34,7 @@
3334#include "categories.h"
3435#include "categoryresults.h"
3536#include "bottombarvisibilitycommunicatorshell.h"
37+#include "genericoptionsmodel.h"
3638
3739// libqtdee
3840#include "deelistmodel.h"
@@ -47,6 +49,7 @@ void UnityPlugin::registerTypes(const char *uri)
4749 qmlRegisterUncreatableType<PreviewAction>(uri, 0, 1, "PreviewAction", "Can't create PreviewAction object in QML.");
4850 qmlRegisterUncreatableType<PreviewInfoHint>(uri, 0, 1, "PreviewInfoHint", "Can't create PreviewInfoHint object in QML.");
4951 qmlRegisterUncreatableType<SocialPreviewComment>(uri, 0, 1, "SocialPreviewComment", "Can't create SocialPreviewComment object in QML.");
52+ qmlRegisterUncreatableType<GenericOptionsModel>(uri, 0, 1, "GenericOptionsModel", "Can't create options model in QML.");
5053 qmlRegisterType<Scope>(uri, 0, 1, "Scope");
5154 qmlRegisterType<Scopes>(uri, 0, 1, "Scopes");
5255 qmlRegisterType<Categories>(uri, 0, 1, "Categories");
plugins/Utils/plugin.cpp+1−1
@@ -17,7 +17,7 @@
1717 */
1818
1919// Qt
20−#include <QtQml>
20+#include <QtQml/qqml.h>
2121#include <QtQuick/QQuickWindow>
2222
2323// self
po/fi.po+13−13
@@ -8,15 +8,15 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2013-08-09 12:18+0200\n"
11−"PO-Revision-Date: 2013-08-12 17:45+0000\n"
12−"Last-Translator: Aleksi Kinnunen <Unknown>\n"
11+"PO-Revision-Date: 2013-08-23 16:15+0000\n"
12+"Last-Translator: Timo Jyrinki <timo.jyrinki@canonical.com>\n"
1313"Language-Team: Finnish <fi@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
1717"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-08-22 06:11+0000\n"
19−"X-Generator: Launchpad (build 16734)\n"
18+"X-Launchpad-Export-Date: 2013-08-24 05:59+0000\n"
19+"X-Generator: Launchpad (build 16738)\n"
2020
2121#: Applications/applications.js:23
2222msgid "Phone"
@@ -94,35 +94,35 @@ msgstr "Viimeisimmät haut"
9494#: Dash/Apps/AppInfo.qml:72
9595#, qt-format
9696msgid "(%1)"
97−msgstr ""
97+msgstr "(%1)"
9898
9999#. TRANSLATORS: Number of persons who wrote reviews for this app
100100#: Dash/Apps/AppInfo.qml:84 tests/qmltests/Dash/Apps/tst_AppPreview.qml:115
101101#, qt-format
102102msgid "%1 review"
103103msgid_plural "%1 reviews"
104−msgstr[0] ""
105−msgstr[1] ""
104+msgstr[0] "%1 arvostelu"
105+msgstr[1] "%1 arvostelua"
106106
107107#: Dash/Apps/AppPreview.qml:181
108108msgid "Rate this"
109−msgstr ""
109+msgstr "Arvioi tämä"
110110
111111#: Dash/Apps/AppReviews.qml:72
112112msgid "Add a review"
113−msgstr ""
113+msgstr "Lisää arvostelu"
114114
115115#: Dash/Apps/AppReviews.qml:84
116116msgid "Review"
117−msgstr ""
117+msgstr "Arvostelu"
118118
119119#: Dash/Apps/AppReviews.qml:118
120120msgid "Send"
121−msgstr ""
121+msgstr "Lähetä"
122122
123123#: Dash/Apps/AppReviews.qml:136
124124msgid "Comments:"
125−msgstr ""
125+msgstr "Kommentit:"
126126
127127#: Dash/DashApps.qml:65
128128msgid "Running apps"
@@ -244,4 +244,4 @@ msgstr "Hakuun täsmääviä tuloksia ei löydy."
244244#: Shell.qml:399
245245#, qt-format
246246msgid "Please enter %1"
247−msgstr ""
247+msgstr "Kirjoita %1"
tests/autopilot/unity8/shell/emulators/create_interactive_notification.pynew file+102
@@ -0,0 +1,102 @@
1+#!/usr/bin/env python
2+
3+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
4+#
5+# Unity Autopilot Test Suite
6+# Copyright (C) 2012-2013 Canonical
7+#
8+# This program is free software: you can redistribute it and/or modify
9+# it under the terms of the GNU General Public License as published by
10+# the Free Software Foundation, either version 3 of the License, or
11+# (at your option) any later version.
12+#
13+# This program is distributed in the hope that it will be useful,
14+# but WITHOUT ANY WARRANTY; without even the implied warranty of
15+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+# GNU General Public License for more details.
17+#
18+# You should have received a copy of the GNU General Public License
19+# along with this program. If not, see <http://www.gnu.org/licenses/>.
20+#
21+
22+import argparse
23+from gi.repository import GLib, Notify
24+import signal
25+
26+
27+def action_callback(notification, action_id, data):
28+ print action_id
29+
30+
31+def quit_callback(notification):
32+ loop.quit()
33+
34+
35+if __name__ == '__main__':
36+ parser = argparse.ArgumentParser(
37+ description='Create an interactive notification'
38+ )
39+
40+ parser.add_argument(
41+ '--summary',
42+ help='summary text of the notification',
43+ default='Summary'
44+ )
45+ parser.add_argument(
46+ '--body',
47+ help='body text of the notification',
48+ default='Body'
49+ )
50+ parser.add_argument(
51+ '--icon',
52+ help='path to the icon to display',
53+ default=None
54+ )
55+ parser.add_argument(
56+ '--action',
57+ help='id and label for the callback in the format: id,label',
58+ action='append',
59+ default=[]
60+ )
61+ parser.add_argument(
62+ '--urgency',
63+ help='LOW, NORMAL, CRITICAL',
64+ choices=['LOW', 'NORMAL', 'CRITICAL'],
65+ default='NORMAL'
66+ )
67+ parser.add_argument(
68+ '--hints',
69+ help='list of comma sep options',
70+ action='append',
71+ default=[]
72+ )
73+
74+ args = parser.parse_args()
75+
76+ Notify.init('Interactive Notifications')
77+
78+ notification = Notify.Notification.new(args.summary, args.body, args.icon)
79+
80+ for hint in args.hints:
81+ key, value = hint.split(',', 1)
82+ notification.set_hint_string(key, value)
83+
84+ for action in args.action:
85+ action_id, action_label = action.split(',', 1)
86+ notification.add_action(
87+ action_id,
88+ action_label,
89+ action_callback,
90+ None,
91+ None
92+ )
93+
94+ def signal_handler(signam, frame):
95+ loop.quit()
96+
97+ signal.signal(signal.SIGINT, signal_handler)
98+
99+ loop = GLib.MainLoop.new(None, False)
100+ notification.connect('closed', quit_callback)
101+ notification.show()
102+ loop.run()
tests/autopilot/unity8/shell/tests/__init__.py+20−20
@@ -51,26 +51,25 @@ UNITYSHELL_GSETTINGS_PATH = "/org/compiz/profiles/unity/plugins/unityshell/"
5151UNITYSHELL_LAUNCHER_KEY = "launcher-hide-mode"
5252UNITYSHELL_LAUNCHER_MODE = 1 # launcher hidden
5353
54−def _get_device_emulation_scenarios():
54+def _get_device_emulation_scenarios(devices='All'):
55+ nexus4 = ('Desktop Nexus 4',
56+ dict(app_width=768, app_height=1280, grid_unit_px=18))
57+ nexus10 = ('Desktop Nexus 10',
58+ dict(app_width=2560, app_height=1600, grid_unit_px=20))
59+ native = ('Native Device',
60+ dict(app_width=0, app_height=0, grid_unit_px=0))
61+
5562 if model() == 'Desktop':
56− return [
57− (
58− 'Desktop Nexus 4',
59− dict(app_width=768, app_height=1280, grid_unit_px=18)
60− ),
61− (
62− 'Desktop Nexus 10',
63− dict(app_width=2560, app_height=1600, grid_unit_px=20)
64− ),
65− ]
63+ if devices == 'All':
64+ return [nexus4, nexus10]
65+ elif devices == 'Nexus4':
66+ return [nexus4]
67+ elif devices == 'Nexus10':
68+ return [nexus10]
69+ else:
70+ raise RuntimeException('Unrecognized device-option "%s" passed.' % devices)
6671 else:
67− return [
68− (
69− 'Native Device',
70− dict(app_width=0, app_height=0, grid_unit_px=0)
71− )
72− ]
73−
72+ return [native]
7473
7574class UnityTestCase(AutopilotTestCase):
7675
@@ -148,7 +147,7 @@ class UnityTestCase(AutopilotTestCase):
148147 divisor = divisor * 2
149148 return divisor
150149
151− def launch_unity(self):
150+ def launch_unity(self, **kwargs):
152151 """Launch the unity shell, return a proxy object for it."""
153152 binary_path = get_binary_path()
154153 lib_path = get_lib_path()
@@ -167,7 +166,8 @@ class UnityTestCase(AutopilotTestCase):
167166 binary_path,
168167 *self.unity_geometry_args,
169168 app_type='qt',
170− emulator_base=UnityEmulatorBase
169+ emulator_base=UnityEmulatorBase,
170+ **kwargs
171171 )
172172 self._set_proxy(app_proxy)
173173
tests/autopilot/unity8/shell/tests/test_notifications.pynew file+737
@@ -0,0 +1,737 @@
1+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2+#
3+# Unity Autopilot Test Suite
4+# Copyright (C) 2012-2013 Canonical
5+#
6+# This program is free software: you can redistribute it and/or modify
7+# it under the terms of the GNU General Public License as published by
8+# the Free Software Foundation, either version 3 of the License, or
9+# (at your option) any later version.
10+#
11+# This program is distributed in the hope that it will be useful,
12+# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+# GNU General Public License for more details.
15+#
16+# You should have received a copy of the GNU General Public License
17+# along with this program. If not, see <http://www.gnu.org/licenses/>.
18+#
19+
20+"""Tests for Notifications"""
21+
22+from __future__ import absolute_import
23+
24+from unity8.shell.tests import UnityTestCase, _get_device_emulation_scenarios
25+
26+from testtools.matchers import Equals, NotEquals
27+from autopilot.matchers import Eventually
28+
29+from gi.repository import Notify
30+import time
31+import os
32+import logging
33+import signal
34+import subprocess
35+
36+logger = logging.getLogger(__name__)
37+
38+
39+class NotificationsBase(UnityTestCase):
40+ """Base class for all notification tests that provides helper methods."""
41+
42+ scenarios = _get_device_emulation_scenarios('Nexus4')
43+
44+ def _get_icon_path(self, icon_name):
45+ """Given an icons file name returns the full path (either system or
46+ source tree.
47+
48+ Consider the graphics directory as root so for example (runnign tests
49+ from installed unity8-autopilot package):
50+ >>> self.get_icon_path('clock.png')
51+ /usr/share/unity8/graphics/clock.png
52+
53+ >>> self.get_icon_path('applicationIcons/facebook.png')
54+ /usr/share/unity8/graphics/applicationIcons/facebook.png
55+
56+ """
57+ if os.path.abspath(__file__).startswith('/usr/'):
58+ return '/usr/share/unity8/graphics/' + icon_name
59+ else:
60+ return os.path.abspath(
61+ os.getcwd() + "/../../graphics/" + icon_name
62+ )
63+
64+ def _get_notifications_list(self):
65+ main_view = self.main_window.get_qml_view()
66+ return main_view.select_single(
67+ "QQuickListView",
68+ objectName='notificationList'
69+ )
70+
71+ def _assert_notification(
72+ self,
73+ notification,
74+ summary=None,
75+ body=None,
76+ icon=True,
77+ secondary_icon=False,
78+ opacity=None
79+ ):
80+ """Assert that the expected qualities of a notification are as
81+ expected.
82+
83+ """
84+
85+ if summary is not None:
86+ self.assertThat(notification.summary, Eventually(Equals(summary)))
87+
88+ if body is not None:
89+ self.assertThat(notification.body, Eventually(Equals(body)))
90+
91+ if icon:
92+ self.assertThat(notification.iconSource, Eventually(NotEquals("")))
93+ else:
94+ self.assertThat(notification.iconSource, Eventually(Equals("")))
95+
96+ if secondary_icon:
97+ self.assertThat(
98+ notification.secondaryIconSource,
99+ Eventually(NotEquals(""))
100+ )
101+ else:
102+ self.assertThat(
103+ notification.secondaryIconSource,
104+ Eventually(Equals(""))
105+ )
106+
107+ if opacity is not None:
108+ self.assertThat(notification.opacity, Eventually(Equals(opacity)))
109+
110+
111+class InteractiveNotificationBase(NotificationsBase):
112+ """Collection of test for Interactive tests including snap decisions."""
113+
114+ def setUp(self):
115+ super(InteractiveNotificationBase, self).setUp()
116+ # Need to keep track when we launch the notification script.
117+ self._notify_proc = None
118+
119+ def test_interactive(self):
120+ """Interactive notification must react upon click on itself."""
121+ self.launch_unity()
122+ greeter = self.main_window.get_greeter()
123+ greeter.swipe()
124+
125+ notify_list = self._get_notifications_list()
126+
127+ summary = "Interactive notification"
128+ body = "This notification can be clicked on to trigger an action."
129+ icon_path = self._get_icon_path('avatars/anna_olsson.png')
130+ actions = [("action_id", "dummy")]
131+ hints = [
132+ ("x-canonical-switch-to-application", "true"),
133+ (
134+ "x-canonical-secondary-icon",
135+ self._get_icon_path('applicationIcons/phone-app.png')
136+ )
137+ ]
138+
139+ self._create_interactive_notification(
140+ summary,
141+ body,
142+ icon_path,
143+ "NORMAL",
144+ actions,
145+ hints,
146+ )
147+
148+ get_notification = lambda: notify_list.select_single('Notification')
149+ self.assertThat(get_notification, Eventually(NotEquals(None)))
150+ notification = get_notification()
151+
152+ self.touch.tap_object(
153+ notification.select_single(objectName="interactiveArea")
154+ )
155+
156+ self.assert_notification_action_id_was_called('action_id')
157+
158+ def test_sd_incoming_call(self):
159+ """Rejecting a call should make notification expand and offer more options."""
160+ self.launch_unity()
161+ greeter = self.main_window.get_greeter()
162+ greeter.swipe()
163+
164+ notify_list = self._get_notifications_list()
165+
166+ summary = "Incoming call"
167+ body = "Frank Zappa\n+44 (0)7736 027340"
168+ icon_path = self._get_icon_path('avatars/anna_olsson.png')
169+ hints = [
170+ (
171+ "x-canonical-secondary-icon",
172+ self._get_icon_path('applicationIcons/phone-app.png')
173+ ),
174+ ("x-canonical-snap-decisions", "true"),
175+ ]
176+
177+ actions = [
178+ ('action_accept', 'Accept'),
179+ ('action_decline_1', 'Decline'),
180+ ('action_decline_2', '"Can\'t talk now, what\'s up?"'),
181+ ('action_decline_3', '"I call you back."'),
182+ ('action_decline_4', 'Send custom message...'),
183+ ]
184+
185+ self._create_interactive_notification(
186+ summary,
187+ body,
188+ icon_path,
189+ "NORMAL",
190+ actions,
191+ hints
192+ )
193+
194+ get_notification = lambda: notify_list.select_single('Notification')
195+ self.assertThat(get_notification, Eventually(NotEquals(None)))
196+ notification = get_notification()
197+ self._assert_notification(notification, None, None, True, True, 1.0)
198+ initial_height = notification.height
199+ self.touch.tap_object(notification.select_single(objectName="button1"))
200+ self.assertThat(
201+ notification.height,
202+ Eventually(Equals(initial_height +
203+ 3 * notification.select_single(objectName="buttonColumn").spacing +
204+ 3 * notification.select_single(objectName="button4").height)))
205+ self.touch.tap_object(notification.select_single(objectName="button4"))
206+ self.assert_notification_action_id_was_called("action_decline_4")
207+
208+ def _create_interactive_notification(
209+ self,
210+ summary="",
211+ body="",
212+ icon=None,
213+ urgency="NORMAL",
214+ actions=[],
215+ hints=[]
216+ ):
217+ """Create a interactive notification command.
218+
219+ :param summary: Summary text for the notification
220+ :param body: Body text to display in the notification
221+ :param icon: Path string to the icon to use
222+ :param urgency: Urgency string for the noticiation, either: 'LOW',
223+ 'NORMAL', 'CRITICAL'
224+ :param actions: List of tuples containing the 'id' and 'label' for all
225+ the actions to add
226+ :param hint_strings: List of tuples containing the 'name' and value for
227+ setting the hint strings for the notification
228+
229+ """
230+
231+ logger.info(
232+ "Creating snap-decision notification with summary(%s), body(%s) "
233+ "and urgency(%r)",
234+ summary,
235+ body,
236+ urgency
237+ )
238+
239+ script_args = [
240+ '--summary', summary,
241+ '--body', body,
242+ '--urgency', urgency
243+ ]
244+
245+ if icon is not None:
246+ script_args.extend(['--icon', icon])
247+
248+ for hint in hints:
249+ key, value = hint
250+ script_args.extend(['--hint', "%s,%s" % (key, value)])
251+
252+ for action in actions:
253+ action_id, action_label = action
254+ action_string = "%s,%s" % (action_id, action_label)
255+ script_args.extend(['--action', action_string])
256+
257+ python_bin = subprocess.check_output(['which', 'python']).strip()
258+ command = [python_bin, self._get_notify_script()] + script_args
259+ logger.info("Launching snap-decision notification as: %s", command)
260+ self._notify_proc = subprocess.Popen(
261+ command,
262+ stdin=subprocess.PIPE,
263+ stdout=subprocess.PIPE,
264+ stderr=subprocess.PIPE,
265+ close_fds=True,
266+ )
267+
268+ self.addCleanup(self._tidy_up_script_process)
269+
270+ poll_result = self._notify_proc.poll()
271+ if poll_result is not None and self._notify_proc.returncode != 0:
272+ error_output = self._notify_proc.communicate()[1]
273+ raise RuntimeError("Call to script failed with: %s" % error_output)
274+
275+ def _get_notify_script(self):
276+ """Returns the path to the interactive notification creation script."""
277+ file_path = "../../emulators/create_interactive_notification.py"
278+
279+ the_path = os.path.abspath(
280+ os.path.join(__file__, file_path))
281+
282+ return the_path
283+
284+ def _tidy_up_script_process(self):
285+ if self._notify_proc is not None and self._notify_proc.poll() is None:
286+ logger.error("Notification process wasn't killed, killing now.")
287+ os.killpg(self._notify_proc.pid, signal.SIGTERM)
288+
289+ def assert_notification_action_id_was_called(self, action_id, timeout=10):
290+ """Assert that the interactive notification callback of id *action_id*
291+ was called.
292+
293+ :raises AssertionError: If no interactive notification has actually
294+ been created.
295+ :raises AssertionError: When *action_id* does not match the actual
296+ returned.
297+ :raises AssertionError: If no callback was called at all.
298+ """
299+
300+ if self._notify_proc is None:
301+ raise AssertionError("No interactive notification was created.")
302+
303+ for i in xrange(timeout):
304+ self._notify_proc.poll()
305+ if self._notify_proc.returncode is not None:
306+ output = self._notify_proc.communicate()
307+ actual_action_id = output[0].strip("\n")
308+ if actual_action_id != action_id:
309+ raise AssertionError(
310+ "action id '%s' does not match actual returned '%s'"
311+ % (action_id, actual_action_id)
312+ )
313+ else:
314+ return
315+ time.sleep(1)
316+
317+ os.killpg(self._notify_proc.pid, signal.SIGTERM)
318+ self._notify_proc = None
319+ raise AssertionError(
320+ "No callback was called, killing interactivenotification script"
321+ )
322+
323+
324+class EphemeralNotificationsTests(NotificationsBase):
325+ """Collection of tests for Emphemeral notifications (non-interactive.)"""
326+
327+ def setUp(self):
328+ super(EphemeralNotificationsTests, self).setUp()
329+ # Because we are using the Notify library we need to init and un-init
330+ # otherwise we get crashes.
331+ Notify.init("Autopilot Ephemeral Notification Tests")
332+ self.addCleanup(Notify.uninit)
333+
334+ def test_icon_summary_body(self):
335+ """Notification must display the expected summary and body text."""
336+ self.launch_unity()
337+ greeter = self.main_window.get_greeter()
338+ greeter.swipe()
339+
340+ notify_list = self._get_notifications_list()
341+
342+ summary = "Icon-Summary-Body"
343+ body = "Hey pal, what's up with the party next weekend? Will you " \
344+ "join me and Anna?"
345+ icon_path = self._get_icon_path('avatars/anna_olsson.png')
346+ hints = [
347+ (
348+ "x-canonical-secondary-icon",
349+ self._get_icon_path('applicationIcons/phone-app.png')
350+ )
351+ ]
352+
353+ notification = self._create_ephemeral_notification(
354+ summary,
355+ body,
356+ icon_path,
357+ hints,
358+ "NORMAL",
359+ )
360+
361+ notification.show()
362+
363+ get_notification = lambda: notify_list.select_single('Notification')
364+ self.assertThat(get_notification, Eventually(NotEquals(None)))
365+ notification = get_notification()
366+ self._assert_notification(notification, summary, body, True, True, 1.0)
367+
368+ def test_icon_summary(self):
369+ """Notification must display the expected summary and secondary icon."""
370+ self.launch_unity()
371+ greeter = self.main_window.get_greeter()
372+ greeter.swipe()
373+
374+ notify_list = self._get_notifications_list()
375+
376+ summary = "Upload of image completed"
377+ hints = [
378+ (
379+ "x-canonical-secondary-icon",
380+ self._get_icon_path('applicationIcons/facebook.png')
381+ )
382+ ]
383+
384+ notification = self._create_ephemeral_notification(
385+ summary,
386+ None,
387+ None,
388+ hints,
389+ "NORMAL",
390+ )
391+
392+ notification.show()
393+
394+ get_notification = lambda: notify_list.select_single('Notification')
395+ self.assertThat(get_notification, Eventually(NotEquals(None)))
396+ notification = get_notification()
397+ self._assert_notification(
398+ notification,
399+ summary,
400+ None,
401+ False,
402+ True,
403+ 1.0
404+ )
405+
406+ def test_urgency_order(self):
407+ """Notifications must be displayed in order according to their urgency."""
408+ self.launch_unity()
409+ greeter = self.main_window.get_greeter()
410+ greeter.swipe()
411+
412+ notify_list = self._get_notifications_list()
413+
414+ summary_low = 'Low Urgency'
415+ body_low = "No, I'd rather see paint dry, pal *yawn*"
416+ icon_path_low = self._get_icon_path('avatars/amanda.png')
417+
418+ summary_normal = 'Normal Urgency'
419+ body_normal = "Hey pal, what's up with the party next weekend? Will " \
420+ "you join me and Anna?"
421+ icon_path_normal = self._get_icon_path('avatars/funky.png')
422+
423+ summary_critical = 'Critical Urgency'
424+ body_critical = 'Dude, this is so urgent you have no idea :)'
425+ icon_path_critical = self._get_icon_path('avatars/anna_olsson.png')
426+
427+ notification_normal = self._create_ephemeral_notification(
428+ summary_normal,
429+ body_normal,
430+ icon_path_normal,
431+ urgency="NORMAL"
432+ )
433+ notification_normal.show()
434+
435+ notification_low = self._create_ephemeral_notification(
436+ summary_low,
437+ body_low,
438+ icon_path_low,
439+ urgency="LOW"
440+ )
441+ notification_low.show()
442+
443+ notification_critical = self._create_ephemeral_notification(
444+ summary_critical,
445+ body_critical,
446+ icon_path_critical,
447+ urgency="CRITICAL"
448+ )
449+ notification_critical.show()
450+
451+ get_notification = lambda: notify_list.select_single(
452+ 'Notification',
453+ summary=summary_critical
454+ )
455+ self.assertThat(get_notification, Eventually(NotEquals(None)))
456+
457+ notification = get_notification()
458+ self._assert_notification(
459+ notification,
460+ summary_critical,
461+ body_critical,
462+ True,
463+ False,
464+ 1.0
465+ )
466+
467+ get_normal_notification = lambda: notify_list.select_single(
468+ 'Notification',
469+ summary=summary_normal
470+ )
471+ self.assertThat(get_normal_notification, Eventually(NotEquals(None)))
472+ notification = get_normal_notification()
473+ self._assert_notification(
474+ notification,
475+ summary_normal,
476+ body_normal,
477+ True,
478+ False,
479+ 1.0
480+ )
481+
482+ get_low_notification = lambda: notify_list.select_single(
483+ 'Notification',
484+ summary=summary_low
485+ )
486+ self.assertThat(get_low_notification, Eventually(NotEquals(None)))
487+ notification = get_low_notification()
488+ self._assert_notification(
489+ notification,
490+ summary_low,
491+ body_low,
492+ True,
493+ False,
494+ 1.0
495+ )
496+
497+ def test_summary_and_body(self):
498+ """Notification must display the expected summary- and body-text."""
499+ self.launch_unity()
500+ greeter = self.main_window.get_greeter()
501+ greeter.swipe()
502+
503+ notify_list = self._get_notifications_list()
504+
505+ summary = 'Summary-Body'
506+ body = 'This is a superfluous notification'
507+
508+ notification = self._create_ephemeral_notification(summary, body)
509+ notification.show()
510+
511+ get_notification = lambda: notify_list.select_single('Notification')
512+ self.assertThat(get_notification, Eventually(NotEquals(None)))
513+ notification = get_notification()
514+ self._assert_notification(
515+ notification,
516+ summary,
517+ body,
518+ False,
519+ False,
520+ 1.0
521+ )
522+
523+ def test_summary_only(self):
524+ """Notification must display only the expected summary-text."""
525+ self.launch_unity()
526+ greeter = self.main_window.get_greeter()
527+ greeter.swipe()
528+
529+ notify_list = self._get_notifications_list()
530+
531+ summary = 'Summary-Only'
532+
533+ notification = self._create_ephemeral_notification(summary)
534+ notification.show()
535+
536+ get_notification = lambda: notify_list.select_single('Notification')
537+ self.assertThat(get_notification, Eventually(NotEquals(None)))
538+ notification = get_notification()
539+
540+ self._assert_notification(notification, summary, '', False, False, 1.0)
541+
542+ def test_update_notification_same_layout(self):
543+ """Notification must allow updating its contents while being displayed."""
544+ self.launch_unity()
545+ greeter = self.main_window.get_greeter()
546+ greeter.swipe()
547+
548+ notify_list = self._get_notifications_list()
549+
550+ summary = 'Initial notification'
551+ body = 'This is the original content of this notification-bubble.'
552+ icon_path = self._get_icon_path('avatars/funky.png')
553+
554+ notification = self._create_ephemeral_notification(
555+ summary,
556+ body,
557+ icon_path
558+ )
559+ notification.show()
560+
561+ get_notification = lambda: notify_list.select_single('Notification', summary=summary)
562+ self.assertThat(get_notification, Eventually(NotEquals(None)))
563+ self._assert_notification(
564+ get_notification(),
565+ summary,
566+ body,
567+ True,
568+ False,
569+ 1.0
570+ )
571+
572+ summary = 'Updated notification'
573+ body = 'Here the same bubble with new title- and body-text, even ' \
574+ 'the icon can be changed on the update.'
575+ icon_path = self._get_icon_path('avatars/amanda.png')
576+ notification.update(summary, body, icon_path)
577+ notification.show()
578+ self.assertThat(get_notification, Eventually(NotEquals(None)))
579+ self._assert_notification(get_notification(), summary, body)
580+
581+ def test_update_notification_layout_change(self):
582+ """Notification must allow updating its contents and layout while being displayed."""
583+ self.launch_unity()
584+ greeter = self.main_window.get_greeter()
585+ greeter.swipe()
586+
587+ notify_list = self._get_notifications_list()
588+
589+ summary = 'Initial layout'
590+ body = 'This bubble uses the icon-title-body layout with a ' \
591+ 'secondary icon.'
592+ icon_path = self._get_icon_path('avatars/anna_olsson.png')
593+ hint_icon = self._get_icon_path('applicationIcons/phone-app.png')
594+
595+ notification = self._create_ephemeral_notification(
596+ summary,
597+ body,
598+ icon_path
599+ )
600+ notification.set_hint_string(
601+ 'x-canonical-secondary-icon',
602+ hint_icon
603+ )
604+ notification.show()
605+
606+ get_notification = lambda: notify_list.select_single('Notification')
607+ self.assertThat(get_notification, Eventually(NotEquals(None)))
608+ self._assert_notification(
609+ get_notification(),
610+ summary,
611+ body,
612+ True,
613+ True,
614+ 1.0
615+ )
616+
617+ notification.clear_hints()
618+ summary = 'Updated layout'
619+ body = 'After the update we now have a bubble using the title-body ' \
620+ 'layout.'
621+ notification.update(summary, body, None)
622+ notification.show()
623+
624+ self.assertThat(get_notification, Eventually(NotEquals(None)))
625+ self._assert_notification(get_notification(), summary, body, False)
626+
627+ def test_append_hint(self):
628+ """Notification has to accumulate body-text using append-hint."""
629+ self.launch_unity()
630+ greeter = self.main_window.get_greeter()
631+ greeter.swipe()
632+
633+ notify_list = self._get_notifications_list()
634+
635+ summary = 'Cole Raby'
636+ body = 'Hey Bro Coly!'
637+ icon_path = self._get_icon_path('avatars/amanda.png')
638+ body_sum = body
639+ notification = self._create_ephemeral_notification(
640+ summary,
641+ body,
642+ icon_path,
643+ hints=[('x-canonical-append', 'true')]
644+ )
645+
646+ notification.show()
647+
648+ get_notification = lambda: notify_list.select_single('Notification')
649+ self.assertThat(get_notification, Eventually(NotEquals(None)))
650+ notification = get_notification()
651+ self._assert_notification(
652+ notification,
653+ summary,
654+ body_sum,
655+ True,
656+ False,
657+ 1.0
658+ )
659+
660+ bodies = [
661+ 'What\'s up dude?',
662+ 'Did you watch the air-race in Oshkosh last week?',
663+ 'Phil owned the place like no one before him!',
664+ 'Did really everything in the race work according to regulations?',
665+ 'Somehow I think to remember Burt Williams did cut corners and was not punished for this.',
666+ 'Hopefully the referees will watch the videos of the race.',
667+ 'Burt could get fined with US$ 50000 for that rule-violation :)'
668+ ]
669+
670+ for new_body in bodies:
671+ body = new_body
672+ body_sum += '\n' + body
673+ notification = self._create_ephemeral_notification(
674+ summary,
675+ body,
676+ icon_path,
677+ hints=[('x-canonical-append', 'true')]
678+ )
679+ notification.show()
680+ get_notification = lambda: notify_list.select_single(
681+ 'Notification'
682+ )
683+ self.assertThat(get_notification, Eventually(NotEquals(None)))
684+ notification = get_notification()
685+ self._assert_notification(
686+ notification,
687+ summary,
688+ body_sum,
689+ True,
690+ False,
691+ 1.0
692+ )
693+
694+ def _create_ephemeral_notification(
695+ self,
696+ summary="",
697+ body="",
698+ icon=None,
699+ hints=[],
700+ urgency="NORMAL"
701+ ):
702+ """Create an ephemeral (non-interactive) notification
703+
704+ :param summary: Summary text for the notification
705+ :param body: Body text to display in the notification
706+ :param icon: Path string to the icon to use
707+ :param hint_strings: List of tuples containing the 'name' and value
708+ for setting the hint strings for the notification
709+ :param urgency: Urgency string for the noticiation, either: 'LOW',
710+ 'NORMAL', 'CRITICAL'
711+
712+ """
713+ logger.info(
714+ "Creating ephemeral: summary(%s), body(%s), urgency(%r) "
715+ "and Icon(%s)",
716+ summary,
717+ body,
718+ urgency,
719+ icon
720+ )
721+
722+ n = Notify.Notification.new(summary, body, icon)
723+
724+ for hint in hints:
725+ key, value = hint
726+ n.set_hint_string(key, value)
727+ logger.info("Adding hint to notification: (%s, %s)", key, value)
728+ n.set_urgency(self._get_urgency(urgency))
729+
730+ return n
731+
732+ def _get_urgency(self, urgency):
733+ """Translates urgency string to enum."""
734+ _urgency_enums = {'LOW': Notify.Urgency.LOW,
735+ 'NORMAL': Notify.Urgency.NORMAL,
736+ 'CRITICAL': Notify.Urgency.CRITICAL}
737+ return _urgency_enums.get(urgency.upper())
tests/mocks/GSettings.1.0/fake_gsettings.h+1−1
@@ -18,7 +18,7 @@
1818#define FAKE_GSETTINGS_H
1919
2020#include <QList>
21−#include <QtQml>
21+#include <QObject>
2222
2323class GSettingsSchemaQml: public QObject
2424{
tests/mocks/GSettings.1.0/plugin.cpp+1−1
@@ -17,7 +17,7 @@
1717#include "plugin.h"
1818#include "fake_gsettings.h"
1919
20−#include <QtQml>
20+#include <QtQml/qqml.h>
2121
2222static QObject* controllerProvider(QQmlEngine* /* engine */, QJSEngine* /* scriptEngine */)
2323{
tests/mocks/Powerd/plugin.cpp+1−1
@@ -19,7 +19,7 @@
1919#include "plugin.h"
2020#include "Powerd.h"
2121
22−#include <QtQml/QtQml>
22+#include <QtQml/qqml.h>
2323
2424static QObject *powerd_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
2525{
tests/mocks/Ubuntu/DownloadDaemonListener/plugin.cpp−1
@@ -17,7 +17,6 @@
1717#include "plugin.h"
1818#include "MockDownloadTracker.h"
1919
20−#include <QtQml>
2120#include <QtQml/QQmlContext>
2221#include <qqml.h>
2322
tests/mocks/Unity/Indicators/fakeplugin.cpp+1−1
@@ -17,7 +17,7 @@
1717 */
1818
1919// Qt
20−#include <QtQml>
20+#include <QtQml/qqml.h>
2121
2222// self
2323#include "fakeplugin.h"
tests/mocks/Unity/Launcher/MockLauncherModel.cpp+6
@@ -174,3 +174,9 @@ int MockLauncherModel::findApp(const QString &appId)
174174 }
175175 return -1;
176176}
177+
178+void MockLauncherModel::setUser(const QString &username)
179+{
180+ Q_UNUSED(username)
181+ // TODO: implement this...
182+}
tests/mocks/Unity/Launcher/MockLauncherModel.h+1
@@ -43,6 +43,7 @@ public:
4343 Q_INVOKABLE void pin(const QString &appId, int index = -1);
4444 Q_INVOKABLE void requestRemove(const QString &appId);
4545 Q_INVOKABLE void quickListActionInvoked(const QString &appId, int actionIndex);
46+ Q_INVOKABLE void setUser(const QString &username);
4647
4748Q_SIGNALS:
4849 void quickListTriggered(const QString &appId, int index);
tests/mocks/Unity/fake_unity_plugin.cpp+2−1
@@ -26,7 +26,8 @@
2626
2727// External
2828#include <glib-object.h>
29−#include <QtQml>
29+
30+#include <QtQml/qqml.h>
3031
3132void FakeUnityPlugin::registerTypes(const char *uri)
3233{
tests/qmltests/Launcher/tst_Launcher.qml+38−14
@@ -277,19 +277,24 @@ Item {
277277
278278 function test_dragndrop_data() {
279279 return [
280− {tag: "startDrag", fullDrag: false},
281− {tag: "fullDrag", fullDrag: true},
280+ {tag: "startDrag", fullDrag: false },
281+ {tag: "fullDrag horizontal", fullDrag: true, orientation: ListView.Horizontal },
282+ {tag: "fullDrag vertical", fullDrag: true, orientation: ListView.Vertical },
282283 ];
283284 }
284285
285286 function test_dragndrop(data) {
286287 revealer.dragLauncherIntoView();
287− var draggedItem = findChild(launcher, "launcherDelegate5")
288+ var draggedItem = findChild(launcher, "launcherDelegate4")
288289 var item0 = findChild(launcher, "launcherDelegate0")
289290 var fakeDragItem = findChild(launcher, "fakeDragItem")
290291 var initialItemHeight = draggedItem.height
291− var item5Name = LauncherModel.get(5).iconName
292− var item4Name = LauncherModel.get(4).iconName
292+ var item4 = LauncherModel.get(4).appId
293+ var item3 = LauncherModel.get(3).appId
294+
295+ var listView = findChild(launcher, "launcherListView");
296+ listView.flick(0, units.gu(200));
297+ tryCompare(listView, "flicking", false);
293298
294299 // Initial state
295300 compare(draggedItem.itemOpacity, 1, "Item's opacity is not 1 at beginning")
@@ -297,28 +302,43 @@ Item {
297302 tryCompare(findChild(draggedItem, "dropIndicator"), "opacity", 0)
298303
299304 // Doing longpress
300− mousePress(draggedItem, draggedItem.width / 2, draggedItem.height / 2)
305+ var currentMouseX = draggedItem.width / 2
306+ var currentMouseY = draggedItem.height / 2
307+ mousePress(draggedItem, currentMouseX, currentMouseY)
301308 // DraggedItem needs to hide and fakeDragItem become visible
302309 tryCompare(draggedItem, "itemOpacity", 0)
303310 tryCompare(fakeDragItem, "visible", true)
304311
305312 // Dragging a bit (> 1.5 gu)
306− mouseMove(draggedItem, -units.gu(2), draggedItem.height / 2)
313+ currentMouseX -= units.gu(2)
314+ mouseMove(draggedItem, currentMouseX, currentMouseY)
307315 // Other items need to expand and become 0.6 opaque
308316 tryCompare(item0, "angle", 0)
309317 tryCompare(item0, "itemOpacity", 0.6)
310318
311319 if (data.fullDrag) {
312320 // Dragging a bit more
313− mouseMove(draggedItem, -units.gu(15), draggedItem.height / 2, 100)
314− tryCompare(findChild(draggedItem, "dropIndicator"), "opacity", 1)
315− tryCompare(draggedItem, "height", units.gu(1))
321+ if (data.orientation == ListView.Horizontal) {
322+ currentMouseX -= units.gu(15)
323+ mouseMove(draggedItem, currentMouseX, currentMouseY, 100)
324+
325+ tryCompare(findChild(draggedItem, "dropIndicator"), "opacity", 1)
326+ tryCompare(draggedItem, "height", units.gu(1))
327+
328+ // Dragging downwards. Item needs to move in the model
329+ currentMouseY -= initialItemHeight * 1.5
330+ mouseMove(draggedItem, currentMouseX, currentMouseY)
331+ } else if (data.orientation == ListView.Vertical) {
332+ currentMouseY -= initialItemHeight * 1.5
333+ mouseMove(draggedItem, currentMouseX, currentMouseY, 100)
334+
335+ tryCompare(findChild(draggedItem, "dropIndicator"), "opacity", 1)
336+ tryCompare(draggedItem, "height", units.gu(1))
337+ }
316338
317− // Dragging downwards. Item needs to move in the model
318− mouseMove(draggedItem, -units.gu(15), -initialItemHeight)
319339 waitForRendering(draggedItem)
320− compare(LauncherModel.get(5).iconName, item4Name)
321− compare(LauncherModel.get(5).iconName, item5Name)
340+ compare(LauncherModel.get(4).appId, item3)
341+ compare(LauncherModel.get(3).appId, item4)
322342 }
323343
324344 // Releasing and checking if initial values are restored
@@ -326,6 +346,10 @@ Item {
326346 tryCompare(findChild(draggedItem, "dropIndicator"), "opacity", 0)
327347 tryCompare(draggedItem, "itemOpacity", 1)
328348 tryCompare(fakeDragItem, "visible", false)
349+
350+ // Click somewhere in the empty space to make it hide in case it isn't
351+ mouseClick(launcher, launcher.width - units.gu(1), units.gu(1));
352+ revealer.waitUntilLauncherDisappears();
329353 }
330354
331355 function test_quicklist_dismiss() {
tests/utils/modules/Unity/Test/TouchEventSequenceWrapper.h+1−1
@@ -19,7 +19,7 @@
1919
2020#include <QtCore/QObject>
2121#include <QtTest/QTest>
22−#include <QtQml/QtQml>
22+#include <QtQml/qqml.h>
2323
2424class TouchEventSequenceWrapper : public QObject
2525{
tests/utils/modules/Unity/Test/UnityTestCase.qml+21−2
@@ -69,9 +69,28 @@ TestCase {
6969 }
7070
7171
72− // Find an object with the given name recursively, starting
73− // at the given object "obj"
72+ // Find an object with the given name in the children tree of "obj"
7473 function findChild(obj,objectName) {
74+ var childs = new Array(0);
75+ childs.push(obj)
76+ while (childs.length > 0) {
77+ if (childs[0].objectName == objectName) {
78+ return childs[0]
79+ }
80+ for (var i in childs[0].children) {
81+ childs.push(childs[0].children[i])
82+ }
83+ childs.splice(0, 1);
84+ }
85+ return undefined;
86+ }
87+
88+ // Find an object with the given name in the children tree of "obj"
89+ // Including invisible children like animations, timers etc.
90+ // Note: you should use findChild if you're not sure you need this
91+ // as this tree is much bigger and might contain stuff that goes
92+ // away randomly.
93+ function findInvisibleChild(obj,objectName) {
7594 var childs = new Array(0);
7695 childs.push(obj)
7796 while (childs.length > 0) {