on xenial help sign in
SpreadDelegate - properly transition between splash screen, surface and screenshot
95bae9bDaniel d'Andrada
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()) {
qml/Dash/graphics/phone/screenshots/vkb_portrait.png

Binary file not shown.

qml/Stages/ApplicationWindow.qmlnew file+212
@@ -0,0 +1,212 @@
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+ // helpers so that we don't have to check for the existence of an application everywhere
32+ // (in order to avoid breaking qml binding due to a javascript exception)
33+ readonly property string name: application ? application.name : ""
34+ readonly property url icon: application ? application.icon : ""
35+ readonly property Item surface: application ? application.surface : null
36+ readonly property int applicationState: application ? application.state : -1
37+
38+ property bool _needToTakeScreenshot:
39+ root.surface && root._surfaceInitialized && screenshotImage.status === Image.Null
40+ && root.applicationState === ApplicationInfo.Stopped
41+ on_NeedToTakeScreenshotChanged: {
42+ if (_needToTakeScreenshot) {
43+ application.updateScreenshot();
44+ }
45+ }
46+
47+ //FIXME - this is a hack to avoid the first few rendered frames as they
48+ // might show the UI accommodating due to surface resizes on startup.
49+ // Remove this when possible
50+ property bool _surfaceInitialized: false
51+ onSurfaceChanged: {
52+ if (surface) {
53+ _surfaceInitTimer.start();
54+ } else {
55+ _surfaceInitialized = false;
56+ }
57+ }
58+ Timer {
59+ id: _surfaceInitTimer
60+ interval: 100
61+ onTriggered: { if (root.surface) {root._surfaceInitialized = true;} }
62+ }
63+
64+ SurfaceContainer {
65+ id: surfaceContainer
66+ objectName: "surfaceContainer"
67+ anchors.fill: parent
68+ surface: root.surface
69+
70+ Binding {
71+ target: surface
72+ property: "enabled"
73+ value: root.interactive
74+ }
75+ Binding {
76+ target: surface
77+ property: "focus"
78+ value: root.interactive
79+ }
80+
81+ Connections {
82+ target: surface
83+ // FIXME: I would rather not need to do this, but currently it doesn't get
84+ // active focus without it and I don't know why.
85+ onFocusChanged: forceSurfaceActiveFocusIfReady();
86+ onParentChanged: forceSurfaceActiveFocusIfReady();
87+ onEnabledChanged: forceSurfaceActiveFocusIfReady();
88+ function forceSurfaceActiveFocusIfReady() {
89+ if (surface.focus && surface.parent === surfaceContainer && surface.enabled) {
90+ surface.forceActiveFocus();
91+ }
92+ }
93+ }
94+ }
95+
96+ Image {
97+ id: screenshotImage
98+ source: root.application ? root.application.screenshot : ""
99+ anchors.fill: parent
100+ }
101+
102+ Loader {
103+ id: splashLoader
104+ visible: active
105+ active: false
106+ anchors.fill: surfaceContainer
107+ sourceComponent: Component {
108+ Splash { name: root.name; image: root.icon }
109+ }
110+ }
111+
112+ StateGroup {
113+ objectName: "applicationWindowStateGroup"
114+ states: [
115+ State {
116+ name: "splashScreen"
117+ when:
118+ (!root.surface || !root._surfaceInitialized)
119+ &&
120+ screenshotImage.status !== Image.Ready
121+ },
122+ State {
123+ name: "surface"
124+ when:
125+ (root.surface && root._surfaceInitialized)
126+ &&
127+ (root.applicationState !== ApplicationInfo.Stopped
128+ || screenshotImage.status !== Image.Ready)
129+ },
130+ State {
131+ name: "screenshot"
132+ when:
133+ screenshotImage.status === Image.Ready
134+ &&
135+ (
136+ root.applicationState === ApplicationInfo.Stopped
137+ || !root.surface || !root._surfaceInitialized
138+ )
139+ }
140+ ]
141+
142+ // For testing purposes
143+ property bool transitioning: defaultToSplash.running
144+ || splashToSurface.running
145+ || surfaceToSplash.running
146+ || surfaceToScreenshot.running
147+ || screenshotToSurface.running
148+
149+ transitions: [
150+ Transition {
151+ id: defaultToSplash
152+ from: ""; to: "splashScreen"
153+ PropertyAction { target: splashLoader; property: "active"; value: true }
154+ },
155+ Transition {
156+ id: splashToSurface
157+ from: "splashScreen"; to: "surface"
158+ SequentialAnimation {
159+ PropertyAction { target: surfaceContainer
160+ property: "visible"; value: true }
161+ UbuntuNumberAnimation { target: splashLoader; property: "opacity";
162+ from: 1.0; to: 0.0
163+ duration: UbuntuAnimation.BriskDuration }
164+ PropertyAction { target: splashLoader; property: "active"; value: false }
165+ }
166+ },
167+ Transition {
168+ id: surfaceToSplash
169+ from: "surface"; to: "splashScreen"
170+ SequentialAnimation {
171+ PropertyAction { target: splashLoader; property: "active"; value: true }
172+ PropertyAction { target: surfaceContainer
173+ property: "visible"; value: true }
174+ UbuntuNumberAnimation { target: splashLoader; property: "opacity";
175+ from: 0.0; to: 1.0
176+ duration: UbuntuAnimation.BriskDuration }
177+ }
178+ },
179+ Transition {
180+ id: surfaceToScreenshot
181+ from: "surface"; to: "screenshot"
182+ SequentialAnimation {
183+ PropertyAction { target: screenshotImage
184+ property: "visible"; value: true }
185+ UbuntuNumberAnimation { target: screenshotImage; property: "opacity";
186+ from: 0.0; to: 1.0
187+ duration: UbuntuAnimation.BriskDuration }
188+ PropertyAction { target: surfaceContainer
189+ property: "visible"; value: false }
190+ ScriptAction { script: { if (root.surface) { root.surface.release(); } } }
191+ }
192+ },
193+ Transition {
194+ id: screenshotToSurface
195+ from: "screenshot"; to: "surface"
196+ SequentialAnimation {
197+ PropertyAction { target: surfaceContainer
198+ property: "visible"; value: true }
199+ UbuntuNumberAnimation { target: screenshotImage; property: "opacity";
200+ from: 1.0; to: 0.0
201+ duration: UbuntuAnimation.BriskDuration }
202+ PropertyAction { target: screenshotImage
203+ property: "visible"; value: false }
204+ ScriptAction { script: {
205+ if (root.application.screenshot) { root.application.discardScreenshot(); }
206+ } }
207+ }
208+ }
209+ ]
210+ }
211+
212+}
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+ && numDelegatesBeingDragged === 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 numDelegatesBeingDragged: 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.numDelegatesBeingDragged++;
383+ } else {
384+ spreadView.numDelegatesBeingDragged--;
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+33−107
@@ -14,147 +14,71 @@
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− SurfaceContainer {
39− id: surfaceContainer
40− objectName: "surfaceContainer"
41− anchors.fill: parent
42− surface: model.surface
43− property bool appHasCreatedASurface: false
44−
45− onSurfaceChanged: {
46− if (surface) {
47− if (!appHasCreatedASurface) {
48− surface.visible = false; // hide until splash screen removed
49− appHasCreatedASurface = true;
50− }
51− }
52− }
53−
54− function revealSurface() {
55− surface.visible = true;
56− splashLoader.source = "";
57− }
58
42
59− Binding {
43+ x: 0
60− target: surfaceContainer.surface
44+ y: dragArea.distance
61− property: "anchors.topMargin"
45+ width: parent.width
62− value: {
46+ height: parent.height
63− return surfaceContainer.surface.state === MirSurfaceItem.Fullscreen ? 0 : maximizedAppTopMargin;
64− }
65− }
66−
67− Binding {
68− target: surface
69− property: "enabled"
70− value: root.interactive
71− }
72− Binding {
73− target: surface
74− property: "focus"
75− value: root.interactive
76− }
77−
78− Timer { //FIXME - need to delay removing splash screen to allow surface resize to complete
79− id: surfaceRevealDelay
80− interval: 100
81− onTriggered: surfaceContainer.revealSurface()
82− }
83−
84− Connections {
85− target: surface
86− // FIXME: I would rather not need to do this, but currently it doesn't get
87− // active focus without it and I don't know why.
88− onFocusChanged: forceSurfaceActiveFocusIfReady();
89− onParentChanged: forceSurfaceActiveFocusIfReady();
90− onEnabledChanged: forceSurfaceActiveFocusIfReady();
91− function forceSurfaceActiveFocusIfReady() {
92− if (surface.focus && surface.parent === surfaceContainer && surface.enabled) {
93− surface.forceActiveFocus();
94− }
95− }
96− }
97
47
98 BorderImage {
48 BorderImage {
99− id: dropShadowImage
100 anchors {
49 anchors {
101− fill: parent
50+ fill: appWindow
102 leftMargin: -units.gu(2)
51 leftMargin: -units.gu(2)
103 rightMargin: -units.gu(2)
52 rightMargin: -units.gu(2)
104 bottomMargin: -units.gu(2)
53 bottomMargin: -units.gu(2)
105− topMargin: -units.gu(2) + (root.isFullscreen ? 0 : maximizedAppTopMargin)
54+ topMargin: -units.gu(2)
106 }
55 }
107 source: "graphics/dropshadow.png"
56 source: "graphics/dropshadow.png"
108 border { left: 50; right: 50; top: 50; bottom: 50 }
57 border { left: 50; right: 50; top: 50; bottom: 50 }
109− opacity: root.dropShadow ? .4 : 0
58+ opacity: root.dropShadow ? 1 : 0
110 Behavior on opacity { UbuntuNumberAnimation {} }
59 Behavior on opacity { UbuntuNumberAnimation {} }
111 }
60 }
112
61
113− transform: Translate {
62+ ApplicationWindow {
114− y: dragArea.distance
63+ id: appWindow
115− }
64+ anchors {
116− }
65+ fill: parent
117−
66+ topMargin: appWindow.fullscreen ? 0 : maximizedAppTopMargin
118−
119− StateGroup {
120− id: appSurfaceState
121− states: [
122− State {
123− name: "noSurfaceYet"
124− when: !surfaceContainer.appHasCreatedASurface
125− StateChangeScript {
126− script: { splashLoader.setSource("Splash.qml", { "name": model.name, "image": model.icon }); }
127− }
128− },
129− State {
130− name: "hasSurface"
131− when: surfaceContainer.appHasCreatedASurface && (surfaceContainer.surface !== null)
132− StateChangeScript { script: { surfaceRevealDelay.start(); } }
133− },
134− State {
135− name: "surfaceLostButAppStillAlive"
136− when: surfaceContainer.appHasCreatedASurface && (surfaceContainer.surface === null)
137− // TODO - use app snapshot
138 }
67 }
139− ]
140− state: "noSurfaceYet"
141− }
142
68
143− Loader {
69+ interactive: root.interactive
144− id: splashLoader
70+ }
145− anchors.fill: surfaceContainer
146 }
71 }
147
72
148 DraggingArea {
73 DraggingArea {
149 id: dragArea
74 id: dragArea
75+ objectName: "dragArea"
150 anchors.fill: parent
76 anchors.fill: parent
151
77
152 property bool moving: false
78 property bool moving: false
153 property real distance: 0
79 property real distance: 0
154
80
155− onMovingChanged: {
81+ readonly property real minSpeedToClose: units.gu(40)
156− spreadView.draggedIndex = moving ? index : -1
157− }
158
82
159 onDragValueChanged: {
83 onDragValueChanged: {
160 if (!dragging) {
84 if (!dragging) {
@@ -173,15 +97,15 @@ Item {
173 }
97 }
174
98
175 onDragEnd: {
99 onDragEnd: {
176− if (model.appId == "unity8-dash") {
100+ if (!root.closeable) {
177 animation.animate("center")
101 animation.animate("center")
178 return;
102 return;
179 }
103 }
180
104
181 // velocity and distance values specified by design prototype
105 // velocity and distance values specified by design prototype
182− if ((dragVelocity < -units.gu(40) && distance < -units.gu(8)) || distance < -root.height / 2) {
106+ if ((dragVelocity < -minSpeedToClose && distance < -units.gu(8)) || distance < -root.height / 2) {
183 animation.animate("up")
107 animation.animate("up")
184− } else if ((dragVelocity > units.gu(40) && distance > units.gu(8)) || distance > root.height / 2) {
108+ } else if ((dragVelocity > minSpeedToClose && distance > units.gu(8)) || distance > root.height / 2) {
185 animation.animate("down")
109 animation.animate("down")
186 } else {
110 } else {
187 animation.animate("center")
111 animation.animate("center")
@@ -190,6 +114,7 @@ Item {
190
114
191 UbuntuNumberAnimation {
115 UbuntuNumberAnimation {
192 id: animation
116 id: animation
117+ objectName: "closeAnimation"
193 target: dragArea
118 target: dragArea
194 property: "distance"
119 property: "distance"
195 property bool requestClose: false
120 property bool requestClose: false
@@ -214,9 +139,10 @@ Item {
214 onRunningChanged: {
139 onRunningChanged: {
215 if (!running) {
140 if (!running) {
216 dragArea.moving = false;
141 dragArea.moving = false;
217− dragArea.distance = 0;
218 if (requestClose) {
142 if (requestClose) {
219 root.closed();
143 root.closed();
144+ } else {
145+ dragArea.distance = 0;
220 }
146 }
221 }
147 }
222 }
148 }
qml/Stages/TabletStage.qml+1
@@ -475,6 +475,7 @@ Rectangle {
475 swipeToCloseEnabled: spreadView.interactive
475 swipeToCloseEnabled: spreadView.interactive
476 maximizedAppTopMargin: root.maximizedAppTopMargin
476 maximizedAppTopMargin: root.maximizedAppTopMargin
477 dragOffset: !isDash && model.appId == priv.mainStageAppId && root.inverseProgress > 0 ? root.inverseProgress : 0
477 dragOffset: !isDash && model.appId == priv.mainStageAppId && root.inverseProgress > 0 ? root.inverseProgress : 0
478+ closeable: !isDash
478
479
479 readonly property bool isDash: model.appId == "unity8-dash"
480 readonly property bool isDash: model.appId == "unity8-dash"
480
481
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
qml/Stages/TransformedTabletSpreadDelegate.qml+2−2
@@ -137,7 +137,7 @@ SpreadDelegate {
137 Behavior on xTranslate {
137 Behavior on xTranslate {
138 enabled: !spreadView.active &&
138 enabled: !spreadView.active &&
139 !snapAnimation.running &&
139 !snapAnimation.running &&
140− model.appId !== "unity8-dash" &&
140+ root.closeable &&
141 !spreadView.sideStageDragging
141 !spreadView.sideStageDragging
142 UbuntuNumberAnimation {
142 UbuntuNumberAnimation {
143 duration: UbuntuAnimation.FastDuration
143 duration: UbuntuAnimation.FastDuration
@@ -172,7 +172,7 @@ SpreadDelegate {
172 }
172 }
173 newTranslate += root.dragOffset;
173 newTranslate += root.dragOffset;
174 }
174 }
175− if (!spreadView.active && model.appId == "unity8-dash" && !root.active) {
175+ if (!spreadView.active && !root.closeable && !root.active) {
176 newTranslate -= root.width;
176 newTranslate -= root.width;
177 }
177 }
178
178
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+91−14
@@ -18,6 +18,9 @@
18#include "MirSurfaceItem.h"
18#include "MirSurfaceItem.h"
19#include "SurfaceManager.h"
19#include "SurfaceManager.h"
20
20
21+// unity8
22+#include <paths.h>
23+
21#include <QGuiApplication>
24#include <QGuiApplication>
22#include <QQuickItem>
25#include <QQuickItem>
23#include <QQuickView>
26#include <QQuickView>
@@ -31,10 +34,9 @@ ApplicationInfo::ApplicationInfo(const QString &appId, QObject *parent)
31 , m_state(Starting)
34 , m_state(Starting)
32 , m_focused(false)
35 , m_focused(false)
33 , m_fullscreen(false)
36 , m_fullscreen(false)
34− , m_parentItem(0)
35 , m_surface(0)
37 , m_surface(0)
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,9 @@ 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_parentItem(0)
48{
50{
49− connect(this, &ApplicationInfo::stateChanged, this, &ApplicationInfo::onStateChanged);
50}
51}
51
52
52ApplicationInfo::~ApplicationInfo()
53ApplicationInfo::~ApplicationInfo()
@@ -57,15 +58,6 @@ ApplicationInfo::~ApplicationInfo()
57 }
58 }
58}
59}
59
60
60−void ApplicationInfo::onStateChanged(State state)
61−{
62− if (state == ApplicationInfo::Running) {
63− QTimer::singleShot(1000, this, SLOT(createSurface()));
64− } else if (state == ApplicationInfo::Stopped) {
65− setSurface(nullptr);
66− }
67−}
68−
69void ApplicationInfo::createSurface()
61void ApplicationInfo::createSurface()
70{
62{
71 if (m_surface || state() == ApplicationInfo::Stopped) return;
63 if (m_surface || state() == ApplicationInfo::Stopped) return;
@@ -73,7 +65,7 @@ void ApplicationInfo::createSurface()
73 setSurface(new MirSurfaceItem(name(),
65 setSurface(new MirSurfaceItem(name(),
74 MirSurfaceItem::Normal,
66 MirSurfaceItem::Normal,
75 fullscreen() ? MirSurfaceItem::Fullscreen : MirSurfaceItem::Maximized,
67 fullscreen() ? MirSurfaceItem::Fullscreen : MirSurfaceItem::Maximized,
76− screenshot()));
68+ m_screenshotUrl));
77}
69}
78
70
79void ApplicationInfo::setSurface(MirSurfaceItem* surface)
71void ApplicationInfo::setSurface(MirSurfaceItem* surface)
@@ -98,3 +90,88 @@ void ApplicationInfo::setSurface(MirSurfaceItem* surface)
98 Q_EMIT surfaceChanged(m_surface);
90 Q_EMIT surfaceChanged(m_surface);
99 SurfaceManager::singleton()->registerSurface(m_surface);
91 SurfaceManager::singleton()->registerSurface(m_surface);
100}
92}
93+
94+void ApplicationInfo::updateScreenshot()
95+{
96+ qDebug() << "ApplicationInfo::updateScreenshot()";
97+ setScreenshot(m_screenshotUrl);
98+}
99+
100+void ApplicationInfo::discardScreenshot()
101+{
102+ setScreenshot(QUrl());
103+}
104+
105+void ApplicationInfo::setIconId(const QString &iconId)
106+{
107+ setIcon(QString("file://%1/graphics/applicationIcons/%2@18.png")
108+ .arg(qmlDirectory())
109+ .arg(iconId));
110+}
111+
112+void ApplicationInfo::setScreenshotId(const QString &screenshotId)
113+{
114+ m_screenshotUrl = QString("file://%1/Dash/graphics/phone/screenshots/%2@12.png")
115+ .arg(qmlDirectory())
116+ .arg(screenshotId);
117+}
118+
119+void ApplicationInfo::setName(const QString &value)
120+{
121+ if (value != m_name) {
122+ m_name = value;
123+ Q_EMIT nameChanged(value);
124+ }
125+}
126+
127+void ApplicationInfo::setIcon(const QUrl &value)
128+{
129+ if (value != m_icon) {
130+ m_icon = value;
131+ Q_EMIT iconChanged(value);
132+ }
133+}
134+
135+void ApplicationInfo::setStage(Stage value)
136+{
137+ if (value != m_stage) {
138+ m_stage = value;
139+ Q_EMIT stageChanged(value);
140+ }
141+}
142+
143+void ApplicationInfo::setState(State value)
144+{
145+ if (value != m_state) {
146+ m_state = value;
147+ Q_EMIT stateChanged(value);
148+
149+ if (m_state == ApplicationInfo::Running) {
150+ QTimer::singleShot(1000, this, SLOT(createSurface()));
151+ }
152+ }
153+}
154+
155+void ApplicationInfo::setFocused(bool value)
156+{
157+ if (value != m_focused) {
158+ m_focused = value;
159+ Q_EMIT focusedChanged(value);
160+ }
161+}
162+
163+void ApplicationInfo::setScreenshot(const QUrl &value)
164+{
165+ if (value != m_screenshot) {
166+ m_screenshot = value;
167+ Q_EMIT screenshotChanged(value);
168+ }
169+}
170+
171+void ApplicationInfo::setFullscreen(bool value)
172+{
173+ if (value != m_fullscreen) {
174+ m_fullscreen = value;
175+ Q_EMIT fullscreenChanged(value);
176+ }
177+}
tests/mocks/Unity/Application/ApplicationInfo.h+45−43
@@ -26,76 +26,78 @@ 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
36
42 // Only exists in this fake implementation
37 // Only exists in this fake implementation
43
38
44− // QML component used to represent its image/screenhot
45− Q_PROPERTY(QString imageQml READ imageQml WRITE setImageQml NOTIFY imageQmlChanged)
46−
47− // QML component used to represent the application window
48− Q_PROPERTY(QString windowQml READ windowQml WRITE setWindowQml NOTIFY windowQmlChanged)
49−
50public:
39public:
51 ApplicationInfo(QObject *parent = NULL);
40 ApplicationInfo(QObject *parent = NULL);
52 ApplicationInfo(const QString &appId, QObject *parent = NULL);
41 ApplicationInfo(const QString &appId, QObject *parent = NULL);
53 ~ApplicationInfo();
42 ~ApplicationInfo();
54
43
55− #define IMPLEMENT_PROPERTY(name, Name, type) \
44+ Q_INVOKABLE void updateScreenshot() override;
56− public: \
45+ Q_INVOKABLE void discardScreenshot() override;
57− type name() const { return m_##name; } \
46+
58− void set##Name(const type& value) \
47+ void setIconId(const QString &iconId);
59− { \
48+ void setScreenshotId(const QString &screenshotId);
60− if (m_##name != value) { \
49+
61− m_##name = value; \
50+ void setAppId(const QString &value) { m_appId = value; }
62− Q_EMIT name##Changed(value); \
51+ QString appId() const override { return m_appId; }
63− } \
52+
64− } \
53+ void setName(const QString &value);
65− Q_SIGNALS: \
54+ QString name() const override { return m_name; }
66− void name##Changed(const type&); \
55+
67− private: \
56+ QString comment() const override { return QString(); }
68− type m_##name;
57+
69−
58+ QUrl icon() const override { return m_icon; }
70− IMPLEMENT_PROPERTY(appId, AppId, QString)
59+
71− IMPLEMENT_PROPERTY(name, Name, QString)
60+ void setStage(Stage value);
72− IMPLEMENT_PROPERTY(comment, Comment, QString)
61+ Stage stage() const override { return m_stage; }
73− IMPLEMENT_PROPERTY(icon, Icon, QUrl)
62+
74− IMPLEMENT_PROPERTY(stage, Stage, Stage)
63+ Q_INVOKABLE void setState(State value);
75− IMPLEMENT_PROPERTY(state, State, State)
64+ State state() const override { return m_state; }
76− IMPLEMENT_PROPERTY(focused, Focused, bool)
65+
77− IMPLEMENT_PROPERTY(fullscreen, Fullscreen, bool)
66+ void setFocused(bool value);
78− IMPLEMENT_PROPERTY(imageQml, ImageQml, QString)
67+ bool focused() const override { return m_focused; }
79− IMPLEMENT_PROPERTY(windowQml, WindowQml, QString)
68+
80− IMPLEMENT_PROPERTY(screenshot, Screenshot, QUrl)
69+ QUrl screenshot() const override { return m_screenshot; }
81−
70+
82− #undef IMPLEMENT_PROPERTY
71+ void setFullscreen(bool value);
72+ bool fullscreen() const { return m_fullscreen; }
83
73
84−public:
85 void setSurface(MirSurfaceItem* surface);
74 void setSurface(MirSurfaceItem* surface);
86 MirSurfaceItem* surface() const { return m_surface; }
75 MirSurfaceItem* surface() const { return m_surface; }
87
76
88Q_SIGNALS:
77Q_SIGNALS:
89 void surfaceChanged(MirSurfaceItem*);
78 void surfaceChanged(MirSurfaceItem*);
79+ void fullscreenChanged(bool value);
90
80
91private Q_SLOTS:
81private Q_SLOTS:
92− void onStateChanged(State state);
93−
94 void createSurface();
82 void createSurface();
95
83
96private:
84private:
97− QQuickItem *m_parentItem;
85+ void setIcon(const QUrl &value);
86+ void setScreenshot(const QUrl &value);
87+
88+ QUrl m_screenshotUrl;
89+
90+ QString m_appId;
91+ QString m_name;
92+ QUrl m_icon;
93+ Stage m_stage;
94+ State m_state;
95+ bool m_focused;
96+ QUrl m_screenshot;
97+ bool m_fullscreen;
98 MirSurfaceItem* m_surface;
98 MirSurfaceItem* m_surface;
99+
100+ QQuickItem *m_parentItem;
99};
101};
100
102
101Q_DECLARE_METATYPE(ApplicationInfo*)
103Q_DECLARE_METATYPE(ApplicationInfo*)
tests/mocks/Unity/Application/ApplicationManager.cpp+69−70
@@ -54,14 +54,32 @@ ApplicationManager::ApplicationManager(QObject *parent)
54
54
55 buildListOfAvailableApplications();
55 buildListOfAvailableApplications();
56
56
57− startApplication("unity8-dash");
57+ // polling to find out when the toplevel window has been created as there's
58− focusApplication("unity8-dash");
58+ // no signal telling us that
59+ connect(&m_windowCreatedTimer, &QTimer::timeout,
60+ this, &ApplicationManager::onWindowCreatedTimerTimeout);
61+ m_windowCreatedTimer.setSingleShot(false);
62+ m_windowCreatedTimer.start(200);
59}
63}
60
64
61ApplicationManager::~ApplicationManager()
65ApplicationManager::~ApplicationManager()
62{
66{
63}
67}
64
68
69+void ApplicationManager::onWindowCreatedTimerTimeout()
70+{
71+ if (QGuiApplication::topLevelWindows().count() > 0) {
72+ m_windowCreatedTimer.stop();
73+ onWindowCreated();
74+ }
75+}
76+
77+void ApplicationManager::onWindowCreated()
78+{
79+ startApplication("unity8-dash");
80+ focusApplication("unity8-dash");
81+}
82+
65int ApplicationManager::rowCount(const QModelIndex& parent) const {
83int ApplicationManager::rowCount(const QModelIndex& parent) const {
66 return !parent.isValid() ? m_runningApplications.size() : 0;
84 return !parent.isValid() ? m_runningApplications.size() : 0;
67}
85}
@@ -140,6 +158,16 @@ void ApplicationManager::add(ApplicationInfo *application) {
140 if (!appIndex.isValid()) return;
158 if (!appIndex.isValid()) return;
141 Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << ApplicationManager::RoleSurface);
159 Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << ApplicationManager::RoleSurface);
142 });
160 });
161+ connect(application, &ApplicationInfo::focusedChanged, this, [application, this]() {
162+ QModelIndex appIndex = findIndex(application);
163+ if (!appIndex.isValid()) return;
164+ Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << ApplicationManager::RoleFocused);
165+ });
166+ connect(application, &ApplicationInfo::stateChanged, this, [application, this]() {
167+ QModelIndex appIndex = findIndex(application);
168+ if (!appIndex.isValid()) return;
169+ Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << ApplicationManager::RoleState);
170+ });
143}
171}
144
172
145void ApplicationManager::remove(ApplicationInfo *application) {
173void ApplicationManager::remove(ApplicationInfo *application) {
@@ -152,7 +180,7 @@ void ApplicationManager::remove(ApplicationInfo *application) {
152 Q_EMIT countChanged();
180 Q_EMIT countChanged();
153 if (isEmpty()) Q_EMIT emptyChanged(isEmpty());
181 if (isEmpty()) Q_EMIT emptyChanged(isEmpty());
154 }
182 }
155− disconnect(application, &ApplicationInfo::surfaceChanged, this, 0);
183+ disconnect(application, 0, this, 0);
156}
184}
157
185
158void ApplicationManager::move(int from, int to) {
186void ApplicationManager::move(int from, int to) {
@@ -290,32 +318,18 @@ bool ApplicationManager::focusApplication(const QString &appId)
290 if (application == nullptr)
318 if (application == nullptr)
291 return false;
319 return false;
292
320
293− if (application->stage() == ApplicationInfo::MainStage) {
294− // unfocus currently focused mainstage app
321+ // unfocus currently focused app
295− for (ApplicationInfo *app : m_runningApplications) {
296− if (app->focused() && app->stage() == ApplicationInfo::MainStage) {
297− app->setFocused(false);
298− app->setState(ApplicationInfo::Suspended);
299− }
300− }
301−
302− // focus this app
303− application->setFocused(true);
304− application->setState(ApplicationInfo::Running);
305− } else if (application->stage() == ApplicationInfo::SideStage) {
306− // unfocus currently focused sidestage app
307− for (ApplicationInfo *app : m_runningApplications) {
322+ for (ApplicationInfo *app : m_runningApplications) {
308− if (app->focused() && app->stage() == ApplicationInfo::SideStage) {
323+ if (app->focused()) {
309− app->setFocused(false);
324+ app->setFocused(false);
310− app->setState(ApplicationInfo::Suspended);
325+ app->setState(ApplicationInfo::Suspended);
311− }
312 }
326 }
313−
314− // focus this app
315− application->setFocused(true);
316− application->setState(ApplicationInfo::Running);
317 }
327 }
318
328
329+ // focus this app
330+ application->setFocused(true);
331+ application->setState(ApplicationInfo::Running);
332+
319 // move app to top of stack
333 // move app to top of stack
320 move(m_runningApplications.indexOf(application), 0);
334 move(m_runningApplications.indexOf(application), 0);
321 Q_EMIT focusedApplicationIdChanged();
335 Q_EMIT focusedApplicationIdChanged();
@@ -338,12 +352,6 @@ void ApplicationManager::unfocusCurrentApplication()
338 Q_EMIT focusedApplicationIdChanged();
352 Q_EMIT focusedApplicationIdChanged();
339}
353}
340
354
341−void ApplicationManager::generateQmlStrings(ApplicationInfo *application)
342−{
343− application->setScreenshot(QString("file://%1/Dash/graphics/phone/screenshots/%2@12.png").arg(qmlDirectory())
344− .arg(application->icon().toString()));
345−}
346−
347void ApplicationManager::buildListOfAvailableApplications()
355void ApplicationManager::buildListOfAvailableApplications()
348{
356{
349 ApplicationInfo *application;
357 ApplicationInfo *application;
@@ -351,128 +359,119 @@ void ApplicationManager::buildListOfAvailableApplications()
351 application = new ApplicationInfo(this);
359 application = new ApplicationInfo(this);
352 application->setAppId("unity8-dash");
360 application->setAppId("unity8-dash");
353 application->setName("Unity 8 Mock Dash");
361 application->setName("Unity 8 Mock Dash");
354− application->setIcon(QUrl("unity8-dash"));
362+ application->setScreenshotId("unity8-dash");
355 application->setStage(ApplicationInfo::MainStage);
363 application->setStage(ApplicationInfo::MainStage);
356− generateQmlStrings(application);
357 m_availableApplications.append(application);
364 m_availableApplications.append(application);
358
365
359 application = new ApplicationInfo(this);
366 application = new ApplicationInfo(this);
360 application->setAppId("dialer-app");
367 application->setAppId("dialer-app");
361 application->setName("Dialer");
368 application->setName("Dialer");
362− application->setIcon(QUrl("dialer"));
369+ application->setScreenshotId("dialer");
370+ application->setIconId("dialer-app");
363 application->setStage(ApplicationInfo::SideStage);
371 application->setStage(ApplicationInfo::SideStage);
364− generateQmlStrings(application);
365 m_availableApplications.append(application);
372 m_availableApplications.append(application);
366
373
367 application = new ApplicationInfo(this);
374 application = new ApplicationInfo(this);
368 application->setAppId("camera-app");
375 application->setAppId("camera-app");
369 application->setName("Camera");
376 application->setName("Camera");
370− application->setIcon(QUrl("camera"));
377+ application->setScreenshotId("camera");
378+ application->setIconId("camera");
371 application->setFullscreen(true);
379 application->setFullscreen(true);
372− generateQmlStrings(application);
373 m_availableApplications.append(application);
380 m_availableApplications.append(application);
374
381
375 application = new ApplicationInfo(this);
382 application = new ApplicationInfo(this);
376 application->setAppId("gallery-app");
383 application->setAppId("gallery-app");
377 application->setName("Gallery");
384 application->setName("Gallery");
378− application->setIcon(QUrl("gallery"));
385+ application->setScreenshotId("gallery");
379− generateQmlStrings(application);
386+ application->setIconId("gallery");
380 m_availableApplications.append(application);
387 m_availableApplications.append(application);
381
388
382 application = new ApplicationInfo(this);
389 application = new ApplicationInfo(this);
383 application->setAppId("facebook-webapp");
390 application->setAppId("facebook-webapp");
384 application->setName("Facebook");
391 application->setName("Facebook");
385− application->setIcon(QUrl("facebook"));
392+ application->setScreenshotId("facebook");
393+ application->setIconId("facebook");
386 application->setStage(ApplicationInfo::SideStage);
394 application->setStage(ApplicationInfo::SideStage);
387− generateQmlStrings(application);
388 m_availableApplications.append(application);
395 m_availableApplications.append(application);
389
396
390 application = new ApplicationInfo(this);
397 application = new ApplicationInfo(this);
391 application->setAppId("webbrowser-app");
398 application->setAppId("webbrowser-app");
392 application->setFullscreen(true);
399 application->setFullscreen(true);
393 application->setName("Browser");
400 application->setName("Browser");
394− application->setIcon(QUrl("browser"));
401+ application->setScreenshotId("browser");
395− generateQmlStrings(application);
402+ application->setIconId("browser");
396 m_availableApplications.append(application);
403 m_availableApplications.append(application);
397
404
398 application = new ApplicationInfo(this);
405 application = new ApplicationInfo(this);
399 application->setAppId("twitter-webapp");
406 application->setAppId("twitter-webapp");
400 application->setName("Twitter");
407 application->setName("Twitter");
401− application->setIcon(QUrl("twitter"));
408+ application->setScreenshotId("twitter");
409+ application->setIconId("twitter");
402 application->setStage(ApplicationInfo::SideStage);
410 application->setStage(ApplicationInfo::SideStage);
411+ m_availableApplications.append(application);
412+
413+ application = new ApplicationInfo(this);
414+ application->setAppId("map");
415+ application->setName("Map");
403− generateQmlStrings(application);
416+ application->setIconId("map");
417+ application->setScreenshotId("map");
404 m_availableApplications.append(application);
418 m_availableApplications.append(application);
405
419
406 application = new ApplicationInfo(this);
420 application = new ApplicationInfo(this);
407 application->setAppId("gmail-webapp");
421 application->setAppId("gmail-webapp");
408 application->setName("GMail");
422 application->setName("GMail");
409− application->setIcon(QUrl("gmail"));
423+ application->setIconId("gmail");
410 m_availableApplications.append(application);
424 m_availableApplications.append(application);
411
425
412 application = new ApplicationInfo(this);
426 application = new ApplicationInfo(this);
413 application->setAppId("ubuntu-weather-app");
427 application->setAppId("ubuntu-weather-app");
414 application->setName("Weather");
428 application->setName("Weather");
415− application->setIcon(QUrl("weather"));
429+ application->setIconId("weather");
416 application->setStage(ApplicationInfo::SideStage);
430 application->setStage(ApplicationInfo::SideStage);
417− generateQmlStrings(application);
418 m_availableApplications.append(application);
431 m_availableApplications.append(application);
419
432
420 application = new ApplicationInfo(this);
433 application = new ApplicationInfo(this);
421 application->setAppId("notes-app");
434 application->setAppId("notes-app");
422 application->setName("Notepad");
435 application->setName("Notepad");
423− application->setIcon(QUrl("notepad"));
436+ application->setIconId("notepad");
424 application->setStage(ApplicationInfo::SideStage);
437 application->setStage(ApplicationInfo::SideStage);
425 m_availableApplications.append(application);
438 m_availableApplications.append(application);
426
439
427 application = new ApplicationInfo(this);
440 application = new ApplicationInfo(this);
428 application->setAppId("calendar-app");
441 application->setAppId("calendar-app");
429 application->setName("Calendar");
442 application->setName("Calendar");
430− application->setIcon(QUrl("calendar"));
443+ application->setIconId("calendar");
431 application->setStage(ApplicationInfo::SideStage);
444 application->setStage(ApplicationInfo::SideStage);
432 m_availableApplications.append(application);
445 m_availableApplications.append(application);
433
446
434− application = new ApplicationInfo(this);
435− application->setAppId("mediaplayer-app");
436− application->setName("Media Player");
437− application->setIcon(QUrl("mediaplayer-app"));
438− application->setFullscreen(true);
439− m_availableApplications.append(application);
440−
441 application = new ApplicationInfo(this);
447 application = new ApplicationInfo(this);
442 application->setAppId("evernote");
448 application->setAppId("evernote");
443 application->setName("Evernote");
449 application->setName("Evernote");
444− application->setIcon(QUrl("evernote"));
450+ application->setIconId("evernote");
445− m_availableApplications.append(application);
446−
447− application = new ApplicationInfo(this);
448− application->setAppId("map");
449− application->setName("Map");
450− application->setIcon(QUrl("map"));
451− generateQmlStrings(application);
452 m_availableApplications.append(application);
451 m_availableApplications.append(application);
453
452
454 application = new ApplicationInfo(this);
453 application = new ApplicationInfo(this);
455 application->setAppId("pinterest");
454 application->setAppId("pinterest");
456 application->setName("Pinterest");
455 application->setName("Pinterest");
457− application->setIcon(QUrl("pinterest"));
456+ application->setIconId("pinterest");
458 m_availableApplications.append(application);
457 m_availableApplications.append(application);
459
458
460 application = new ApplicationInfo(this);
459 application = new ApplicationInfo(this);
461 application->setAppId("soundcloud");
460 application->setAppId("soundcloud");
462 application->setName("SoundCloud");
461 application->setName("SoundCloud");
463− application->setIcon(QUrl("soundcloud"));
462+ application->setIconId("soundcloud");
464 m_availableApplications.append(application);
463 m_availableApplications.append(application);
465
464
466 application = new ApplicationInfo(this);
465 application = new ApplicationInfo(this);
467 application->setAppId("wikipedia");
466 application->setAppId("wikipedia");
468 application->setName("Wikipedia");
467 application->setName("Wikipedia");
469− application->setIcon(QUrl("wikipedia"));
468+ application->setIconId("wikipedia");
470 m_availableApplications.append(application);
469 m_availableApplications.append(application);
471
470
472 application = new ApplicationInfo(this);
471 application = new ApplicationInfo(this);
473 application->setAppId("youtube");
472 application->setAppId("youtube");
474 application->setName("YouTube");
473 application->setName("YouTube");
475− application->setIcon(QUrl("youtube"));
474+ application->setIconId("youtube");
476 m_availableApplications.append(application);
475 m_availableApplications.append(application);
477}
476}
478
477
tests/mocks/Unity/Application/ApplicationManager.h+6−2
@@ -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
@@ -114,15 +115,18 @@ class ApplicationManager : public ApplicationManagerInterface {
114 void focusRequested(const QString &appId);
115 void focusRequested(const QString &appId);
115 void emptyChanged(bool empty);
116 void emptyChanged(bool empty);
116
117
118+ private Q_SLOTS:
119+ void onWindowCreatedTimerTimeout();
120+
117 private:
121 private:
118 void add(ApplicationInfo *application);
122 void add(ApplicationInfo *application);
119 void remove(ApplicationInfo* application);
123 void remove(ApplicationInfo* application);
120− void showApplicationWindow(ApplicationInfo *application);
121 void buildListOfAvailableApplications();
124 void buildListOfAvailableApplications();
122− void generateQmlStrings(ApplicationInfo *application);
125+ void onWindowCreated();
123 bool m_suspended;
126 bool m_suspended;
124 QList<ApplicationInfo*> m_runningApplications;
127 QList<ApplicationInfo*> m_runningApplications;
125 QList<ApplicationInfo*> m_availableApplications;
128 QList<ApplicationInfo*> m_availableApplications;
129+ QTimer m_windowCreatedTimer;
126
130
127 static ApplicationManager *the_application_manager;
131 static ApplicationManager *the_application_manager;
128};
132};
tests/mocks/Unity/Application/CMakeLists.txt−2
@@ -4,12 +4,10 @@ set(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+89−13
@@ -17,31 +17,70 @@
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 QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
45 QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
38
46
39 // The virtual keyboard (input method) has a big transparent area so that
47 // The virtual keyboard (input method) has a big transparent area so that
40 // content behind it show through
48 // content behind it show through
41− setFillColor(Qt::transparent);
49+ //setFillColor(Qt::transparent);
42
50
43 connect(this, &QQuickItem::focusChanged,
51 connect(this, &QQuickItem::focusChanged,
44 this, &MirSurfaceItem::onFocusChanged);
52 this, &MirSurfaceItem::onFocusChanged);
53+
54+
55+ // The assumptions I make here really should hold.
56+ QQuickView *quickView =
57+ qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
58+
59+ QString qmlComponentFilePath;
60+ if (!qmlFilePath.isEmpty()) {
61+ qmlComponentFilePath.append(qmlFilePath);
62+ } else {
63+ qmlComponentFilePath = QString("%1/Unity/Application/MirSurfaceItem.qml")
64+ .arg(mockPluginsDir());
65+ }
66+
67+ m_qmlContentComponent = new QQmlComponent(quickView->engine(), qmlComponentFilePath);
68+
69+ switch (m_qmlContentComponent->status()) {
70+ case QQmlComponent::Ready:
71+ createQmlContentItem();
72+ break;
73+ case QQmlComponent::Loading:
74+ connect(m_qmlContentComponent, &QQmlComponent::statusChanged,
75+ this, &MirSurfaceItem::onComponentStatusChanged);
76+ break;
77+ case QQmlComponent::Error:
78+ printComponentErrors();
79+ qFatal("MirSurfaceItem: failed to create content component.");
80+ break;
81+ default:
82+ qFatal("MirSurfaceItem: Unhandled component status");
83+ }
45}
84}
46
85
47MirSurfaceItem::~MirSurfaceItem()
86MirSurfaceItem::~MirSurfaceItem()
@@ -57,10 +96,11 @@ MirSurfaceItem::~MirSurfaceItem()
57 m_application->setSurface(nullptr);
96 m_application->setSurface(nullptr);
58}
97}
59
98
60−void MirSurfaceItem::paint(QPainter * painter)
99+void MirSurfaceItem::printComponentErrors()
61{
100{
62− if (!m_img.isNull()) {
101+ QList<QQmlError> errors = m_qmlContentComponent->errors();
63− painter->drawImage(contentsBoundingRect(), m_img, QRect(QPoint(0,0), m_img.size()));
102+ for (int i = 0; i < errors.count(); ++i) {
103+ qDebug() << errors[i];
64 }
104 }
65}
105}
66
106
@@ -147,19 +187,24 @@ MirSurfaceItem* MirSurfaceItem::childSurfaceAt(QQmlListProperty<MirSurfaceItem>
147}
187}
148
188
149
189
150−void MirSurfaceItem::touchEvent(QTouchEvent * event)
190+void MirSurfaceItem::onQmlWantInputMethodChanged()
151{
191{
152− if (event->type() == QEvent::TouchBegin && hasFocus()) {
192+ QQmlProperty prop(m_qmlItem, "wantInputMethod");
193+ bool wantInputMethod = prop.read().toBool();
194+
195+ if (hasFocus() && wantInputMethod) {
153 Q_EMIT inputMethodRequested();
196 Q_EMIT inputMethodRequested();
154− m_haveInputMethod = true;
155 }
197 }
156}
198}
157
199
158void MirSurfaceItem::onFocusChanged()
200void MirSurfaceItem::onFocusChanged()
159{
201{
160− if (!hasFocus() && m_haveInputMethod) {
202+ QQmlProperty prop(m_qmlItem, "wantInputMethod");
203+ bool wantInputMethod = prop.read().toBool();
204+
205+ if (!hasFocus() && wantInputMethod) {
161 Q_EMIT inputMethodDismissed();
206 Q_EMIT inputMethodDismissed();
162− m_haveInputMethod = false;
207+ prop.write(QVariant::fromValue(false));
163 }
208 }
164}
209}
165
210
@@ -170,3 +215,34 @@ void MirSurfaceItem::setState(MirSurfaceItem::State newState)
170 Q_EMIT stateChanged(newState);
215 Q_EMIT stateChanged(newState);
171 }
216 }
172}
217}
218+
219+void MirSurfaceItem::onComponentStatusChanged(QQmlComponent::Status status)
220+{
221+ if (status == QQmlComponent::Ready) {
222+ createQmlContentItem();
223+ }
224+}
225+
226+void MirSurfaceItem::createQmlContentItem()
227+{
228+ m_qmlItem = qobject_cast<QQuickItem*>(m_qmlContentComponent->create());
229+ m_qmlItem->setParentItem(this);
230+
231+ {
232+ QQmlProperty anchorsFill(m_qmlItem, "anchors.fill");
233+ anchorsFill.write(QVariant::fromValue(this));
234+ }
235+
236+ {
237+ QQmlProperty screenshotSource(m_qmlItem, "screenshotSource");
238+ screenshotSource.write(QVariant::fromValue(m_screenshotUrl));
239+ }
240+
241+ {
242+ QQmlProperty prop(m_qmlItem, "wantInputMethod");
243+ if (prop.type() == QQmlProperty::Property) {
244+ bool ok = prop.connectNotifySignal(this, SLOT(onQmlWantInputMethodChanged()));
245+ if (!ok) qCritical("MirSurfaceItem: failed to connect to wantInputMethod notify signal");
246+ }
247+ }
248+}
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+46
@@ -0,0 +1,46 @@
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+ id: root
21+ color: "yellow"
22+
23+ property alias screenshotSource: screenshotImage.source
24+
25+ property bool wantInputMethod: false
26+
27+ Image {
28+ id: screenshotImage
29+ anchors.fill: parent
30+ }
31+
32+ Text {
33+ anchors.fill: parent
34+ text: "SURFACE"
35+ color: "yellow"
36+ font.bold: true
37+ fontSizeMode: Text.Fit
38+ minimumPixelSize: 10; font.pixelSize: 200
39+ verticalAlignment: Text.AlignVCenter
40+ }
41+
42+ MultiPointTouchArea {
43+ anchors.fill: parent
44+ onPressed: { root.wantInputMethod = true }
45+ }
46+}
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+34
@@ -0,0 +1,34 @@
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+
21+ property alias screenshotSource: screenshotImage.source
22+
23+ Image {
24+ id: screenshotImage
25+ height: width * 0.6
26+ anchors.bottom: parent.bottom
27+ anchors.left: parent.left
28+ anchors.right: parent.right
29+
30+ MultiPointTouchArea {
31+ anchors.fill: parent
32+ }
33+ }
34+}
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/launchermodeltest.cpp+3
@@ -41,6 +41,9 @@ public:
41 bool focused() const { return m_focused; }
41 bool focused() const { return m_focused; }
42 QUrl screenshot() const { return QUrl(); }
42 QUrl screenshot() const { return QUrl(); }
43
43
44+ Q_INVOKABLE void updateScreenshot() override {}
45+ Q_INVOKABLE void discardScreenshot() override {}
46+
44 // Methods used for mocking (not in the interface)
47 // Methods used for mocking (not in the interface)
45 void setFocused(bool focused) { m_focused = focused; Q_EMIT focusedChanged(focused); }
48 void setFocused(bool focused) { m_focused = focused; Q_EMIT focusedChanged(focused); }
46private:
49private:
tests/qmltests/CMakeLists.txt+2
@@ -74,4 +74,6 @@ add_qml_test(Panel/Indicators DefaultIndicatorPage ENVIRONMENT "LD_LIBRARY_PATH=
74# These MenuItemFactory tests need the test/mocks/ to come before plugins/
74# These MenuItemFactory tests need the test/mocks/ to come before plugins/
75add_qml_test(Panel/Indicators MenuItemFactory IMPORT_PATHS ${CMAKE_BINARY_DIR}/tests/mocks ${qmltest_DEFAULT_IMPORT_PATHS})
75add_qml_test(Panel/Indicators MenuItemFactory IMPORT_PATHS ${CMAKE_BINARY_DIR}/tests/mocks ${qmltest_DEFAULT_IMPORT_PATHS})
76add_qml_test(Panel/Indicators MessageMenuItemFactory IMPORT_PATHS ${CMAKE_BINARY_DIR}/tests/mocks ${qmltest_DEFAULT_IMPORT_PATHS})
76add_qml_test(Panel/Indicators MessageMenuItemFactory IMPORT_PATHS ${CMAKE_BINARY_DIR}/tests/mocks ${qmltest_DEFAULT_IMPORT_PATHS})
77+add_qml_test(Stages ApplicationWindow ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/libusermetrics:${CMAKE_BINARY_DIR}/tests/mocks/LightDM/single")
77add_qml_test(Stages PhoneStage ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/libusermetrics:${CMAKE_BINARY_DIR}/tests/mocks/LightDM/single")
78add_qml_test(Stages PhoneStage ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/libusermetrics:${CMAKE_BINARY_DIR}/tests/mocks/LightDM/single")
79+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+359
@@ -0,0 +1,359 @@
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 {
33+ id: fakeSurfaceComponent
34+ Image {
35+ source: "../../../qml/Dash/graphics/phone/screenshots/gallery@12.png"
36+ width: applicationWindowLoader.width
37+ height: applicationWindowLoader.height
38+
39+ property var application: fakeApplication
40+ property int touchPressCount: 0
41+ property int touchReleaseCount: 0
42+
43+ Text {
44+ anchors.fill: parent
45+ text: "SURFACE"
46+ color: "yellow"
47+ font.bold: true
48+ fontSizeMode: Text.Fit
49+ minimumPixelSize: 10; font.pixelSize: 200
50+ verticalAlignment: Text.AlignVCenter
51+ }
52+
53+ MultiPointTouchArea {
54+ anchors.fill: parent
55+ onPressed: { touchPressCount++; }
56+ onReleased: { touchReleaseCount++; }
57+ }
58+
59+ signal removed()
60+
61+ function release() {
62+ fakeApplication.surface = null;
63+ parent = null;
64+ removed();
65+ surfaceCheckbox.checked = false;
66+ }
67+ }
68+ }
69+
70+ QtObject {
71+ id: fakeApplication
72+ property url screenshot: ""
73+
74+ function updateScreenshot() {
75+ fakeScreenshotTimer.start();
76+ }
77+
78+ function discardScreenshot() {
79+ screenshot = "";
80+ }
81+
82+ property alias state: appStateSelector.selectedApplicationState
83+ property string name: "Gallery"
84+ property url icon: "../../../qml/graphics/applicationIcons/gallery.png"
85+ property var surface: null
86+ }
87+ Timer {
88+ id: fakeScreenshotTimer
89+ interval: 100
90+ onTriggered: {
91+ fakeApplication.screenshot = "../../../qml/Dash/graphics/phone/screenshots/gallery@12.png";
92+ }
93+ }
94+
95+ Component {
96+ id: applicationWindowComponent
97+ ApplicationWindow {
98+ anchors.fill: parent
99+ application: fakeApplication
100+ }
101+ }
102+ Loader {
103+ id: applicationWindowLoader
104+ anchors {
105+ top: parent.top
106+ bottom: parent.bottom
107+ left: parent.left
108+ }
109+ width: units.gu(40)
110+ sourceComponent: applicationWindowComponent
111+ }
112+
113+ Rectangle {
114+ color: "white"
115+ anchors {
116+ top: parent.top
117+ bottom: parent.bottom
118+ left: applicationWindowLoader.right
119+ right: parent.right
120+ }
121+
122+ Column {
123+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
124+ spacing: units.gu(1)
125+ Row {
126+ anchors { left: parent.left; right: parent.right }
127+ CheckBox {
128+ id: surfaceCheckbox; checked: false
129+ onCheckedChanged: {
130+ if (applicationWindowLoader.status !== Loader.Ready)
131+ return;
132+
133+ if (checked && !fakeApplication.surface) {
134+ fakeApplication.surface = fakeSurfaceComponent.createObject(null, {});
135+ } else if (!checked && fakeApplication.surface) {
136+ fakeApplication.surface.release();
137+ }
138+ }
139+ }
140+ Label { text: "Surface" }
141+ }
142+ ListItem.ItemSelector {
143+ id: appStateSelector
144+ anchors { left: parent.left; right: parent.right }
145+ text: "Application state"
146+ model: ["Starting",
147+ "Running",
148+ "Suspended",
149+ "Stopped"]
150+ property int selectedApplicationState: {
151+ if (model[selectedIndex] === "Starting") {
152+ return ApplicationInfo.Starting;
153+ } else if (model[selectedIndex] === "Running") {
154+ return ApplicationInfo.Running;
155+ } else if (model[selectedIndex] === "Suspended") {
156+ return ApplicationInfo.Suspended;
157+ } else {
158+ return ApplicationInfo.Stopped;
159+ }
160+ }
161+ }
162+ }
163+ }
164+
165+ UT.UnityTestCase {
166+ id: testCase
167+ name: "ApplicationWindow"
168+ when: windowShown
169+
170+ // just to make them shorter
171+ property int appStarting: ApplicationInfo.Starting
172+ property int appRunning: ApplicationInfo.Running
173+ property int appSuspended: ApplicationInfo.Suspended
174+ property int appStopped: ApplicationInfo.Stopped
175+
176+ function setApplicationState(appState) {
177+ switch (appState) {
178+ case appStarting:
179+ appStateSelector.selectedIndex = 0;
180+ break;
181+ case appRunning:
182+ appStateSelector.selectedIndex = 1;
183+ break;
184+ case appSuspended:
185+ appStateSelector.selectedIndex = 2;
186+ break;
187+ case appStopped:
188+ appStateSelector.selectedIndex = 3;
189+ break;
190+ }
191+ }
192+
193+ function cleanup() {
194+ // reload our test subject to get it in a fresh state once again
195+ applicationWindowLoader.active = false;
196+
197+ appStateSelector.selectedIndex = 0;
198+ surfaceCheckbox.checked = false;
199+
200+ fakeScreenshotTimer.stop();
201+ fakeApplication.screenshot = "";
202+ fakeApplication.surface = null;
203+
204+ applicationWindowLoader.active = true;
205+ }
206+
207+ function test_showSplashUntilAppFullyInit1() {
208+ var stateGroup = findInvisibleChild(applicationWindowLoader.item, "applicationWindowStateGroup");
209+
210+ verify(stateGroup.state === "splashScreen");
211+
212+ setApplicationState(appRunning);
213+
214+ verify(stateGroup.state === "splashScreen");
215+
216+ surfaceCheckbox.checked = true;
217+
218+ tryCompare(stateGroup, "state", "surface");
219+ }
220+
221+ function test_showSplashUntilAppFullyInit2() {
222+ var stateGroup = findInvisibleChild(applicationWindowLoader.item, "applicationWindowStateGroup");
223+
224+ verify(stateGroup.state === "splashScreen");
225+
226+ surfaceCheckbox.checked = true;
227+
228+ verify(stateGroup.state === "splashScreen");
229+
230+ setApplicationState(appRunning);
231+
232+ tryCompare(stateGroup, "state", "surface");
233+ }
234+
235+ function test_suspendedAppShowsSurface() {
236+ var stateGroup = findInvisibleChild(applicationWindowLoader.item, "applicationWindowStateGroup");
237+
238+ surfaceCheckbox.checked = true;
239+ setApplicationState(appRunning);
240+ tryCompare(stateGroup, "state", "surface");
241+
242+ setApplicationState(appSuspended);
243+
244+ for (var i = 0; i < 10; ++i) {
245+ wait(50);
246+ verify(stateGroup.state === "surface");
247+ }
248+ }
249+
250+ function test_killedAppShowsScreenshot() {
251+ var stateGroup = findInvisibleChild(applicationWindowLoader.item, "applicationWindowStateGroup");
252+
253+ surfaceCheckbox.checked = true;
254+ setApplicationState(appRunning);
255+ tryCompare(stateGroup, "state", "surface");
256+
257+ setApplicationState(appSuspended);
258+
259+ verify(stateGroup.state === "surface");
260+ verify(fakeApplication.surface !== null);
261+
262+ // kill it!
263+ setApplicationState(appStopped);
264+
265+ tryCompare(stateGroup, "state", "screenshot");
266+ tryCompare(fakeApplication, "surface", null);
267+ }
268+
269+ function test_restartApp() {
270+ var stateGroup = findInvisibleChild(applicationWindowLoader.item, "applicationWindowStateGroup");
271+
272+ surfaceCheckbox.checked = true;
273+ setApplicationState(appRunning);
274+ tryCompare(stateGroup, "state", "surface");
275+
276+ setApplicationState(appSuspended);
277+
278+ // kill it
279+ setApplicationState(appStopped);
280+
281+ tryCompare(stateGroup, "state", "screenshot");
282+ tryCompare(fakeApplication, "surface", null);
283+
284+ // and restart it
285+ setApplicationState(appStarting);
286+
287+ wait(50);
288+ verify(fakeApplication.surface === null);
289+ tryCompare(stateGroup, "state", "screenshot");
290+
291+ setApplicationState(appRunning);
292+
293+ wait(50);
294+ tryCompare(stateGroup, "state", "screenshot");
295+
296+ surfaceCheckbox.checked = true;
297+
298+ tryCompare(stateGroup, "state", "surface");
299+ tryCompare(fakeApplication, "screenshot", "");
300+ }
301+
302+ function test_appCrashed() {
303+ var stateGroup = findInvisibleChild(applicationWindowLoader.item, "applicationWindowStateGroup");
304+
305+ surfaceCheckbox.checked = true;
306+ setApplicationState(appRunning);
307+ tryCompare(stateGroup, "state", "surface");
308+
309+ // oh, it crashed...
310+ setApplicationState(appStopped);
311+
312+ tryCompare(stateGroup, "state", "screenshot");
313+ tryCompare(fakeApplication, "surface", null);
314+ }
315+
316+ function test_keepSurfaceWhileInvisible() {
317+ var stateGroup = findInvisibleChild(applicationWindowLoader.item, "applicationWindowStateGroup");
318+
319+ surfaceCheckbox.checked = true;
320+ setApplicationState(appRunning);
321+ tryCompare(stateGroup, "state", "surface");
322+ verify(fakeApplication.surface !== null);
323+
324+ applicationWindowLoader.item.visible = false;
325+
326+ wait(50);
327+ verify(stateGroup.state === "surface");
328+ verify(fakeApplication.surface !== null);
329+
330+ applicationWindowLoader.item.visible = true;
331+
332+ wait(50);
333+ verify(stateGroup.state === "surface");
334+ verify(fakeApplication.surface !== null);
335+ }
336+
337+ function test_touchesReachSurfaceWhenItsShown() {
338+ var stateGroup = findInvisibleChild(applicationWindowLoader.item, "applicationWindowStateGroup");
339+
340+ setApplicationState(appRunning);
341+ surfaceCheckbox.checked = true;
342+
343+ tryCompare(stateGroup, "state", "surface");
344+
345+ // wait until any transition animation has finished
346+ tryCompare(stateGroup, "transitioning", false, 2000);
347+
348+ fakeApplication.surface.touchPressCount = 0;
349+ fakeApplication.surface.touchReleaseCount = 0;
350+
351+ tap(applicationWindowLoader.item,
352+ applicationWindowLoader.item.width / 2, applicationWindowLoader.item.height / 2);
353+
354+ verify(fakeApplication.surface.touchPressCount === 1);
355+ verify(fakeApplication.surface.touchReleaseCount === 1);
356+ }
357+ }
358+}
359+
tests/qmltests/Stages/tst_PhoneStage.qml+33−4
@@ -46,6 +46,7 @@ Item {
46 anchors { fill: parent; leftMargin: phoneStage.width }
46 anchors { fill: parent; leftMargin: phoneStage.width }
47
47
48 Column {
48 Column {
49+ id: buttons
49 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
50 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
50 spacing: units.gu(1)
51 spacing: units.gu(1)
51 Button {
52 Button {
@@ -57,9 +58,33 @@ Item {
57 }
58 }
58 Button {
59 Button {
59 anchors { left: parent.left; right: parent.right }
60 anchors { left: parent.left; right: parent.right }
60− text: "Remove App"
61+ text: "Remove Selected"
61 onClicked: {
62 onClicked: {
62− ApplicationManager.stopApplication(ApplicationManager.get(0).appId)
63+ ApplicationManager.stopApplication(ApplicationManager.get(appList.selectedAppIndex).appId);
64+ }
65+ }
66+ Button {
67+ anchors { left: parent.left; right: parent.right }
68+ text: "Stop Selected"
69+ onClicked: {
70+ ApplicationManager.get(appList.selectedAppIndex).setState(ApplicationInfo.Stopped);
71+ }
72+ }
73+ }
74+ ListView {
75+ id: appList
76+ property int selectedAppIndex
77+ anchors { left: parent.left; right: parent.right; top: buttons.bottom; bottom: parent.bottom }
78+ boundsBehavior: Flickable.StopAtBounds
79+ model: ApplicationManager
80+ delegate: Rectangle {
81+ anchors { left: parent.left; right: parent.right }
82+ height: units.gu(2)
83+ color: appList.selectedAppIndex === model.index ? "red" : "white"
84+ Text { anchors.fill: parent; text: model.appId }
85+ MouseArea {
86+ anchors.fill: parent
87+ onPressed: { appList.selectedAppIndex = model.index; }
63 }
88 }
64 }
89 }
65 }
90 }
@@ -84,7 +109,9 @@ Item {
84 function goToSpread() {
109 function goToSpread() {
85 var spreadView = findChild(phoneStage, "spreadView");
110 var spreadView = findChild(phoneStage, "spreadView");
86
111
112+ // Keep it inside the PhoneStage otherwise the controls on the right side will
113+ // capture the press thus the "- 2" on startX.
87− var startX = phoneStage.width;
114+ var startX = phoneStage.width - 2;
88 var startY = phoneStage.height / 2;
115 var startY = phoneStage.height / 2;
89 var endY = startY;
116 var endY = startY;
90 var endX = units.gu(2);
117 var endX = units.gu(2);
@@ -132,7 +159,9 @@ Item {
132
159
133 var spreadView = findChild(phoneStage, "spreadView");
160 var spreadView = findChild(phoneStage, "spreadView");
134
161
162+ // Keep it inside the PhoneStage otherwise the controls on the right side will
163+ // capture the press thus the "- 2" on startX.
135− var startX = phoneStage.width;
164+ var startX = phoneStage.width - 2;
136 var startY = phoneStage.height / 2;
165 var startY = phoneStage.height / 2;
137 var endY = startY;
166 var endY = startY;
138 var endX = spreadView.width - (spreadView.width * spreadView[data.positionMarker]) - data.offset;
167 var endX = spreadView.width - (spreadView.width * spreadView[data.positionMarker]) - data.offset;
tests/qmltests/Stages/tst_SpreadDelegate.qmlnew file+185
@@ -0,0 +1,185 @@
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+ QtObject {
33+ id: fakeApplication
34+ property url screenshot: ""
35+
36+ function updateScreenshot() {}
37+ function discardScreenshot() {}
38+
39+ property int state: ApplicationInfo.Running
40+ property string name: "Gallery"
41+ property url icon: "../../../qml/graphics/applicationIcons/gallery.png"
42+
43+ property var surface: Image {
44+ source: "../../../qml/Dash/graphics/phone/screenshots/gallery@12.png"
45+ width: spreadDelegateLoader.width
46+ height: spreadDelegateLoader.height
47+
48+ property var application: fakeApplication
49+
50+ signal removed()
51+ function release() {}
52+ }
53+ }
54+
55+ Component {
56+ id: spreadDelegateComponent
57+ SpreadDelegate {
58+ anchors.fill: parent
59+ swipeToCloseEnabled: swipeToCloseCheckbox.checked
60+ closeable: closeableCheckbox.checked
61+ application: fakeApplication
62+ }
63+ }
64+ Loader {
65+ id: spreadDelegateLoader
66+ anchors {
67+ top: parent.top
68+ bottom: parent.bottom
69+ left: parent.left
70+ }
71+ width: units.gu(40)
72+ sourceComponent: spreadDelegateComponent
73+ }
74+
75+ Rectangle {
76+ color: "white"
77+ anchors {
78+ top: parent.top
79+ bottom: parent.bottom
80+ left: spreadDelegateLoader.right
81+ right: parent.right
82+ }
83+
84+ Column {
85+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
86+ spacing: units.gu(1)
87+ Row {
88+ anchors { left: parent.left; right: parent.right }
89+ CheckBox { id: swipeToCloseCheckbox; checked: false; }
90+ Label { text: "swipeToCloseEnabled" }
91+ }
92+ Row {
93+ anchors { left: parent.left; right: parent.right }
94+ CheckBox { id: closeableCheckbox; checked: false }
95+ Label { text: "closeable" }
96+ }
97+ }
98+ }
99+
100+ UT.UnityTestCase {
101+ id: testCase
102+ name: "SpreadDelegate"
103+ when: windowShown
104+
105+ SignalSpy {
106+ id: spyClosedSignal
107+ target: spreadDelegateLoader.item
108+ signalName: "closed"
109+ }
110+
111+ property var dragArea
112+
113+ function init() {
114+ dragArea = findInvisibleChild(spreadDelegateLoader.item, "dragArea");
115+ dragArea.__dateTime = fakeDateTime;
116+ }
117+
118+ function cleanup() {
119+ // reload our test subject to get it in a fresh state once again
120+ spreadDelegateLoader.active = false;
121+ spreadDelegateLoader.active = true;
122+
123+ spyClosedSignal.clear();
124+ }
125+
126+ function test_swipeToClose_data() {
127+ return [
128+ {tag: "swipeToClose=true closeable=true -> appWindow moves away",
129+ swipeToClose: true, closeable: true },
130+
131+ {tag: "swipeToClose=true closeable=alse -> appWindow bounces back",
132+ swipeToClose: true, closeable: false },
133+
134+ {tag: "swipeToClose=false -> appWindow stays put",
135+ swipeToClose: false, closeable: true },
136+ ]
137+ }
138+
139+ function test_swipeToClose(data) {
140+ var appWindowWithShadow = findChild(spreadDelegateLoader.item, "appWindowWithShadow");
141+
142+ verify(appWindowWithShadow.y === 0);
143+
144+ swipeToCloseCheckbox.checked = data.swipeToClose;
145+ closeableCheckbox.checked = data.closeable;
146+
147+ var dragDistance = spreadDelegateLoader.item.height / 2;
148+ var touchX = spreadDelegateLoader.item.width / 2;
149+ var fromY = spreadDelegateLoader.item.height / 2;
150+ var toY = fromY - dragDistance;
151+ touchFlick(spreadDelegateLoader.item,
152+ touchX /* fromX */, fromY, touchX /* toX */, toY,
153+ true /* beginTouch */, false /* endTouch */, dragArea.minSpeedToClose * 1.1 /* speed */);
154+
155+
156+ if (data.swipeToClose) {
157+ verify(appWindowWithShadow.y < 0);
158+ verify(Math.abs(Math.abs(appWindowWithShadow.y) - dragDistance) < units.gu(1));
159+
160+ touchRelease(spreadDelegateLoader.item, touchX, toY - units.gu(1));
161+
162+ waitForCloseAnimationToFinish();
163+
164+ if (data.closeable) {
165+ verify(spyClosedSignal.count === 1);
166+ } else {
167+ verify(spyClosedSignal.count === 0);
168+ tryCompare(appWindowWithShadow, "y", 0);
169+ }
170+
171+ } else {
172+ verify(appWindowWithShadow.y === 0);
173+
174+ touchRelease(spreadDelegateLoader.item, touchX, toY);
175+ }
176+ }
177+
178+ function waitForCloseAnimationToFinish() {
179+ var closeAnimation = findInvisibleChild(spreadDelegateLoader.item, "closeAnimation");
180+ wait(closeAnimation.duration * 1.5);
181+ tryCompare(closeAnimation, "running", false);
182+ }
183+
184+ }
185+}
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