on xenial help sign in
merge prereq
1dbbef5Michael Zanetti
CMakeLists.txt+1−1
@@ -57,7 +57,7 @@ find_package(Qt5DBus 5.2 REQUIRED)
57find_package(Qt5Concurrent 5.2 REQUIRED)
57find_package(Qt5Concurrent 5.2 REQUIRED)
58find_package(Qt5Sql 5.2 REQUIRED)
58find_package(Qt5Sql 5.2 REQUIRED)
59
59
60−pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=9)
60+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=10)
61
61
62# Standard install paths
62# Standard install paths
63include(GNUInstallDirs)
63include(GNUInstallDirs)
debian/control+2−2
@@ -127,7 +127,7 @@ Depends: qml-module-qtquick-layouts,
127 qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.3.1627) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.3.1627),
127 qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.3.1627) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.3.1627),
128 qtdeclarative5-unity-notifications-plugin (>= 0.1.2) | unity-notifications-impl,
128 qtdeclarative5-unity-notifications-plugin (>= 0.1.2) | unity-notifications-impl,
129 ubuntu-thumbnailer-impl-0,
129 ubuntu-thumbnailer-impl-0,
130− unity-application-impl-9,
130+ unity-application-impl-10,
131 unity-notifications-impl-3,
131 unity-notifications-impl-3,
132 unity-plugin-scopes | unity-scopes-impl,
132 unity-plugin-scopes | unity-scopes-impl,
133 unity-scopes-impl-7,
133 unity-scopes-impl-7,
@@ -173,7 +173,7 @@ Pre-Depends: ${misc:Pre-Depends},
173Depends: ${misc:Depends},
173Depends: ${misc:Depends},
174 ${shlibs:Depends},
174 ${shlibs:Depends},
175Provides: unity-application-impl,
175Provides: unity-application-impl,
176− unity-application-impl-9,
176+ unity-application-impl-10,
177Replaces: unity8-autopilot (<< 8.02+15.04.20150422-0ubuntu1)
177Replaces: unity8-autopilot (<< 8.02+15.04.20150422-0ubuntu1)
178Description: Fake environment for running Unity 8 shell
178Description: Fake environment for running Unity 8 shell
179 Provides fake implementations of some QML modules used by Unity 8 shell
179 Provides fake implementations of some QML modules used by Unity 8 shell
plugins/Utils/CMakeLists.txt+2
@@ -10,6 +10,8 @@ include_directories(
10
10
11set(QMLPLUGIN_SRC
11set(QMLPLUGIN_SRC
12 activefocuslogger.cpp
12 activefocuslogger.cpp
13+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
14+ applicationsfiltermodel.cpp
13 constants.cpp
15 constants.cpp
14 HomeKeyWatcher.cpp
16 HomeKeyWatcher.cpp
15 inputwatcher.cpp
17 inputwatcher.cpp
plugins/Utils/applicationsfiltermodel.cppnew file+101
@@ -0,0 +1,101 @@
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+// unity-api
18+#include <unity/shell/application/ApplicationManagerInterface.h>
19+#include <unity/shell/application/ApplicationInfoInterface.h>
20+
21+#include "applicationsfiltermodel.h"
22+
23+using namespace unity::shell::application;
24+
25+ApplicationsFilterModel::ApplicationsFilterModel(QObject *parent):
26+ QSortFilterProxyModel(parent),
27+ m_appModel(nullptr),
28+ m_filterTouchApps(false),
29+ m_filterLegacyApps(false)
30+{
31+}
32+
33+ApplicationManagerInterface *ApplicationsFilterModel::applicationsModel() const
34+{
35+ return m_appModel;
36+}
37+
38+void ApplicationsFilterModel::setApplicationsModel(ApplicationManagerInterface *applicationsModel)
39+{
40+ if (m_appModel != applicationsModel) {
41+ if (m_appModel) {
42+ disconnect(m_appModel, &ApplicationManagerInterface::countChanged, this, &ApplicationsFilterModel::countChanged);
43+ }
44+ m_appModel = applicationsModel;
45+ setSourceModel(m_appModel);
46+ Q_EMIT applicationsModelChanged();
47+ connect(m_appModel, &ApplicationManagerInterface::countChanged, this, &ApplicationsFilterModel::countChanged);
48+ }
49+}
50+
51+bool ApplicationsFilterModel::filterTouchApps() const
52+{
53+ return m_filterTouchApps;
54+}
55+
56+void ApplicationsFilterModel::setFilterTouchApps(bool filterTouchApps)
57+{
58+ if (m_filterTouchApps != filterTouchApps) {
59+ m_filterTouchApps = filterTouchApps;
60+ Q_EMIT filterTouchAppsChanged();
61+
62+ invalidateFilter();
63+ Q_EMIT countChanged();
64+ }
65+}
66+
67+bool ApplicationsFilterModel::filterLegacyApps() const
68+{
69+ return m_filterLegacyApps;
70+}
71+
72+void ApplicationsFilterModel::setFilterLegacyApps(bool filterLegacyApps)
73+{
74+ if (m_filterLegacyApps != filterLegacyApps) {
75+ m_filterLegacyApps = filterLegacyApps;
76+ Q_EMIT filterLegacyAppsChanged();
77+
78+ invalidateFilter();
79+ Q_EMIT countChanged();
80+ }
81+}
82+
83+bool ApplicationsFilterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
84+{
85+ Q_UNUSED(source_parent);
86+
87+ ApplicationInfoInterface *app = m_appModel->get(source_row);
88+ Q_ASSERT(app);
89+ if (m_filterLegacyApps && !app->isTouchApp()) {
90+ return false;
91+ }
92+ if (m_filterTouchApps && app->isTouchApp()) {
93+ return false;
94+ }
95+ return true;
96+}
97+
98+ApplicationInfoInterface *ApplicationsFilterModel::get(int index) const
99+{
100+ return m_appModel->get(mapToSource(this->index(index, 0)).row());
101+}
plugins/Utils/applicationsfiltermodel.hnew file+69
@@ -0,0 +1,69 @@
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 APPLICATIONSFILTERMODEL_H
18+#define APPLICATIONSFILTERMODEL_H
19+
20+#include <QSortFilterProxyModel>
21+
22+namespace unity {
23+namespace shell {
24+namespace application {
25+class ApplicationManagerInterface;
26+class ApplicationInfoInterface;
27+}
28+}
29+}
30+using namespace unity::shell::application;
31+
32+class ApplicationsFilterModel: public QSortFilterProxyModel
33+{
34+ Q_OBJECT
35+
36+ Q_PROPERTY(unity::shell::application::ApplicationManagerInterface* applicationsModel READ applicationsModel WRITE setApplicationsModel NOTIFY applicationsModelChanged)
37+ Q_PROPERTY(bool filterTouchApps READ filterTouchApps WRITE setFilterTouchApps NOTIFY filterTouchAppsChanged)
38+ Q_PROPERTY(bool filterLegacyApps READ filterLegacyApps WRITE setFilterLegacyApps NOTIFY filterLegacyAppsChanged)
39+
40+ Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
41+public:
42+ ApplicationsFilterModel(QObject *parent = 0);
43+
44+ ApplicationManagerInterface* applicationsModel() const;
45+ void setApplicationsModel(ApplicationManagerInterface* applicationsModel);
46+
47+ bool filterTouchApps() const;
48+ void setFilterTouchApps(bool filterTouchApps);
49+
50+ bool filterLegacyApps() const;
51+ void setFilterLegacyApps(bool filterLegacyApps);
52+
53+ bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
54+
55+ Q_INVOKABLE unity::shell::application::ApplicationInfoInterface* get(int index) const;
56+
57+Q_SIGNALS:
58+ void applicationsModelChanged();
59+ void filterTouchAppsChanged();
60+ void filterLegacyAppsChanged();
61+ void countChanged();
62+
63+private:
64+ ApplicationManagerInterface* m_appModel;
65+ bool m_filterTouchApps;
66+ bool m_filterLegacyApps;
67+};
68+
69+#endif
plugins/Utils/plugin.cpp+2
@@ -35,6 +35,7 @@
35#include "windowstatestorage.h"
35#include "windowstatestorage.h"
36#include "constants.h"
36#include "constants.h"
37#include "timezoneFormatter.h"
37#include "timezoneFormatter.h"
38+#include "applicationsfiltermodel.h"
38
39
39static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
40static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
40{
41{
@@ -66,6 +67,7 @@ void UtilsPlugin::registerTypes(const char *uri)
66 qmlRegisterSingletonType<TimezoneFormatter>(uri, 0, 1, "TimezoneFormatter",
67 qmlRegisterSingletonType<TimezoneFormatter>(uri, 0, 1, "TimezoneFormatter",
67 [](QQmlEngine*, QJSEngine*) -> QObject* { return new TimezoneFormatter; });
68 [](QQmlEngine*, QJSEngine*) -> QObject* { return new TimezoneFormatter; });
68 qmlRegisterType<ActiveFocusLogger>(uri, 0, 1, "ActiveFocusLogger");
69 qmlRegisterType<ActiveFocusLogger>(uri, 0, 1, "ActiveFocusLogger");
70+ qmlRegisterType<ApplicationsFilterModel>(uri, 0, 1, "ApplicationsFilterModel");
69}
71}
70
72
71void UtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
73void UtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
plugins/Utils/windowstatestorage.cpp+61−22
@@ -47,26 +47,38 @@ WindowStateStorage::~WindowStateStorage()
47 m_db.close();
47 m_db.close();
48}
48}
49
49
50−void WindowStateStorage::saveGeometry(const QString &windowId, const QRect &rect)
50+void WindowStateStorage::saveState(const QString &windowId, WindowStateStorage::WindowState state)
51{
51{
52− QMutexLocker mutexLocker(&s_mutex);
52+ const QString queryString = QStringLiteral("INSERT OR REPLACE INTO state (windowId, state) values ('%1', '%2');")
53+ .arg(windowId)
54+ .arg((int)state);
55+
56+ saveValue(queryString);
57+}
58+
59+WindowStateStorage::WindowState WindowStateStorage::getState(const QString &windowId, WindowStateStorage::WindowState defaultValue) const
60+{
61+ const QString queryString = QStringLiteral("SELECT * FROM state WHERE windowId = '%1';")
62+ .arg(windowId);
53
63
64+ QSqlQuery query = getValue(queryString);
65+
66+ if (!query.first()) {
67+ return defaultValue;
68+ }
69+ return (WindowState)query.value("state").toInt();
70+}
71+
72+void WindowStateStorage::saveGeometry(const QString &windowId, const QRect &rect)
73+{
54− QString queryString = QStringLiteral("INSERT OR REPLACE INTO geometry (windowId, x, y, width, height) values ('%1', '%2', '%3', '%4', '%5');")
74+ const QString queryString = QStringLiteral("INSERT OR REPLACE INTO geometry (windowId, x, y, width, height) values ('%1', '%2', '%3', '%4', '%5');")
55 .arg(windowId)
75 .arg(windowId)
56 .arg(rect.x())
76 .arg(rect.x())
57 .arg(rect.y())
77 .arg(rect.y())
58 .arg(rect.width())
78 .arg(rect.width())
59 .arg(rect.height());
79 .arg(rect.height());
60
80
61− QFuture<void> future = QtConcurrent::run(executeAsyncQuery, queryString);
81+ saveValue(queryString);
62− m_asyncQueries.append(future);
63−
64− QFutureWatcher<void> *futureWatcher = new QFutureWatcher<void>();
65− futureWatcher->setFuture(future);
66− connect(futureWatcher, &QFutureWatcher<void>::finished,
67− this,
68− [=](){ m_asyncQueries.removeAll(futureWatcher->future());
69− futureWatcher->deleteLater(); });
70}
82}
71
83
72void WindowStateStorage::executeAsyncQuery(const QString &queryString)
84void WindowStateStorage::executeAsyncQuery(const QString &queryString)
@@ -82,20 +94,13 @@ void WindowStateStorage::executeAsyncQuery(const QString &queryString)
82 }
94 }
83}
95}
84
96
85−QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue)
97+QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue) const
86{
98{
87− QMutexLocker l(&s_mutex);
88 QString queryString = QStringLiteral("SELECT * FROM geometry WHERE windowId = '%1';")
99 QString queryString = QStringLiteral("SELECT * FROM geometry WHERE windowId = '%1';")
89 .arg(windowId);
100 .arg(windowId);
90− QSqlQuery query;
91
101
92− bool ok = query.exec(queryString);
102+ QSqlQuery query = getValue(queryString);
93− if (!ok) {
103+
94− qWarning() << "Error retrieving window state for" << windowId
95− << "Driver error:" << query.lastError().driverText()
96− << "Database error:" << query.lastError().databaseText();
97− return defaultValue;
98− }
99 if (!query.first()) {
104 if (!query.first()) {
100 return defaultValue;
105 return defaultValue;
101 }
106 }
@@ -114,4 +119,38 @@ void WindowStateStorage::initdb()
114 QSqlQuery query;
119 QSqlQuery query;
115 query.exec(QStringLiteral("CREATE TABLE geometry(windowId TEXT UNIQUE, x INTEGER, y INTEGER, width INTEGER, height INTEGER);"));
120 query.exec(QStringLiteral("CREATE TABLE geometry(windowId TEXT UNIQUE, x INTEGER, y INTEGER, width INTEGER, height INTEGER);"));
116 }
121 }
122+
123+ if (!m_db.tables().contains("state")) {
124+ QSqlQuery query;
125+ query.exec("CREATE TABLE state(windowId TEXT UNIQUE, state INTEGER);");
126+ }
127+}
128+
129+void WindowStateStorage::saveValue(const QString &queryString)
130+{
131+ QMutexLocker mutexLocker(&s_mutex);
132+
133+ QFuture<void> future = QtConcurrent::run(executeAsyncQuery, queryString);
134+ m_asyncQueries.append(future);
135+
136+ QFutureWatcher<void> *futureWatcher = new QFutureWatcher<void>();
137+ futureWatcher->setFuture(future);
138+ connect(futureWatcher, &QFutureWatcher<void>::finished,
139+ this,
140+ [=](){ m_asyncQueries.removeAll(futureWatcher->future());
141+ futureWatcher->deleteLater(); });
142+}
143+
144+QSqlQuery WindowStateStorage::getValue(const QString &queryString) const
145+{
146+ QMutexLocker l(&s_mutex);
147+ QSqlQuery query;
148+
149+ bool ok = query.exec(queryString);
150+ if (!ok) {
151+ qWarning() << "Error retrieving database query:" << queryString
152+ << "Driver error:" << query.lastError().driverText()
153+ << "Database error:" << query.lastError().databaseText();
154+ }
155+ return query;
117}
156}
plugins/Utils/windowstatestorage.h+13−1
@@ -22,16 +22,28 @@
22class WindowStateStorage: public QObject
22class WindowStateStorage: public QObject
23{
23{
24 Q_OBJECT
24 Q_OBJECT
25+ Q_ENUMS(WindowState)
25public:
26public:
27+ enum WindowState {
28+ WindowStateNormal,
29+ WindowStateMaximized
30+ };
31+
26 WindowStateStorage(QObject *parent = 0);
32 WindowStateStorage(QObject *parent = 0);
27 virtual ~WindowStateStorage();
33 virtual ~WindowStateStorage();
28
34
35+ Q_INVOKABLE void saveState(const QString &windowId, WindowState state);
36+ Q_INVOKABLE WindowState getState(const QString &windowId, WindowState defaultValue) const;
37+
29 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect);
38 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect);
30− Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue);
39+ Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue) const;
31
40
32private:
41private:
33 void initdb();
42 void initdb();
34
43
44+ void saveValue(const QString &queryString);
45+ QSqlQuery getValue(const QString &queryString) const;
46+
35 static void executeAsyncQuery(const QString &queryString);
47 static void executeAsyncQuery(const QString &queryString);
36 static QMutex s_mutex;
48 static QMutex s_mutex;
37
49
qml/Components/Dialogs.qml+29−2
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@ import Unity.Session 0.1
21import Ubuntu.Components 1.1
21import Ubuntu.Components 1.1
22import GlobalShortcut 1.0
22import GlobalShortcut 1.0
23import Unity.Platform 1.0
23import Unity.Platform 1.0
24+import Utils 0.1
24import "../Greeter"
25import "../Greeter"
25
26
26Item {
27Item {
@@ -45,6 +46,31 @@ Item {
45 d.showPowerDialog();
46 d.showPowerDialog();
46 }
47 }
47
48
49+ onUsageScenarioChanged: {
50+ if (usageScenario != "desktop" && legacyAppsModel.count > 0 && !d.modeSwitchWarningPopup) {
51+ var comp = Qt.createComponent(Qt.resolvedUrl("ModeSwitchWarningDialog.qml"))
52+ d.modeSwitchWarningPopup = comp.createObject(root, {model: legacyAppsModel});
53+ d.modeSwitchWarningPopup.forceClose.connect(function() {
54+ while (legacyAppsModel.count > 0) {
55+ ApplicationManager.stopApplication(legacyAppsModel.get(0).appId);
56+ }
57+ d.modeSwitchWarningPopup.hide();
58+ d.modeSwitchWarningPopup.destroy();
59+ d.modeSwitchWarningPopup = null;
60+ })
61+ } else if (usageScenario == "desktop" && d.modeSwitchWarningPopup) {
62+ d.modeSwitchWarningPopup.hide();
63+ d.modeSwitchWarningPopup.destroy();
64+ d.modeSwitchWarningPopup = null;
65+ }
66+ }
67+
68+ ApplicationsFilterModel {
69+ id: legacyAppsModel
70+ applicationsModel: ApplicationManager
71+ filterTouchApps: true
72+ }
73+
48 GlobalShortcut { // reboot/shutdown dialog
74 GlobalShortcut { // reboot/shutdown dialog
49 shortcut: Qt.Key_PowerDown
75 shortcut: Qt.Key_PowerDown
50 active: Platform.isPC
76 active: Platform.isPC
@@ -91,6 +117,8 @@ Item {
91 id: d // private stuff
117 id: d // private stuff
92 objectName: "dialogsPrivate"
118 objectName: "dialogsPrivate"
93
119
120+ property var modeSwitchWarningPopup: null
121+
94 function showPowerDialog() {
122 function showPowerDialog() {
95 if (!dialogLoader.active) {
123 if (!dialogLoader.active) {
96 dialogLoader.sourceComponent = powerDialogComponent;
124 dialogLoader.sourceComponent = powerDialogComponent;
@@ -270,5 +298,4 @@ Item {
270 unitySessionService.endSession();
298 unitySessionService.endSession();
271 }
299 }
272 }
300 }
273−
274}
301}
qml/Components/ModeSwitchWarningDialog.qmlnew file+95
@@ -0,0 +1,95 @@
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.4
18+import QtQuick.Layouts 1.1
19+import Ubuntu.Components 1.2
20+import Ubuntu.Components.ListItems 1.2
21+import Ubuntu.Components.Popups 1.2
22+
23+ShellDialog {
24+ id: root
25+ objectName: "modeSwitchWarningDialog"
26+
27+ property alias model: appRepeater.model
28+
29+ signal forceClose();
30+
31+ Label {
32+ text: i18n.tr("Apps may have unsaved data:")
33+ fontSize: "large"
34+ color: "#5D5D5D"
35+ }
36+
37+ Repeater {
38+ id: appRepeater
39+ RowLayout {
40+ spacing: units.gu(2)
41+ Image {
42+ Layout.preferredHeight: units.gu(2)
43+ Layout.preferredWidth: units.gu(2)
44+ source: model.icon
45+ sourceSize.width: width
46+ sourceSize.height: height
47+ }
48+ Label {
49+ Layout.fillWidth: true
50+ text: model.name
51+ color: "#888888"
52+ }
53+ }
54+ }
55+
56+ Label {
57+ text: i18n.tr("Re-dock, save your work and close these apps to continue.")
58+ wrapMode: Text.WordWrap
59+ color: "#888888"
60+ }
61+
62+ Label {
63+ text: i18n.tr("Or force close now (unsaved data will be lost).")
64+ wrapMode: Text.WordWrap
65+ color: "#888888"
66+ }
67+
68+ ThinDivider {}
69+
70+ RowLayout {
71+ Label {
72+ objectName: "reconnectLabel"
73+ Layout.fillWidth: true
74+ property bool clicked: false
75+ property string notClickedText: i18n.tr("OK, I will reconnect")
76+ property string clickedText: i18n.tr("Reconnect now!")
77+ text: clicked ? clickedText : notClickedText
78+ color: "#333333"
79+
80+ MouseArea {
81+ anchors.fill: parent
82+ onClicked: parent.clicked = true;
83+ }
84+ }
85+
86+ Button {
87+ objectName: "forceCloseButton"
88+ text: i18n.tr("Close all")
89+ color: UbuntuColors.red
90+ onClicked: {
91+ root.forceClose();
92+ }
93+ }
94+ }
95+}
qml/Components/Orientations.qmlnew file+29
@@ -0,0 +1,29 @@
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 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.4
18+
19+QtObject {
20+ // Just because "native" is a reserved keyword :(
21+ property int native_: Qt.PortraitOrientation
22+
23+ property int primary: Qt.PortraitOrientation
24+
25+ property int landscape: Qt.LandscapeOrientation
26+ property int invertedLandscape: Qt.InvertedLandscapeOrientation
27+ property int portrait: Qt.PortraitOrientation
28+ property int invertedPortrait: Qt.InvertedPortraitOrientation
29+}
qml/Components/ShellDialog.qml+1−1
@@ -31,7 +31,7 @@ Dialog {
31 // NB: PopupBase, Dialog's superclass, will check for the existence of this property
31 // NB: PopupBase, Dialog's superclass, will check for the existence of this property
32 property bool reparentToRootItem: false
32 property bool reparentToRootItem: false
33
33
34− onVisibleChanged: { if (!visible) { dialogLoader.active = false; } }
34+ onVisibleChanged: { if (!visible && dialogLoader) { dialogLoader.active = false; } }
35
35
36 Keys.onEscapePressed: hide()
36 Keys.onEscapePressed: hide()
37
37
qml/Components/WindowControlButtons.qml+3
@@ -28,6 +28,7 @@ Row {
28 signal maximize()
28 signal maximize()
29
29
30 Rectangle {
30 Rectangle {
31+ objectName: "closeWindowButton"
31 height: parent.height; width: height; radius: height / 2
32 height: parent.height; width: height; radius: height / 2
32 gradient: Gradient {
33 gradient: Gradient {
33 GradientStop { color: "#F49073"; position: 0 }
34 GradientStop { color: "#F49073"; position: 0 }
@@ -38,6 +39,7 @@ Row {
38 MouseArea { anchors.fill: parent; onClicked: root.close() }
39 MouseArea { anchors.fill: parent; onClicked: root.close() }
39 }
40 }
40 Rectangle {
41 Rectangle {
42+ objectName: "minimizeWindowButton"
41 height: parent.height; width: height; radius: height / 2
43 height: parent.height; width: height; radius: height / 2
42 gradient: Gradient {
44 gradient: Gradient {
43 GradientStop { color: "#92918C"; position: 0 }
45 GradientStop { color: "#92918C"; position: 0 }
@@ -48,6 +50,7 @@ Row {
48 MouseArea { anchors.fill: parent; onClicked: root.minimize() }
50 MouseArea { anchors.fill: parent; onClicked: root.minimize() }
49 }
51 }
50 Rectangle {
52 Rectangle {
53+ objectName: "maximizeWindowButton"
51 height: parent.height; width: height; radius: height / 2
54 height: parent.height; width: height; radius: height / 2
52 gradient: Gradient {
55 gradient: Gradient {
53 GradientStop { color: "#92918C"; position: 0 }
56 GradientStop { color: "#92918C"; position: 0 }
qml/Dash/graphics/phone/screenshots/libreoffice@12.pngnew file

Binary file not shown.

qml/DeviceConfiguration.qml+84−64
@@ -16,79 +16,99 @@
16
16
17import QtQuick 2.0
17import QtQuick 2.0
18
18
19−StateGroup {
19+QtObject {
20 id: root
20 id: root
21
21
22 readonly property int useNativeOrientation: -1
22 readonly property int useNativeOrientation: -1
23
23
24− property int primaryOrientation: useNativeOrientation
24+ // The only writable property in the API
25+ // all other properties are set according to the device name
26+ property alias name: priv.state
25
27
26− property int supportedOrientations: Qt.PortraitOrientation
28+ readonly property alias primaryOrientation: priv.primaryOrientation
27− | Qt.InvertedPortraitOrientation
29+ readonly property alias supportedOrientations: priv.supportedOrientations
28− | Qt.LandscapeOrientation
30+ readonly property alias landscapeOrientation: priv.landscapeOrientation
29− | Qt.InvertedLandscapeOrientation
31+ readonly property alias invertedLandscapeOrientation: priv.invertedLandscapeOrientation
32+ readonly property alias portraitOrientation: priv.portraitOrientation
33+ readonly property alias invertedPortraitOrientation: priv.invertedPortraitOrientation
30
34
31− // Supported values so far:
32− // "phone", "tablet" or "desktop"
33− property string category: "phone"
35+ readonly property alias category: priv.category
34
36
35− property int ignoredMice: 0
37+ readonly property alias ignoredMice: priv.ignoredMice
36
38
39+ readonly property var priv: StateGroup {
40+ id: priv
37
41
38− property alias name: root.state
42+ property int primaryOrientation: root.useNativeOrientation
39
43
40− states: [
44+ property int supportedOrientations: Qt.PortraitOrientation
41− State {
45+ | Qt.InvertedPortraitOrientation
42− name: "mako"
43− PropertyChanges {
44− target: root
45− supportedOrientations: Qt.PortraitOrientation
46− | Qt.LandscapeOrientation
46+ | Qt.LandscapeOrientation
47− | Qt.InvertedLandscapeOrientation
47+ | Qt.InvertedLandscapeOrientation
48− }
48+
49− },
49+ property int landscapeOrientation: Qt.LandscapeOrientation
50− State {
51− name: "krillin"
52− PropertyChanges {
53− target: root
54− supportedOrientations: Qt.PortraitOrientation
55− | Qt.LandscapeOrientation
56− | Qt.InvertedLandscapeOrientation
50+ property int invertedLandscapeOrientation: Qt.InvertedLandscapeOrientation
57− }
58− },
59− State {
60− name: "arale"
61− PropertyChanges {
62− target: root
63− supportedOrientations: Qt.PortraitOrientation
51+ property int portraitOrientation: Qt.PortraitOrientation
64− | Qt.LandscapeOrientation
52+ property int invertedPortraitOrientation: Qt.InvertedPortraitOrientation
65− | Qt.InvertedLandscapeOrientation
53+
66− ignoredMice: 1
54+ // Supported values so far:
67− }
68− },
69− State {
70− name: "manta"
71− PropertyChanges {
72− target: root
73− category: "tablet"
74− }
75− },
76− State {
77− name: "flo"
78− PropertyChanges {
79− target: root
80− primaryOrientation: Qt.InvertedLandscapeOrientation
81− category: "tablet"
82− }
83− },
84− State {
85− name: "desktop"
55+ // "phone", "tablet" or "desktop"
86− PropertyChanges {
56+ property string category: "phone"
87− target: root
88− category: "desktop"
89− supportedOrientations: root.useNativeOrientation
90− }
91− }
92− ]
93
57
58+ property int ignoredMice: 0
59+
60+ states: [
61+ State {
62+ name: "mako"
63+ PropertyChanges {
64+ target: priv
65+ supportedOrientations: Qt.PortraitOrientation
66+ | Qt.LandscapeOrientation
67+ | Qt.InvertedLandscapeOrientation
68+ }
69+ },
70+ State {
71+ name: "krillin"
72+ PropertyChanges {
73+ target: priv
74+ supportedOrientations: Qt.PortraitOrientation
75+ | Qt.LandscapeOrientation
76+ | Qt.InvertedLandscapeOrientation
77+ }
78+ },
79+ State {
80+ name: "arale"
81+ PropertyChanges {
82+ target: priv
83+ supportedOrientations: Qt.PortraitOrientation
84+ | Qt.LandscapeOrientation
85+ | Qt.InvertedLandscapeOrientation
86+ ignoredMice: 1
87+ }
88+ },
89+ State {
90+ name: "manta"
91+ PropertyChanges {
92+ target: priv
93+ category: "tablet"
94+ }
95+ },
96+ State {
97+ name: "flo"
98+ PropertyChanges {
99+ target: priv
100+ landscapeOrientation: Qt.InvertedLandscapeOrientation
101+ primaryOrientation: Qt.InvertedLandscapeOrientation
102+ category: "tablet"
103+ }
104+ },
105+ State {
106+ name: "desktop"
107+ PropertyChanges {
108+ target: priv
109+ category: "desktop"
110+ }
111+ }
112+ ]
113+ }
94}
114}
qml/OrientedShell.qml+31−21
@@ -32,20 +32,31 @@ Rectangle {
32 implicitWidth: units.gu(40)
32 implicitWidth: units.gu(40)
33 implicitHeight: units.gu(71)
33 implicitHeight: units.gu(71)
34
34
35− // NB: native and primary orientations here don't map exactly to their QScreen counterparts
36− readonly property int nativeOrientation: width > height ? Qt.LandscapeOrientation : Qt.PortraitOrientation
37−
38− readonly property int primaryOrientation:
39− deviceConfiguration.primaryOrientation == deviceConfiguration.useNativeOrientation
40− ? nativeOrientation : deviceConfiguration.primaryOrientation
41−
42 DeviceConfiguration {
35 DeviceConfiguration {
43 id: deviceConfiguration
36 id: deviceConfiguration
44 name: applicationArguments.deviceName
37 name: applicationArguments.deviceName
45 }
38 }
46
39
40+ property alias orientations: d.orientations
41+
42+ Item {
43+ id: d
44+
45+ property Orientations orientations: Orientations {
46+ id: orientations
47+ // NB: native and primary orientations here don't map exactly to their QScreen counterparts
48+ native_: root.width > root.height ? Qt.LandscapeOrientation : Qt.PortraitOrientation
47
49
50+ primary: deviceConfiguration.primaryOrientation == deviceConfiguration.useNativeOrientation
51+ ? native_ : deviceConfiguration.primaryOrientation
52+
53+ landscape: deviceConfiguration.landscapeOrientation
54+ invertedLandscape: deviceConfiguration.invertedLandscapeOrientation
55+ portrait: deviceConfiguration.portraitOrientation
56+ invertedPortrait: deviceConfiguration.invertedPortraitOrientation
57+ }
58+ }
48− // to be overwritten by tests
59+ // to be overwritten by tests
49 property var unity8Settings: Unity8Settings {}
60 property var unity8Settings: Unity8Settings {}
50 property var oskSettings: GSettings { schema.id: "com.canonical.keyboard.maliit" }
61 property var oskSettings: GSettings { schema.id: "com.canonical.keyboard.maliit" }
51
62
@@ -101,7 +112,7 @@ Rectangle {
101
112
102 property int acceptedOrientationAngle: {
113 property int acceptedOrientationAngle: {
103 if (orientation & supportedOrientations) {
114 if (orientation & supportedOrientations) {
104− return Screen.angleBetween(nativeOrientation, orientation);
115+ return Screen.angleBetween(orientations.native_, orientation);
105 } else if (shell.orientation & supportedOrientations) {
116 } else if (shell.orientation & supportedOrientations) {
106 // stay where we are
117 // stay where we are
107 return shell.orientationAngle;
118 return shell.orientationAngle;
@@ -111,16 +122,16 @@ Rectangle {
111 // rotate to some supported orientation as we can't stay where we currently are
122 // rotate to some supported orientation as we can't stay where we currently are
112 // TODO: Choose the closest to the current one
123 // TODO: Choose the closest to the current one
113 if (supportedOrientations & Qt.PortraitOrientation) {
124 if (supportedOrientations & Qt.PortraitOrientation) {
114− return Screen.angleBetween(nativeOrientation, Qt.PortraitOrientation);
125+ return Screen.angleBetween(orientations.native_, Qt.PortraitOrientation);
115 } else if (supportedOrientations & Qt.LandscapeOrientation) {
126 } else if (supportedOrientations & Qt.LandscapeOrientation) {
116− return Screen.angleBetween(nativeOrientation, Qt.LandscapeOrientation);
127+ return Screen.angleBetween(orientations.native_, Qt.LandscapeOrientation);
117 } else if (supportedOrientations & Qt.InvertedPortraitOrientation) {
128 } else if (supportedOrientations & Qt.InvertedPortraitOrientation) {
118− return Screen.angleBetween(nativeOrientation, Qt.InvertedPortraitOrientation);
129+ return Screen.angleBetween(orientations.native_, Qt.InvertedPortraitOrientation);
119 } else if (supportedOrientations & Qt.InvertedLandscapeOrientation) {
130 } else if (supportedOrientations & Qt.InvertedLandscapeOrientation) {
120− return Screen.angleBetween(nativeOrientation, Qt.InvertedLandscapeOrientation);
131+ return Screen.angleBetween(orientations.native_, Qt.InvertedLandscapeOrientation);
121 } else {
132 } else {
122 // if all fails, fallback to primary orientation
133 // if all fails, fallback to primary orientation
123− return Screen.angleBetween(nativeOrientation, primaryOrientation);
134+ return Screen.angleBetween(orientations.native_, orientations.primary);
124 }
135 }
125 }
136 }
126 }
137 }
@@ -128,19 +139,19 @@ Rectangle {
128 function angleToOrientation(angle) {
139 function angleToOrientation(angle) {
129 switch (angle) {
140 switch (angle) {
130 case 0:
141 case 0:
131− return nativeOrientation;
142+ return orientations.native_;
132 case 90:
143 case 90:
133− return nativeOrientation === Qt.PortraitOrientation ? Qt.InvertedLandscapeOrientation
144+ return orientations.native_ === Qt.PortraitOrientation ? Qt.InvertedLandscapeOrientation
134 : Qt.PortraitOrientation;
145 : Qt.PortraitOrientation;
135 case 180:
146 case 180:
136− return nativeOrientation === Qt.PortraitOrientation ? Qt.InvertedPortraitOrientation
147+ return orientations.native_ === Qt.PortraitOrientation ? Qt.InvertedPortraitOrientation
137 : Qt.InvertedLandscapeOrientation;
148 : Qt.InvertedLandscapeOrientation;
138 case 270:
149 case 270:
139− return nativeOrientation === Qt.PortraitOrientation ? Qt.LandscapeOrientation
150+ return orientations.native_ === Qt.PortraitOrientation ? Qt.LandscapeOrientation
140 : Qt.InvertedPortraitOrientation;
151 : Qt.InvertedPortraitOrientation;
141 default:
152 default:
142 console.warn("angleToOrientation: Invalid orientation angle: " + angle);
153 console.warn("angleToOrientation: Invalid orientation angle: " + angle);
143− return primaryOrientation;
154+ return orientations.primary;
144 }
155 }
145 }
156 }
146
157
@@ -159,8 +170,7 @@ Rectangle {
159 width: root.width
170 width: root.width
160 height: root.height
171 height: root.height
161 orientation: root.angleToOrientation(orientationAngle)
172 orientation: root.angleToOrientation(orientationAngle)
162− primaryOrientation: root.primaryOrientation
173+ orientations: root.orientations
163− nativeOrientation: root.nativeOrientation
164 nativeWidth: root.width
174 nativeWidth: root.width
165 nativeHeight: root.height
175 nativeHeight: root.height
166 mode: applicationArguments.mode
176 mode: applicationArguments.mode
qml/Panel/Panel.qml+5−3
@@ -28,6 +28,7 @@ Item {
28 property alias callHint: __callHint
28 property alias callHint: __callHint
29 property bool fullscreenMode: false
29 property bool fullscreenMode: false
30 property real indicatorAreaShowProgress: 1.0
30 property real indicatorAreaShowProgress: 1.0
31+ property bool locked: false
31
32
32 opacity: fullscreenMode && indicators.fullyClosed ? 0.0 : 1.0
33 opacity: fullscreenMode && indicators.fullyClosed ? 0.0 : 1.0
33
34
@@ -136,7 +137,7 @@ Item {
136 }
137 }
137
138
138 shown: false
139 shown: false
139− width: root.width - (PanelState.buttonsVisible ? windowControlButtons.width : 0)
140+ width: root.width - (windowControlButtons.visible ? windowControlButtons.width : 0)
140 minimizedPanelHeight: units.gu(3)
141 minimizedPanelHeight: units.gu(3)
141 expandedPanelHeight: units.gu(7)
142 expandedPanelHeight: units.gu(7)
142 openedHeight: root.height - indicatorOrangeLine.height
143 openedHeight: root.height - indicatorOrangeLine.height
@@ -164,13 +165,14 @@ Item {
164
165
165 WindowControlButtons {
166 WindowControlButtons {
166 id: windowControlButtons
167 id: windowControlButtons
168+ objectName: "panelWindowControlButtons"
167 anchors {
169 anchors {
168 left: parent.left
170 left: parent.left
169 top: parent.top
171 top: parent.top
170 margins: units.gu(0.7)
172 margins: units.gu(0.7)
171 }
173 }
172 height: indicators.minimizedPanelHeight - anchors.margins * 2
174 height: indicators.minimizedPanelHeight - anchors.margins * 2
173− visible: PanelState.buttonsVisible
175+ visible: PanelState.buttonsVisible && !root.locked
174 onClose: PanelState.close()
176 onClose: PanelState.close()
175 onMinimize: PanelState.minimize()
177 onMinimize: PanelState.minimize()
176 onMaximize: PanelState.maximize()
178 onMaximize: PanelState.maximize()
@@ -189,7 +191,7 @@ Item {
189 id: __callHint
191 id: __callHint
190 anchors {
192 anchors {
191 top: parent.top
193 top: parent.top
192− left: PanelState.buttonsVisible ? windowControlButtons.right : parent.left
194+ left: windowControlButtons.visible ? windowControlButtons.right : parent.left
193 }
195 }
194 height: indicators.minimizedPanelHeight
196 height: indicators.minimizedPanelHeight
195 visible: active && indicators.state == "initial"
197 visible: active && indicators.state == "initial"
qml/Shell.qml+7−11
@@ -49,14 +49,14 @@ Item {
49 // to be set from outside
49 // to be set from outside
50 property int orientationAngle: 0
50 property int orientationAngle: 0
51 property int orientation
51 property int orientation
52− property int primaryOrientation
52+ property Orientations orientations
53− property int nativeOrientation
54 property real nativeWidth
53 property real nativeWidth
55 property real nativeHeight
54 property real nativeHeight
56 property alias indicatorAreaShowProgress: panel.indicatorAreaShowProgress
55 property alias indicatorAreaShowProgress: panel.indicatorAreaShowProgress
57 property bool beingResized
56 property bool beingResized
58 property string usageScenario: "phone" // supported values: "phone", "tablet" or "desktop"
57 property string usageScenario: "phone" // supported values: "phone", "tablet" or "desktop"
59 property string mode: "full-greeter"
58 property string mode: "full-greeter"
59+ property bool cursorVisible: false
60 function updateFocusedAppOrientation() {
60 function updateFocusedAppOrientation() {
61 applicationsDisplayLoader.item.updateFocusedAppOrientation();
61 applicationsDisplayLoader.item.updateFocusedAppOrientation();
62 }
62 }
@@ -298,18 +298,13 @@ Item {
298 }
298 }
299 Binding {
299 Binding {
300 target: applicationsDisplayLoader.item
300 target: applicationsDisplayLoader.item
301− property: "background"
301+ property: "orientations"
302− value: wallpaperResolver.background
302+ value: shell.orientations
303 }
303 }
304 Binding {
304 Binding {
305 target: applicationsDisplayLoader.item
305 target: applicationsDisplayLoader.item
306− property: "shellPrimaryOrientation"
306+ property: "background"
307− value: shell.primaryOrientation
307+ value: wallpaperResolver.background
308− }
309− Binding {
310− target: applicationsDisplayLoader.item
311− property: "nativeOrientation"
312− value: shell.nativeOrientation
313 }
308 }
314 Binding {
309 Binding {
315 target: applicationsDisplayLoader.item
310 target: applicationsDisplayLoader.item
@@ -536,6 +531,7 @@ Item {
536
531
537 fullscreenMode: (topmostApplicationIsFullscreen && !lightDM.greeter.active && launcher.progress == 0)
532 fullscreenMode: (topmostApplicationIsFullscreen && !lightDM.greeter.active && launcher.progress == 0)
538 || greeter.hasLockedApp
533 || greeter.hasLockedApp
534+ locked: greeter && greeter.active
539 }
535 }
540
536
541 Launcher {
537 Launcher {
qml/Stages/AbstractStage.qmlnew file+63
@@ -0,0 +1,63 @@
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.4
18+import Ubuntu.Components 1.3
19+import GSettings 1.0
20+
21+Rectangle {
22+ id: root
23+
24+ color: "#111111"
25+
26+ // Controls to be set from outside
27+ property bool altTabPressed
28+ property url background
29+ property bool beingResized
30+ property int dragAreaWidth
31+ property bool interactive
32+ property real inverseProgress // This is the progress for left edge drags, in pixels.
33+ property bool keepDashRunning: true
34+ property real maximizedAppTopMargin
35+ property real nativeHeight
36+ property real nativeWidth
37+ property QtObject orientations
38+ property int shellOrientation
39+ property int shellOrientationAngle
40+ property bool spreadEnabled: true // If false, animations and right edge will be disabled
41+ property bool suspended
42+
43+ // To be read from outside
44+ property var mainApp: null
45+ property int mainAppWindowOrientationAngle
46+ property bool orientationChangesEnabled
47+
48+ // Shared code for use in stage implementations
49+ GSettings {
50+ id: lifecycleExceptions
51+ schema.id: "com.canonical.qtmir"
52+ }
53+
54+ function isExemptFromLifecycle(appId) {
55+ var shortAppId = appId.split('_')[0];
56+ for (var i = 0; i < lifecycleExceptions.lifecycleExemptAppids.length; i++) {
57+ if (shortAppId === lifecycleExceptions.lifecycleExemptAppids[i]) {
58+ return true;
59+ }
60+ }
61+ return false;
62+ }
63+}
qml/Stages/DesktopStage.qml+14−24
@@ -22,39 +22,21 @@ import Ubuntu.Components 1.1
22import Unity.Application 0.1
22import Unity.Application 0.1
23import "../Components"
23import "../Components"
24import "../Components/PanelState"
24import "../Components/PanelState"
25+import "../Components"
25import Utils 0.1
26import Utils 0.1
26import Ubuntu.Gestures 0.1
27import Ubuntu.Gestures 0.1
27
28
28−Rectangle {
29+AbstractStage {
29 id: root
30 id: root
30 anchors.fill: parent
31 anchors.fill: parent
31
32
32− // Controls to be set from outside
33− property int dragAreaWidth // just to comply with the interface shared between stages
34− property real maximizedAppTopMargin
35− property bool interactive
36− property bool spreadEnabled // just to comply with the interface shared between stages
37− property real inverseProgress: 0 // just to comply with the interface shared between stages
38− property int shellOrientationAngle: 0
39− property int shellOrientation
40− property int shellPrimaryOrientation
41− property int nativeOrientation
42− property bool beingResized: false
43− property bool keepDashRunning: true
44− property bool suspended: false
45− property alias background: wallpaper.source
46− property alias altTabPressed: spread.altTabPressed
47−
48 // functions to be called from outside
33 // functions to be called from outside
49 function updateFocusedAppOrientation() { /* TODO */ }
34 function updateFocusedAppOrientation() { /* TODO */ }
50 function updateFocusedAppOrientationAnimated() { /* TODO */}
35 function updateFocusedAppOrientationAnimated() { /* TODO */}
51
36
52− // To be read from outside
53− readonly property var mainApp: ApplicationManager.focusedApplicationId
37+ mainApp: ApplicationManager.focusedApplicationId
54 ? ApplicationManager.findApplication(ApplicationManager.focusedApplicationId)
38 ? ApplicationManager.findApplication(ApplicationManager.focusedApplicationId)
55 : null
39 : null
56− property int mainAppWindowOrientationAngle: 0
57− readonly property bool orientationChangesEnabled: false
58
40
59 Connections {
41 Connections {
60 target: ApplicationManager
42 target: ApplicationManager
@@ -111,6 +93,7 @@ Rectangle {
111 property: "buttonsVisible"
93 property: "buttonsVisible"
112 value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.state === "maximized"
94 value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.state === "maximized"
113 }
95 }
96+ Component.onDestruction: PanelState.buttonsVisible = false;
114
97
115 FocusScope {
98 FocusScope {
116 id: appContainer
99 id: appContainer
@@ -121,6 +104,7 @@ Rectangle {
121 CrossFadeImage {
104 CrossFadeImage {
122 id: wallpaper
105 id: wallpaper
123 anchors.fill: parent
106 anchors.fill: parent
107+ source: root.background
124 sourceSize { height: root.height; width: root.width }
108 sourceSize { height: root.height; width: root.width }
125 fillMode: Image.PreserveAspectCrop
109 fillMode: Image.PreserveAspectCrop
126 }
110 }
@@ -140,6 +124,7 @@ Rectangle {
140
124
141 property bool maximized: false
125 property bool maximized: false
142 property bool minimized: false
126 property bool minimized: false
127+ property bool animationsEnabled: true
143
128
144 onFocusChanged: {
129 onFocusChanged: {
145 if (focus && ApplicationManager.focusedApplicationId !== model.appId) {
130 if (focus && ApplicationManager.focusedApplicationId !== model.appId) {
@@ -158,15 +143,18 @@ Rectangle {
158 value: ApplicationInfoInterface.RequestedRunning // Always running for now
143 value: ApplicationInfoInterface.RequestedRunning // Always running for now
159 }
144 }
160
145
161− function maximize() {
146+ function maximize(animated) {
147+ animationsEnabled = (animated === undefined) || animated;
162 minimized = false;
148 minimized = false;
163 maximized = true;
149 maximized = true;
164 }
150 }
165− function minimize() {
151+ function minimize(animated) {
152+ animationsEnabled = (animated === undefined) || animated;
166 maximized = false;
153 maximized = false;
167 minimized = true;
154 minimized = true;
168 }
155 }
169− function unmaximize() {
156+ function unmaximize(animated) {
157+ animationsEnabled = (animated === undefined) || animated;
170 minimized = false;
158 minimized = false;
171 maximized = false;
159 maximized = false;
172 }
160 }
@@ -188,6 +176,7 @@ Rectangle {
188 Transition {
176 Transition {
189 from: "maximized,minimized,normal,"
177 from: "maximized,minimized,normal,"
190 to: "maximized,minimized,normal,"
178 to: "maximized,minimized,normal,"
179+ enabled: appDelegate.animationsEnabled
191 PropertyAnimation { target: appDelegate; properties: "x,y,opacity,width,height,scale" }
180 PropertyAnimation { target: appDelegate; properties: "x,y,opacity,width,height,scale" }
192 },
181 },
193 Transition {
182 Transition {
@@ -269,5 +258,6 @@ Rectangle {
269 anchors.fill: parent
258 anchors.fill: parent
270 workspace: appContainer
259 workspace: appContainer
271 focus: state == "altTab"
260 focus: state == "altTab"
261+ altTabPressed: root.altTabPressed
272 }
262 }
273}
263}
qml/Stages/PhoneStage.qml+21−34
@@ -22,29 +22,15 @@ import Unity.Session 0.1
22import Utils 0.1
22import Utils 0.1
23import "../Components"
23import "../Components"
24
24
25−Rectangle {
25+AbstractStage {
26 id: root
26 id: root
27
27
28− // Controls to be set from outside
29− property int dragAreaWidth
30− property real maximizedAppTopMargin
31− property bool interactive
32− property bool spreadEnabled: true // If false, animations and right edge will be disabled
33− property real inverseProgress: 0 // This is the progress for left edge drags, in pixels.
34 property QtObject applicationManager: ApplicationManager
28 property QtObject applicationManager: ApplicationManager
35 property bool focusFirstApp: true // If false, focused app will appear on right edge like other apps
29 property bool focusFirstApp: true // If false, focused app will appear on right edge like other apps
36 property bool altTabEnabled: true
30 property bool altTabEnabled: true
37 property real startScale: 1.1
31 property real startScale: 1.1
38 property real endScale: 0.7
32 property real endScale: 0.7
39− property bool keepDashRunning: true
33+
40− property bool suspended: false
41− property int shellOrientationAngle: 0
42− property int shellOrientation
43− property int shellPrimaryOrientation
44− property int nativeOrientation
45− property real nativeWidth
46− property real nativeHeight
47− property bool beingResized: false
48 onBeingResizedChanged: {
34 onBeingResizedChanged: {
49 if (beingResized) {
35 if (beingResized) {
50 // Brace yourselves for impact!
36 // Brace yourselves for impact!
@@ -56,6 +42,8 @@ Rectangle {
56 priv.reset();
42 priv.reset();
57 }
43 }
58 }
44 }
45+
46+ // Functions to be called from outside
59 function updateFocusedAppOrientation() {
47 function updateFocusedAppOrientation() {
60 if (spreadRepeater.count > 0) {
48 if (spreadRepeater.count > 0) {
61 spreadRepeater.itemAt(0).matchShellOrientation();
49 spreadRepeater.itemAt(0).matchShellOrientation();
@@ -71,7 +59,7 @@ Rectangle {
71
59
72 var supportedOrientations = spreadDelegate.application.supportedOrientations;
60 var supportedOrientations = spreadDelegate.application.supportedOrientations;
73 if (supportedOrientations === Qt.PrimaryOrientation) {
61 if (supportedOrientations === Qt.PrimaryOrientation) {
74− supportedOrientations = spreadDelegate.shellPrimaryOrientation;
62+ supportedOrientations = root.orientations.primary;
75 }
63 }
76
64
77 if (delta === 180 && (supportedOrientations & spreadDelegate.shellOrientation)) {
65 if (delta === 180 && (supportedOrientations & spreadDelegate.shellOrientation)) {
@@ -85,16 +73,14 @@ Rectangle {
85 }
73 }
86 }
74 }
87
75
88− // To be read from outside
89− readonly property var mainApp: applicationManager.focusedApplicationId
76+ mainApp: applicationManager.focusedApplicationId
90 ? applicationManager.findApplication(applicationManager.focusedApplicationId)
77 ? applicationManager.findApplication(applicationManager.focusedApplicationId)
91 : null
78 : null
92
79
93− property int mainAppWindowOrientationAngle: 0
94− readonly property bool orientationChangesEnabled: priv.focusedAppOrientationChangesEnabled
80+ orientationChangesEnabled: priv.focusedAppOrientationChangesEnabled
95− && !priv.focusedAppDelegateIsDislocated
81+ && !priv.focusedAppDelegateIsDislocated
96− && !(priv.focusedAppDelegate && priv.focusedAppDelegate.xBehavior.running)
82+ && !(priv.focusedAppDelegate && priv.focusedAppDelegate.xBehavior.running)
97− && spreadView.phase === 0
83+ && spreadView.phase === 0
98
84
99 // How far left the stage has been dragged
85 // How far left the stage has been dragged
100 readonly property real dragProgress: spreadRepeater.count > 0 ? -spreadRepeater.itemAt(0).xTranslate : 0
86 readonly property real dragProgress: spreadRepeater.count > 0 ? -spreadRepeater.itemAt(0).xTranslate : 0
@@ -106,8 +92,6 @@ Rectangle {
106
92
107 signal opened()
93 signal opened()
108
94
109− color: "#111111"
110−
111 function select(appId) {
95 function select(appId) {
112 spreadView.snapTo(priv.indexOf(appId));
96 spreadView.snapTo(priv.indexOf(appId));
113 }
97 }
@@ -438,16 +422,21 @@ Rectangle {
438 dropShadow: spreadView.active || priv.focusedAppDelegateIsDislocated
422 dropShadow: spreadView.active || priv.focusedAppDelegateIsDislocated
439 focusFirstApp: root.focusFirstApp
423 focusFirstApp: root.focusFirstApp
440
424
425+ readonly property bool isDash: model.appId == "unity8-dash"
426+
427+ readonly property bool canSuspend: model.isTouchApp
428+ && !isExemptFromLifecycle(model.appId)
429+
441 Binding {
430 Binding {
442 target: appDelegate.application
431 target: appDelegate.application
443 property: "requestedState"
432 property: "requestedState"
433+ value: !canSuspend
434+ || (isDash && root.keepDashRunning)
444− value: (isDash && root.keepDashRunning) || (!root.suspended && appDelegate.focus)
435+ || (!root.suspended && appDelegate.focus)
445− ? ApplicationInfoInterface.RequestedRunning
436+ ? ApplicationInfoInterface.RequestedRunning
446− : ApplicationInfoInterface.RequestedSuspended
437+ : ApplicationInfoInterface.RequestedSuspended
447 }
438 }
448
439
449− readonly property bool isDash: model.appId == "unity8-dash"
450−
451 z: isDash && !spreadView.active ? -1 : behavioredIndex
440 z: isDash && !spreadView.active ? -1 : behavioredIndex
452
441
453 x: {
442 x: {
@@ -535,11 +524,9 @@ Rectangle {
535 visible: (progress >= 0 && progress < 1.7)
524 visible: (progress >= 0 && progress < 1.7)
536 || (isDash && priv.focusedAppDelegateIsDislocated)
525 || (isDash && priv.focusedAppDelegateIsDislocated)
537
526
538−
539 shellOrientationAngle: root.shellOrientationAngle
527 shellOrientationAngle: root.shellOrientationAngle
540 shellOrientation: root.shellOrientation
528 shellOrientation: root.shellOrientation
541− shellPrimaryOrientation: root.shellPrimaryOrientation
529+ orientations: root.orientations
542− nativeOrientation: root.nativeOrientation
543
530
544 onClicked: {
531 onClicked: {
545 if (root.altTabEnabled && spreadView.phase == 2) {
532 if (root.altTabEnabled && spreadView.phase == 2) {
qml/Stages/ShimStage.qml+1−1
@@ -17,7 +17,7 @@
17import QtQuick 2.3
17import QtQuick 2.3
18import Ubuntu.Components 0.1
18import Ubuntu.Components 0.1
19
19
20−Rectangle {
20+AbstractStage {
21 id: shimStage
21 id: shimStage
22
22
23 anchors.fill: parent
23 anchors.fill: parent
qml/Stages/SpreadDelegate.qml+8−9
@@ -42,8 +42,7 @@ FocusScope {
42 property alias application: appWindow.application
42 property alias application: appWindow.application
43 property int shellOrientationAngle
43 property int shellOrientationAngle
44 property int shellOrientation
44 property int shellOrientation
45− property int shellPrimaryOrientation
45+ property QtObject orientations
46− property int nativeOrientation
47
46
48 function matchShellOrientation() {
47 function matchShellOrientation() {
49 if (!root.application)
48 if (!root.application)
@@ -157,7 +156,7 @@ FocusScope {
157 var supportedOrientations = root.application.supportedOrientations;
156 var supportedOrientations = root.application.supportedOrientations;
158
157
159 if (supportedOrientations === Qt.PrimaryOrientation) {
158 if (supportedOrientations === Qt.PrimaryOrientation) {
160− supportedOrientations = root.shellPrimaryOrientation;
159+ supportedOrientations = root.orientations.primary;
161 }
160 }
162
161
163 // If it doesn't support shell's current orientation
162 // If it doesn't support shell's current orientation
@@ -166,18 +165,18 @@ FocusScope {
166 if (supportedOrientations & root.shellOrientation) {
165 if (supportedOrientations & root.shellOrientation) {
167 chosenOrientation = root.shellOrientation;
166 chosenOrientation = root.shellOrientation;
168 } else if (supportedOrientations & Qt.PortraitOrientation) {
167 } else if (supportedOrientations & Qt.PortraitOrientation) {
169− chosenOrientation = Qt.PortraitOrientation;
168+ chosenOrientation = root.orientations.portrait;
170 } else if (supportedOrientations & Qt.LandscapeOrientation) {
169 } else if (supportedOrientations & Qt.LandscapeOrientation) {
171− chosenOrientation = Qt.LandscapeOrientation;
170+ chosenOrientation = root.orientations.landscape;
172 } else if (supportedOrientations & Qt.InvertedPortraitOrientation) {
171 } else if (supportedOrientations & Qt.InvertedPortraitOrientation) {
173− chosenOrientation = Qt.InvertedPortraitOrientation;
172+ chosenOrientation = root.orientations.invertedPortrait;
174 } else if (supportedOrientations & Qt.InvertedLandscapeOrientation) {
173 } else if (supportedOrientations & Qt.InvertedLandscapeOrientation) {
175− chosenOrientation = Qt.InvertedLandscapeOrientation;
174+ chosenOrientation = root.orientations.invertedLandscape;
176 } else {
175 } else {
177− chosenOrientation = root.shellPrimaryOrientation;
176+ chosenOrientation = root.orientations.primary;
178 }
177 }
179
178
180− return Screen.angleBetween(root.nativeOrientation, chosenOrientation);
179+ return Screen.angleBetween(root.orientations.native_, chosenOrientation);
181 }
180 }
182
181
183 rotation: normalizeAngle(appWindowWithShadow.orientationAngle - root.shellOrientationAngle)
182 rotation: normalizeAngle(appWindowWithShadow.orientationAngle - root.shellOrientationAngle)
qml/Stages/TabletStage.qml+24−36
@@ -21,29 +21,12 @@ import Unity.Application 0.1
21import Utils 0.1
21import Utils 0.1
22import "../Components"
22import "../Components"
23
23
24−Rectangle {
24+AbstractStage {
25 id: root
25 id: root
26 objectName: "stages"
26 objectName: "stages"
27 anchors.fill: parent
27 anchors.fill: parent
28− color: "#111111"
28+
29−
30− // Controls to be set from outside
29+ // Functions to be called from outside
31− property int dragAreaWidth
32− property real maximizedAppTopMargin
33− property bool interactive
34− property alias beingResized: spreadView.beingResized
35−
36− property bool spreadEnabled: true // If false, animations and right edge will be disabled
37−
38− property real inverseProgress: 0 // This is the progress for left edge drags, in pixels.
39− property bool keepDashRunning: true
40− property bool suspended: false
41− property int shellOrientationAngle: 0
42− property int shellOrientation
43− property int shellPrimaryOrientation
44− property int nativeOrientation
45− property real nativeWidth
46− property real nativeHeight
47 function updateFocusedAppOrientation() {
30 function updateFocusedAppOrientation() {
48 var mainStageAppIndex = priv.indexOf(priv.mainStageAppId);
31 var mainStageAppIndex = priv.indexOf(priv.mainStageAppId);
49 if (mainStageAppIndex >= 0 && mainStageAppIndex < spreadRepeater.count) {
32 if (mainStageAppIndex >= 0 && mainStageAppIndex < spreadRepeater.count) {
@@ -64,7 +47,7 @@ Rectangle {
64
47
65 var supportedOrientations = spreadDelegate.application.supportedOrientations;
48 var supportedOrientations = spreadDelegate.application.supportedOrientations;
66 if (supportedOrientations === Qt.PrimaryOrientation) {
49 if (supportedOrientations === Qt.PrimaryOrientation) {
67− supportedOrientations = spreadDelegate.shellPrimaryOrientation;
50+ supportedOrientations = spreadDelegate.orientations.primary;
68 }
51 }
69
52
70 if (delta === 180 && (supportedOrientations & spreadDelegate.shellOrientation)) {
53 if (delta === 180 && (supportedOrientations & spreadDelegate.shellOrientation)) {
@@ -86,10 +69,7 @@ Rectangle {
86 }
69 }
87 }
70 }
88
71
89− // To be read from outside
90− property var mainApp: null
91− property int mainAppWindowOrientationAngle: 0
92− readonly property bool orientationChangesEnabled: priv.mainAppOrientationChangesEnabled
72+ orientationChangesEnabled: priv.mainAppOrientationChangesEnabled
93
73
94 onWidthChanged: {
74 onWidthChanged: {
95 spreadView.selectedIndex = -1;
75 spreadView.selectedIndex = -1;
@@ -130,7 +110,7 @@ Rectangle {
130 property string oldFocusedAppId: ""
110 property string oldFocusedAppId: ""
131 property bool mainAppOrientationChangesEnabled: false
111 property bool mainAppOrientationChangesEnabled: false
132
112
133− property real landscapeHeight: root.nativeOrientation == Qt.LandscapeOrientation ?
113+ property real landscapeHeight: root.orientations.native_ == Qt.LandscapeOrientation ?
134 root.nativeHeight : root.nativeWidth
114 root.nativeHeight : root.nativeWidth
135
115
136 property bool shellIsLandscape: root.shellOrientation === Qt.LandscapeOrientation
116 property bool shellIsLandscape: root.shellOrientation === Qt.LandscapeOrientation
@@ -302,7 +282,7 @@ Rectangle {
302 }
282 }
303
283
304 property bool animateX: true
284 property bool animateX: true
305− property bool beingResized: false
285+ property bool beingResized: root.beingResized
306 onBeingResizedChanged: {
286 onBeingResizedChanged: {
307 if (beingResized) {
287 if (beingResized) {
308 // Brace yourselves for impact!
288 // Brace yourselves for impact!
@@ -629,15 +609,18 @@ Rectangle {
629
609
630 readonly property bool isDash: model.appId == "unity8-dash"
610 readonly property bool isDash: model.appId == "unity8-dash"
631
611
612+ readonly property bool canSuspend: model.isTouchApp
613+ && !isExemptFromLifecycle(model.appId)
614+
632 Binding {
615 Binding {
633 target: spreadTile.application
616 target: spreadTile.application
634 property: "requestedState"
617 property: "requestedState"
618+ value: !canSuspend
635− value: (spreadTile.isDash && root.keepDashRunning)
619+ || (isDash && root.keepDashRunning)
636− ||
637− (!root.suspended && (model.appId == priv.mainStageAppId
620+ || (!root.suspended && (model.appId == priv.mainStageAppId
638− || model.appId == priv.sideStageAppId))
621+ || model.appId == priv.sideStageAppId))
639− ? ApplicationInfoInterface.RequestedRunning
622+ ? ApplicationInfoInterface.RequestedRunning
640− : ApplicationInfoInterface.RequestedSuspended
623+ : ApplicationInfoInterface.RequestedSuspended
641 }
624 }
642
625
643 // FIXME: A regular binding doesn't update any more after closing an app.
626 // FIXME: A regular binding doesn't update any more after closing an app.
@@ -691,9 +674,14 @@ Rectangle {
691
674
692 shellOrientationAngle: wantsMainStage ? root.shellOrientationAngle : 0
675 shellOrientationAngle: wantsMainStage ? root.shellOrientationAngle : 0
693 shellOrientation: wantsMainStage ? root.shellOrientation : Qt.PortraitOrientation
676 shellOrientation: wantsMainStage ? root.shellOrientation : Qt.PortraitOrientation
677+ orientations: Orientations {
694− shellPrimaryOrientation: wantsMainStage ? root.shellPrimaryOrientation : Qt.PortraitOrientation
678+ primary: spreadTile.wantsMainStage ? root.orientations.primary : Qt.PortraitOrientation
695− nativeOrientation: wantsMainStage ? root.nativeOrientation : Qt.PortraitOrientation
679+ native_: spreadTile.wantsMainStage ? root.orientations.native_ : Qt.PortraitOrientation
696−
680+ portrait: root.orientations.portrait
681+ invertedPortrait: root.orientations.invertedPortrait
682+ landscape: root.orientations.landscape
683+ invertedLandscape: root.orientations.invertedLandscape
684+ }
697
685
698 onClicked: {
686 onClicked: {
699 if (spreadView.phase == 2) {
687 if (spreadView.phase == 2) {
qml/Stages/WindowResizeArea.qml+38−7
@@ -36,18 +36,49 @@ MouseArea {
36 property int minWidth: 0
36 property int minWidth: 0
37 property int minHeight: 0
37 property int minHeight: 0
38
38
39+ QtObject {
40+ id: priv
41+
42+ property int normalX: 0
43+ property int normalY: 0
44+ property int normalWidth: 0
45+ property int normalHeight: 0
46+
47+ function updateNormalGeometry() {
48+ if (root.target.state == "normal") {
49+ normalX = root.target.x
50+ normalY = root.target.y
51+ normalWidth = root.target.width
52+ normalHeight = root.target.height
53+ }
54+ }
55+ }
56+
57+ Connections {
58+ target: root.target
59+ onXChanged: priv.updateNormalGeometry();
60+ onYChanged: priv.updateNormalGeometry();
61+ onWidthChanged: priv.updateNormalGeometry();
62+ onHeightChanged: priv.updateNormalGeometry();
63+ }
64+
39 Component.onCompleted: {
65 Component.onCompleted: {
40− var windowState = windowStateStorage.getGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height))
66+ var windowGeometry = windowStateStorage.getGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height))
41− if (windowState !== undefined) {
67+ if (windowGeometry !== undefined) {
42− target.x = windowState.x
68+ target.x = windowGeometry.x
43− target.y = windowState.y
69+ target.y = windowGeometry.y
44− target.width = windowState.width
70+ target.width = windowGeometry.width
45− target.height = windowState.height
71+ target.height = windowGeometry.height
72+ }
73+ var windowState = windowStateStorage.getState(root.windowId, WindowStateStorage.WindowStateNormal)
74+ if (windowState === WindowStateStorage.WindowStateMaximized) {
75+ target.maximize(false)
46 }
76 }
47 }
77 }
48
78
49 Component.onDestruction: {
79 Component.onDestruction: {
50− windowStateStorage.saveGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height))
80+ windowStateStorage.saveState(root.windowId, target.state == "maximized" ? WindowStateStorage.WindowStateMaximized : WindowStateStorage.WindowStateNormal)
81+ windowStateStorage.saveGeometry(root.windowId, Qt.rect(priv.normalX, priv.normalY, priv.normalWidth, priv.normalHeight))
51 }
82 }
52
83
53 QtObject {
84 QtObject {
qml/graphics/applicationIcons/libreoffice@18.pngnew file

Binary file not shown.

tests/mocks/GSettings.1.0/fake_gsettings.cpp+31
@@ -75,6 +75,19 @@ void GSettingsControllerQml::setLockedOutTime(qint64 timestamp)
75 }
75 }
76}
76}
77
77
78+QStringList GSettingsControllerQml::lifecycleExemptAppids() const
79+{
80+ return m_lifecycleExemptAppids;
81+}
82+
83+void GSettingsControllerQml::setLifecycleExemptAppids(const QStringList &appIds)
84+{
85+ if (m_lifecycleExemptAppids != appIds) {
86+ m_lifecycleExemptAppids = appIds;
87+ Q_EMIT lifecycleExemptAppidsChanged(m_lifecycleExemptAppids);
88+ }
89+}
90+
78GSettingsSchemaQml::GSettingsSchemaQml(QObject *parent): QObject(parent) {
91GSettingsSchemaQml::GSettingsSchemaQml(QObject *parent): QObject(parent) {
79}
92}
80
93
@@ -114,6 +127,8 @@ GSettingsQml::GSettingsQml(QObject *parent)
114 this, &GSettingsQml::usageModeChanged);
127 this, &GSettingsQml::usageModeChanged);
115 connect(GSettingsControllerQml::instance(), &GSettingsControllerQml::lockedOutTimeChanged,
128 connect(GSettingsControllerQml::instance(), &GSettingsControllerQml::lockedOutTimeChanged,
116 this, &GSettingsQml::lockedOutTimeChanged);
129 this, &GSettingsQml::lockedOutTimeChanged);
130+ connect(GSettingsControllerQml::instance(), &GSettingsControllerQml::lifecycleExemptAppidsChanged,
131+ this, &GSettingsQml::lifecycleExemptAppidsChanged);
117}
132}
118
133
119GSettingsSchemaQml * GSettingsQml::schema() const {
134GSettingsSchemaQml * GSettingsQml::schema() const {
@@ -167,3 +182,19 @@ void GSettingsQml::setLockedOutTime(qint64 timestamp)
167 GSettingsControllerQml::instance()->setLockedOutTime(timestamp);
182 GSettingsControllerQml::instance()->setLockedOutTime(timestamp);
168 }
183 }
169}
184}
185+
186+QStringList GSettingsQml::lifecycleExemptAppids() const
187+{
188+ if (m_schema->id() == "com.canonical.qtmir") {
189+ return GSettingsControllerQml::instance()->lifecycleExemptAppids();
190+ } else {
191+ return QStringList();
192+ }
193+}
194+
195+void GSettingsQml::setLifecycleExemptAppids(const QStringList &appIds)
196+{
197+ if (m_schema->id() == "com.canonical.qtmir") {
198+ GSettingsControllerQml::instance()->setLifecycleExemptAppids(appIds);
199+ }
200+}
tests/mocks/GSettings.1.0/fake_gsettings.h+10
@@ -19,6 +19,7 @@
19
19
20#include <QList>
20#include <QList>
21#include <QObject>
21#include <QObject>
22+#include <QStringList>
22
23
23class GSettingsSchemaQml: public QObject
24class GSettingsSchemaQml: public QObject
24{
25{
@@ -48,6 +49,7 @@ class GSettingsQml: public QObject
48 Q_PROPERTY(QString pictureUri READ pictureUri WRITE setPictureUri NOTIFY pictureUriChanged)
49 Q_PROPERTY(QString pictureUri READ pictureUri WRITE setPictureUri NOTIFY pictureUriChanged)
49 Q_PROPERTY(QString usageMode READ usageMode WRITE setUsageMode NOTIFY usageModeChanged)
50 Q_PROPERTY(QString usageMode READ usageMode WRITE setUsageMode NOTIFY usageModeChanged)
50 Q_PROPERTY(qint64 lockedOutTime READ lockedOutTime WRITE setLockedOutTime NOTIFY lockedOutTimeChanged)
51 Q_PROPERTY(qint64 lockedOutTime READ lockedOutTime WRITE setLockedOutTime NOTIFY lockedOutTimeChanged)
52+ Q_PROPERTY(QStringList lifecycleExemptAppids READ lifecycleExemptAppids WRITE setLifecycleExemptAppids NOTIFY lifecycleExemptAppidsChanged)
51
53
52public:
54public:
53 GSettingsQml(QObject *parent = nullptr);
55 GSettingsQml(QObject *parent = nullptr);
@@ -56,16 +58,19 @@ public:
56 QString pictureUri() const;
58 QString pictureUri() const;
57 QString usageMode() const;
59 QString usageMode() const;
58 qint64 lockedOutTime() const;
60 qint64 lockedOutTime() const;
61+ QStringList lifecycleExemptAppids() const;
59
62
60 void setPictureUri(const QString &str);
63 void setPictureUri(const QString &str);
61 void setUsageMode(const QString &usageMode);
64 void setUsageMode(const QString &usageMode);
62 void setLockedOutTime(qint64 timestamp);
65 void setLockedOutTime(qint64 timestamp);
66+ void setLifecycleExemptAppids(const QStringList &appIds);
63
67
64Q_SIGNALS:
68Q_SIGNALS:
65 void schemaChanged();
69 void schemaChanged();
66 void pictureUriChanged(const QString&);
70 void pictureUriChanged(const QString&);
67 void usageModeChanged(const QString&);
71 void usageModeChanged(const QString&);
68 void lockedOutTimeChanged(qint64);
72 void lockedOutTimeChanged(qint64);
73+ void lifecycleExemptAppidsChanged(const QStringList &);
69
74
70private:
75private:
71 GSettingsSchemaQml* m_schema;
76 GSettingsSchemaQml* m_schema;
@@ -90,10 +95,14 @@ public:
90 qint64 lockedOutTime() const;
95 qint64 lockedOutTime() const;
91 Q_INVOKABLE void setLockedOutTime(qint64 timestamp);
96 Q_INVOKABLE void setLockedOutTime(qint64 timestamp);
92
97
98+ QStringList lifecycleExemptAppids() const;
99+ Q_INVOKABLE void setLifecycleExemptAppids(const QStringList &appIds);
100+
93Q_SIGNALS:
101Q_SIGNALS:
94 void pictureUriChanged(const QString&);
102 void pictureUriChanged(const QString&);
95 void usageModeChanged(const QString&);
103 void usageModeChanged(const QString&);
96 void lockedOutTimeChanged(qint64 timestamp);
104 void lockedOutTimeChanged(qint64 timestamp);
105+ void lifecycleExemptAppidsChanged(const QStringList&);
97
106
98private:
107private:
99 GSettingsControllerQml();
108 GSettingsControllerQml();
@@ -101,6 +110,7 @@ private:
101 QString m_pictureUri;
110 QString m_pictureUri;
102 QString m_usageMode;
111 QString m_usageMode;
103 qint64 m_lockedOutTime;
112 qint64 m_lockedOutTime;
113+ QStringList m_lifecycleExemptAppids;
104
114
105 static GSettingsControllerQml* s_controllerInstance;
115 static GSettingsControllerQml* s_controllerInstance;
106 QList<GSettingsQml *> m_registeredGSettings;
116 QList<GSettingsQml *> m_registeredGSettings;
tests/mocks/Unity/Application/ApplicationInfo.cpp+12
@@ -41,6 +41,7 @@ ApplicationInfo::ApplicationInfo(const QString &appId, QObject *parent)
41 Qt::InvertedLandscapeOrientation)
41 Qt::InvertedLandscapeOrientation)
42 , m_rotatesWindowContents(false)
42 , m_rotatesWindowContents(false)
43 , m_requestedState(RequestedRunning)
43 , m_requestedState(RequestedRunning)
44+ , m_isTouchApp(true)
44 , m_manualSurfaceCreation(false)
45 , m_manualSurfaceCreation(false)
45{
46{
46}
47}
@@ -58,6 +59,7 @@ ApplicationInfo::ApplicationInfo(QObject *parent)
58 Qt::InvertedLandscapeOrientation)
59 Qt::InvertedLandscapeOrientation)
59 , m_rotatesWindowContents(false)
60 , m_rotatesWindowContents(false)
60 , m_requestedState(RequestedRunning)
61 , m_requestedState(RequestedRunning)
62+ , m_isTouchApp(true)
61 , m_manualSurfaceCreation(false)
63 , m_manualSurfaceCreation(false)
62{
64{
63}
65}
@@ -245,6 +247,16 @@ void ApplicationInfo::setRequestedState(RequestedState value)
245 }
247 }
246}
248}
247
249
250+bool ApplicationInfo::isTouchApp() const
251+{
252+ return m_isTouchApp;
253+}
254+
255+void ApplicationInfo::setIsTouchApp(bool isTouchApp)
256+{
257+ m_isTouchApp = isTouchApp;
258+}
259+
248void ApplicationInfo::onSessionSurfaceChanged(MirSurface* surface)
260void ApplicationInfo::onSessionSurfaceChanged(MirSurface* surface)
249{
261{
250 if (surface != nullptr && m_state == Starting) {
262 if (surface != nullptr && m_state == Starting) {
tests/mocks/Unity/Application/ApplicationInfo.h+4
@@ -89,6 +89,9 @@ public:
89 bool manualSurfaceCreation() const { return m_manualSurfaceCreation; }
89 bool manualSurfaceCreation() const { return m_manualSurfaceCreation; }
90 void setManualSurfaceCreation(bool value);
90 void setManualSurfaceCreation(bool value);
91
91
92+ bool isTouchApp() const override;
93+ void setIsTouchApp(bool isTouchApp); // only in mock
94+
92public:
95public:
93 void setSession(Session* session);
96 void setSession(Session* session);
94 Session* session() const { return m_session; }
97 Session* session() const { return m_session; }
@@ -121,6 +124,7 @@ private:
121 Qt::ScreenOrientations m_supportedOrientations;
124 Qt::ScreenOrientations m_supportedOrientations;
122 bool m_rotatesWindowContents;
125 bool m_rotatesWindowContents;
123 RequestedState m_requestedState;
126 RequestedState m_requestedState;
127+ bool m_isTouchApp;
124
128
125 bool m_manualSurfaceCreation;
129 bool m_manualSurfaceCreation;
126};
130};
tests/mocks/Unity/Application/ApplicationManager.cpp+10
@@ -105,6 +105,8 @@ QVariant ApplicationManager::data(const QModelIndex& index, int role) const {
105 return app->state();
105 return app->state();
106 case RoleFocused:
106 case RoleFocused:
107 return app->focused();
107 return app->focused();
108+ case RoleIsTouchApp:
109+ return app->isTouchApp();
108 case RoleSession:
110 case RoleSession:
109 return QVariant::fromValue(app->session());
111 return QVariant::fromValue(app->session());
110 case RoleFullscreen:
112 case RoleFullscreen:
@@ -458,6 +460,14 @@ void ApplicationManager::buildListOfAvailableApplications()
458 application->setName("YouTube");
460 application->setName("YouTube");
459 application->setIconId("youtube");
461 application->setIconId("youtube");
460 m_availableApplications.append(application);
462 m_availableApplications.append(application);
463+
464+ application = new ApplicationInfo(this);
465+ application->setAppId("libreoffice");
466+ application->setName("LibreOffice");
467+ application->setIconId("libreoffice");
468+ application->setScreenshotId("libreoffice");
469+ application->setIsTouchApp(false);
470+ m_availableApplications.append(application);
461}
471}
462
472
463
473
tests/mocks/Unity/Application/ApplicationManager.h+1−1
@@ -43,7 +43,7 @@ class ApplicationManager : public ApplicationManagerInterface {
43 static ApplicationManager *singleton();
43 static ApplicationManager *singleton();
44
44
45 enum MoreRoles {
45 enum MoreRoles {
46− RoleSession = RoleFocused+1,
46+ RoleSession = RoleIsTouchApp+1,
47 RoleFullscreen,
47 RoleFullscreen,
48 };
48 };
49 enum Flag {
49 enum Flag {
tests/mocks/Unity/Launcher/MockLauncherModel.cpp+3
@@ -75,6 +75,9 @@ MockLauncherModel::MockLauncherModel(QObject* parent): LauncherModelInterface(pa
75 item = new MockLauncherItem("calendar-app", "/usr/share/applications/calendar-app.desktop","Calendar", "calendar", this);
75 item = new MockLauncherItem("calendar-app", "/usr/share/applications/calendar-app.desktop","Calendar", "calendar", this);
76 item->setPinned(true);
76 item->setPinned(true);
77 m_list.append(item);
77 m_list.append(item);
78+ item = new MockLauncherItem("libreoffice", "/usr/share/applications/libreoffice.desktop","Libre Office", "libreoffice", this);
79+ item->setPinned(true);
80+ m_list.append(item);
78}
81}
79
82
80MockLauncherModel::~MockLauncherModel()
83MockLauncherModel::~MockLauncherModel()
tests/mocks/Utils/CMakeLists.txt+2
@@ -18,6 +18,8 @@ set(QMLPLUGIN_SRC
18 ${CMAKE_SOURCE_DIR}/plugins/Utils/windowscreenshotprovider.cpp
18 ${CMAKE_SOURCE_DIR}/plugins/Utils/windowscreenshotprovider.cpp
19 ${CMAKE_SOURCE_DIR}/plugins/Utils/easingcurve.cpp
19 ${CMAKE_SOURCE_DIR}/plugins/Utils/easingcurve.cpp
20 ${CMAKE_SOURCE_DIR}/plugins/Utils/inputwatcher.cpp
20 ${CMAKE_SOURCE_DIR}/plugins/Utils/inputwatcher.cpp
21+ ${CMAKE_SOURCE_DIR}/plugins/Utils/applicationsfiltermodel.cpp
22+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
21 constants.cpp
23 constants.cpp
22 plugin.cpp
24 plugin.cpp
23 windowstatestorage.cpp
25 windowstatestorage.cpp
tests/mocks/Utils/plugin.cpp+2
@@ -35,6 +35,7 @@
35#include <windowkeysfilter.h>
35#include <windowkeysfilter.h>
36#include <windowscreenshotprovider.h>
36#include <windowscreenshotprovider.h>
37#include <easingcurve.h>
37#include <easingcurve.h>
38+#include <applicationsfiltermodel.h>
38
39
39static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
40static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
40{
41{
@@ -63,6 +64,7 @@ void FakeUtilsPlugin::registerTypes(const char *uri)
63 qmlRegisterType<InputWatcher>(uri, 0, 1, "InputWatcher");
64 qmlRegisterType<InputWatcher>(uri, 0, 1, "InputWatcher");
64 qmlRegisterSingletonType<Constants>(uri, 0, 1, "Constants", createConstants);
65 qmlRegisterSingletonType<Constants>(uri, 0, 1, "Constants", createConstants);
65 qmlRegisterType<ActiveFocusLogger>(uri, 0, 1, "ActiveFocusLogger");
66 qmlRegisterType<ActiveFocusLogger>(uri, 0, 1, "ActiveFocusLogger");
67+ qmlRegisterType<ApplicationsFilterModel>(uri, 0, 1, "ApplicationsFilterModel");
66}
68}
67
69
68void FakeUtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
70void FakeUtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
tests/mocks/Utils/windowstatestorage.cpp+17
@@ -44,3 +44,20 @@ QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defa
44 if (!m_geometry.contains(windowId)) return defaultValue;
44 if (!m_geometry.contains(windowId)) return defaultValue;
45 return m_geometry.value(windowId).toRect();
45 return m_geometry.value(windowId).toRect();
46}
46}
47+
48+void WindowStateStorage::clear()
49+{
50+ m_state.clear();
51+ m_geometry.clear();
52+}
53+
54+void WindowStateStorage::saveState(const QString &windowId, WindowState state)
55+{
56+ m_state[windowId] = state;
57+}
58+
59+WindowStateStorage::WindowState WindowStateStorage::getState(const QString &windowId, WindowStateStorage::WindowState defaultValue)
60+{
61+ if (!m_state.contains(windowId)) return defaultValue;
62+ return m_state.value(windowId);
63+}
tests/mocks/Utils/windowstatestorage.h+12
@@ -22,12 +22,23 @@ class WindowStateStorage: public QObject
22{
22{
23 Q_OBJECT
23 Q_OBJECT
24 Q_PROPERTY(QVariantMap geometry READ geometry WRITE setGeometry NOTIFY geometryChanged)
24 Q_PROPERTY(QVariantMap geometry READ geometry WRITE setGeometry NOTIFY geometryChanged)
25+ Q_ENUMS(WindowState)
25public:
26public:
27+ enum WindowState {
28+ WindowStateNormal,
29+ WindowStateMaximized
30+ };
26 WindowStateStorage(QObject *parent = 0);
31 WindowStateStorage(QObject *parent = 0);
27
32
33+ Q_INVOKABLE void saveState(const QString &windowId, WindowState state);
34+ Q_INVOKABLE WindowState getState(const QString &windowId, WindowState defaultValue);
35+
28 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect);
36 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect);
29 Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue);
37 Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue);
30
38
39+ // Only in the mock, to easily restore a fresh state
40+ Q_INVOKABLE void clear();
41+
31Q_SIGNALS:
42Q_SIGNALS:
32 void geometryChanged(const QVariantMap& geometry);
43 void geometryChanged(const QVariantMap& geometry);
33
44
@@ -35,5 +46,6 @@ private:
35 void setGeometry(const QVariantMap& geometry);
46 void setGeometry(const QVariantMap& geometry);
36 QVariantMap geometry() const;
47 QVariantMap geometry() const;
37
48
49+ QHash<QString, WindowState> m_state;
38 QVariantMap m_geometry;
50 QVariantMap m_geometry;
39};
51};
tests/plugins/Unity/Launcher/launchermodeltest.cpp+1
@@ -57,6 +57,7 @@ public:
57 QColor splashColorFooter() const override { return QColor(0,0,0,0); }
57 QColor splashColorFooter() const override { return QColor(0,0,0,0); }
58 Qt::ScreenOrientations supportedOrientations() const override { return Qt::PortraitOrientation; }
58 Qt::ScreenOrientations supportedOrientations() const override { return Qt::PortraitOrientation; }
59 bool rotatesWindowContents() const override { return false; }
59 bool rotatesWindowContents() const override { return false; }
60+ bool isTouchApp() const override { return true; }
60
61
61 // Methods used for mocking (not in the interface)
62 // Methods used for mocking (not in the interface)
62 void setFocused(bool focused) { m_focused = focused; Q_EMIT focusedChanged(focused); }
63 void setFocused(bool focused) { m_focused = focused; Q_EMIT focusedChanged(focused); }
tests/qmltests/CMakeLists.txt+1
@@ -11,6 +11,7 @@ add_unity8_qmltest(Components Dialogs LIGHTDM)
11add_unity8_qmltest(Components DraggingArea)
11add_unity8_qmltest(Components DraggingArea)
12add_unity8_qmltest(Components LazyImage)
12add_unity8_qmltest(Components LazyImage)
13add_unity8_qmltest(Components Lockscreen)
13add_unity8_qmltest(Components Lockscreen)
14+add_unity8_qmltest(Components ModeSwitchWarningDialog)
14add_unity8_qmlunittest(Components PhysicalKeysMapper)
15add_unity8_qmlunittest(Components PhysicalKeysMapper)
15add_unity8_qmltest(Components Rating)
16add_unity8_qmltest(Components Rating)
16add_unity8_qmltest(Components ResponsiveGridView)
17add_unity8_qmltest(Components ResponsiveGridView)
tests/qmltests/Components/tst_ModeSwitchWarningDialog.qmlnew file+75
@@ -0,0 +1,75 @@
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 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 "../../../qml/Components"
20+import Unity.Test 0.1 as UT
21+import Ubuntu.Components 1.2
22+import Ubuntu.Components.Popups 1.2
23+
24+Rectangle {
25+ id: root
26+ width: units.gu(40)
27+ height: units.gu(70)
28+ color: "lightgrey"
29+
30+ ListModel {
31+ id: fakeModel
32+ ListElement { icon: "../graphics/applicationIcons/facebook.png"; name: "Facebook" }
33+ ListElement { icon: "../graphics/applicationIcons/gmail.png"; name: "Mail" }
34+ }
35+
36+ function openWarning() {
37+ return PopupUtils.open(Qt.resolvedUrl("../../../qml/Components/ModeSwitchWarningDialog.qml"), root, { model: fakeModel });
38+ }
39+
40+ Button {
41+ anchors.centerIn: parent
42+ text: "trigger warning"
43+ onClicked: {
44+ openWarning();
45+ }
46+ }
47+
48+ SignalSpy {
49+ id: spy
50+ signalName: "forceClose"
51+ }
52+
53+ UT.UnityTestCase {
54+ name: "ModeSwitchWarning"
55+ when: windowShown
56+
57+ function test_labelChangesOnClick() {
58+ var popup = openWarning();
59+ var reconnectLabel = findChild(popup, "reconnectLabel");
60+ tryCompare(reconnectLabel, "text", reconnectLabel.notClickedText)
61+ mouseClick(reconnectLabel, reconnectLabel.width / 2, reconnectLabel.height / 2)
62+ tryCompare(reconnectLabel, "text", reconnectLabel.clickedText)
63+ }
64+
65+ function test_closeButtonEmitsSignal() {
66+ var popup = openWarning();
67+ spy.target = popup;
68+ spy.clear();
69+ var closeButton = findChild(popup, "forceCloseButton");
70+ mouseClick(closeButton, closeButton.width / 2, closeButton.height / 2)
71+ tryCompare(spy, "count", 1)
72+
73+ }
74+ }
75+}
tests/qmltests/Stages/tst_PhoneStage.qml−2
@@ -34,8 +34,6 @@ Item {
34 maximizedAppTopMargin: units.gu(3) + units.dp(2)
34 maximizedAppTopMargin: units.gu(3) + units.dp(2)
35 interactive: true
35 interactive: true
36 shellOrientation: Qt.PortraitOrientation
36 shellOrientation: Qt.PortraitOrientation
37− shellPrimaryOrientation: Qt.PortraitOrientation
38− nativeOrientation: Qt.PortraitOrientation
39 }
37 }
40
38
41 Binding {
39 Binding {
tests/qmltests/Stages/tst_SpreadDelegate.qml+6−2
@@ -18,6 +18,7 @@ import QtQuick 2.0
18import QtTest 1.0
18import QtTest 1.0
19import Unity.Test 0.1 as UT
19import Unity.Test 0.1 as UT
20import ".."
20import ".."
21+import "../../../qml/Components"
21import "../../../qml/Stages"
22import "../../../qml/Stages"
22import Ubuntu.Components 0.1
23import Ubuntu.Components 0.1
23import Ubuntu.Components.ListItems 1.0 as ListItem
24import Ubuntu.Components.ListItems 1.0 as ListItem
@@ -73,8 +74,11 @@ Rectangle {
73 return Qt.LandscapeOrientation;
74 return Qt.LandscapeOrientation;
74 }
75 }
75 }
76 }
76− shellPrimaryOrientation: Qt.PortraitOrientation
77+
77− nativeOrientation: Qt.PortraitOrientation
78+ orientations: Orientations {
79+ // the default values will do
80+ }
81+
78 maximizedAppTopMargin: units.gu(3)
82 maximizedAppTopMargin: units.gu(3)
79 Component.onDestruction: {
83 Component.onDestruction: {
80 spreadDelegateLoader.itemDestroyed = true;
84 spreadDelegateLoader.itemDestroyed = true;
tests/qmltests/Stages/tst_TabletStage.qml+2−2
@@ -22,6 +22,7 @@ import Unity.Application 0.1
22import Unity.Test 0.1
22import Unity.Test 0.1
23
23
24import "../../../qml/Stages"
24import "../../../qml/Stages"
25+import "../../../qml/Components"
25
26
26Rectangle {
27Rectangle {
27 id: root
28 id: root
@@ -50,10 +51,9 @@ Rectangle {
50 maximizedAppTopMargin: units.gu(3) + units.dp(2)
51 maximizedAppTopMargin: units.gu(3) + units.dp(2)
51 interactive: true
52 interactive: true
52 shellOrientation: Qt.LandscapeOrientation
53 shellOrientation: Qt.LandscapeOrientation
53− shellPrimaryOrientation: Qt.LandscapeOrientation
54− nativeOrientation: Qt.LandscapeOrientation
55 nativeWidth: width
54 nativeWidth: width
56 nativeHeight: height
55 nativeHeight: height
56+ orientations: Orientations{} // Defaults are fine for testing
57 focus: true
57 focus: true
58 }
58 }
59 }
59 }
tests/qmltests/Stages/tst_WindowResizeArea.qml+35−2
@@ -44,8 +44,11 @@ Item {
44 width: units.gu(20)
44 width: units.gu(20)
45 property int windowHeight: height
45 property int windowHeight: height
46 property int windowWidth: width
46 property int windowWidth: width
47− onWindowHeightChanged: height = windowHeight
47+ state: "normal"
48− onWindowWidthChanged: width = windowWidth
48+
49+ function maximize() {
50+ state = "maximized"
51+ }
49
52
50 WindowResizeArea {
53 WindowResizeArea {
51 id: windowResizeArea
54 id: windowResizeArea
@@ -195,5 +198,35 @@ Item {
195 tryCompare(fakeWindow, "width", initialWindowWidth);
198 tryCompare(fakeWindow, "width", initialWindowWidth);
196 tryCompare(fakeWindow, "height", initialWindowHeight);
199 tryCompare(fakeWindow, "height", initialWindowHeight);
197 }
200 }
201+
202+ function test_saveRestoreMaximized() {
203+ var initialWindowX = fakeWindow.x;
204+ var initialWindowY = fakeWindow.y;
205+
206+ var moveDelta = units.gu(5);
207+
208+ fakeWindow.x = initialWindowX + moveDelta
209+ fakeWindow.y = initialWindowY + moveDelta
210+
211+ // Now change the state to maximized. The window should not keep updating the stored values
212+ fakeWindow.state = "maximized"
213+ fakeWindow.x = 31415 // 0 is too risky to pass the test even when broken
214+ fakeWindow.y = 31415
215+
216+ // This will destroy the window and recreate it
217+ windowLoader.active = false;
218+ waitForRendering(root);
219+ windowLoader.active = true;
220+
221+ // Make sure it's again where we left it in normal state before destroying
222+ tryCompare(fakeWindow, "x", initialWindowX + moveDelta)
223+ tryCompare(fakeWindow, "y", initialWindowX + moveDelta)
224+
225+ // Make sure maximize() has been called after restoring
226+ tryCompare(fakeWindow, "state", "maximized")
227+
228+ // clean up
229+ fakeWindow.state = "normal"
230+ }
198 }
231 }
199}
232}
tests/qmltests/tst_OrientedShell.qml+1−1
@@ -840,7 +840,7 @@ Rectangle {
840 // shell should remain in its primery orientation as the app in the main stage
840 // shell should remain in its primery orientation as the app in the main stage
841 // is the one that dictates its orientation. In this case it's unity8-dash
841 // is the one that dictates its orientation. In this case it's unity8-dash
842 // which supports only primary orientation
842 // which supports only primary orientation
843− compare(shell.orientation, orientedShell.primaryOrientation);
843+ compare(shell.orientation, orientedShell.orientations.primary);
844 }
844 }
845
845
846 function test_sideStageAppsRemainPortraitInSpread_data() {
846 function test_sideStageAppsRemainPortraitInSpread_data() {
tests/qmltests/tst_Shell.qml+179−2
@@ -29,8 +29,11 @@ import Unity.Notifications 1.0
29import Unity.Test 0.1
29import Unity.Test 0.1
30import Powerd 0.1
30import Powerd 0.1
31import Wizard 0.1 as Wizard
31import Wizard 0.1 as Wizard
32+import Utils 0.1
32
33
33import "../../qml"
34import "../../qml"
35+import "../../qml/Components"
36+import "../../qml/Components/PanelState"
34import "Stages"
37import "Stages"
35
38
36Rectangle {
39Rectangle {
@@ -97,8 +100,7 @@ Rectangle {
97 id: __shell
100 id: __shell
98 usageScenario: usageScenarioSelector.model[usageScenarioSelector.selectedIndex]
101 usageScenario: usageScenarioSelector.model[usageScenarioSelector.selectedIndex]
99 orientation: Qt.PortraitOrientation
102 orientation: Qt.PortraitOrientation
100− primaryOrientation: Qt.PortraitOrientation
103+ orientations: Orientations{} // Defaults are fine for testing
101− nativeOrientation: Qt.PortraitOrientation
102 Component.onDestruction: {
104 Component.onDestruction: {
103 shellLoader.itemDestroyed = true;
105 shellLoader.itemDestroyed = true;
104 }
106 }
@@ -302,6 +304,7 @@ Rectangle {
302 mouseEmulation.checked = true;
304 mouseEmulation.checked = true;
303 tryCompare(shell, "enabled", true); // make sure greeter didn't leave us in disabled state
305 tryCompare(shell, "enabled", true); // make sure greeter didn't leave us in disabled state
304 tearDown();
306 tearDown();
307+ WindowStateStorage.clear();
305 }
308 }
306
309
307 function loadShell(formFactor) {
310 function loadShell(formFactor) {
@@ -377,6 +380,8 @@ Rectangle {
377 LightDM.Greeter.authenticate(""); // reset greeter
380 LightDM.Greeter.authenticate(""); // reset greeter
378
381
379 sessionSpy.clear();
382 sessionSpy.clear();
383+
384+ GSettingsController.setLifecycleExemptAppids([]);
380 }
385 }
381
386
382 function killApps() {
387 function killApps() {
@@ -1707,5 +1712,177 @@ Rectangle {
1707
1712
1708 keyRelease(Qt.Key_Control);
1713 keyRelease(Qt.Key_Control);
1709 }
1714 }
1715+
1716+ // regression test for http://pad.lv/1443319
1717+ function test_closeMaximizedAndRestart() {
1718+ loadDesktopShellWithApps();
1719+
1720+ var appRepeater = findChild(shell, "appRepeater")
1721+ var appId = ApplicationManager.get(0).appId;
1722+ var appDelegate = appRepeater.itemAt(0);
1723+ var maximizeButton = findChild(appDelegate, "maximizeWindowButton");
1724+
1725+ wait(5000)
1726+ tryCompare(appDelegate, "state", "normal");
1727+ tryCompare(PanelState, "buttonsVisible", false)
1728+
1729+ wait(5000)
1730+ mouseClick(maximizeButton, maximizeButton.width / 2, maximizeButton.height / 2);
1731+ tryCompare(appDelegate, "state", "maximized");
1732+ tryCompare(PanelState, "buttonsVisible", true)
1733+
1734+ ApplicationManager.stopApplication(appId);
1735+ tryCompare(PanelState, "buttonsVisible", false)
1736+
1737+ ApplicationManager.startApplication(appId);
1738+ tryCompare(PanelState, "buttonsVisible", true)
1739+ }
1740+
1741+ // bug http://pad.lv/1431566
1742+ function test_switchToStagedHidesPanelButtons() {
1743+ loadDesktopShellWithApps();
1744+ var appRepeater = findChild(shell, "appRepeater")
1745+ var appId = ApplicationManager.get(0).appId;
1746+ var appDelegate = appRepeater.itemAt(0);
1747+ var panelButtons = findChild(shell, "panelWindowControlButtons")
1748+
1749+ tryCompare(appDelegate, "state", "normal");
1750+ tryCompare(panelButtons, "visible", false);
1751+
1752+ appDelegate.maximize(false);
1753+ tryCompare(panelButtons, "visible", true);
1754+
1755+ shell.usageScenario = "phone";
1756+ waitForRendering(shell);
1757+ tryCompare(panelButtons, "visible", false);
1758+
1759+ shell.usageScenario = "desktop";
1760+ waitForRendering(shell);
1761+ tryCompare(panelButtons, "visible", true);
1762+ }
1763+
1764+ function test_lockingGreeterHidesPanelButtons() {
1765+ loadDesktopShellWithApps();
1766+ var appRepeater = findChild(shell, "appRepeater")
1767+ var appId = ApplicationManager.get(0).appId;
1768+ var appDelegate = appRepeater.itemAt(0);
1769+ var panelButtons = findChild(shell, "panelWindowControlButtons")
1770+
1771+ tryCompare(appDelegate, "state", "normal");
1772+ tryCompare(panelButtons, "visible", false);
1773+
1774+ appDelegate.maximize(false);
1775+ tryCompare(panelButtons, "visible", true);
1776+
1777+ LightDM.Greeter.showGreeter();
1778+ waitForRendering(shell);
1779+ tryCompare(panelButtons, "visible", false);
1780+
1781+ LightDM.Greeter.hideGreeter();
1782+ waitForRendering(shell);
1783+ tryCompare(panelButtons, "visible", true);
1784+ }
1785+
1786+ function test_lifecyclePolicyForNonTouchApp_data() {
1787+ return [
1788+ {tag: "phone", formFactor: "phone", usageScenario: "phone"},
1789+ {tag: "tablet", formFactor: "tablet", usageScenario: "tablet"}
1790+ ]
1791+ }
1792+
1793+ function test_lifecyclePolicyForNonTouchApp(data) {
1794+ loadShell(data.formFactor);
1795+ shell.usageScenario = data.usageScenario;
1796+
1797+ var app1 = ApplicationManager.startApplication("libreoffice");
1798+ waitUntilAppWindowIsFullyLoaded(app1);
1799+ var app2 = ApplicationManager.startApplication("dialer-app");
1800+ waitUntilAppWindowIsFullyLoaded(app2);
1801+
1802+ // Make sure app1 is unfocused but still running
1803+ compare(app1.session.surface.activeFocus, false);
1804+ compare(app1.isTouchApp, false); // sanity check our mock, which sets this for us
1805+ compare(app1.requestedState, ApplicationInfoInterface.RequestedRunning);
1806+ }
1807+
1808+ function test_lifecyclePolicyExemption_data() {
1809+ return [
1810+ {tag: "phone", formFactor: "phone", usageScenario: "phone", suspendsApps: true},
1811+ {tag: "tablet", formFactor: "tablet", usageScenario: "tablet", suspendsApps: true}
1812+ ]
1813+ }
1814+
1815+ function test_lifecyclePolicyExemption(data) {
1816+ loadShell(data.formFactor);
1817+ shell.usageScenario = data.usageScenario;
1818+
1819+ GSettingsController.setLifecycleExemptAppids(["webbrowser-app"]);
1820+
1821+ var app1 = ApplicationManager.startApplication("webbrowser-app");
1822+ waitUntilAppWindowIsFullyLoaded(app1);
1823+ var app2 = ApplicationManager.startApplication("dialer-app");
1824+ waitUntilAppWindowIsFullyLoaded(app2);
1825+
1826+ // Make sure app1 is unfocused but still running
1827+ compare(app1.session.surface.activeFocus, false);
1828+ compare(app1.requestedState, ApplicationInfoInterface.RequestedRunning);
1829+ }
1830+
1831+ function test_switchToStagedForcesLegacyAppClosing_data() {
1832+ return [
1833+ {tag: "forceClose", replug: false },
1834+ {tag: "replug", replug: true }
1835+ ];
1836+ }
1837+
1838+ function test_switchToStagedForcesLegacyAppClosing(data) {
1839+ loadShell("desktop")
1840+ shell.usageScenario = "desktop"
1841+ waitForRendering(shell);
1842+
1843+ ApplicationManager.startApplication("camera-app")
1844+
1845+ shell.usageScenario = "phone"
1846+ waitForRendering(shell);
1847+
1848+ // No legacy app running yet... Popup must *not* show.
1849+ var popup = findChild(root, "modeSwitchWarningDialog");
1850+ compare(popup, null);
1851+
1852+ shell.usageScenario = "desktop"
1853+ waitForRendering(shell);
1854+
1855+ // Now start a legacy app
1856+ ApplicationManager.startApplication("libreoffice")
1857+
1858+ shell.usageScenario = "phone"
1859+ waitForRendering(shell);
1860+
1861+ // The popup must appear now
1862+ popup = findChild(root, "modeSwitchWarningDialog");
1863+ compare(popup !== null, true);
1864+
1865+ if (data.replug) {
1866+ shell.usageScenario = "desktop"
1867+ waitForRendering(shell);
1868+
1869+ } else {
1870+ var forceCloseButton = findChild(popup, "forceCloseButton");
1871+ mouseClick(forceCloseButton, forceCloseButton.width / 2, forceCloseButton.height / 2);
1872+ waitForRendering(root);
1873+ }
1874+
1875+ // Popup must be gone now
1876+ popup = findChild(root, "modeSwitchWarningDialog");
1877+ compare(popup === null, true);
1878+
1879+ if (data.replug) {
1880+ // Libreoffice must still be running
1881+ compare(ApplicationManager.findApplication("libreoffice") !== null, true);
1882+ } else {
1883+ // Libreoffice must be gone now
1884+ compare(ApplicationManager.findApplication("libreoffice") === null, true);
1885+ }
1886+ }
1710 }
1887 }
1711}
1888}