on xenial help sign in
merge lifecycle branch as a prereq
e168fb1Michael Zanetti
debian/control+1−1
@@ -24,7 +24,7 @@ Build-Depends: cmake,
24 libpulse-dev,
24 libpulse-dev,
25 libqmenumodel-dev (>= 0.2.8),
25 libqmenumodel-dev (>= 0.2.8),
26 libqt5xmlpatterns5-dev,
26 libqt5xmlpatterns5-dev,
27− libunity-api-dev (>= 7.88),
27+ libunity-api-dev (>= 7.89),
28 libusermetricsoutput1-dev,
28 libusermetricsoutput1-dev,
29 libxcb1-dev,
29 libxcb1-dev,
30 pkg-config,
30 pkg-config,
include/paths.h.in+8
@@ -80,6 +80,14 @@ inline QStringList fallbackImportPaths() {
80 return paths;
80 return paths;
81}
81}
82
82
83+inline QString mockPluginsDir() {
84+ if (isRunningInstalled()) {
85+ return QString("@CMAKE_INSTALL_PREFIX@/@SHELL_INSTALL_QML@/mocks");
86+ } else {
87+ return QString("@CMAKE_BINARY_DIR@/tests/mocks");
88+ }
89+}
90+
83inline QStringList shellDataDirs() {
91inline QStringList shellDataDirs() {
84 QStringList dirs = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
92 QStringList dirs = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
85 if (!isRunningInstalled()) {
93 if (!isRunningInstalled()) {
plugins/Unity/Launcher/CMakeLists.txt+1−1
@@ -1,6 +1,6 @@
1include(FindPkgConfig)
1include(FindPkgConfig)
2pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=3)
2pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=3)
3−pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=1)
3+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=3)
4pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
4pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
5
5
6add_definitions(-DSM_BUSNAME=systemBus)
6add_definitions(-DSM_BUSNAME=systemBus)
qml/Dash/graphics/phone/screenshots/vkb_portrait.png

Binary file not shown.

qml/Stages/AppSurfaceContainer.qmldeleted−96
@@ -1,96 +0,0 @@
1−/*
2− * Copyright 2014 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−
17−import QtQuick 2.0
18−import "Animations"
19−
20−SurfaceContainer {
21− id: container
22− property var promptSurfaces
23−
24− property bool appHasCreatedASurface: false
25−
26− onSurfaceChanged: {
27− if (surface) {
28− if (!appHasCreatedASurface) {
29− surface.visible = false; // hide until splash screen removed
30− appHasCreatedASurface = true;
31− }
32− }
33− }
34−
35− function revealSurface() {
36− surface.visible = true;
37− splashLoader.source = "";
38− }
39−
40− Timer { //FIXME - need to delay removing splash screen to allow surface resize to complete
41− id: surfaceRevealDelay
42− interval: 100
43− onTriggered: surfaceContainer.revealSurface()
44− }
45−
46− Loader {
47− z: 3
48− id: splashLoader
49− anchors.fill: parent
50− }
51−
52− Repeater {
53− model: container.promptSurfaces
54−
55− delegate: SurfaceContainer {
56− anchors {
57− fill: container
58− topMargin: container.surface.anchors.topMargin
59− rightMargin: container.surface.anchors.rightMargin
60− bottomMargin: container.surface.anchors.bottomMargin
61− leftMargin: container.surface.anchors.leftMargin
62− }
63−
64− z: 4 + index
65− surface: modelData
66−
67− Component.onCompleted: {
68− animateIn();
69− }
70− }
71− }
72−
73− StateGroup {
74− id: appSurfaceState
75− states: [
76− State {
77− name: "noSurfaceYet"
78− when: !surfaceContainer.appHasCreatedASurface
79− StateChangeScript {
80− script: { splashLoader.setSource("Splash.qml", { "name": model.name, "image": model.icon }); }
81− }
82− },
83− State {
84− name: "hasSurface"
85− when: surfaceContainer.appHasCreatedASurface && (surfaceContainer.surface !== null)
86− StateChangeScript { script: { surfaceRevealDelay.start(); } }
87− },
88− State {
89− name: "surfaceLostButAppStillAlive"
90− when: surfaceContainer.appHasCreatedASurface && (surfaceContainer.surface === null)
91− // TODO - use app snapshot
92− }
93− ]
94− state: "noSurfaceYet"
95− }
96−}
qml/Stages/ApplicationWindow.qmlnew file+258
@@ -0,0 +1,258 @@
1+/*
2+ * Copyright 2014 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+
17+import QtQuick 2.0
18+import Ubuntu.Components 1.1
19+import Unity.Application 0.1
20+
21+Item {
22+ id: root
23+
24+ // to be read from outside
25+ readonly property bool fullscreen: application ? application.fullscreen : false
26+
27+ // to be set from outside
28+ property bool interactive: true
29+ property QtObject application
30+
31+ QtObject {
32+ id: d
33+
34+ // helpers so that we don't have to check for the existence of an application everywhere
35+ // (in order to avoid breaking qml binding due to a javascript exception)
36+ readonly property string name: root.application ? root.application.name : ""
37+ readonly property url icon: root.application ? root.application.icon : ""
38+ readonly property Item surface: root.application ? root.application.surface : null
39+ readonly property int applicationState: root.application ? root.application.state : -1
40+ readonly property var promptSurfaces: root.application ? root.application.promptSurfaces : null
41+
42+ // Whether the Application had a surface before but lost it.
43+ property bool hadSurface: false
44+
45+ property bool needToTakeScreenshot:
46+ d.surface && d.surfaceInitialized && screenshotImage.status === Image.Null
47+ && d.applicationState === ApplicationInfo.Stopped
48+ onNeedToTakeScreenshotChanged: {
49+ if (needToTakeScreenshot) {
50+ screenshotImage.take();
51+ }
52+ }
53+
54+ //FIXME - this is a hack to avoid the first few rendered frames as they
55+ // might show the UI accommodating due to surface resizes on startup.
56+ // Remove this when possible
57+ property bool surfaceInitialized: false
58+ onSurfaceChanged: {
59+ if (surface) {
60+ surfaceInitTimer.start();
61+ } else {
62+ hadSurface = true;
63+ surfaceInitialized = false;
64+ }
65+ }
66+ }
67+
68+ Timer {
69+ id: surfaceInitTimer
70+ interval: 100
71+ onTriggered: { if (d.surface) {d.surfaceInitialized = true;} }
72+ }
73+
74+ Binding {
75+ target: d.surface
76+ when: d.surface
77+ property: "enabled"
78+ value: root.interactive
79+ }
80+ Binding {
81+ target: d.surface
82+ when: d.surface
83+ property: "focus"
84+ value: root.interactive
85+ }
86+ Connections {
87+ target: d.surface
88+ // FIXME: I would rather not need to do this, but currently it doesn't get
89+ // active focus without it and I don't know why.
90+ onFocusChanged: forceSurfaceActiveFocusIfReady();
91+ onParentChanged: forceSurfaceActiveFocusIfReady();
92+ onEnabledChanged: forceSurfaceActiveFocusIfReady();
93+ function forceSurfaceActiveFocusIfReady() {
94+ if (d.surface.focus && d.surface.parent === surfaceContainer && d.surface.enabled) {
95+ d.surface.forceActiveFocus();
96+ }
97+ }
98+ }
99+
100+ SurfaceContainer {
101+ id: surfaceContainer
102+ objectName: "surfaceContainer"
103+ anchors.fill: parent
104+ surface: d.surface
105+ }
106+
107+ Image {
108+ id: screenshotImage
109+ objectName: "screenshotImage"
110+ source: ""
111+ anchors.fill: parent
112+
113+ function take() {
114+ // Format: "image://application/$APP_ID/$CURRENT_TIME_MS"
115+ // eg: "image://application/calculator-app/123456"
116+ var timeMs = new Date().getTime();
117+ source = "image://application/" + root.application.appId + "/" + timeMs;
118+ }
119+
120+ // Save memory by using a half-resolution (thus quarter size) screenshot
121+ sourceSize.width: root.width / 2
122+ sourceSize.height: root.height / 2
123+ }
124+
125+ Loader {
126+ id: splashLoader
127+ visible: active
128+ active: false
129+ anchors.fill: surfaceContainer
130+ sourceComponent: Component {
131+ Splash { name: d.name; image: d.icon }
132+ }
133+ }
134+
135+ Repeater {
136+ model: d.promptSurfaces
137+
138+ delegate: SurfaceContainer {
139+ anchors {
140+ fill: container
141+ }
142+
143+ surface: modelData
144+
145+ Component.onCompleted: {
146+ animateIn();
147+ }
148+ }
149+ }
150+
151+ StateGroup {
152+ objectName: "applicationWindowStateGroup"
153+ states: [
154+ State {
155+ name: "void"
156+ when:
157+ d.hadSurface && (!d.surface || !d.surfaceInitialized)
158+ &&
159+ screenshotImage.status !== Image.Ready
160+ },
161+ State {
162+ name: "splashScreen"
163+ when:
164+ !d.hadSurface && (!d.surface || !d.surfaceInitialized)
165+ &&
166+ screenshotImage.status !== Image.Ready
167+ },
168+ State {
169+ name: "surface"
170+ when:
171+ (d.surface && d.surfaceInitialized)
172+ &&
173+ (d.applicationState !== ApplicationInfo.Stopped
174+ || screenshotImage.status !== Image.Ready)
175+ },
176+ State {
177+ name: "screenshot"
178+ when:
179+ screenshotImage.status === Image.Ready
180+ &&
181+ (d.applicationState === ApplicationInfo.Stopped
182+ || !d.surface || !d.surfaceInitialized)
183+ }
184+ ]
185+
186+ transitions: [
187+ Transition {
188+ from: ""; to: "splashScreen"
189+ PropertyAction { target: splashLoader; property: "active"; value: true }
190+ },
191+ Transition {
192+ from: "splashScreen"; to: "surface"
193+ SequentialAnimation {
194+ PropertyAction { target: surfaceContainer
195+ property: "visible"; value: true }
196+ UbuntuNumberAnimation { target: splashLoader; property: "opacity";
197+ from: 1.0; to: 0.0
198+ duration: UbuntuAnimation.BriskDuration }
199+ PropertyAction { target: splashLoader; property: "active"; value: false }
200+ }
201+ },
202+ Transition {
203+ from: "surface"; to: "splashScreen"
204+ SequentialAnimation {
205+ PropertyAction { target: splashLoader; property: "active"; value: true }
206+ PropertyAction { target: surfaceContainer
207+ property: "visible"; value: true }
208+ UbuntuNumberAnimation { target: splashLoader; property: "opacity";
209+ from: 0.0; to: 1.0
210+ duration: UbuntuAnimation.BriskDuration }
211+ }
212+ },
213+ Transition {
214+ from: "surface"; to: "screenshot"
215+ SequentialAnimation {
216+ PropertyAction { target: screenshotImage
217+ property: "visible"; value: true }
218+ UbuntuNumberAnimation { target: screenshotImage; property: "opacity";
219+ from: 0.0; to: 1.0
220+ duration: UbuntuAnimation.BriskDuration }
221+ PropertyAction { target: surfaceContainer
222+ property: "visible"; value: false }
223+ ScriptAction { script: { if (d.surface) { d.surface.release(); } } }
224+ }
225+ },
226+ Transition {
227+ from: "screenshot"; to: "surface"
228+ SequentialAnimation {
229+ PropertyAction { target: surfaceContainer
230+ property: "visible"; value: true }
231+ UbuntuNumberAnimation { target: screenshotImage; property: "opacity";
232+ from: 1.0; to: 0.0
233+ duration: UbuntuAnimation.BriskDuration }
234+ PropertyAction { target: screenshotImage; property: "visible"; value: false }
235+ PropertyAction { target: screenshotImage; property: "source"; value: "" }
236+ }
237+ },
238+ Transition {
239+ from: "surface"; to: "void"
240+ SequentialAnimation {
241+ PropertyAction { target: surfaceContainer; property: "visible"; value: false }
242+ ScriptAction { script: { if (d.surface) { d.surface.release(); } } }
243+ }
244+ },
245+ Transition {
246+ from: "void"; to: "surface"
247+ SequentialAnimation {
248+ PropertyAction { target: surfaceContainer; property: "opacity"; value: 0.0 }
249+ PropertyAction { target: surfaceContainer; property: "visible"; value: true }
250+ UbuntuNumberAnimation { target: surfaceContainer; property: "opacity";
251+ from: 0.0; to: 1.0
252+ duration: UbuntuAnimation.BriskDuration }
253+ }
254+ }
255+ ]
256+ }
257+
258+}
qml/Stages/PhoneStage.qml+16−4
@@ -110,7 +110,8 @@ Rectangle {
110 id: spreadView
110 id: spreadView
111 objectName: "spreadView"
111 objectName: "spreadView"
112 anchors.fill: parent
112 anchors.fill: parent
113− interactive: (spreadDragArea.status == DirectionalDragArea.Recognized || phase > 1) && draggedIndex == -1
113+ interactive: (spreadDragArea.status == DirectionalDragArea.Recognized || phase > 1)
114+ && draggedDelegateCount === 0
114 contentWidth: spreadRow.width - shift
115 contentWidth: spreadRow.width - shift
115 contentX: -shift
116 contentX: -shift
116
117
@@ -148,7 +149,7 @@ Rectangle {
148 property int phase: 0
149 property int phase: 0
149
150
150 property int selectedIndex: -1
151 property int selectedIndex: -1
151− property int draggedIndex: -1
152+ property int draggedDelegateCount: 0
152 property int closingIndex: -1
153 property int closingIndex: -1
153
154
154 property bool focusChanging: false
155 property bool focusChanging: false
@@ -269,7 +270,7 @@ Rectangle {
269 swipeToCloseEnabled: spreadView.interactive
270 swipeToCloseEnabled: spreadView.interactive
270 maximizedAppTopMargin: root.maximizedAppTopMargin
271 maximizedAppTopMargin: root.maximizedAppTopMargin
271 dropShadow: spreadView.active ||
272 dropShadow: spreadView.active ||
272− priv.focusedAppDelegate.x !== 0
273+ (priv.focusedAppDelegate && priv.focusedAppDelegate.x !== 0)
273
274
274 readonly property bool isDash: model.appId == "unity8-dash"
275 readonly property bool isDash: model.appId == "unity8-dash"
275
276
@@ -290,6 +291,10 @@ Rectangle {
290 // Otherwise line up for the spread
291 // Otherwise line up for the spread
291 return spreadView.width + (index - 1) * spreadView.tileDistance;
292 return spreadView.width + (index - 1) * spreadView.tileDistance;
292 }
293 }
294+
295+ application: ApplicationManager.get(index)
296+ closeable: !isDash
297+
293 property real behavioredIndex: index
298 property real behavioredIndex: index
294 Behavior on behavioredIndex {
299 Behavior on behavioredIndex {
295 enabled: spreadView.closingIndex >= 0
300 enabled: spreadView.closingIndex >= 0
@@ -372,8 +377,15 @@ Rectangle {
372 }
377 }
373 }
378 }
374
379
380+ onDraggedChanged: {
381+ if (dragged) {
382+ spreadView.draggedDelegateCount++;
383+ } else {
384+ spreadView.draggedDelegateCount--;
385+ }
386+ }
387+
375 onClosed: {
388 onClosed: {
376− spreadView.draggedIndex = -1;
377 spreadView.closingIndex = index;
389 spreadView.closingIndex = index;
378 ApplicationManager.stopApplication(ApplicationManager.get(index).appId);
390 ApplicationManager.stopApplication(ApplicationManager.get(index).appId);
379 }
391 }
qml/Stages/SpreadDelegate.qml+32−57
@@ -14,75 +14,43 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
15 *
16 * Authors: Michael Zanetti <michael.zanetti@canonical.com>
16 * Authors: Michael Zanetti <michael.zanetti@canonical.com>
17+ * Daniel d'Andrada <daniel.dandrada@canonical.com>
17−*/
18+ */
18
19
19import QtQuick 2.0
20import QtQuick 2.0
20−import Unity.Application 0.1
21import Ubuntu.Components 1.1
21import Ubuntu.Components 1.1
22import "../Components"
22import "../Components"
23
23
24Item {
24Item {
25 id: root
25 id: root
26
26
27+ // to be read from outside
28+ readonly property bool dragged: dragArea.moving
29+ signal clicked()
30+ signal closed()
31+
27 // to be set from outside
32 // to be set from outside
28 property bool interactive: true
33 property bool interactive: true
29 property bool dropShadow: true
34 property bool dropShadow: true
30 property real maximizedAppTopMargin
35 property real maximizedAppTopMargin
31 property alias swipeToCloseEnabled: dragArea.enabled
36 property alias swipeToCloseEnabled: dragArea.enabled
37+ property bool closeable
38+ property alias application: appWindow.application
32
39
33− readonly property bool isFullscreen: surface !== null && surfaceContainer.surface.anchors.topMargin == 0
40+ Item {
34−
41+ objectName: "appWindowWithShadow"
35− signal clicked()
36− signal closed()
37−
38− AppSurfaceContainer {
39− id: surfaceContainer
40− objectName: "surfaceContainer"
41− anchors.fill: parent
42− surface: model.surface
43− promptSurfaces: model.application.promptSurfaces
44−
45− Binding {
46− target: surfaceContainer.surface
47− property: "anchors.topMargin"
48− value: {
49− return surfaceContainer.surface.state === MirSurfaceItem.Fullscreen ? 0 : maximizedAppTopMargin;
50− }
51− }
52−
53− Binding {
54− target: surface
55− property: "enabled"
56− value: root.interactive
57− }
58− Binding {
59− target: surface
60− property: "focus"
61− value: root.interactive
62− }
63
42
64− Connections {
43+ y: dragArea.distance
65− target: surface
44+ width: parent.width
66− // FIXME: I would rather not need to do this, but currently it doesn't get
45+ height: parent.height
67− // active focus without it and I don't know why.
68− onFocusChanged: forceSurfaceActiveFocusIfReady();
69− onParentChanged: forceSurfaceActiveFocusIfReady();
70− onEnabledChanged: forceSurfaceActiveFocusIfReady();
71− function forceSurfaceActiveFocusIfReady() {
72− if (surface.focus && surface.parent === surfaceContainer && surface.enabled) {
73− surface.forceActiveFocus();
74− }
75− }
76− }
77
46
78 BorderImage {
47 BorderImage {
79− id: dropShadowImage
80 anchors {
48 anchors {
81− fill: parent
49+ fill: appWindow
82 leftMargin: -units.gu(2)
50 leftMargin: -units.gu(2)
83 rightMargin: -units.gu(2)
51 rightMargin: -units.gu(2)
84 bottomMargin: -units.gu(2)
52 bottomMargin: -units.gu(2)
85− topMargin: -units.gu(2) + (root.isFullscreen ? 0 : maximizedAppTopMargin)
53+ topMargin: -units.gu(2)
86 }
54 }
87 source: "graphics/dropshadow.png"
55 source: "graphics/dropshadow.png"
88 border { left: 50; right: 50; top: 50; bottom: 50 }
56 border { left: 50; right: 50; top: 50; bottom: 50 }
@@ -90,21 +58,26 @@ Item {
90 Behavior on opacity { UbuntuNumberAnimation {} }
58 Behavior on opacity { UbuntuNumberAnimation {} }
91 }
59 }
92
60
93− transform: Translate {
61+ ApplicationWindow {
94− y: dragArea.distance
62+ id: appWindow
63+ anchors {
64+ fill: parent
65+ topMargin: appWindow.fullscreen ? 0 : maximizedAppTopMargin
66+ }
67+
68+ interactive: root.interactive
95 }
69 }
96 }
70 }
97
71
98 DraggingArea {
72 DraggingArea {
99 id: dragArea
73 id: dragArea
74+ objectName: "dragArea"
100 anchors.fill: parent
75 anchors.fill: parent
101
76
102 property bool moving: false
77 property bool moving: false
103 property real distance: 0
78 property real distance: 0
104
79
105− onMovingChanged: {
80+ readonly property real minSpeedToClose: units.gu(40)
106− spreadView.draggedIndex = moving ? index : -1
107− }
108
81
109 onDragValueChanged: {
82 onDragValueChanged: {
110 if (!dragging) {
83 if (!dragging) {
@@ -123,15 +96,15 @@ Item {
123 }
96 }
124
97
125 onDragEnd: {
98 onDragEnd: {
126− if (model.appId == "unity8-dash") {
99+ if (!root.closeable) {
127 animation.animate("center")
100 animation.animate("center")
128 return;
101 return;
129 }
102 }
130
103
131 // velocity and distance values specified by design prototype
104 // velocity and distance values specified by design prototype
132− if ((dragVelocity < -units.gu(40) && distance < -units.gu(8)) || distance < -root.height / 2) {
105+ if ((dragVelocity < -minSpeedToClose && distance < -units.gu(8)) || distance < -root.height / 2) {
133 animation.animate("up")
106 animation.animate("up")
134− } else if ((dragVelocity > units.gu(40) && distance > units.gu(8)) || distance > root.height / 2) {
107+ } else if ((dragVelocity > minSpeedToClose && distance > units.gu(8)) || distance > root.height / 2) {
135 animation.animate("down")
108 animation.animate("down")
136 } else {
109 } else {
137 animation.animate("center")
110 animation.animate("center")
@@ -140,6 +113,7 @@ Item {
140
113
141 UbuntuNumberAnimation {
114 UbuntuNumberAnimation {
142 id: animation
115 id: animation
116+ objectName: "closeAnimation"
143 target: dragArea
117 target: dragArea
144 property: "distance"
118 property: "distance"
145 property bool requestClose: false
119 property bool requestClose: false
@@ -164,9 +138,10 @@ Item {
164 onRunningChanged: {
138 onRunningChanged: {
165 if (!running) {
139 if (!running) {
166 dragArea.moving = false;
140 dragArea.moving = false;
167− dragArea.distance = 0;
168 if (requestClose) {
141 if (requestClose) {
169 root.closed();
142 root.closed();
143+ } else {
144+ dragArea.distance = 0;
170 }
145 }
171 }
146 }
172 }
147 }
qml/Stages/SurfaceContainer.qml+1−1
@@ -45,7 +45,7 @@ Item {
45 }
45 }
46
46
47 Repeater {
47 Repeater {
48− model: surface.childSurfaces
48+ model: surface ? surface.childSurfaces : null
49
49
50 delegate: Loader {
50 delegate: Loader {
51 z: 2
51 z: 2
qml/Stages/TabletStage.qml+13−3
@@ -145,7 +145,8 @@ Rectangle {
145 Flickable {
145 Flickable {
146 id: spreadView
146 id: spreadView
147 anchors.fill: parent
147 anchors.fill: parent
148− interactive: (spreadDragArea.status == DirectionalDragArea.Recognized || phase > 1) && draggedIndex == -1
148+ interactive: (spreadDragArea.status == DirectionalDragArea.Recognized || phase > 1)
149+ && draggedDelegateCount === 0
149 contentWidth: spreadRow.width - shift
150 contentWidth: spreadRow.width - shift
150 contentX: -shift
151 contentX: -shift
151
152
@@ -189,7 +190,7 @@ Rectangle {
189 readonly property real snapPosition: 0.75
190 readonly property real snapPosition: 0.75
190
191
191 property int selectedIndex: -1
192 property int selectedIndex: -1
192− property int draggedIndex: -1
193+ property int draggedDelegateCount: 0
193 property int closingIndex: -1
194 property int closingIndex: -1
194
195
195 property bool sideStageDragging: sideStageDragHandle.dragging
196 property bool sideStageDragging: sideStageDragHandle.dragging
@@ -475,6 +476,8 @@ Rectangle {
475 swipeToCloseEnabled: spreadView.interactive
476 swipeToCloseEnabled: spreadView.interactive
476 maximizedAppTopMargin: root.maximizedAppTopMargin
477 maximizedAppTopMargin: root.maximizedAppTopMargin
477 dragOffset: !isDash && model.appId == priv.mainStageAppId && root.inverseProgress > 0 ? root.inverseProgress : 0
478 dragOffset: !isDash && model.appId == priv.mainStageAppId && root.inverseProgress > 0 ? root.inverseProgress : 0
479+ application: ApplicationManager.get(index)
480+ closeable: !isDash
478
481
479 readonly property bool isDash: model.appId == "unity8-dash"
482 readonly property bool isDash: model.appId == "unity8-dash"
480
483
@@ -533,8 +536,15 @@ Rectangle {
533 }
536 }
534 }
537 }
535
538
539+ onDraggedChanged: {
540+ if (dragged) {
541+ spreadView.draggedDelegateCount++;
542+ } else {
543+ spreadView.draggedDelegateCount--;
544+ }
545+ }
546+
536 onClosed: {
547 onClosed: {
537− spreadView.draggedIndex = -1;
538 spreadView.closingIndex = index;
548 spreadView.closingIndex = index;
539 ApplicationManager.stopApplication(ApplicationManager.get(index).appId);
549 ApplicationManager.stopApplication(ApplicationManager.get(index).appId);
540 }
550 }
qml/Stages/TransformedSpreadDelegate.qml+1−1
@@ -309,7 +309,7 @@ SpreadDelegate {
309 Scale {
309 Scale {
310 origin { x: 0; y: (spreadView.height * priv.scale) + maximizedAppTopMargin * 3 }
310 origin { x: 0; y: (spreadView.height * priv.scale) + maximizedAppTopMargin * 3 }
311 xScale: 1
311 xScale: 1
312− yScale: isFullscreen ? 1 - priv.topMarginProgress * maximizedAppTopMargin / spreadView.height : 1
312+ yScale: fullscreen ? 1 - priv.topMarginProgress * maximizedAppTopMargin / spreadView.height : 1
313 },
313 },
314 Translate {
314 Translate {
315 x: priv.xTranslate
315 x: priv.xTranslate
tests/mocks/Unity/Application/Application.qmltypes−19
@@ -7,25 +7,6 @@ import QtQuick.tooling 1.1
7// 'qmlplugindump -notrelocatable Unity.Application 0.1 tests/mocks'
7// 'qmlplugindump -notrelocatable Unity.Application 0.1 tests/mocks'
8
8
9Module {
9Module {
10− Component {
11− name: "ApplicationImage"
12− defaultProperty: "data"
13− prototype: "QQuickItem"
14− exports: ["Unity.Application/ApplicationImage 0.1"]
15− exportMetaObjectRevisions: [0]
16− Enum {
17− name: "FillMode"
18− values: {
19− "Stretch": 0,
20− "PreserveAspectCrop": 1
21− }
22− }
23− Property { name: "source"; type: "ApplicationInfo"; isPointer: true }
24− Property { name: "fillMode"; type: "FillMode" }
25− Property { name: "ready"; type: "bool" }
26− Method { name: "scheduleUpdate" }
27− Method { name: "updateFromCache" }
28− }
29 Component {
10 Component {
30 name: "ApplicationInfo"
11 name: "ApplicationInfo"
31 prototype: "unity::shell::application::ApplicationInfoInterface"
12 prototype: "unity::shell::application::ApplicationInfoInterface"
tests/mocks/Unity/Application/ApplicationImage.cppdeleted−123
@@ -1,123 +0,0 @@
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−
17−#include "ApplicationImage.h"
18−#include "ApplicationInfo.h"
19−
20−#include <QQmlEngine>
21−#include <QQmlComponent>
22−#include <QQmlContext>
23−
24−ApplicationImage::ApplicationImage(QQuickItem* parent)
25− : QQuickItem(parent),
26− m_source(NULL),
27− m_fillMode(Stretch),
28− m_ready(false),
29− m_imageComponent(0),
30− m_imageItem(0)
31−{
32−}
33−
34−void ApplicationImage::setSource(ApplicationInfo* source)
35−{
36− if (m_source != source) {
37− if (m_source)
38− disconnect(m_source, &ApplicationInfo::imageQmlChanged,
39− this, &ApplicationImage::updateImage);
40−
41− m_source = source;
42−
43− if (m_source) {
44− connect(m_source, &ApplicationInfo::imageQmlChanged,
45− this, &ApplicationImage::updateImage);
46− }
47− updateImage();
48−
49− Q_EMIT sourceChanged();
50− }
51−}
52−
53−void ApplicationImage::setFillMode(FillMode newFillMode)
54−{
55− if (m_fillMode != newFillMode) {
56− m_fillMode = newFillMode;
57− Q_EMIT fillModeChanged();
58− }
59−}
60−
61−void ApplicationImage::setReady(bool value)
62−{
63− if (value != m_ready) {
64− m_ready = value;
65− Q_EMIT readyChanged();
66− }
67−}
68−
69−void ApplicationImage::destroyImage()
70−{
71− delete m_imageItem;
72− m_imageItem = 0;
73− delete m_imageComponent;
74− m_imageComponent = 0;
75− m_qmlUsed.clear();
76− setReady(false);
77−}
78−
79−void ApplicationImage::updateImage()
80−{
81− if (!m_source || m_source->imageQml().isEmpty()) {
82− destroyImage();
83− } else if (m_source->imageQml() != m_qmlUsed) {
84− destroyImage();
85− createImageItem();
86− }
87−}
88−
89−void ApplicationImage::createImageItem()
90−{
91−
92− if (!m_imageComponent)
93− createImageComponent();
94−
95− // only create the windowItem one the component is ready
96− if (!m_imageComponent->isReady()) {
97− connect(m_imageComponent, &QQmlComponent::statusChanged,
98− this, &ApplicationImage::onImageComponentStatusChanged);
99− } else {
100− doCreateImageItem();
101− }
102−
103−}
104−
105−void ApplicationImage::createImageComponent()
106−{
107− QQmlEngine *engine = QQmlEngine::contextForObject(this)->engine();
108− m_imageComponent = new QQmlComponent(engine, this);
109− m_imageComponent->setData(m_source->imageQml().toLatin1(), QUrl());
110−}
111−
112−void ApplicationImage::doCreateImageItem()
113−{
114− m_imageItem = qobject_cast<QQuickItem *>(m_imageComponent->create());
115− m_imageItem->setParentItem(this);
116− setReady(true);
117−}
118−
119−void ApplicationImage::onImageComponentStatusChanged(QQmlComponent::Status status)
120−{
121− if (status == QQmlComponent::Ready && !m_imageItem)
122− doCreateImageItem();
123−}
tests/mocks/Unity/Application/ApplicationImage.hdeleted−79
@@ -1,79 +0,0 @@
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−
17−#ifndef APPLICATION_IMAGE_H
18−#define APPLICATION_IMAGE_H
19−
20−#include <QQuickItem>
21−
22−class ApplicationInfo;
23−
24−/* Fake implementation of ApplicationImage
25−
26− That fake implementation is not made in QML just because we can only declare
27− enumerations in C++. We can't even make readonly properties to mimic an enum
28− because properties must begin with a lower case letter.
29−*/
30−class ApplicationImage : public QQuickItem {
31− Q_OBJECT
32− Q_ENUMS(FillMode)
33− Q_PROPERTY(ApplicationInfo* source READ source WRITE setSource NOTIFY sourceChanged)
34− Q_PROPERTY(FillMode fillMode READ fillMode WRITE setFillMode NOTIFY fillModeChanged)
35− Q_PROPERTY(bool ready READ ready WRITE setReady NOTIFY readyChanged)
36−
37−public:
38− explicit ApplicationImage(QQuickItem* parent = 0);
39− virtual ~ApplicationImage() {}
40−
41− enum FillMode { Stretch, PreserveAspectCrop };
42−
43− ApplicationInfo* source() const { return m_source; }
44− void setSource(ApplicationInfo* source);
45−
46− FillMode fillMode() const { return m_fillMode; }
47− void setFillMode(FillMode);
48−
49− void setReady(bool value);
50− bool ready() const { return m_ready; }
51−
52− Q_INVOKABLE void scheduleUpdate() {}
53− Q_INVOKABLE void updateFromCache() {}
54−
55−Q_SIGNALS:
56− void sourceChanged();
57− void fillModeChanged();
58− void readyChanged();
59−
60−private Q_SLOTS:
61− void updateImage();
62− void onImageComponentStatusChanged(QQmlComponent::Status status);
63−
64−private:
65− void createImageItem();
66− void createImageComponent();
67− void doCreateImageItem();
68− void destroyImage();
69−
70− ApplicationInfo* m_source;
71− FillMode m_fillMode;
72− bool m_ready;
73− QQmlComponent *m_imageComponent;
74− QQuickItem* m_imageItem;
75− // the QML script used to create the current m_imageItem
76− QString m_qmlUsed;
77−};
78−
79−#endif // APPLICATION_IMAGE_H
tests/mocks/Unity/Application/ApplicationInfo.cpp+83−14
@@ -18,6 +18,8 @@
18#include "MirSurfaceItem.h"
18#include "MirSurfaceItem.h"
19#include "SurfaceManager.h"
19#include "SurfaceManager.h"
20
20
21+#include <paths.h>
22+
21#include <QGuiApplication>
23#include <QGuiApplication>
22#include <QQuickItem>
24#include <QQuickItem>
23#include <QQuickView>
25#include <QQuickView>
@@ -31,10 +33,10 @@ ApplicationInfo::ApplicationInfo(const QString &appId, QObject *parent)
31 , m_state(Starting)
33 , m_state(Starting)
32 , m_focused(false)
34 , m_focused(false)
33 , m_fullscreen(false)
35 , m_fullscreen(false)
34− , m_parentItem(0)
35 , m_surface(0)
36 , m_surface(0)
37+ , m_manualSurfaceCreation(false)
38+ , m_parentItem(0)
36{
39{
37− connect(this, &ApplicationInfo::stateChanged, this, &ApplicationInfo::onStateChanged);
38}
40}
39
41
40ApplicationInfo::ApplicationInfo(QObject *parent)
42ApplicationInfo::ApplicationInfo(QObject *parent)
@@ -43,10 +45,10 @@ ApplicationInfo::ApplicationInfo(QObject *parent)
43 , m_state(Starting)
45 , m_state(Starting)
44 , m_focused(false)
46 , m_focused(false)
45 , m_fullscreen(false)
47 , m_fullscreen(false)
46− , m_parentItem(0)
47 , m_surface(0)
48 , m_surface(0)
49+ , m_manualSurfaceCreation(false)
50+ , m_parentItem(0)
48{
51{
49− connect(this, &ApplicationInfo::stateChanged, this, &ApplicationInfo::onStateChanged);
50}
52}
51
53
52ApplicationInfo::~ApplicationInfo()
54ApplicationInfo::~ApplicationInfo()
@@ -58,23 +60,16 @@ ApplicationInfo::~ApplicationInfo()
58 }
60 }
59}
61}
60
62
61−void ApplicationInfo::onStateChanged(State state)
62−{
63− if (state == ApplicationInfo::Running) {
64− QTimer::singleShot(1000, this, SLOT(createSurface()));
65− } else if (state == ApplicationInfo::Stopped) {
66− setSurface(nullptr);
67− }
68−}
69−
70void ApplicationInfo::createSurface()
63void ApplicationInfo::createSurface()
71{
64{
72 if (m_surface || state() == ApplicationInfo::Stopped) return;
65 if (m_surface || state() == ApplicationInfo::Stopped) return;
73
66
67+ QUrl screenshotUrl = QString("file://%1").arg(m_screenshotFileName);
68+
74 setSurface(new MirSurfaceItem(name(),
69 setSurface(new MirSurfaceItem(name(),
75 MirSurfaceItem::Normal,
70 MirSurfaceItem::Normal,
76 fullscreen() ? MirSurfaceItem::Fullscreen : MirSurfaceItem::Maximized,
71 fullscreen() ? MirSurfaceItem::Fullscreen : MirSurfaceItem::Maximized,
77− screenshot()));
72+ screenshotUrl));
78}
73}
79
74
80void ApplicationInfo::setSurface(MirSurfaceItem* surface)
75void ApplicationInfo::setSurface(MirSurfaceItem* surface)
@@ -152,3 +147,77 @@ MirSurfaceItem* ApplicationInfo::promptSurfaceAt(QQmlListProperty<MirSurfaceItem
152 return nullptr;
147 return nullptr;
153 return p->m_promptSurfaces[index];
148 return p->m_promptSurfaces[index];
154}
149}
150+
151+void ApplicationInfo::setIconId(const QString &iconId)
152+{
153+ setIcon(QString("file://%1/graphics/applicationIcons/%2@18.png")
154+ .arg(qmlDirectory())
155+ .arg(iconId));
156+}
157+
158+void ApplicationInfo::setScreenshotId(const QString &screenshotId)
159+{
160+ m_screenshotFileName = QString("%1/Dash/graphics/phone/screenshots/%2@12.png")
161+ .arg(qmlDirectory())
162+ .arg(screenshotId);
163+}
164+
165+void ApplicationInfo::setName(const QString &value)
166+{
167+ if (value != m_name) {
168+ m_name = value;
169+ Q_EMIT nameChanged(value);
170+ }
171+}
172+
173+void ApplicationInfo::setIcon(const QUrl &value)
174+{
175+ if (value != m_icon) {
176+ m_icon = value;
177+ Q_EMIT iconChanged(value);
178+ }
179+}
180+
181+void ApplicationInfo::setStage(Stage value)
182+{
183+ if (value != m_stage) {
184+ m_stage = value;
185+ Q_EMIT stageChanged(value);
186+ }
187+}
188+
189+void ApplicationInfo::setState(State value)
190+{
191+ if (value != m_state) {
192+ m_state = value;
193+ Q_EMIT stateChanged(value);
194+
195+ if (!m_manualSurfaceCreation && m_state == ApplicationInfo::Running) {
196+ QTimer::singleShot(1000, this, SLOT(createSurface()));
197+ }
198+ }
199+}
200+
201+void ApplicationInfo::setFocused(bool value)
202+{
203+ if (value != m_focused) {
204+ m_focused = value;
205+ Q_EMIT focusedChanged(value);
206+ }
207+}
208+
209+void ApplicationInfo::setFullscreen(bool value)
210+{
211+ if (value != m_fullscreen) {
212+ m_fullscreen = value;
213+ Q_EMIT fullscreenChanged(value);
214+ }
215+}
216+
217+void ApplicationInfo::setManualSurfaceCreation(bool value)
218+{
219+ if (value != m_manualSurfaceCreation) {
220+ m_manualSurfaceCreation = value;
221+ Q_EMIT manualSurfaceCreationChanged(value);
222+ }
223+}
tests/mocks/Unity/Application/ApplicationInfo.h+50−44
@@ -26,63 +26,52 @@ class MirSurfaceItem;
26// unity-api
26// unity-api
27#include <unity/shell/application/ApplicationInfoInterface.h>
27#include <unity/shell/application/ApplicationInfoInterface.h>
28
28
29−// A pretty dumb file. Just a container for properties.
30−// Implemented in C++ instead of QML just because of the enumerations
31−// See QTBUG-14861
32−
33using namespace unity::shell::application;
29using namespace unity::shell::application;
34
30
35class ApplicationInfo : public ApplicationInfoInterface {
31class ApplicationInfo : public ApplicationInfoInterface {
36 Q_OBJECT
32 Q_OBJECT
37
33
38 Q_PROPERTY(bool fullscreen READ fullscreen WRITE setFullscreen NOTIFY fullscreenChanged)
34 Q_PROPERTY(bool fullscreen READ fullscreen WRITE setFullscreen NOTIFY fullscreenChanged)
39− Q_PROPERTY(Stage stage READ stage WRITE setStage NOTIFY stageChanged)
40 Q_PROPERTY(MirSurfaceItem* surface READ surface NOTIFY surfaceChanged)
35 Q_PROPERTY(MirSurfaceItem* surface READ surface NOTIFY surfaceChanged)
41 Q_PROPERTY(QQmlListProperty<MirSurfaceItem> promptSurfaces READ promptSurfaces NOTIFY promptSurfacesChanged DESIGNABLE false)
36 Q_PROPERTY(QQmlListProperty<MirSurfaceItem> promptSurfaces READ promptSurfaces NOTIFY promptSurfacesChanged DESIGNABLE false)
42
37
43 // Only exists in this fake implementation
38 // Only exists in this fake implementation
44
39
45− // QML component used to represent its image/screenhot
40+ // whether the test code will explicitly control the creation of the application surface
46− Q_PROPERTY(QString imageQml READ imageQml WRITE setImageQml NOTIFY imageQmlChanged)
41+ Q_PROPERTY(bool manualSurfaceCreation READ manualSurfaceCreation WRITE setManualSurfaceCreation NOTIFY manualSurfaceCreationChanged)
47−
48− // QML component used to represent the application window
49− Q_PROPERTY(QString windowQml READ windowQml WRITE setWindowQml NOTIFY windowQmlChanged)
50
42
51public:
43public:
52 ApplicationInfo(QObject *parent = NULL);
44 ApplicationInfo(QObject *parent = NULL);
53 ApplicationInfo(const QString &appId, QObject *parent = NULL);
45 ApplicationInfo(const QString &appId, QObject *parent = NULL);
54 ~ApplicationInfo();
46 ~ApplicationInfo();
55
47
56− #define IMPLEMENT_PROPERTY(name, Name, type) \
48+ void setIconId(const QString &iconId);
57− public: \
49+ void setScreenshotId(const QString &screenshotId);
58− type name() const { return m_##name; } \
50+
59− void set##Name(const type& value) \
51+ void setAppId(const QString &value) { m_appId = value; }
60− { \
52+ QString appId() const override { return m_appId; }
61− if (m_##name != value) { \
53+
62− m_##name = value; \
54+ void setName(const QString &value);
63− Q_EMIT name##Changed(value); \
55+ QString name() const override { return m_name; }
64− } \
56+
65− } \
57+ QString comment() const override { return QString(); }
66− Q_SIGNALS: \
58+
67− void name##Changed(const type&); \
59+ QUrl icon() const override { return m_icon; }
68− private: \
60+
69− type m_##name;
61+ void setStage(Stage value);
70−
62+ Stage stage() const override { return m_stage; }
71− IMPLEMENT_PROPERTY(appId, AppId, QString)
63+
72− IMPLEMENT_PROPERTY(name, Name, QString)
64+ Q_INVOKABLE void setState(State value);
73− IMPLEMENT_PROPERTY(comment, Comment, QString)
65+ State state() const override { return m_state; }
74− IMPLEMENT_PROPERTY(icon, Icon, QUrl)
66+
75− IMPLEMENT_PROPERTY(stage, Stage, Stage)
67+ void setFocused(bool value);
76− IMPLEMENT_PROPERTY(state, State, State)
68+ bool focused() const override { return m_focused; }
77− IMPLEMENT_PROPERTY(focused, Focused, bool)
69+
78− IMPLEMENT_PROPERTY(fullscreen, Fullscreen, bool)
70+ QString screenshot() const { return m_screenshotFileName; }
79− IMPLEMENT_PROPERTY(imageQml, ImageQml, QString)
71+
80− IMPLEMENT_PROPERTY(windowQml, WindowQml, QString)
72+ void setFullscreen(bool value);
81− IMPLEMENT_PROPERTY(screenshot, Screenshot, QUrl)
73+ bool fullscreen() const { return m_fullscreen; }
82−
83− #undef IMPLEMENT_PROPERTY
84
74
85−public:
86 void setSurface(MirSurfaceItem* surface);
75 void setSurface(MirSurfaceItem* surface);
87 MirSurfaceItem* surface() const { return m_surface; }
76 MirSurfaceItem* surface() const { return m_surface; }
88
77
@@ -92,21 +81,38 @@ public:
92 QList<MirSurfaceItem*> promptSurfaceList() const;
81 QList<MirSurfaceItem*> promptSurfaceList() const;
93 QQmlListProperty<MirSurfaceItem> promptSurfaces();
82 QQmlListProperty<MirSurfaceItem> promptSurfaces();
94
83
84+ bool manualSurfaceCreation() const { return m_manualSurfaceCreation; }
85+ void setManualSurfaceCreation(bool value);
86+
95Q_SIGNALS:
87Q_SIGNALS:
96 void surfaceChanged(MirSurfaceItem*);
88 void surfaceChanged(MirSurfaceItem*);
97 void promptSurfacesChanged();
89 void promptSurfacesChanged();
90+ void fullscreenChanged(bool value);
91+ void manualSurfaceCreationChanged(bool value);
98
92
99−private Q_SLOTS:
93+public Q_SLOTS:
100− void onStateChanged(State state);
101−
102− void createSurface();
94+ Q_INVOKABLE void createSurface();
103
95
104private:
96private:
105 static int promptSurfaceCount(QQmlListProperty<MirSurfaceItem> *prop);
97 static int promptSurfaceCount(QQmlListProperty<MirSurfaceItem> *prop);
106 static MirSurfaceItem* promptSurfaceAt(QQmlListProperty<MirSurfaceItem> *prop, int index);
98 static MirSurfaceItem* promptSurfaceAt(QQmlListProperty<MirSurfaceItem> *prop, int index);
99+ void setIcon(const QUrl &value);
100+ void setScreenshot(const QUrl &value);
101+
102+ QString m_screenshotFileName;
103+
104+ QString m_appId;
105+ QString m_name;
106+ QUrl m_icon;
107+ Stage m_stage;
108+ State m_state;
109+ bool m_focused;
110+ bool m_fullscreen;
111+ MirSurfaceItem* m_surface;
112+
113+ bool m_manualSurfaceCreation;
107
114
108 QQuickItem *m_parentItem;
115 QQuickItem *m_parentItem;
109− MirSurfaceItem* m_surface;
110 QList<MirSurfaceItem*> m_promptSurfaces;
116 QList<MirSurfaceItem*> m_promptSurfaces;
111};
117};
112
118
tests/mocks/Unity/Application/ApplicationManager.cpp+87−102
@@ -55,14 +55,32 @@ ApplicationManager::ApplicationManager(QObject *parent)
55
55
56 buildListOfAvailableApplications();
56 buildListOfAvailableApplications();
57
57
58− startApplication("unity8-dash");
58+ // polling to find out when the toplevel window has been created as there's
59− focusApplication("unity8-dash");
59+ // no signal telling us that
60+ connect(&m_windowCreatedTimer, &QTimer::timeout,
61+ this, &ApplicationManager::onWindowCreatedTimerTimeout);
62+ m_windowCreatedTimer.setSingleShot(false);
63+ m_windowCreatedTimer.start(200);
60}
64}
61
65
62ApplicationManager::~ApplicationManager()
66ApplicationManager::~ApplicationManager()
63{
67{
64}
68}
65
69
70+void ApplicationManager::onWindowCreatedTimerTimeout()
71+{
72+ if (QGuiApplication::topLevelWindows().count() > 0) {
73+ m_windowCreatedTimer.stop();
74+ onWindowCreated();
75+ }
76+}
77+
78+void ApplicationManager::onWindowCreated()
79+{
80+ startApplication("unity8-dash");
81+ focusApplication("unity8-dash");
82+}
83+
66int ApplicationManager::rowCount(const QModelIndex& parent) const {
84int ApplicationManager::rowCount(const QModelIndex& parent) const {
67 return !parent.isValid() ? m_runningApplications.size() : 0;
85 return !parent.isValid() ? m_runningApplications.size() : 0;
68}
86}
@@ -87,8 +105,6 @@ QVariant ApplicationManager::data(const QModelIndex& index, int role) const {
87 return app->state();
105 return app->state();
88 case RoleFocused:
106 case RoleFocused:
89 return app->focused();
107 return app->focused();
90− case RoleScreenshot:
91− return app->screenshot();
92 case RoleSurface:
108 case RoleSurface:
93 return QVariant::fromValue(app->surface());
109 return QVariant::fromValue(app->surface());
94 case RoleFullscreen:
110 case RoleFullscreen:
@@ -143,6 +159,16 @@ void ApplicationManager::add(ApplicationInfo *application) {
143 if (!appIndex.isValid()) return;
159 if (!appIndex.isValid()) return;
144 Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << ApplicationManager::RoleSurface);
160 Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << ApplicationManager::RoleSurface);
145 });
161 });
162+ connect(application, &ApplicationInfo::focusedChanged, this, [application, this]() {
163+ QModelIndex appIndex = findIndex(application);
164+ if (!appIndex.isValid()) return;
165+ Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << ApplicationManager::RoleFocused);
166+ });
167+ connect(application, &ApplicationInfo::stateChanged, this, [application, this]() {
168+ QModelIndex appIndex = findIndex(application);
169+ if (!appIndex.isValid()) return;
170+ Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << ApplicationManager::RoleState);
171+ });
146}
172}
147
173
148void ApplicationManager::remove(ApplicationInfo *application) {
174void ApplicationManager::remove(ApplicationInfo *application) {
@@ -155,7 +181,7 @@ void ApplicationManager::remove(ApplicationInfo *application) {
155 Q_EMIT countChanged();
181 Q_EMIT countChanged();
156 if (isEmpty()) Q_EMIT emptyChanged(isEmpty());
182 if (isEmpty()) Q_EMIT emptyChanged(isEmpty());
157 }
183 }
158− disconnect(application, &ApplicationInfo::surfaceChanged, this, 0);
184+ disconnect(application, 0, this, 0);
159}
185}
160
186
161void ApplicationManager::move(int from, int to) {
187void ApplicationManager::move(int from, int to) {
@@ -198,6 +224,21 @@ ApplicationInfo* ApplicationManager::startApplication(const QString &appId,
198 const QStringList &arguments)
224 const QStringList &arguments)
199{
225{
200 Q_UNUSED(arguments)
226 Q_UNUSED(arguments)
227+ ApplicationInfo *application = add(appId);
228+ if (!application)
229+ return 0;
230+
231+ if (flags.testFlag(ApplicationManager::ForceMainStage)
232+ && application->stage() == ApplicationInfo::SideStage) {
233+ application->setStage(ApplicationInfo::MainStage);
234+ }
235+ application->setState(ApplicationInfo::Running);
236+
237+ return application;
238+}
239+
240+ApplicationInfo* ApplicationManager::add(QString appId)
241+{
201 ApplicationInfo *application = 0;
242 ApplicationInfo *application = 0;
202
243
203 for (ApplicationInfo *availableApp : m_availableApplications) {
244 for (ApplicationInfo *availableApp : m_availableApplications) {
@@ -207,15 +248,8 @@ ApplicationInfo* ApplicationManager::startApplication(const QString &appId,
207 }
248 }
208 }
249 }
209
250
210− if (!application)
251+ if (application)
211− return 0;
212−
213− if (flags.testFlag(ApplicationManager::ForceMainStage)
214− && application->stage() == ApplicationInfo::SideStage) {
215− application->setStage(ApplicationInfo::MainStage);
216− }
217− add(application);
252+ add(application);
218− application->setState(ApplicationInfo::Running);
219
253
220 return application;
254 return application;
221}
255}
@@ -238,27 +272,6 @@ bool ApplicationManager::stopApplication(const QString &appId)
238 return true;
272 return true;
239}
273}
240
274
241−bool ApplicationManager::updateScreenshot(const QString &appId)
242−{
243− int idx = -1;
244− ApplicationInfo *application = nullptr;
245− for (int i = 0; i < m_availableApplications.count(); ++i) {
246− application = m_availableApplications.at(i);
247− if (application->appId() == appId) {
248− idx = i;
249− break;
250− }
251− }
252−
253− if (idx == -1) {
254− return false;
255− }
256−
257− QModelIndex appIndex = index(idx);
258− Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << RoleScreenshot);
259− return true;
260−}
261−
262QString ApplicationManager::focusedApplicationId() const {
275QString ApplicationManager::focusedApplicationId() const {
263 for (ApplicationInfo *app : m_runningApplications) {
276 for (ApplicationInfo *app : m_runningApplications) {
264 if (app->focused()) {
277 if (app->focused()) {
@@ -293,32 +306,18 @@ bool ApplicationManager::focusApplication(const QString &appId)
293 if (application == nullptr)
306 if (application == nullptr)
294 return false;
307 return false;
295
308
296− if (application->stage() == ApplicationInfo::MainStage) {
297− // unfocus currently focused mainstage app
309+ // unfocus currently focused app
298− for (ApplicationInfo *app : m_runningApplications) {
299− if (app->focused() && app->stage() == ApplicationInfo::MainStage) {
300− app->setFocused(false);
301− app->setState(ApplicationInfo::Suspended);
302− }
303− }
304−
305− // focus this app
306− application->setFocused(true);
307− application->setState(ApplicationInfo::Running);
308− } else if (application->stage() == ApplicationInfo::SideStage) {
309− // unfocus currently focused sidestage app
310− for (ApplicationInfo *app : m_runningApplications) {
310+ for (ApplicationInfo *app : m_runningApplications) {
311− if (app->focused() && app->stage() == ApplicationInfo::SideStage) {
311+ if (app->focused()) {
312− app->setFocused(false);
312+ app->setFocused(false);
313− app->setState(ApplicationInfo::Suspended);
313+ app->setState(ApplicationInfo::Suspended);
314− }
315 }
314 }
316−
317− // focus this app
318− application->setFocused(true);
319− application->setState(ApplicationInfo::Running);
320 }
315 }
321
316
317+ // focus this app
318+ application->setFocused(true);
319+ application->setState(ApplicationInfo::Running);
320+
322 // move app to top of stack
321 // move app to top of stack
323 move(m_runningApplications.indexOf(application), 0);
322 move(m_runningApplications.indexOf(application), 0);
324 Q_EMIT focusedApplicationIdChanged();
323 Q_EMIT focusedApplicationIdChanged();
@@ -341,12 +340,6 @@ void ApplicationManager::unfocusCurrentApplication()
341 Q_EMIT focusedApplicationIdChanged();
340 Q_EMIT focusedApplicationIdChanged();
342}
341}
343
342
344−void ApplicationManager::generateQmlStrings(ApplicationInfo *application)
345−{
346− application->setScreenshot(QString("file://%1/Dash/graphics/phone/screenshots/%2@12.png").arg(qmlDirectory())
347− .arg(application->icon().toString()));
348−}
349−
350void ApplicationManager::buildListOfAvailableApplications()
343void ApplicationManager::buildListOfAvailableApplications()
351{
344{
352 ApplicationInfo *application;
345 ApplicationInfo *application;
@@ -354,128 +347,120 @@ void ApplicationManager::buildListOfAvailableApplications()
354 application = new ApplicationInfo(this);
347 application = new ApplicationInfo(this);
355 application->setAppId("unity8-dash");
348 application->setAppId("unity8-dash");
356 application->setName("Unity 8 Mock Dash");
349 application->setName("Unity 8 Mock Dash");
357− application->setIcon(QUrl("unity8-dash"));
350+ application->setScreenshotId("unity8-dash");
358 application->setStage(ApplicationInfo::MainStage);
351 application->setStage(ApplicationInfo::MainStage);
359− generateQmlStrings(application);
360 m_availableApplications.append(application);
352 m_availableApplications.append(application);
361
353
362 application = new ApplicationInfo(this);
354 application = new ApplicationInfo(this);
363 application->setAppId("dialer-app");
355 application->setAppId("dialer-app");
364 application->setName("Dialer");
356 application->setName("Dialer");
365− application->setIcon(QUrl("dialer"));
357+ application->setScreenshotId("dialer");
358+ application->setIconId("dialer-app");
366 application->setStage(ApplicationInfo::SideStage);
359 application->setStage(ApplicationInfo::SideStage);
367− generateQmlStrings(application);
368 m_availableApplications.append(application);
360 m_availableApplications.append(application);
369
361
370 application = new ApplicationInfo(this);
362 application = new ApplicationInfo(this);
371 application->setAppId("camera-app");
363 application->setAppId("camera-app");
372 application->setName("Camera");
364 application->setName("Camera");
373− application->setIcon(QUrl("camera"));
365+ application->setScreenshotId("camera");
366+ application->setIconId("camera");
374 application->setFullscreen(true);
367 application->setFullscreen(true);
375− generateQmlStrings(application);
376 m_availableApplications.append(application);
368 m_availableApplications.append(application);
377
369
378 application = new ApplicationInfo(this);
370 application = new ApplicationInfo(this);
379 application->setAppId("gallery-app");
371 application->setAppId("gallery-app");
380 application->setName("Gallery");
372 application->setName("Gallery");
381− application->setIcon(QUrl("gallery"));
373+ application->setScreenshotId("gallery");
382− generateQmlStrings(application);
374+ application->setIconId("gallery");
375+ application->setFullscreen(true);
383 m_availableApplications.append(application);
376 m_availableApplications.append(application);
384
377
385 application = new ApplicationInfo(this);
378 application = new ApplicationInfo(this);
386 application->setAppId("facebook-webapp");
379 application->setAppId("facebook-webapp");
387 application->setName("Facebook");
380 application->setName("Facebook");
388− application->setIcon(QUrl("facebook"));
381+ application->setScreenshotId("facebook");
382+ application->setIconId("facebook");
389 application->setStage(ApplicationInfo::SideStage);
383 application->setStage(ApplicationInfo::SideStage);
390− generateQmlStrings(application);
391 m_availableApplications.append(application);
384 m_availableApplications.append(application);
392
385
393 application = new ApplicationInfo(this);
386 application = new ApplicationInfo(this);
394 application->setAppId("webbrowser-app");
387 application->setAppId("webbrowser-app");
395 application->setFullscreen(true);
388 application->setFullscreen(true);
396 application->setName("Browser");
389 application->setName("Browser");
397− application->setIcon(QUrl("browser"));
390+ application->setScreenshotId("browser");
398− generateQmlStrings(application);
391+ application->setIconId("browser");
399 m_availableApplications.append(application);
392 m_availableApplications.append(application);
400
393
401 application = new ApplicationInfo(this);
394 application = new ApplicationInfo(this);
402 application->setAppId("twitter-webapp");
395 application->setAppId("twitter-webapp");
403 application->setName("Twitter");
396 application->setName("Twitter");
404− application->setIcon(QUrl("twitter"));
397+ application->setScreenshotId("twitter");
398+ application->setIconId("twitter");
405 application->setStage(ApplicationInfo::SideStage);
399 application->setStage(ApplicationInfo::SideStage);
400+ m_availableApplications.append(application);
401+
402+ application = new ApplicationInfo(this);
403+ application->setAppId("map");
404+ application->setName("Map");
406− generateQmlStrings(application);
405+ application->setIconId("map");
406+ application->setScreenshotId("map");
407 m_availableApplications.append(application);
407 m_availableApplications.append(application);
408
408
409 application = new ApplicationInfo(this);
409 application = new ApplicationInfo(this);
410 application->setAppId("gmail-webapp");
410 application->setAppId("gmail-webapp");
411 application->setName("GMail");
411 application->setName("GMail");
412− application->setIcon(QUrl("gmail"));
412+ application->setIconId("gmail");
413 m_availableApplications.append(application);
413 m_availableApplications.append(application);
414
414
415 application = new ApplicationInfo(this);
415 application = new ApplicationInfo(this);
416 application->setAppId("ubuntu-weather-app");
416 application->setAppId("ubuntu-weather-app");
417 application->setName("Weather");
417 application->setName("Weather");
418− application->setIcon(QUrl("weather"));
418+ application->setIconId("weather");
419 application->setStage(ApplicationInfo::SideStage);
419 application->setStage(ApplicationInfo::SideStage);
420− generateQmlStrings(application);
421 m_availableApplications.append(application);
420 m_availableApplications.append(application);
422
421
423 application = new ApplicationInfo(this);
422 application = new ApplicationInfo(this);
424 application->setAppId("notes-app");
423 application->setAppId("notes-app");
425 application->setName("Notepad");
424 application->setName("Notepad");
426− application->setIcon(QUrl("notepad"));
425+ application->setIconId("notepad");
427 application->setStage(ApplicationInfo::SideStage);
426 application->setStage(ApplicationInfo::SideStage);
428 m_availableApplications.append(application);
427 m_availableApplications.append(application);
429
428
430 application = new ApplicationInfo(this);
429 application = new ApplicationInfo(this);
431 application->setAppId("calendar-app");
430 application->setAppId("calendar-app");
432 application->setName("Calendar");
431 application->setName("Calendar");
433− application->setIcon(QUrl("calendar"));
432+ application->setIconId("calendar");
434 application->setStage(ApplicationInfo::SideStage);
433 application->setStage(ApplicationInfo::SideStage);
435 m_availableApplications.append(application);
434 m_availableApplications.append(application);
436
435
437− application = new ApplicationInfo(this);
438− application->setAppId("mediaplayer-app");
439− application->setName("Media Player");
440− application->setIcon(QUrl("mediaplayer-app"));
441− application->setFullscreen(true);
442− m_availableApplications.append(application);
443−
444 application = new ApplicationInfo(this);
436 application = new ApplicationInfo(this);
445 application->setAppId("evernote");
437 application->setAppId("evernote");
446 application->setName("Evernote");
438 application->setName("Evernote");
447− application->setIcon(QUrl("evernote"));
439+ application->setIconId("evernote");
448− m_availableApplications.append(application);
449−
450− application = new ApplicationInfo(this);
451− application->setAppId("map");
452− application->setName("Map");
453− application->setIcon(QUrl("map"));
454− generateQmlStrings(application);
455 m_availableApplications.append(application);
440 m_availableApplications.append(application);
456
441
457 application = new ApplicationInfo(this);
442 application = new ApplicationInfo(this);
458 application->setAppId("pinterest");
443 application->setAppId("pinterest");
459 application->setName("Pinterest");
444 application->setName("Pinterest");
460− application->setIcon(QUrl("pinterest"));
445+ application->setIconId("pinterest");
461 m_availableApplications.append(application);
446 m_availableApplications.append(application);
462
447
463 application = new ApplicationInfo(this);
448 application = new ApplicationInfo(this);
464 application->setAppId("soundcloud");
449 application->setAppId("soundcloud");
465 application->setName("SoundCloud");
450 application->setName("SoundCloud");
466− application->setIcon(QUrl("soundcloud"));
451+ application->setIconId("soundcloud");
467 m_availableApplications.append(application);
452 m_availableApplications.append(application);
468
453
469 application = new ApplicationInfo(this);
454 application = new ApplicationInfo(this);
470 application->setAppId("wikipedia");
455 application->setAppId("wikipedia");
471 application->setName("Wikipedia");
456 application->setName("Wikipedia");
472− application->setIcon(QUrl("wikipedia"));
457+ application->setIconId("wikipedia");
473 m_availableApplications.append(application);
458 m_availableApplications.append(application);
474
459
475 application = new ApplicationInfo(this);
460 application = new ApplicationInfo(this);
476 application->setAppId("youtube");
461 application->setAppId("youtube");
477 application->setName("YouTube");
462 application->setName("YouTube");
478− application->setIcon(QUrl("youtube"));
463+ application->setIconId("youtube");
479 m_availableApplications.append(application);
464 m_availableApplications.append(application);
480}
465}
481
466
tests/mocks/Unity/Application/ApplicationManager.h+8−4
@@ -20,6 +20,7 @@
20#include <QObject>
20#include <QObject>
21#include <QList>
21#include <QList>
22#include <QStringList>
22#include <QStringList>
23+#include <QTimer>
23#include "ApplicationInfo.h"
24#include "ApplicationInfo.h"
24
25
25// unity-api
26// unity-api
@@ -51,7 +52,7 @@ class ApplicationManager : public ApplicationManagerInterface {
51 static ApplicationManager *singleton();
52 static ApplicationManager *singleton();
52
53
53 enum MoreRoles {
54 enum MoreRoles {
54− RoleSurface = RoleScreenshot+1,
55+ RoleSurface = RoleFocused+1,
55 RoleFullscreen,
56 RoleFullscreen,
56 RoleApplication,
57 RoleApplication,
57 };
58 };
@@ -97,7 +98,6 @@ class ApplicationManager : public ApplicationManagerInterface {
97 Q_INVOKABLE ApplicationInfo *startApplication(const QString &appId, const QStringList &arguments = QStringList()) override;
98 Q_INVOKABLE ApplicationInfo *startApplication(const QString &appId, const QStringList &arguments = QStringList()) override;
98 Q_INVOKABLE ApplicationInfo *startApplication(const QString &appId, ExecFlags flags, const QStringList &arguments = QStringList());
99 Q_INVOKABLE ApplicationInfo *startApplication(const QString &appId, ExecFlags flags, const QStringList &arguments = QStringList());
99 Q_INVOKABLE bool stopApplication(const QString &appId) override;
100 Q_INVOKABLE bool stopApplication(const QString &appId) override;
100− Q_INVOKABLE bool updateScreenshot(const QString &appId) override;
101
101
102 QString focusedApplicationId() const override;
102 QString focusedApplicationId() const override;
103 bool suspended() const;
103 bool suspended() const;
@@ -105,6 +105,7 @@ class ApplicationManager : public ApplicationManagerInterface {
105
105
106 // Only for testing
106 // Only for testing
107 Q_INVOKABLE QStringList availableApplications();
107 Q_INVOKABLE QStringList availableApplications();
108+ Q_INVOKABLE ApplicationInfo* add(QString appId);
108
109
109 QModelIndex findIndex(ApplicationInfo* application);
110 QModelIndex findIndex(ApplicationInfo* application);
110
111
@@ -115,15 +116,18 @@ class ApplicationManager : public ApplicationManagerInterface {
115 void focusRequested(const QString &appId);
116 void focusRequested(const QString &appId);
116 void emptyChanged(bool empty);
117 void emptyChanged(bool empty);
117
118
119+ private Q_SLOTS:
120+ void onWindowCreatedTimerTimeout();
121+
118 private:
122 private:
119 void add(ApplicationInfo *application);
123 void add(ApplicationInfo *application);
120 void remove(ApplicationInfo* application);
124 void remove(ApplicationInfo* application);
121− void showApplicationWindow(ApplicationInfo *application);
122 void buildListOfAvailableApplications();
125 void buildListOfAvailableApplications();
123− void generateQmlStrings(ApplicationInfo *application);
126+ void onWindowCreated();
124 bool m_suspended;
127 bool m_suspended;
125 QList<ApplicationInfo*> m_runningApplications;
128 QList<ApplicationInfo*> m_runningApplications;
126 QList<ApplicationInfo*> m_availableApplications;
129 QList<ApplicationInfo*> m_availableApplications;
130+ QTimer m_windowCreatedTimer;
127
131
128 static ApplicationManager *the_application_manager;
132 static ApplicationManager *the_application_manager;
129};
133};
tests/mocks/Unity/Application/ApplicationScreenshotProvider.cpp+2−4
@@ -44,11 +44,9 @@ QImage ApplicationScreenshotProvider::requestImage(const QString &imageId, QSize
44 return QImage();
44 return QImage();
45 }
45 }
46
46
47− QString filePath = QString("%1/Dash/graphics/phone/screenshots/%2@12.png").arg(qmlDirectory()).arg(app->icon().toString());
48−
49 QImage image;
47 QImage image;
50− if (!image.load(filePath)) {
48+ if (!image.load(app->screenshot())) {
51− qWarning() << "failed loading app image" << filePath;
49+ qWarning() << "failed loading app image" << app->screenshot();
52 }
50 }
53
51
54
52
tests/mocks/Unity/Application/CMakeLists.txt+1−3
@@ -1,15 +1,13 @@
1−pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application)
1+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=3)
2
2
3set(FakeUnityApplicationQml_SOURCES
3set(FakeUnityApplicationQml_SOURCES
4 plugin.cpp
4 plugin.cpp
5 ApplicationDBusAdaptor.cpp
5 ApplicationDBusAdaptor.cpp
6 ApplicationInfo.cpp
6 ApplicationInfo.cpp
7− ApplicationImage.cpp
8 ApplicationManager.cpp
7 ApplicationManager.cpp
9 ApplicationScreenshotProvider.cpp
8 ApplicationScreenshotProvider.cpp
10 MirSurfaceItem.cpp
9 MirSurfaceItem.cpp
11 SurfaceManager.cpp
10 SurfaceManager.cpp
12− VirtualKeyboard.cpp
13 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
11 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
14 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
12 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
15)
13)
tests/mocks/Unity/Application/InputFilterArea.qmldeleted−21
@@ -1,21 +0,0 @@
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−
17−import QtQuick 2.0;
18−
19−Item {
20− property bool blockInput: false
21−}
tests/mocks/Unity/Application/MirSurfaceItem.cpp+87−16
@@ -17,33 +17,67 @@
17#include "MirSurfaceItem.h"
17#include "MirSurfaceItem.h"
18#include "ApplicationInfo.h"
18#include "ApplicationInfo.h"
19
19
20−#include <QPainter>
20+#include <paths.h>
21+
22+#include <QGuiApplication>
23+#include <QQuickView>
24+#include <QQmlProperty>
21#include <QQmlEngine>
25#include <QQmlEngine>
26+#include <QString>
27+
28+#include <QDebug>
22
29
23MirSurfaceItem::MirSurfaceItem(const QString& name,
30MirSurfaceItem::MirSurfaceItem(const QString& name,
24 MirSurfaceItem::Type type,
31 MirSurfaceItem::Type type,
25 MirSurfaceItem::State state,
32 MirSurfaceItem::State state,
26 const QUrl& screenshot,
33 const QUrl& screenshot,
34+ const QString &qmlFilePath,
27 QQuickItem *parent)
35 QQuickItem *parent)
28− : QQuickPaintedItem(parent)
36+ : QQuickItem(parent)
29 , m_application(nullptr)
37 , m_application(nullptr)
30 , m_name(name)
38 , m_name(name)
31 , m_type(type)
39 , m_type(type)
32 , m_state(state)
40 , m_state(state)
33− , m_img(screenshot.isLocalFile() ? screenshot.toLocalFile() : screenshot.toString())
34 , m_parentSurface(nullptr)
41 , m_parentSurface(nullptr)
35− , m_haveInputMethod(false)
42+ , m_qmlItem(nullptr)
43+ , m_screenshotUrl(screenshot)
36{
44{
37 qDebug() << "MirSurfaceItem::MirSurfaceItem() " << this->name();
45 qDebug() << "MirSurfaceItem::MirSurfaceItem() " << this->name();
38
46
39 QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
47 QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
40
48
41− // The virtual keyboard (input method) has a big transparent area so that
42− // content behind it show through
43− setFillColor(Qt::transparent);
44−
45 connect(this, &QQuickItem::focusChanged,
49 connect(this, &QQuickItem::focusChanged,
46 this, &MirSurfaceItem::onFocusChanged);
50 this, &MirSurfaceItem::onFocusChanged);
51+
52+ // The assumptions I make here really should hold.
53+ QQuickView *quickView =
54+ qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
55+
56+ QString qmlComponentFilePath;
57+ if (!qmlFilePath.isEmpty()) {
58+ qmlComponentFilePath.append(qmlFilePath);
59+ } else {
60+ qmlComponentFilePath = QString("%1/Unity/Application/MirSurfaceItem.qml")
61+ .arg(mockPluginsDir());
62+ }
63+
64+ m_qmlContentComponent = new QQmlComponent(quickView->engine(), qmlComponentFilePath);
65+
66+ switch (m_qmlContentComponent->status()) {
67+ case QQmlComponent::Ready:
68+ createQmlContentItem();
69+ break;
70+ case QQmlComponent::Loading:
71+ connect(m_qmlContentComponent, &QQmlComponent::statusChanged,
72+ this, &MirSurfaceItem::onComponentStatusChanged);
73+ break;
74+ case QQmlComponent::Error:
75+ printComponentErrors();
76+ qFatal("MirSurfaceItem: failed to create content component.");
77+ break;
78+ default:
79+ qFatal("MirSurfaceItem: Unhandled component status");
80+ }
47}
81}
48
82
49MirSurfaceItem::~MirSurfaceItem()
83MirSurfaceItem::~MirSurfaceItem()
@@ -61,10 +95,11 @@ MirSurfaceItem::~MirSurfaceItem()
61 m_application->removeSurface(this);
95 m_application->removeSurface(this);
62}
96}
63
97
64−void MirSurfaceItem::paint(QPainter * painter)
98+void MirSurfaceItem::printComponentErrors()
65{
99{
66− if (!m_img.isNull()) {
100+ QList<QQmlError> errors = m_qmlContentComponent->errors();
67− painter->drawImage(contentsBoundingRect(), m_img, QRect(QPoint(0,0), m_img.size()));
101+ for (int i = 0; i < errors.count(); ++i) {
102+ qDebug() << errors[i];
68 }
103 }
69}
104}
70
105
@@ -155,19 +190,24 @@ MirSurfaceItem* MirSurfaceItem::childSurfaceAt(QQmlListProperty<MirSurfaceItem>
155}
190}
156
191
157
192
158−void MirSurfaceItem::touchEvent(QTouchEvent * event)
193+void MirSurfaceItem::onQmlWantInputMethodChanged()
159{
194{
160− if (event->type() == QEvent::TouchBegin && hasFocus()) {
195+ QQmlProperty prop(m_qmlItem, "wantInputMethod");
196+ bool wantInputMethod = prop.read().toBool();
197+
198+ if (hasFocus() && wantInputMethod) {
161 Q_EMIT inputMethodRequested();
199 Q_EMIT inputMethodRequested();
162− m_haveInputMethod = true;
163 }
200 }
164}
201}
165
202
166void MirSurfaceItem::onFocusChanged()
203void MirSurfaceItem::onFocusChanged()
167{
204{
168− if (!hasFocus() && m_haveInputMethod) {
205+ QQmlProperty prop(m_qmlItem, "wantInputMethod");
206+ bool wantInputMethod = prop.read().toBool();
207+
208+ if (!hasFocus() && wantInputMethod) {
169 Q_EMIT inputMethodDismissed();
209 Q_EMIT inputMethodDismissed();
170− m_haveInputMethod = false;
210+ prop.write(QVariant::fromValue(false));
171 }
211 }
172}
212}
173
213
@@ -178,3 +218,34 @@ void MirSurfaceItem::setState(MirSurfaceItem::State newState)
178 Q_EMIT stateChanged(newState);
218 Q_EMIT stateChanged(newState);
179 }
219 }
180}
220}
221+
222+void MirSurfaceItem::onComponentStatusChanged(QQmlComponent::Status status)
223+{
224+ if (status == QQmlComponent::Ready) {
225+ createQmlContentItem();
226+ }
227+}
228+
229+void MirSurfaceItem::createQmlContentItem()
230+{
231+ qDebug() << "MirSurfaceItem::createQmlContentItem()";
232+
233+ m_qmlItem = qobject_cast<QQuickItem*>(m_qmlContentComponent->create());
234+ m_qmlItem->setParentItem(this);
235+
236+ setImplicitWidth(m_qmlItem->implicitWidth());
237+ setImplicitHeight(m_qmlItem->implicitHeight());
238+
239+ {
240+ QQmlProperty screenshotSource(m_qmlItem, "screenshotSource");
241+ screenshotSource.write(QVariant::fromValue(m_screenshotUrl));
242+ }
243+
244+ {
245+ QQmlProperty prop(m_qmlItem, "wantInputMethod");
246+ if (prop.type() == QQmlProperty::Property) {
247+ bool ok = prop.connectNotifySignal(this, SLOT(onQmlWantInputMethodChanged()));
248+ if (!ok) qCritical("MirSurfaceItem: failed to connect to wantInputMethod notify signal");
249+ }
250+ }
251+}
tests/mocks/Unity/Application/MirSurfaceItem.h+14−11
@@ -17,12 +17,13 @@
17#ifndef MIRSURFACEITEM_H
17#ifndef MIRSURFACEITEM_H
18#define MIRSURFACEITEM_H
18#define MIRSURFACEITEM_H
19
19
20−#include <QQuickPaintedItem>
20+#include <QQuickItem>
21−#include <QImage>
21+#include <QQmlComponent>
22+#include <QUrl>
22
23
23class ApplicationInfo;
24class ApplicationInfo;
24
25
25−class MirSurfaceItem : public QQuickPaintedItem
26+class MirSurfaceItem : public QQuickItem
26{
27{
27 Q_OBJECT
28 Q_OBJECT
28 Q_ENUMS(Type)
29 Q_ENUMS(Type)
@@ -59,6 +60,7 @@ public:
59 Type type,
60 Type type,
60 State state,
61 State state,
61 const QUrl& screenshot,
62 const QUrl& screenshot,
63+ const QString &qmlFilePath = QString(),
62 QQuickItem *parent = 0);
64 QQuickItem *parent = 0);
63 ~MirSurfaceItem();
65 ~MirSurfaceItem();
64
66
@@ -76,9 +78,6 @@ public:
76 Q_INVOKABLE void setState(State newState);
78 Q_INVOKABLE void setState(State newState);
77 Q_INVOKABLE void release();
79 Q_INVOKABLE void release();
78
80
79− void paint(QPainter * painter) override;
80− void touchEvent(QTouchEvent * event) override;
81−
82Q_SIGNALS:
81Q_SIGNALS:
83 void typeChanged(Type);
82 void typeChanged(Type);
84 void stateChanged(State);
83 void stateChanged(State);
@@ -93,9 +92,8 @@ Q_SIGNALS:
93
92
94private Q_SLOTS:
93private Q_SLOTS:
95 void onFocusChanged();
94 void onFocusChanged();
96−
95+ void onComponentStatusChanged(QQmlComponent::Status status);
97−protected:
96+ void onQmlWantInputMethodChanged();
98− const QImage &screenshotImage() { return m_img; }
99
97
100private:
98private:
101 void addChildSurface(MirSurfaceItem* surface);
99 void addChildSurface(MirSurfaceItem* surface);
@@ -105,15 +103,20 @@ private:
105 static int childSurfaceCount(QQmlListProperty<MirSurfaceItem> *prop);
103 static int childSurfaceCount(QQmlListProperty<MirSurfaceItem> *prop);
106 static MirSurfaceItem* childSurfaceAt(QQmlListProperty<MirSurfaceItem> *prop, int index);
104 static MirSurfaceItem* childSurfaceAt(QQmlListProperty<MirSurfaceItem> *prop, int index);
107
105
106+ void createQmlContentItem();
107+ void printComponentErrors();
108+
108 ApplicationInfo* m_application;
109 ApplicationInfo* m_application;
109 const QString m_name;
110 const QString m_name;
110 const Type m_type;
111 const Type m_type;
111 State m_state;
112 State m_state;
112− const QImage m_img;
113
113
114 MirSurfaceItem* m_parentSurface;
114 MirSurfaceItem* m_parentSurface;
115 QList<MirSurfaceItem*> m_children;
115 QList<MirSurfaceItem*> m_children;
116− bool m_haveInputMethod;
116+
117+ QQmlComponent *m_qmlContentComponent;
118+ QQuickItem *m_qmlItem;
119+ QUrl m_screenshotUrl;
117};
120};
118
121
119Q_DECLARE_METATYPE(MirSurfaceItem*)
122Q_DECLARE_METATYPE(MirSurfaceItem*)
tests/mocks/Unity/Application/MirSurfaceItem.qmlnew file+57
@@ -0,0 +1,57 @@
1+/*
2+ * Copyright 2014 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+
17+import QtQuick 2.0
18+
19+Rectangle {
20+ objectName: "fakeSurfaceQML"
21+ id: root
22+ color: "pink"
23+
24+ anchors.fill: parent
25+
26+ implicitWidth: units.gu(40)
27+ implicitHeight: units.gu(70)
28+
29+ property alias screenshotSource: screenshotImage.source
30+
31+ property bool wantInputMethod: false
32+
33+ property int touchPressCount: 0
34+ property int touchReleaseCount: 0
35+
36+ Image {
37+ id: screenshotImage
38+ anchors.fill: parent
39+ fillMode: Image.Stretch
40+ }
41+
42+ Text {
43+ anchors.fill: parent
44+ text: "SURFACE"
45+ color: "yellow"
46+ font.bold: true
47+ fontSizeMode: Text.Fit
48+ minimumPixelSize: 10; font.pixelSize: 200
49+ verticalAlignment: Text.AlignVCenter
50+ }
51+
52+ MultiPointTouchArea {
53+ anchors.fill: parent
54+ onPressed: { root.wantInputMethod = true; root.touchPressCount++; }
55+ onReleased: { root.touchReleaseCount++; }
56+ }
57+}
tests/mocks/Unity/Application/OSKController.qmldeleted−20
@@ -1,20 +0,0 @@
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−
17−import QtQuick 2.0;
18−
19−Item {
20−}
tests/mocks/Unity/Application/SurfaceManager.cpp+14−3
@@ -16,9 +16,8 @@
16
16
17#include "SurfaceManager.h"
17#include "SurfaceManager.h"
18
18
19+#include <paths.h>
19#include "MirSurfaceItem.h"
20#include "MirSurfaceItem.h"
20−#include "VirtualKeyboard.h"
21−
22
21
23SurfaceManager *SurfaceManager::the_surface_manager = nullptr;
22SurfaceManager *SurfaceManager::the_surface_manager = nullptr;
24
23
@@ -66,7 +65,19 @@ void SurfaceManager::hideInputMethod()
66MirSurfaceItem *SurfaceManager::inputMethodSurface()
65MirSurfaceItem *SurfaceManager::inputMethodSurface()
67{
66{
68 if (!m_virtualKeyboard) {
67 if (!m_virtualKeyboard) {
68+
69+ QString screenshotPath = QString("file://%1/Dash/graphics/phone/screenshots/vkb_portrait.png")
70+ .arg(qmlDirectory());
71+
72+ QString qmlFilePath = QString("%1/Unity/Application/VirtualKeyboard.qml")
73+ .arg(mockPluginsDir());
74+
75+ m_virtualKeyboard = new MirSurfaceItem(
76+ "input-method",
77+ MirSurfaceItem::InputMethod,
69− m_virtualKeyboard = new VirtualKeyboard(MirSurfaceItem::Minimized);
78+ MirSurfaceItem::Minimized,
79+ screenshotPath,
80+ qmlFilePath);
70 Q_EMIT surfaceCreated(m_virtualKeyboard);
81 Q_EMIT surfaceCreated(m_virtualKeyboard);
71 }
82 }
72 return m_virtualKeyboard;
83 return m_virtualKeyboard;
tests/mocks/Unity/Application/SurfaceManager.h+1−2
@@ -20,7 +20,6 @@
20#include <QObject>
20#include <QObject>
21
21
22class MirSurfaceItem;
22class MirSurfaceItem;
23−class VirtualKeyboard;
24
23
25class SurfaceManager : public QObject
24class SurfaceManager : public QObject
26{
25{
@@ -48,7 +47,7 @@ private Q_SLOTS:
48
47
49private:
48private:
50 static SurfaceManager *the_surface_manager;
49 static SurfaceManager *the_surface_manager;
51− VirtualKeyboard *m_virtualKeyboard;
50+ MirSurfaceItem *m_virtualKeyboard;
52};
51};
53
52
54#endif // SURFACEMANAGER_H
53#endif // SURFACEMANAGER_H
tests/mocks/Unity/Application/VirtualKeyboard.cppdeleted−55
@@ -1,55 +0,0 @@
1−/*
2− * Copyright (C) 2014 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−
17−#include "VirtualKeyboard.h"
18−
19−#include <paths.h>
20−
21−VirtualKeyboard::VirtualKeyboard(State state, QQuickItem *parent)
22− : MirSurfaceItem("input-method",
23− MirSurfaceItem::InputMethod,
24− state,
25− QString("file://%1/Dash/graphics/phone/screenshots/vkb_portrait.png").arg(qmlDirectory()),
26− parent)
27−{
28−}
29−
30−void VirtualKeyboard::touchEvent(QTouchEvent *event)
31−{
32− if (event->type() == QEvent::TouchBegin && !hasTouchInsideKeyboard(event)) {
33− event->ignore();
34− }
35−}
36−
37−bool VirtualKeyboard::hasTouchInsideKeyboard(QTouchEvent *event)
38−{
39− const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
40− for (int i = 0; i < touchPoints.count(); ++i) {
41− QPoint pos = touchPoints.at(i).pos().toPoint();
42−
43− // Map to image coords
44− int imageX = (int)( ( ((qreal)pos.x()) / width() ) * ((qreal)screenshotImage().size().width()) );
45− int imageY = (int)( ( ((qreal)pos.y()) / height() ) * ((qreal)screenshotImage().size().height()) );
46−
47− QRgb pixelHit = screenshotImage().pixel(imageX, imageY);
48−
49− // The keyboard depicted in the image is in its opaque part.
50− if (qAlpha(pixelHit) != 0) {
51− return true;
52− }
53− }
54− return false;
55−}
tests/mocks/Unity/Application/VirtualKeyboard.hdeleted−35
@@ -1,35 +0,0 @@
1−/*
2− * Copyright (C) 2014 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−
17−#ifndef VIRTUALKEYBOARD_H
18−#define VIRTUALKEYBOARD_H
19−
20−#include "MirSurfaceItem.h"
21−
22−class VirtualKeyboard : public MirSurfaceItem
23−{
24− Q_OBJECT
25−public:
26− VirtualKeyboard(State state,
27− QQuickItem *parent = 0);
28−
29− void touchEvent(QTouchEvent * event) override;
30−
31−private:
32− bool hasTouchInsideKeyboard(QTouchEvent *event);
33−};
34−
35−#endif // VIRTUALKEYBOARD_H
tests/mocks/Unity/Application/VirtualKeyboard.qmlnew file+40
@@ -0,0 +1,40 @@
1+/*
2+ * Copyright 2014 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+
17+import QtQuick 2.0
18+
19+Item {
20+ implicitWidth: units.gu(40)
21+ implicitHeight: units.gu(70)
22+
23+ anchors.fill: parent
24+
25+ property alias screenshotSource: screenshotImage.source
26+
27+ property bool landscape: width > height
28+
29+ Image {
30+ id: screenshotImage
31+ height: landscape ? parent.height * 0.4 : width * 0.6
32+ anchors.bottom: parent.bottom
33+ anchors.left: parent.left
34+ anchors.right: parent.right
35+
36+ MultiPointTouchArea {
37+ anchors.fill: parent
38+ }
39+ }
40+}
tests/mocks/Unity/Application/plugin.cpp−3
@@ -16,7 +16,6 @@
16
16
17#include "plugin.h"
17#include "plugin.h"
18#include "ApplicationInfo.h"
18#include "ApplicationInfo.h"
19−#include "ApplicationImage.h"
20#include "ApplicationManager.h"
19#include "ApplicationManager.h"
21#include "ApplicationScreenshotProvider.h"
20#include "ApplicationScreenshotProvider.h"
22#include "MirSurfaceItem.h"
21#include "MirSurfaceItem.h"
@@ -49,8 +48,6 @@ void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
49 qmlRegisterType<ApplicationInfo>(uri, 0, 1, "ApplicationInfo");
48 qmlRegisterType<ApplicationInfo>(uri, 0, 1, "ApplicationInfo");
50
49
51 qmlRegisterUncreatableType<MirSurfaceItem>(uri, 0, 1, "MirSurfaceItem", "MirSurfaceItem can't be instantiated from QML");
50 qmlRegisterUncreatableType<MirSurfaceItem>(uri, 0, 1, "MirSurfaceItem", "MirSurfaceItem can't be instantiated from QML");
52−
53− qmlRegisterType<ApplicationImage>(uri, 0, 1, "ApplicationImage");
54}
51}
55
52
56void FakeUnityApplicationQmlPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
53void FakeUnityApplicationQmlPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
tests/mocks/Unity/Application/qmldir−3
@@ -1,6 +1,3 @@
1module Unity.Application
1module Unity.Application
2plugin FakeUnityApplicationQml
2plugin FakeUnityApplicationQml
3typeinfo Application.qmltypes
3typeinfo Application.qmltypes
4−
5−InputFilterArea 0.1 InputFilterArea.qml
6−OSKController 0.1 OSKController.qml
tests/plugins/Unity/Launcher/CMakeLists.txt+1−1
@@ -1,6 +1,6 @@
1pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
1pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
2pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=3)
2pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=3)
3−pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=1)
3+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=3)
4
4
5include_directories(
5include_directories(
6 ${CMAKE_CURRENT_BINARY_DIR}
6 ${CMAKE_CURRENT_BINARY_DIR}
tests/plugins/Unity/Launcher/launchermodeltest.cpp−1
@@ -93,7 +93,6 @@ public:
93 m_list.takeAt(index)->deleteLater();
93 m_list.takeAt(index)->deleteLater();
94 endRemoveRows();
94 endRemoveRows();
95 }
95 }
96− bool updateScreenshot(const QString &appId) { Q_UNUSED(appId); return true; }
97 bool requestFocusApplication(const QString &appId) { Q_UNUSED(appId); return true; }
96 bool requestFocusApplication(const QString &appId) { Q_UNUSED(appId); return true; }
98 bool suspended() const { return false; }
97 bool suspended() const { return false; }
99 void setSuspended(bool) {}
98 void setSuspended(bool) {}
tests/qmltests/CMakeLists.txt+2
@@ -76,4 +76,6 @@ add_qml_test(Panel/Indicators CachedUnityMenuModel ENVIRONMENT "LD_LIBRARY_PATH=
76# These MenuItemFactory tests need the test/mocks/ to come before plugins/
76# These MenuItemFactory tests need the test/mocks/ to come before plugins/
77add_qml_test(Panel/Indicators MenuItemFactory IMPORT_PATHS ${CMAKE_BINARY_DIR}/tests/mocks ${qmltest_DEFAULT_IMPORT_PATHS})
77add_qml_test(Panel/Indicators MenuItemFactory IMPORT_PATHS ${CMAKE_BINARY_DIR}/tests/mocks ${qmltest_DEFAULT_IMPORT_PATHS})
78add_qml_test(Panel/Indicators MessageMenuItemFactory IMPORT_PATHS ${CMAKE_BINARY_DIR}/tests/mocks ${qmltest_DEFAULT_IMPORT_PATHS})
78add_qml_test(Panel/Indicators MessageMenuItemFactory IMPORT_PATHS ${CMAKE_BINARY_DIR}/tests/mocks ${qmltest_DEFAULT_IMPORT_PATHS})
79+add_qml_test(Stages ApplicationWindow ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/libusermetrics:${CMAKE_BINARY_DIR}/tests/mocks/LightDM/single")
79add_qml_test(Stages PhoneStage ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/libusermetrics:${CMAKE_BINARY_DIR}/tests/mocks/LightDM/single")
80add_qml_test(Stages PhoneStage ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/libusermetrics:${CMAKE_BINARY_DIR}/tests/mocks/LightDM/single")
81+add_qml_test(Stages SpreadDelegate ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/libusermetrics:${CMAKE_BINARY_DIR}/tests/mocks/LightDM/single")
tests/qmltests/Stages/tst_ApplicationWindow.qmlnew file+346
@@ -0,0 +1,346 @@
1+/*
2+ * Copyright 2014 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+
17+import QtQuick 2.0
18+import QtTest 1.0
19+import Unity.Test 0.1 as UT
20+import ".."
21+import "../../../qml/Stages"
22+import Ubuntu.Components 0.1
23+import Ubuntu.Components.ListItems 1.0 as ListItem
24+import Unity.Application 0.1
25+
26+Rectangle {
27+ color: "red"
28+ id: root
29+ width: units.gu(70)
30+ height: units.gu(70)
31+
32+ Component.onCompleted: {
33+ root.fakeApplication = ApplicationManager.add("gallery-app");
34+ root.fakeApplication.manualSurfaceCreation = true;
35+ root.fakeApplication.setState(ApplicationInfo.Starting);
36+ }
37+ property QtObject fakeApplication: null
38+
39+ Connections {
40+ target: fakeApplication
41+ onSurfaceChanged: {
42+ surfaceCheckbox.checked = fakeApplication.surface !== null;
43+ }
44+ }
45+
46+ Component {
47+ id: applicationWindowComponent
48+ ApplicationWindow {
49+ anchors.fill: parent
50+ application: fakeApplication
51+ }
52+ }
53+ Loader {
54+ id: applicationWindowLoader
55+ anchors {
56+ top: parent.top
57+ bottom: parent.bottom
58+ left: parent.left
59+ }
60+ width: units.gu(40)
61+ sourceComponent: applicationWindowComponent
62+ }
63+
64+ Rectangle {
65+ color: "white"
66+ anchors {
67+ top: parent.top
68+ bottom: parent.bottom
69+ left: applicationWindowLoader.right
70+ right: parent.right
71+ }
72+
73+ Column {
74+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
75+ spacing: units.gu(1)
76+ Row {
77+ anchors { left: parent.left; right: parent.right }
78+ CheckBox {
79+ id: surfaceCheckbox; checked: false
80+ onCheckedChanged: {
81+ if (applicationWindowLoader.status !== Loader.Ready)
82+ return;
83+
84+ if (checked && !fakeApplication.surface) {
85+ fakeApplication.createSurface();
86+ } else if (!checked && fakeApplication.surface) {
87+ fakeApplication.surface.release();
88+ }
89+ }
90+ }
91+ Label { text: "Surface" }
92+ }
93+ ListItem.ItemSelector {
94+ id: appStateSelector
95+ anchors { left: parent.left; right: parent.right }
96+ text: "Application state"
97+ model: ["Starting",
98+ "Running",
99+ "Suspended",
100+ "Stopped"]
101+ property int selectedApplicationState: {
102+ if (model[selectedIndex] === "Starting") {
103+ return ApplicationInfo.Starting;
104+ } else if (model[selectedIndex] === "Running") {
105+ return ApplicationInfo.Running;
106+ } else if (model[selectedIndex] === "Suspended") {
107+ return ApplicationInfo.Suspended;
108+ } else {
109+ return ApplicationInfo.Stopped;
110+ }
111+ }
112+ onSelectedApplicationStateChanged: {
113+ // state is a read-only property, thus we have to call the setter function
114+ fakeApplication.setState(selectedApplicationState);
115+ }
116+ }
117+ }
118+ }
119+
120+ UT.UnityTestCase {
121+ id: testCase
122+ name: "ApplicationWindow"
123+ when: windowShown
124+
125+ // just to make them shorter
126+ property int appStarting: ApplicationInfo.Starting
127+ property int appRunning: ApplicationInfo.Running
128+ property int appSuspended: ApplicationInfo.Suspended
129+ property int appStopped: ApplicationInfo.Stopped
130+
131+ function setApplicationState(appState) {
132+ switch (appState) {
133+ case appStarting:
134+ appStateSelector.selectedIndex = 0;
135+ break;
136+ case appRunning:
137+ appStateSelector.selectedIndex = 1;
138+ break;
139+ case appSuspended:
140+ appStateSelector.selectedIndex = 2;
141+ break;
142+ case appStopped:
143+ appStateSelector.selectedIndex = 3;
144+ break;
145+ }
146+ }
147+
148+ // holds some of the internal ApplicationWindow objects we probe during the tests
149+ property var stateGroup: null
150+
151+ function findInterestingApplicationWindowChildren() {
152+ stateGroup = findInvisibleChild(applicationWindowLoader.item, "applicationWindowStateGroup");
153+ verify(stateGroup);
154+ }
155+
156+ function forgetApplicationWindowChildren() {
157+ stateGroup = null;
158+ }
159+
160+ // wait until any transition animation has finished
161+ function waitUntilTransitionsEnd() {
162+ var transitions = stateGroup.transitions;
163+ for (var i = 0; i < transitions.length; ++i) {
164+ var transition = transitions[i];
165+ tryCompare(transition, "running", false, 2000);
166+ }
167+ }
168+
169+ function init() {
170+ findInterestingApplicationWindowChildren();
171+ }
172+
173+ function cleanup() {
174+ forgetApplicationWindowChildren();
175+
176+ // reload our test subject to get it in a fresh state once again
177+ applicationWindowLoader.active = false;
178+
179+ appStateSelector.selectedIndex = 0;
180+ surfaceCheckbox.checked = false;
181+
182+ if (fakeApplication.surface)
183+ fakeApplication.surface.release();
184+
185+ applicationWindowLoader.active = true;
186+ }
187+
188+ function test_showSplashUntilAppFullyInit_data() {
189+ return [
190+ {tag: "state=Running then create surface", swapInitOrder: false},
191+
192+ {tag: "create surface then state=Running", swapInitOrder: true},
193+ ]
194+ }
195+
196+ function test_showSplashUntilAppFullyInit() {
197+ verify(stateGroup.state === "splashScreen");
198+
199+ if (data.swapInitOrder) {
200+ surfaceCheckbox.checked = true;
201+ } else {
202+ setApplicationState(appRunning);
203+ }
204+
205+ verify(stateGroup.state === "splashScreen");
206+
207+ if (data.swapInitOrder) {
208+ setApplicationState(appRunning);
209+ } else {
210+ surfaceCheckbox.checked = true;
211+ }
212+
213+ tryCompare(stateGroup, "state", "surface");
214+ }
215+
216+ function test_suspendedAppShowsSurface() {
217+ surfaceCheckbox.checked = true;
218+ setApplicationState(appRunning);
219+
220+ tryCompare(stateGroup, "state", "surface");
221+
222+ waitUntilTransitionsEnd();
223+
224+ setApplicationState(appSuspended);
225+
226+ verify(stateGroup.state === "surface");
227+ waitUntilTransitionsEnd();
228+ }
229+
230+ function test_killedAppShowsScreenshot() {
231+ surfaceCheckbox.checked = true;
232+ setApplicationState(appRunning);
233+ tryCompare(stateGroup, "state", "surface");
234+
235+ setApplicationState(appSuspended);
236+
237+ verify(stateGroup.state === "surface");
238+ verify(fakeApplication.surface !== null);
239+
240+ // kill it!
241+ setApplicationState(appStopped);
242+
243+ tryCompare(stateGroup, "state", "screenshot");
244+ tryCompare(fakeApplication, "surface", null);
245+ }
246+
247+ function test_restartApp() {
248+ var screenshotImage = findChild(applicationWindowLoader.item, "screenshotImage");
249+
250+ surfaceCheckbox.checked = true;
251+ setApplicationState(appRunning);
252+ tryCompare(stateGroup, "state", "surface");
253+ waitUntilTransitionsEnd();
254+
255+ setApplicationState(appSuspended);
256+
257+ // kill it
258+ setApplicationState(appStopped);
259+
260+ tryCompare(stateGroup, "state", "screenshot");
261+ waitUntilTransitionsEnd();
262+ tryCompare(fakeApplication, "surface", null);
263+
264+ // and restart it
265+ setApplicationState(appStarting);
266+
267+ waitUntilTransitionsEnd();
268+ verify(stateGroup.state === "screenshot");
269+ verify(fakeApplication.surface === null);
270+
271+ setApplicationState(appRunning);
272+
273+ waitUntilTransitionsEnd();
274+ verify(stateGroup.state === "screenshot");
275+
276+ surfaceCheckbox.checked = true;
277+
278+ tryCompare(stateGroup, "state", "surface");
279+ tryCompare(screenshotImage, "status", Image.Null);
280+ }
281+
282+ function test_appCrashed() {
283+ surfaceCheckbox.checked = true;
284+ setApplicationState(appRunning);
285+ tryCompare(stateGroup, "state", "surface");
286+ waitUntilTransitionsEnd();
287+
288+ // oh, it crashed...
289+ setApplicationState(appStopped);
290+
291+ tryCompare(stateGroup, "state", "screenshot");
292+ tryCompare(fakeApplication, "surface", null);
293+ }
294+
295+ function test_keepSurfaceWhileInvisible() {
296+ surfaceCheckbox.checked = true;
297+ setApplicationState(appRunning);
298+ tryCompare(stateGroup, "state", "surface");
299+ waitUntilTransitionsEnd();
300+ verify(fakeApplication.surface !== null);
301+
302+ applicationWindowLoader.item.visible = false;
303+
304+ waitUntilTransitionsEnd();
305+ verify(stateGroup.state === "surface");
306+ verify(fakeApplication.surface !== null);
307+
308+ applicationWindowLoader.item.visible = true;
309+
310+ waitUntilTransitionsEnd();
311+ verify(stateGroup.state === "surface");
312+ verify(fakeApplication.surface !== null);
313+ }
314+
315+ function test_touchesReachSurfaceWhenItsShown() {
316+ setApplicationState(appRunning);
317+ surfaceCheckbox.checked = true;
318+
319+ tryCompare(stateGroup, "state", "surface");
320+
321+ waitUntilTransitionsEnd();
322+
323+ // Because doing stuff in C++ is a PITA we keep the counter in the interal qml impl.
324+ var fakeSurface = findChild(fakeApplication.surface, "fakeSurfaceQML");
325+ fakeSurface.touchPressCount = 0;
326+ fakeSurface.touchReleaseCount = 0;
327+
328+ tap(applicationWindowLoader.item,
329+ applicationWindowLoader.item.width / 2, applicationWindowLoader.item.height / 2);
330+
331+ verify(fakeSurface.touchPressCount === 1);
332+ verify(fakeSurface.touchReleaseCount === 1);
333+ }
334+
335+ function test_showNothingOnSuddenSurfaceLoss() {
336+ surfaceCheckbox.checked = true;
337+ setApplicationState(appRunning);
338+ tryCompare(stateGroup, "state", "surface");
339+ waitUntilTransitionsEnd();
340+
341+ surfaceCheckbox.checked = false;
342+
343+ verify(stateGroup.state === "void");
344+ }
345+ }
346+}
tests/qmltests/Stages/tst_PhoneStage.qml+34−8
@@ -26,14 +26,11 @@ Item {
26 width: units.gu(70)
26 width: units.gu(70)
27 height: units.gu(70)
27 height: units.gu(70)
28
28
29− Rectangle {
30−
31− }
32−
33 PhoneStage {
29 PhoneStage {
34 id: phoneStage
30 id: phoneStage
35 anchors { fill: parent; rightMargin: units.gu(30) }
31 anchors { fill: parent; rightMargin: units.gu(30) }
36 dragAreaWidth: units.gu(2)
32 dragAreaWidth: units.gu(2)
33+ maximizedAppTopMargin: units.gu(3) + units.dp(2)
37 }
34 }
38
35
39 Binding {
36 Binding {
@@ -46,6 +43,7 @@ Item {
46 anchors { fill: parent; leftMargin: phoneStage.width }
43 anchors { fill: parent; leftMargin: phoneStage.width }
47
44
48 Column {
45 Column {
46+ id: buttons
49 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
47 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
50 spacing: units.gu(1)
48 spacing: units.gu(1)
51 Button {
49 Button {
@@ -57,9 +55,33 @@ Item {
57 }
55 }
58 Button {
56 Button {
59 anchors { left: parent.left; right: parent.right }
57 anchors { left: parent.left; right: parent.right }
60− text: "Remove App"
58+ text: "Remove Selected"
61 onClicked: {
59 onClicked: {
62− ApplicationManager.stopApplication(ApplicationManager.get(0).appId)
60+ ApplicationManager.stopApplication(ApplicationManager.get(appList.selectedAppIndex).appId);
61+ }
62+ }
63+ Button {
64+ anchors { left: parent.left; right: parent.right }
65+ text: "Stop Selected"
66+ onClicked: {
67+ ApplicationManager.get(appList.selectedAppIndex).setState(ApplicationInfo.Stopped);
68+ }
69+ }
70+ }
71+ ListView {
72+ id: appList
73+ property int selectedAppIndex
74+ anchors { left: parent.left; right: parent.right; top: buttons.bottom; bottom: parent.bottom }
75+ boundsBehavior: Flickable.StopAtBounds
76+ model: ApplicationManager
77+ delegate: Rectangle {
78+ anchors { left: parent.left; right: parent.right }
79+ height: units.gu(2)
80+ color: appList.selectedAppIndex === model.index ? "red" : "white"
81+ Text { anchors.fill: parent; text: model.appId }
82+ MouseArea {
83+ anchors.fill: parent
84+ onPressed: { appList.selectedAppIndex = model.index; }
63 }
85 }
64 }
86 }
65 }
87 }
@@ -84,7 +106,9 @@ Item {
84 function goToSpread() {
106 function goToSpread() {
85 var spreadView = findChild(phoneStage, "spreadView");
107 var spreadView = findChild(phoneStage, "spreadView");
86
108
109+ // Keep it inside the PhoneStage otherwise the controls on the right side will
110+ // capture the press thus the "- 2" on startX.
87− var startX = phoneStage.width;
111+ var startX = phoneStage.width - 2;
88 var startY = phoneStage.height / 2;
112 var startY = phoneStage.height / 2;
89 var endY = startY;
113 var endY = startY;
90 var endX = units.gu(2);
114 var endX = units.gu(2);
@@ -132,7 +156,9 @@ Item {
132
156
133 var spreadView = findChild(phoneStage, "spreadView");
157 var spreadView = findChild(phoneStage, "spreadView");
134
158
159+ // Keep it inside the PhoneStage otherwise the controls on the right side will
160+ // capture the press thus the "- 2" on startX.
135− var startX = phoneStage.width;
161+ var startX = phoneStage.width - 2;
136 var startY = phoneStage.height / 2;
162 var startY = phoneStage.height / 2;
137 var endY = startY;
163 var endY = startY;
138 var endX = spreadView.width - (spreadView.width * spreadView[data.positionMarker]) - data.offset;
164 var endX = spreadView.width - (spreadView.width * spreadView[data.positionMarker]) - data.offset;
tests/qmltests/Stages/tst_SpreadDelegate.qmlnew file+167
@@ -0,0 +1,167 @@
1+/*
2+ * Copyright 2014 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+
17+import QtQuick 2.0
18+import QtTest 1.0
19+import Unity.Test 0.1 as UT
20+import ".."
21+import "../../../qml/Stages"
22+import Ubuntu.Components 0.1
23+import Ubuntu.Components.ListItems 1.0 as ListItem
24+import Unity.Application 0.1
25+
26+Rectangle {
27+ color: "red"
28+ id: root
29+ width: units.gu(70)
30+ height: units.gu(70)
31+
32+ Component.onCompleted: {
33+ root.fakeApplication = ApplicationManager.startApplication("gallery-app");
34+ }
35+ property QtObject fakeApplication: null
36+
37+ Component {
38+ id: spreadDelegateComponent
39+ SpreadDelegate {
40+ anchors.fill: parent
41+ swipeToCloseEnabled: swipeToCloseCheckbox.checked
42+ closeable: closeableCheckbox.checked
43+ application: fakeApplication
44+ }
45+ }
46+ Loader {
47+ id: spreadDelegateLoader
48+ anchors {
49+ top: parent.top
50+ bottom: parent.bottom
51+ left: parent.left
52+ }
53+ width: units.gu(40)
54+ sourceComponent: spreadDelegateComponent
55+ }
56+
57+ Rectangle {
58+ color: "white"
59+ anchors {
60+ top: parent.top
61+ bottom: parent.bottom
62+ left: spreadDelegateLoader.right
63+ right: parent.right
64+ }
65+
66+ Column {
67+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
68+ spacing: units.gu(1)
69+ Row {
70+ anchors { left: parent.left; right: parent.right }
71+ CheckBox { id: swipeToCloseCheckbox; checked: false; }
72+ Label { text: "swipeToCloseEnabled" }
73+ }
74+ Row {
75+ anchors { left: parent.left; right: parent.right }
76+ CheckBox { id: closeableCheckbox; checked: false }
77+ Label { text: "closeable" }
78+ }
79+ }
80+ }
81+
82+ UT.UnityTestCase {
83+ id: testCase
84+ name: "SpreadDelegate"
85+ when: windowShown
86+
87+ SignalSpy {
88+ id: spyClosedSignal
89+ target: spreadDelegateLoader.item
90+ signalName: "closed"
91+ }
92+
93+ property var dragArea
94+
95+ function init() {
96+ dragArea = findInvisibleChild(spreadDelegateLoader.item, "dragArea");
97+ dragArea.__dateTime = fakeDateTime;
98+ }
99+
100+ function cleanup() {
101+ // reload our test subject to get it in a fresh state once again
102+ spreadDelegateLoader.active = false;
103+ spreadDelegateLoader.active = true;
104+
105+ spyClosedSignal.clear();
106+ }
107+
108+ function test_swipeToClose_data() {
109+ return [
110+ {tag: "swipeToClose=true closeable=true -> appWindow moves away",
111+ swipeToClose: true, closeable: true },
112+
113+ {tag: "swipeToClose=true closeable=alse -> appWindow bounces back",
114+ swipeToClose: true, closeable: false },
115+
116+ {tag: "swipeToClose=false -> appWindow stays put",
117+ swipeToClose: false, closeable: true },
118+ ]
119+ }
120+
121+ function test_swipeToClose(data) {
122+ var appWindowWithShadow = findChild(spreadDelegateLoader.item, "appWindowWithShadow");
123+
124+ verify(appWindowWithShadow.y === 0);
125+
126+ swipeToCloseCheckbox.checked = data.swipeToClose;
127+ closeableCheckbox.checked = data.closeable;
128+
129+ var dragDistance = spreadDelegateLoader.item.height / 2;
130+ var touchX = spreadDelegateLoader.item.width / 2;
131+ var fromY = spreadDelegateLoader.item.height / 2;
132+ var toY = fromY - dragDistance;
133+ touchFlick(spreadDelegateLoader.item,
134+ touchX /* fromX */, fromY, touchX /* toX */, toY,
135+ true /* beginTouch */, false /* endTouch */, dragArea.minSpeedToClose * 1.1 /* speed */);
136+
137+
138+ if (data.swipeToClose) {
139+ verify(appWindowWithShadow.y < 0);
140+ verify(Math.abs(Math.abs(appWindowWithShadow.y) - dragDistance) < units.gu(1));
141+
142+ touchRelease(spreadDelegateLoader.item, touchX, toY - units.gu(1));
143+
144+ waitForCloseAnimationToFinish();
145+
146+ if (data.closeable) {
147+ verify(spyClosedSignal.count === 1);
148+ } else {
149+ verify(spyClosedSignal.count === 0);
150+ tryCompare(appWindowWithShadow, "y", 0);
151+ }
152+
153+ } else {
154+ verify(appWindowWithShadow.y === 0);
155+
156+ touchRelease(spreadDelegateLoader.item, touchX, toY);
157+ }
158+ }
159+
160+ function waitForCloseAnimationToFinish() {
161+ var closeAnimation = findInvisibleChild(spreadDelegateLoader.item, "closeAnimation");
162+ wait(closeAnimation.duration * 1.5);
163+ tryCompare(closeAnimation, "running", false);
164+ }
165+
166+ }
167+}
tests/qmltests/tst_Shell.qml+2−2
@@ -403,11 +403,11 @@ Item {
403 compare(panel.fullscreenMode, false);
403 compare(panel.fullscreenMode, false);
404 ApplicationManager.startApplication("camera-app");
404 ApplicationManager.startApplication("camera-app");
405 tryCompare(panel, "fullscreenMode", true);
405 tryCompare(panel, "fullscreenMode", true);
406− ApplicationManager.startApplication("gallery-app");
406+ ApplicationManager.startApplication("dialer-app");
407 tryCompare(panel, "fullscreenMode", false);
407 tryCompare(panel, "fullscreenMode", false);
408 ApplicationManager.requestFocusApplication("camera-app");
408 ApplicationManager.requestFocusApplication("camera-app");
409 tryCompare(panel, "fullscreenMode", true);
409 tryCompare(panel, "fullscreenMode", true);
410− ApplicationManager.requestFocusApplication("gallery-app");
410+ ApplicationManager.requestFocusApplication("dialer-app");
411 tryCompare(panel, "fullscreenMode", false);
411 tryCompare(panel, "fullscreenMode", false);
412 }
412 }
413
413
tests/utils/modules/Unity/Test/UnityTestCase.qml+5−2
@@ -312,12 +312,15 @@ TestCase {
312 }
312 }
313
313
314 function tap(item, x, y) {
314 function tap(item, x, y) {
315+ var root = fetchRootItem(item)
316+ var rootPoint = item.mapToItem(root, x, y)
317+
315 var event = touchEvent()
318 var event = touchEvent()
316− event.press(0 /* touchId */, x, y)
319+ event.press(0 /* touchId */, rootPoint.x, rootPoint.y)
317 event.commit()
320 event.commit()
318
321
319 event = touchEvent()
322 event = touchEvent()
320− event.release(0 /* touchId */, x, y)
323+ event.release(0 /* touchId */, rootPoint.x, rootPoint.y)
321 event.commit()
324 event.commit()
322 }
325 }
323
326