on xenial help sign in
Fixed desktop stage app focus.

Approved by: Daniel d'Andrada

commitdce2b426d76f1229bc64d5aa0e612e9d3661de1f authorNick Dedekind <nick.dedekind@canonical.com> dateMon 18 May 2015, 23:03 (11 years ago) parent3f7c49d0dd13b0
24 files changed+843−159
qml/Shell.qml−1 qml/Stages/DecoratedWindow.qml+1 qml/Stages/DesktopStage.qml+20−14 qml/Stages/PhoneStage.qml+4−4 qml/Stages/TabletStage.qml+10−11 tests/mocks/CMakeLists.txt+1 tests/mocks/GSettings.1.0/fake_gsettings.h+2−2 tests/mocks/GSettings.1.0/plugin.cpp+6 tests/mocks/Unity/Application/ApplicationManager.cpp+1−1 tests/mocks/Utils/CMakeLists.txt+39 tests/mocks/Utils/HomeKeyWatcher.qml+21 tests/mocks/Utils/Style.js+37 tests/mocks/Utils/Utils.qmltypes+175 tests/mocks/Utils/constants.cpp+23 tests/mocks/Utils/constants.h+43 tests/mocks/Utils/plugin.cpp+73 tests/mocks/Utils/plugin.h+33 tests/mocks/Utils/qmldir+5 tests/mocks/Utils/windowstatestorage.cpp+48 tests/mocks/Utils/windowstatestorage.h+38 tests/qmltests/Stages/ApplicationCheckBox.qml+49−20 tests/qmltests/Stages/tst_DesktopStage.qml+77−67 tests/qmltests/tst_Shell.qml+136−39 tests/utils/modules/Unity/Test/MouseTouchEmulationCheckbox.qml+1
qml/Shell.qml−1
@@ -109,7 +109,6 @@ Item {
109109
110110 GSettings {
111111 id: backgroundSettings
112− objectName: "backgroundSettings"
113112 schema.id: "org.gnome.desktop.background"
114113 }
115114
qml/Stages/DecoratedWindow.qml+1
@@ -42,6 +42,7 @@ FocusScope {
4242
4343 WindowDecoration {
4444 id: decoration
45+ objectName: application ? "appWindowDecoration_" + application.appId : "appWindowDecoration_null"
4546 anchors { left: parent.left; top: parent.top; right: parent.right }
4647 height: units.gu(3)
4748 title: model.name
qml/Stages/DesktopStage.qml+20−14
@@ -20,17 +20,14 @@ import QtQuick 2.3
2020import Ubuntu.Components 1.1
2121import Unity.Application 0.1
2222import "../Components/PanelState"
23−import Utils 0.1
2423
25−FocusScope {
24+Rectangle {
2625 id: root
2726
2827 anchors.fill: parent
2928
3029 property alias background: wallpaper.source
3130
32− property var windowStateStorage: WindowStateStorage
33−
3431 CrossFadeImage {
3532 id: wallpaper
3633 anchors.fill: parent
@@ -58,7 +55,16 @@ FocusScope {
5855 id: priv
5956
6057 readonly property string focusedAppId: ApplicationManager.focusedApplicationId
61− readonly property var focusedAppDelegate: focusedAppId ? appRepeater.itemAt(indexOf(focusedAppId)) : null
58+ readonly property var focusedAppDelegate: {
59+ var index = indexOf(focusedAppId);
60+ return index >= 0 && index < appRepeater.count ? appRepeater.itemAt(index) : null
61+ }
62+
63+ onFocusedAppDelegateChanged: {
64+ if (focusedAppDelegate) {
65+ focusedAppDelegate.focus = true;
66+ }
67+ }
6268
6369 function indexOf(appId) {
6470 for (var i = 0; i < ApplicationManager.count; i++) {
@@ -89,13 +95,19 @@ FocusScope {
8995 id: appRepeater
9096 model: ApplicationManager
9197
92− delegate: Item {
98+ delegate: FocusScope {
9399 id: appDelegate
94100 z: ApplicationManager.count - index
95101 y: units.gu(3)
96102 width: units.gu(60)
97103 height: units.gu(50)
98104
105+ onFocusChanged: {
106+ if (focus) {
107+ ApplicationManager.requestFocusApplication(model.appId);
108+ }
109+ }
110+
99111 readonly property int minWidth: units.gu(10)
100112 readonly property int minHeight: units.gu(10)
101113
@@ -119,14 +131,13 @@ FocusScope {
119131 ]
120132
121133 WindowMoveResizeArea {
122− windowStateStorage: root.windowStateStorage
123134 target: appDelegate
124135 minWidth: appDelegate.minWidth
125136 minHeight: appDelegate.minHeight
126137 resizeHandleWidth: units.gu(0.5)
127138 windowId: model.appId // FIXME: Change this to point to windowId once we have such a thing
128139
129− onPressed: decoratedWindow.focus = true;
140+ onPressed: appDelegate.focus = true;
130141 }
131142
132143 DecoratedWindow {
@@ -135,12 +146,7 @@ FocusScope {
135146 anchors.fill: parent
136147 application: ApplicationManager.get(index)
137148 active: ApplicationManager.focusedApplicationId === model.appId
138−
139− onFocusChanged: {
140− if (focus) {
141− ApplicationManager.requestFocusApplication(model.appId);
142− }
143− }
149+ focus: true
144150
145151 onClose: ApplicationManager.stopApplication(model.appId)
146152 onMaximize: appDelegate.state = (appDelegate.state == "maximized" ? "normal" : "maximized")
qml/Stages/PhoneStage.qml+4−4
@@ -117,14 +117,14 @@ Rectangle {
117117
118118 readonly property int firstSpreadIndex: root.focusFirstApp ? 1 : 0
119119 property string focusedAppId: applicationManager.focusedApplicationId
120− property var focusedApplication: applicationManager.findApplication(focusedAppId)
121− property var focusedAppDelegate: null
120+ readonly property var focusedAppDelegate: {
121+ var index = indexOf(focusedAppId);
122+ return index >= 0 && index < spreadRepeater.count ? spreadRepeater.itemAt(index) : null
123+ }
122124
123125 property real oldInverseProgress: 0
124126 property bool animateX: false
125127
126− onFocusedAppIdChanged: focusedAppDelegate = spreadRepeater.itemAt(0);
127−
128128 onFocusedAppDelegateChanged: {
129129 if (focusedAppDelegate) {
130130 focusedAppDelegate.focus = true;
qml/Stages/TabletStage.qml+10−11
@@ -53,6 +53,11 @@ Rectangle {
5353 id: priv
5454
5555 property string focusedAppId: ApplicationManager.focusedApplicationId
56+ readonly property var focusedAppDelegate: {
57+ var index = indexOf(focusedAppId);
58+ return index >= 0 && index < spreadRepeater.count ? spreadRepeater.itemAt(index) : null
59+ }
60+
5661 property string oldFocusedAppId: ""
5762
5863 property string mainStageAppId
@@ -76,19 +81,11 @@ Rectangle {
7681
7782 appId0 = ApplicationManager.count >= 1 ? ApplicationManager.get(0).appId : "";
7883 appId1 = ApplicationManager.count > 1 ? ApplicationManager.get(1).appId : "";
79−
80− // Update the QML focus accordingly
81− updateSpreadDelegateFocus();
8284 }
8385
84− function updateSpreadDelegateFocus() {
85− if (priv.focusedAppId) {
86− var focusedAppIndex = priv.indexOf(priv.focusedAppId);
87− if (focusedAppIndex !== -1) {
88− spreadRepeater.itemAt(focusedAppIndex).focus = true;
89− } else {
90− console.warn("TabletStage: Failed to find the SpreadDelegate for appID=" + priv.focusedAppId);
91− }
86+ onFocusedAppDelegateChanged: {
87+ if (focusedAppDelegate) {
88+ focusedAppDelegate.focus = true;
9289 }
9390 }
9491
@@ -495,6 +492,8 @@ Rectangle {
495492
496493 delegate: TransformedTabletSpreadDelegate {
497494 id: spreadTile
495+ objectName: model.appId ? "tabletSpreadDelegate_" + model.appId
496+ : "tabletSpreadDelegate_null";
498497 height: spreadView.height
499498 width: model.stage == ApplicationInfoInterface.MainStage ? spreadView.width : spreadView.sideStageWidth
500499 active: model.appId == priv.mainStageAppId || model.appId == priv.sideStageAppId
tests/mocks/CMakeLists.txt+1
@@ -41,6 +41,7 @@ add_subdirectory(Ubuntu)
4141add_subdirectory(Unity)
4242add_subdirectory(QtMultimedia)
4343add_subdirectory(Wizard)
44+add_subdirectory(Utils)
4445
4546install(
4647 DIRECTORY data
tests/mocks/GSettings.1.0/fake_gsettings.h+2−2
@@ -78,10 +78,10 @@ public:
7878 ~GSettingsControllerQml();
7979
8080 QString pictureUri() const;
81− void setPictureUri(const QString &str);
81+ Q_INVOKABLE void setPictureUri(const QString &str);
8282
8383 QString usageMode() const;
84− void setUsageMode(const QString &usageMode);
84+ Q_INVOKABLE void setUsageMode(const QString &usageMode);
8585
8686Q_SIGNALS:
8787 void pictureUriChanged(const QString&);
tests/mocks/GSettings.1.0/plugin.cpp+6
@@ -19,8 +19,14 @@
1919
2020#include <QtQml/qqml.h>
2121
22+static QObject* controllerProvider(QQmlEngine* /* engine */, QJSEngine* /* scriptEngine */)
23+{
24+ return GSettingsControllerQml::instance();
25+}
26+
2227void FakeGSettingsQmlPlugin::registerTypes(const char *uri)
2328{
29+ qmlRegisterSingletonType<GSettingsControllerQml>(uri, 1, 0, "GSettingsController", controllerProvider);
2430 qmlRegisterType<GSettingsQml>(uri, 1, 0, "GSettings");
2531 qmlRegisterUncreatableType<GSettingsSchemaQml>(uri, 1, 0, "GSettingsSchema",
2632 "GSettingsSchema can only be used inside of a GSettings component");
tests/mocks/Unity/Application/ApplicationManager.cpp+1−1
@@ -143,7 +143,7 @@ QModelIndex ApplicationManager::findIndex(ApplicationInfo* application)
143143}
144144
145145void ApplicationManager::add(ApplicationInfo *application) {
146− if (!application) {
146+ if (!application || m_runningApplications.contains(application)) {
147147 return;
148148 }
149149
tests/mocks/Utils/CMakeLists.txtnew file+39
@@ -0,0 +1,39 @@
1+pkg_search_module(GIO REQUIRED gio-2.0)
2+
3+include_directories(
4+ ${CMAKE_CURRENT_SOURCE_DIR}
5+ ${CMAKE_CURRENT_BINARY_DIR}
6+ ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
7+ ${GIO_INCLUDE_DIRS}
8+ ${CMAKE_SOURCE_DIR}/plugins/Utils
9+)
10+
11+set(QMLPLUGIN_SRC
12+ ${CMAKE_SOURCE_DIR}/plugins/Utils/inputwatcher.cpp
13+ ${CMAKE_SOURCE_DIR}/plugins/Utils/qlimitproxymodelqml.cpp
14+ ${CMAKE_SOURCE_DIR}/plugins/Utils/unitysortfilterproxymodelqml.cpp
15+ ${CMAKE_SOURCE_DIR}/plugins/Utils/relativetimeformatter.cpp
16+ ${CMAKE_SOURCE_DIR}/plugins/Utils/timeformatter.cpp
17+ ${CMAKE_SOURCE_DIR}/plugins/Utils/unitymenumodelpaths.cpp
18+ ${CMAKE_SOURCE_DIR}/plugins/Utils/windowkeysfilter.cpp
19+ ${CMAKE_SOURCE_DIR}/plugins/Utils/easingcurve.cpp
20+ constants.cpp
21+ plugin.cpp
22+ windowstatestorage.cpp
23+ )
24+
25+add_library(FakeUtils-qml SHARED
26+ ${QMLPLUGIN_SRC}
27+ )
28+
29+target_link_libraries(FakeUtils-qml ${GIO_LDFLAGS})
30+
31+# Because this is an internal support library, we want
32+# to expose all symbols in it. Consider changing this
33+# either to a static library or just using the
34+# files directly in targets.
35+set_target_properties(FakeUtils-qml PROPERTIES COMPILE_FLAGS -fvisibility=default)
36+
37+qt5_use_modules(FakeUtils-qml Qml Quick DBus Network Gui)
38+
39+add_unity8_mock(Utils 0.1 Utils TARGETS FakeUtils-qml)
tests/mocks/Utils/HomeKeyWatcher.qmlnew file+21
@@ -0,0 +1,21 @@
1+/*
2+ * Copyright (C) 2015 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+QtObject {
20+ signal activated()
21+}
tests/mocks/Utils/Style.jsnew file+37
@@ -0,0 +1,37 @@
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+.pragma library
18+
19+/*! \brief Calculate average luminance of the passed colors
20+
21+ \note If not fully opaque, luminance is dependant on blending.
22+ */
23+function luminance() {
24+ var sum = 0;
25+ // TODO this was originally
26+ // for (var k in arguments) {
27+ // but for some unkown reason was causing crashes in testDash/testDashContent
28+ // investigate when we have some time
29+ for (var k = 0; k < arguments.length; ++k) {
30+ // only way to convert string to color
31+ var c = Qt.lighter(arguments[k], 1.0);
32+
33+ sum += 0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b;
34+ }
35+
36+ return sum / arguments.length;
37+}
tests/mocks/Utils/Utils.qmltypesnew file+175
@@ -0,0 +1,175 @@
1+import QtQuick.tooling 1.1
2+
3+// This file describes the plugin-supplied types contained in the library.
4+// It is used for QML tooling purposes only.
5+//
6+// This file was auto-generated by:
7+// 'qmlplugindump -notrelocatable Utils 0.1 plugins'
8+
9+Module {
10+ Component {
11+ name: "EasingCurve"
12+ prototype: "QObject"
13+ exports: ["Utils/EasingCurve 0.1"]
14+ exportMetaObjectRevisions: [0]
15+ Property { name: "type"; type: "QEasingCurve::Type" }
16+ Property { name: "period"; type: "double" }
17+ Property { name: "progress"; type: "double" }
18+ Property { name: "value"; type: "double"; isReadonly: true }
19+ }
20+ Component {
21+ name: "QAbstractProxyModel"
22+ prototype: "QAbstractItemModel"
23+ Property { name: "sourceModel"; type: "QAbstractItemModel"; isPointer: true }
24+ }
25+ Component { name: "QIdentityProxyModel"; prototype: "QAbstractProxyModel" }
26+ Component {
27+ name: "QLimitProxyModelQML"
28+ prototype: "QIdentityProxyModel"
29+ exports: ["Utils/LimitProxyModel 0.1"]
30+ exportMetaObjectRevisions: [0]
31+ Property { name: "model"; type: "QAbstractItemModel"; isPointer: true }
32+ Property { name: "limit"; type: "int" }
33+ Property { name: "count"; type: "int"; isReadonly: true }
34+ Signal { name: "totalCountChanged" }
35+ }
36+ Component {
37+ name: "QSortFilterProxyModel"
38+ prototype: "QAbstractProxyModel"
39+ Property { name: "filterRegExp"; type: "QRegExp" }
40+ Property { name: "filterKeyColumn"; type: "int" }
41+ Property { name: "dynamicSortFilter"; type: "bool" }
42+ Property { name: "filterCaseSensitivity"; type: "Qt::CaseSensitivity" }
43+ Property { name: "sortCaseSensitivity"; type: "Qt::CaseSensitivity" }
44+ Property { name: "isSortLocaleAware"; type: "bool" }
45+ Property { name: "sortRole"; type: "int" }
46+ Property { name: "filterRole"; type: "int" }
47+ Method {
48+ name: "setFilterRegExp"
49+ Parameter { name: "pattern"; type: "string" }
50+ }
51+ Method {
52+ name: "setFilterWildcard"
53+ Parameter { name: "pattern"; type: "string" }
54+ }
55+ Method {
56+ name: "setFilterFixedString"
57+ Parameter { name: "pattern"; type: "string" }
58+ }
59+ Method { name: "clear" }
60+ Method { name: "invalidate" }
61+ }
62+ Component {
63+ name: "RelativeTimeFormatter"
64+ prototype: "TimeFormatter"
65+ exports: ["Utils/RelativeTimeFormatter 0.1"]
66+ exportMetaObjectRevisions: [0]
67+ }
68+ Component {
69+ name: "TimeFormatter"
70+ prototype: "QObject"
71+ exports: ["Utils/GDateTimeFormatter 0.1", "Utils/TimeFormatter 0.1"]
72+ exportMetaObjectRevisions: [0, 0]
73+ Property { name: "format"; type: "string" }
74+ Property { name: "timeString"; type: "string"; isReadonly: true }
75+ Property { name: "time"; type: "qlonglong" }
76+ Signal {
77+ name: "formatChanged"
78+ Parameter { name: "format"; type: "string" }
79+ }
80+ Signal {
81+ name: "timeStringChanged"
82+ Parameter { name: "timeString"; type: "string" }
83+ }
84+ Signal {
85+ name: "timeChanged"
86+ Parameter { name: "time"; type: "qlonglong" }
87+ }
88+ }
89+ Component {
90+ name: "UnityMenuModelPaths"
91+ prototype: "QObject"
92+ exports: ["Utils/UnityMenuModelPaths 0.1"]
93+ exportMetaObjectRevisions: [0]
94+ Property { name: "source"; type: "QVariant" }
95+ Property { name: "busName"; type: "QByteArray"; isReadonly: true }
96+ Property { name: "actions"; type: "QVariantMap"; isReadonly: true }
97+ Property { name: "menuObjectPath"; type: "QByteArray"; isReadonly: true }
98+ Property { name: "busNameHint"; type: "QByteArray" }
99+ Property { name: "actionsHint"; type: "QByteArray" }
100+ Property { name: "menuObjectPathHint"; type: "QByteArray" }
101+ }
102+ Component {
103+ name: "UnitySortFilterProxyModelQML"
104+ prototype: "QSortFilterProxyModel"
105+ exports: ["Utils/UnitySortFilterProxyModel 0.1"]
106+ exportMetaObjectRevisions: [0]
107+ Property { name: "model"; type: "QAbstractItemModel"; isPointer: true }
108+ Property { name: "totalCount"; type: "int"; isReadonly: true }
109+ Property { name: "count"; type: "int"; isReadonly: true }
110+ Property { name: "invertMatch"; type: "bool" }
111+ Signal {
112+ name: "invertMatchChanged"
113+ Parameter { type: "bool" }
114+ }
115+ Method {
116+ name: "get"
117+ type: "QVariantMap"
118+ Parameter { name: "row"; type: "int" }
119+ }
120+ Method {
121+ name: "data"
122+ type: "QVariant"
123+ Parameter { name: "row"; type: "int" }
124+ Parameter { name: "role"; type: "int" }
125+ }
126+ Method { name: "count"; type: "int" }
127+ Method {
128+ name: "findFirst"
129+ type: "int"
130+ Parameter { name: "role"; type: "int" }
131+ Parameter { name: "value"; type: "QVariant" }
132+ }
133+ Method {
134+ name: "mapRowToSource"
135+ type: "int"
136+ Parameter { name: "row"; type: "int" }
137+ }
138+ Method {
139+ name: "mapFromSource"
140+ type: "int"
141+ Parameter { name: "row"; type: "int" }
142+ }
143+ Method {
144+ name: "mapToSource"
145+ type: "int"
146+ Parameter { name: "row"; type: "int" }
147+ }
148+ }
149+ Component {
150+ name: "WindowKeysFilter"
151+ defaultProperty: "data"
152+ prototype: "QQuickItem"
153+ exports: ["Utils/WindowKeysFilter 0.1"]
154+ exportMetaObjectRevisions: [0]
155+ }
156+ Component {
157+ name: "WindowStateStorage"
158+ prototype: "QObject"
159+ exports: ["Utils/WindowStateStorage 0.1"]
160+ isCreatable: false
161+ isSingleton: true
162+ exportMetaObjectRevisions: [0]
163+ Method {
164+ name: "saveGeometry"
165+ Parameter { name: "windowId"; type: "string" }
166+ Parameter { name: "rect"; type: "QRect" }
167+ }
168+ Method {
169+ name: "getGeometry"
170+ type: "QRect"
171+ Parameter { name: "windowId"; type: "string" }
172+ Parameter { name: "defaultValue"; type: "QRect" }
173+ }
174+ }
175+}
tests/mocks/Utils/constants.cppnew file+23
@@ -0,0 +1,23 @@
1+/*
2+ * Copyright 2015 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+#include "constants.h"
18+
19+Constants::Constants(QObject *parent)
20+ : QObject(parent)
21+{
22+ m_indicatorValueTimeout = 5000;
23+}
tests/mocks/Utils/constants.hnew file+43
@@ -0,0 +1,43 @@
1+/*
2+ * Copyright 2015 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+#ifndef CONSTANTS_H
18+#define CONSTANTS_H
19+
20+#include <QObject>
21+
22+/**
23+ * @brief The Constants class
24+ *
25+ * This singleton class exposes contants to Qml
26+ *
27+ */
28+
29+class Constants: public QObject
30+{
31+ Q_OBJECT
32+ Q_PROPERTY(int indicatorValueTimeout READ indicatorValueTimeout CONSTANT)
33+
34+public:
35+ Constants(QObject *parent = 0);
36+
37+ int indicatorValueTimeout() const { return m_indicatorValueTimeout; }
38+
39+private:
40+ int m_indicatorValueTimeout;
41+};
42+
43+#endif
tests/mocks/Utils/plugin.cppnew file+73
@@ -0,0 +1,73 @@
1+/*
2+ * Copyright (C) 2015 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+// Qt
18+#include <QtQml/qqml.h>
19+#include <QDBusConnection>
20+#include <QQmlContext>
21+#include <QtQuick/QQuickWindow>
22+#include <QDebug>
23+
24+// local
25+#include "plugin.h"
26+#include "windowstatestorage.h"
27+#include "constants.h"
28+
29+// plugin
30+#include <inputwatcher.h>
31+#include <qlimitproxymodelqml.h>
32+#include <unitysortfilterproxymodelqml.h>
33+#include <relativetimeformatter.h>
34+#include <timeformatter.h>
35+#include <unitymenumodelpaths.h>
36+#include <windowkeysfilter.h>
37+#include <easingcurve.h>
38+
39+static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
40+{
41+ Q_UNUSED(engine)
42+ Q_UNUSED(scriptEngine)
43+ return new WindowStateStorage();
44+}
45+
46+static QObject *createConstants(QQmlEngine *engine, QJSEngine *scriptEngine)
47+{
48+ Q_UNUSED(engine)
49+ Q_UNUSED(scriptEngine)
50+ return new Constants();
51+}
52+
53+void FakeUtilsPlugin::registerTypes(const char *uri)
54+{
55+ Q_ASSERT(uri == QLatin1String("Utils"));;
56+ qmlRegisterType<QAbstractItemModel>();
57+ qmlRegisterType<QLimitProxyModelQML>(uri, 0, 1, "LimitProxyModel");
58+ qmlRegisterType<UnitySortFilterProxyModelQML>(uri, 0, 1, "UnitySortFilterProxyModel");
59+ qmlRegisterType<UnityMenuModelPaths>(uri, 0, 1, "UnityMenuModelPaths");
60+ qmlRegisterType<TimeFormatter>(uri, 0, 1, "TimeFormatter");
61+ qmlRegisterType<WindowKeysFilter>(uri, 0, 1, "WindowKeysFilter");
62+ qmlRegisterType<GDateTimeFormatter>(uri, 0, 1, "GDateTimeFormatter");
63+ qmlRegisterType<EasingCurve>(uri, 0, 1, "EasingCurve");
64+ qmlRegisterType<RelativeTimeFormatter>(uri, 0, 1, "RelativeTimeFormatter");
65+ qmlRegisterSingletonType<WindowStateStorage>(uri, 0, 1, "WindowStateStorage", createWindowStateStorage);
66+ qmlRegisterType<InputWatcher>(uri, 0, 1, "InputWatcher");
67+ qmlRegisterSingletonType<Constants>(uri, 0, 1, "Constants", createConstants);
68+}
69+
70+void FakeUtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
71+{
72+ QQmlExtensionPlugin::initializeEngine(engine, uri);
73+}
tests/mocks/Utils/plugin.hnew file+33
@@ -0,0 +1,33 @@
1+/*
2+ * Copyright (C) 2015 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 FAKE_UTILS_PLUGIN_H
18+#define FAKE_UTILS_PLUGIN_H
19+
20+#include <QtQml/QQmlEngine>
21+#include <QtQml/QQmlExtensionPlugin>
22+
23+class FakeUtilsPlugin : public QQmlExtensionPlugin
24+{
25+ Q_OBJECT
26+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
27+
28+public:
29+ void registerTypes(const char *uri);
30+ void initializeEngine(QQmlEngine *engine, const char *uri);
31+};
32+
33+#endif
tests/mocks/Utils/qmldirnew file+5
@@ -0,0 +1,5 @@
1+module Utils
2+plugin FakeUtils-qml
3+typeinfo Utils.qmltypes
4+Style 0.1 Style.js
5+HomeKeyWatcher 0.1 HomeKeyWatcher.qml
tests/mocks/Utils/windowstatestorage.cppnew file+48
@@ -0,0 +1,48 @@
1+/*
2+ * Copyright 2015 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+#include "windowstatestorage.h"
18+
19+#include <QRect>
20+
21+WindowStateStorage::WindowStateStorage(QObject *parent):
22+ QObject(parent)
23+{
24+}
25+
26+void WindowStateStorage::setGeometry(const QVariantMap& geometry)
27+{
28+ if (geometry != m_geometry) {
29+ m_geometry = geometry;
30+ Q_EMIT geometryChanged(m_geometry);
31+ }
32+}
33+
34+QVariantMap WindowStateStorage::geometry() const
35+{
36+ return m_geometry;
37+}
38+
39+void WindowStateStorage::saveGeometry(const QString &windowId, const QRect &rect)
40+{
41+ m_geometry[windowId] = rect;
42+}
43+
44+QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue)
45+{
46+ if (!m_geometry.contains(windowId)) return defaultValue;
47+ return m_geometry.value(windowId).toRect();
48+}
tests/mocks/Utils/windowstatestorage.hnew file+38
@@ -0,0 +1,38 @@
1+/*
2+ * Copyright 2015 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+#include <QObject>
18+#include <QVariantMap>
19+
20+class WindowStateStorage: public QObject
21+{
22+ Q_OBJECT
23+ Q_PROPERTY(QVariantMap geometry READ geometry WRITE setGeometry NOTIFY geometryChanged)
24+public:
25+ WindowStateStorage(QObject *parent = 0);
26+
27+ Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect);
28+ Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue);
29+
30+Q_SIGNALS:
31+ void geometryChanged(const QVariantMap& geometry);
32+
33+private:
34+ void setGeometry(const QVariantMap& geometry);
35+ QVariantMap geometry() const;
36+
37+ QVariantMap m_geometry;
38+};
tests/qmltests/Stages/ApplicationCheckBox.qml+49−20
@@ -22,27 +22,67 @@ import Unity.Application 0.1
2222RowLayout {
2323 id: root
2424 property string appId
25− property alias checked: checkbox.checked
25+ property bool checked: false
2626
2727 enabled: appId !== "unity8-dash"
2828
29+ onCheckedChanged: {
30+ if (d.bindGuard) { return; }
31+ d.bindGuard = true;
32+
33+ if (checked) {
34+ ApplicationManager.startApplication(root.appId);
35+ } else {
36+ ApplicationManager.stopApplication(root.appId);
37+ }
38+ d.bindGuard = false;
39+ }
40+
41+ QtObject {
42+ id: d
43+ property bool bindGuard: false
44+ property var application: null
45+ Component.onCompleted: {
46+ application = ApplicationManager.findApplication(root.appId);
47+ }
48+ }
49+
50+ Connections {
51+ target: ApplicationManager
52+ onCountChanged: {
53+ d.application = ApplicationManager.findApplication(root.appId);
54+ }
55+ }
56+
2957 Layout.fillWidth: true
3058 CheckBox {
3159 id: checkbox
3260 checked: false
3361 activeFocusOnPress: false
34− property bool noop: false
35− onCheckedChanged: {
36− if (noop) {
37− return;
38− }
62+
63+ onTriggered: {
64+ if (d.bindGuard) { return; }
65+ d.bindGuard = true;
66+
3967 if (checked) {
40− var application = ApplicationManager.startApplication(root.appId);
41− appConnections.target = application;
68+ ApplicationManager.startApplication(root.appId);
4269 } else {
43− appConnections.target = null;
4470 ApplicationManager.stopApplication(root.appId);
4571 }
72+ d.bindGuard = false;
73+ }
74+ onCheckedChanged: {
75+ if (d.bindGuard) { return; }
76+ d.bindGuard = true;
77+
78+ root.checked = checked;
79+
80+ d.bindGuard = false;
81+ }
82+ Binding {
83+ target: checkbox
84+ property: "checked"
85+ value: d.application != null
4686 }
4787 }
4888 Label {
@@ -50,15 +90,4 @@ RowLayout {
5090 color: "white"
5191 anchors.verticalCenter: parent.verticalCenter
5292 }
53−
54− Connections {
55− id: appConnections
56− onStateChanged: {
57− if (target.state == ApplicationInfoInterface.Stopped) {
58− checkbox.noop = true;
59− checkbox.checked = false;
60− checkbox.noop = false;
61− }
62− }
63− }
6493}
tests/qmltests/Stages/tst_DesktopStage.qml+77−67
@@ -20,6 +20,7 @@ import Ubuntu.Components 1.1
2020import Ubuntu.Components.ListItems 1.0 as ListItem
2121import Unity.Application 0.1
2222import Unity.Test 0.1
23+import Utils 0.1
2324
2425import "../../../qml/Stages"
2526
@@ -29,31 +30,18 @@ Rectangle {
2930 width: desktopStageLoader.width + controls.width
3031 height: desktopStageLoader.height
3132
32− QtObject {
33− id: fakeWindowStateStorage
34−
35− property var storedGeom: [
36− ["unity8-dash", Qt.rect(units.gu(2), units.gu(2), units.gu(50), units.gu(50))],
37− ["webbrowser-app", Qt.rect(units.gu(60), units.gu(2), units.gu(50), units.gu(50))]
38− ]
33+ Binding {
34+ target: MouseTouchAdaptor
35+ property: "enabled"
36+ value: false
37+ }
3938
40− function saveGeometry(windowId, geometry) {
41− for (var i = 0; i < storedGeom.length; ++i) {
42− if (storedGeom[i][0] === windowId) {
43− storedGeom[i][1] = geometry;
44− return;
45− }
46− }
47− // if new
48− storedGeom[storedGeom.length] = [windowId, geometry];
49− }
50− function getGeometry(windowId, defaultGeometry) {
51− for (var i = 0; i < storedGeom.length; ++i) {
52− if (storedGeom[i][0] === windowId) {
53− return storedGeom[i][1];
54− }
55− }
56− return defaultGeometry;
39+ Component.onCompleted: {
40+ // ensures apps which are tested decorations are in view.
41+ WindowStateStorage.geometry = {
42+ 'unity8-dash': Qt.rect(0, units.gu(3), units.gu(50), units.gu(40)),
43+ 'dialer-app': Qt.rect(units.gu(51), units.gu(3), units.gu(50), units.gu(40)),
44+ 'camera-app': Qt.rect(0, units.gu(44), units.gu(50), units.gu(40)),
5745 }
5846 }
5947
@@ -70,7 +58,6 @@ Rectangle {
7058 sourceComponent: Component {
7159 DesktopStage {
7260 anchors.fill: parent
73− windowStateStorage: fakeWindowStateStorage
7461 Component.onDestruction: {
7562 desktopStageLoader.itemDestroyed = true;
7663 }
@@ -108,9 +95,6 @@ Rectangle {
10895
10996 property Item desktopStage: desktopStageLoader.status === Loader.Ready ? desktopStageLoader.item : null
11097
111− function init() {
112− }
113−
11498 function cleanup() {
11599 desktopStageLoader.itemDestroyed = false;
116100 desktopStageLoader.active = false;
@@ -145,57 +129,83 @@ Rectangle {
145129 tryCompare(appWindowStates, "state", "surface");
146130 }
147131
148− function rectsIntersect(aLocal, bLocal) {
149−
150− var a = aLocal.mapToItem(null, 0, 0, aLocal.width, aLocal.height);
151− var b = bLocal.mapToItem(null, 0, 0, bLocal.width, bLocal.height);
152−
153− return !((a.y+a.height) < b.y
154− || a.y > (b.y+b.height)
155− || (a.x+a.width) < b.x
156− || a.x > (b.x+b.width));
132+ function startApplication(appId) {
133+ var app = ApplicationManager.findApplication(appId);
134+ if (!app) {
135+ app = ApplicationManager.startApplication(appId);
136+ }
137+ verify(app);
138+ waitUntilAppSurfaceShowsUp(appId);
139+ verify(app.session.surface);
140+ return app;
157141 }
158142
159− function test_tappingOnWindowChangesFocusedApp() {
160− ApplicationManager.startApplication("webbrowser-app");
161− waitUntilAppSurfaceShowsUp("webbrowser-app");
143+ function test_appFocusSwitch_data() {
144+ return [
145+ {tag: "dash", apps: [ "unity8-dash", "dialer-app", "camera-app" ], focusfrom: 0, focusTo: 1 },
146+ {tag: "dash", apps: [ "unity8-dash", "dialer-app", "camera-app" ], focusfrom: 1, focusTo: 0 },
147+ ]
148+ }
162149
163− var webbrowserWindow = findChild(desktopStage, "appWindow_webbrowser-app");
164− verify(webbrowserWindow);
165− var dashWindow = findChild(desktopStage, "appWindow_unity8-dash");
166− verify(dashWindow);
150+ function test_appFocusSwitch(data) {
151+ var i;
152+ for (i = 0; i < data.apps.length; i++) {
153+ startApplication(data.apps[i]);
154+ }
167155
168− // some sanity check
169− compare(rectsIntersect(dashWindow, webbrowserWindow), false);
156+ ApplicationManager.requestFocusApplication(data.apps[data.focusfrom]);
157+ tryCompare(ApplicationManager.findApplication(data.apps[data.focusfrom]).session.surface, "activeFocus", true);
170158
171− tap(dashWindow);
172− compare(dashWindow.application.session.surface.activeFocus, true);
159+ ApplicationManager.requestFocusApplication(data.apps[data.focusTo]);
160+ tryCompare(ApplicationManager.findApplication(data.apps[data.focusTo]).session.surface, "activeFocus", true);
161+ }
173162
174− tap(webbrowserWindow);
175− compare(webbrowserWindow.application.session.surface.activeFocus, true);
163+ function test_tappingOnWindowChangesFocusedApp_data() {
164+ return [
165+ {tag: "dash", apps: [ "unity8-dash", "dialer-app", "camera-app" ], focusfrom: 0, focusTo: 1 },
166+ {tag: "dash", apps: [ "unity8-dash", "dialer-app", "camera-app" ], focusfrom: 1, focusTo: 0 },
167+ ]
176168 }
177169
178− function test_tappingOnWindowTitleChangesFocusedApp() {
179− ApplicationManager.startApplication("webbrowser-app");
180− waitUntilAppSurfaceShowsUp("webbrowser-app");
170+ function test_tappingOnWindowChangesFocusedApp(data) {
171+ var i;
172+ for (i = 0; i < data.apps.length; i++) {
173+ startApplication(data.apps[i]);
174+ }
175+
176+ var fromAppWindow = findChild(desktopStage, "appWindow_" + data.apps[data.focusfrom]);
177+ verify(fromAppWindow);
178+ tap(fromAppWindow);
179+ compare(fromAppWindow.application.session.surface.activeFocus, true);
181180
182− var webbrowserWindow = findChild(desktopStage, "decoratedWindow_webbrowser-app");
183− verify(webbrowserWindow);
184− var webbrowserWindowTitle = findChild(webbrowserWindow, "windowDecorationTitle");
185− verify(webbrowserWindowTitle);
186− var dashWindow = findChild(desktopStage, "decoratedWindow_unity8-dash");
187− verify(dashWindow);
188− var dashWindowTitle = findChild(dashWindow, "windowDecorationTitle");
189− verify(dashWindowTitle);
181+ var toAppWindow = findChild(desktopStage, "appWindow_" + data.apps[data.focusTo]);
182+ verify(toAppWindow);
183+ tap(toAppWindow);
184+ compare(toAppWindow.application.session.surface.activeFocus, true);
185+ }
190186
191− // some sanity check
192− compare(rectsIntersect(dashWindow, webbrowserWindow), false);
187+ function test_tappingOnDecorationFocusesApplication_data() {
188+ return [
189+ {tag: "dash", apps: [ "unity8-dash", "dialer-app", "camera-app" ], focusfrom: 0, focusTo: 1 },
190+ {tag: "dash", apps: [ "unity8-dash", "dialer-app", "camera-app" ], focusfrom: 1, focusTo: 0 },
191+ ]
192+ }
193+
194+ function test_tappingOnDecorationFocusesApplication(data) {
195+ var i;
196+ for (i = 0; i < data.apps.length; i++) {
197+ startApplication(data.apps[i]);
198+ }
193199
194− tap(dashWindowTitle);
195− compare(dashWindow.application.session.surface.activeFocus, true);
200+ var fromAppDecoration = findChild(desktopStage, "appWindowDecoration_" + data.apps[data.focusfrom]);
201+ verify(fromAppDecoration);
202+ tap(fromAppDecoration);
203+ tryCompare(ApplicationManager.findApplication(data.apps[data.focusfrom]).session.surface, "activeFocus", true);
196204
197− tap(webbrowserWindowTitle);
198− compare(webbrowserWindow.application.session.surface.activeFocus, true);
205+ var toAppDecoration = findChild(desktopStage, "appWindowDecoration_" + data.apps[data.focusTo]);
206+ verify(toAppDecoration);
207+ tap(toAppDecoration);
208+ tryCompare(ApplicationManager.findApplication(data.apps[data.focusTo]).session.surface, "activeFocus", true);
199209 }
200210 }
201211}
tests/qmltests/tst_Shell.qml+136−39
@@ -34,6 +34,7 @@ import Powerd 0.1
3434import Wizard 0.1 as Wizard
3535
3636import "../../qml"
37+import "Stages"
3738
3839Rectangle {
3940 id: root
@@ -109,19 +110,25 @@ Rectangle {
109110 }
110111 }
111112
112− Rectangle {
113+ Flickable {
113114 id: controls
114− color: "darkgrey"
115− width: units.gu(30)
115+ contentHeight: controlRect.height
116+
116117 anchors.top: root.top
117118 anchors.bottom: root.bottom
118119 anchors.right: root.right
120+ width: units.gu(30)
121+
122+ Rectangle {
123+ id: controlRect
124+ anchors { left: parent.left; right: parent.right }
125+ color: "darkgrey"
126+ height: childrenRect.height + units.gu(2)
127+
128+ Column {
129+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
130+ spacing: units.gu(1)
119131
120− Column {
121− anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
122− spacing: units.gu(1)
123− Row {
124− anchors { left: parent.left; right: parent.right }
125132 Button {
126133 text: "Show Greeter"
127134 activeFocusOnPress: false
@@ -135,30 +142,54 @@ Rectangle {
135142 }
136143 }
137144 }
138− }
139− ListItem.ItemSelector {
140− anchors { left: parent.left; right: parent.right }
141− activeFocusOnPress: false
142− text: "LightDM mock mode"
143− model: ["single", "single-passphrase", "single-pin", "full"]
144− onSelectedIndexChanged: {
145− shellLoader.active = false;
146− LightDM.Greeter.mockMode = model[selectedIndex];
147− LightDM.Users.mockMode = model[selectedIndex];
148− shellLoader.active = true;
145+ ListItem.ItemSelector {
146+ anchors { left: parent.left; right: parent.right }
147+ activeFocusOnPress: false
148+ text: "LightDM mock mode"
149+ model: ["single", "single-passphrase", "single-pin", "full"]
150+ onSelectedIndexChanged: {
151+ shellLoader.active = false;
152+ LightDM.Greeter.mockMode = model[selectedIndex];
153+ LightDM.Users.mockMode = model[selectedIndex];
154+ shellLoader.active = true;
155+ }
149156 }
150− }
151− ListItem.ItemSelector {
152− anchors { left: parent.left; right: parent.right }
153− activeFocusOnPress: false
154− text: "Size"
155− model: ["phone", "tablet"]
156− onSelectedIndexChanged: {
157− shellLoader.active = false;
158− shellLoader.state = model[selectedIndex];
159− shellLoader.active = true;
157+ ListItem.ItemSelector {
158+ anchors { left: parent.left; right: parent.right }
159+ activeFocusOnPress: false
160+ text: "Size"
161+ model: ["phone", "tablet"]
162+ onSelectedIndexChanged: {
163+ shellLoader.active = false;
164+ shellLoader.state = model[selectedIndex];
165+ shellLoader.active = true;
166+ }
167+ }
168+ ListItem.ItemSelector {
169+ id: usageModeSelector
170+ anchors { left: parent.left; right: parent.right }
171+ activeFocusOnPress: false
172+ text: "Usage mode"
173+ model: ["Staged", "Windowed"]
174+ onSelectedIndexChanged: {
175+ GSettingsController.setUsageMode(model[selectedIndex]);
176+ }
177+ }
178+ MouseTouchEmulationCheckbox {
179+ id: mouseEmulation
180+ checked: true
181+ color: "white"
182+ }
183+
184+ Label { text: "Applications"; font.bold: true }
185+
186+ Repeater {
187+ id: appRepeater
188+ model: ApplicationManager.availableApplications
189+ ApplicationCheckBox {
190+ appId: modelData
191+ }
160192 }
161− MouseTouchEmulationCheckbox { color: "white" }
162193 }
163194 }
164195 }
@@ -239,6 +270,7 @@ Rectangle {
239270 function cleanup() {
240271 tryCompare(shell, "enabled", true); // make sure greeter didn't leave us in disabled state
241272 tearDown();
273+ GSettingsController.setUsageMode("Staged");
242274 }
243275
244276 function loadShell(formFactor) {
@@ -685,22 +717,57 @@ Rectangle {
685717
686718 function test_launchedAppHasActiveFocus_data() {
687719 return [
688− {tag:"phone", formFactor:"phone"},
689− {tag:"tablet", formFactor:"tablet"},
690− ];
720+ {tag: "phone", formFactor: "phone", usageMode: "Staged"},
721+ {tag: "tablet", formFactor: "tablet", usageMode: "Staged"},
722+ {tag: "desktop", formFactor: "tablet", usageMode: "Windowed"}
723+ ]
691724 }
692725
693726 function test_launchedAppHasActiveFocus(data) {
727+ GSettingsController.setUsageMode(data.usageMode);
694728 loadShell(data.formFactor);
695729 swipeAwayGreeter();
696730
697− var dialerApp = ApplicationManager.startApplication("webbrowser-app");
698− verify(dialerApp);
731+ var webApp = ApplicationManager.startApplication("webbrowser-app");
732+ verify(webApp);
699733 waitUntilAppSurfaceShowsUp("webbrowser-app")
700734
701− verify(dialerApp.session.surface);
735+ verify(webApp.session.surface);
702736
703− tryCompare(dialerApp.session.surface, "activeFocus", true);
737+ tryCompare(webApp.session.surface, "activeFocus", true);
738+ }
739+
740+ function test_launchedAppKeepsActiveFocusOnUsageModeChange() {
741+ loadShell("tablet");
742+ swipeAwayGreeter();
743+
744+ var webApp = ApplicationManager.startApplication("webbrowser-app");
745+ verify(webApp);
746+ waitUntilAppSurfaceShowsUp("webbrowser-app")
747+
748+ verify(webApp.session.surface);
749+
750+ tryCompare(webApp.session.surface, "activeFocus", true);
751+
752+ GSettingsController.setUsageMode("Windowed");
753+
754+ // check that the desktop stage and window have been loaded
755+ {
756+ var desktopWindow = findChild(shell, "decoratedWindow_webbrowser-app");
757+ verify(desktopWindow);
758+ }
759+
760+ tryCompare(webApp.session.surface, "activeFocus", true);
761+
762+ GSettingsController.setUsageMode("Staged");
763+
764+ // check that the tablet stage and app surface delegate have been loaded
765+ {
766+ var desktopWindow = findChild(shell, "tabletSpreadDelegate_webbrowser-app");
767+ verify(desktopWindow);
768+ }
769+
770+ tryCompare(webApp.session.surface, "activeFocus", true);
704771 }
705772
706773 function waitUntilAppSurfaceShowsUp(appId) {
@@ -1130,8 +1197,7 @@ Rectangle {
11301197 loadShell("phone");
11311198 swipeAwayGreeter();
11321199 AccountsService.backgroundFile = data.accounts;
1133− var backgroundSettings = findInvisibleChild(shell, "backgroundSettings");
1134− backgroundSettings.pictureUri = data.gsettings;
1200+ GSettingsController.setPictureUri(data.gsettings);
11351201
11361202 if (data.output === "defaultBackground") {
11371203 tryCompare(shell, "background", shell.defaultBackground);
@@ -1188,5 +1254,36 @@ Rectangle {
11881254 tryCompare(passwordInput, "focus", true)
11891255 }
11901256 }
1257+
1258+ function test_stageLoader_data() {
1259+ return [
1260+ {tag: "phone", source: "Stages/PhoneStage.qml", formFactor: "phone", usageMode: "Staged"},
1261+ {tag: "tablet", source: "Stages/TabletStage.qml", formFactor: "tablet", usageMode: "Staged"},
1262+ {tag: "desktop", source: "Stages/DesktopStage.qml", formFactor: "tablet", usageMode: "Windowed"}
1263+ ]
1264+ }
1265+
1266+ function test_stageLoader(data) {
1267+ GSettingsController.setUsageMode(data.usageMode);
1268+ loadShell(data.formFactor);
1269+ var stageLoader = findChild(shell, "applicationsDisplayLoader");
1270+ verify(String(stageLoader.source).indexOf(data.source) >= 0);
1271+ }
1272+
1273+ function test_launcherInverted_data() {
1274+ return [
1275+ {tag: "phone", formFactor: "phone", usageMode: "Staged", launcherInverted: true},
1276+ {tag: "tablet", formFactor: "tablet", usageMode: "Staged", launcherInverted: true},
1277+ {tag: "desktop", formFactor: "tablet", usageMode: "Windowed", launcherInverted: false}
1278+ ]
1279+ }
1280+
1281+ function test_launcherInverted(data) {
1282+ GSettingsController.setUsageMode(data.usageMode);
1283+ loadShell(data.formFactor);
1284+
1285+ var launcher = findChild(shell, "launcher");
1286+ compare(launcher.inverted, data.launcherInverted);
1287+ }
11911288 }
11921289}
tests/utils/modules/Unity/Test/MouseTouchEmulationCheckbox.qml+1
@@ -22,6 +22,7 @@ import Unity.Test 0.1
2222RowLayout {
2323 id: root
2424 property alias color: label.color
25+ property alias checked: checkbox.checked
2526
2627 Binding {
2728 target: MouseTouchAdaptor