on xenial help sign in
Fixed desktop stage app focus.
dce2b42Nick Dedekind
qml/Shell.qml−1
@@ -109,7 +109,6 @@ Item {
109
109
110 GSettings {
110 GSettings {
111 id: backgroundSettings
111 id: backgroundSettings
112− objectName: "backgroundSettings"
113 schema.id: "org.gnome.desktop.background"
112 schema.id: "org.gnome.desktop.background"
114 }
113 }
115
114
qml/Stages/DecoratedWindow.qml+1
@@ -42,6 +42,7 @@ FocusScope {
42
42
43 WindowDecoration {
43 WindowDecoration {
44 id: decoration
44 id: decoration
45+ objectName: application ? "appWindowDecoration_" + application.appId : "appWindowDecoration_null"
45 anchors { left: parent.left; top: parent.top; right: parent.right }
46 anchors { left: parent.left; top: parent.top; right: parent.right }
46 height: units.gu(3)
47 height: units.gu(3)
47 title: model.name
48 title: model.name
qml/Stages/DesktopStage.qml+20−14
@@ -20,17 +20,14 @@ import QtQuick 2.3
20import Ubuntu.Components 1.1
20import Ubuntu.Components 1.1
21import Unity.Application 0.1
21import Unity.Application 0.1
22import "../Components/PanelState"
22import "../Components/PanelState"
23−import Utils 0.1
24
23
25−FocusScope {
24+Rectangle {
26 id: root
25 id: root
27
26
28 anchors.fill: parent
27 anchors.fill: parent
29
28
30 property alias background: wallpaper.source
29 property alias background: wallpaper.source
31
30
32− property var windowStateStorage: WindowStateStorage
33−
34 CrossFadeImage {
31 CrossFadeImage {
35 id: wallpaper
32 id: wallpaper
36 anchors.fill: parent
33 anchors.fill: parent
@@ -58,7 +55,16 @@ FocusScope {
58 id: priv
55 id: priv
59
56
60 readonly property string focusedAppId: ApplicationManager.focusedApplicationId
57 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+ }
62
68
63 function indexOf(appId) {
69 function indexOf(appId) {
64 for (var i = 0; i < ApplicationManager.count; i++) {
70 for (var i = 0; i < ApplicationManager.count; i++) {
@@ -89,13 +95,19 @@ FocusScope {
89 id: appRepeater
95 id: appRepeater
90 model: ApplicationManager
96 model: ApplicationManager
91
97
92− delegate: Item {
98+ delegate: FocusScope {
93 id: appDelegate
99 id: appDelegate
94 z: ApplicationManager.count - index
100 z: ApplicationManager.count - index
95 y: units.gu(3)
101 y: units.gu(3)
96 width: units.gu(60)
102 width: units.gu(60)
97 height: units.gu(50)
103 height: units.gu(50)
98
104
105+ onFocusChanged: {
106+ if (focus) {
107+ ApplicationManager.requestFocusApplication(model.appId);
108+ }
109+ }
110+
99 readonly property int minWidth: units.gu(10)
111 readonly property int minWidth: units.gu(10)
100 readonly property int minHeight: units.gu(10)
112 readonly property int minHeight: units.gu(10)
101
113
@@ -119,14 +131,13 @@ FocusScope {
119 ]
131 ]
120
132
121 WindowMoveResizeArea {
133 WindowMoveResizeArea {
122− windowStateStorage: root.windowStateStorage
123 target: appDelegate
134 target: appDelegate
124 minWidth: appDelegate.minWidth
135 minWidth: appDelegate.minWidth
125 minHeight: appDelegate.minHeight
136 minHeight: appDelegate.minHeight
126 resizeHandleWidth: units.gu(0.5)
137 resizeHandleWidth: units.gu(0.5)
127 windowId: model.appId // FIXME: Change this to point to windowId once we have such a thing
138 windowId: model.appId // FIXME: Change this to point to windowId once we have such a thing
128
139
129− onPressed: decoratedWindow.focus = true;
140+ onPressed: appDelegate.focus = true;
130 }
141 }
131
142
132 DecoratedWindow {
143 DecoratedWindow {
@@ -135,12 +146,7 @@ FocusScope {
135 anchors.fill: parent
146 anchors.fill: parent
136 application: ApplicationManager.get(index)
147 application: ApplicationManager.get(index)
137 active: ApplicationManager.focusedApplicationId === model.appId
148 active: ApplicationManager.focusedApplicationId === model.appId
138−
139− onFocusChanged: {
140− if (focus) {
149+ focus: true
141− ApplicationManager.requestFocusApplication(model.appId);
142− }
143− }
144
150
145 onClose: ApplicationManager.stopApplication(model.appId)
151 onClose: ApplicationManager.stopApplication(model.appId)
146 onMaximize: appDelegate.state = (appDelegate.state == "maximized" ? "normal" : "maximized")
152 onMaximize: appDelegate.state = (appDelegate.state == "maximized" ? "normal" : "maximized")
qml/Stages/PhoneStage.qml+4−4
@@ -117,14 +117,14 @@ Rectangle {
117
117
118 readonly property int firstSpreadIndex: root.focusFirstApp ? 1 : 0
118 readonly property int firstSpreadIndex: root.focusFirstApp ? 1 : 0
119 property string focusedAppId: applicationManager.focusedApplicationId
119 property string focusedAppId: applicationManager.focusedApplicationId
120− property var focusedApplication: applicationManager.findApplication(focusedAppId)
120+ readonly property var focusedAppDelegate: {
121− property var focusedAppDelegate: null
121+ var index = indexOf(focusedAppId);
122+ return index >= 0 && index < spreadRepeater.count ? spreadRepeater.itemAt(index) : null
123+ }
122
124
123 property real oldInverseProgress: 0
125 property real oldInverseProgress: 0
124 property bool animateX: false
126 property bool animateX: false
125
127
126− onFocusedAppIdChanged: focusedAppDelegate = spreadRepeater.itemAt(0);
127−
128 onFocusedAppDelegateChanged: {
128 onFocusedAppDelegateChanged: {
129 if (focusedAppDelegate) {
129 if (focusedAppDelegate) {
130 focusedAppDelegate.focus = true;
130 focusedAppDelegate.focus = true;
qml/Stages/TabletStage.qml+10−11
@@ -53,6 +53,11 @@ Rectangle {
53 id: priv
53 id: priv
54
54
55 property string focusedAppId: ApplicationManager.focusedApplicationId
55 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+
56 property string oldFocusedAppId: ""
61 property string oldFocusedAppId: ""
57
62
58 property string mainStageAppId
63 property string mainStageAppId
@@ -76,19 +81,11 @@ Rectangle {
76
81
77 appId0 = ApplicationManager.count >= 1 ? ApplicationManager.get(0).appId : "";
82 appId0 = ApplicationManager.count >= 1 ? ApplicationManager.get(0).appId : "";
78 appId1 = ApplicationManager.count > 1 ? ApplicationManager.get(1).appId : "";
83 appId1 = ApplicationManager.count > 1 ? ApplicationManager.get(1).appId : "";
79−
80− // Update the QML focus accordingly
81− updateSpreadDelegateFocus();
82 }
84 }
83
85
84− function updateSpreadDelegateFocus() {
86+ onFocusedAppDelegateChanged: {
85− if (priv.focusedAppId) {
87+ if (focusedAppDelegate) {
86− var focusedAppIndex = priv.indexOf(priv.focusedAppId);
88+ focusedAppDelegate.focus = true;
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− }
92 }
89 }
93 }
90 }
94
91
@@ -495,6 +492,8 @@ Rectangle {
495
492
496 delegate: TransformedTabletSpreadDelegate {
493 delegate: TransformedTabletSpreadDelegate {
497 id: spreadTile
494 id: spreadTile
495+ objectName: model.appId ? "tabletSpreadDelegate_" + model.appId
496+ : "tabletSpreadDelegate_null";
498 height: spreadView.height
497 height: spreadView.height
499 width: model.stage == ApplicationInfoInterface.MainStage ? spreadView.width : spreadView.sideStageWidth
498 width: model.stage == ApplicationInfoInterface.MainStage ? spreadView.width : spreadView.sideStageWidth
500 active: model.appId == priv.mainStageAppId || model.appId == priv.sideStageAppId
499 active: model.appId == priv.mainStageAppId || model.appId == priv.sideStageAppId
tests/mocks/CMakeLists.txt+1
@@ -41,6 +41,7 @@ add_subdirectory(Ubuntu)
41add_subdirectory(Unity)
41add_subdirectory(Unity)
42add_subdirectory(QtMultimedia)
42add_subdirectory(QtMultimedia)
43add_subdirectory(Wizard)
43add_subdirectory(Wizard)
44+add_subdirectory(Utils)
44
45
45install(
46install(
46 DIRECTORY data
47 DIRECTORY data
tests/mocks/GSettings.1.0/fake_gsettings.h+2−2
@@ -78,10 +78,10 @@ public:
78 ~GSettingsControllerQml();
78 ~GSettingsControllerQml();
79
79
80 QString pictureUri() const;
80 QString pictureUri() const;
81− void setPictureUri(const QString &str);
81+ Q_INVOKABLE void setPictureUri(const QString &str);
82
82
83 QString usageMode() const;
83 QString usageMode() const;
84− void setUsageMode(const QString &usageMode);
84+ Q_INVOKABLE void setUsageMode(const QString &usageMode);
85
85
86Q_SIGNALS:
86Q_SIGNALS:
87 void pictureUriChanged(const QString&);
87 void pictureUriChanged(const QString&);
tests/mocks/GSettings.1.0/plugin.cpp+6
@@ -19,8 +19,14 @@
19
19
20#include <QtQml/qqml.h>
20#include <QtQml/qqml.h>
21
21
22+static QObject* controllerProvider(QQmlEngine* /* engine */, QJSEngine* /* scriptEngine */)
23+{
24+ return GSettingsControllerQml::instance();
25+}
26+
22void FakeGSettingsQmlPlugin::registerTypes(const char *uri)
27void FakeGSettingsQmlPlugin::registerTypes(const char *uri)
23{
28{
29+ qmlRegisterSingletonType<GSettingsControllerQml>(uri, 1, 0, "GSettingsController", controllerProvider);
24 qmlRegisterType<GSettingsQml>(uri, 1, 0, "GSettings");
30 qmlRegisterType<GSettingsQml>(uri, 1, 0, "GSettings");
25 qmlRegisterUncreatableType<GSettingsSchemaQml>(uri, 1, 0, "GSettingsSchema",
31 qmlRegisterUncreatableType<GSettingsSchemaQml>(uri, 1, 0, "GSettingsSchema",
26 "GSettingsSchema can only be used inside of a GSettings component");
32 "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)
143}
143}
144
144
145void ApplicationManager::add(ApplicationInfo *application) {
145void ApplicationManager::add(ApplicationInfo *application) {
146− if (!application) {
146+ if (!application || m_runningApplications.contains(application)) {
147 return;
147 return;
148 }
148 }
149
149
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
22RowLayout {
22RowLayout {
23 id: root
23 id: root
24 property string appId
24 property string appId
25− property alias checked: checkbox.checked
25+ property bool checked: false
26
26
27 enabled: appId !== "unity8-dash"
27 enabled: appId !== "unity8-dash"
28
28
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+
29 Layout.fillWidth: true
57 Layout.fillWidth: true
30 CheckBox {
58 CheckBox {
31 id: checkbox
59 id: checkbox
32 checked: false
60 checked: false
33 activeFocusOnPress: false
61 activeFocusOnPress: false
34− property bool noop: false
62+
35− onCheckedChanged: {
63+ onTriggered: {
36− if (noop) {
37− return;
64+ if (d.bindGuard) { return; }
38− }
65+ d.bindGuard = true;
66+
39 if (checked) {
67 if (checked) {
40− var application = ApplicationManager.startApplication(root.appId);
68+ ApplicationManager.startApplication(root.appId);
41− appConnections.target = application;
42 } else {
69 } else {
43− appConnections.target = null;
44 ApplicationManager.stopApplication(root.appId);
70 ApplicationManager.stopApplication(root.appId);
45 }
71 }
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
46 }
86 }
47 }
87 }
48 Label {
88 Label {
@@ -50,15 +90,4 @@ RowLayout {
50 color: "white"
90 color: "white"
51 anchors.verticalCenter: parent.verticalCenter
91 anchors.verticalCenter: parent.verticalCenter
52 }
92 }
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− }
64}
93}
tests/qmltests/Stages/tst_DesktopStage.qml+77−67
@@ -20,6 +20,7 @@ import Ubuntu.Components 1.1
20import Ubuntu.Components.ListItems 1.0 as ListItem
20import Ubuntu.Components.ListItems 1.0 as ListItem
21import Unity.Application 0.1
21import Unity.Application 0.1
22import Unity.Test 0.1
22import Unity.Test 0.1
23+import Utils 0.1
23
24
24import "../../../qml/Stages"
25import "../../../qml/Stages"
25
26
@@ -29,31 +30,18 @@ Rectangle {
29 width: desktopStageLoader.width + controls.width
30 width: desktopStageLoader.width + controls.width
30 height: desktopStageLoader.height
31 height: desktopStageLoader.height
31
32
32− QtObject {
33+ Binding {
33− id: fakeWindowStateStorage
34+ target: MouseTouchAdaptor
34−
35− property var storedGeom: [
35+ property: "enabled"
36− ["unity8-dash", Qt.rect(units.gu(2), units.gu(2), units.gu(50), units.gu(50))],
36+ value: false
37− ["webbrowser-app", Qt.rect(units.gu(60), units.gu(2), units.gu(50), units.gu(50))]
37+ }
38− ]
39
38
40− function saveGeometry(windowId, geometry) {
39+ Component.onCompleted: {
41− for (var i = 0; i < storedGeom.length; ++i) {
40+ // ensures apps which are tested decorations are in view.
42− if (storedGeom[i][0] === windowId) {
41+ WindowStateStorage.geometry = {
43− storedGeom[i][1] = geometry;
42+ 'unity8-dash': Qt.rect(0, units.gu(3), units.gu(50), units.gu(40)),
44− return;
43+ 'dialer-app': Qt.rect(units.gu(51), units.gu(3), units.gu(50), units.gu(40)),
45− }
44+ 'camera-app': Qt.rect(0, units.gu(44), units.gu(50), units.gu(40)),
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;
57 }
45 }
58 }
46 }
59
47
@@ -70,7 +58,6 @@ Rectangle {
70 sourceComponent: Component {
58 sourceComponent: Component {
71 DesktopStage {
59 DesktopStage {
72 anchors.fill: parent
60 anchors.fill: parent
73− windowStateStorage: fakeWindowStateStorage
74 Component.onDestruction: {
61 Component.onDestruction: {
75 desktopStageLoader.itemDestroyed = true;
62 desktopStageLoader.itemDestroyed = true;
76 }
63 }
@@ -108,9 +95,6 @@ Rectangle {
108
95
109 property Item desktopStage: desktopStageLoader.status === Loader.Ready ? desktopStageLoader.item : null
96 property Item desktopStage: desktopStageLoader.status === Loader.Ready ? desktopStageLoader.item : null
110
97
111− function init() {
112− }
113−
114 function cleanup() {
98 function cleanup() {
115 desktopStageLoader.itemDestroyed = false;
99 desktopStageLoader.itemDestroyed = false;
116 desktopStageLoader.active = false;
100 desktopStageLoader.active = false;
@@ -145,57 +129,83 @@ Rectangle {
145 tryCompare(appWindowStates, "state", "surface");
129 tryCompare(appWindowStates, "state", "surface");
146 }
130 }
147
131
148− function rectsIntersect(aLocal, bLocal) {
132+ function startApplication(appId) {
149−
133+ var app = ApplicationManager.findApplication(appId);
150− var a = aLocal.mapToItem(null, 0, 0, aLocal.width, aLocal.height);
134+ if (!app) {
151− var b = bLocal.mapToItem(null, 0, 0, bLocal.width, bLocal.height);
135+ app = ApplicationManager.startApplication(appId);
152−
136+ }
137+ verify(app);
138+ waitUntilAppSurfaceShowsUp(appId);
139+ verify(app.session.surface);
153− return !((a.y+a.height) < b.y
140+ return app;
154− || a.y > (b.y+b.height)
155− || (a.x+a.width) < b.x
156− || a.x > (b.x+b.width));
157 }
141 }
158
142
159− function test_tappingOnWindowChangesFocusedApp() {
143+ function test_appFocusSwitch_data() {
160− ApplicationManager.startApplication("webbrowser-app");
144+ return [
161− waitUntilAppSurfaceShowsUp("webbrowser-app");
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+ }
162
149
163− var webbrowserWindow = findChild(desktopStage, "appWindow_webbrowser-app");
150+ function test_appFocusSwitch(data) {
164− verify(webbrowserWindow);
151+ var i;
165− var dashWindow = findChild(desktopStage, "appWindow_unity8-dash");
152+ for (i = 0; i < data.apps.length; i++) {
166− verify(dashWindow);
153+ startApplication(data.apps[i]);
154+ }
167
155
168− // some sanity check
156+ ApplicationManager.requestFocusApplication(data.apps[data.focusfrom]);
169− compare(rectsIntersect(dashWindow, webbrowserWindow), false);
157+ tryCompare(ApplicationManager.findApplication(data.apps[data.focusfrom]).session.surface, "activeFocus", true);
170
158
171− tap(dashWindow);
159+ ApplicationManager.requestFocusApplication(data.apps[data.focusTo]);
172− compare(dashWindow.application.session.surface.activeFocus, true);
160+ tryCompare(ApplicationManager.findApplication(data.apps[data.focusTo]).session.surface, "activeFocus", true);
161+ }
173
162
174− tap(webbrowserWindow);
163+ function test_tappingOnWindowChangesFocusedApp_data() {
175− compare(webbrowserWindow.application.session.surface.activeFocus, true);
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+ ]
176 }
168 }
177
169
178− function test_tappingOnWindowTitleChangesFocusedApp() {
170+ function test_tappingOnWindowChangesFocusedApp(data) {
171+ var i;
172+ for (i = 0; i < data.apps.length; i++) {
179− ApplicationManager.startApplication("webbrowser-app");
173+ startApplication(data.apps[i]);
180− waitUntilAppSurfaceShowsUp("webbrowser-app");
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);
181
180
182− var webbrowserWindow = findChild(desktopStage, "decoratedWindow_webbrowser-app");
181+ var toAppWindow = findChild(desktopStage, "appWindow_" + data.apps[data.focusTo]);
183− verify(webbrowserWindow);
182+ verify(toAppWindow);
184− var webbrowserWindowTitle = findChild(webbrowserWindow, "windowDecorationTitle");
183+ tap(toAppWindow);
185− verify(webbrowserWindowTitle);
184+ compare(toAppWindow.application.session.surface.activeFocus, true);
186− var dashWindow = findChild(desktopStage, "decoratedWindow_unity8-dash");
185+ }
187− verify(dashWindow);
188− var dashWindowTitle = findChild(dashWindow, "windowDecorationTitle");
189− verify(dashWindowTitle);
190
186
191− // some sanity check
187+ function test_tappingOnDecorationFocusesApplication_data() {
192− compare(rectsIntersect(dashWindow, webbrowserWindow), false);
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+ }
193
199
194− tap(dashWindowTitle);
200+ var fromAppDecoration = findChild(desktopStage, "appWindowDecoration_" + data.apps[data.focusfrom]);
195− compare(dashWindow.application.session.surface.activeFocus, true);
201+ verify(fromAppDecoration);
202+ tap(fromAppDecoration);
203+ tryCompare(ApplicationManager.findApplication(data.apps[data.focusfrom]).session.surface, "activeFocus", true);
196
204
197− tap(webbrowserWindowTitle);
205+ var toAppDecoration = findChild(desktopStage, "appWindowDecoration_" + data.apps[data.focusTo]);
198− compare(webbrowserWindow.application.session.surface.activeFocus, true);
206+ verify(toAppDecoration);
207+ tap(toAppDecoration);
208+ tryCompare(ApplicationManager.findApplication(data.apps[data.focusTo]).session.surface, "activeFocus", true);
199 }
209 }
200 }
210 }
201}
211}
tests/qmltests/tst_Shell.qml+136−39
@@ -34,6 +34,7 @@ import Powerd 0.1
34import Wizard 0.1 as Wizard
34import Wizard 0.1 as Wizard
35
35
36import "../../qml"
36import "../../qml"
37+import "Stages"
37
38
38Rectangle {
39Rectangle {
39 id: root
40 id: root
@@ -109,19 +110,25 @@ Rectangle {
109 }
110 }
110 }
111 }
111
112
112− Rectangle {
113+ Flickable {
113 id: controls
114 id: controls
114− color: "darkgrey"
115+ contentHeight: controlRect.height
115− width: units.gu(30)
116+
116 anchors.top: root.top
117 anchors.top: root.top
117 anchors.bottom: root.bottom
118 anchors.bottom: root.bottom
118 anchors.right: root.right
119 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)
119
131
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 }
125 Button {
132 Button {
126 text: "Show Greeter"
133 text: "Show Greeter"
127 activeFocusOnPress: false
134 activeFocusOnPress: false
@@ -135,30 +142,54 @@ Rectangle {
135 }
142 }
136 }
143 }
137 }
144 }
138− }
139− ListItem.ItemSelector {
145+ ListItem.ItemSelector {
140− anchors { left: parent.left; right: parent.right }
146+ anchors { left: parent.left; right: parent.right }
141− activeFocusOnPress: false
147+ activeFocusOnPress: false
142− text: "LightDM mock mode"
148+ text: "LightDM mock mode"
143− model: ["single", "single-passphrase", "single-pin", "full"]
149+ model: ["single", "single-passphrase", "single-pin", "full"]
144− onSelectedIndexChanged: {
150+ onSelectedIndexChanged: {
145− shellLoader.active = false;
151+ shellLoader.active = false;
146− LightDM.Greeter.mockMode = model[selectedIndex];
152+ LightDM.Greeter.mockMode = model[selectedIndex];
147− LightDM.Users.mockMode = model[selectedIndex];
153+ LightDM.Users.mockMode = model[selectedIndex];
148− shellLoader.active = true;
154+ shellLoader.active = true;
155+ }
149 }
156 }
150− }
151− ListItem.ItemSelector {
157+ ListItem.ItemSelector {
152− anchors { left: parent.left; right: parent.right }
158+ anchors { left: parent.left; right: parent.right }
153− activeFocusOnPress: false
159+ activeFocusOnPress: false
154− text: "Size"
160+ text: "Size"
155− model: ["phone", "tablet"]
161+ model: ["phone", "tablet"]
156− onSelectedIndexChanged: {
162+ onSelectedIndexChanged: {
157− shellLoader.active = false;
163+ shellLoader.active = false;
158− shellLoader.state = model[selectedIndex];
164+ shellLoader.state = model[selectedIndex];
159− shellLoader.active = true;
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+ }
160 }
192 }
161− MouseTouchEmulationCheckbox { color: "white" }
162 }
193 }
163 }
194 }
164 }
195 }
@@ -239,6 +270,7 @@ Rectangle {
239 function cleanup() {
270 function cleanup() {
240 tryCompare(shell, "enabled", true); // make sure greeter didn't leave us in disabled state
271 tryCompare(shell, "enabled", true); // make sure greeter didn't leave us in disabled state
241 tearDown();
272 tearDown();
273+ GSettingsController.setUsageMode("Staged");
242 }
274 }
243
275
244 function loadShell(formFactor) {
276 function loadShell(formFactor) {
@@ -685,22 +717,57 @@ Rectangle {
685
717
686 function test_launchedAppHasActiveFocus_data() {
718 function test_launchedAppHasActiveFocus_data() {
687 return [
719 return [
688− {tag:"phone", formFactor:"phone"},
720+ {tag: "phone", formFactor: "phone", usageMode: "Staged"},
689− {tag:"tablet", formFactor:"tablet"},
721+ {tag: "tablet", formFactor: "tablet", usageMode: "Staged"},
722+ {tag: "desktop", formFactor: "tablet", usageMode: "Windowed"}
690− ];
723+ ]
691 }
724 }
692
725
693 function test_launchedAppHasActiveFocus(data) {
726 function test_launchedAppHasActiveFocus(data) {
727+ GSettingsController.setUsageMode(data.usageMode);
694 loadShell(data.formFactor);
728 loadShell(data.formFactor);
695 swipeAwayGreeter();
729 swipeAwayGreeter();
696
730
697− var dialerApp = ApplicationManager.startApplication("webbrowser-app");
731+ var webApp = ApplicationManager.startApplication("webbrowser-app");
698− verify(dialerApp);
732+ verify(webApp);
699 waitUntilAppSurfaceShowsUp("webbrowser-app")
733 waitUntilAppSurfaceShowsUp("webbrowser-app")
700
734
701− verify(dialerApp.session.surface);
735+ verify(webApp.session.surface);
702
736
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+
703− tryCompare(dialerApp.session.surface, "activeFocus", true);
770+ tryCompare(webApp.session.surface, "activeFocus", true);
704 }
771 }
705
772
706 function waitUntilAppSurfaceShowsUp(appId) {
773 function waitUntilAppSurfaceShowsUp(appId) {
@@ -1130,8 +1197,7 @@ Rectangle {
1130 loadShell("phone");
1197 loadShell("phone");
1131 swipeAwayGreeter();
1198 swipeAwayGreeter();
1132 AccountsService.backgroundFile = data.accounts;
1199 AccountsService.backgroundFile = data.accounts;
1133− var backgroundSettings = findInvisibleChild(shell, "backgroundSettings");
1200+ GSettingsController.setPictureUri(data.gsettings);
1134− backgroundSettings.pictureUri = data.gsettings;
1135
1201
1136 if (data.output === "defaultBackground") {
1202 if (data.output === "defaultBackground") {
1137 tryCompare(shell, "background", shell.defaultBackground);
1203 tryCompare(shell, "background", shell.defaultBackground);
@@ -1188,5 +1254,36 @@ Rectangle {
1188 tryCompare(passwordInput, "focus", true)
1254 tryCompare(passwordInput, "focus", true)
1189 }
1255 }
1190 }
1256 }
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+ }
1191 }
1288 }
1192}
1289}
tests/utils/modules/Unity/Test/MouseTouchEmulationCheckbox.qml+1
@@ -22,6 +22,7 @@ import Unity.Test 0.1
22RowLayout {
22RowLayout {
23 id: root
23 id: root
24 property alias color: label.color
24 property alias color: label.color
25+ property alias checked: checkbox.checked
25
26
26 Binding {
27 Binding {
27 target: MouseTouchAdaptor
28 target: MouseTouchAdaptor