on xenial help sign in
Give a visual feedback on right-edge drag with no running apps (LP: #1116207). Fixes: https://bugs.launchpad.net/bugs/1116207.
b7fee02Daniel d'Andrada
Components/Stage.qml+14
@@ -39,6 +39,7 @@ Showable {
39 type == ApplicationInfo.MainStage ? applicationManager.mainStageApplications
39 type == ApplicationInfo.MainStage ? applicationManager.mainStageApplications
40 : applicationManager.sideStageApplications
40 : applicationManager.sideStageApplications
41 property bool fullyShown: false
41 property bool fullyShown: false
42+ property bool fullyHidden: true
42 readonly property var focusedApplication:
43 readonly property var focusedApplication:
43 type == ApplicationInfo.MainStage ? applicationManager.mainStageFocusedApplication
44 type == ApplicationInfo.MainStage ? applicationManager.mainStageFocusedApplication
44 : applicationManager.sideStageFocusedApplication
45 : applicationManager.sideStageFocusedApplication
@@ -249,6 +250,19 @@ Showable {
249 }
250 }
250 }
251 }
251
252
253+ Connections {
254+ target: stage.applications
255+ onCountChanged: { __discardObsoleteScreenshots(); }
256+ ignoreUnknownSignals: true
257+ }
258+ onFullyHiddenChanged: { __discardObsoleteScreenshots(); }
259+ function __discardObsoleteScreenshots() {
260+ if (stage.fullyHidden && stage.applications && stage.applications.count == 0) {
261+ stage.focusedApplicationWhenUsingScreenshots = undefined;
262+ stage.__hideScreenshots();
263+ }
264+ }
265+
252
266
253 SequentialAnimation {
267 SequentialAnimation {
254 id: showStartingApplicationAnimation
268 id: showStartingApplicationAnimation
Dash/DashHome.qml+1
@@ -26,6 +26,7 @@ import "Music"
26
26
27ScopeView {
27ScopeView {
28 id: root
28 id: root
29+ objectName: "DashHome"
29
30
30 onMovementStarted: listView.showHeader()
31 onMovementStarted: listView.showHeader()
31
32
Shell.qml+25−9
@@ -36,8 +36,8 @@ FocusScope {
36
36
37 // this is only here to select the width / height of the window if not running fullscreen
37 // this is only here to select the width / height of the window if not running fullscreen
38 property bool tablet: false
38 property bool tablet: false
39− width: tablet ? units.gu(160) : ApplicationArguments.hasGeometry() ? ApplicationArguments.width() : units.gu(40)
39+ width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
40− height: tablet ? units.gu(100) : ApplicationArguments.hasGeometry() ? ApplicationArguments.height() : units.gu(71)
40+ height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
41
41
42 property real edgeSize: units.gu(2)
42 property real edgeSize: units.gu(2)
43 property url default_background: shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg"
43 property url default_background: shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg"
@@ -129,11 +129,20 @@ FocusScope {
129
129
130 Item {
130 Item {
131 id: underlay
131 id: underlay
132+ objectName: "underlay"
132 anchors.fill: parent
133 anchors.fill: parent
134+
135+ // Whether the underlay is fully covered by opaque UI elements.
133− visible: !(panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth)
136+ property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
134− && (stages.fullyHidden
137+
138+ // Whether the user should see the topmost application surface (if there's one at all).
139+ property bool applicationSurfaceShouldBeSeen:
140+ (mainStage.applications && mainStage.applications.count > 0)
135− || (stages.fullyShown && mainStage.usingScreenshots)
141+ && (!stages.fullyHidden && !mainStage.usingScreenshots)
136− || !stages.fullyShown && (mainStage.usingScreenshots || (sideStage.shown && sideStage.usingScreenshots)))
142+
143+ // NB! Application surfaces are stacked behing the shell one. So they can only be seen by the user
144+ // through the translucent parts of the shell surface.
145+ visible: !fullyCovered && !applicationSurfaceShouldBeSeen
137
146
138 Image {
147 Image {
139 id: backgroundImage
148 id: backgroundImage
@@ -151,6 +160,7 @@ FocusScope {
151
160
152 Dash {
161 Dash {
153 id: dash
162 id: dash
163+ objectName: "dash"
154
164
155 available: !greeter.shown && !lockscreen.shown
165 available: !greeter.shown && !lockscreen.shown
156 hides: [stages, launcher, panel.indicators]
166 hides: [stages, launcher, panel.indicators]
@@ -178,7 +188,7 @@ FocusScope {
178 }
188 }
179 }
189 }
180
190
181− // FIXME: only necessary because stagesRevealer.animatedProgress and
191+ // FIXME: only necessary because stagesOuterContainer.showProgress and
182 // greeterRevealer.animatedProgress are not animated
192 // greeterRevealer.animatedProgress are not animated
183 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
193 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
184 }
194 }
@@ -198,6 +208,7 @@ FocusScope {
198
208
199 Showable {
209 Showable {
200 id: stages
210 id: stages
211+ objectName: "stages"
201
212
202 x: width
213 x: width
203
214
@@ -254,6 +265,7 @@ FocusScope {
254
265
255 anchors.fill: parent
266 anchors.fill: parent
256 fullyShown: stages.fullyShown
267 fullyShown: stages.fullyShown
268+ fullyHidden: stages.fullyHidden
257 shouldUseScreenshots: !fullyShown
269 shouldUseScreenshots: !fullyShown
258 rightEdgeEnabled: !sideStage.enabled
270 rightEdgeEnabled: !sideStage.enabled
259
271
@@ -338,7 +350,7 @@ FocusScope {
338 }
350 }
339
351
340 DragHandle {
352 DragHandle {
341− id: stagesRevealer
353+ id: stagesDragHandle
342
354
343 anchors.top: parent.top
355 anchors.top: parent.top
344 anchors.bottom: parent.bottom
356 anchors.bottom: parent.bottom
@@ -346,7 +358,11 @@ FocusScope {
346
358
347 width: shell.edgeSize
359 width: shell.edgeSize
348 direction: Direction.Leftwards
360 direction: Direction.Leftwards
349− enabled: mainStage.applications.count > 0 || sideStage.applications.count > 0
361+ property bool haveApps: mainStage.applications.count > 0 || sideStage.applications.count > 0
362+
363+ maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
364+ // Make autocompletion impossible when !haveApps
365+ edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
350 }
366 }
351 }
367 }
352 }
368 }
main.cpp+1−1
@@ -113,7 +113,7 @@ int main(int argc, char** argv)
113 view->setResizeMode(QQuickView::SizeRootObjectToView);
113 view->setResizeMode(QQuickView::SizeRootObjectToView);
114 view->setTitle("Qml Phone Shell");
114 view->setTitle("Qml Phone Shell");
115 view->engine()->setBaseUrl(QUrl::fromLocalFile(::shellAppDirectory()));
115 view->engine()->setBaseUrl(QUrl::fromLocalFile(::shellAppDirectory()));
116− view->rootContext()->setContextProperty("ApplicationArguments", &qmlArgs);
116+ view->rootContext()->setContextProperty("applicationArguments", &qmlArgs);
117 if (args.contains(QLatin1String("-frameless"))) {
117 if (args.contains(QLatin1String("-frameless"))) {
118 view->setFlags(Qt::FramelessWindowHint);
118 view->setFlags(Qt::FramelessWindowHint);
119 }
119 }
tests/mocks/CMakeLists.txt+1
@@ -1,5 +1,6 @@
1add_subdirectory(libusermetrics)
1add_subdirectory(libusermetrics)
2add_subdirectory(LightDM)
2add_subdirectory(LightDM)
3+add_subdirectory(QMenuModel)
3add_subdirectory(Ubuntu)
4add_subdirectory(Ubuntu)
4add_subdirectory(Unity)
5add_subdirectory(Unity)
5add_subdirectory(HudClient)
6add_subdirectory(HudClient)
tests/mocks/QMenuModel/CMakeLists.txtnew file+11
@@ -0,0 +1,11 @@
1+file(GLOB QMLFILES
2+ *.qml
3+ *.js
4+ qmldir
5+)
6+
7+# copy files into build directory for shadow builds
8+add_custom_target(QMenuModelQmlFiles ALL
9+ COMMAND cp ${QMLFILES} ${CMAKE_CURRENT_BINARY_DIR}
10+ DEPENDS ${QMLFILES}
11+)
tests/mocks/QMenuModel/QDBusActionGroup.qmlnew file+27
@@ -0,0 +1,27 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * Authors:
5+ * Daniel d'Andrada <daniel.dandrada@canonical.com>
6+ *
7+ * This program is free software; you can redistribute it and/or modify
8+ * it under the terms of the GNU General Public License as published by
9+ * the Free Software Foundation; version 3.
10+ *
11+ * This program is distributed in the hope that it will be useful,
12+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+ * GNU General Public License for more details.
15+ *
16+ * You should have received a copy of the GNU General Public License
17+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
18+ */
19+
20+import QtQuick 2.0
21+
22+QtObject {
23+ property int busType
24+ property string busName
25+ property string objectPath
26+ function start() {}
27+}
tests/mocks/QMenuModel/qmldirnew file+2
@@ -0,0 +1,2 @@
1+module QMenuModel
2+QDBusActionGroup 0.1 QDBusActionGroup.qml
tests/mocks/Ubuntu/Application/ApplicationManager.cpp+6−2
@@ -37,8 +37,6 @@ ApplicationManager::ApplicationManager(QObject *parent)
37 , m_sideStage(0)
37 , m_sideStage(0)
38{
38{
39 buildListOfAvailableApplications();
39 buildListOfAvailableApplications();
40− createMainStageComponent();
41− createSideStageComponent();
42}
40}
43
41
44ApplicationManager::~ApplicationManager()
42ApplicationManager::~ApplicationManager()
@@ -403,6 +401,9 @@ void ApplicationManager::createMainStageComponent()
403
401
404void ApplicationManager::createMainStage()
402void ApplicationManager::createMainStage()
405{
403{
404+ if (!m_mainStageComponent)
405+ createMainStageComponent();
406+
406 // The assumptions I make here really should hold.
407 // The assumptions I make here really should hold.
407 QQuickView *quickView =
408 QQuickView *quickView =
408 qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
409 qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
@@ -437,6 +438,9 @@ void ApplicationManager::createSideStageComponent()
437
438
438void ApplicationManager::createSideStage()
439void ApplicationManager::createSideStage()
439{
440{
441+ if (!m_sideStageComponent)
442+ createSideStageComponent();
443+
440 // The assumptions I make here really should hold.
444 // The assumptions I make here really should hold.
441 QQuickView *quickView =
445 QQuickView *quickView =
442 qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
446 qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
tests/mocks/Unity/CMakeLists.txt+1
@@ -44,3 +44,4 @@ file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/qmldir"
44 )
44 )
45
45
46add_subdirectory(Launcher)
46add_subdirectory(Launcher)
47+add_subdirectory(Notifications)
tests/mocks/Unity/Notifications/CMakeLists.txtnew file+11
@@ -0,0 +1,11 @@
1+file(GLOB QMLFILES
2+ *.qml
3+ *.js
4+ qmldir
5+)
6+
7+# copy files into build directory for shadow builds
8+add_custom_target(NotificationsQmlFiles ALL
9+ COMMAND cp ${QMLFILES} ${CMAKE_CURRENT_BINARY_DIR}
10+ DEPENDS ${QMLFILES}
11+)
tests/mocks/Unity/fake_scopes.cpp+2−2
@@ -52,8 +52,8 @@ void Scopes::updateScopes()
52 clear();
52 clear();
53 addScope(new Scope("MockScope1", "People", true, this));
53 addScope(new Scope("MockScope1", "People", true, this));
54 addScope(new Scope("MockScope2", "Music", false, this));
54 addScope(new Scope("MockScope2", "Music", false, this));
55− addScope(new Scope("MockScope3", "Home", true, this));
55+ addScope(new Scope("home.scope", "Home", true, this));
56− addScope(new Scope("MockScope4", "Applications", true, this));
56+ addScope(new Scope("applications.scope", "Applications", true, this));
57 addScope(new Scope("MockScope5", "Videos", true, this));
57 addScope(new Scope("MockScope5", "Videos", true, this));
58
58
59 if (!m_loaded) {
59 if (!m_loaded) {
tests/qmltests/CMakeLists.txt+3
@@ -25,6 +25,9 @@ add_qml_test(utils/Unity/Test UnityTest)
25set(qmltest_DEFAULT_TARGETS qmluitests)
25set(qmltest_DEFAULT_TARGETS qmluitests)
26set(qmltest_DEFAULT_NO_ADD_TEST TRUE)
26set(qmltest_DEFAULT_NO_ADD_TEST TRUE)
27set(qmltest_DEFAULT_PROPERTIES "")
27set(qmltest_DEFAULT_PROPERTIES "")
28+
29+add_qml_test(. Shell IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS}
30+ ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/LightDM/single")
28add_manual_qml_test(Components DragHandle IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
31add_manual_qml_test(Components DragHandle IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
29add_qml_test(Components DraggingArea)
32add_qml_test(Components DraggingArea)
30add_qml_test(Components FilterGrid IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/plugins)
33add_qml_test(Components FilterGrid IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/plugins)
tests/qmltests/Components/tst_Stage.qml+1
@@ -161,6 +161,7 @@ Rectangle {
161 applicationManager: fakeAppManager
161 applicationManager: fakeAppManager
162 rightEdgeDraggingAreaWidth: units.gu(2)
162 rightEdgeDraggingAreaWidth: units.gu(2)
163 fullyShown: true
163 fullyShown: true
164+ fullyHidden: false
164 newApplicationScreenshot: FakeApplicationScreenshot {
165 newApplicationScreenshot: FakeApplicationScreenshot {
165 id: newAppScreenshot
166 id: newAppScreenshot
166 parent: stage
167 parent: stage
tests/qmltests/Dash/tst_Dash.qml+4−4
@@ -38,8 +38,8 @@ Item {
38 scopeDelegateMapping: {
38 scopeDelegateMapping: {
39 "MockScope1": "../tests/qmltests/Dash/qml/fake_scopeView1.qml",
39 "MockScope1": "../tests/qmltests/Dash/qml/fake_scopeView1.qml",
40 "MockScope2": "../tests/qmltests/Dash/qml/fake_scopeView2.qml",
40 "MockScope2": "../tests/qmltests/Dash/qml/fake_scopeView2.qml",
41− "MockScope3": "../tests/qmltests/Dash/qml/fake_scopeView3.qml",
41+ "home.scope": "../tests/qmltests/Dash/qml/fake_scopeView3.qml",
42− "MockScope4": "../tests/qmltests/Dash/qml/fake_scopeView4.qml"
42+ "applications.scope": "../tests/qmltests/Dash/qml/fake_scopeView4.qml"
43 }
43 }
44 genericScope: "../tests/qmltests/Dash/qml/fake_generic_scopeView.qml"
44 genericScope: "../tests/qmltests/Dash/qml/fake_generic_scopeView.qml"
45 }
45 }
@@ -69,8 +69,8 @@ Item {
69 return [
69 return [
70 { tag: "MockScope1", visualIndex: 0, shouldBeVisible: true },
70 { tag: "MockScope1", visualIndex: 0, shouldBeVisible: true },
71 { tag: "MockScope2", visualIndex: -1, shouldBeVisible: false },
71 { tag: "MockScope2", visualIndex: -1, shouldBeVisible: false },
72− { tag: "MockScope3", visualIndex: 1, shouldBeVisible: true },
72+ { tag: "home.scope", visualIndex: 1, shouldBeVisible: true },
73− { tag: "MockScope4", visualIndex: 2, shouldBeVisible: true },
73+ { tag: "applications.scope", visualIndex: 2, shouldBeVisible: true },
74 { tag: "MockScope5", visualIndex: 3, shouldBeVisible: true },
74 { tag: "MockScope5", visualIndex: 3, shouldBeVisible: true },
75 ]
75 ]
76 }
76 }
tests/qmltests/Dash/tst_DashContent.qml+12−12
@@ -31,8 +31,8 @@ Item {
31 property var scopeStatus: {
31 property var scopeStatus: {
32 'MockScope1': { 'movementStarted': 0, 'positionedAtBeginning': 0 },
32 'MockScope1': { 'movementStarted': 0, 'positionedAtBeginning': 0 },
33 'MockScope2': { 'movementStarted': 0, 'positionedAtBeginning': 0 },
33 'MockScope2': { 'movementStarted': 0, 'positionedAtBeginning': 0 },
34− 'MockScope3': { 'movementStarted': 0, 'positionedAtBeginning': 0 },
34+ 'home.scope': { 'movementStarted': 0, 'positionedAtBeginning': 0 },
35− 'MockScope4': { 'movementStarted': 0, 'positionedAtBeginning': 0 },
35+ 'applications.scope': { 'movementStarted': 0, 'positionedAtBeginning': 0 },
36 'MockScope5': { 'movementStarted': 0, 'positionedAtBeginning': 0 }
36 'MockScope5': { 'movementStarted': 0, 'positionedAtBeginning': 0 }
37 }
37 }
38
38
@@ -55,8 +55,8 @@ Item {
55 scopeDelegateMapping: {
55 scopeDelegateMapping: {
56 "MockScope1": "../tests/qmltests/Dash/qml/fake_scopeView1.qml",
56 "MockScope1": "../tests/qmltests/Dash/qml/fake_scopeView1.qml",
57 "MockScope2": "../tests/qmltests/Dash/qml/fake_scopeView2.qml",
57 "MockScope2": "../tests/qmltests/Dash/qml/fake_scopeView2.qml",
58− "MockScope3": "../tests/qmltests/Dash/qml/fake_scopeView3.qml",
58+ "home.scope": "../tests/qmltests/Dash/qml/fake_scopeView3.qml",
59− "MockScope4": "../tests/qmltests/Dash/qml/fake_scopeView4.qml"
59+ "applications.scope": "../tests/qmltests/Dash/qml/fake_scopeView4.qml"
60 }
60 }
61 genericScope: "../tests/qmltests/Dash/qml/fake_generic_scopeView.qml"
61 genericScope: "../tests/qmltests/Dash/qml/fake_generic_scopeView.qml"
62 }
62 }
@@ -68,11 +68,11 @@ Item {
68 scopeStatus["MockScope2"].movementStarted = 0;
68 scopeStatus["MockScope2"].movementStarted = 0;
69 scopeStatus["MockScope2"].positionedAtBeginning = 0;
69 scopeStatus["MockScope2"].positionedAtBeginning = 0;
70
70
71− scopeStatus["MockScope3"].movementStarted = 0;
71+ scopeStatus["home.scope"].movementStarted = 0;
72− scopeStatus["MockScope3"].positionedAtBeginning = 0;
72+ scopeStatus["home.scope"].positionedAtBeginning = 0;
73
73
74− scopeStatus["MockScope4"].movementStarted = 0;
74+ scopeStatus["applications.scope"].movementStarted = 0;
75− scopeStatus["MockScope4"].positionedAtBeginning = 0;
75+ scopeStatus["applications.scope"].positionedAtBeginning = 0;
76
76
77 scopeStatus["MockScope5"].movementStarted = 0;
77 scopeStatus["MockScope5"].movementStarted = 0;
78 scopeStatus["MockScope5"].positionedAtBeginning = 0;
78 scopeStatus["MockScope5"].positionedAtBeginning = 0;
@@ -127,8 +127,8 @@ Item {
127 compare(movementStartedSpy.count, 1, "DashContent should have emitted movementStarted signal when content list did.");
127 compare(movementStartedSpy.count, 1, "DashContent should have emitted movementStarted signal when content list did.");
128 compare(scopeStatus["MockScope1"].movementStarted, 1, "MockScope1 should have emitted movementStarted signal when content list did.");
128 compare(scopeStatus["MockScope1"].movementStarted, 1, "MockScope1 should have emitted movementStarted signal when content list did.");
129 compare(scopeStatus["MockScope2"].movementStarted, 1, "MockScope2 should have emitted movementStarted signal when content list did.");
129 compare(scopeStatus["MockScope2"].movementStarted, 1, "MockScope2 should have emitted movementStarted signal when content list did.");
130− compare(scopeStatus["MockScope3"].movementStarted, 1, "MockScope3 should have emitted movementStarted signal when content list did.");
130+ compare(scopeStatus["home.scope"].movementStarted, 1, "home.scope should have emitted movementStarted signal when content list did.");
131− compare(scopeStatus["MockScope4"].movementStarted, 1, "MockScope4 should have emitted movementStarted signal when content list did.");
131+ compare(scopeStatus["applications.scope"].movementStarted, 1, "applications.scope should have emitted movementStarted signal when content list did.");
132 compare(scopeStatus["MockScope5"].movementStarted, 1, "MockScope5 should have emitted movementStarted signal when content list did.");
132 compare(scopeStatus["MockScope5"].movementStarted, 1, "MockScope5 should have emitted movementStarted signal when content list did.");
133 }
133 }
134
134
@@ -140,8 +140,8 @@ Item {
140 dashContent.positionedAtBeginning();
140 dashContent.positionedAtBeginning();
141 compare(scopeStatus["MockScope1"].positionedAtBeginning, 1, "MockScope1 should have emitted positionedAtBeginning signal when DashContent did.");
141 compare(scopeStatus["MockScope1"].positionedAtBeginning, 1, "MockScope1 should have emitted positionedAtBeginning signal when DashContent did.");
142 compare(scopeStatus["MockScope2"].positionedAtBeginning, 1, "MockScope2 should have emitted positionedAtBeginning signal when DashContent did.");
142 compare(scopeStatus["MockScope2"].positionedAtBeginning, 1, "MockScope2 should have emitted positionedAtBeginning signal when DashContent did.");
143− compare(scopeStatus["MockScope3"].positionedAtBeginning, 1, "MockScope3 should have emitted positionedAtBeginning signal when DashContent did.");
143+ compare(scopeStatus["home.scope"].positionedAtBeginning, 1, "home.scope should have emitted positionedAtBeginning signal when DashContent did.");
144− compare(scopeStatus["MockScope4"].positionedAtBeginning, 1, "MockScope4 should have emitted positionedAtBeginning signal when DashContent did.");
144+ compare(scopeStatus["applications.scope"].positionedAtBeginning, 1, "applications.scope should have emitted positionedAtBeginning signal when DashContent did.");
145 compare(scopeStatus["MockScope5"].positionedAtBeginning, 1, "MockScope5 should have emitted positionedAtBeginning signal when DashContent did.");
145 compare(scopeStatus["MockScope5"].positionedAtBeginning, 1, "MockScope5 should have emitted positionedAtBeginning signal when DashContent did.");
146 }
146 }
147
147
tests/qmltests/tst_Shell.qmlnew file+281
@@ -0,0 +1,281 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * Authors:
5+ * Daniel d'Andrada <daniel.dandrada@canonical.com>
6+ *
7+ * This program is free software; you can redistribute it and/or modify
8+ * it under the terms of the GNU General Public License as published by
9+ * the Free Software Foundation; version 3.
10+ *
11+ * This program is distributed in the hope that it will be useful,
12+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+ * GNU General Public License for more details.
15+ *
16+ * You should have received a copy of the GNU General Public License
17+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
18+ */
19+
20+import QtQuick 2.0
21+import QtTest 1.0
22+import Ubuntu.Application 0.1
23+import Unity.Test 0.1 as UT
24+
25+import "../.."
26+
27+Item {
28+ width: shell.width
29+ height: shell.height
30+
31+ QtObject {
32+ id: applicationArguments
33+
34+ function hasGeometry() {
35+ return false;
36+ }
37+
38+ function width() {
39+ return 0;
40+ }
41+
42+ function height() {
43+ return 0;
44+ }
45+ }
46+
47+ Shell {
48+ id: shell
49+ }
50+
51+ UT.UnityTestCase {
52+ name: "Shell"
53+ when: windowShown
54+
55+ function initTestCase() {
56+ // swipe away the greeter/lockscreen
57+ var touchX = shell.width - (shell.edgeSize / 2);
58+ var touchY = shell.height / 2;
59+ touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY);
60+
61+ var dash = findChild(shell, "dash");
62+ // wait until the animation has finished
63+ tryCompare(dash, "contentScale", 1.0);
64+ tryCompare(dash, "opacity", 1.0);
65+ }
66+
67+ function cleanup() {
68+ // kill all (fake) running apps
69+ ApplicationManager.mainStageApplications.clear();
70+ ApplicationManager.sideStageApplications.clear();
71+
72+ var dashHome = findChild(shell, "DashHome");
73+ swipeUntilScopeViewIsReached(dashHome);
74+ }
75+
76+ /*
77+ Test the effect of a right-edge drag on the dash in 3 situations:
78+ 1 - when no application has been launched yet
79+ 2 - when there's a minimized application
80+ 3 - after the last running application has been closed/stopped
81+
82+ The behavior of Dash on 3 should be the same as on 1.
83+ */
84+ function test_rightEdgeDrag() {
85+ checkRightEdgeDragWithNoRunningApps();
86+
87+ dragLauncherIntoView();
88+
89+ // Launch an app from the launcher
90+ tapOnAppIconInLauncher();
91+ waitUntilApplicationWindowIsFullyVisible();
92+
93+ // Minimize the application we just launched
94+ swipeFromLeftEdge();
95+
96+ waitForUIToSettle();
97+
98+ checkRightEdgeDragWithMinimizedApp();
99+
100+ // Minimize that application once again
101+ swipeFromLeftEdge();
102+
103+ // kill it
104+ ApplicationManager.mainStageApplications.clear();
105+
106+ waitForUIToSettle();
107+
108+ // Right edge behavior should now be the same as before that app was launched
109+ checkRightEdgeDragWithNoRunningApps();
110+ }
111+
112+ /*
113+ Perform a right-edge drag when the Dash is being show and there are
114+ no running/minimized apps to be restored.
115+
116+ The expected behavior is that an animation should be played to hint the
117+ user that his right-edge drag gesture has been successfully recognized
118+ but there is no application to be brought to foreground.
119+ */
120+ function checkRightEdgeDragWithNoRunningApps() {
121+ var touchX = shell.width - (shell.edgeSize / 2);
122+ var touchY = shell.height / 2;
123+
124+ var dash = findChild(shell, "dash");
125+ // check that dash has normal scale and opacity
126+ compare(dash.contentScale, 1.0);
127+ compare(dash.opacity, 1.0);
128+
129+ touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY,
130+ true /* beginTouch */, false /* endTouch */);
131+
132+ // check that Dash has been scaled down and had its opacity reduced
133+ tryCompareFunction(function() { return dash.contentScale <= 0.9; }, true);
134+ tryCompareFunction(function() { return dash.opacity <= 0.5; }, true);
135+
136+ touchRelease(shell, shell.width * 0.1, touchY);
137+
138+ // and now everything should have gone back to normal
139+ tryCompare(dash, "contentScale", 1.0);
140+ tryCompare(dash, "opacity", 1.0);
141+ }
142+
143+ /*
144+ Perform a right-edge drag when the Dash is being show and there is
145+ a running/minimized app to be restored.
146+
147+ The expected behavior is that the dash should fade away and ultimately be
148+ made invisible once the gesture is finished as the restored app will now
149+ be on foreground.
150+ */
151+ function checkRightEdgeDragWithMinimizedApp() {
152+ var touchX = shell.width - (shell.edgeSize / 2);
153+ var touchY = shell.height / 2;
154+
155+ var dash = findChild(shell, "dash");
156+ // check that dash has normal scale and opacity
157+ tryCompare(dash, "contentScale", 1.0);
158+ tryCompare(dash, "opacity", 1.0);
159+
160+ touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY,
161+ true /* beginTouch */, false /* endTouch */);
162+
163+ // check that Dash has been scaled down and had its opacity reduced
164+ tryCompareFunction(function() { return dash.contentScale <= 0.9; }, true);
165+ tryCompareFunction(function() { return dash.opacity <= 0.5; }, true);
166+
167+ touchRelease(shell, shell.width * 0.1, touchY);
168+
169+ // dash should have gone away, now that the app is on foreground
170+ tryCompare(dash, "visible", false);
171+ }
172+
173+ // Wait for the whole UI to settle down
174+ function waitForUIToSettle() {
175+ waitUntilApplicationWindowIsFullyHidden();
176+ var dashContentList = findChild(shell, "dashContentList");
177+ tryCompare(dashContentList, "moving", false);
178+ }
179+
180+ function dragLauncherIntoView() {
181+ var launcherPanel = findChild(shell, "launcherPanel");
182+ verify(launcherPanel.x = - launcherPanel.width);
183+ swipeFromLeftEdge();
184+ tryCompare(launcherPanel, "x", 0);
185+ }
186+
187+ function tapOnAppIconInLauncher() {
188+ var launcherPanel = findChild(shell, "launcherPanel");
189+
190+ // pick the first icon, the one at the bottom.
191+ var appIcon = findChild(launcherPanel, "launcherDelegate0")
192+
193+ // Swipe upwards over the launcher to ensure that this icon
194+ // at the bottom is not folded and faded away.
195+ var touchStartX = launcherPanel.width / 2;
196+ var touchStartY = launcherPanel.height / 2;
197+ touchFlick(launcherPanel, touchStartX, touchStartY, touchStartX, 0);
198+ tryCompare(launcherPanel, "moving", false);
199+
200+ // NB tapping (i.e., using touch events) doesn't activate the icon... go figure...
201+ mouseClick(appIcon, appIcon.width / 2, appIcon.height / 2);
202+ }
203+
204+ function waitUntilApplicationWindowIsFullyVisible() {
205+ var underlay = findChild(shell, "underlay");
206+ tryCompare(underlay, "visible", false);
207+ }
208+
209+ function waitUntilApplicationWindowIsFullyHidden() {
210+ var stages = findChild(shell, "stages");
211+ tryCompare(stages, "fullyHidden", true);
212+ }
213+
214+ function swipeUntilScopeViewIsReached(scopeView) {
215+ while (!itemIsOnScreen(scopeView)) {
216+ if (itemIsToLeftOfScreen(scopeView)) {
217+ swipeRightFromCenter();
218+ } else {
219+ swipeLeftFromCenter();
220+ }
221+ waitUntilItemStopsMoving(scopeView);
222+ }
223+ }
224+
225+ function swipeFromLeftEdge() {
226+ var touchStartX = 2;
227+ var touchStartY = shell.height / 2;
228+ touchFlick(shell, touchStartX, touchStartY, shell.width * 0.75, touchStartY);
229+ }
230+
231+ function swipeLeftFromCenter() {
232+ var touchStartX = shell.width / 2;
233+ var touchStartY = shell.height / 2;
234+ touchFlick(shell, touchStartX, touchStartY, 0, touchStartY);
235+ }
236+
237+ function swipeRightFromCenter() {
238+ var touchStartX = shell.width / 2;
239+ var touchStartY = shell.height / 2;
240+ touchFlick(shell, touchStartX, touchStartY, shell.width, touchStartY);
241+ }
242+
243+ function swipeUpFromCenter() {
244+ var touchStartX = shell.width / 2;
245+ var touchStartY = shell.height / 2;
246+ touchFlick(shell, touchStartX, touchStartY, touchStartX, 0);
247+ }
248+
249+ function itemIsOnScreen(item) {
250+ var itemRectInShell = item.mapToItem(shell, 0, 0, item.width, item.height);
251+
252+ return itemRectInShell.x >= 0
253+ && itemRectInShell.y >= 0
254+ && itemRectInShell.x + itemRectInShell.width <= shell.width
255+ && itemRectInShell.y + itemRectInShell.height <= shell.height;
256+ }
257+
258+ function itemIsToLeftOfScreen(item) {
259+ var itemRectInShell = item.mapToItem(shell, 0, 0, item.width, item.height);
260+ return itemRectInShell.x < 0;
261+ }
262+
263+ function waitUntilItemStopsMoving(item) {
264+ var itemRectInShell = item.mapToItem(shell, 0, 0, item.width, item.height);
265+ var previousX = itemRectInShell.x;
266+ var previousY = itemRectInShell.y;
267+ var isStill = false;
268+
269+ do {
270+ wait(100);
271+ itemRectInShell = item.mapToItem(shell, 0, 0, item.width, item.height);
272+ if (itemRectInShell.x == previousX && itemRectInShell.y == previousY) {
273+ isStill = true;
274+ } else {
275+ previousX = itemRectInShell.x;
276+ previousY = itemRectInShell.y;
277+ }
278+ } while (!isStill);
279+ }
280+ }
281+}