on xenial help sign in
Merge trunk and fix conflicts
0106125Andrea Cimitan
ApplicationArguments.hnew file+49
@@ -0,0 +1,49 @@
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+ * Authored by: Nick Dedekind <nick.dedekind@canonical.com>
17+ */
18+
19+
20+#ifndef APPLICATION_ARGUMENTS_H
21+#define APPLICATION_ARGUMENTS_H
22+
23+#include <QObject>
24+#include <QSize>
25+#include <QStringList>
26+
27+class ApplicationArguments : public QObject
28+{
29+ Q_OBJECT
30+public:
31+ ApplicationArguments(const QStringList& args) {
32+ if (args.contains(QLatin1String("-geometry")) && args.size() > args.indexOf(QLatin1String("-geometry")) + 1) {
33+ QStringList geometryArg = args.at(args.indexOf(QLatin1String("-geometry")) + 1).split('x');
34+ if (geometryArg.size() == 2) {
35+ m_size.rwidth() = geometryArg.at(0).toInt();
36+ m_size.rheight() = geometryArg.at(1).toInt();
37+ }
38+ }
39+ }
40+
41+ Q_INVOKABLE bool hasGeometry() const { return m_size.isValid(); }
42+ Q_INVOKABLE int width() const { return m_size.width(); }
43+ Q_INVOKABLE int height() const { return m_size.height(); }
44+
45+private:
46+ QSize m_size;
47+};
48+
49+#endif // APPLICATION_ARGUMENTS_H
CMakeLists.txt+1
@@ -101,6 +101,7 @@ set(shell_app_HDRS
101
101
102add_executable(${SHELL_APP}
102add_executable(${SHELL_APP}
103 ${shellapplication_MOC_SRCS}
103 ${shellapplication_MOC_SRCS}
104+ ApplicationArguments.h
104 main.cpp
105 main.cpp
105 MouseTouchAdaptor.cpp
106 MouseTouchAdaptor.cpp
106 )
107 )
Dash/Dash.qml−3
@@ -27,9 +27,6 @@ Showable {
27 property string showScopeOnLoaded: "home.scope"
27 property string showScopeOnLoaded: "home.scope"
28 property real contentScale: 1.0
28 property real contentScale: 1.0
29
29
30− width: units.gu(40)
31− height: units.gu(71)
32−
33 function setCurrentScope(scopeId, animate, reset) {
30 function setCurrentScope(scopeId, animate, reset) {
34 var scopeIndex = filteredScopes.findFirst(Scopes.RoleId, scopeId)
31 var scopeIndex = filteredScopes.findFirst(Scopes.RoleId, scopeId)
35
32
Dash/DashApps.qml+1−1
@@ -88,7 +88,7 @@ ScopeView {
88 model: scopeView.scope.categories
88 model: scopeView.scope.categories
89
89
90 delegate: Item {
90 delegate: Item {
91− Component.onCompleted: if (index == 3) scopeView.installedModel = Qt.binding(function() { return model.results; });
91+ Component.onCompleted: if (id == "installed") scopeView.installedModel = Qt.binding(function() { return model.results; });
92 }
92 }
93 }
93 }
94
94
Dash/DashContent.qml+5−2
@@ -20,8 +20,6 @@ import Unity 0.1
20
20
21Item {
21Item {
22 id: dashContent
22 id: dashContent
23− width: units.gu(40)
24− height: units.gu(71)
25
23
26 property var model: null
24 property var model: null
27 property var scopes: null
25 property var scopes: null
@@ -117,6 +115,11 @@ Item {
117 asynchronous: true
115 asynchronous: true
118 source: scopeMapper.map(scope.id)
116 source: scopeMapper.map(scope.id)
119
117
118+ // these are needed for autopilot tests
119+ readonly property string scopeId: scope.id
120+ readonly property bool isCurrent: ListView.isCurrentItem
121+ readonly property bool isLoaded: status == Loader.Ready
122+
120 onLoaded: {
123 onLoaded: {
121 item.scope = Qt.binding(function() { return scope })
124 item.scope = Qt.binding(function() { return scope })
122 item.isCurrent = Qt.binding(function() { return ListView.isCurrentItem })
125 item.isCurrent = Qt.binding(function() { return ListView.isCurrentItem })
Dash/GenericScopeView.qml+9
@@ -114,7 +114,16 @@ ScopeView {
114 }
114 }
115 }
115 }
116
116
117+ function getDefaultRendererId(contentType) {
118+ switch (contentType) {
119+ default: return "grid";
120+ }
121+ }
122+
117 function getRenderer(rendererId, contentType) {
123 function getRenderer(rendererId, contentType) {
124+ if (rendererId == "default") {
125+ rendererId = getDefaultRendererId(contentType);
126+ }
118 switch (rendererId) {
127 switch (rendererId) {
119 case "grid": {
128 case "grid": {
120 switch (contentType) {
129 switch (contentType) {
Notifications/Notification.qml+4−3
@@ -28,6 +28,7 @@ UbuntuShape {
28 property var actions
28 property var actions
29 property var notificationId
29 property var notificationId
30 property var type
30 property var type
31+ property var hints
31 property var notification
32 property var notification
32
33
33 objectName: "background"
34 objectName: "background"
@@ -164,7 +165,7 @@ UbuntuShape {
164 bottom: parent.bottom
165 bottom: parent.bottom
165 }
166 }
166 text: notification.type == Notification.SnapDecision && actionRepeater.count >= 2 ? actionRepeater.itemAt(1).actionLabel : ""
167 text: notification.type == Notification.SnapDecision && actionRepeater.count >= 2 ? actionRepeater.itemAt(1).actionLabel : ""
167− color: "#cdcdcb"
168+ gradient: UbuntuColors.greyGradient
168 onClicked: {
169 onClicked: {
169 if (actionRepeater.count > 2) {
170 if (actionRepeater.count > 2) {
170 buttonRow.expanded = !buttonRow.expanded
171 buttonRow.expanded = !buttonRow.expanded
@@ -194,7 +195,7 @@ UbuntuShape {
194 top: parent.top
195 top: parent.top
195 bottom: parent.bottom
196 bottom: parent.bottom
196 }
197 }
197− color: "#d85317" // FIXME ??
198+ gradient: notification.hints == Notification.ButtonTint ? UbuntuColors.orangeGradient : UbuntuColors.greyGradient
198 visible: width > 0
199 visible: width > 0
199 onClicked: notification.notification.invokeAction(actionRepeater.itemAt(0).actionId)
200 onClicked: notification.notification.invokeAction(actionRepeater.itemAt(0).actionId)
200 }
201 }
@@ -241,7 +242,7 @@ UbuntuShape {
241
242
242 text: loader.actionLabel
243 text: loader.actionLabel
243 height: units.gu(4)
244 height: units.gu(4)
244− color: "#cdcdcb" // FIXME ?? which color?
245+ gradient: UbuntuColors.greyGradient
245 onClicked: notification.notification.invokeAction(loader.actionId)
246 onClicked: notification.notification.invokeAction(loader.actionId)
246 }
247 }
247 }
248 }
Notifications/Notifications.qml+1
@@ -32,6 +32,7 @@ ListView {
32 right: parent.right
32 right: parent.right
33 }
33 }
34 type: model.type
34 type: model.type
35+ hints: model.hints
35 iconSource: model.icon
36 iconSource: model.icon
36 secondaryIconSource: model.secondaryIcon
37 secondaryIconSource: model.secondaryIcon
37 summary: model.summary
38 summary: model.summary
Panel/IndicatorItem.qml+5
@@ -17,6 +17,7 @@
17import QtQuick 2.0
17import QtQuick 2.0
18import Ubuntu.Components 0.1
18import Ubuntu.Components 0.1
19import Unity.Indicators 0.1 as Indicators
19import Unity.Indicators 0.1 as Indicators
20+import "../Components"
20
21
21Item {
22Item {
22 id: indicatorItem
23 id: indicatorItem
@@ -27,6 +28,7 @@ Item {
27 property var indicatorProperties: undefined
28 property var indicatorProperties: undefined
28
29
29 opacity: dimmed ? 0.4 : 1
30 opacity: dimmed ? 0.4 : 1
31+ Behavior on opacity { StandardAnimation {} }
30
32
31 // only visible when non-empty
33 // only visible when non-empty
32 visible: loader.item != undefined && loader.status == Loader.Ready ? loader.item.enabled : false
34 visible: loader.item != undefined && loader.status == Loader.Ready ? loader.item.enabled : false
@@ -53,5 +55,8 @@ Item {
53 width: parent.width
55 width: parent.width
54 anchors.top: parent.bottom
56 anchors.top: parent.bottom
55 visible: highlighted
57 visible: highlighted
58+
59+ opacity: highlighted ? 1 : 0
60+ Behavior on opacity { StandardAnimation {} }
56 }
61 }
57}
62}
Panel/IndicatorRow.qml+34−48
@@ -16,6 +16,7 @@
16
16
17import QtQuick 2.0
17import QtQuick 2.0
18import Ubuntu.Components 0.1
18import Ubuntu.Components 0.1
19+import "../Components"
19
20
20Item {
21Item {
21 id: indicatorRow
22 id: indicatorRow
@@ -25,21 +26,18 @@ Item {
25 property alias row: row
26 property alias row: row
26 property QtObject indicatorsModel: null
27 property QtObject indicatorsModel: null
27
28
28− Behavior on y {NumberAnimation {duration: 300; easing.type: Easing.OutCubic} }
29−
30 width: units.gu(40)
29 width: units.gu(40)
31 height: units.gu(3)
30 height: units.gu(3)
32
31
33− Component.onCompleted: setDefaultItem()
34−
35 function setDefaultItem() {
32 function setDefaultItem() {
36 // The leftmost indicator
33 // The leftmost indicator
37− var defaultItemIndex = 0
34+ setCurrentItem(0);
38− setItem(defaultItemIndex)
39 }
35 }
40
36
41− function setItem(index) {
37+ function setCurrentItem(index) {
38+ if (currentItemIndex !== index) {
42− currentItem = rowRepeater.itemAt(index)
39+ currentItem = rowRepeater.itemAt(index);
40+ }
43 }
41 }
44
42
45 Row {
43 Row {
@@ -54,51 +52,39 @@ Item {
54 objectName: "rowRepeater"
52 objectName: "rowRepeater"
55 model: indicatorsModel ? indicatorsModel : undefined
53 model: indicatorsModel ? indicatorsModel : undefined
56
54
57− IndicatorItem {
55+ Item {
58− id: indicatorItem
56+ id: itemWrapper
57+ height: indicatorRow.height
58+ width: childrenRect.width
59
59
60− property int ownIndex: index
60+ property int ownIndex: index
61+ property alias highlighted: indicatorItem.highlighted
62+ property alias dimmed: indicatorItem.dimmed
61
63
64+ IndicatorItem {
65+ id: indicatorItem
66+ height: parent.height
67+
68+ highlighted: indicatorRow.state != "initial" ? itemWrapper.ownIndex == indicatorRow.currentItemIndex : false
69+ dimmed: indicatorRow.state != "initial" ? itemWrapper.ownIndex != indicatorRow.currentItemIndex : false
70+
62− widgetSource: model.widgetSource
71+ widgetSource: model.widgetSource
72+ indicatorProperties : model.indicatorProperties
73+ }
63
74
64− indicatorProperties : model.indicatorProperties
75+ opacity: {
65− highlighted: indicatorRow.state == "reveal" || indicatorRow.state == "locked" || indicatorRow.state == "commit" ? ownIndex == indicatorRow.currentItemIndex : false
66− dimmed: { //See FIXME in Indicators regarding the "states" change
67− if (indicatorRow.state == "initial" || indicatorRow.state == "") {
68− return false;
69− } else if (indicatorRow.state == "hint") {
70− return true
71− } else {
72− return ownIndex != indicatorRow.currentItemIndex
73− }
74− }
75− height: indicatorRow.height
76− y: {
77− //FIXME: all indicators will be initial for now.
78− if (!highlighted && (indicatorRow.state == "locked" || indicatorRow.state == "commit")) {
76+ if (!indicatorItem.highlighted && (indicatorRow.state == "locked" || indicatorRow.state == "commit")) {
79− return -indicatorRow.height
77+ return 0.0;
80− } else {
78+ } else {
81− return 0
82− }
83− }
84− Behavior on y {
85− NumberAnimation{
86− duration: {
87− if (index == 0) {
88− return 250
79+ return 1.0;
89− } else if (index == 1) {
90− return 150
91− } else if (index == 2) {
92− return 550
93− } else if (index == 3) {
94− return 400
95− } else {
96− return 100 + Math.random() * 200
97− }
98− }
99− easing.type: Easing.OutCubic
100 }
80 }
101 }
81 }
82+ Behavior on opacity {
83+ StandardAnimation {
84+ // flow away from current index
85+ duration: (rowRepeater.count - Math.abs(indicatorRow.currentItemIndex - index)) * (500/rowRepeater.count)
86+ }
87+ }
102 }
88 }
103 }
89 }
104 }
90 }
Panel/Indicators.qml+89−101
@@ -25,16 +25,15 @@ import "../Components/Math.js" as MathLocal
25Showable {
25Showable {
26 id: indicators
26 id: indicators
27
27
28− property int referenceOpenedHeight: units.gu(71)
28+ property real openedHeight: units.gu(71)
29− property real openedHeight: pinnedMode ? referenceOpenedHeight - panelHeight
30− : referenceOpenedHeight
31 property int panelHeight: units.gu(3)
29 property int panelHeight: units.gu(3)
32 property bool pinnedMode: true //should be set true if indicators menu can cover whole screen
30 property bool pinnedMode: true //should be set true if indicators menu can cover whole screen
33
31
34 property int hintValue
32 property int hintValue
35− readonly property int lockThreshold: referenceOpenedHeight / 2
33+ readonly property int lockThreshold: openedHeight / 2
36 property bool fullyOpened: height == openedHeight
34 property bool fullyOpened: height == openedHeight
37 property bool partiallyOpened: height > panelHeight && !fullyOpened
35 property bool partiallyOpened: height > panelHeight && !fullyOpened
36+ property real visualBottom: Math.max(y+height, y+indicatorRow.y+indicatorRow.height)
38
37
39 // TODO: Perhaps we need a animation standard for showing/hiding? Each showable seems to
38 // TODO: Perhaps we need a animation standard for showing/hiding? Each showable seems to
40 // use its own values. Need to ask design about this.
39 // use its own values. Need to ask design about this.
@@ -43,6 +42,13 @@ Showable {
43 duration: 350
42 duration: 350
44 to: openedHeight
43 to: openedHeight
45 easing.type: Easing.OutCubic
44 easing.type: Easing.OutCubic
45+
46+ // Re-size if we've changed the openHeight while shown.
47+ onToChanged: {
48+ if (indicators.shown) {
49+ indicators.show();
50+ }
51+ }
46 }
52 }
47
53
48 hideAnimation: StandardAnimation {
54 hideAnimation: StandardAnimation {
@@ -56,28 +62,30 @@ Showable {
56
62
57 onHeightChanged: {
63 onHeightChanged: {
58 // need to use handle.get_height(). As the handle height depends on indicators.height changes (but this is called first!)
64 // need to use handle.get_height(). As the handle height depends on indicators.height changes (but this is called first!)
59− var contentProgress = indicators.height - handle.get_height();
65+ var revealProgress = indicators.height - panelHeight
60 if (!showAnimation.running && !hideAnimation.running) {
66 if (!showAnimation.running && !hideAnimation.running) {
67+ if (revealProgress == 0) {
61− if (contentProgress <= hintValue && indicators.state == "reveal") {
68+ indicators.state = "initial";
69+ } else if (revealProgress <= hintValue) {
62 indicators.state = "hint";
70 indicators.state = "hint";
63− } else if (contentProgress > hintValue && contentProgress < lockThreshold) {
71+ } else if (revealProgress > hintValue && revealProgress < lockThreshold) {
64 indicators.state = "reveal";
72 indicators.state = "reveal";
65− } else if (contentProgress >= lockThreshold && lockThreshold > 0) {
73+ } else if (revealProgress >= lockThreshold && lockThreshold > 0) {
66 indicators.state = "locked";
74 indicators.state = "locked";
67 }
75 }
68 }
76 }
69
77
70− if (contentProgress == 0) {
78+ if (revealProgress == 0) {
71 menuContent.releaseContent();
79 menuContent.releaseContent();
72 }
80 }
73 }
81 }
74
82
75 function calculateCurrentItem(xValue, useBuffer) {
83 function calculateCurrentItem(xValue, useBuffer) {
76− var rowCoordinates
84+ var rowCoordinates;
77− var itemCoordinates
85+ var itemCoordinates;
78− var currentItem
86+ var currentItem;
79− var distanceFromRightEdge
87+ var distanceFromRightEdge;
80− var bufferExceeded = false
88+ var bufferExceeded = false;
81
89
82 if (indicators.state == "commit" || indicators.state == "locked" || showAnimation.running || hideAnimation.running) return;
90 if (indicators.state == "commit" || indicators.state == "locked" || showAnimation.running || hideAnimation.running) return;
83
91
@@ -90,6 +98,14 @@ Showable {
90 MathLocal.clamp((indicators.height - handle.height - hintValue) /
98 MathLocal.clamp((indicators.height - handle.height - hintValue) /
91 (lockThreshold - hintValue), 0, 1);
99 (lockThreshold - hintValue), 0, 1);
92
100
101+ /*
102+ Vertical velocity check. Don't change the indicator if we're moving too quickly.
103+ */
104+ var verticalSpeed = Math.abs(yVelocityCalculator.calculate());
105+ if (verticalSpeed >= 0.05 && indicatorRow.currentItem!=null) {
106+ return;
107+ }
108+
93 /*
109 /*
94 Percentage of an indicator icon's width the user's press can stray horizontally from the
110 Percentage of an indicator icon's width the user's press can stray horizontally from the
95 focused icon before we change focus to another icon. E.g. a value of 0.5 means you must
111 focused icon before we change focus to another icon. E.g. a value of 0.5 means you must
@@ -110,7 +126,7 @@ Showable {
110 currentItem = indicatorRow.row.childAt(rowCoordinates.x, 0);
126 currentItem = indicatorRow.row.childAt(rowCoordinates.x, 0);
111 if (currentItem && currentItem != indicatorRow.currentItem ) {
127 if (currentItem && currentItem != indicatorRow.currentItem ) {
112 itemCoordinates = indicatorRow.row.mapToItem(currentItem, rowCoordinates.x, 0);
128 itemCoordinates = indicatorRow.row.mapToItem(currentItem, rowCoordinates.x, 0);
113− distanceFromRightEdge = (currentItem.width - itemCoordinates.x) / (currentItem.width)
129+ distanceFromRightEdge = (currentItem.width - itemCoordinates.x) / (currentItem.width);
114 if (Math.abs(currentItem.ownIndex - indicatorRow.currentItemIndex) > 1) {
130 if (Math.abs(currentItem.ownIndex - indicatorRow.currentItemIndex) > 1) {
115 bufferExceeded = true;
131 bufferExceeded = true;
116 } else {
132 } else {
@@ -123,6 +139,8 @@ Showable {
123 if ((!useBuffer || (useBuffer && bufferExceeded)) || indicatorRow.currentItem < 0 || indicatorRow.currentItem == null) {
139 if ((!useBuffer || (useBuffer && bufferExceeded)) || indicatorRow.currentItem < 0 || indicatorRow.currentItem == null) {
124 indicatorRow.currentItem = currentItem;
140 indicatorRow.currentItem = currentItem;
125 }
141 }
142+ } else if (!currentItem) {
143+ indicatorRow.setDefaultItem();
126 }
144 }
127 }
145 }
128
146
@@ -155,13 +173,20 @@ Showable {
155 left: parent.left
173 left: parent.left
156 right: parent.right
174 right: parent.right
157 top: indicatorRow.bottom
175 top: indicatorRow.bottom
158− bottom: parent.bottom
176+ bottom: handle.top
159 }
177 }
160 indicatorsModel: indicatorsModel
178 indicatorsModel: indicatorsModel
161 clip: !indicators.fullyOpened
179 clip: !indicators.fullyOpened
162
180
163− onMenuSelected: {
181+ Connections {
164− indicatorRow.setItem(index)
182+ target: indicatorRow
183+ onCurrentItemIndexChanged: menuContent.updateCurrentMenu();
184+ }
185+
186+ function updateCurrentMenu() {
187+ if (currentMenuIndex != indicatorRow.currentItemIndex) {
188+ currentMenuIndex = indicatorRow.currentItemIndex;
189+ }
165 }
190 }
166
191
167 //small shadow gradient at bottom of menu
192 //small shadow gradient at bottom of menu
@@ -188,7 +213,7 @@ Showable {
188 anchors {
213 anchors {
189 left: parent.left
214 left: parent.left
190 right: parent.right
215 right: parent.right
191− top: menuContent.bottom
216+ bottom: parent.bottom
192 }
217 }
193 height: get_height()
218 height: get_height()
194 clip: height < handleImage.height
219 clip: height < handleImage.height
@@ -212,33 +237,14 @@ Showable {
212 }
237 }
213 }
238 }
214
239
215− PanelSeparatorLine {
216− id: indicatorsSeparatorLine
217− visible: true
218− anchors {
219− top: handle.bottom
220− left: indicators.left
221− right: indicators.right
222− }
223− }
224−
225− BorderImage {
226− id: dropShadow
227− anchors {
228− top: indicators.top
229− bottom: indicatorsSeparatorLine.bottom
230− left: indicators.left
231− right: indicators.right
232− margins: -units.gu(1)
233− }
234− visible: indicators.height > panelHeight
235− source: "graphics/rectangular_dropshadow.sci"
236− }
237−
238 PanelBackground {
240 PanelBackground {
239 anchors.fill: indicatorRow
241 anchors.fill: indicatorRow
240 }
242 }
241
243
244+ IndicatorsDataModel {
245+ id: indicatorsModel
246+ }
247+
242 IndicatorRow {
248 IndicatorRow {
243 id: indicatorRow
249 id: indicatorRow
244 objectName: "indicatorRow"
250 objectName: "indicatorRow"
@@ -250,57 +256,45 @@ Showable {
250 indicatorsModel: indicatorsModel
256 indicatorsModel: indicatorsModel
251 state: indicators.state
257 state: indicators.state
252
258
253− onCurrentItemIndexChanged: menuContent.currentIndex = currentItemIndex
254−
255− }
256−
257− IndicatorsDataModel {
258− id: indicatorsModel
259− }
260−
261− Connections {
259+ Connections {
262− target: hideAnimation
260+ target: menuContent
263− onRunningChanged: {
261+ onCurrentMenuIndexChanged: {
264− if (hideAnimation.running) {
262+ indicatorRow.setCurrentItem(menuContent.currentMenuIndex);
265− indicators.state = "initial"
266− } else {
267− if (state == "initial") {
268− indicatorRow.setDefaultItem()
269− }
270 }
263 }
271 }
264 }
272 }
265 }
266+
273 Connections {
267 Connections {
274 target: showAnimation
268 target: showAnimation
275 onRunningChanged: {
269 onRunningChanged: {
276 if (showAnimation.running) {
270 if (showAnimation.running) {
277− indicators.calculateCurrentItem(dragHandle.touchX, false);
278 indicators.state = "commit";
271 indicators.state = "commit";
279 }
272 }
280 }
273 }
281 }
274 }
282
275
283 Connections {
276 Connections {
284− target: dragHandle
277+ target: hideAnimation
285− onTouchXChanged: {
278+ onRunningChanged: {
286− var buffer = dragHandle.dragging ? true : false;
279+ if (hideAnimation.running) {
287− indicators.calculateCurrentItem(dragHandle.touchX, buffer);
280+ indicators.state = "initial";
281+ indicatorRow.currentItem = null;
282+ }
288 }
283 }
289 }
284 }
290
285
291− // We start with pinned states. (default pinnedMode: true)
286+ property var activeDragHandle: showDragHandle.dragging ? showDragHandle : hideDragHandle.dragging ? hideDragHandle : null
292− states: pinnedModeStates
287+ // connections to the active drag handle
293− // because of dynamic assignment of states, we need to assign state after completion.
288+ Connections {
294− Component.onCompleted: state = "initial"
289+ target: activeDragHandle
295−
290+ onTouchXChanged: {
296− // changing states will reset state to "".
291+ indicators.calculateCurrentItem(activeDragHandle.touchX, true);
297− onPinnedModeChanged: {
292+ }
298− var last_state = state;
293+ onTouchSceneYChanged: {
299− states = (pinnedMode) ? pinnedModeStates : offScreenModeStates;
294+ yVelocityCalculator.trackedPosition = activeDragHandle.touchSceneY;
300− state = last_state;
295+ }
301 }
296 }
302
297
303− property var dragHandle: showDragHandle.dragging ? showDragHandle : hideDragHandle
304 DragHandle {
298 DragHandle {
305 id: showDragHandle
299 id: showDragHandle
306 anchors.bottom: parent.bottom
300 anchors.bottom: parent.bottom
@@ -314,7 +308,7 @@ Showable {
314 hintDisplacement: pinnedMode ? indicators.hintValue : 0
308 hintDisplacement: pinnedMode ? indicators.hintValue : 0
315 autoCompleteDragThreshold: maxTotalDragDistance / 2
309 autoCompleteDragThreshold: maxTotalDragDistance / 2
316 stretch: true
310 stretch: true
317− maxTotalDragDistance: openedHeight - handle.height
311+ maxTotalDragDistance: openedHeight - panelHeight
318 distanceThreshold: pinnedMode ? 0 : units.gu(3)
312 distanceThreshold: pinnedMode ? 0 : units.gu(3)
319
313
320 onStatusChanged: {
314 onStatusChanged: {
@@ -326,28 +320,40 @@ Showable {
326 DragHandle {
320 DragHandle {
327 id: hideDragHandle
321 id: hideDragHandle
328 anchors.fill: handle
322 anchors.fill: handle
329− height: panelHeight
330 direction: Direction.Upwards
323 direction: Direction.Upwards
331 enabled: indicators.shown
324 enabled: indicators.shown
332− hintDisplacement: units.gu(2)
325+ hintDisplacement: indicators.hintValue
333 autoCompleteDragThreshold: maxTotalDragDistance / 6
326 autoCompleteDragThreshold: maxTotalDragDistance / 6
334 stretch: true
327 stretch: true
335− maxTotalDragDistance: referenceOpenedHeight - 2*panelHeight
328+ maxTotalDragDistance: openedHeight - panelHeight
336 distanceThreshold: 0
329 distanceThreshold: 0
337 }
330 }
338
331
339− property list<State> offScreenModeStates: [
332+ AxisVelocityCalculator {
333+ id: yVelocityCalculator
334+ }
335+
336+ states: [
340 State {
337 State {
341 name: "initial"
338 name: "initial"
342 },
339 },
343 State {
340 State {
344 name: "hint"
341 name: "hint"
342+ PropertyChanges {
345− PropertyChanges { target: indicatorRow; y: panelHeight }
343+ target: indicatorRow;
344+ y: pinnedMode ? 0 : panelHeight
345+ }
346+ StateChangeScript {
347+ script: {
348+ if (activeDragHandle) {
349+ calculateCurrentItem(activeDragHandle.touchX, false);
350+ }
351+ }
352+ }
346 },
353 },
347 State {
354 State {
348 name: "reveal"
355 name: "reveal"
349 extend: "hint"
356 extend: "hint"
350− StateChangeScript { script: calculateCurrentItem(dragHandle.touchX, false); }
351 },
357 },
352 State {
358 State {
353 name: "locked"
359 name: "locked"
@@ -358,25 +364,7 @@ Showable {
358 extend: "hint"
364 extend: "hint"
359 }
365 }
360 ]
366 ]
361−
362− property list<State> pinnedModeStates: [
363− State {
364− name: "initial"
367+ state: "initial"
365− },
366− State {
367− name: "hint"
368− },
369− State {
370− name: "reveal"
371− StateChangeScript { script: calculateCurrentItem(dragHandle.touchX, false); }
372− },
373− State {
374− name: "locked"
375− },
376− State {
377− name: "commit"
378− }
379− ]
380
368
381 transitions: [
369 transitions: [
382 Transition {
370 Transition {
Panel/MenuContent.qml+13−3
@@ -24,7 +24,7 @@ Rectangle {
24
24
25 property QtObject indicatorsModel: null
25 property QtObject indicatorsModel: null
26 property bool __contentActive: false
26 property bool __contentActive: false
27− property alias currentIndex : menus.currentIndex
27+ property alias currentMenuIndex : menus.currentIndex
28 property color backgroundColor: "#221e1c" // FIXME not in palette yet
28 property color backgroundColor: "#221e1c" // FIXME not in palette yet
29 property int contentReleaseInterval: 20000
29 property int contentReleaseInterval: 20000
30
30
@@ -63,7 +63,7 @@ Rectangle {
63
63
64 delegate: Loader {
64 delegate: Loader {
65 clip: true
65 clip: true
66− property bool contentActive: content.__contentActive
66+ property bool contentActive: content.__contentActive && menuActivator.content[index].active
67
67
68 onContentActiveChanged: {
68 onContentActiveChanged: {
69 if (contentActive && item) {
69 if (contentActive && item) {
@@ -137,6 +137,16 @@ Rectangle {
137 id: contentReleaseTimer
137 id: contentReleaseTimer
138
138
139 interval: contentReleaseInterval
139 interval: contentReleaseInterval
140+ onTriggered: {
140− onTriggered: __contentActive = false
141+ __contentActive = false;
142+ menuActivator.clear();
143+ }
144+ }
145+
146+ Indicators.MenuContentActivator {
147+ id: menuActivator
148+ running: content.__contentActive
149+ baseIndex: content.currentMenuIndex
150+ count: indicatorsModel.count
141 }
151 }
142}
152}
Panel/Panel.qml+28−9
@@ -39,11 +39,8 @@ Item {
39 }
39 }
40 height: __panelMinusSeparatorLineHeight
40 height: __panelMinusSeparatorLineHeight
41 y: 0
41 y: 0
42− onYChanged: indicatorsMenu.y = y;
43
42
44− Behavior on y {
43+ Behavior on y { StandardAnimation {} }
45− NumberAnimation { duration: 200 }
46− }
47 }
44 }
48
45
49 PanelSeparatorLine {
46 PanelSeparatorLine {
@@ -78,18 +75,40 @@ Item {
78 id: indicatorsMenu
75 id: indicatorsMenu
79
76
80 anchors.right: parent.right
77 anchors.right: parent.right
81− y: 0
78+ y: panelBackground.y
82 width: root.indicatorsMenuWidth
79 width: root.indicatorsMenuWidth
83 shown: false
80 shown: false
84 hintValue: __panelMinusSeparatorLineHeight * 3
81 hintValue: __panelMinusSeparatorLineHeight * 3
85 panelHeight: __panelMinusSeparatorLineHeight
82 panelHeight: __panelMinusSeparatorLineHeight
86− referenceOpenedHeight: parent.height
83+ openedHeight: parent.height + (pinnedMode ? 0 : root.panelHeight)
87−
88 pinnedMode: !fullscreenMode
84 pinnedMode: !fullscreenMode
89
85
90 property real unitProgress: (height - panelHeight) / (openedHeight - panelHeight)
86 property real unitProgress: (height - panelHeight) / (openedHeight - panelHeight)
91 }
87 }
92
88
89+ PanelSeparatorLine {
90+ id: indicatorsSeparatorLine
91+ visible: true
92+ anchors {
93+ left: indicatorsMenu.left
94+ right: indicatorsMenu.right
95+ }
96+ y: indicatorsMenu.visualBottom
97+ }
98+
99+ BorderImage {
100+ id: dropShadow
101+ anchors {
102+ top: indicators.top
103+ bottom: indicatorsSeparatorLine.bottom
104+ left: indicators.left
105+ right: indicators.right
106+ margins: -units.gu(1)
107+ }
108+ visible: indicatorsMenu.height > indicatorsMenu.panelHeight
109+ source: "graphics/rectangular_dropshadow.sci"
110+ }
111+
93 SearchIndicator {
112 SearchIndicator {
94 id: search
113 id: search
95 objectName: "search"
114 objectName: "search"
@@ -98,14 +117,14 @@ Item {
98 state: {
117 state: {
99 if (parent.width < indicatorsMenu.width + width) {
118 if (parent.width < indicatorsMenu.width + width) {
100 if (indicatorsMenu.state != "initial") {
119 if (indicatorsMenu.state != "initial") {
101− return "hiddenUp";
120+ return "hidden";
102 }
121 }
103 }
122 }
104 if (root.searchVisible) {
123 if (root.searchVisible) {
105 return "visible";
124 return "visible";
106 }
125 }
107
126
108− return "hiddenUp";
127+ return "hidden";
109 }
128 }
110
129
111 width: units.gu(13)
130 width: units.gu(13)
Panel/SearchIndicator.qml+3−5
@@ -16,6 +16,7 @@
16
16
17import QtQuick 2.0
17import QtQuick 2.0
18import Ubuntu.Components 0.1
18import Ubuntu.Components 0.1
19+import "../Components"
19
20
20Item {
21Item {
21 id: search
22 id: search
@@ -40,8 +41,7 @@ Item {
40 width: parent.width
41 width: parent.width
41 height: parent.height
42 height: parent.height
42
43
43− Behavior on opacity {NumberAnimation {}}
44+ Behavior on opacity { StandardAnimation {} }
44− Behavior on y {NumberAnimation {duration: 300; easing.type: Easing.OutCubic}}
45
45
46 Image {
46 Image {
47 id: icon
47 id: icon
@@ -67,15 +67,13 @@ Item {
67 name: "visible"
67 name: "visible"
68 PropertyChanges {
68 PropertyChanges {
69 target: container
69 target: container
70− y: 0
71 opacity: 1
70 opacity: 1
72 }
71 }
73 },
72 },
74 State {
73 State {
75− name: "hiddenUp"
74+ name: "hidden"
76 PropertyChanges {
75 PropertyChanges {
77 target: container
76 target: container
78− y: -height
79 opacity: 0
77 opacity: 0
80 }
78 }
81 }
79 }
Shell.qml+2−2
@@ -37,8 +37,8 @@ FocusScope {
37
37
38 // this is only here to select the width / height of the window if not running fullscreen
38 // this is only here to select the width / height of the window if not running fullscreen
39 property bool tablet: false
39 property bool tablet: false
40− width: tablet ? units.gu(160) : units.gu(40)
40+ width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
41− height: tablet ? units.gu(100) : units.gu(71)
41+ height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
42
42
43 property real edgeSize: units.gu(2)
43 property real edgeSize: units.gu(2)
44 property url defaultBackground: shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg"
44 property url defaultBackground: shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg"
cmake/modules/autopilot.cmake+2−1
@@ -2,8 +2,9 @@ add_custom_target(autopilot)
2
2
3function(declare_autopilot_test TEST_NAME TEST_SUITE WORKING_DIR)
3function(declare_autopilot_test TEST_NAME TEST_SUITE WORKING_DIR)
4 add_custom_target(autopilot-${TEST_NAME}
4 add_custom_target(autopilot-${TEST_NAME}
5− COMMAND autopilot run ${TEST_SUITE}
5+ COMMAND LANG=C UBUNTU_ICON_THEME=ubuntu-mobile QML2_IMPORT_PATH=${SHELL_PRIVATE_LIBDIR}/qml/mocks autopilot run ${TEST_SUITE}
6 WORKING_DIRECTORY ${WORKING_DIR}
6 WORKING_DIRECTORY ${WORKING_DIR}
7+ DEPENDS install
7 )
8 )
8
9
9 add_dependencies(autopilot autopilot-${TEST_NAME})
10 add_dependencies(autopilot autopilot-${TEST_NAME})
debian/changelog+35
@@ -1,3 +1,38 @@
1+unity8 (7.81.3+13.10.20130725ubuntu.unity.next-0ubuntu1) saucy; urgency=low
2+
3+ [ Michal Hruby ]
4+ * Fix adding items into an empty LVWPH.
5+
6+ [ Michał Sawicz ]
7+ * Move to using upstart in run_on_device.
8+ * Added heeding and qmltest for button-tint hint.
9+ * Refactoring and cleanup of the Unity8 Autopilot tests.
10+ * Hide the Unity launcher during autopiloting and skip battery tests
11+ if unavailable.
12+
13+ [ Ying-Chun Liu ]
14+ * Let GenericScope support loading different renderers.
15+
16+ [ Nick Dedekind ]
17+ * Removed overview from indicators.
18+ * Menus for indicators is now created prioritised by distance from
19+ current item to speed up user experience.
20+ * Add ApplicationArguments to know the geometry from start.
21+
22+ [ Mirco Müller ]
23+ * Added heeding and qmltest for button-tint hint.
24+
25+ [ Christopher Lee ]
26+ * Refactoring and cleanup of the Unity8 Autopilot tests.
27+
28+ [ Thomi Richards ]
29+ * Refactoring and cleanup of the Unity8 Autopilot tests.
30+
31+ [ Ubuntu daily release ]
32+ * Automatic snapshot from revision 129 (ubuntu-unity/next)
33+
34+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 25 Jul 2013 03:02:12 +0000
35+
1unity8 (7.81.3+13.10.20130718ubuntu.unity.next-0ubuntu1) saucy; urgency=low
36unity8 (7.81.3+13.10.20130718ubuntu.unity.next-0ubuntu1) saucy; urgency=low
2
37
3 [ Andrea Cimitan ]
38 [ Andrea Cimitan ]
debian/control+2
@@ -89,6 +89,8 @@ Depends: indicators-client (>= ${source:Version}),
89 ${misc:Depends},
89 ${misc:Depends},
90 ${python:Depends},
90 ${python:Depends},
91 ${shlibs:Depends},
91 ${shlibs:Depends},
92+Recommends: gir1.2-glib-2.0,
93+ python-gi,
92Description: Test package for Unity 8 shell
94Description: Test package for Unity 8 shell
93 Autopilot tests for the unity8 package
95 Autopilot tests for the unity8 package
94
96
main.cpp+4−6
@@ -18,6 +18,7 @@
18 */
18 */
19
19
20// Qt
20// Qt
21+#include <QtQml>
21#include <QtQuick/QQuickView>
22#include <QtQuick/QQuickView>
22#include <QtGui/QIcon>
23#include <QtGui/QIcon>
23#include <QtGui/QGuiApplication>
24#include <QtGui/QGuiApplication>
@@ -30,6 +31,7 @@
30// local
31// local
31#include "paths.h"
32#include "paths.h"
32#include "MouseTouchAdaptor.h"
33#include "MouseTouchAdaptor.h"
34+#include "ApplicationArguments.h"
33
35
34namespace {
36namespace {
35
37
@@ -86,6 +88,7 @@ int main(int argc, char** argv)
86 resolveIconTheme();
88 resolveIconTheme();
87
89
88 QStringList args = application.arguments();
90 QStringList args = application.arguments();
91+ ApplicationArguments qmlArgs(args);
89
92
90 // The testability driver is only loaded by QApplication but not by QGuiApplication.
93 // The testability driver is only loaded by QApplication but not by QGuiApplication.
91 // However, QApplication depends on QWidget which would add some unneeded overhead => Let's load the testability driver on our own.
94 // However, QApplication depends on QWidget which would add some unneeded overhead => Let's load the testability driver on our own.
@@ -110,6 +113,7 @@ int main(int argc, char** argv)
110 view->setResizeMode(QQuickView::SizeRootObjectToView);
113 view->setResizeMode(QQuickView::SizeRootObjectToView);
111 view->setTitle("Qml Phone Shell");
114 view->setTitle("Qml Phone Shell");
112 view->engine()->setBaseUrl(QUrl::fromLocalFile(::shellAppDirectory()));
115 view->engine()->setBaseUrl(QUrl::fromLocalFile(::shellAppDirectory()));
116+ view->rootContext()->setContextProperty("applicationArguments", &qmlArgs);
113 if (args.contains(QLatin1String("-frameless"))) {
117 if (args.contains(QLatin1String("-frameless"))) {
114 view->setFlags(Qt::FramelessWindowHint);
118 view->setFlags(Qt::FramelessWindowHint);
115 }
119 }
@@ -141,12 +145,6 @@ int main(int argc, char** argv)
141 if (qgetenv("QT_QPA_PLATFORM") == "ubuntu" || args.contains(QLatin1String("-fullscreen"))) {
145 if (qgetenv("QT_QPA_PLATFORM") == "ubuntu" || args.contains(QLatin1String("-fullscreen"))) {
142 view->showFullScreen();
146 view->showFullScreen();
143 } else {
147 } else {
144− if (args.contains(QLatin1String("-geometry")) && args.size() > args.indexOf(QLatin1String("-geometry")) + 1) {
145− QStringList geometryArg = args.at(args.indexOf(QLatin1String("-geometry")) + 1).split('x');
146− if (geometryArg.size() == 2) {
147− view->resize(geometryArg.at(0).toInt(), geometryArg.at(1).toInt());
148− }
149− }
150 view->show();
148 view->show();
151 }
149 }
152
150
plugins/ListViewWithPageHeader/listviewwithpageheader.cpp+9−5
@@ -785,10 +785,14 @@ void ListViewWithPageHeader::onModelUpdated(const QQuickChangeSet &changeSet, bo
785 }
785 }
786 if (growDown) {
786 if (growDown) {
787 adjustMinYExtent();
787 adjustMinYExtent();
788− } else if (remove.index <= m_firstVisibleIndex && !m_visibleItems.isEmpty()) {
788+ } else if (remove.index <= m_firstVisibleIndex) {
789+ if (!m_visibleItems.isEmpty()) {
789− // We removed the first item that is the one that positions the rest
790+ // We removed the first item that is the one that positions the rest
790− // position the new first item correctly
791+ // position the new first item correctly
791− m_visibleItems.first()->setY(oldFirstValidIndexPos);
792+ m_visibleItems.first()->setY(oldFirstValidIndexPos);
793+ } else {
794+ m_firstVisibleIndex = -1;
795+ }
792 }
796 }
793 } else if (remove.index + remove.count <= m_firstVisibleIndex) {
797 } else if (remove.index + remove.count <= m_firstVisibleIndex) {
794 m_firstVisibleIndex -= remove.count;
798 m_firstVisibleIndex -= remove.count;
@@ -806,7 +810,7 @@ void ListViewWithPageHeader::onModelUpdated(const QQuickChangeSet &changeSet, bo
806 Q_FOREACH(const QQuickChangeSet::Insert &insert, changeSet.inserts()) {
810 Q_FOREACH(const QQuickChangeSet::Insert &insert, changeSet.inserts()) {
807// qDebug() << "ListViewWithPageHeader::onModelUpdated Insert" << insert.index << insert.count;
811// qDebug() << "ListViewWithPageHeader::onModelUpdated Insert" << insert.index << insert.count;
808 const bool insertingInValidIndexes = insert.index > m_firstVisibleIndex && insert.index < m_firstVisibleIndex + m_visibleItems.count();
812 const bool insertingInValidIndexes = insert.index > m_firstVisibleIndex && insert.index < m_firstVisibleIndex + m_visibleItems.count();
809− const bool firstItemWithViewOnTop = insert.index == 0 && m_firstVisibleIndex == 0 && !m_visibleItems.isEmpty() && m_visibleItems.first()->y() + m_clipItem->y() > contentY();
813+ const bool firstItemWithViewOnTop = insert.index == 0 && m_firstVisibleIndex == 0 && m_visibleItems.first()->y() + m_clipItem->y() > contentY();
810 if (insertingInValidIndexes || firstItemWithViewOnTop)
814 if (insertingInValidIndexes || firstItemWithViewOnTop)
811 {
815 {
812 // If the items we are adding won't be really visible
816 // If the items we are adding won't be really visible
plugins/Unity/Indicators/CMakeLists.txt+1
@@ -23,6 +23,7 @@ set(IndicatorsQML_SOURCES
23 indicators.h
23 indicators.h
24 indicatorsmanager.cpp
24 indicatorsmanager.cpp
25 indicatorsmodel.cpp
25 indicatorsmodel.cpp
26+ menucontentactivator.cpp
26 plugin.cpp
27 plugin.cpp
27)
28)
28add_definitions(-DUNITYINDICATORS_LIBRARY)
29add_definitions(-DUNITYINDICATORS_LIBRARY)
plugins/Unity/Indicators/menucontentactivator.cppnew file+345
@@ -0,0 +1,345 @@
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+ * Author: Nick Dedekind <nick.dedekind@canonical.com>
17+ */
18+
19+ #include "menucontentactivator.h"
20+
21+// Essentially a QTimer wrapper
22+class ContentTimer : public UnityIndicators::AbstractTimer
23+{
24+ Q_OBJECT
25+public:
26+ ContentTimer(QObject *parent) : UnityIndicators::AbstractTimer(parent) {
27+ m_timer.setSingleShot(false);
28+ connect(&m_timer, &QTimer::timeout,
29+ this, &UnityIndicators::AbstractTimer::timeout);
30+ }
31+ virtual int interval() const { return m_timer.interval(); }
32+ virtual void setInterval(int msecs) { m_timer.setInterval(msecs); }
33+ virtual void start() { m_timer.start(); UnityIndicators::AbstractTimer::start(); }
34+ virtual void stop() { m_timer.stop(); UnityIndicators::AbstractTimer::stop(); }
35+private:
36+ QTimer m_timer;
37+};
38+
39+class MenuContentActivatorPrivate : public QObject
40+{
41+ Q_OBJECT
42+public:
43+ MenuContentActivatorPrivate(MenuContentActivator* parent)
44+ : m_running(false),
45+ m_baseIndex(0),
46+ m_delta(0),
47+ m_count(0),
48+ m_timer(NULL),
49+ q(parent)
50+ {}
51+
52+ ~MenuContentActivatorPrivate()
53+ {
54+ qDeleteAll(m_content);
55+ m_content.clear();
56+ }
57+
58+ int findNextInactiveDelta(bool* finished = NULL);
59+
60+ static int content_count(QQmlListProperty<MenuContentState> *prop);
61+ static MenuContentState* content_at(QQmlListProperty<MenuContentState> *prop, int index);
62+
63+ bool m_running;
64+ int m_baseIndex;
65+ int m_delta;
66+ int m_count;
67+ UnityIndicators::AbstractTimer* m_timer;
68+ QMap<int, MenuContentState*> m_content;
69+ MenuContentActivator* q;
70+};
71+
72+MenuContentActivator::MenuContentActivator(QObject* parent)
73+ : QObject(parent),
74+ d(new MenuContentActivatorPrivate(this))
75+{
76+ qRegisterMetaType<QQmlListProperty<MenuContentState> > ("QQmlListProperty<MenuContentState>");
77+
78+ setContentTimer(new ContentTimer(this));
79+ d->m_timer->setInterval(75);
80+}
81+
82+MenuContentActivator::~MenuContentActivator()
83+{
84+ delete d;
85+}
86+
87+void MenuContentActivator::restart()
88+{
89+ // when we start, make sure we have the base index in the list.
90+ setMenuContentState(d->m_baseIndex, true);
91+ setDelta(0);
92+
93+ // check if we've finished before starting the timer.
94+ bool finished = false;
95+ d->findNextInactiveDelta(&finished);
96+ if (!finished) {
97+ d->m_timer->start();
98+ } else {
99+ d->m_timer->stop();
100+ }
101+
102+ if (!d->m_running) {
103+ d->m_running = true;
104+ Q_EMIT runningChanged(true);
105+ }
106+}
107+
108+void MenuContentActivator::stop()
109+{
110+ d->m_timer->stop();
111+ if (!d->m_running) {
112+ d->m_running = false;
113+ Q_EMIT runningChanged(false);
114+ }
115+}
116+
117+void MenuContentActivator::clear()
118+{
119+ qDeleteAll(d->m_content);
120+ d->m_content.clear();
121+
122+ setDelta(0);
123+ d->m_timer->stop();
124+
125+ Q_EMIT contentChanged();
126+}
127+
128+bool MenuContentActivator::isMenuContentActive(int index) const
129+{
130+ if (d->m_content.contains(index))
131+ return d->m_content[index]->isActive();
132+ return false;
133+}
134+
135+void MenuContentActivator::setRunning(bool running)
136+{
137+ if (running) {
138+ restart();
139+ } else {
140+ stop();
141+ }
142+}
143+
144+bool MenuContentActivator::isRunning() const
145+{
146+ return d->m_running;
147+}
148+
149+void MenuContentActivator::setBaseIndex(int index)
150+{
151+ if (d->m_baseIndex != index) {
152+ d->m_baseIndex = index;
153+
154+ if (isRunning()) {
155+ restart();
156+ }
157+
158+ Q_EMIT baseIndexChanged(index);
159+ }
160+}
161+
162+int MenuContentActivator::baseIndex() const
163+{
164+ return d->m_baseIndex;
165+}
166+
167+void MenuContentActivator::setCount(int count)
168+{
169+ if (d->m_count != count) {
170+ d->m_count = count;
171+ Q_EMIT countChanged(count);
172+
173+ if (isRunning()) {
174+ restart();
175+ }
176+ }
177+}
178+
179+int MenuContentActivator::count() const
180+{
181+ return d->m_count;
182+}
183+
184+void MenuContentActivator::setDelta(int delta)
185+{
186+ if (d->m_delta != delta) {
187+ d->m_delta = delta;
188+ Q_EMIT deltaChanged(d->m_delta);
189+ }
190+}
191+
192+int MenuContentActivator::delta() const
193+{
194+ return d->m_delta;
195+}
196+
197+QQmlListProperty<MenuContentState> MenuContentActivator::content()
198+{
199+ return QQmlListProperty<MenuContentState>(this,
200+ 0,
201+ MenuContentActivatorPrivate::content_count,
202+ MenuContentActivatorPrivate::content_at);
203+}
204+
205+void MenuContentActivator::onTimeout()
206+{
207+ bool finished = false;
208+ int tempDelta = d->findNextInactiveDelta(&finished);
209+ if (!finished) {
210+ setMenuContentState(d->m_baseIndex + tempDelta, true);
211+ setDelta(tempDelta);
212+ }
213+
214+ if (finished) {
215+ d->m_timer->stop();
216+ }
217+}
218+
219+void MenuContentActivator::setContentTimer(UnityIndicators::AbstractTimer *timer)
220+{
221+ int interval = 0;
222+ bool timerWasRunning = false;
223+
224+ // can be null when called from the constructor
225+ if (d->m_timer) {
226+ interval = d->m_timer->interval();
227+ timerWasRunning = d->m_timer->isRunning();
228+ if (d->m_timer->parent() == this) {
229+ delete d->m_timer;
230+ }
231+ }
232+
233+ d->m_timer = timer;
234+ timer->setInterval(interval);
235+ connect(timer, &UnityIndicators::AbstractTimer::timeout,
236+ this, &MenuContentActivator::onTimeout);
237+ if (timerWasRunning) {
238+ d->m_timer->start();
239+ }
240+}
241+
242+void MenuContentActivator::setMenuContentState(int index, bool active)
243+{
244+ if (d->m_content.contains(index)) {
245+ d->m_content[index]->setActive(active);
246+ } else {
247+ d->m_content[index] = new MenuContentState(active);
248+ Q_EMIT contentChanged();
249+ }
250+}
251+
252+int MenuContentActivatorPrivate::findNextInactiveDelta(bool* finished)
253+{
254+ if (m_count == 0 || m_baseIndex >= m_count) {
255+ if (finished) *finished = true;
256+ return 0;
257+ }
258+
259+ int tmpDelta = m_delta;
260+ bool topReached = false, bottomReached = false;
261+ while(true) {
262+
263+ // prechecks for bottom and top limits.
264+ if (tmpDelta > 0 && bottomReached) tmpDelta = -tmpDelta;
265+ if (tmpDelta < 0 && topReached) tmpDelta = (-tmpDelta) + 1;
266+
267+ if (tmpDelta > 0) {
268+ // negative of baseIndex
269+ tmpDelta = -tmpDelta;
270+ // reached the bottom?
271+ if (m_baseIndex + tmpDelta < 0) {
272+ bottomReached = true;
273+ // if we've reached the top as well, then we know we're done.
274+ if (topReached) {
275+ if (finished) *finished = true;
276+ return 0;
277+ }
278+ continue;
279+ }
280+ } else {
281+ // positive of baseIndex
282+ tmpDelta = (-tmpDelta) + 1;
283+ // reached the top?
284+ if (m_baseIndex + tmpDelta >= m_count) {
285+ topReached = true;
286+ // if we've reached the bottom as well, then we know we're done.
287+ if (bottomReached) {
288+ if (finished) *finished = true;
289+ return 0;
290+ }
291+ continue;
292+ }
293+ }
294+
295+ if (q->isMenuContentActive(m_baseIndex + tmpDelta)) {
296+ continue;
297+ }
298+ break;
299+ }
300+ if (finished) *finished = false;
301+ return tmpDelta;
302+}
303+
304+int MenuContentActivatorPrivate::content_count(QQmlListProperty<MenuContentState> *prop)
305+{
306+ MenuContentActivator *p = qobject_cast<MenuContentActivator*>(prop->object);
307+ // we'll create MenuContentState on demand.
308+ return p->count();
309+}
310+
311+MenuContentState* MenuContentActivatorPrivate::content_at(QQmlListProperty<MenuContentState> *prop, int index)
312+{
313+ MenuContentActivator *p = qobject_cast<MenuContentActivator*>(prop->object);
314+ MenuContentActivatorPrivate *d = p->d;
315+
316+ if (!d->m_content.contains(index)) {
317+ MenuContentState* content = new MenuContentState(false);
318+ d->m_content[index] = content;
319+ Q_EMIT p->contentChanged();
320+ return content;
321+ }
322+
323+ return d->m_content[index];
324+}
325+
326+MenuContentState::MenuContentState(bool active)
327+ : m_active(active)
328+{
329+}
330+
331+bool MenuContentState::isActive() const
332+{
333+ return m_active;
334+}
335+
336+void MenuContentState::setActive(bool active)
337+{
338+ if (m_active != active) {
339+ m_active = active;
340+ Q_EMIT activeChanged();
341+ }
342+}
343+
344+// Because we are defining a new QObject-based class (ContentTimer) here.
345+#include "menucontentactivator.moc"
plugins/Unity/Indicators/menucontentactivator.hnew file+117
@@ -0,0 +1,117 @@
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+ * Author: Nick Dedekind <nick.dedekind@canonical.com>
17+ */
18+
19+#ifndef MENUCONTENTACTIVATOR_H
20+#define MENUCONTENTACTIVATOR_H
21+
22+#include "unityindicatorsglobal.h"
23+
24+#include <QObject>
25+#include <QTimer>
26+#include <QQmlListProperty>
27+
28+
29+namespace UnityIndicators {
30+/* Defines an interface for a Timer. */
31+class UNITYINDICATORS_EXPORT AbstractTimer : public QObject {
32+ Q_OBJECT
33+public:
34+ AbstractTimer(QObject *parent) : QObject(parent), m_isRunning(false) {}
35+ virtual int interval() const = 0;
36+ virtual void setInterval(int msecs) = 0;
37+ virtual void start() { m_isRunning = true; };
38+ virtual void stop() { m_isRunning = false; }
39+ bool isRunning() const { return m_isRunning; }
40+Q_SIGNALS:
41+ void timeout();
42+private:
43+ bool m_isRunning;
44+};
45+}
46+
47+/* Defines a object to express the active state of a menu. */
48+class UNITYINDICATORS_EXPORT MenuContentState : public QObject
49+{
50+ Q_OBJECT
51+ Q_PROPERTY(bool active READ isActive NOTIFY activeChanged)
52+public:
53+ MenuContentState(bool active);
54+
55+ bool isActive() const;
56+ void setActive(bool active);
57+
58+Q_SIGNALS:
59+ void activeChanged();
60+private:
61+ bool m_active;
62+};
63+
64+class MenuContentActivatorPrivate;
65+
66+class UNITYINDICATORS_EXPORT MenuContentActivator : public QObject
67+{
68+ Q_OBJECT
69+ Q_PROPERTY(int baseIndex READ baseIndex WRITE setBaseIndex NOTIFY baseIndexChanged)
70+ Q_PROPERTY(bool running READ isRunning WRITE setRunning NOTIFY runningChanged)
71+ Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged)
72+ Q_PROPERTY(QQmlListProperty<MenuContentState> content READ content NOTIFY contentChanged DESIGNABLE false)
73+public:
74+ MenuContentActivator(QObject* parent = NULL);
75+ ~MenuContentActivator();
76+
77+ Q_INVOKABLE void restart();
78+ Q_INVOKABLE void stop();
79+ Q_INVOKABLE void clear();
80+ Q_INVOKABLE bool isMenuContentActive(int index) const;
81+
82+ void setRunning(bool running);
83+ bool isRunning() const;
84+
85+ void setBaseIndex(int index);
86+ int baseIndex() const;
87+
88+ void setCount(int index);
89+ int count() const;
90+
91+ void setDelta(int index);
92+ int delta() const;
93+
94+ QQmlListProperty<MenuContentState> content();
95+
96+ // Replaces the existing Timer with the given one.
97+ //
98+ // Useful for providing a fake timer when testing.
99+ void setContentTimer(UnityIndicators::AbstractTimer *timer);
100+ void setMenuContentState(int index, bool active);
101+
102+Q_SIGNALS:
103+ void baseIndexChanged(int baseIndex);
104+ void deltaChanged(int delta);
105+ void runningChanged(bool running);
106+ void countChanged(int count);
107+ void contentChanged();
108+
109+private Q_SLOTS:
110+ void onTimeout();
111+
112+private:
113+ MenuContentActivatorPrivate* d;
114+ friend class MenuContentActivatorPrivate;
115+};
116+
117+#endif // MENUCONTENTACTIVATOR_H
plugins/Unity/Indicators/plugin.cpp+3
@@ -27,6 +27,7 @@
27#include "indicatorsmanager.h"
27#include "indicatorsmanager.h"
28#include "indicatorsmodel.h"
28#include "indicatorsmodel.h"
29#include "indicators.h"
29#include "indicators.h"
30+#include "menucontentactivator.h"
30
31
31void IndicatorsPlugin::registerTypes(const char *uri)
32void IndicatorsPlugin::registerTypes(const char *uri)
32{
33{
@@ -35,7 +36,9 @@ void IndicatorsPlugin::registerTypes(const char *uri)
35 qmlRegisterType<IndicatorsManager>(uri, 0, 1, "IndicatorsManager");
36 qmlRegisterType<IndicatorsManager>(uri, 0, 1, "IndicatorsManager");
36 qmlRegisterType<FlatMenuProxyModel>(uri, 0, 1, "FlatMenuProxyModel");
37 qmlRegisterType<FlatMenuProxyModel>(uri, 0, 1, "FlatMenuProxyModel");
37 qmlRegisterType<IndicatorsModel>(uri, 0, 1, "IndicatorsModel");
38 qmlRegisterType<IndicatorsModel>(uri, 0, 1, "IndicatorsModel");
39+ qmlRegisterType<MenuContentActivator>(uri, 0, 1, "MenuContentActivator");
38
40
41+ qmlRegisterUncreatableType<MenuContentState>(uri, 0, 1, "MenuContentState", "Can't create MenuContentState class");
39 qmlRegisterUncreatableType<ActionState>(uri, 0, 1, "ActionState", "Can't create ActionState class");
42 qmlRegisterUncreatableType<ActionState>(uri, 0, 1, "ActionState", "Can't create ActionState class");
40 qmlRegisterUncreatableType<NetworkActionState>(uri, 0, 1, "NetworkActionState", "Can't create NetworkActionState class");
43 qmlRegisterUncreatableType<NetworkActionState>(uri, 0, 1, "NetworkActionState", "Can't create NetworkActionState class");
41 qmlRegisterUncreatableType<NetworkConnection>(uri, 0, 1, "NetworkConnection", "Can't create NetworkConnection class");
44 qmlRegisterUncreatableType<NetworkConnection>(uri, 0, 1, "NetworkConnection", "Can't create NetworkConnection class");
plugins/Unity/Indicators/qml/IndicatorPage.qml−6
@@ -188,10 +188,4 @@ IndicatorBase {
188 mainMenu.currentIndex = -1;
188 mainMenu.currentIndex = -1;
189 mainMenu.positionViewAtBeginning();
189 mainMenu.positionViewAtBeginning();
190 }
190 }
191−
192− Component.onCompleted: {
193− if ((busType != 0) && (busName != "") && (deviceMenuObjectPath != "")) {
194− start();
195− }
196− }
197}
191}
plugins/Unity/categories.cpp+4−1
@@ -42,6 +42,7 @@ Categories::Categories(QObject* parent)
42 m_roles[Categories::RoleHints] = "hints";
42 m_roles[Categories::RoleHints] = "hints";
43 m_roles[Categories::RoleResults] = "results";
43 m_roles[Categories::RoleResults] = "results";
44 m_roles[Categories::RoleCount] = "count";
44 m_roles[Categories::RoleCount] = "count";
45+ m_roles[Categories::RoleCategoryIndex] = "categoryIndex";
45
46
46 // TODO This should not be needed but accumulatting the count changes
47 // TODO This should not be needed but accumulatting the count changes
47 // makes the visualization more stable and also makes crashes on fast
48 // makes the visualization more stable and also makes crashes on fast
@@ -131,7 +132,7 @@ Categories::data(const QModelIndex& index, int role) const
131
132
132 switch (role) {
133 switch (role) {
133 case RoleId:
134 case RoleId:
134− return QVariant::fromValue(index.row());
135+ return DeeListModel::data(index, CategoryColumn::ID);
135 case RoleName:
136 case RoleName:
136 return DeeListModel::data(index, CategoryColumn::DISPLAY_NAME);
137 return DeeListModel::data(index, CategoryColumn::DISPLAY_NAME);
137 case RoleIcon:
138 case RoleIcon:
@@ -149,6 +150,8 @@ Categories::data(const QModelIndex& index, int role) const
149 return QVariant::fromValue(getResults(index.row()));
150 return QVariant::fromValue(getResults(index.row()));
150 case RoleCount:
151 case RoleCount:
151 return QVariant::fromValue(getResults(index.row())->rowCount());
152 return QVariant::fromValue(getResults(index.row())->rowCount());
153+ case RoleCategoryIndex:
154+ return QVariant::fromValue(index.row());
152 default:
155 default:
153 return QVariant();
156 return QVariant();
154 }
157 }
plugins/Unity/categories.h+2−1
@@ -49,7 +49,8 @@ public:
49 RoleContentType,
49 RoleContentType,
50 RoleHints,
50 RoleHints,
51 RoleResults,
51 RoleResults,
52− RoleCount
52+ RoleCount,
53+ RoleCategoryIndex
53 };
54 };
54
55
55 QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
56 QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
po/uk.po+4−4
@@ -8,13 +8,13 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2013-06-26 22:58+0200\n"
10"POT-Creation-Date: 2013-06-26 22:58+0200\n"
11−"PO-Revision-Date: 2013-06-27 10:47+0000\n"
11+"PO-Revision-Date: 2013-07-21 05:47+0000\n"
12−"Last-Translator: Yuri Chornoivan <yurchor@gmail.com>\n"
12+"Last-Translator: Ярослав Смук <Smuk.Slavko@yandex.ua>\n"
13"Language-Team: Ukrainian <uk@li.org>\n"
13"Language-Team: Ukrainian <uk@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2013-07-17 07:49+0000\n"
17+"X-Launchpad-Export-Date: 2013-07-22 06:19+0000\n"
18"X-Generator: Launchpad (build 16696)\n"
18"X-Generator: Launchpad (build 16696)\n"
19
19
20#: Applications/applications.js:23
20#: Applications/applications.js:23
@@ -217,4 +217,4 @@ msgstr "Вибачте, для вказаних вами ключових слі
217#: Shell.qml:383
217#: Shell.qml:383
218#, qt-format
218#, qt-format
219msgid "Please enter %1"
219msgid "Please enter %1"
220−msgstr ""
220+msgstr "Будь ласка, введіть %1"
tests/autopilot/setup.py+17−6
@@ -1,11 +1,22 @@
1#!/usr/bin/python
1#!/usr/bin/python
2−
3# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
4−# Copyright 2013 Canonical
5#
3#
4+# Unity Autopilot Test Suite
5+# Copyright (C) 2012-2013 Canonical
6+#
6−# This program is free software: you can redistribute it and/or modify it
7+# This program is free software: you can redistribute it and/or modify
7−# under the terms of the GNU General Public License version 3, as published
8+# it under the terms of the GNU General Public License as published by
8−# by the Free Software Foundation.
9+# the Free Software Foundation, either version 3 of the License, or
10+# (at your option) any later version.
11+#
12+# This program is distributed in the hope that it will be useful,
13+# but WITHOUT ANY WARRANTY; without even the implied warranty of
14+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+# GNU General Public License for more details.
16+#
17+# You should have received a copy of the GNU General Public License
18+# along with this program. If not, see <http://www.gnu.org/licenses/>.
19+#
9
20
10
21
11from distutils.core import setup
22from distutils.core import setup
@@ -14,7 +25,7 @@ from setuptools import find_packages
14setup(
25setup(
15 name='unity',
26 name='unity',
16 version='8.0',
27 version='8.0',
17− description='Unity 8 autopilot tests.',
28+ description='Unity autopilot tests.',
18 url='https://launchpad.net/unity',
29 url='https://launchpad.net/unity',
19 license='GPLv3',
30 license='GPLv3',
20 packages=find_packages(),
31 packages=find_packages(),
tests/autopilot/unity8/__init__.py+90−5
@@ -1,8 +1,93 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2−# Copyright 2013 Canonical
3#
2#
3+# Unity Autopilot Test Suite
4+# Copyright (C) 2012-2013 Canonical
5+#
4−# This program is free software: you can redistribute it and/or modify it
6+# This program is free software: you can redistribute it and/or modify
5−# under the terms of the GNU General Public License version 3, as published
7+# it under the terms of the GNU General Public License as published by
6−# by the Free Software Foundation.
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+"""unity autopilot tests and emulators - top level package."""
21+import os
22+import os.path
23+import subprocess
24+import sysconfig
25+
26+
27+def running_installed_tests():
28+ binary_path = get_binary_path()
29+ return binary_path.startswith('/usr')
30+
31+
32+def get_lib_path():
33+ """Return the library path to use in this test run."""
34+ if running_installed_tests():
35+ lib_path = os.path.join(
36+ "/usr/lib/",
37+ sysconfig.get_config_var('MULTIARCH'),
38+ "unity8"
39+ )
40+ else:
41+ binary_path = get_binary_path()
42+ lib_path = os.path.dirname(binary_path)
43+ return lib_path
44+
45+
46+def get_default_extra_mock_libraries():
47+ mocks_path = get_mocks_library_path()
48+ return os.path.join(mocks_path, 'libusermetrics')
49+
50+
51+def get_mocks_library_path():
52+ if running_installed_tests():
53+ mock_path = "qml/mocks/"
54+ else:
55+ mock_path = "../lib/x86_64-linux-gnu/unity8/qml/mocks/"
56+ lib_path = get_lib_path()
57+ ld_library_path = os.path.abspath(
58+ os.path.join(
59+ lib_path,
60+ mock_path,
61+ )
62+ )
63+
64+ if not os.path.exists(ld_library_path):
65+ raise RuntimeError(
66+ "Expected library path does not exists: %s." % (ld_library_path)
67+ )
68+ return ld_library_path
69+
70+
71+def get_binary_path(binary="unity8"):
72+ """Return the path to the specified binary."""
73+ binary_path = os.path.abspath(
74+ os.path.join(
75+ os.path.dirname(__file__),
76+ "../../../builddir/install/bin/%s" % binary
77+ )
78+ )
79+ if not os.path.exists(binary_path):
80+ try:
81+ binary_path = subprocess.check_output(['which', binary]).strip()
82+ except subprocess.CalledProcessError as e:
83+ raise RuntimeError("Unable to locate %s binary: %r" % (binary, e))
84+ return binary_path
85+
7
86
8−"""unity8 autopilot tests and emulators - top level package."""
87+def get_grid_size():
88+ grid_size = os.getenv('GRID_UNIT_PX')
89+ if grid_size is None:
90+ raise RuntimeError(
91+ "Environment variable GRID_UNIT_PX has not been set."
92+ )
93+ return int(grid_size)
tests/autopilot/unity8/indicators_client/__init__.py+1−1
@@ -5,4 +5,4 @@
5# under the terms of the GNU General Public License version 3, as published
5# under the terms of the GNU General Public License version 3, as published
6# by the Free Software Foundation.
6# by the Free Software Foundation.
7
7
8−"""unity8 indicators-client autopilot tests and emulators - sub level package."""
8+"""unity indicators-client autopilot tests and emulators - sub level package."""
tests/autopilot/unity8/indicators_client/tests/__init__.py+13−9
@@ -5,7 +5,7 @@
5# under the terms of the GNU General Public License version 3, as published
5# under the terms of the GNU General Public License version 3, as published
6# by the Free Software Foundation.
6# by the Free Software Foundation.
7
7
8−"""qml-phone-shell autopilot tests."""
8+"""indicators-client autopilot tests."""
9
9
10import os.path
10import os.path
11
11
@@ -15,6 +15,7 @@ from autopilot.matchers import Eventually
15from autopilot.platform import model
15from autopilot.platform import model
16from testtools.matchers import Equals
16from testtools.matchers import Equals
17
17
18+from unity8 import get_binary_path
18from unity8.indicators_client.emulators.main_window import MainWindow
19from unity8.indicators_client.emulators.main_window import MainWindow
19from logging import getLogger
20from logging import getLogger
20import sys
21import sys
@@ -29,9 +30,12 @@ class IndicatorsTestCase(AutopilotTestCase):
29
30
30 """A common test case class that provides several useful methods for indicator tests."""
31 """A common test case class that provides several useful methods for indicator tests."""
31
32
33+ geometry = None
34+ grid_unit = None
35+
32 if model() == 'Desktop':
36 if model() == 'Desktop':
33 scenarios = [
37 scenarios = [
34− ('with mouse', dict(input_device_class=Mouse)),
38+ ('with mouse', dict(input_device_class=Mouse, geometry="400x800", grid_unit="12")),
35 ]
39 ]
36 else:
40 else:
37 scenarios = [
41 scenarios = [
@@ -41,11 +45,11 @@ class IndicatorsTestCase(AutopilotTestCase):
41 def setUp(self, geometry, grid_size):
45 def setUp(self, geometry, grid_size):
42 self.pointing_device = Pointer(self.input_device_class.create())
46 self.pointing_device = Pointer(self.input_device_class.create())
43 super(IndicatorsTestCase, self).setUp()
47 super(IndicatorsTestCase, self).setUp()
44− if grid_size != "0":
48+ if grid_size is not None:
45− os.environ['GRID_UNIT_PX'] = grid_size
49+ self.patch_environment("GRID_UNIT_PX", str(grid_size))
46 self.grid_size = int(grid_size)
50 self.grid_size = int(grid_size)
47 else:
51 else:
48− self.grid_size = int(os.environ['GRID_UNIT_PX'])
52+ self.grid_size = int(os.getenv('GRID_UNIT_PX'))
49
53
50 if os.path.realpath(__file__).startswith("/usr/"):
54 if os.path.realpath(__file__).startswith("/usr/"):
51 self.launch_test_installed(geometry)
55 self.launch_test_installed(geometry)
@@ -53,15 +57,15 @@ class IndicatorsTestCase(AutopilotTestCase):
53 self.launch_test_local(geometry)
57 self.launch_test_local(geometry)
54
58
55 def launch_test_local(self, geometry):
59 def launch_test_local(self, geometry):
56− if geometry != "0x0":
60+ if geometry is None:
57− self.app = self.launch_test_application("../../builddir/src/Panel/Indicators/client/indicators-client",
61+ self.app = self.launch_test_application(get_binary_path("indicators-client"),
58 "-geometry", geometry, app_type='qt')
62 "-geometry", geometry, app_type='qt')
59 else:
63 else:
60− self.app = self.launch_test_application("../../builddir/src/Panel/Indicators/client/indicators-client",
64+ self.app = self.launch_test_application(get_binary_path("indicators-client"),
61 app_type='qt')
65 app_type='qt')
62
66
63 def launch_test_installed(self, geometry):
67 def launch_test_installed(self, geometry):
64− if geometry != "0x0":
68+ if geometry is not None:
65 self.app = self.launch_test_application("indicators-client", "-geometry", geometry, app_type='qt')
69 self.app = self.launch_test_application("indicators-client", "-geometry", geometry, app_type='qt')
66 else:
70 else:
67 self.app = self.launch_test_application("indicators-client", app_type='qt')
71 self.app = self.launch_test_application("indicators-client", app_type='qt')
tests/autopilot/unity8/indicators_client/tests/test_battery.py+14−3
@@ -8,6 +8,7 @@
8"""Add tests here if you want to ensure the behaviour of the power indicator menus are correct"""
8"""Add tests here if you want to ensure the behaviour of the power indicator menus are correct"""
9
9
10from testtools.matchers import Equals, NotEquals
10from testtools.matchers import Equals, NotEquals
11+from autopilot.input import Touch
11from autopilot.matchers import Eventually
12from autopilot.matchers import Eventually
12
13
13from unity8.indicators_client.tests import IndicatorsTestCase
14from unity8.indicators_client.tests import IndicatorsTestCase
@@ -17,7 +18,7 @@ import math
17
18
18class TestDisplayMenus(IndicatorsTestCase):
19class TestDisplayMenus(IndicatorsTestCase):
19 def setUp(self):
20 def setUp(self):
20− super(TestDisplayMenus, self).setUp("400x800", "12")
21+ super(TestDisplayMenus, self).setUp(self.geometry, self.grid_unit)
21
22
22 # This opens the messaging menu so you don't have to do that in
23 # This opens the messaging menu so you don't have to do that in
23 # every test case
24 # every test case
@@ -69,7 +70,11 @@ class TestDisplayMenus(IndicatorsTestCase):
69
70
70 # wait for the switch menu item
71 # wait for the switch menu item
71 fn_auto_brightness = lambda: self.app.select_single("SwitchMenuItem", objectName="auto-brightness");
72 fn_auto_brightness = lambda: self.app.select_single("SwitchMenuItem", objectName="auto-brightness");
73+ # FIXME: this should go away when we switch to indicator-power
74+ try:
72− self.assertThat(fn_auto_brightness, Eventually(NotEquals(None)));
75+ self.assertThat(fn_auto_brightness, Eventually(NotEquals(None)));
76+ except AssertionError:
77+ self.skipTest("WARNING: test skipped due to missing indicator-battery")
73 auto_brightness = fn_auto_brightness();
78 auto_brightness = fn_auto_brightness();
74
79
75 old_ab_value = auto_brightness.checked
80 old_ab_value = auto_brightness.checked
@@ -82,8 +87,14 @@ class TestDisplayMenus(IndicatorsTestCase):
82 def test_brightness_slider(self):
87 def test_brightness_slider(self):
83 """Test the auto-bright switch"""
88 """Test the auto-bright switch"""
84
89
90+ if self.input_device_class is Touch:
91+ self.skipTest("Dragging is broken with Touch input (LP: #1203808).")
85 fn_brightness_menu = lambda: self.app.select_single("SliderMenuItem", objectName="brightness");
92 fn_brightness_menu = lambda: self.app.select_single("SliderMenuItem", objectName="brightness");
93+ # FIXME: this should go away when we switch to indicator-power
94+ try:
86− self.assertThat(fn_brightness_menu, Eventually(NotEquals(None)));
95+ self.assertThat(fn_brightness_menu, Eventually(NotEquals(None)));
96+ except AssertionError:
97+ self.skipTest("WARNING: test skipped due to missing indicator-battery")
87 brightness_menu = fn_brightness_menu();
98 brightness_menu = fn_brightness_menu();
88
99
89 old_ab_value = brightness_menu.value
100 old_ab_value = brightness_menu.value
tests/autopilot/unity8/shell/__init__.py+35−5
@@ -1,8 +1,38 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2−# Copyright 2013 Canonical
3#
2#
3+# Unity Autopilot Test Suite
4+# Copyright (C) 2012-2013 Canonical
5+#
4−# This program is free software: you can redistribute it and/or modify it
6+# This program is free software: you can redistribute it and/or modify
5−# under the terms of the GNU General Public License version 3, as published
7+# it under the terms of the GNU General Public License as published by
6−# by the Free Software Foundation.
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+"""unity shell autopilot tests and emulators - sub level package."""
21+
22+from functools import wraps
23+import logging
24+
25+
26+logger = logging.getLogger(__name__)
27+
7
28
8−"""unity8 shell autopilot tests and emulators - sub level package."""
29+def with_lightdm_mock(mock_type):
30+ """A simple decorator that sets up the LightDM mock for a single test."""
31+ def with_lightdm_mock_internal(fn):
32+ @wraps(fn)
33+ def wrapper(*args, **kwargs):
34+ tests_self = args[0]
35+ tests_self.patch_lightdm_mock(mock_type)
36+ return fn(*args, **kwargs)
37+ return wrapper
38+ return with_lightdm_mock_internal
tests/autopilot/unity8/shell/emulators/__init__.py+23−4
@@ -1,6 +1,25 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2−# Copyright 2013 Canonical
3#
2#
3+# Unity Autopilot Test Suite
4+# Copyright (C) 2012-2013 Canonical
5+#
4−# This program is free software: you can redistribute it and/or modify it
6+# This program is free software: you can redistribute it and/or modify
5−# under the terms of the GNU General Public License version 3, as published
7+# it under the terms of the GNU General Public License as published by
6−# by the Free Software Foundation.
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+
21+from autopilot.introspection import CustomEmulatorBase
22+
23+
24+class UnityEmulatorBase(CustomEmulatorBase):
25+ """A base class for all unity emulators."""
tests/autopilot/unity8/shell/emulators/dash.pynew file+52
@@ -0,0 +1,52 @@
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+from unity8.shell.emulators import UnityEmulatorBase
21+
22+
23+class Dash(UnityEmulatorBase):
24+
25+ """An emulator that understands the Dash."""
26+
27+ def get_home_applications_grid(self):
28+ return self.select_single(
29+ "ApplicationsFilterGrid",
30+ objectName="dashHomeApplicationsGrid"
31+ )
32+
33+ def get_application_icon(self, text):
34+ """Returns a 'Tile' icon that has the text 'text' from the application
35+ grid.
36+
37+ Will return None if the icon isn't found.
38+
39+ :param text: String containing the text of the icon to search for.
40+
41+ """
42+ app_grid = self.get_home_applications_grid()
43+ resp_grid = app_grid.select_single('ResponsiveGridView')
44+ return resp_grid.select_single('Tile', text=text)
45+
46+ def get_scope(self, scope_name='home'):
47+ dash_content = self.select_single(
48+ 'QQuickListView',
49+ objectName='dashContentList'
50+ )
51+ scope_id = "%s.scope" % scope_name
52+ return dash_content.select_single('QQuickLoader', scopeId=scope_id)
tests/autopilot/unity8/shell/emulators/greeter.pynew file+40
@@ -0,0 +1,40 @@
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+from unity8.shell.emulators import UnityEmulatorBase
21+from autopilot.input import Touch
22+
23+
24+class Greeter(UnityEmulatorBase):
25+
26+ """An emulator that understands the greeter screen."""
27+
28+ def unlock(self):
29+ """Swipe the greeter screen away."""
30+ self.created.wait_for(True)
31+ touch = Touch.create()
32+
33+ rect = self.globalRect
34+ start_x = rect[0] + rect[2] - 3
35+ start_y = int(rect[1] + rect[3] / 2)
36+ stop_x = int(rect[0] + rect[2] * 0.2)
37+ stop_y = start_y
38+ touch.drag(start_x, start_y, stop_x, stop_y)
39+
40+ self.created.wait_for(False)
tests/autopilot/unity8/shell/emulators/hud.pynew file+83
@@ -0,0 +1,83 @@
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+from collections import namedtuple
21+
22+from unity8 import get_grid_size
23+from unity8.shell.emulators import UnityEmulatorBase
24+from autopilot.input import Touch
25+
26+
27+SwipeCoords = namedtuple('SwipeCoords', 'start_x end_x start_y end_y')
28+
29+
30+class Hud(UnityEmulatorBase):
31+
32+ """An emulator that understands the Hud."""
33+
34+ def show(self):
35+ """Swipes open the Hud."""
36+ touch = Touch.create()
37+
38+ window = self.get_root_instance().select_single('QQuickView')
39+ hud_show_button = window.select_single("HudButton")
40+
41+ start_x = int(window.x + (window.width / 2))
42+ end_x = start_x
43+ start_y = window.y + window.height
44+ end_y = int(hud_show_button.y + (hud_show_button.height/2))
45+
46+ touch.press(start_x, start_y)
47+ touch._finger_move(end_x, end_y)
48+ try:
49+ hud_show_button.opacity.wait_for(1.0)
50+ touch.release()
51+ self.shown.wait_for(True)
52+ except AssertionError:
53+ raise
54+ finally:
55+ if touch._touch_finger is not None:
56+ touch.release()
57+
58+ def dismiss(self):
59+ """Closes the open Hud."""
60+ # Ensure that the Hud is actually open
61+ self.shown.wait_for(True)
62+ touch = Touch.create()
63+ x, y = self.get_close_button_coords()
64+ touch.tap(x, y)
65+ self.y.wait_for(self.height)
66+
67+ def get_close_button_coords(self):
68+ """Returns the coordinates of the Huds close button bar."""
69+ rect = self.globalRect
70+ x = int(rect[0] + rect[2] / 2)
71+ y = rect[1] + get_grid_size()
72+ return x, y
73+
74+ def get_button_swipe_coords(self, main_view, hud_show_button):
75+ """Returns the coords both start and end x,y for swiping to make the
76+ 'hud show' button appear.
77+ """
78+ start_x = int(main_view.x + (main_view.width / 2))
79+ end_x = start_x
80+ start_y = main_view.y + (main_view.height - 3)
81+ end_y = int(hud_show_button.y + (hud_show_button.height/2))
82+
83+ return SwipeCoords(start_x, end_x, start_y, end_y)
tests/autopilot/unity8/shell/emulators/launcher.pynew file+38
@@ -0,0 +1,38 @@
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+from unity8.shell.emulators import UnityEmulatorBase
21+from autopilot.input import Touch
22+
23+
24+class Launcher(UnityEmulatorBase):
25+
26+ """An emulator that understands the Launcher."""
27+
28+ def show(self):
29+ """Swipes open the launcher."""
30+ touch = Touch.create()
31+
32+ view = self.get_root_instance().select_single('QQuickView')
33+ start_x = view.x + 1
34+ start_y = view.y + view.height / 2
35+ stop_x = start_x + self.panelWidth + 1
36+ stop_y = start_y
37+ touch.drag(start_x, start_y, stop_x, stop_y)
38+ self.shown.wait_for(True)
tests/autopilot/unity8/shell/emulators/main_window.py+43−12
@@ -1,9 +1,28 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2−# Copyright 2013 Canonical
3#
2#
3+# Unity Autopilot Test Suite
4+# Copyright (C) 2012-2013 Canonical
5+#
4−# This program is free software: you can redistribute it and/or modify it
6+# This program is free software: you can redistribute it and/or modify
5−# under the terms of the GNU General Public License version 3, as published
7+# it under the terms of the GNU General Public License as published by
6−# by the Free Software Foundation.
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+
21+from unity8.shell.emulators.greeter import Greeter
22+from unity8.shell.emulators.hud import Hud
23+from unity8.shell.emulators.dash import Dash
24+from unity8.shell.emulators.launcher import Launcher
25+
7
26
8class MainWindow(object):
27class MainWindow(object):
9 """An emulator class that makes it easy to interact with the shell"""
28 """An emulator class that makes it easy to interact with the shell"""
@@ -16,10 +35,13 @@ class MainWindow(object):
16 return self.app.select_single("QQuickView")
35 return self.app.select_single("QQuickView")
17
36
18 def get_greeter(self):
37 def get_greeter(self):
19− return self.app.select_single("Greeter")
38+ return self.app.select_single(Greeter)
20
39
21 def get_greeter_content_loader(self):
40 def get_greeter_content_loader(self):
41+ return self.app.select_single(
42+ "QQuickLoader",
22− return self.app.select_single("QQuickLoader", objectName="greeterContentLoader")
43+ objectName="greeterContentLoader"
44+ )
23
45
24 def get_login_loader(self):
46 def get_login_loader(self):
25 return self.app.select_single("QQuickLoader", objectName="loginLoader")
47 return self.app.select_single("QQuickLoader", objectName="loginLoader")
@@ -28,7 +50,7 @@ class MainWindow(object):
28 return self.app.select_single("LoginList")
50 return self.app.select_single("LoginList")
29
51
30 def get_hud(self):
52 def get_hud(self):
31− return self.app.select_single("Hud")
53+ return self.app.select_single(Hud)
32
54
33 def get_hud_showable(self):
55 def get_hud_showable(self):
34 return self.app.select_single("Showable", objectName="hudShowable")
56 return self.app.select_single("Showable", objectName="hudShowable")
@@ -37,22 +59,31 @@ class MainWindow(object):
37 return self.app.select_single("HudButton")
59 return self.app.select_single("HudButton")
38
60
39 def get_dash(self):
61 def get_dash(self):
40− return self.app.select_single("Dash")
62+ return self.app.select_single(Dash)
41
63
42 def get_dash_home_applications_grid(self):
64 def get_dash_home_applications_grid(self):
65+ return self.app.select_single(
66+ "ApplicationsFilterGrid",
43− return self.app.select_single("ApplicationsFilterGrid", objectName="dashHomeApplicationsGrid")
67+ objectName="dashHomeApplicationsGrid"
68+ )
44
69
45 def get_bottombar(self):
70 def get_bottombar(self):
46 return self.app.select_single("Bottombar")
71 return self.app.select_single("Bottombar")
47
72
48 def get_launcher(self):
73 def get_launcher(self):
49− return self.app.select_single("Launcher")
74+ return self.app.select_single(Launcher)
50
75
51 def get_pinPadLoader(self):
76 def get_pinPadLoader(self):
52− return self.app.select_single("QQuickLoader", objectName="pinPadLoader")
77+ return self.app.select_single(
78+ "QQuickLoader",
79+ objectName="pinPadLoader"
80+ )
53
81
54 def get_pinPadButton(self, buttonId):
82 def get_pinPadButton(self, buttonId):
83+ return self.app.select_single(
84+ "PinPadButton",
55− return self.app.select_single("PinPadButton", objectName="pinPadButton%i" % buttonId)
85+ objectName="pinPadButton%i" % buttonId
86+ )
56
87
57 def get_lockscreen(self):
88 def get_lockscreen(self):
58 return self.app.select_single("Lockscreen")
89 return self.app.select_single("Lockscreen")
tests/autopilot/unity8/shell/tests/__init__.py+216−58
@@ -1,81 +1,239 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2−# Copyright 2013 Canonical
3#
2#
3+# Unity Autopilot Test Suite
4+# Copyright (C) 2012-2013 Canonical
5+#
4−# This program is free software: you can redistribute it and/or modify it
6+# This program is free software: you can redistribute it and/or modify
5−# under the terms of the GNU General Public License version 3, as published
7+# it under the terms of the GNU General Public License as published by
6−# by the Free Software Foundation.
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+#
7
19
8−"""unity8 autopilot tests."""
20+"""unity autopilot tests."""
9
21
10−import os.path
22+try:
11−import sysconfig
23+ from gi.repository import Gio
24+except ImportError:
25+ Gio = None
12
26
27+from autopilot.platform import model
13from autopilot.testcase import AutopilotTestCase
28from autopilot.testcase import AutopilotTestCase
14from autopilot.matchers import Eventually
29from autopilot.matchers import Eventually
15−from autopilot.platform import model
30+from autopilot.input import Touch
31+from autopilot.display import Display
32+import logging
33+import os.path
16−from testtools.matchers import Equals
34+from testtools.matchers import Equals, NotEquals
17
35
36+from unity8 import (
37+ get_lib_path,
38+ get_binary_path,
39+ get_mocks_library_path,
40+ get_default_extra_mock_libraries,
41+)
42+from unity8.shell.emulators import UnityEmulatorBase
43+from unity8.shell.emulators.dash import Dash
18from unity8.shell.emulators.main_window import MainWindow
44from unity8.shell.emulators.main_window import MainWindow
19
45
20−class FormFactors(object):
21− Phone, Tablet, Desktop = range(3)
22
46
23−class ShellTestCase(AutopilotTestCase):
47+logger = logging.getLogger(__name__)
24
48
25− """A common test case class that provides several useful methods for shell tests."""
49+UNITYSHELL_GSETTINGS_SCHEMA = "org.compiz.unityshell"
50+UNITYSHELL_GSETTINGS_PATH = "/org/compiz/profiles/unity/plugins/unityshell/"
51+UNITYSHELL_LAUNCHER_KEY = "launcher-hide-mode"
52+UNITYSHELL_LAUNCHER_MODE = 1 # launcher hidden
26
53
27− libdir = "/usr/lib/{0}/unity8".format(sysconfig.get_config_var('MULTIARCH'))
54+def _get_device_emulation_scenarios():
28− lightdm_mock = "full"
55+ 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+ ]
66+ else:
67+ return [
68+ (
69+ 'Native Device',
70+ dict(app_width=0, app_height=0, grid_unit_px=0)
71+ )
72+ ]
29
73
30− def setUp(self, geometry, grid_size):
74+
31− super(ShellTestCase, self).setUp()
75+class UnityTestCase(AutopilotTestCase):
32− # Lets assume we are installed system wide if this file is somewhere in /usr
76+
33− if grid_size != "0":
77+ """A test case base class for the Unity shell tests."""
34− os.environ['GRID_UNIT_PX'] = grid_size
78+
35− self.grid_size = int(grid_size)
79+ def setUp(self):
36− else:
80+ super(UnityTestCase, self).setUp()
37− self.grid_size = int(os.environ['GRID_UNIT_PX'])
81+ if Gio is not None and UNITYSHELL_GSETTINGS_SCHEMA in Gio.Settings.list_relocatable_schemas():
38− if os.path.realpath(__file__).startswith("/usr/"):
82+ # Hide Unity launcher
39− self.launch_test_installed(geometry)
83+ self._unityshell_schema = Gio.Settings.new_with_path(UNITYSHELL_GSETTINGS_SCHEMA, UNITYSHELL_GSETTINGS_PATH)
84+ self._launcher_hide_mode = self._unityshell_schema.get_int(UNITYSHELL_LAUNCHER_KEY)
85+ self._unityshell_schema.set_int(UNITYSHELL_LAUNCHER_KEY, UNITYSHELL_LAUNCHER_MODE)
86+ self.addCleanup(self._reset_launcher)
87+
88+ self._proxy = None
89+ self._lightdm_mock_type = None
90+ self.touch = Touch.create()
91+ self._setup_display_details()
92+
93+ def _reset_launcher(self):
94+ """Reset Unity launcher hide mode"""
95+ self._unityshell_schema.set_int(UNITYSHELL_LAUNCHER_KEY, self._launcher_hide_mode)
96+
97+ def _setup_display_details(self):
98+ scale_divisor = self._determine_geometry()
99+ self._setup_grid_size(scale_divisor)
100+
101+ def _determine_geometry(self):
102+ """Use the geometry that may be supplied or use the default."""
103+ width = getattr(self, 'app_width', 0)
104+ height = getattr(self, 'app_height', 0)
105+ scale_divisor = 1
106+ if width == 0 and width == 0:
107+ self.unity_geometry_args = ['-fullscreen']
40 else:
108 else:
41− self.launch_test_local(geometry)
109+ if self._geo_larger_than_display(width, height):
42−
110+ scale_divisor = self._get_scaled_down_geo(width, height)
43− def launch_test_local(self, geometry):
111+ width = width / scale_divisor
44− os.environ['LD_LIBRARY_PATH'] = "../../builddir/tests/mocks/libusermetrics:../../builddir/tests/mocks/LightDM" + self.lightdm_mock
112+ height = height / scale_divisor
45− os.environ['QML2_IMPORT_PATH'] = "../../builddir/tests/mocks"
113+ logger.info(
46− if geometry != "0x0":
114+ "Geometry larger than display, scaled down to: %dx%d",
47− self.app = self.launch_test_application(
115+ width,
48− "../../builddir/unity8", "-geometry", geometry, "-frameless", app_type='qt')
116+ height
117+ )
118+ geo_string = "%dx%d" % (width, height)
119+ self.unity_geometry_args = [
120+ '-geometry',
121+ geo_string,
122+ '-frameless',
123+ '-mousetouch'
124+ ]
125+ return scale_divisor
126+
127+ def _setup_grid_size(self, scale_divisor):
128+ """Use the grid size that may be supplied or use the default."""
129+ if getattr(self, 'grid_unit_px', 0) == 0:
130+ self.grid_size = int(os.getenv('GRID_UNIT_PX'))
49 else:
131 else:
50− self.app = self.launch_test_application(
132+ self.grid_size = int(self.grid_unit_px / scale_divisor)
51− "../../builddir/unity8", "-fullscreen", app_type='qt')
133+ self.patch_environment("GRID_UNIT_PX", str(self.grid_size))
52−
134+
53− def launch_test_installed(self, geometry):
135+ def _geo_larger_than_display(self, width, height):
54− os.environ['LD_LIBRARY_PATH'] = "{0}/qml/mocks/libusermetrics:{0}/qml/mocks/LightDM/{1}".format(self.libdir, self.lightdm_mock)
136+ should_scale = getattr(self, 'scale_geo', True)
55− os.environ['QML2_IMPORT_PATH'] = "{0}/qml/mocks".format(self.libdir)
137+ if should_scale:
56− if model() == 'Desktop' and geometry != "0x0":
138+ screen = Display.create()
57− self.app = self.launch_test_application(
139+ screen_width = screen.get_screen_width()
58− "unity8", "-geometry", geometry, "-frameless", app_type='qt')
140+ screen_height = screen.get_screen_height()
141+ return (width > screen_width) or (height > screen_height)
59 else:
142 else:
60− self.app = self.launch_test_application(
143+ return False
61− "unity8", "-fullscreen", app_type='qt')
144+
145+ def _get_scaled_down_geo(self, width, height):
146+ divisor = 1
147+ while self._geo_larger_than_display(width / divisor, height / divisor):
148+ divisor = divisor * 2
149+ return divisor
150+
151+ def launch_unity(self):
152+ """Launch the unity shell, return a proxy object for it."""
153+ binary_path = get_binary_path()
154+ lib_path = get_lib_path()
155+
156+ logger.info(
157+ "Lib path is '%s', binary path is '%s'",
158+ lib_path,
159+ binary_path
160+ )
161+
162+ if self._lightdm_mock_type is None:
163+ self.patch_lightdm_mock()
164+ self._setup_extra_mock_environment_patch()
165+
166+ app_proxy = self.launch_test_application(
167+ binary_path,
168+ *self.unity_geometry_args,
169+ app_type='qt',
170+ emulator_base=UnityEmulatorBase
171+ )
172+ self._set_proxy(app_proxy)
173+
174+ # Ensure that the dash is visible before we return:
175+ logger.debug("Unity started, waiting for it to be ready.")
176+ self.assertUnityReady()
177+ logger.debug("Unity loaded and ready.")
178+
179+ return app_proxy
180+
181+ def patch_lightdm_mock(self, mock_type='single'):
182+ self._lightdm_mock_type = mock_type
183+ logger.info("Setting up LightDM mock type '%s'", mock_type)
184+ new_ld_library_path = "%s:%s" % (
185+ get_default_extra_mock_libraries(),
186+ self._get_lightdm_mock_path(mock_type)
187+ )
188+ logger.info("New library path: %s", new_ld_library_path)
189+
190+ self.patch_environment('LD_LIBRARY_PATH', new_ld_library_path)
191+
192+ def _get_lightdm_mock_path(self, mock_type):
193+ lib_path = get_mocks_library_path()
194+ lightdm_mock_path = os.path.abspath(
195+ os.path.join(lib_path, "LightDM", mock_type)
196+ )
197+
198+ if not os.path.exists(lightdm_mock_path):
199+ raise RuntimeError(
200+ "LightDM mock '%s' does not exist at path '%s'."
201+ % (mock_type, lightdm_mock_path)
202+ )
203+ return lightdm_mock_path
204+
205+ def _setup_extra_mock_environment_patch(self):
206+ mocks_library_path = get_mocks_library_path()
207+ self.patch_environment('QML2_IMPORT_PATH', mocks_library_path)
208+
209+ def _set_proxy(self, proxy):
210+ """Keep a copy of the proxy object, so we can use it to get common
211+ parts of the shell later on.
212+
213+ """
214+ self._proxy = proxy
215+ self.addCleanup(self._clear_proxy)
216+
217+ def _clear_proxy(self):
218+ self._proxy = None
62
219
63− def skipWrapper(*args, **kwargs):
220+ def assertUnityReady(self):
64− pass
221+ dash = self.get_dash()
222+ home_scope = dash.get_scope('home')
65
223
66− def form_factor(self):
224+ # FIXME! There is a huge timeout here for when we're doing CI on
67− return FormFactors.Desktop
225+ # VMs. See lp:1203715
226+ self.assertThat(
227+ home_scope.isLoaded,
228+ Eventually(Equals(True), timeout=60)
229+ )
230+ self.assertThat(home_scope.isCurrent, Eventually(Equals(True)))
68
231
69− def __getattribute__(self, attr_name):
232+ def get_dash(self):
70− attr = object.__getattribute__(self, attr_name);
233+ dash = self._proxy.select_single(Dash)
71− if attr_name.startswith("test_"):
234+ self.assertThat(dash, NotEquals(None))
72− try:
235+ return dash
73− if self.form_factor() in attr.blacklist:
74− return self.skipWrapper
75− except:
76− pass
77− return attr
78
236
79 @property
237 @property
80 def main_window(self):
238 def main_window(self):
81− return MainWindow(self.app)
239+ return MainWindow(self._proxy)
tests/autopilot/unity8/shell/tests/helpers.pydeleted−161
@@ -1,161 +0,0 @@
1−# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2−# Copyright 2013 Canonical
3−#
4−# This program is free software: you can redistribute it and/or modify it
5−# under the terms of the GNU General Public License version 3, as published
6−# by the Free Software Foundation.
7−
8−from testtools.matchers import Equals, NotEquals
9−from autopilot.matchers import Eventually
10−
11−import logging
12−
13−logger = logging.getLogger(__name__)
14−
15−class TestShellHelpers(object):
16− """Helpers for testing the Shell"""
17−
18− def select_greeter_user(self, username):
19− greeter = self.main_window.get_greeter()
20− self.assertThat(greeter.created, Eventually(Equals(True)))
21− self.assertThat(greeter.multiUser, Eventually(Equals(True)))
22−
23− login_loader = self.main_window.get_login_loader()
24− self.assertThat(login_loader.progress, Eventually(Equals(1)))
25−
26− login_list = self.main_window.get_login_list()
27− list_view = login_list.get_children_by_type("QQuickListView")[0]
28−
29− try_count = 0
30− max_tries = 50 # just in case we go off rails
31− while try_count < max_tries:
32− users = list_view.get_children_by_type("QQuickItem")[0].get_children_by_type("QQuickItem")
33− target_user = None
34− for user in users:
35− try:
36− user_label = user.get_children_by_type("Label")[0]
37− if user.opacity < 0.1:
38− continue # off-screen item
39− if user_label.text == username:
40− target_user = user
41− break
42− elif target_user is None or user.y > target_user.y:
43− target_user = user
44− except Exception:
45− pass
46− if target_user is None:
47− break
48− user_label = target_user.get_children_by_type("Label")[0]
49− self.touch.tap_object(user_label)
50− self.assertThat(list_view.movingInternally, Eventually(Equals(False)))
51− if user_label.text == username:
52− return login_list.get_children_by_type("TextField")[0]
53− try_count = try_count + 1
54− self.fail() # We didn't find it
55−
56− def unlock_greeter(self, retries=2):
57− greeter = self.main_window.get_greeter()
58− self.assertThat(greeter.created, Eventually(Equals(True)))
59−
60− if greeter.multiUser:
61− password_field = self.select_greeter_user("No Password")
62− self.assertThat(password_field.opacity, Eventually(Equals(1)))
63− self.touch.tap_object(password_field)
64−
65− else:
66− rect = greeter.globalRect
67− start_x = rect[0] + rect[2] - 3
68− start_y = int(rect[1] + rect[3] / 2)
69− stop_x = int(rect[0] + rect[2] * 0.2)
70− stop_y = start_y
71− self.touch.drag(start_x, start_y, stop_x, stop_y)
72−
73− # Because the shell loads up lots of stuff, unlocking the greeter can
74− # be a bit stuttery while scopes are still consuming all resources.
75− # Give it another (max retries) chance
76− try:
77− self.assertThat(greeter.created, Eventually(Equals(False)))
78− except:
79− if retries > 0:
80− logger.warning("Failed to unlock greeter. Retrying...")
81− self.unlock_greeter(retries-1)
82− else:
83− logger.warning("Failed to unlock greeter. Giving up. Tests may fail...")
84−
85−
86− def open_first_dash_home_app(self, retries=2):
87− self.assertThat(lambda: self.main_window.get_dash_home_applications_grid(), Eventually(NotEquals(None)))
88− app_grid = self.main_window.get_dash_home_applications_grid()
89− # Wait for the grids to be expanded
90− self.assertThat(app_grid.get_children()[0].totalContentHeight, Eventually(NotEquals(0)))
91− self.assertThat(app_grid.get_children()[0].height, Eventually(Equals(app_grid.get_children()[0].totalContentHeight)))
92− first_app = app_grid.get_children()[0].get_children()[0].get_children()[0].get_children()[0]
93− self.touch.tap_object(first_app)
94− bottombar = self.main_window.get_bottombar()
95−
96− # if we have huge amounts of pixels (e.g. Nexus10), but slow video (e.g. VM) it might take a little
97− # until the dash is fully rendered after sliding away the greeter/lockscreen and the click might
98− # go to the void for the first time because there's no real way to know what the display has
99− # painted already. Give it another (max retries) chance.
100− try:
101− self.assertThat(lambda: getattr(bottombar, "applicationIsOnForeground"),
102− Eventually(Equals(True)))
103− except:
104− if retries > 0:
105− logger.warning("Failed to launch app. Retrying...")
106− self.open_first_dash_home_app(retries-1)
107− else:
108− logger.warning("Failed to launch app. Giving up. Tests may fail...")
109−
110−
111− def show_hud(self, retries=2):
112− self.open_first_dash_home_app()
113−
114− hud_show_button = self.main_window.get_hud_show_button()
115− hud = self.main_window.get_hud()
116− window = self.main_window.get_qml_view()
117− start_x = int(window.x + window.width / 2)
118− start_y = window.y + window.height - 3
119− self.assertThat(hud_show_button.opacity, Eventually(Equals(0)))
120− self.touch.press(start_x, start_y)
121− self.touch._finger_move(start_x, start_y - self.grid_size)
122− self.assertThat(hud_show_button.opacity, Eventually(Equals(0)))
123− self.touch._finger_move(start_x, start_y - self.grid_size * 2)
124− self.touch._finger_move(start_x, start_y - self.grid_size * 3)
125− self.touch._finger_move(start_x, start_y - self.grid_size * 4)
126− try:
127− self.assertThat(hud_show_button.opacity, Eventually(Equals(1.0)))
128− except:
129− if retries > 0:
130− logger.warning("Failed to get hud button to show. Retrying...")
131− self.touch.release()
132− self.show_hud(retries-1)
133− return
134− else:
135− logger.warning("Failed to get hud button to show. Giving up. Tests may fail...")
136− self.assertThat(hud_show_button.mouseOver, Eventually(Equals(False)))
137− self.touch._finger_move(start_x, start_y - self.grid_size * 34)
138− self.assertThat(hud_show_button.opacity, Eventually(Equals(1.0)))
139− self.assertThat(hud_show_button.mouseOver, Eventually(Equals(True)))
140− self.touch.release()
141− self.assertThat(hud_show_button.opacity, Eventually(Equals(0)))
142− self.assertThat(hud.shown, Eventually(Equals(True)))
143−
144− def close_hud_click(self):
145− hud = self.main_window.get_hud()
146− self.assertThat(hud.shown, Eventually(Equals(True)))
147− rect = hud.globalRect
148− x = int(rect[0] + rect[2] / 2)
149− y = rect[1] + self.grid_size
150− self.touch.tap(x, y)
151− self.assertThat(hud.shown, Eventually(Equals(False)))
152−
153− def show_launcher(self):
154− launcher = self.main_window.get_launcher()
155− view = self.main_window.get_qml_view()
156− start_x = view.x + 1
157− start_y = view.y + view.height / 2
158− stop_x = start_x + launcher.panelWidth + 1
159− stop_y = start_y
160− self.touch.drag(start_x, start_y, stop_x, stop_y)
161− self.assertThat(launcher.shown, Eventually(Equals(True)))
tests/autopilot/unity8/shell/tests/test_hud.pynew file+151
@@ -0,0 +1,151 @@
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+from __future__ import absolute_import
21+
22+from unity8.shell import with_lightdm_mock
23+from unity8.shell.tests import UnityTestCase, _get_device_emulation_scenarios
24+
25+from testtools.matchers import Equals
26+from autopilot.matchers import Eventually
27+
28+
29+class TestHud(UnityTestCase):
30+
31+ """Tests the Shell HUD."""
32+
33+ scenarios = _get_device_emulation_scenarios()
34+
35+ def test_show_hud_button_appears(self):
36+ """Swiping up while an app is active must show the 'show hud' button.
37+
38+ """
39+ self.launch_unity()
40+ self.main_window.get_greeter().unlock()
41+ window = self.main_window.get_qml_view()
42+ hud_show_button = self.main_window.get_hud_show_button()
43+ hud = self.main_window.get_hud()
44+
45+ self._launch_test_app_from_app_screen()
46+
47+ swipe_coords = hud.get_button_swipe_coords(
48+ window,
49+ hud_show_button
50+ )
51+ self.touch.press(swipe_coords.start_x, swipe_coords.start_y)
52+ self.addCleanup(self.touch.release)
53+ self.touch._finger_move(swipe_coords.end_x, swipe_coords.end_y)
54+ self.assertThat(hud_show_button.opacity, Eventually(Equals(1.0)))
55+
56+ def test_show_hud_appears(self):
57+ """Releasing the touch on the 'show hud' button must display the hud.
58+
59+ """
60+ self.launch_unity()
61+ self.main_window.get_greeter().unlock()
62+ window = self.main_window.get_qml_view()
63+ hud_show_button = self.main_window.get_hud_show_button()
64+ hud = self.main_window.get_hud()
65+
66+ self._launch_test_app_from_app_screen()
67+
68+ swipe_coords = hud.get_button_swipe_coords(
69+ window,
70+ hud_show_button
71+ )
72+ self.touch.press(swipe_coords.start_x, swipe_coords.start_y)
73+ self.addCleanup(self._maybe_release_finger)
74+ self.touch._finger_move(swipe_coords.end_x, swipe_coords.end_y)
75+
76+ self.assertThat(hud.shown, Eventually(Equals(False)))
77+ self.assertThat(hud_show_button.opacity, Eventually(Equals(1.0)))
78+ self.touch.release()
79+ self.assertThat(hud.shown, Eventually(Equals(True)))
80+
81+ def test_hide_hud_click(self):
82+ """Tapping the close button of the Hud must dismiss it."""
83+ self.launch_unity()
84+ self.main_window.get_greeter().unlock()
85+ hud = self.main_window.get_hud()
86+
87+ self._launch_test_app_from_app_screen()
88+ hud.show()
89+
90+ x, y = hud.get_close_button_coords()
91+ self.touch.tap(x, y)
92+ self.assertThat(hud.shown, Eventually(Equals(False)))
93+
94+ def test_hide_hud_dragging(self):
95+ """Once open the Hud must close if the upper bar is dragged and
96+ released downward.
97+
98+ """
99+ self.launch_unity()
100+ self.main_window.get_greeter().unlock()
101+ hud = self.main_window.get_hud()
102+ window = self.main_window.get_qml_view()
103+
104+ self._launch_test_app_from_app_screen()
105+ hud.show()
106+
107+ start_x, start_y = hud.get_close_button_coords()
108+ end_x = start_x
109+ end_y = int(window.height / 2)
110+
111+ self.touch.drag(start_x, start_y, end_x, end_y)
112+ self.assertThat(hud.shown, Eventually(Equals(False)))
113+
114+ def test_launcher_hides_hud(self):
115+ """Opening the Launcher while the Hud is active must close the Hud."""
116+ self.launch_unity()
117+ self.main_window.get_greeter().unlock()
118+ hud = self.main_window.get_hud()
119+ launcher = self.main_window.get_launcher()
120+
121+ self._launch_test_app_from_app_screen()
122+
123+ hud.show()
124+ launcher.show()
125+
126+ self.assertThat(hud.shown, Eventually(Equals(False)))
127+
128+ def _launch_test_app_from_app_screen(self):
129+ """Launches the camera app using the Dash UI.
130+
131+ Because when testing on the desktop running
132+ self.launch_application() will launch the application on the desktop
133+ itself and not within the Unity UI.
134+
135+ """
136+ dash = self.main_window.get_dash()
137+ icon = dash.get_application_icon('Camera')
138+ self.touch.tap_object(icon)
139+
140+ # Ensure application is open
141+ bottombar = self.main_window.get_bottombar()
142+ self.assertThat(bottombar.applicationIsOnForeground,
143+ Eventually(Equals(True)))
144+
145+ # Because some tests are manually manipulating the finger, we want to
146+ # cleanup if the test fails, but we don't want to fail with an exception if
147+ # we don't.
148+ def _maybe_release_finger(self):
149+ """Only release the finger if it is in fact down."""
150+ if self.touch._touch_finger is not None:
151+ self.touch.release()
tests/autopilot/unity8/shell/tests/test_lock_screen.pynew file+131
@@ -0,0 +1,131 @@
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+
21+from __future__ import absolute_import
22+
23+from unity8.shell import with_lightdm_mock
24+from unity8.shell.tests import UnityTestCase, _get_device_emulation_scenarios
25+
26+from autopilot.matchers import Eventually
27+from autopilot.platform import model
28+from testtools import skipUnless
29+from testtools.matchers import Equals
30+
31+
32+class TestLockscreen(UnityTestCase):
33+
34+ """Tests for the lock screen."""
35+
36+ scenarios = _get_device_emulation_scenarios()
37+
38+ @with_lightdm_mock("single-pin")
39+ def test_can_unlock_pin_screen(self):
40+ """Must be able to unlock the PIN entry lock screen."""
41+ self.launch_unity()
42+ greeter = self.main_window.get_greeter()
43+ greeter.unlock()
44+
45+ lockscreen = self._wait_for_lockscreen()
46+ self._enter_pincode("1234")
47+
48+ self.assertThat(lockscreen.shown, Eventually(Equals(False)))
49+
50+ @with_lightdm_mock("single-passphrase")
51+ def test_can_unlock_passphrase_screen(self):
52+ """Must be able to unlock the passphrase entry screen."""
53+ self.launch_unity()
54+ greeter = self.main_window.get_greeter()
55+ greeter.unlock()
56+
57+ lockscreen = self._wait_for_lockscreen()
58+ self._enter_passphrase("password")
59+
60+ self.assertThat(lockscreen.shown, Eventually(Equals(False)))
61+
62+ @with_lightdm_mock("single-pin")
63+ def test_pin_screen_wrong_code(self):
64+ """Entering the wrong pin code must not dismiss the lock screen."""
65+ self.launch_unity()
66+ greeter = self.main_window.get_greeter()
67+ greeter.unlock()
68+
69+ lockscreen = self._wait_for_lockscreen()
70+ self._enter_pincode("4321")
71+
72+ pinentryField = self.main_window.get_pinentryField()
73+ self.assertThat(pinentryField.text, Eventually(Equals("")))
74+ self.assertThat(lockscreen.shown, Eventually(Equals(True)))
75+
76+ @with_lightdm_mock("single-passphrase")
77+ def test_passphrase_screen_wrong_password(self):
78+ """Entering the wrong password must not dismiss the lock screen."""
79+ self.launch_unity()
80+ greeter = self.main_window.get_greeter()
81+ greeter.unlock()
82+
83+ lockscreen = self._wait_for_lockscreen()
84+ self._enter_passphrase("foobar")
85+
86+ pinentryField = self.main_window.get_pinentryField()
87+ self.assertThat(pinentryField.text, Eventually(Equals("")))
88+ self.assertThat(lockscreen.shown, Eventually(Equals(True)))
89+
90+ def _wait_for_lockscreen(self):
91+ """Wait for the lock screen to load, and return it."""
92+ pinPadLoader = self.main_window.get_pinPadLoader()
93+ self.assertThat(pinPadLoader.progress, Eventually(Equals(1)))
94+ lockscreen = self.main_window.get_lockscreen()
95+ self.assertThat(lockscreen.shown, Eventually(Equals(True)))
96+ return lockscreen
97+
98+ def _enter_pincode(self, code):
99+ """Enter code 'code' into the single-pin lightdm pincode entry
100+ screen.
101+
102+ :param code: must be a string of numeric characters.
103+ :raises: TypeError if code is not a string.
104+ :raises: ValueError if code contains non-numeric characters.
105+
106+ """
107+
108+ if not isinstance(code, basestring):
109+ raise TypeError("'code' parameter must be a string.")
110+ for num in code:
111+ if not num.isdigit():
112+ raise ValueError(
113+ "'code' parameter contains non-numeric characters."
114+ )
115+ self.touch.tap_object(self.main_window.get_pinPadButton(int(num)))
116+
117+ def _enter_passphrase(self, passphrase):
118+ """Enter the password specified in 'passphrase' into the password entry
119+ field.
120+
121+ :param passphrase: The string you want to enter.
122+ :raises: TypeError if passphrase is not a string.
123+
124+ """
125+ if not isinstance(passphrase, basestring):
126+ raise TypeError("'passphrase' parameter must be a string.")
127+
128+ pinentryField = self.main_window.get_pinentryField()
129+ self.touch.tap_object(pinentryField)
130+ self.keyboard.type(passphrase)
131+ self.keyboard.type("\n")
tests/autopilot/unity8/shell/tests/testbigscreen.pydeleted−25
@@ -1,25 +0,0 @@
1−# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2−# Copyright 2013 Canonical
3−#
4−# This program is free software: you can redistribute it and/or modify it
5−# under the terms of the GNU General Public License version 3, as published
6−# by the Free Software Foundation.
7−
8−"""Add tests here if you want to ensure everything is aligned correctly on huge screens. Because this test does not fit on most screens, you should not use input devices here or your tests are likely to fail"""
9−
10−from testtools.matchers import Equals
11−from autopilot.matchers import Eventually
12−
13−from unity8.shell.tests import ShellTestCase
14−
15−class TestBig(ShellTestCase):
16− def setUp(self):
17− super(TestBig, self).setUp("2560x1600", "20")
18− self.assertThat(self.main_window.get_qml_view().visible, Eventually(Equals(True)))
19−
20− def tearDown(self):
21− super(TestBig, self).tearDown()
22−
23− def test_hud_not_shown_greeter(self):
24− hud_showable = self.main_window.get_hud_showable()
25− self.assertThat(hud_showable.y, Eventually(Equals(1600)))
tests/autopilot/unity8/shell/tests/testhud.pydeleted−145
@@ -1,145 +0,0 @@
1−# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2−# Copyright 2013 Canonical
3−#
4−# This program is free software: you can redistribute it and/or modify it
5−# under the terms of the GNU General Public License version 3, as published
6−# by the Free Software Foundation.
7−
8−
9−# This file contains general purpose test cases for Unity.
10−# Each test written in this file will be executed for a variety of
11−# configurations, such as Phone, Tablet or Desktop form factors.
12−#
13−# Sometimes there is the need to disable a certain test for a particular
14−# configuration. To do so, add this in a new line directly below your test:
15−#
16−# test_testname.blacklist = (FormFactors.Tablet, FormFactors.Desktop,)
17−#
18−# Available form factors are:
19−# FormFactors.Phone
20−# FormFactors.Tablet
21−# FormFactors.Desktop
22−
23−
24−"""Tests for the Shell"""
25−
26−from __future__ import absolute_import
27−
28−from unity8.shell.tests import ShellTestCase, FormFactors
29−from unity8.shell.tests.helpers import TestShellHelpers
30−
31−from autopilot.input import Mouse, Touch, Pointer
32−from testtools.matchers import Equals, NotEquals, GreaterThan, MismatchError
33−from autopilot.matchers import Eventually
34−from autopilot.display import Display
35−from autopilot.platform import model
36−
37−import unittest
38−import time
39−import os
40−from os import path
41−
42−
43−class TestHud(ShellTestCase, TestShellHelpers):
44−
45− """Tests the Shell"""
46−
47− # Scenarios:
48− # Fill in the scenarios to run the whole test suite with multiple configurations.
49− # Use app_width, app_height and grid_unit_px to set the apps geometry.
50− # Set app_width and app_height to 0 to use fullscreen.
51− # Set grid_unit_px to 0 to use the current system environment.
52−
53− if model() == 'Desktop':
54− scenarios = [
55− ('Nexus 4', dict(app_width=768, app_height=1280, grid_unit_px=18, lightdm_mock="single")),
56− ('Nexus 10', dict(app_width=2560, app_height=1600, grid_unit_px=20, lightdm_mock="full")),
57−# TODO: don't run fullscreen tests just yet as the VM is performing too badly for that. Enable this once
58−# Autopilot tests are running on bear metal.
59−# ('Fullscreen', dict(app_width=0, app_height=0, grid_unit_px=10, lightdm_mock="full")),
60− ]
61− else:
62− scenarios = [
63− ('Fullscreen', dict(app_width=0, app_height=0, grid_unit_px=0, lightdm_mock="single")),
64− ]
65−
66− def setUp(self):
67− self.touch = Touch.create()
68−
69− sg = Display().create()
70− divisor = 1
71− while (sg.get_screen_width() < self.app_width / divisor or sg.get_screen_height() < self.app_height / divisor):
72− divisor = divisor * 2
73− super(TestHud, self).setUp("%sx%s" % (self.app_width / divisor, self.app_height / divisor), "%s" % (self.grid_unit_px / divisor))
74−
75− dash = self.main_window.get_dash()
76− self.assertThat(dash.showScopeOnLoaded, Eventually(Equals(""), timeout=30))
77−
78− def test_show_hud(self):
79− hud = self.main_window.get_hud()
80− self.unlock_greeter()
81− self.show_hud()
82− self.assertThat(hud.shown, Eventually(Equals(True)))
83−
84− def test_show_hud_button_dont_open(self):
85− self.unlock_greeter()
86− self.open_first_dash_home_app()
87− hud_show_button = self.main_window.get_hud_show_button()
88− hud = self.main_window.get_hud()
89− window = self.main_window.get_qml_view()
90− start_x = int(window.x + window.width / 2)
91− start_y = window.y + window.height - 2
92− self.assertThat(hud_show_button.opacity, Eventually(Equals(0)))
93− self.touch.press(start_x, start_y)
94− self.touch._finger_move(start_x, start_y - self.grid_size)
95− self.assertThat(hud_show_button.opacity, Eventually(Equals(0)))
96− self.touch._finger_move(start_x, start_y - self.grid_size * 2)
97− self.touch._finger_move(start_x, start_y - self.grid_size * 3)
98− self.touch._finger_move(start_x, start_y - self.grid_size * 4)
99− self.assertThat(hud_show_button.opacity, Eventually(Equals(1.0)))
100− self.assertThat(hud_show_button.mouseOver, Eventually(Equals(False)))
101− self.touch._finger_move(start_x, start_y - self.grid_size * 34)
102− self.assertThat(hud_show_button.opacity, Eventually(Equals(1.0)))
103− self.assertThat(hud_show_button.mouseOver, Eventually(Equals(True)))
104− self.touch._finger_move(start_x, start_y - self.grid_size)
105− self.assertThat(hud_show_button.opacity, Eventually(Equals(1.0)))
106− self.assertThat(hud_show_button.mouseOver, Eventually(Equals(False)))
107− self.touch.release()
108− self.assertThat(hud_show_button.opacity, Eventually(Equals(0)))
109− self.assertThat(hud.shown, Eventually(Equals(False)))
110−
111− def test_hide_hud_click(self):
112− hud = self.main_window.get_hud()
113− self.unlock_greeter()
114− self.show_hud()
115− self.close_hud_click()
116− self.assertThat(hud.shown, Eventually(Equals(False)))
117−
118− def test_hide_hud_click_outside_handle(self):
119− hud = self.main_window.get_hud()
120− self.unlock_greeter()
121− self.show_hud()
122− rect = hud.globalRect
123− x = int(rect[0] + rect[2] / 2)
124− y = rect[1] + hud.handleHeight + 1
125− self.touch.tap(x, y)
126− self.assertRaises(MismatchError, lambda: self.assertThat(hud.shown, Eventually(Equals(False), timeout=3)))
127−
128− def test_hide_hud_dragging(self):
129− hud = self.main_window.get_hud()
130− self.unlock_greeter()
131− self.show_hud()
132− rect = hud.globalRect
133− start_x = rect[0] + (rect[2] - rect[0]) / 2
134− start_y = rect[1] + 1
135− stop_x = start_x
136− stop_y = start_y + (rect[3] - rect[1]) / 2
137− self.touch.drag(start_x, start_y, stop_x, stop_y)
138− self.assertThat(hud.shown, Eventually(Equals(False)))
139−
140− def test_hide_hud_launcher(self):
141− hud = self.main_window.get_hud()
142− self.unlock_greeter()
143− self.show_hud()
144− self.show_launcher()
145− self.assertThat(hud.shown, Eventually(Equals(False)))
tests/autopilot/unity8/shell/tests/testlockscreen.pydeleted−118
@@ -1,118 +0,0 @@
1−# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2−# Copyright 2013 Canonical
3−#
4−# This program is free software: you can redistribute it and/or modify it
5−# under the terms of the GNU General Public License version 3, as published
6−# by the Free Software Foundation.
7−
8−
9−# This file contains general purpose test cases for Unity.
10−# Each test written in this file will be executed for a variety of
11−# configurations, such as Phone, Tablet or Desktop form factors.
12−#
13−# Sometimes there is the need to disable a certain test for a particular
14−# configuration. To do so, add this in a new line directly below your test:
15−#
16−# test_testname.blacklist = (FormFactors.Tablet, FormFactors.Desktop,)
17−#
18−# Available form factors are:
19−# FormFactors.Phone
20−# FormFactors.Tablet
21−# FormFactors.Desktop
22−
23−
24−"""Tests for the Shell"""
25−
26−from __future__ import absolute_import
27−
28−from unity8.shell.tests import ShellTestCase, FormFactors
29−from unity8.shell.tests.helpers import TestShellHelpers
30−
31−from autopilot.input import Mouse, Touch, Pointer
32−from testtools.matchers import Equals, NotEquals, GreaterThan, MismatchError
33−from autopilot.matchers import Eventually
34−from autopilot.display import Display
35−from autopilot.platform import model
36−
37−import unittest
38−import time
39−import os
40−from os import path
41−
42−
43−class TestLockscreens(ShellTestCase, TestShellHelpers):
44−
45− """Tests the Lockscreens"""
46−
47− # Scenarios:
48− # Fill in the scenarios to run the whole test suite with multiple configurations.
49− # Use app_width, app_height and grid_unit_px to set the apps geometry.
50− # Set app_width and app_height to 0 to use fullscreen.
51− # Set grid_unit_px to 0 to use the current system environment.
52−
53− if model() == 'Desktop':
54− scenarios = [
55− ('Pinlock', dict(app_width=768, app_height=1280, grid_unit_px=18, lightdm_mock="single-pin")),
56− ('Keylock', dict(app_width=768, app_height=1280, grid_unit_px=18, lightdm_mock="single-passphrase")),
57− ]
58− else:
59− scenarios = [
60− ('Pinlock', dict(app_width=0, app_height=0, grid_unit_px=0, lightdm_mock="single-pin")),
61− ('Keylock', dict(app_width=0, app_height=0, grid_unit_px=0, lightdm_mock="single-key")),
62− ]
63−
64− def setUp(self):
65− self.touch = Touch.create()
66−
67− sg = Display().create()
68− divisor = 1
69− while (sg.get_screen_width() < self.app_width / divisor or sg.get_screen_height() < self.app_height / divisor):
70− divisor = divisor * 2
71− super(TestLockscreens, self).setUp("%sx%s" % (self.app_width / divisor, self.app_height / divisor), "%s" % (self.grid_unit_px / divisor))
72−
73− dash = self.main_window.get_dash()
74− self.assertThat(dash.showScopeOnLoaded, Eventually(Equals(""), timeout=30))
75−
76− def test_unlock(self):
77− self.unlock_greeter()
78−
79− pinPadLoader = self.main_window.get_pinPadLoader();
80− self.assertThat(pinPadLoader.progress, Eventually(Equals(1)))
81− lockscreen = self.main_window.get_lockscreen();
82− self.assertThat(lockscreen.shown, Eventually(Equals(True)))
83−
84− if self.lightdm_mock == "single-pin":
85− self.touch.tap_object(self.main_window.get_pinPadButton(1))
86− self.touch.tap_object(self.main_window.get_pinPadButton(2))
87− self.touch.tap_object(self.main_window.get_pinPadButton(3))
88− self.touch.tap_object(self.main_window.get_pinPadButton(4))
89− self.assertThat(lockscreen.shown, Eventually(Equals(False)))
90− else:
91− pinentryField = self.main_window.get_pinentryField()
92− self.touch.tap_object(pinentryField)
93− self.keyboard.type("password\n")
94− self.assertThat(lockscreen.shown, Eventually(Equals(False)))
95−
96− def test_unlock_wrong(self):
97− self.unlock_greeter()
98−
99− pinPadLoader = self.main_window.get_pinPadLoader();
100− self.assertThat(pinPadLoader.progress, Eventually(Equals(1)))
101− lockscreen = self.main_window.get_lockscreen();
102− self.assertThat(lockscreen.shown, Eventually(Equals(True)))
103− pinentryField = self.main_window.get_pinentryField()
104−
105− if self.lightdm_mock == "single-pin":
106− self.touch.tap_object(self.main_window.get_pinPadButton(4))
107− self.touch.tap_object(self.main_window.get_pinPadButton(3))
108− self.touch.tap_object(self.main_window.get_pinPadButton(2))
109− self.assertThat(pinentryField.text, Eventually(Equals("432")))
110− self.touch.tap_object(self.main_window.get_pinPadButton(1))
111−
112− self.assertThat(pinentryField.text, Eventually(Equals("")))
113− self.assertThat(lockscreen.shown, Eventually(Equals(True)))
114− else:
115− self.touch.tap_object(pinentryField)
116− self.keyboard.type("foobar\n")
117− self.assertThat(pinentryField.text, Eventually(Equals("")))
118− self.assertThat(lockscreen.shown, Eventually(Equals(True)))
tests/mocks/Unity/Indicators/CMakeLists.txt+1
@@ -12,6 +12,7 @@ include_directories(
12set(IndicatorsFakeQml_SOURCES
12set(IndicatorsFakeQml_SOURCES
13 fakeplugin.cpp
13 fakeplugin.cpp
14 ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/indicators.h
14 ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/indicators.h
15+ ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/menucontentactivator.cpp
15)
16)
16
17
17set(IndicatorsFakeQml_RESOURCES
18set(IndicatorsFakeQml_RESOURCES
tests/mocks/Unity/Indicators/fakeplugin.cpp+4
@@ -24,11 +24,15 @@
24
24
25// local
25// local
26#include "indicators.h"
26#include "indicators.h"
27+#include "menucontentactivator.h"
27
28
28void IndicatorsFakePlugin::registerTypes(const char * uri)
29void IndicatorsFakePlugin::registerTypes(const char * uri)
29{
30{
30 Q_INIT_RESOURCE(indicators_fake);
31 Q_INIT_RESOURCE(indicators_fake);
31
32
33+ qmlRegisterType<MenuContentActivator>(uri, 0, 1, "MenuContentActivator");
34+
35+ qmlRegisterUncreatableType<MenuContentState>(uri, 0, 1, "MenuContentState", "Can't create MenuContentState class");
32 qmlRegisterUncreatableType<ActionState>(uri, 0, 1, "ActionState", "Can't create ActionState class");
36 qmlRegisterUncreatableType<ActionState>(uri, 0, 1, "ActionState", "Can't create ActionState class");
33 qmlRegisterUncreatableType<NetworkActionState>(uri, 0, 1, "NetworkActionState", "Can't create NetworkActionState class");
37 qmlRegisterUncreatableType<NetworkActionState>(uri, 0, 1, "NetworkActionState", "Can't create NetworkActionState class");
34 qmlRegisterUncreatableType<NetworkConnection>(uri, 0, 1, "NetworkConnection", "Can't create NetworkConnection class");
38 qmlRegisterUncreatableType<NetworkConnection>(uri, 0, 1, "NetworkConnection", "Can't create NetworkConnection class");
tests/mocks/Unity/Notifications/CMakeLists.txt−4
@@ -9,7 +9,3 @@ add_custom_target(NotificationsQmlFiles ALL
9 COMMAND cp ${QMLFILES} ${CMAKE_CURRENT_BINARY_DIR}
9 COMMAND cp ${QMLFILES} ${CMAKE_CURRENT_BINARY_DIR}
10 DEPENDS ${QMLFILES}
10 DEPENDS ${QMLFILES}
11)
11)
12−
13−install(FILES ${QMLFILES}
14− DESTINATION ${SHELL_PRIVATE_LIBDIR}/qml/mocks/Unity/Notifications
15− )
tests/mocks/Unity/fake_categories.cpp+5−2
@@ -32,10 +32,11 @@ Categories::Categories(QObject* parent)
32 m_roles[Categories::RoleName] = "name";
32 m_roles[Categories::RoleName] = "name";
33 m_roles[Categories::RoleIcon] = "icon";
33 m_roles[Categories::RoleIcon] = "icon";
34 m_roles[Categories::RoleRenderer] = "renderer";
34 m_roles[Categories::RoleRenderer] = "renderer";
35− m_roles[Categories::RoleContentType] = "content_type";
35+ m_roles[Categories::RoleContentType] = "contentType";
36 m_roles[Categories::RoleHints] = "hints";
36 m_roles[Categories::RoleHints] = "hints";
37 m_roles[Categories::RoleResults] = "results";
37 m_roles[Categories::RoleResults] = "results";
38 m_roles[Categories::RoleCount] = "count";
38 m_roles[Categories::RoleCount] = "count";
39+ m_roles[Categories::RoleCategoryIndex] = "categoryIndex";
39
40
40 // TODO This should not be needed but accumulatting the count changes
41 // TODO This should not be needed but accumulatting the count changes
41 // makes the visualization more stable and also makes crashes on fast
42 // makes the visualization more stable and also makes crashes on fast
@@ -154,7 +155,7 @@ Categories::data(const QModelIndex& index, int role) const
154
155
155 switch (role) {
156 switch (role) {
156 case RoleId:
157 case RoleId:
157− return QVariant::fromValue(index.row());
158+ return DeeListModel::data(index, 0); //ID
158 case RoleName:
159 case RoleName:
159 return DeeListModel::data(index, 1); //DISPLAY_NAME
160 return DeeListModel::data(index, 1); //DISPLAY_NAME
160 case RoleIcon:
161 case RoleIcon:
@@ -172,6 +173,8 @@ Categories::data(const QModelIndex& index, int role) const
172 return QVariant::fromValue(getFilter(index.row()));
173 return QVariant::fromValue(getFilter(index.row()));
173 case RoleCount:
174 case RoleCount:
174 return QVariant::fromValue(getFilter(index.row())->rowCount());
175 return QVariant::fromValue(getFilter(index.row())->rowCount());
176+ case RoleCategoryIndex:
177+ return QVariant::fromValue(index.row());
175 default:
178 default:
176 return QVariant();
179 return QVariant();
177 }
180 }
tests/mocks/Unity/fake_categories.h+2−1
@@ -41,7 +41,8 @@ public:
41 RoleContentType,
41 RoleContentType,
42 RoleHints,
42 RoleHints,
43 RoleResults,
43 RoleResults,
44− RoleCount
44+ RoleCount,
45+ RoleCategoryIndex
45 };
46 };
46
47
47 QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
48 QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
tests/plugins/Unity/Indicators/CMakeLists.txt+1
@@ -24,4 +24,5 @@ endmacro(run_tests)
24run_tests(
24run_tests(
25 indicatorsmanagertest
25 indicatorsmanagertest
26 indicatorsmodeltest
26 indicatorsmodeltest
27+ menucontentactivatortest
27 )
28 )
tests/plugins/Unity/Indicators/menucontentactivatortest.cppnew file+303
@@ -0,0 +1,303 @@
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+ * Nick Dedekind <nick.dedekind@canonical.com>
18+ */
19+
20+#include "menucontentactivator.h"
21+#include "paths.h"
22+
23+#include <QtTest>
24+#include <QDebug>
25+
26+class FakeTimer : public UnityIndicators::AbstractTimer
27+{
28+ Q_OBJECT
29+public:
30+ FakeTimer(QObject *parent = 0)
31+ : UnityIndicators::AbstractTimer(parent)
32+ {}
33+
34+ virtual int interval() const { return m_duration; }
35+ virtual void setInterval(int msecs) { m_duration = msecs; }
36+
37+ void emitTimeout() {
38+ if (isRunning()) {
39+ Q_EMIT timeout();
40+ }
41+ }
42+private:
43+ int m_duration;
44+};
45+
46+
47+class MenuConentActivatorTest : public QObject
48+{
49+ Q_OBJECT
50+private Q_SLOTS:
51+ void init() // called right before each and every test function is executed
52+ {
53+ m_fakeTimeSource = new FakeTimer(this);
54+ m_deltas.clear();
55+ }
56+ void cleanup() // called right after each and every test function is executed
57+ {
58+ delete m_fakeTimeSource;
59+ m_fakeTimeSource = 0;
60+ }
61+
62+ /*
63+ * Tests the ordering of activation
64+ */
65+ void testContentActiveChange()
66+ {
67+ MenuContentActivator activator;
68+ activator.setContentTimer(m_fakeTimeSource);
69+ activator.setCount(10);
70+ activator.setBaseIndex(5);
71+ activator.restart();
72+
73+ QCOMPARE(activator.isMenuContentActive(4), false);
74+ QCOMPARE(activator.isMenuContentActive(5), true); // always active at base index.
75+ QCOMPARE(activator.isMenuContentActive(6), false);
76+
77+ m_fakeTimeSource->emitTimeout();
78+ QCOMPARE(activator.isMenuContentActive(4), false);
79+ QCOMPARE(activator.isMenuContentActive(5), true);
80+ QCOMPARE(activator.isMenuContentActive(6), true);
81+ QCOMPARE(activator.isMenuContentActive(7), false);
82+
83+ m_fakeTimeSource->emitTimeout();
84+ QCOMPARE(activator.isMenuContentActive(3), false);
85+ QCOMPARE(activator.isMenuContentActive(4), true);
86+ QCOMPARE(activator.isMenuContentActive(5), true);
87+ QCOMPARE(activator.isMenuContentActive(6), true);
88+ QCOMPARE(activator.isMenuContentActive(7), false);
89+
90+ m_fakeTimeSource->emitTimeout();
91+ QCOMPARE(activator.isMenuContentActive(3), false);
92+ QCOMPARE(activator.isMenuContentActive(4), true);
93+ QCOMPARE(activator.isMenuContentActive(5), true);
94+ QCOMPARE(activator.isMenuContentActive(6), true);
95+ QCOMPARE(activator.isMenuContentActive(7), true);
96+ QCOMPARE(activator.isMenuContentActive(8), false);
97+
98+ m_fakeTimeSource->emitTimeout();
99+ QCOMPARE(activator.isMenuContentActive(2), false);
100+ QCOMPARE(activator.isMenuContentActive(3), true);
101+ QCOMPARE(activator.isMenuContentActive(4), true);
102+ QCOMPARE(activator.isMenuContentActive(5), true);
103+ QCOMPARE(activator.isMenuContentActive(6), true);
104+ QCOMPARE(activator.isMenuContentActive(7), true);
105+ QCOMPARE(activator.isMenuContentActive(8), false);
106+
107+ m_fakeTimeSource->emitTimeout();
108+ QCOMPARE(activator.isMenuContentActive(2), false);
109+ QCOMPARE(activator.isMenuContentActive(3), true);
110+ QCOMPARE(activator.isMenuContentActive(4), true);
111+ QCOMPARE(activator.isMenuContentActive(5), true);
112+ QCOMPARE(activator.isMenuContentActive(6), true);
113+ QCOMPARE(activator.isMenuContentActive(7), true);
114+ QCOMPARE(activator.isMenuContentActive(8), true);
115+ QCOMPARE(activator.isMenuContentActive(9), false);
116+
117+ m_fakeTimeSource->emitTimeout();
118+ QCOMPARE(activator.isMenuContentActive(1), false);
119+ QCOMPARE(activator.isMenuContentActive(2), true);
120+ QCOMPARE(activator.isMenuContentActive(3), true);
121+ QCOMPARE(activator.isMenuContentActive(4), true);
122+ QCOMPARE(activator.isMenuContentActive(5), true);
123+ QCOMPARE(activator.isMenuContentActive(6), true);
124+ QCOMPARE(activator.isMenuContentActive(7), true);
125+ QCOMPARE(activator.isMenuContentActive(8), true);
126+ QCOMPARE(activator.isMenuContentActive(9), false);
127+
128+ m_fakeTimeSource->emitTimeout();
129+ QCOMPARE(activator.isMenuContentActive(1), false);
130+ QCOMPARE(activator.isMenuContentActive(2), true);
131+ QCOMPARE(activator.isMenuContentActive(3), true);
132+ QCOMPARE(activator.isMenuContentActive(4), true);
133+ QCOMPARE(activator.isMenuContentActive(5), true);
134+ QCOMPARE(activator.isMenuContentActive(6), true);
135+ QCOMPARE(activator.isMenuContentActive(7), true);
136+ QCOMPARE(activator.isMenuContentActive(8), true);
137+ QCOMPARE(activator.isMenuContentActive(9), true);
138+ QCOMPARE(activator.isMenuContentActive(10), false);
139+
140+ m_fakeTimeSource->emitTimeout();
141+ QCOMPARE(activator.isMenuContentActive(0), false);
142+ QCOMPARE(activator.isMenuContentActive(1), true);
143+ QCOMPARE(activator.isMenuContentActive(2), true);
144+ QCOMPARE(activator.isMenuContentActive(3), true);
145+ QCOMPARE(activator.isMenuContentActive(4), true);
146+ QCOMPARE(activator.isMenuContentActive(5), true);
147+ QCOMPARE(activator.isMenuContentActive(6), true);
148+ QCOMPARE(activator.isMenuContentActive(7), true);
149+ QCOMPARE(activator.isMenuContentActive(8), true);
150+ QCOMPARE(activator.isMenuContentActive(9), true);
151+ QCOMPARE(activator.isMenuContentActive(10), false);
152+
153+ m_fakeTimeSource->emitTimeout();
154+ QCOMPARE(activator.isMenuContentActive(-1), false);
155+ QCOMPARE(activator.isMenuContentActive(0), true);
156+ QCOMPARE(activator.isMenuContentActive(1), true);
157+ QCOMPARE(activator.isMenuContentActive(2), true);
158+ QCOMPARE(activator.isMenuContentActive(3), true);
159+ QCOMPARE(activator.isMenuContentActive(4), true);
160+ QCOMPARE(activator.isMenuContentActive(5), true);
161+ QCOMPARE(activator.isMenuContentActive(6), true);
162+ QCOMPARE(activator.isMenuContentActive(7), true);
163+ QCOMPARE(activator.isMenuContentActive(8), true);
164+ QCOMPARE(activator.isMenuContentActive(9), true);
165+ QCOMPARE(activator.isMenuContentActive(10), false);
166+ }
167+
168+ /*
169+ * Tests the delta calculation for each timeout.
170+ */
171+ void testDelta()
172+ {
173+ MenuContentActivator activator;
174+ activator.setContentTimer(m_fakeTimeSource);
175+ activator.setCount(9);
176+ activator.setBaseIndex(3);
177+
178+ connect(&activator, &MenuContentActivator::deltaChanged,
179+ this, &MenuConentActivatorTest::onDeltaChange);
180+ activator.restart();
181+
182+ QCOMPARE(m_deltas, QList<int>()); // empty
183+ QCOMPARE(getIndexList(&activator), QList<int>()); // empty
184+
185+ m_fakeTimeSource->emitTimeout();
186+ QCOMPARE(m_deltas, QList<int>() << 1);
187+ QCOMPARE(getIndexList(&activator), QList<int>() << 4);
188+
189+ m_fakeTimeSource->emitTimeout();
190+ QCOMPARE(m_deltas, QList<int>() << 1 << -1);
191+ QCOMPARE(getIndexList(&activator), QList<int>() << 4 << 2);
192+
193+ m_fakeTimeSource->emitTimeout();
194+ QCOMPARE(m_deltas, QList<int>() << 1 << -1 << 2);
195+ QCOMPARE(getIndexList(&activator), QList<int>() << 4 << 2 << 5);
196+
197+ m_fakeTimeSource->emitTimeout();
198+ QCOMPARE(m_deltas, QList<int>() << 1 << -1 << 2 << -2);
199+ QCOMPARE(getIndexList(&activator), QList<int>() << 4 << 2 << 5 << 1);
200+
201+ m_fakeTimeSource->emitTimeout();
202+ QCOMPARE(m_deltas, QList<int>() << 1 << -1 << 2 << -2 << 3);
203+ QCOMPARE(getIndexList(&activator), QList<int>() << 4 << 2 << 5 << 1 << 6);
204+
205+ m_fakeTimeSource->emitTimeout();
206+ QCOMPARE(m_deltas, QList<int>() << 1 << -1 << 2 << -2 << 3 << -3);
207+ QCOMPARE(getIndexList(&activator), QList<int>() << 4 << 2 << 5 << 1 << 6 << 0);
208+
209+ m_fakeTimeSource->emitTimeout();
210+ QCOMPARE(m_deltas, QList<int>() << 1 << -1 << 2 << -2 << 3 << -3 << 4);
211+ QCOMPARE(getIndexList(&activator), QList<int>() << 4 << 2 << 5 << 1 << 6 << 0 << 7);
212+
213+ m_fakeTimeSource->emitTimeout();
214+ QCOMPARE(m_deltas, QList<int>() << 1 << -1 << 2 << -2 << 3 << -3 << 4 << 5);
215+ QCOMPARE(getIndexList(&activator), QList<int>() << 4 << 2 << 5 << 1 << 6 << 0 << 7 << 8);
216+
217+ m_fakeTimeSource->emitTimeout();
218+ QCOMPARE(m_deltas, QList<int>() << 1 << -1 << 2 << -2 << 3 << -3 << 4 << 5);
219+ QCOMPARE(getIndexList(&activator), QList<int>() << 4 << 2 << 5 << 1 << 6 << 0 << 7 << 8);
220+ }
221+
222+ /*
223+ * Tests that changing the base index re-prioritizes the activation
224+ * around the base index.
225+ */
226+ void testBaseIndexChange()
227+ {
228+ MenuContentActivator activator;
229+ activator.setContentTimer(m_fakeTimeSource);
230+ activator.setCount(12);
231+ activator.setBaseIndex(5);
232+ activator.restart();
233+
234+ m_fakeTimeSource->emitTimeout();
235+ m_fakeTimeSource->emitTimeout();
236+ QCOMPARE(activator.isMenuContentActive(3), false);
237+ QCOMPARE(activator.isMenuContentActive(4), true);
238+ QCOMPARE(activator.isMenuContentActive(5), true); // always active at base index.
239+ QCOMPARE(activator.isMenuContentActive(6), true);
240+ QCOMPARE(activator.isMenuContentActive(7), false);
241+
242+ activator.setBaseIndex(8);
243+ QCOMPARE(activator.isMenuContentActive(3), false);
244+ QCOMPARE(activator.isMenuContentActive(4), true);
245+ QCOMPARE(activator.isMenuContentActive(5), true);
246+ QCOMPARE(activator.isMenuContentActive(6), true);
247+ QCOMPARE(activator.isMenuContentActive(7), false);
248+ QCOMPARE(activator.isMenuContentActive(8), true);
249+ QCOMPARE(activator.isMenuContentActive(9), false);
250+
251+ m_fakeTimeSource->emitTimeout();
252+ QCOMPARE(activator.isMenuContentActive(3), false);
253+ QCOMPARE(activator.isMenuContentActive(4), true);
254+ QCOMPARE(activator.isMenuContentActive(5), true);
255+ QCOMPARE(activator.isMenuContentActive(6), true);
256+ QCOMPARE(activator.isMenuContentActive(7), false);
257+ QCOMPARE(activator.isMenuContentActive(8), true);
258+ QCOMPARE(activator.isMenuContentActive(9), true);
259+ QCOMPARE(activator.isMenuContentActive(10), false);
260+
261+ m_fakeTimeSource->emitTimeout();
262+ QCOMPARE(activator.isMenuContentActive(3), false);
263+ QCOMPARE(activator.isMenuContentActive(4), true);
264+ QCOMPARE(activator.isMenuContentActive(5), true);
265+ QCOMPARE(activator.isMenuContentActive(6), true);
266+ QCOMPARE(activator.isMenuContentActive(7), true);
267+ QCOMPARE(activator.isMenuContentActive(8), true);
268+ QCOMPARE(activator.isMenuContentActive(9), true);
269+ QCOMPARE(activator.isMenuContentActive(10), false);
270+
271+ m_fakeTimeSource->emitTimeout();
272+ QCOMPARE(activator.isMenuContentActive(3), false);
273+ QCOMPARE(activator.isMenuContentActive(4), true);
274+ QCOMPARE(activator.isMenuContentActive(5), true);
275+ QCOMPARE(activator.isMenuContentActive(6), true);
276+ QCOMPARE(activator.isMenuContentActive(7), true);
277+ QCOMPARE(activator.isMenuContentActive(8), true);
278+ QCOMPARE(activator.isMenuContentActive(9), true);
279+ QCOMPARE(activator.isMenuContentActive(10), true);
280+ QCOMPARE(activator.isMenuContentActive(11), false);
281+ }
282+
283+ void onDeltaChange(int delta)
284+ {
285+ m_deltas << delta;
286+ }
287+
288+private:
289+ QList<int> getIndexList(MenuContentActivator* activator)
290+ {
291+ QList<int> list;
292+ Q_FOREACH(int delta, m_deltas) {
293+ list << (activator->baseIndex() + delta);
294+ }
295+ return list;
296+ }
297+
298+ FakeTimer* m_fakeTimeSource;
299+ QList<int> m_deltas;
300+};
301+
302+QTEST_MAIN(MenuConentActivatorTest)
303+#include "menucontentactivatortest.moc"
tests/qmltests/Dash/tst_Dash.qml+4−4
@@ -38,8 +38,8 @@ Item {
38 scopeDelegateMapping: {
38 scopeDelegateMapping: {
39 "MockScope1": "../tests/qmltests/Dash/qml/fake_scopeView1.qml",
39 "MockScope1": "../tests/qmltests/Dash/qml/fake_scopeView1.qml",
40 "MockScope2": "../tests/qmltests/Dash/qml/fake_scopeView2.qml",
40 "MockScope2": "../tests/qmltests/Dash/qml/fake_scopeView2.qml",
41− "MockScope3": "../tests/qmltests/Dash/qml/fake_scopeView3.qml",
41+ "home.scope": "../tests/qmltests/Dash/qml/fake_scopeView3.qml",
42− "MockScope4": "../tests/qmltests/Dash/qml/fake_scopeView4.qml"
42+ "applications.scope": "../tests/qmltests/Dash/qml/fake_scopeView4.qml"
43 }
43 }
44 genericScope: "../tests/qmltests/Dash/qml/fake_generic_scopeView.qml"
44 genericScope: "../tests/qmltests/Dash/qml/fake_generic_scopeView.qml"
45 }
45 }
@@ -69,8 +69,8 @@ Item {
69 return [
69 return [
70 { tag: "MockScope1", visualIndex: 0, shouldBeVisible: true },
70 { tag: "MockScope1", visualIndex: 0, shouldBeVisible: true },
71 { tag: "MockScope2", visualIndex: -1, shouldBeVisible: false },
71 { tag: "MockScope2", visualIndex: -1, shouldBeVisible: false },
72− { tag: "MockScope3", visualIndex: 1, shouldBeVisible: true },
72+ { tag: "home.scope", visualIndex: 1, shouldBeVisible: true },
73− { tag: "MockScope4", visualIndex: 2, shouldBeVisible: true },
73+ { tag: "applications.scope", visualIndex: 2, shouldBeVisible: true },
74 { tag: "MockScope5", visualIndex: 3, shouldBeVisible: true },
74 { tag: "MockScope5", visualIndex: 3, shouldBeVisible: true },
75 ]
75 ]
76 }
76 }
tests/qmltests/Dash/tst_DashContent.qml+12−12
@@ -31,8 +31,8 @@ Item {
31 property var scopeStatus: {
31 property var scopeStatus: {
32 'MockScope1': { 'movementStarted': 0, 'positionedAtBeginning': 0 },
32 'MockScope1': { 'movementStarted': 0, 'positionedAtBeginning': 0 },
33 'MockScope2': { 'movementStarted': 0, 'positionedAtBeginning': 0 },
33 'MockScope2': { 'movementStarted': 0, 'positionedAtBeginning': 0 },
34− 'MockScope3': { 'movementStarted': 0, 'positionedAtBeginning': 0 },
34+ 'home.scope': { 'movementStarted': 0, 'positionedAtBeginning': 0 },
35− 'MockScope4': { 'movementStarted': 0, 'positionedAtBeginning': 0 },
35+ 'applications.scope': { 'movementStarted': 0, 'positionedAtBeginning': 0 },
36 'MockScope5': { 'movementStarted': 0, 'positionedAtBeginning': 0 }
36 'MockScope5': { 'movementStarted': 0, 'positionedAtBeginning': 0 }
37 }
37 }
38
38
@@ -55,8 +55,8 @@ Item {
55 scopeDelegateMapping: {
55 scopeDelegateMapping: {
56 "MockScope1": "../tests/qmltests/Dash/qml/fake_scopeView1.qml",
56 "MockScope1": "../tests/qmltests/Dash/qml/fake_scopeView1.qml",
57 "MockScope2": "../tests/qmltests/Dash/qml/fake_scopeView2.qml",
57 "MockScope2": "../tests/qmltests/Dash/qml/fake_scopeView2.qml",
58− "MockScope3": "../tests/qmltests/Dash/qml/fake_scopeView3.qml",
58+ "home.scope": "../tests/qmltests/Dash/qml/fake_scopeView3.qml",
59− "MockScope4": "../tests/qmltests/Dash/qml/fake_scopeView4.qml"
59+ "applications.scope": "../tests/qmltests/Dash/qml/fake_scopeView4.qml"
60 }
60 }
61 genericScope: "../tests/qmltests/Dash/qml/fake_generic_scopeView.qml"
61 genericScope: "../tests/qmltests/Dash/qml/fake_generic_scopeView.qml"
62 }
62 }
@@ -68,11 +68,11 @@ Item {
68 scopeStatus["MockScope2"].movementStarted = 0;
68 scopeStatus["MockScope2"].movementStarted = 0;
69 scopeStatus["MockScope2"].positionedAtBeginning = 0;
69 scopeStatus["MockScope2"].positionedAtBeginning = 0;
70
70
71− scopeStatus["MockScope3"].movementStarted = 0;
71+ scopeStatus["home.scope"].movementStarted = 0;
72− scopeStatus["MockScope3"].positionedAtBeginning = 0;
72+ scopeStatus["home.scope"].positionedAtBeginning = 0;
73
73
74− scopeStatus["MockScope4"].movementStarted = 0;
74+ scopeStatus["applications.scope"].movementStarted = 0;
75− scopeStatus["MockScope4"].positionedAtBeginning = 0;
75+ scopeStatus["applications.scope"].positionedAtBeginning = 0;
76
76
77 scopeStatus["MockScope5"].movementStarted = 0;
77 scopeStatus["MockScope5"].movementStarted = 0;
78 scopeStatus["MockScope5"].positionedAtBeginning = 0;
78 scopeStatus["MockScope5"].positionedAtBeginning = 0;
@@ -127,8 +127,8 @@ Item {
127 compare(movementStartedSpy.count, 1, "DashContent should have emitted movementStarted signal when content list did.");
127 compare(movementStartedSpy.count, 1, "DashContent should have emitted movementStarted signal when content list did.");
128 compare(scopeStatus["MockScope1"].movementStarted, 1, "MockScope1 should have emitted movementStarted signal when content list did.");
128 compare(scopeStatus["MockScope1"].movementStarted, 1, "MockScope1 should have emitted movementStarted signal when content list did.");
129 compare(scopeStatus["MockScope2"].movementStarted, 1, "MockScope2 should have emitted movementStarted signal when content list did.");
129 compare(scopeStatus["MockScope2"].movementStarted, 1, "MockScope2 should have emitted movementStarted signal when content list did.");
130− compare(scopeStatus["MockScope3"].movementStarted, 1, "MockScope3 should have emitted movementStarted signal when content list did.");
130+ compare(scopeStatus["home.scope"].movementStarted, 1, "home.scope should have emitted movementStarted signal when content list did.");
131− compare(scopeStatus["MockScope4"].movementStarted, 1, "MockScope4 should have emitted movementStarted signal when content list did.");
131+ compare(scopeStatus["applications.scope"].movementStarted, 1, "applications.scope should have emitted movementStarted signal when content list did.");
132 compare(scopeStatus["MockScope5"].movementStarted, 1, "MockScope5 should have emitted movementStarted signal when content list did.");
132 compare(scopeStatus["MockScope5"].movementStarted, 1, "MockScope5 should have emitted movementStarted signal when content list did.");
133 }
133 }
134
134
@@ -140,8 +140,8 @@ Item {
140 dashContent.positionedAtBeginning();
140 dashContent.positionedAtBeginning();
141 compare(scopeStatus["MockScope1"].positionedAtBeginning, 1, "MockScope1 should have emitted positionedAtBeginning signal when DashContent did.");
141 compare(scopeStatus["MockScope1"].positionedAtBeginning, 1, "MockScope1 should have emitted positionedAtBeginning signal when DashContent did.");
142 compare(scopeStatus["MockScope2"].positionedAtBeginning, 1, "MockScope2 should have emitted positionedAtBeginning signal when DashContent did.");
142 compare(scopeStatus["MockScope2"].positionedAtBeginning, 1, "MockScope2 should have emitted positionedAtBeginning signal when DashContent did.");
143− compare(scopeStatus["MockScope3"].positionedAtBeginning, 1, "MockScope3 should have emitted positionedAtBeginning signal when DashContent did.");
143+ compare(scopeStatus["home.scope"].positionedAtBeginning, 1, "home.scope should have emitted positionedAtBeginning signal when DashContent did.");
144− compare(scopeStatus["MockScope4"].positionedAtBeginning, 1, "MockScope4 should have emitted positionedAtBeginning signal when DashContent did.");
144+ compare(scopeStatus["applications.scope"].positionedAtBeginning, 1, "applications.scope should have emitted positionedAtBeginning signal when DashContent did.");
145 compare(scopeStatus["MockScope5"].positionedAtBeginning, 1, "MockScope5 should have emitted positionedAtBeginning signal when DashContent did.");
145 compare(scopeStatus["MockScope5"].positionedAtBeginning, 1, "MockScope5 should have emitted positionedAtBeginning signal when DashContent did.");
146 }
146 }
147
147
tests/qmltests/Notifications/tst_Notifications.qml+20−9
@@ -48,6 +48,7 @@ Row {
48 function addSnapDecisionNotification() {
48 function addSnapDecisionNotification() {
49 var n = {
49 var n = {
50 type: Notification.SnapDecision,
50 type: Notification.SnapDecision,
51+ hints: Notification.ButtonTint,
51 summary: "Tom Ato",
52 summary: "Tom Ato",
52 body: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.",
53 body: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.",
53 icon: "../graphics/avatars/funky.png",
54 icon: "../graphics/avatars/funky.png",
@@ -189,8 +190,9 @@ Row {
189 function test_NotificationRenderer_data() {
190 function test_NotificationRenderer_data() {
190 return [
191 return [
191 {
192 {
192− tag: "Snap Decision with secondary icon",
193+ tag: "Snap Decision with secondary icon and button-tint",
193 type: Notification.SnapDecision,
194 type: Notification.SnapDecision,
195+ hints: Notification.ButtonTint,
194 summary: "Tom Ato",
196 summary: "Tom Ato",
195 body: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.",
197 body: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.",
196 icon: "../graphics/avatars/funky.png",
198 icon: "../graphics/avatars/funky.png",
@@ -205,7 +207,8 @@ Row {
205 interactiveAreaEnabled: false,
207 interactiveAreaEnabled: false,
206 iconVisible: true,
208 iconVisible: true,
207 secondaryIconVisible: true,
209 secondaryIconVisible: true,
208− buttonRowVisible: true
210+ buttonRowVisible: true,
211+ buttonTinted: true
209 },
212 },
210 {
213 {
211 tag: "Ephemeral notification - icon-summary layout",
214 tag: "Ephemeral notification - icon-summary layout",
@@ -220,7 +223,8 @@ Row {
220 interactiveAreaEnabled: false,
223 interactiveAreaEnabled: false,
221 iconVisible: false,
224 iconVisible: false,
222 secondaryIconVisible: true,
225 secondaryIconVisible: true,
223− buttonRowVisible: false
226+ buttonRowVisible: false,
227+ buttonTinted: false
224 },
228 },
225 {
229 {
226 tag: "Ephemeral notification - check suppression of secondary icon for icon-summary layout",
230 tag: "Ephemeral notification - check suppression of secondary icon for icon-summary layout",
@@ -235,7 +239,8 @@ Row {
235 interactiveAreaEnabled: false,
239 interactiveAreaEnabled: false,
236 iconVisible: false,
240 iconVisible: false,
237 secondaryIconVisible: true,
241 secondaryIconVisible: true,
238− buttonRowVisible: false
242+ buttonRowVisible: false,
243+ buttonTinted: false
239 },
244 },
240 {
245 {
241 tag: "Interactive notification",
246 tag: "Interactive notification",
@@ -250,10 +255,11 @@ Row {
250 interactiveAreaEnabled: true,
255 interactiveAreaEnabled: true,
251 iconVisible: true,
256 iconVisible: true,
252 secondaryIconVisible: false,
257 secondaryIconVisible: false,
253− buttonRowVisible: false
258+ buttonRowVisible: false,
259+ buttonTinted: false
254 },
260 },
255 {
261 {
256− tag: "Snap Decision without secondary icon",
262+ tag: "Snap Decision without secondary icon and no button-tint",
257 type: Notification.SnapDecision,
263 type: Notification.SnapDecision,
258 summary: "Bro Coly",
264 summary: "Bro Coly",
259 body: "At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.",
265 body: "At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.",
@@ -266,7 +272,8 @@ Row {
266 interactiveAreaEnabled: false,
272 interactiveAreaEnabled: false,
267 iconVisible: true,
273 iconVisible: true,
268 secondaryIconVisible: false,
274 secondaryIconVisible: false,
269− buttonRowVisible: true
275+ buttonRowVisible: true,
276+ buttonTinted: false
270 },
277 },
271 {
278 {
272 tag: "Ephemeral notification",
279 tag: "Ephemeral notification",
@@ -281,7 +288,8 @@ Row {
281 interactiveAreaEnabled: false,
288 interactiveAreaEnabled: false,
282 iconVisible: true,
289 iconVisible: true,
283 secondaryIconVisible: true,
290 secondaryIconVisible: true,
284− buttonRowVisible: false
291+ buttonRowVisible: false,
292+ buttonTinted: false
285 }
293 }
286 ]
294 ]
287 }
295 }
@@ -356,6 +364,9 @@ Row {
356 actionSpy.clear()
364 actionSpy.clear()
357 }
365 }
358
366
367+ // check the tinting of the positive/right button
368+ verify(buttonAccept.gradient === data.buttonTinted ? UbuntuColors.orangeGradient : UbuntuColors.greyGradient, "button has the wrong color-tint")
369+
359 // click the positive/right button
370 // click the positive/right button
360 mouseClick(buttonAccept, buttonAccept.width / 2, buttonAccept.height / 2)
371 mouseClick(buttonAccept, buttonAccept.width / 2, buttonAccept.height / 2)
361 actionSpy.wait()
372 actionSpy.wait()
@@ -370,7 +381,7 @@ Row {
370 mouseClick(buttonCancel, buttonCancel.width / 2, buttonCancel.height / 2)
381 mouseClick(buttonCancel, buttonCancel.width / 2, buttonCancel.height / 2)
371 waitForRendering(notification)
382 waitForRendering(notification)
372 actionSpy.clear()
383 actionSpy.clear()
373− 
384+
374 // test the additional buttons
385 // test the additional buttons
375 for (var i = 2; i < data.actions.length; i++) {
386 for (var i = 2; i < data.actions.length; i++) {
376 waitForRendering(notification)
387 waitForRendering(notification)
tests/qmltests/Panel/tst_IndicatorItem.qml+10−16
@@ -34,34 +34,28 @@ Rectangle {
34 UT.UnityTestCase {
34 UT.UnityTestCase {
35 name: "IndicatorItem"
35 name: "IndicatorItem"
36
36
37− function init_test() {
38− }
39−
40 function test_dimmed() {
37 function test_dimmed() {
41− init_test()
38+ indicatorItem.dimmed = false;
42−
39+ tryCompareFunction(function(){return indicatorItem.opacity}, 1.0);
43− indicatorItem.dimmed = true
40+ indicatorItem.dimmed = true;
44− compare(indicatorItem.opacity > 0, true, "IndicatorItem opacity should not be 0")
41+ tryCompareFunction(function(){return indicatorItem.opacity < 1.0}, true);
45− compare(indicatorItem.opacity < 1, true, "IndicatorItem opacity should not be 1")
46 }
42 }
47
43
48 function test_highlight() {
44 function test_highlight() {
49− var itemHighlight = findChild(indicatorItem, "highlight")
45+ var itemHighlight = findChild(indicatorItem, "highlight");
50− verify(itemHighlight != undefined)
46+ verify(itemHighlight != undefined);
51
47
52 indicatorItem.highlighted = true;
48 indicatorItem.highlighted = true;
53− compare(itemHighlight.visible, true, "Indicator should be highlighted")
49+ compare(itemHighlight.visible, true, "Indicator should be highlighted");
54
50
55 indicatorItem.highlighted = false;
51 indicatorItem.highlighted = false;
56− compare(itemHighlight.visible, false, "Indicator should not be highlighted")
52+ compare(itemHighlight.visible, false, "Indicator should not be highlighted");
57 }
53 }
58
54
59 function test_empty() {
55 function test_empty() {
60− init_test()
56+ compare(indicatorItem.visible, false, "IndicatorItem should not be visible.");
61−
62− compare(indicatorItem.visible, false, "IndicatorItem should not be visible.")
63 indicatorItem.widgetSource = "qrc:/tests/indciators/qml/fake_menu_widget1.qml";
57 indicatorItem.widgetSource = "qrc:/tests/indciators/qml/fake_menu_widget1.qml";
64− tryCompare(indicatorItem, "visible", true)
58+ tryCompare(indicatorItem, "visible", true);
65 }
59 }
66 }
60 }
67}
61}
tests/qmltests/Panel/tst_IndicatorRow.qml+21−21
@@ -61,13 +61,13 @@ Item {
61
61
62 function test_set_current_item() {
62 function test_set_current_item() {
63 init_test();
63 init_test();
64− indicatorRow.setItem(0);
64+ indicatorRow.setCurrentItem(0);
65 compare(indicatorRow.indicatorsModel.get(indicatorRow.currentItemIndex).title, "Menu1", "Incorrect item at position 0");
65 compare(indicatorRow.indicatorsModel.get(indicatorRow.currentItemIndex).title, "Menu1", "Incorrect item at position 0");
66
66
67− indicatorRow.setItem(1);
67+ indicatorRow.setCurrentItem(1);
68 compare(indicatorRow.indicatorsModel.get(indicatorRow.currentItemIndex).title, "Menu2", "Incorrect item at position 1");
68 compare(indicatorRow.indicatorsModel.get(indicatorRow.currentItemIndex).title, "Menu2", "Incorrect item at position 1");
69
69
70− indicatorRow.setItem(2);
70+ indicatorRow.setCurrentItem(2);
71 compare(indicatorRow.indicatorsModel.get(indicatorRow.currentItemIndex).title, "Menu3", "Incorrect item at position 2");
71 compare(indicatorRow.indicatorsModel.get(indicatorRow.currentItemIndex).title, "Menu3", "Incorrect item at position 2");
72 }
72 }
73 }
73 }
@@ -83,13 +83,13 @@ Item {
83 function test_current_item_commit() {
83 function test_current_item_commit() {
84 init_test();
84 init_test();
85
85
86− indicatorRow.setItem(1);
86+ indicatorRow.setCurrentItem(1);
87 indicatorRow.state = "commit";
87 indicatorRow.state = "commit";
88− tryCompare(get_indicator_item_at(0), "y", -indicatorRow.height);
88+ tryCompare(get_indicator_item_at(0), "opacity", 0.0);
89− tryCompare(get_indicator_item_at(1), "y", 0);
89+ tryCompare(get_indicator_item_at(1), "opacity", 1.0);
90− tryCompare(get_indicator_item_at(2), "y", -indicatorRow.height);
90+ tryCompare(get_indicator_item_at(2), "opacity", 0.0);
91− tryCompare(get_indicator_item_at(3), "y", -indicatorRow.height);
91+ tryCompare(get_indicator_item_at(3), "opacity", 0.0);
92− tryCompare(get_indicator_item_at(4), "y", -indicatorRow.height);
92+ tryCompare(get_indicator_item_at(4), "opacity", 0.0);
93
93
94 }
94 }
95 }
95 }
@@ -106,7 +106,7 @@ Item {
106 init_test();
106 init_test();
107
107
108 indicatorRow.state = "initial";
108 indicatorRow.state = "initial";
109− indicatorRow.setItem(0);
109+ indicatorRow.setCurrentItem(0);
110
110
111 compare(indicatorRow.currentItem.highlighted, false, "Indicator should not highlight when in initial state");
111 compare(indicatorRow.currentItem.highlighted, false, "Indicator should not highlight when in initial state");
112 compare(get_indicator_item_at(1).highlighted, false, "Other indicators should not highlight when in initial state");
112 compare(get_indicator_item_at(1).highlighted, false, "Other indicators should not highlight when in initial state");
@@ -119,9 +119,9 @@ Item {
119 init_test();
119 init_test();
120
120
121 indicatorRow.state = "hint";
121 indicatorRow.state = "hint";
122− indicatorRow.setItem(0);
122+ indicatorRow.setCurrentItem(0);
123
123
124− compare(indicatorRow.currentItem.highlighted, false, "Indicator should not highlight when in hint state");
124+ compare(indicatorRow.currentItem.highlighted, true, "Indicator should highlight when in hint state");
125 compare(get_indicator_item_at(1).highlighted, false, "Other indicators should not highlight when in hint state");
125 compare(get_indicator_item_at(1).highlighted, false, "Other indicators should not highlight when in hint state");
126 compare(get_indicator_item_at(2).highlighted, false, "Other indicators should not highlight when in hint state");
126 compare(get_indicator_item_at(2).highlighted, false, "Other indicators should not highlight when in hint state");
127 compare(get_indicator_item_at(3).highlighted, false, "Other indicators should not highlight when in hint state");
127 compare(get_indicator_item_at(3).highlighted, false, "Other indicators should not highlight when in hint state");
@@ -132,7 +132,7 @@ Item {
132 init_test();
132 init_test();
133
133
134 indicatorRow.state = "reveal";
134 indicatorRow.state = "reveal";
135− indicatorRow.setItem(0);
135+ indicatorRow.setCurrentItem(0);
136
136
137 compare(indicatorRow.currentItem.highlighted, true, "Indicator should highlight when in reveal state");
137 compare(indicatorRow.currentItem.highlighted, true, "Indicator should highlight when in reveal state");
138 compare(get_indicator_item_at(1).highlighted, false, "Other indicators should not highlight when in commit state");
138 compare(get_indicator_item_at(1).highlighted, false, "Other indicators should not highlight when in commit state");
@@ -145,7 +145,7 @@ Item {
145 init_test();
145 init_test();
146
146
147 indicatorRow.state = "commit";
147 indicatorRow.state = "commit";
148− indicatorRow.setItem(0);
148+ indicatorRow.setCurrentItem(0);
149
149
150 compare(indicatorRow.currentItem.highlighted, true, "Indicator should highlight when in commit state");
150 compare(indicatorRow.currentItem.highlighted, true, "Indicator should highlight when in commit state");
151 compare(get_indicator_item_at(1).highlighted, false, "Other indicators should not highlight when in commit state");
151 compare(get_indicator_item_at(1).highlighted, false, "Other indicators should not highlight when in commit state");
@@ -158,7 +158,7 @@ Item {
158 init_test();
158 init_test();
159
159
160 indicatorRow.state = "locked";
160 indicatorRow.state = "locked";
161− indicatorRow.setItem(0);
161+ indicatorRow.setCurrentItem(0);
162
162
163 compare(indicatorRow.currentItem.highlighted, true, "Indicator should highlight when in locked state");
163 compare(indicatorRow.currentItem.highlighted, true, "Indicator should highlight when in locked state");
164 compare(get_indicator_item_at(1).highlighted, false, "Other indicators should not highlight when in locked state");
164 compare(get_indicator_item_at(1).highlighted, false, "Other indicators should not highlight when in locked state");
@@ -180,7 +180,7 @@ Item {
180 init_test();
180 init_test();
181
181
182 indicatorRow.state = "initial";
182 indicatorRow.state = "initial";
183− indicatorRow.setItem(0);
183+ indicatorRow.setCurrentItem(0);
184
184
185 compare(get_indicator_item_at(0).dimmed, false, "Current indicator should not dim when in intiial state");
185 compare(get_indicator_item_at(0).dimmed, false, "Current indicator should not dim when in intiial state");
186 compare(get_indicator_item_at(1).dimmed, false, "Other indicators should not dim when in initial state");
186 compare(get_indicator_item_at(1).dimmed, false, "Other indicators should not dim when in initial state");
@@ -193,9 +193,9 @@ Item {
193 init_test();
193 init_test();
194
194
195 indicatorRow.state = "hint";
195 indicatorRow.state = "hint";
196− indicatorRow.setItem(0);
196+ indicatorRow.setCurrentItem(0);
197
197
198− compare(get_indicator_item_at(0).dimmed, true, "Current indicator should dim when in hint state");
198+ compare(get_indicator_item_at(0).dimmed, false, "Current indicator should not dim when in hint state");
199 compare(get_indicator_item_at(1).dimmed, true, "Other indicators should dim when in hint state");
199 compare(get_indicator_item_at(1).dimmed, true, "Other indicators should dim when in hint state");
200 compare(get_indicator_item_at(2).dimmed, true, "Other indicators should dim when in hint state");
200 compare(get_indicator_item_at(2).dimmed, true, "Other indicators should dim when in hint state");
201 compare(get_indicator_item_at(3).dimmed, true, "Other indicators should dim when in hint state");
201 compare(get_indicator_item_at(3).dimmed, true, "Other indicators should dim when in hint state");
@@ -206,7 +206,7 @@ Item {
206 init_test();
206 init_test();
207
207
208 indicatorRow.state = "reveal";
208 indicatorRow.state = "reveal";
209− indicatorRow.setItem(0);
209+ indicatorRow.setCurrentItem(0);
210
210
211 compare(get_indicator_item_at(0).dimmed, false, "Current indicator should not dim when in reveal state");
211 compare(get_indicator_item_at(0).dimmed, false, "Current indicator should not dim when in reveal state");
212 compare(get_indicator_item_at(1).dimmed, true, "Other indicators should dim when in reveal state");
212 compare(get_indicator_item_at(1).dimmed, true, "Other indicators should dim when in reveal state");
@@ -219,7 +219,7 @@ Item {
219 init_test();
219 init_test();
220
220
221 indicatorRow.state = "commit";
221 indicatorRow.state = "commit";
222− indicatorRow.setItem(0);
222+ indicatorRow.setCurrentItem(0);
223
223
224 compare(get_indicator_item_at(0).dimmed, false, "Current indicator should not dim when in commit state");
224 compare(get_indicator_item_at(0).dimmed, false, "Current indicator should not dim when in commit state");
225 compare(get_indicator_item_at(1).dimmed, true, "Other indicators should dim when in commit state");
225 compare(get_indicator_item_at(1).dimmed, true, "Other indicators should dim when in commit state");
@@ -232,7 +232,7 @@ Item {
232 init_test();
232 init_test();
233
233
234 indicatorRow.state = "locked";
234 indicatorRow.state = "locked";
235− indicatorRow.setItem(0);
235+ indicatorRow.setCurrentItem(0);
236
236
237 compare(get_indicator_item_at(0).dimmed, false, "Current indicator should not dim when in locked state");
237 compare(get_indicator_item_at(0).dimmed, false, "Current indicator should not dim when in locked state");
238 compare(get_indicator_item_at(1).dimmed, true, "Other indicators should dim when in locked state");
238 compare(get_indicator_item_at(1).dimmed, true, "Other indicators should dim when in locked state");
tests/qmltests/Panel/tst_MenuContent.qml+4−4
@@ -77,15 +77,15 @@ Item {
77
77
78 function activate_next_content()
78 function activate_next_content()
79 {
79 {
80− if (menuContent.currentIndex == -1)
80+ if (menuContent.currentMenuIndex == -1)
81 activate_content(0);
81 activate_content(0);
82 else
82 else
83− activate_content((menuContent.currentIndex + 1) % indicatorsModel.count)
83+ activate_content((menuContent.currentMenuIndex + 1) % indicatorsModel.count)
84 }
84 }
85
85
86 function activate_content(index)
86 function activate_content(index)
87 {
87 {
88− menuContent.currentIndex = index
88+ menuContent.currentMenuIndex = index
89 }
89 }
90
90
91 function get_test_menu_objecName(index) {
91 function get_test_menu_objecName(index) {
@@ -197,7 +197,7 @@ Item {
197
197
198 for (var i = 0; i < 100; i++) {
198 for (var i = 0; i < 100; i++) {
199 activate_content(i % menu_count);
199 activate_content(i % menu_count);
200− compare(menuContent.currentIndex, i%menu_count);
200+ compare(menuContent.currentMenuIndex, i%menu_count);
201 }
201 }
202 wait(100);
202 wait(100);
203 }
203 }
tests/qmltests/Panel/tst_Panel.qml+83−43
@@ -30,11 +30,11 @@ Item {
30 width: units.gu(40)
30 width: units.gu(40)
31 height: units.gu(80)
31 height: units.gu(80)
32
32
33− property bool search_clicked: false
33+ property bool searchClicked: false
34
34
35 Connections {
35 Connections {
36 target: panel
36 target: panel
37− onSearchClicked: search_clicked = true
37+ onSearchClicked: searchClicked = true
38 }
38 }
39
39
40 Panel {
40 Panel {
@@ -54,27 +54,26 @@ Item {
54
54
55
55
56 function init() {
56 function init() {
57− search_clicked = false;
57+ searchClicked = false;
58 panel.indicators.hide();
58 panel.indicators.hide();
59 tryCompare(panel.indicators.hideAnimation, "running", false);
59 tryCompare(panel.indicators.hideAnimation, "running", false);
60 tryCompare(panel.indicators, "state", "initial");
60 tryCompare(panel.indicators, "state", "initial");
61 }
61 }
62
62
63 function get_indicator_item(index) {
63 function get_indicator_item(index) {
64− var row_repeater = findChild(panel.indicators, "rowRepeater");
64+ var rowRepeater = findChild(panel.indicators, "rowRepeater");
65− verify(row_repeater != undefined);
65+ verify(rowRepeater != undefined);
66− return row_repeater.itemAt(index);
66+ return rowRepeater.itemAt(index);
67 }
67 }
68
68
69 function get_indicator_item_position(index) {
69 function get_indicator_item_position(index) {
70+ var indicatorRow = findChild(panel.indicators, "indicatorRow");
71+ verify(indicatorRow != undefined);
70
72
71− var indicator_row = findChild(panel.indicators, "indicatorRow");
73+ var indicatorItem = get_indicator_item(index);
72− verify(indicator_row != undefined);
74+ verify(indicatorItem != undefined);
73−
74− var indicator_item = get_indicator_item(index);
75− verify(indicator_item != undefined);
76
75
77− return panel.mapFromItem(indicator_item, indicator_item.width/2, indicator_item.height/2);
76+ return panel.mapFromItem(indicatorItem, indicatorItem.width/2, indicatorItem.height/2);
78 }
77 }
79
78
80 // Pressing on the indicator panel should activate the indicator hints
79 // Pressing on the indicator panel should activate the indicator hints
@@ -85,9 +84,9 @@ Item {
85 // (switches between normal and fullscreen modes are animated)
84 // (switches between normal and fullscreen modes are animated)
86 tryCompare(panel.indicators, "y", 0);
85 tryCompare(panel.indicators, "y", 0);
87
86
88− var indicator_item_coord = get_indicator_item_position(0);
87+ var indicatorItemCoord = get_indicator_item_position(0);
89
88
90− touchPress(panel, indicator_item_coord.x, panel.panelHeight / 2);
89+ touchPress(panel, indicatorItemCoord.x, panel.panelHeight / 2);
91
90
92 // hint animation should be run, meaning that indicators will move downwards
91 // hint animation should be run, meaning that indicators will move downwards
93 // by hintValue pixels without any drag taking place
92 // by hintValue pixels without any drag taking place
@@ -96,7 +95,7 @@ Item {
96 tryCompare(panel.indicators, "partiallyOpened", true);
95 tryCompare(panel.indicators, "partiallyOpened", true);
97 tryCompare(panel.indicators, "fullyOpened", false);
96 tryCompare(panel.indicators, "fullyOpened", false);
98
97
99− touchRelease(panel, indicator_item_coord.x, panel.panelHeight/2);
98+ touchRelease(panel, indicatorItemCoord.x, panel.panelHeight/2);
100 }
99 }
101
100
102 // Pressing on the top edge of the screen should have no effect if the panel
101 // Pressing on the top edge of the screen should have no effect if the panel
@@ -107,9 +106,9 @@ Item {
107 // (switches between normal and fullscreen modes are animated)
106 // (switches between normal and fullscreen modes are animated)
108 tryCompare(panel.indicators, "y", -panel.panelHeight);
107 tryCompare(panel.indicators, "y", -panel.panelHeight);
109
108
110− var indicator_item_coord = get_indicator_item_position(0);
109+ var indicatorItemCoord = get_indicator_item_position(0);
111
110
112− touchPress(panel, indicator_item_coord.x, panel.panelHeight / 2);
111+ touchPress(panel, indicatorItemCoord.x, panel.panelHeight / 2);
113
112
114 // Give some time for a hint animation to change things, if any
113 // Give some time for a hint animation to change things, if any
115 wait(500);
114 wait(500);
@@ -126,7 +125,7 @@ Item {
126 compare(panel.indicators.fullyOpened, false, "Indicator should not be partially" +
125 compare(panel.indicators.fullyOpened, false, "Indicator should not be partially" +
127 " opened when panel is pressed in fullscreenmode");
126 " opened when panel is pressed in fullscreenmode");
128
127
129− touchRelease(panel, indicator_item_coord.x, panel.panelHeight/2);
128+ touchRelease(panel, indicatorItemCoord.x, panel.panelHeight/2);
130 }
129 }
131
130
132 function test_drag_show_data() { return get_window_data(); }
131 function test_drag_show_data() { return get_window_data(); }
@@ -137,14 +136,14 @@ Item {
137 function test_drag_show(data) {
136 function test_drag_show(data) {
138 panel.fullscreenMode = data.fullscreenFlag;
137 panel.fullscreenMode = data.fullscreenFlag;
139
138
140− var indicator_row = findChild(panel.indicators, "indicatorRow");
139+ var indicatorRow = findChild(panel.indicators, "indicatorRow");
141− verify(indicator_row != undefined);
140+ verify(indicatorRow != undefined);
142
141
143− var row_repeater = findChild(panel.indicators, "rowRepeater");
142+ var rowRepeater = findChild(panel.indicators, "rowRepeater");
144− verify(indicator_row != undefined);
143+ verify(indicatorRow != undefined);
145
144
146− var menu_content = findChild(panel.indicators, "menuContent");
145+ var menuContent = findChild(panel.indicators, "menuContent");
147− verify(indicator_row != undefined);
146+ verify(indicatorRow != undefined);
148
147
149 // Wait for the indicators to get into position.
148 // Wait for the indicators to get into position.
150 // (switches between normal and fullscreen modes are animated)
149 // (switches between normal and fullscreen modes are animated)
@@ -155,20 +154,20 @@ Item {
155 }
154 }
156
155
157 // do this for each indicator item
156 // do this for each indicator item
158− for (var i = 0; i < row_repeater.count; i++) {
157+ for (var i = 0; i < rowRepeater.count; i++) {
159
158
160− var indicator_item = get_indicator_item(i);
159+ var indicatorItem = get_indicator_item(i);
161− verify(indicator_item != undefined);
160+ verify(indicatorItem != undefined);
162
161
163− var indicator_item_coord = get_indicator_item_position(i);
162+ var indicatorItemCoord = get_indicator_item_position(i);
164
163
165 touchPress(panel,
164 touchPress(panel,
166− indicator_item_coord.x, panel.panelHeight / 2);
165+ indicatorItemCoord.x, panel.panelHeight / 2);
167
166
168 // 1) Drag the mouse down
167 // 1) Drag the mouse down
169 touchFlick(panel,
168 touchFlick(panel,
170− indicator_item_coord.x, panel.panelHeight / 2,
169+ indicatorItemCoord.x, panel.panelHeight / 2,
171− indicator_item_coord.x, panel.height * 0.8,
170+ indicatorItemCoord.x, panel.height * 0.8,
172 false /* beginTouch */, false /* endTouch */);
171 false /* beginTouch */, false /* endTouch */);
173
172
174 // Indicators height should follow the drag, and therefore increase accordingly.
173 // Indicators height should follow the drag, and therefore increase accordingly.
@@ -177,13 +176,13 @@ Item {
177 function() {return panel.indicators.height >= panel.height * 0.5},
176 function() {return panel.indicators.height >= panel.height * 0.5},
178 true);
177 true);
179
178
180− touchRelease(panel, indicator_item_coord.x, panel.height * 0.8);
179+ touchRelease(panel, indicatorItemCoord.x, panel.height * 0.8);
181
180
182− compare(indicator_row.currentItem, indicator_item,
181+ compare(indicatorRow.currentItem, indicatorItem,
183 "Incorrect item activated at position " + i);
182 "Incorrect item activated at position " + i);
184− compare(menu_content.currentIndex, i, "Menu conetent should be enabled for item at position " + i);
183+ compare(menuContent.currentMenuIndex, i, "Menu conetent should be enabled for item at position " + i);
185
184
186− // init for next indicator_item
185+ // init for next indicatorItem
187 init();
186 init();
188 }
187 }
189 }
188 }
@@ -192,12 +191,12 @@ Item {
192 panel.fullscreenMode = false;
191 panel.fullscreenMode = false;
193 panel.searchVisible = true;
192 panel.searchVisible = true;
194
193
195− var search_indicator = findChild(panel, "search");
194+ var searchIndicator = findChild(panel, "search");
196− verify(search_indicator != undefined);
195+ verify(searchIndicator != undefined);
197
196
198− tap(search_indicator, 1, 1);
197+ tap(searchIndicator, 1, 1);
199
198
200− compare(search_clicked, true,
199+ compare(searchClicked, true,
201 "Tapping search indicator while it was enabled did not emit searchClicked signal");
200 "Tapping search indicator while it was enabled did not emit searchClicked signal");
202 }
201 }
203
202
@@ -205,13 +204,54 @@ Item {
205 panel.fullscreenMode = false;
204 panel.fullscreenMode = false;
206 panel.searchVisible = false;
205 panel.searchVisible = false;
207
206
208− var search_indicator = findChild(panel, "search");
207+ var searchIndicator = findChild(panel, "search");
209− verify(search_indicator != undefined);
208+ verify(searchIndicator != undefined);
210
209
211− tap(search_indicator, 1, 1);
210+ tap(searchIndicator, 1, 1);
212
211
213− compare(search_clicked, false,
212+ compare(searchClicked, false,
214 "Tapping search indicator while it was not visible emitted searchClicked signal");
213 "Tapping search indicator while it was not visible emitted searchClicked signal");
215 }
214 }
215+
216+ // Test the vertical velocity check when flicking the indicators open at an angle.
217+ // If the vertical velocity is above a specific point, we shouldnt change active indicators
218+ // if the x position changes
219+ function test_vertical_velocity_detector() {
220+ panel.fullscreenMode = false;
221+ panel.searchVisible = false;
222+
223+ var indicatorRow = findChild(panel.indicators, "indicatorRow");
224+ verify(indicatorRow != undefined);
225+
226+ var rowRepeater = findChild(panel.indicators, "rowRepeater");
227+ verify(indicatorRow != undefined);
228+
229+ // Get the first indicator
230+ var indicatorItemFirst = get_indicator_item(0);
231+ verify(indicatorItemFirst != undefined);
232+
233+ var indicatorItemCoordFirst = get_indicator_item_position(0);
234+ var indicatorItemCoordNext = get_indicator_item_position(indicatorRow.count - 1);
235+
236+ touchPress(panel,
237+ indicatorItemCoordFirst.x, panel.panelHeight / 2);
238+
239+ // 1) Drag the mouse down to hint a bit
240+ touchFlick(panel,
241+ indicatorItemCoordFirst.x, panel.panelHeight / 2,
242+ indicatorItemCoordFirst.x, panel.panelHeight * 2,
243+ false /* beginTouch */, false /* endTouch */);
244+
245+ tryCompare(indicatorRow, "currentItem", indicatorItemFirst)
246+
247+ // 1) Flick mouse down to bottom
248+ touchFlick(panel,
249+ indicatorItemCoordFirst.x, panel.panelHeight * 2,
250+ indicatorItemCoordNext.x, panel.height,
251+ false /* beginTouch */, true /* endTouch */,
252+ units.gu(10) /* speed */, 30 /* iterations */); // more samples needed for accurate velocity
253+
254+ compare(indicatorRow.currentItem, indicatorItemFirst, "First indicator should still be the current item");
255+ }
216 }
256 }
217}
257}
tests/qmltests/Panel/tst_SearchIndicator.qml+8−10
@@ -41,24 +41,22 @@ Item {
41 when: windowShown
41 when: windowShown
42
42
43 function test_clickedSignal() {
43 function test_clickedSignal() {
44− clickedSpy.clear()
44+ clickedSpy.clear();
45 mouseClick(searchIndicator,
45 mouseClick(searchIndicator,
46 searchIndicator.width / 2, searchIndicator.height / 2);
46 searchIndicator.width / 2, searchIndicator.height / 2);
47− compare(clickedSpy.count, 1)
47+ compare(clickedSpy.count, 1);
48 }
48 }
49
49
50 function test_hideUp() {
50 function test_hideUp() {
51− var container = findChild(searchIndicator, "container")
51+ var container = findChild(searchIndicator, "container");
52− searchIndicator.state = "hiddenUp"
52+ searchIndicator.state = "hidden";
53− tryCompare(container, "opacity", 0)
53+ tryCompare(container, "opacity", 0);
54− tryCompare(container, "y", -container.height)
55 }
54 }
56
55
57 function test_show() {
56 function test_show() {
58− var container = findChild(searchIndicator, "container")
57+ var container = findChild(searchIndicator, "container");
59− searchIndicator.state = "visible"
58+ searchIndicator.state = "visible";
60− tryCompare(container, "opacity", 1)
59+ tryCompare(container, "opacity", 1);
61− tryCompare(container, "y", 0)
62 }
60 }
63 }
61 }
64}
62}
tests/qmltests/tst_Shell.qml+102−65
@@ -19,14 +19,35 @@
19
19
20import QtQuick 2.0
20import QtQuick 2.0
21import QtTest 1.0
21import QtTest 1.0
22−import Unity.Test 0.1 as UT
23import Ubuntu.Application 0.1
22import Ubuntu.Application 0.1
24−import GSettings 1.0
23+import Unity.Test 0.1 as UT
25
24
26import "../.."
25import "../.."
27
26
28−Shell {
27+Item {
29− id: shell
28+ width: shell.width
29+ height: shell.height
30+
31+ QtObject {
32+ id: applicationArguments
33+
34+ function hasGeometry() {
35+ return false;
36+ }
37+
38+ function width() {
39+ return 0;
40+ }
41+
42+ function height() {
43+ return 0;
44+ }
45+ }
46+
47+ Shell {
48+ id: shell
49+ }
50+
30 UT.UnityTestCase {
51 UT.UnityTestCase {
31 name: "Shell"
52 name: "Shell"
32 when: windowShown
53 when: windowShown
@@ -37,14 +58,10 @@ Shell {
37 var touchY = shell.height / 2;
58 var touchY = shell.height / 2;
38 touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY);
59 touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY);
39
60
40− // wait until the animation has finished
41−
42 var dash = findChild(shell, "dash");
61 var dash = findChild(shell, "dash");
62+ // wait until the animation has finished
43 tryCompare(dash, "contentScale", 1.0);
63 tryCompare(dash, "contentScale", 1.0);
44 tryCompare(dash, "opacity", 1.0);
64 tryCompare(dash, "opacity", 1.0);
45−
46− var greeter = findChild(shell, "greeter");
47− tryCompare(greeter, "x", -greeter.width);
48 }
65 }
49
66
50 function cleanup() {
67 function cleanup() {
@@ -56,6 +73,42 @@ Shell {
56 swipeUntilScopeViewIsReached(dashHome);
73 swipeUntilScopeViewIsReached(dashHome);
57 }
74 }
58
75
76+ /*
77+ Test the effect of a right-edge drag on the dash in 3 situations:
78+ 1 - when no application has been launched yet
79+ 2 - when there's a minimized application
80+ 3 - after the last running application has been closed/stopped
81+
82+ The behavior of Dash on 3 should be the same as on 1.
83+ */
84+ function test_rightEdgeDrag() {
85+ checkRightEdgeDragWithNoRunningApps();
86+
87+ dragLauncherIntoView();
88+
89+ // Launch an app from the launcher
90+ tapOnAppIconInLauncher();
91+ waitUntilApplicationWindowIsFullyVisible();
92+
93+ // Minimize the application we just launched
94+ swipeFromLeftEdge();
95+
96+ waitForUIToSettle();
97+
98+ checkRightEdgeDragWithMinimizedApp();
99+
100+ // Minimize that application once again
101+ swipeFromLeftEdge();
102+
103+ // kill it
104+ ApplicationManager.mainStageApplications.clear();
105+
106+ waitForUIToSettle();
107+
108+ // Right edge behavior should now be the same as before that app was launched
109+ checkRightEdgeDragWithNoRunningApps();
110+ }
111+
59 /*
112 /*
60 Perform a right-edge drag when the Dash is being show and there are
113 Perform a right-edge drag when the Dash is being show and there are
61 no running/minimized apps to be restored.
114 no running/minimized apps to be restored.
@@ -64,7 +117,7 @@ Shell {
64 user that his right-edge drag gesture has been successfully recognized
117 user that his right-edge drag gesture has been successfully recognized
65 but there is no application to be brought to foreground.
118 but there is no application to be brought to foreground.
66 */
119 */
67− function test_rightEdgeDragWithNoRunningApps() {
120+ function checkRightEdgeDragWithNoRunningApps() {
68 var touchX = shell.width - (shell.edgeSize / 2);
121 var touchX = shell.width - (shell.edgeSize / 2);
69 var touchY = shell.height / 2;
122 var touchY = shell.height / 2;
70
123
@@ -88,56 +141,40 @@ Shell {
88 }
141 }
89
142
90 /*
143 /*
91− Regression test for bug https://bugs.launchpad.net/touch-preview-images/+bug/1193419
144+ Perform a right-edge drag when the Dash is being show and there is
92−
145+ a running/minimized app to be restored.
93− When the user minimizes an application (left-edge swipe) he should always end up in the "Running Apps"
94− category of the "Applications" scope view.
95−
96− Steps:
97− - go to apps lens
98− - scroll to the bottom
99− - reveal launcher and launch an app
100− - perform long left edge swipe to go minimize the app and go back to the dash.
101
146
102− Expected Results
147+ The expected behavior is that the dash should fade away and ultimately be
103− - apps lens shown and Running Apps visible on screen
148+ made invisible once the gesture is finished as the restored app will now
149+ be on foreground.
104 */
150 */
105− function test_minimizingAppTakesToRunningApps() {
151+ function checkRightEdgeDragWithMinimizedApp() {
106− var dashApps = findChild(shell, "DashApps");
152+ var touchX = shell.width - (shell.edgeSize / 2);
107−
153+ var touchY = shell.height / 2;
108− swipeUntilScopeViewIsReached(dashApps);
109−
110− // swipe finger up until the running/recent apps section (which we assume
111− // it's the first one) is as far from view as possible.
112− var appsCategoryListView = findChild(dashApps, "categoryListView");
113− while (!appsCategoryListView.atYEnd) {
114− swipeUpFromCenter();
115− tryCompare(appsCategoryListView, "moving", false);
116− }
117
154
118− // Switch away from the Applications scope.
155+ var dash = findChild(shell, "dash");
119− swipeRightFromCenter();
156+ // check that dash has normal scale and opacity
120− waitUntilItemStopsMoving(dashApps);
157+ tryCompare(dash, "contentScale", 1.0);
121− verify(!itemIsOnScreen(dashApps));
158+ tryCompare(dash, "opacity", 1.0);
122
159
123− dragLauncherIntoView();
160+ touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY,
161+ true /* beginTouch */, false /* endTouch */);
124
162
125− // Launch an app from the launcher
163+ // check that Dash has been scaled down and had its opacity reduced
126− tapOnAppIconInLauncher();
164+ tryCompareFunction(function() { return dash.contentScale <= 0.9; }, true);
165+ tryCompareFunction(function() { return dash.opacity <= 0.5; }, true);
127
166
128− waitUntilApplicationWindowIsFullyVisible();
167+ touchRelease(shell, shell.width * 0.1, touchY);
129
168
130− // Minimize the application we just launched
169+ // dash should have gone away, now that the app is on foreground
131− swipeFromLeftEdge();
170+ tryCompare(dash, "visible", false);
171+ }
132
172
133− // Wait for the whole UI to settle down
173+ // Wait for the whole UI to settle down
174+ function waitForUIToSettle() {
134 waitUntilApplicationWindowIsFullyHidden();
175 waitUntilApplicationWindowIsFullyHidden();
135− waitUntilItemStopsMoving(dashApps);
176+ var dashContentList = findChild(shell, "dashContentList");
136− tryCompare(appsCategoryListView, "moving", false);
177+ tryCompare(dashContentList, "moving", false);
137−
138− verify(itemIsOnScreen(dashApps));
139− var runningApplicationsGrid = findChild(dashApps, "runningApplicationsGrid");
140− verify(itemIsOnScreen(runningApplicationsGrid));
141 }
178 }
142
179
143 function dragLauncherIntoView() {
180 function dragLauncherIntoView() {
@@ -209,6 +246,20 @@ Shell {
209 touchFlick(shell, touchStartX, touchStartY, touchStartX, 0);
246 touchFlick(shell, touchStartX, touchStartY, touchStartX, 0);
210 }
247 }
211
248
249+ function itemIsOnScreen(item) {
250+ var itemRectInShell = item.mapToItem(shell, 0, 0, item.width, item.height);
251+
252+ return itemRectInShell.x >= 0
253+ && itemRectInShell.y >= 0
254+ && itemRectInShell.x + itemRectInShell.width <= shell.width
255+ && itemRectInShell.y + itemRectInShell.height <= shell.height;
256+ }
257+
258+ function itemIsToLeftOfScreen(item) {
259+ var itemRectInShell = item.mapToItem(shell, 0, 0, item.width, item.height);
260+ return itemRectInShell.x < 0;
261+ }
262+
212 function waitUntilItemStopsMoving(item) {
263 function waitUntilItemStopsMoving(item) {
213 var itemRectInShell = item.mapToItem(shell, 0, 0, item.width, item.height);
264 var itemRectInShell = item.mapToItem(shell, 0, 0, item.width, item.height);
214 var previousX = itemRectInShell.x;
265 var previousX = itemRectInShell.x;
@@ -227,20 +278,6 @@ Shell {
227 } while (!isStill);
278 } while (!isStill);
228 }
279 }
229
280
230− function itemIsOnScreen(item) {
231− var itemRectInShell = item.mapToItem(shell, 0, 0, item.width, item.height);
232−
233− return itemRectInShell.x >= 0
234− && itemRectInShell.y >= 0
235− && itemRectInShell.x + itemRectInShell.width <= shell.width
236− && itemRectInShell.y + itemRectInShell.height <= shell.height;
237− }
238−
239− function itemIsToLeftOfScreen(item) {
240− var itemRectInShell = item.mapToItem(shell, 0, 0, item.width, item.height);
241− return itemRectInShell.x < 0;
242− }
243−
244 function test_wallpaper_data() {
281 function test_wallpaper_data() {
245 return [
282 return [
246 {tag: "red", url: "tests/data/unity/backgrounds/red.png"},
283 {tag: "red", url: "tests/data/unity/backgrounds/red.png"},
tests/utils/modules/Unity/Test/UnityTestCase.qml+22−18
@@ -32,32 +32,34 @@ TestCase {
32 // Qt uses in QQuickViewTestUtil::flick
32 // Qt uses in QQuickViewTestUtil::flick
33 // speed is in pixels/second
33 // speed is in pixels/second
34 function mouseFlick(item, x, y, toX, toY, pressMouse, releaseMouse,
34 function mouseFlick(item, x, y, toX, toY, pressMouse, releaseMouse,
35− speed) {
35+ speed, iterations) {
36 pressMouse = ((pressMouse != null) ? pressMouse : true); // Default to true for pressMouse if not present
36 pressMouse = ((pressMouse != null) ? pressMouse : true); // Default to true for pressMouse if not present
37 releaseMouse = ((releaseMouse != null) ? releaseMouse : true); // Default to true for releaseMouse if not present
37 releaseMouse = ((releaseMouse != null) ? releaseMouse : true); // Default to true for releaseMouse if not present
38
38
39 // set a default speed if not specified
39 // set a default speed if not specified
40 speed = (speed != null) ? speed : units.gu(10);
40 speed = (speed != null) ? speed : units.gu(10);
41
41
42+ // set a default iterations if not specified
43+ iterations = (iterations !== undefined) ? iterations : 5
44+
42 var distance = Math.sqrt(Math.pow(toX - x, 2) + Math.pow(toY - y, 2))
45 var distance = Math.sqrt(Math.pow(toX - x, 2) + Math.pow(toY - y, 2))
43 var totalTime = (distance / speed) * 1000 /* converting speed to pixels/ms */
46 var totalTime = (distance / speed) * 1000 /* converting speed to pixels/ms */
44
47
45− var nIterations = 5
48+ var timeStep = totalTime / iterations
46− var timeStep = totalTime / nIterations
49+ var diffX = (toX - x) / iterations
47− var diffX = (toX - x) / nIterations
50+ var diffY = (toY - y) / iterations
48− var diffY = (toY - y) / nIterations
49 if (pressMouse) {
51 if (pressMouse) {
50 fakeDateTime.currentTimeMs += timeStep
52 fakeDateTime.currentTimeMs += timeStep
51 mousePress(item, x, y)
53 mousePress(item, x, y)
52 }
54 }
53− for (var i = 0; i < nIterations; ++i) {
55+ for (var i = 0; i < iterations; ++i) {
54 fakeDateTime.currentTimeMs += timeStep
56 fakeDateTime.currentTimeMs += timeStep
55− if (i === nIterations - 1) {
57+ if (i === iterations - 1) {
56 // Avoid any rounding errors by making the last move be at precisely
58 // Avoid any rounding errors by making the last move be at precisely
57 // the point specified
59 // the point specified
58− mouseMove(item, toX, toY, nIterations / speed)
60+ mouseMove(item, toX, toY, iterations / speed)
59 } else {
61 } else {
60− mouseMove(item, x + (i + 1) * diffX, y + (i + 1) * diffY, nIterations / speed)
62+ mouseMove(item, x + (i + 1) * diffX, y + (i + 1) * diffY, iterations / speed)
61 }
63 }
62 }
64 }
63 if (releaseMouse) {
65 if (releaseMouse) {
@@ -186,7 +188,7 @@ TestCase {
186 }
188 }
187
189
188 // speed is in pixels/second
190 // speed is in pixels/second
189− function touchFlick(item, x, y, toX, toY, beginTouch, endTouch, speed) {
191+ function touchFlick(item, x, y, toX, toY, beginTouch, endTouch, speed, iterations) {
190
192
191 // Default to true for beginTouch if not present
193 // Default to true for beginTouch if not present
192 beginTouch = (beginTouch !== undefined) ? beginTouch : true
194 beginTouch = (beginTouch !== undefined) ? beginTouch : true
@@ -197,13 +199,15 @@ TestCase {
197 // Set a default speed if not specified
199 // Set a default speed if not specified
198 speed = (speed !== undefined) ? speed : units.gu(10)
200 speed = (speed !== undefined) ? speed : units.gu(10)
199
201
202+ // Set a default iterations if not specified
203+ var iterations = (iterations !== undefined) ? iterations : 5
204+
200 var distance = Math.sqrt(Math.pow(toX - x, 2) + Math.pow(toY - y, 2))
205 var distance = Math.sqrt(Math.pow(toX - x, 2) + Math.pow(toY - y, 2))
201 var totalTime = (distance / speed) * 1000 /* converting speed to pixels/ms */
206 var totalTime = (distance / speed) * 1000 /* converting speed to pixels/ms */
202
207
203− var nIterations = 5
208+ var timeStep = totalTime / iterations
204− var timeStep = totalTime / nIterations
209+ var diffX = (toX - x) / iterations
205− var diffX = (toX - x) / nIterations
210+ var diffY = (toY - y) / iterations
206− var diffY = (toY - y) / nIterations
207 if (beginTouch) {
211 if (beginTouch) {
208 fakeDateTime.currentTimeMs += timeStep
212 fakeDateTime.currentTimeMs += timeStep
209
213
@@ -211,17 +215,17 @@ TestCase {
211 event.press(0 /* touchId */, x, y)
215 event.press(0 /* touchId */, x, y)
212 event.commit()
216 event.commit()
213 }
217 }
214− for (var i = 0; i < nIterations; ++i) {
218+ for (var i = 0; i < iterations; ++i) {
215 fakeDateTime.currentTimeMs += timeStep
219 fakeDateTime.currentTimeMs += timeStep
216− if (i === nIterations - 1) {
220+ if (i === iterations - 1) {
217 // Avoid any rounding errors by making the last move be at precisely
221 // Avoid any rounding errors by making the last move be at precisely
218 // the point specified
222 // the point specified
219− wait(nIterations / speed)
223+ wait(iterations / speed)
220 var event = touchEvent()
224 var event = touchEvent()
221 event.move(0 /* touchId */, toX, toY)
225 event.move(0 /* touchId */, toX, toY)
222 event.commit()
226 event.commit()
223 } else {
227 } else {
224− wait(nIterations / speed)
228+ wait(iterations / speed)
225 var event = touchEvent()
229 var event = touchEvent()
226 event.move(0 /* touchId */, x + (i + 1) * diffX, y + (i + 1) * diffY)
230 event.move(0 /* touchId */, x + (i + 1) * diffX, y + (i + 1) * diffY)
227 event.commit()
231 event.commit()