on xenial help sign in
Fix for desktop app focus
commit3ecd12d0ee030720ad390bffcb3f724f9c0d1f4b authorNick Dedekind <nick.dedekind@canonical.com> dateWed 4 Mar 2015, 13:04 (12 years ago) parent1ac1b12
15 files changed+811−3
qml/Shell.qml+4−2 qml/Stages/DecoratedWindow.qml+3 qml/Stages/DesktopStage.qml+6 tests/mocks/CMakeLists.txt+1 tests/mocks/Unity/Application/ApplicationManager.cpp+1−1 tests/mocks/Utils/CMakeLists.txt+37 tests/mocks/Utils/Style.js+37 tests/mocks/Utils/Utils.qmltypes+175 tests/mocks/Utils/plugin.cpp+61 tests/mocks/Utils/plugin.h+33 tests/mocks/Utils/qmldir+4 tests/mocks/Utils/windowstatestorage.cpp+48 tests/mocks/Utils/windowstatestorage.h+38 tests/qmltests/CMakeLists.txt+1 tests/qmltests/tst_DesktopShell.qml+362
qml/Shell.qml+4−2
@@ -78,6 +78,8 @@ Item {
7878 }
7979 }
8080
81+ property string usageMode: usageModeSettings.usageMode
82+
8183 function activateApplication(appId) {
8284 if (ApplicationManager.findApplication(appId)) {
8385 ApplicationManager.requestFocusApplication(appId);
@@ -229,7 +231,7 @@ Item {
229231 // the screen larger (maybe connects to monitor) and tries to enter
230232 // tablet mode.
231233 property bool tabletMode: shell.sideStageEnabled && !greeter.hasLockedApp
232− source: usageModeSettings.usageMode === "Windowed" ? "Stages/DesktopStage.qml"
234+ source: shell.usageMode === "Windowed" ? "Stages/DesktopStage.qml"
233235 : tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
234236
235237 property bool interactive: tutorial.spreadEnabled
@@ -483,7 +485,7 @@ Item {
483485 available: tutorial.launcherEnabled
484486 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
485487 && !greeter.hasLockedApp
486− inverted: usageModeSettings.usageMode === "Staged"
488+ inverted: shell.usageMode === "Staged"
487489 shadeBackground: !tutorial.running
488490
489491 onShowDashHome: showHome()
qml/Stages/DecoratedWindow.qml+3
@@ -23,6 +23,7 @@ import Unity.Application 0.1
2323Item {
2424 id: root
2525
26+ property alias window: applicationWindow
2627 property alias application: applicationWindow.application
2728 property alias active: decoration.active
2829
@@ -43,6 +44,7 @@ Item {
4344
4445 WindowDecoration {
4546 id: decoration
47+ objectName: application ? "appWindowDecoration_" + application.appId : "appWindowDecoration_null"
4648 anchors { left: parent.left; top: parent.top; right: parent.right }
4749 height: units.gu(3)
4850 title: model.name
@@ -53,6 +55,7 @@ Item {
5355
5456 ApplicationWindow {
5557 id: applicationWindow
58+ objectName: application ? "appWindow_" + application.appId : "appWindow_null"
5659 anchors.fill: parent
5760 anchors.topMargin: units.gu(3)
5861 interactive: index == 0
qml/Stages/DesktopStage.qml+6
@@ -47,6 +47,7 @@ Item {
4747 if (appDelegate.state === "minimized") {
4848 appDelegate.state = "normal"
4949 }
50+ appDelegate.focusWindow();
5051 ApplicationManager.focusApplication(appId);
5152 }
5253 }
@@ -96,6 +97,10 @@ Item {
9697 readonly property int minWidth: units.gu(10)
9798 readonly property int minHeight: units.gu(10)
9899
100+ function focusWindow() {
101+ decoratedWindow.window.forceActiveFocus();
102+ }
103+
99104 states: [
100105 State {
101106 name: "normal"
@@ -126,6 +131,7 @@ Item {
126131 }
127132
128133 DecoratedWindow {
134+ id: decoratedWindow
129135 anchors.fill: parent
130136 application: ApplicationManager.get(index)
131137 active: ApplicationManager.focusedApplicationId === model.appId
tests/mocks/CMakeLists.txt+1
@@ -40,6 +40,7 @@ add_subdirectory(Ubuntu)
4040add_subdirectory(Unity)
4141add_subdirectory(QtMultimedia)
4242add_subdirectory(Wizard)
43+add_subdirectory(Utils)
4344
4445install(
4546 DIRECTORY data
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+37
@@ -0,0 +1,37 @@
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/qlimitproxymodelqml.cpp
13+ ${CMAKE_SOURCE_DIR}/plugins/Utils/unitysortfilterproxymodelqml.cpp
14+ ${CMAKE_SOURCE_DIR}/plugins/Utils/relativetimeformatter.cpp
15+ ${CMAKE_SOURCE_DIR}/plugins/Utils/timeformatter.cpp
16+ ${CMAKE_SOURCE_DIR}/plugins/Utils/unitymenumodelpaths.cpp
17+ ${CMAKE_SOURCE_DIR}/plugins/Utils/windowkeysfilter.cpp
18+ ${CMAKE_SOURCE_DIR}/plugins/Utils/easingcurve.cpp
19+ windowstatestorage.cpp
20+ plugin.cpp
21+ )
22+
23+add_library(FakeUtils-qml SHARED
24+ ${QMLPLUGIN_SRC}
25+ )
26+
27+target_link_libraries(FakeUtils-qml ${GIO_LDFLAGS})
28+
29+# Because this is an internal support library, we want
30+# to expose all symbols in it. Consider changing this
31+# either to a static library or just using the
32+# files directly in targets.
33+set_target_properties(FakeUtils-qml PROPERTIES COMPILE_FLAGS -fvisibility=default)
34+
35+qt5_use_modules(FakeUtils-qml Qml Quick DBus Network Gui)
36+
37+add_unity8_mock(Utils 0.1 Utils TARGETS FakeUtils-qml)
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/plugin.cppnew file+61
@@ -0,0 +1,61 @@
1+/*
2+ * Copyright (C) 2012 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+// self
24+#include "plugin.h"
25+
26+// local
27+#include "easingcurve.h"
28+#include "qlimitproxymodelqml.h"
29+#include "unitysortfilterproxymodelqml.h"
30+#include "relativetimeformatter.h"
31+#include "timeformatter.h"
32+#include "unitymenumodelpaths.h"
33+#include "windowkeysfilter.h"
34+#include "windowstatestorage.h"
35+
36+static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
37+{
38+ Q_UNUSED(engine)
39+ Q_UNUSED(scriptEngine)
40+ return new WindowStateStorage();
41+}
42+
43+void FakeUtilsPlugin::registerTypes(const char *uri)
44+{
45+ Q_ASSERT(uri == QLatin1String("Utils"));
46+ qmlRegisterType<QAbstractItemModel>();
47+ qmlRegisterType<QLimitProxyModelQML>(uri, 0, 1, "LimitProxyModel");
48+ qmlRegisterType<UnitySortFilterProxyModelQML>(uri, 0, 1, "UnitySortFilterProxyModel");
49+ qmlRegisterType<UnityMenuModelPaths>(uri, 0, 1, "UnityMenuModelPaths");
50+ qmlRegisterType<TimeFormatter>(uri, 0, 1, "TimeFormatter");
51+ qmlRegisterType<WindowKeysFilter>(uri, 0, 1, "WindowKeysFilter");
52+ qmlRegisterType<GDateTimeFormatter>(uri, 0, 1, "GDateTimeFormatter");
53+ qmlRegisterType<EasingCurve>(uri, 0, 1, "EasingCurve");
54+ qmlRegisterType<RelativeTimeFormatter>(uri, 0, 1, "RelativeTimeFormatter");
55+ qmlRegisterSingletonType<WindowStateStorage>(uri, 0, 1, "WindowStateStorage", createWindowStateStorage);
56+}
57+
58+void FakeUtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
59+{
60+ QQmlExtensionPlugin::initializeEngine(engine, uri);
61+}
tests/mocks/Utils/plugin.hnew file+33
@@ -0,0 +1,33 @@
1+/*
2+ * Copyright (C) 2012 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+4
@@ -0,0 +1,4 @@
1+module Utils
2+plugin FakeUtils-qml
3+typeinfo Utils.qmltypes
4+Style 0.1 Style.js
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/CMakeLists.txt+1
@@ -22,6 +22,7 @@ set(qmltest_DEFAULT_PROPERTIES ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/
2222add_qml_test(. Shell)
2323add_qml_test(. ShellWithPin)
2424add_qml_test(. TabletShell)
25+add_qml_test(. DesktopShell)
2526add_qml_test(Components Background)
2627add_qml_test(Components Carousel)
2728add_qml_test(Components Dialogs)
tests/qmltests/tst_DesktopShell.qmlnew file+362
@@ -0,0 +1,362 @@
1+/*
2+ * Copyright (C) 2013,2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import QtTest 1.0
19+import AccountsService 0.1
20+import GSettings 1.0
21+import LightDM 0.1 as LightDM
22+import Ubuntu.Components 1.1
23+import Ubuntu.Telephony 0.1 as Telephony
24+import Unity.Application 0.1
25+import Unity.Connectivity 0.1
26+import Unity.Test 0.1
27+import Powerd 0.1
28+import Utils 0.1
29+
30+import "../../qml"
31+
32+Item {
33+ id: root
34+ width: units.gu(140)
35+ height: units.gu(90)
36+
37+ Binding {
38+ target: MouseTouchAdaptor
39+ property: "enabled"
40+ value: false
41+ }
42+
43+ Component.onCompleted: {
44+ // must set the mock mode before loading the Shell
45+ LightDM.Greeter.mockMode = "full";
46+ LightDM.Users.mockMode = "full";
47+
48+ // ensures apps which are tested decorations are in view.
49+ WindowStateStorage.geometry = {
50+ 'unity8-dash': Qt.rect(0, units.gu(3), units.gu(50), units.gu(40)),
51+ 'dialer-app': Qt.rect(units.gu(51), units.gu(3), units.gu(50), units.gu(40)),
52+ 'camera-app': Qt.rect(0, units.gu(44), units.gu(50), units.gu(40)),
53+ }
54+
55+ shellLoader.active = true;
56+ }
57+
58+ QtObject {
59+ id: applicationArguments
60+
61+ function hasGeometry() {
62+ return false
63+ }
64+
65+ function width() {
66+ return 0
67+ }
68+
69+ function height() {
70+ return 0
71+ }
72+ }
73+
74+ Row {
75+ anchors.fill: parent
76+ Loader {
77+ id: shellLoader
78+
79+ active: false
80+ width: units.gu(110)
81+ height: parent.height
82+
83+ property bool itemDestroyed: false
84+ sourceComponent: Component {
85+ Shell {
86+ usageMode: "Windowed"
87+ property string indicatorProfile: "desktop"
88+
89+ Component.onDestruction: {
90+ shellLoader.itemDestroyed = true
91+ }
92+ }
93+ }
94+ }
95+
96+ Rectangle {
97+ color: "white"
98+ width: units.gu(30)
99+ height: parent.height
100+
101+ Column {
102+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
103+ spacing: units.gu(1)
104+ Button {
105+ text: "Show Greeter"
106+ onClicked: {
107+ if (shellLoader.status !== Loader.Ready)
108+ return
109+
110+ var greeter = testCase.findChild(shellLoader.item, "greeter")
111+ if (!greeter.shown) {
112+ greeter.show()
113+ }
114+ }
115+ }
116+
117+ Repeater {
118+ id: apps
119+ model: ApplicationManager.availableApplications()
120+ anchors {
121+ left: parent.left
122+ right: parent.right
123+ }
124+ height: childrenRect.height
125+
126+ Row {
127+ id: appColumn
128+ enabled: modelData != "unity8-dash"
129+ property var application: null
130+
131+ Component.onCompleted: {
132+ application = ApplicationManager.findApplication(modelData);
133+ }
134+
135+ Connections {
136+ target: ApplicationManager
137+ onCountChanged: {
138+ appColumn.application = ApplicationManager.findApplication(modelData);
139+ }
140+ }
141+
142+ CheckBox {
143+ id: appCheckBox
144+ onTriggered: {
145+ if (checked) ApplicationManager.startApplication(modelData);
146+ else ApplicationManager.stopApplication(modelData);
147+ }
148+ Binding {
149+ target: appCheckBox
150+ property: "checked"
151+ value: appColumn.application != null
152+ }
153+ }
154+
155+ Label {
156+ text: appColumn.application ? appColumn.application.name : modelData
157+ color: "black"
158+ }
159+ }
160+ }
161+ }
162+ }
163+ }
164+
165+ SignalSpy {
166+ id: sessionSpy
167+ signalName: "sessionStarted"
168+ }
169+
170+ SignalSpy {
171+ id: dashCommunicatorSpy
172+ signalName: "setCurrentScopeCalled"
173+ }
174+
175+ SignalSpy {
176+ id: unlockAllModemsSpy
177+ target: Connectivity
178+ signalName: "unlockingAllModems"
179+ }
180+
181+ UnityTestCase {
182+ id: testCase
183+ name: "DesktopShell"
184+ when: windowShown
185+
186+ property Item shell: shellLoader.status === Loader.Ready ? shellLoader.item : null
187+
188+ function init() {
189+ tryCompare(shell, "enabled", true); // will be enabled when greeter is all ready
190+ var userList = findChild(shell, "userList");
191+ tryCompare(userList, "movingInternally", false);
192+ sessionSpy.clear()
193+ sessionSpy.target = findChild(shell, "greeter")
194+ dashCommunicatorSpy.target = findInvisibleChild(shell, "dashCommunicator")
195+ }
196+
197+ function cleanup() {
198+ tryCompare(shell, "enabled", true); // make sure greeter didn't leave us in disabled state
199+
200+ shellLoader.itemDestroyed = false
201+
202+ shellLoader.active = false
203+
204+ tryCompare(shellLoader, "status", Loader.Null)
205+ tryCompare(shellLoader, "item", null)
206+ // Loader.status might be Loader.Null and Loader.item might be null but the Loader
207+ // item might still be alive. So if we set Loader.active back to true
208+ // again right now we will get the very same Shell instance back. So no reload
209+ // actually took place. Likely because Loader waits until the next event loop
210+ // iteration to do its work. So to ensure the reload, we will wait until the
211+ // Shell instance gets destroyed.
212+ tryCompare(shellLoader, "itemDestroyed", true)
213+
214+ // kill all (fake) running apps
215+ killApps()
216+
217+ unlockAllModemsSpy.clear()
218+ AccountsService.demoEdges = false
219+ LightDM.Greeter.authenticate(""); // reset greeter
220+
221+ // reload our test subject to get it in a fresh state once again
222+ shellLoader.active = true
223+
224+ tryCompare(shellLoader, "status", Loader.Ready)
225+ removeTimeConstraintsFromDirectionalDragAreas(shellLoader.item)
226+ }
227+
228+ function killApps() {
229+ while (ApplicationManager.count > 1) {
230+ var appIndex = ApplicationManager.get(0).appId == "unity8-dash" ? 1 : 0
231+ ApplicationManager.stopApplication(ApplicationManager.get(appIndex).appId)
232+ }
233+ compare(ApplicationManager.count, 1)
234+ }
235+
236+ function selectIndex(i) {
237+ // We could be anywhere in list; find target index to know which direction
238+ var greeter = findChild(shell, "greeter")
239+ var userlist = findChild(greeter, "userList")
240+ if (userlist.currentIndex == i)
241+ keyClick(Qt.Key_Escape) // Reset state if we're not moving
242+ while (userlist.currentIndex != i) {
243+ var next = userlist.currentIndex + 1
244+ if (userlist.currentIndex > i) {
245+ next = userlist.currentIndex - 1
246+ }
247+ tap(findChild(greeter, "username"+next));
248+ tryCompare(userlist, "currentIndex", next)
249+ tryCompare(userlist, "movingInternally", false)
250+ }
251+ }
252+
253+ function selectUser(name) {
254+ // Find index of user with the right name
255+ for (var i = 0; i < LightDM.Users.count; i++) {
256+ if (LightDM.Users.data(i, LightDM.UserRoles.NameRole) == name) {
257+ break
258+ }
259+ }
260+ if (i == LightDM.Users.count) {
261+ fail("Didn't find name")
262+ return -1
263+ }
264+ selectIndex(i)
265+ return i
266+ }
267+
268+ function clickPasswordInput(isButton) {
269+ var greeter = findChild(shell, "greeter")
270+ tryCompare(greeter, "fullyShown", true);
271+
272+ var passwordMouseArea = findChild(shell, "passwordMouseArea")
273+ tryCompare(passwordMouseArea, "enabled", isButton)
274+
275+ var passwordInput = findChild(shell, "passwordInput")
276+ mouseClick(passwordInput)
277+ }
278+
279+ function confirmLoggedIn(loggedIn) {
280+ var greeter = findChild(shell, "greeter");
281+ tryCompare(greeter, "shown", loggedIn ? false : true);
282+ verify(loggedIn ? sessionSpy.count > 0 : sessionSpy.count === 0);
283+ }
284+
285+ function swipeFromLeftEdge(swipeLength) {
286+ var touchStartX = 2
287+ var touchStartY = shell.height / 2
288+ touchFlick(shell, touchStartX, touchStartY, swipeLength, touchStartY)
289+ }
290+
291+ function waitUntilAppSurfaceShowsUp(appId) {
292+ var appWindow = findChild(shell, "appWindow_" + appId);
293+ verify(appWindow);
294+ var appWindowStates = findInvisibleChild(appWindow, "applicationWindowStateGroup");
295+ verify(appWindowStates);
296+ tryCompare(appWindowStates, "state", "surface");
297+ }
298+
299+ function startApplication(appId) {
300+ var app = ApplicationManager.findApplication(appId);
301+ if (!app) {
302+ app = ApplicationManager.startApplication(appId);
303+ }
304+ verify(app);
305+ waitUntilAppSurfaceShowsUp(appId);
306+ verify(app.session.surface);
307+ return app;
308+ }
309+
310+ function test_appFocusSwitch_data() {
311+ return [
312+ {tag: "dash", apps: [ "unity8-dash", "dialer-app", "camera-app" ], focusfrom: 0, focusTo: 1 },
313+ {tag: "dash", apps: [ "unity8-dash", "dialer-app", "camera-app" ], focusfrom: 1, focusTo: 0 },
314+ ]
315+ }
316+
317+ function test_appFocusSwitch(data) {
318+ selectUser("no-password");
319+ clickPasswordInput(true)
320+ confirmLoggedIn(true);
321+
322+ var i;
323+ for (i = 0; i < data.apps.length; i++) {
324+ startApplication(data.apps[i]);
325+ }
326+
327+ ApplicationManager.requestFocusApplication(data.apps[data.focusfrom]);
328+ tryCompare(ApplicationManager.findApplication(data.apps[data.focusfrom]).session.surface, "activeFocus", true);
329+
330+ ApplicationManager.requestFocusApplication(data.apps[data.focusTo]);
331+ tryCompare(ApplicationManager.findApplication(data.apps[data.focusTo]).session.surface, "activeFocus", true);
332+ }
333+
334+ function test_decorationPressFocusesApplication_data() {
335+ return [
336+ {tag: "dash", apps: [ "unity8-dash", "dialer-app", "camera-app" ], focusfrom: 0, focusTo: 1 },
337+ {tag: "dash", apps: [ "unity8-dash", "dialer-app", "camera-app" ], focusfrom: 1, focusTo: 0 },
338+ ]
339+ }
340+
341+ function test_decorationPressFocusesApplication(data) {
342+ selectUser("no-password");
343+ clickPasswordInput(true)
344+ confirmLoggedIn(true);
345+
346+ var i;
347+ for (i = 0; i < data.apps.length; i++) {
348+ startApplication(data.apps[i]);
349+ }
350+
351+ var fromAppDecoration = findChild(shell, "appWindowDecoration_" + data.apps[data.focusfrom]);
352+ verify(fromAppDecoration);
353+ mouseClick(fromAppDecoration);
354+ tryCompare(ApplicationManager.findApplication(data.apps[data.focusfrom]).session.surface, "activeFocus", true);
355+
356+ var toAppDecoration = findChild(shell, "appWindowDecoration_" + data.apps[data.focusTo]);
357+ verify(toAppDecoration);
358+ mouseClick(toAppDecoration);
359+ tryCompare(ApplicationManager.findApplication(data.apps[data.focusTo]).session.surface, "activeFocus", true);
360+ }
361+ }
362+}