on xenial help sign in
merge el trunko
d49a396Andrea Cimitan
debian/changelog+30−21
@@ -1,31 +1,40 @@
1−unity8 (8.02+15.04.20150205-0ubuntu1) vivid; urgency=medium
1+unity8 (8.02+15.04.20150211-0ubuntu1) vivid; urgency=medium
2
2
3− [ Andrea Cimitan ]
3+ [ Michael Terry ]
4− * Fix continue button in wifi wizard page, adds qml-module-
4+ * Redesign tutorial to match latest spec (just removing obsolete pages
5− qtsysteminfo as unity8 dep (LP: #1363400)
5+ and redesigning look, no new pages yet)
6− * Background needs to be specified to be visible in horizontal cards
6+ * Add new right-edge and bottom-edge screens to the first-boot edge
7− (LP: #1411748)
7+ tutorial (LP: #1383297)
8−
8+ * Support switching the indicator profile on the fly, as the greeter
9− [ CI Train Bot ]
9+ appears and disappears. (But don't use it yet, not until the
10− * Resync trunk
10+ settings panel to control this is in place.)
11+ * Clip the infographic to its bounding box, instead of letting it
12+ bleed past the welcome page (visible when dragging the welcome page
13+ to the side).
11
14
12− [ Michał Sawicz ]
15+ [ Michael Zanetti ]
13− * Add workaround for gcc ICE.
16+ * Add an AccountService based launcher model to be used with split
17+ greeter
18+ * Launcher fixes for windowed mode
19+ * save and restore window size and position
20+ * Fix resizing windows when making it smaller than minSize and then
21+ larger again.
14
22
15− [ Albert Astals ]
23+ [ handsome_feng<445865575@qq.com> ]
16− * Implement proper updateRanges for horizontal items (i.e. Carousel,
24+ * makes the header fully reveal when tapping the search icon. (LP:
17− Horizontal List)
25+ #1379327)
18
26
19− [ Michael Terry ]
27+ [ Mirco Müller ]
20− * Don't accept multiple "Finish" clicks during the last step of the
28+ * Allow swipe-to-dismiss for contracted snap-decision notifications,
21− wizard
29+ interactive notifications and ephemeral notifications. (LP:
30+ #1355422, #1334855)
22
31
23 [ Daniel d'Andrada ]
32 [ Daniel d'Andrada ]
24− * Unify all liblightdm mocks
33+ * Make indicators bar eat all events
25− * Ensure the greeter password field is not covered by the keyboard
34+ * Improve Launcher tests, making them more reliable
26− * Don't show() the lockscreen if it's already being shown
35+ * Tapping on a surface gives it active focus
27
36
28− -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 05 Feb 2015 10:30:13 +0000
37+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Wed, 11 Feb 2015 17:13:16 +0000
29
38
30unity8 (8.02+15.04.20150123.3-0ubuntu1) vivid; urgency=low
39unity8 (8.02+15.04.20150123.3-0ubuntu1) vivid; urgency=low
31
40
debian/unity8-private.install+1
@@ -1,6 +1,7 @@
1usr/lib/*/libunity8-private.*
1usr/lib/*/libunity8-private.*
2usr/lib/*/unity8/qml/AccountsService
2usr/lib/*/unity8/qml/AccountsService
3usr/lib/*/unity8/qml/Dash
3usr/lib/*/unity8/qml/Dash
4+usr/lib/*/unity8/qml/Greeter
4usr/lib/*/unity8/qml/LightDM
5usr/lib/*/unity8/qml/LightDM
5usr/lib/*/unity8/qml/Lights
6usr/lib/*/unity8/qml/Lights
6usr/lib/*/unity8/qml/Powerd
7usr/lib/*/unity8/qml/Powerd
debian/unity8.install+1
@@ -10,5 +10,6 @@ usr/share/unity8/Launcher
10usr/share/unity8/Panel
10usr/share/unity8/Panel
11usr/share/unity8/Shell.qml
11usr/share/unity8/Shell.qml
12usr/share/unity8/Stages
12usr/share/unity8/Stages
13+usr/share/unity8/Tutorial
13usr/share/unity8/Wizard
14usr/share/unity8/Wizard
14usr/share/url-dispatcher/urls/unity8-dash.url-dispatcher
15usr/share/url-dispatcher/urls/unity8-dash.url-dispatcher
plugins/AccountsService/AccountsServiceDBusAdaptor.cpp+9
@@ -57,6 +57,15 @@ void AccountsServiceDBusAdaptor::setUserProperty(const QString &user, const QStr
57 }
57 }
58}
58}
59
59
60+void AccountsServiceDBusAdaptor::setUserPropertyAsync(const QString &user, const QString &interface, const QString &property, const QVariant &value)
61+{
62+ QDBusInterface *iface = getUserInterface(user);
63+ if (iface != nullptr && iface->isValid()) {
64+ // The value needs to be carefully wrapped
65+ iface->asyncCall("Set", interface, property, QVariant::fromValue(QDBusVariant(value)));
66+ }
67+}
68+
60void AccountsServiceDBusAdaptor::propertiesChangedSlot(const QString &interface, const QVariantMap &changed, const QStringList &invalid)
69void AccountsServiceDBusAdaptor::propertiesChangedSlot(const QString &interface, const QVariantMap &changed, const QStringList &invalid)
61{
70{
62 // Merge changed and invalidated together
71 // Merge changed and invalidated together
plugins/AccountsService/AccountsServiceDBusAdaptor.h+12
@@ -19,6 +19,7 @@
19#ifndef UNITY_ACCOUNTSSERVICEDBUSADAPTOR_H
19#ifndef UNITY_ACCOUNTSSERVICEDBUSADAPTOR_H
20#define UNITY_ACCOUNTSSERVICEDBUSADAPTOR_H
20#define UNITY_ACCOUNTSSERVICEDBUSADAPTOR_H
21
21
22+#include <QDBusArgument>
22#include <QDBusContext>
23#include <QDBusContext>
23#include <QDBusInterface>
24#include <QDBusInterface>
24#include <QMap>
25#include <QMap>
@@ -33,7 +34,18 @@ public:
33 explicit AccountsServiceDBusAdaptor(QObject *parent = 0);
34 explicit AccountsServiceDBusAdaptor(QObject *parent = 0);
34
35
35 Q_INVOKABLE QVariant getUserProperty(const QString &user, const QString &interface, const QString &property);
36 Q_INVOKABLE QVariant getUserProperty(const QString &user, const QString &interface, const QString &property);
37+
38+ template <typename T>
39+ inline T getUserProperty(const QString &user, const QString &interface, const QString &property) {
40+ QVariant variant = getUserProperty(user, interface, property);
41+ if (variant.isValid() && variant.canConvert<QDBusArgument>()) {
42+ return qdbus_cast<T>(variant.value<QDBusArgument>());
43+ }
44+ return T();
45+ }
46+
36 Q_INVOKABLE void setUserProperty(const QString &user, const QString &interface, const QString &property, const QVariant &value);
47 Q_INVOKABLE void setUserProperty(const QString &user, const QString &interface, const QString &property, const QVariant &value);
48+ Q_INVOKABLE void setUserPropertyAsync(const QString &user, const QString &interface, const QString &property, const QVariant &value);
37
49
38Q_SIGNALS:
50Q_SIGNALS:
39 void propertiesChanged(const QString &user, const QString &interface, const QStringList &changed);
51 void propertiesChanged(const QString &user, const QString &interface, const QStringList &changed);
plugins/AccountsService/com.canonical.unity.AccountsService.xml+1−2
@@ -14,8 +14,7 @@
14 <annotation name="org.freedesktop.Accounts.DefaultValue" value="true"/>
14 <annotation name="org.freedesktop.Accounts.DefaultValue" value="true"/>
15 </property>
15 </property>
16
16
17− <!-- Should have been named LauncherItems, sorry folks. -mterry -->
18− <property name="launcher-items" type="aa{sv}" access="readwrite">
17+ <property name="LauncherItems" type="aa{sv}" access="readwrite">
19 <annotation name="org.freedesktop.Accounts.DefaultValue" value="[{'defaults': <true>}]"/>
18 <annotation name="org.freedesktop.Accounts.DefaultValue" value="[{'defaults': <true>}]"/>
20 </property>
19 </property>
21
20
plugins/CMakeLists.txt+1
@@ -12,6 +12,7 @@ macro(add_unity8_plugin PLUGIN VERSION PATH)
12endmacro()
12endmacro()
13
13
14add_subdirectory(AccountsService)
14add_subdirectory(AccountsService)
15+add_subdirectory(Greeter)
15add_subdirectory(LightDM)
16add_subdirectory(LightDM)
16add_subdirectory(Lights)
17add_subdirectory(Lights)
17add_subdirectory(Dash)
18add_subdirectory(Dash)
plugins/Greeter/CMakeLists.txtnew file+6
@@ -0,0 +1,6 @@
1+macro(add_unity8_greeter_plugin PLUGIN VERSION PATH)
2+ export_qmlfiles(${PLUGIN} ${PATH} DESTINATION ${SHELL_INSTALL_QML}/Greeter TARGET_PREFIX Greeter ${ARGN})
3+ export_qmlplugin(${PLUGIN} ${VERSION} ${PATH} DESTINATION ${SHELL_INSTALL_QML}/Greeter TARGET_PREFIX Greeter ${ARGN})
4+endmacro()
5+
6+add_subdirectory(Unity)
plugins/Greeter/Unity/CMakeLists.txtnew file+1
@@ -0,0 +1 @@
1+add_subdirectory(Launcher)
plugins/Greeter/Unity/Launcher/CMakeLists.txtnew file+38
@@ -0,0 +1,38 @@
1+include(FindPkgConfig)
2+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=6)
3+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=5)
4+pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
5+
6+add_definitions(-DSM_BUSNAME=systemBus)
7+
8+include_directories(
9+ ${CMAKE_CURRENT_SOURCE_DIR}
10+ ${CMAKE_SOURCE_DIR}/plugins/AccountsService
11+ ${GSETTINGS_QT_INCLUDE_DIRS}
12+ ${libunity8-private_SOURCE_DIR}
13+)
14+
15+set(QMLLAUNCHERPLUGINAS_SRC
16+ plugin.cpp
17+ launchermodelas.cpp
18+ launcheritem.cpp
19+ quicklistmodel.cpp
20+ quicklistentry.cpp
21+ ${CMAKE_SOURCE_DIR}/plugins/AccountsService/AccountsServiceDBusAdaptor.cpp
22+ ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherItemInterface.h
23+ ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherModelInterface.h
24+ ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/QuickListModelInterface.h
25+ )
26+
27+add_library(UnityLauncherAS-qml MODULE
28+ ${QMLLAUNCHERPLUGINAS_SRC}
29+ )
30+
31+target_link_libraries(UnityLauncherAS-qml
32+ unity8-private
33+ ${GSETTINGS_QT_LDFLAGS}
34+ )
35+
36+qt5_use_modules(UnityLauncherAS-qml DBus Qml Gui)
37+
38+add_unity8_greeter_plugin(Unity.Launcher 0.1 Unity/Launcher TARGETS UnityLauncherAS-qml)
plugins/Greeter/Unity/Launcher/Launcher.qmltypesnew file+179
@@ -0,0 +1,179 @@
1+import QtQuick.tooling 1.1
2+
3+// This file describes the plugin-supplied types contained in the library.
4+// It is used for QML tooling purposes only.
5+//
6+// This file was auto-generated by:
7+// 'qmlplugindump -notrelocatable Unity.Launcher 0.1 plugins'
8+
9+Module {
10+ Component {
11+ name: "LauncherItem"
12+ prototype: "unity::shell::launcher::LauncherItemInterface"
13+ exports: ["Unity.Launcher/LauncherItem 0.1"]
14+ isCreatable: false
15+ exportMetaObjectRevisions: [0]
16+ }
17+ Component {
18+ name: "LauncherModel"
19+ prototype: "unity::shell::launcher::LauncherModelInterface"
20+ exports: ["Unity.Launcher/LauncherModel 0.1"]
21+ isCreatable: false
22+ isSingleton: true
23+ exportMetaObjectRevisions: [0]
24+ Method {
25+ name: "requestRemove"
26+ Parameter { name: "appId"; type: "string" }
27+ }
28+ Method {
29+ name: "get"
30+ type: "unity::shell::launcher::LauncherItemInterface*"
31+ Parameter { name: "index"; type: "int" }
32+ }
33+ Method {
34+ name: "move"
35+ Parameter { name: "oldIndex"; type: "int" }
36+ Parameter { name: "newIndex"; type: "int" }
37+ }
38+ Method {
39+ name: "pin"
40+ Parameter { name: "appId"; type: "string" }
41+ Parameter { name: "index"; type: "int" }
42+ }
43+ Method {
44+ name: "pin"
45+ Parameter { name: "appId"; type: "string" }
46+ }
47+ Method {
48+ name: "quickListActionInvoked"
49+ Parameter { name: "appId"; type: "string" }
50+ Parameter { name: "actionIndex"; type: "int" }
51+ }
52+ Method {
53+ name: "setUser"
54+ Parameter { name: "username"; type: "string" }
55+ }
56+ Method {
57+ name: "getUrlForAppId"
58+ type: "string"
59+ Parameter { name: "appId"; type: "string" }
60+ }
61+ }
62+ Component {
63+ name: "QuickListModel"
64+ prototype: "unity::shell::launcher::QuickListModelInterface"
65+ exports: ["Unity.Launcher/QuickListModel 0.1"]
66+ isCreatable: false
67+ exportMetaObjectRevisions: [0]
68+ }
69+ Component {
70+ name: "unity::shell::launcher::LauncherItemInterface"
71+ prototype: "QObject"
72+ exports: ["Unity.Launcher/LauncherItemInterface 0.1"]
73+ isCreatable: false
74+ exportMetaObjectRevisions: [0]
75+ Property { name: "appId"; type: "string"; isReadonly: true }
76+ Property { name: "name"; type: "string"; isReadonly: true }
77+ Property { name: "icon"; type: "string"; isReadonly: true }
78+ Property { name: "pinned"; type: "bool"; isReadonly: true }
79+ Property { name: "running"; type: "bool"; isReadonly: true }
80+ Property { name: "recent"; type: "bool"; isReadonly: true }
81+ Property { name: "progress"; type: "int"; isReadonly: true }
82+ Property { name: "count"; type: "int"; isReadonly: true }
83+ Property { name: "countVisible"; type: "bool"; isReadonly: true }
84+ Property { name: "focused"; type: "bool"; isReadonly: true }
85+ Property {
86+ name: "quickList"
87+ type: "unity::shell::launcher::QuickListModelInterface"
88+ isReadonly: true
89+ isPointer: true
90+ }
91+ Signal {
92+ name: "nameChanged"
93+ Parameter { name: "name"; type: "string" }
94+ }
95+ Signal {
96+ name: "iconChanged"
97+ Parameter { name: "icon"; type: "string" }
98+ }
99+ Signal {
100+ name: "pinnedChanged"
101+ Parameter { name: "pinned"; type: "bool" }
102+ }
103+ Signal {
104+ name: "runningChanged"
105+ Parameter { name: "running"; type: "bool" }
106+ }
107+ Signal {
108+ name: "recentChanged"
109+ Parameter { name: "running"; type: "bool" }
110+ }
111+ Signal {
112+ name: "progressChanged"
113+ Parameter { name: "progress"; type: "int" }
114+ }
115+ Signal {
116+ name: "countChanged"
117+ Parameter { name: "count"; type: "int" }
118+ }
119+ Signal {
120+ name: "countVisibleChanged"
121+ Parameter { name: "countVisible"; type: "bool" }
122+ }
123+ Signal {
124+ name: "focusedChanged"
125+ Parameter { name: "focused"; type: "bool" }
126+ }
127+ }
128+ Component {
129+ name: "unity::shell::launcher::LauncherModelInterface"
130+ prototype: "QAbstractListModel"
131+ exports: ["Unity.Launcher/LauncherModelInterface 0.1"]
132+ isCreatable: false
133+ exportMetaObjectRevisions: [0]
134+ Property {
135+ name: "applicationManager"
136+ type: "unity::shell::application::ApplicationManagerInterface"
137+ isPointer: true
138+ }
139+ Method {
140+ name: "move"
141+ Parameter { name: "oldIndex"; type: "int" }
142+ Parameter { name: "newIndex"; type: "int" }
143+ }
144+ Method {
145+ name: "get"
146+ type: "unity::shell::launcher::LauncherItemInterface*"
147+ Parameter { name: "index"; type: "int" }
148+ }
149+ Method {
150+ name: "pin"
151+ Parameter { name: "appId"; type: "string" }
152+ Parameter { name: "index"; type: "int" }
153+ }
154+ Method {
155+ name: "pin"
156+ Parameter { name: "appId"; type: "string" }
157+ }
158+ Method {
159+ name: "requestRemove"
160+ Parameter { name: "appId"; type: "string" }
161+ }
162+ Method {
163+ name: "quickListActionInvoked"
164+ Parameter { name: "appId"; type: "string" }
165+ Parameter { name: "actionIndex"; type: "int" }
166+ }
167+ Method {
168+ name: "setUser"
169+ Parameter { name: "username"; type: "string" }
170+ }
171+ }
172+ Component {
173+ name: "unity::shell::launcher::QuickListModelInterface"
174+ prototype: "QAbstractListModel"
175+ exports: ["Unity.Launcher/QuickListInterface 0.1"]
176+ isCreatable: false
177+ exportMetaObjectRevisions: [0]
178+ }
179+}
plugins/Greeter/Unity/Launcher/launcheritem.cppnew file+171
@@ -0,0 +1,171 @@
1+/*
2+ * Copyright 2014-2015 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "launcheritem.h"
18+#include "quicklistmodel.h"
19+
20+#include <libintl.h>
21+
22+LauncherItem::LauncherItem(const QString &appId, const QString &name, const QString &icon, QObject *parent) :
23+ LauncherItemInterface(parent),
24+ m_appId(appId),
25+ m_name(name),
26+ m_icon(icon),
27+ m_pinned(false),
28+ m_running(false),
29+ m_recent(false),
30+ m_progress(-1),
31+ m_count(0),
32+ m_countVisible(false),
33+ m_focused(false),
34+ m_quickList(new QuickListModel(this))
35+{
36+ QuickListEntry nameAction;
37+ nameAction.setActionId("launch_item");
38+ nameAction.setText(m_name);
39+ m_quickList->appendAction(nameAction);
40+}
41+
42+QString LauncherItem::appId() const
43+{
44+ return m_appId;
45+}
46+
47+QString LauncherItem::name() const
48+{
49+ return m_name;
50+}
51+
52+void LauncherItem::setName(const QString &name)
53+{
54+ if (m_name != name) {
55+ m_name = name;
56+ QuickListEntry entry;
57+ entry.setActionId("launch_item");
58+ entry.setText(m_name);
59+ m_quickList->updateAction(entry);
60+ Q_EMIT nameChanged(name);
61+ }
62+}
63+
64+QString LauncherItem::icon() const
65+{
66+ return m_icon;
67+}
68+
69+void LauncherItem::setIcon(const QString &icon)
70+{
71+ if (m_icon != icon) {
72+ m_icon = icon;
73+ Q_EMIT iconChanged(icon);
74+ }
75+}
76+
77+bool LauncherItem::pinned() const
78+{
79+ return m_pinned;
80+}
81+
82+void LauncherItem::setPinned(bool pinned)
83+{
84+ if (m_pinned != pinned) {
85+ m_pinned = pinned;
86+ Q_EMIT pinnedChanged(pinned);
87+ }
88+}
89+
90+bool LauncherItem::running() const
91+{
92+ return m_running;
93+}
94+
95+void LauncherItem::setRunning(bool running)
96+{
97+ if (m_running != running) {
98+ m_running = running;
99+ Q_EMIT runningChanged(running);
100+ }
101+}
102+
103+bool LauncherItem::recent() const
104+{
105+ return m_recent;
106+}
107+
108+void LauncherItem::setRecent(bool recent)
109+{
110+ if (m_recent != recent) {
111+ m_recent = recent;
112+ Q_EMIT recentChanged(recent);
113+ }
114+}
115+
116+int LauncherItem::progress() const
117+{
118+ return m_progress;
119+}
120+
121+void LauncherItem::setProgress(int progress)
122+{
123+ if (m_progress != progress) {
124+ m_progress = progress;
125+ Q_EMIT progressChanged(progress);
126+ }
127+}
128+
129+int LauncherItem::count() const
130+{
131+ return m_count;
132+}
133+
134+void LauncherItem::setCount(int count)
135+{
136+ if (m_count != count) {
137+ m_count = count;
138+ Q_EMIT countChanged(count);
139+ }
140+}
141+
142+bool LauncherItem::countVisible() const
143+{
144+ return m_countVisible;
145+}
146+
147+void LauncherItem::setCountVisible(bool countVisible)
148+{
149+ if (m_countVisible != countVisible) {
150+ m_countVisible = countVisible;
151+ Q_EMIT countVisibleChanged(countVisible);
152+ }
153+}
154+
155+bool LauncherItem::focused() const
156+{
157+ return m_focused;
158+}
159+
160+void LauncherItem::setFocused(bool focused)
161+{
162+ if (m_focused != focused) {
163+ m_focused = focused;
164+ Q_EMIT focusedChanged(focused);
165+ }
166+}
167+
168+unity::shell::launcher::QuickListModelInterface *LauncherItem::quickList() const
169+{
170+ return m_quickList;
171+}
plugins/Greeter/Unity/Launcher/launcheritem.hnew file+74
@@ -0,0 +1,74 @@
1+/*
2+ * Copyright 2014-2015 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef LAUNCHERITEM_H
18+#define LAUNCHERITEM_H
19+
20+#include "quicklistmodel.h"
21+
22+#include <unity/shell/launcher/LauncherItemInterface.h>
23+
24+class QuickListModel;
25+
26+using namespace unity::shell::launcher;
27+
28+class LauncherItem: public LauncherItemInterface
29+{
30+ Q_OBJECT
31+public:
32+ LauncherItem(const QString &appId, const QString &name, const QString &icon, QObject *parent = 0);
33+
34+ QString appId() const override;
35+ QString name() const override;
36+ QString icon() const override;
37+ bool pinned() const override;
38+ bool running() const override;
39+ bool recent() const override;
40+ int progress() const override;
41+ int count() const override;
42+ bool countVisible() const override;
43+ bool focused() const override;
44+
45+ unity::shell::launcher::QuickListModelInterface *quickList() const override;
46+
47+private:
48+ void setName(const QString &name);
49+ void setIcon(const QString &icon);
50+ void setPinned(bool pinned);
51+ void setRunning(bool running);
52+ void setRecent(bool recent);
53+ void setProgress(int progress);
54+ void setCount(int count);
55+ void setCountVisible(bool countVisible);
56+ void setFocused(bool focused);
57+
58+private:
59+ QString m_appId;
60+ QString m_name;
61+ QString m_icon;
62+ bool m_pinned;
63+ bool m_running;
64+ bool m_recent;
65+ int m_progress;
66+ int m_count;
67+ bool m_countVisible;
68+ bool m_focused;
69+ QuickListModel *m_quickList;
70+
71+ friend class LauncherModel;
72+};
73+
74+#endif // LAUNCHERITEM_H
plugins/Greeter/Unity/Launcher/launchermodelas.cppnew file+277
@@ -0,0 +1,277 @@
1+/*
2+ * Copyright 2014-2015 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "launchermodelas.h"
18+#include "launcheritem.h"
19+#include "AccountsServiceDBusAdaptor.h"
20+#include <unity/shell/application/ApplicationInfoInterface.h>
21+
22+#include <QDesktopServices>
23+#include <QDebug>
24+#include <QDBusArgument>
25+
26+using namespace unity::shell::application;
27+
28+LauncherModel::LauncherModel(QObject *parent):
29+ LauncherModelInterface(parent),
30+ m_accounts(new AccountsServiceDBusAdaptor(this)),
31+ m_onlyPinned(true)
32+{
33+ connect(m_accounts, &AccountsServiceDBusAdaptor::propertiesChanged, this, &LauncherModel::propertiesChanged);
34+ refresh();
35+}
36+
37+LauncherModel::~LauncherModel()
38+{
39+ while (!m_list.empty()) {
40+ m_list.takeFirst()->deleteLater();
41+ }
42+}
43+
44+int LauncherModel::rowCount(const QModelIndex &parent) const
45+{
46+ Q_UNUSED(parent)
47+ return m_list.count();
48+}
49+
50+QVariant LauncherModel::data(const QModelIndex &index, int role) const
51+{
52+ LauncherItem *item = m_list.at(index.row());
53+ switch(role) {
54+ case RoleAppId:
55+ return item->appId();
56+ case RoleName:
57+ return item->name();
58+ case RoleIcon:
59+ return item->icon();
60+ case RolePinned:
61+ return item->pinned();
62+ case RoleCount:
63+ return item->count();
64+ case RoleCountVisible:
65+ return item->countVisible();
66+ case RoleProgress:
67+ return item->progress();
68+ case RoleFocused:
69+ return item->focused();
70+ }
71+
72+ return QVariant();
73+}
74+
75+unity::shell::launcher::LauncherItemInterface *LauncherModel::get(int index) const
76+{
77+ if (index < 0 || index >= m_list.count()) {
78+ return 0;
79+ }
80+ return m_list.at(index);
81+}
82+
83+void LauncherModel::move(int oldIndex, int newIndex)
84+{
85+ Q_UNUSED(oldIndex)
86+ Q_UNUSED(newIndex)
87+ qWarning() << "This is a read only implementation. Cannot move items.";
88+}
89+
90+void LauncherModel::pin(const QString &appId, int index)
91+{
92+ Q_UNUSED(appId)
93+ Q_UNUSED(index)
94+ qWarning() << "This is a read only implementation. Cannot pin items";
95+}
96+
97+void LauncherModel::requestRemove(const QString &appId)
98+{
99+ Q_UNUSED(appId)
100+ qWarning() << "This is a read only implementation. Cannot remove items";
101+}
102+
103+void LauncherModel::quickListActionInvoked(const QString &appId, int actionIndex)
104+{
105+ int index = findApplication(appId);
106+ if (index < 0) {
107+ return;
108+ }
109+
110+ LauncherItem *item = m_list.at(index);
111+ QuickListModel *model = qobject_cast<QuickListModel*>(item->quickList());
112+ if (model) {
113+ QString actionId = model->get(actionIndex).actionId();
114+
115+ // Check if this is one of the launcher actions we handle ourselves
116+ if (actionId == "launch_item") {
117+ QDesktopServices::openUrl(getUrlForAppId(appId));
118+
119+ // Nope, we don't know this action, let the backend forward it to the application
120+ } else {
121+ // TODO: forward quicklist action to app, possibly via m_dbusIface
122+ }
123+ }
124+}
125+
126+void LauncherModel::setUser(const QString &username)
127+{
128+ if (m_user != username) {
129+ m_user = username;
130+ refresh();
131+ }
132+}
133+
134+QString LauncherModel::getUrlForAppId(const QString &appId) const
135+{
136+ // appId is either an appId or a legacy app name. Let's find out which
137+ if (appId.isEmpty()) {
138+ return QString();
139+ }
140+
141+ if (!appId.contains("_")) {
142+ return "application:///" + appId + ".desktop";
143+ }
144+
145+ QStringList parts = appId.split('_');
146+ QString package = parts.value(0);
147+ QString app = parts.value(1, "first-listed-app");
148+ return "appid://" + package + "/" + app + "/current-user-version";
149+}
150+
151+ApplicationManagerInterface *LauncherModel::applicationManager() const
152+{
153+ return nullptr;
154+}
155+
156+void LauncherModel::setApplicationManager(unity::shell::application::ApplicationManagerInterface *appManager)
157+{
158+ Q_UNUSED(appManager)
159+ qWarning() << "This plugin syncs all its states from AccountsService. Not using ApplicationManager.";
160+ return;
161+}
162+
163+bool LauncherModel::onlyPinned() const
164+{
165+ return m_onlyPinned;
166+}
167+
168+void LauncherModel::setOnlyPinned(bool onlyPinned)
169+{
170+ if (m_onlyPinned != onlyPinned) {
171+ m_onlyPinned = onlyPinned;
172+ Q_EMIT onlyPinnedChanged();
173+ refresh();
174+ }
175+}
176+
177+int LauncherModel::findApplication(const QString &appId)
178+{
179+ for (int i = 0; i < m_list.count(); ++i) {
180+ LauncherItem *item = m_list.at(i);
181+ if (item->appId() == appId) {
182+ return i;
183+ }
184+ }
185+ return -1;
186+}
187+
188+typedef QList<QVariantMap> VariantMapList;
189+void LauncherModel::refresh()
190+{
191+ QList<QVariantMap> items;
192+
193+ if (m_accounts && !m_user.isEmpty()) {
194+ items = m_accounts->getUserProperty<VariantMapList>(m_user, "com.canonical.unity.AccountsService", "LauncherItems");
195+ }
196+
197+ // First walk through all the existing items and see if we need to remove something
198+ QList<LauncherItem*> toBeRemoved;
199+
200+ Q_FOREACH (LauncherItem* item, m_list) {
201+ bool found = false;
202+ Q_FOREACH(const QVariant &asItem, items) {
203+ if (asItem.toMap().value("id").toString() == item->appId()) {
204+ // Only keep and update it if we either show unpinned or it is pinned
205+ if (!m_onlyPinned || asItem.toMap().value("pinned").toBool()) {
206+ found = true;
207+ item->setName(asItem.toMap().value("name").toString());
208+ item->setIcon(asItem.toMap().value("icon").toString());
209+ item->setCount(asItem.toMap().value("count").toInt());
210+ item->setCountVisible(asItem.toMap().value("countVisible").toBool());
211+ int idx = m_list.indexOf(item);
212+ Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleName << RoleIcon);
213+ }
214+ break;
215+ }
216+ }
217+ if (!found) {
218+ toBeRemoved.append(item);
219+ }
220+ }
221+
222+ Q_FOREACH (LauncherItem* item, toBeRemoved) {
223+ int idx = m_list.indexOf(item);
224+ beginRemoveRows(QModelIndex(), idx, idx);
225+ m_list.takeAt(idx)->deleteLater();
226+ endRemoveRows();
227+ }
228+
229+ // Now walk through list and see if we need to add something
230+ int skipped = 0;
231+ for (int asIndex = 0; asIndex < items.count(); ++asIndex) {
232+ QVariant entry = items.at(asIndex);
233+
234+ if (m_onlyPinned && !entry.toMap().value("pinned").toBool()) {
235+ // Skipping it as we only show pinned and it is not
236+ skipped++;
237+ continue;
238+ }
239+ int newPosition = asIndex - skipped;
240+
241+ int itemIndex = -1;
242+ for (int i = 0; i < m_list.count(); ++i) {
243+ if (m_list.at(i)->appId() == entry.toMap().value("id").toString()) {
244+ itemIndex = i;
245+ break;
246+ }
247+ }
248+
249+ if (itemIndex == -1) {
250+ // Need to add it. Just add it into the addedIndex to keep same ordering as the list in AS.
251+ LauncherItem *item = new LauncherItem(entry.toMap().value("id").toString(),
252+ entry.toMap().value("name").toString(),
253+ entry.toMap().value("icon").toString(),
254+ this);
255+ item->setPinned(true);
256+ item->setCount(entry.toMap().value("count").toInt());
257+ item->setCountVisible(entry.toMap().value("countVisible").toBool());
258+ beginInsertRows(QModelIndex(), newPosition, newPosition);
259+ m_list.insert(newPosition, item);
260+ endInsertRows();
261+ } else if (itemIndex != newPosition) {
262+ // The item is already there, but it is in a different place than in the settings.
263+ // Move it to the addedIndex
264+ beginMoveRows(QModelIndex(), itemIndex, itemIndex, QModelIndex(), newPosition);
265+ m_list.move(itemIndex, newPosition);
266+ endMoveRows();
267+ }
268+ }
269+}
270+
271+void LauncherModel::propertiesChanged(const QString &user, const QString &interface, const QStringList &changed)
272+{
273+ if (user != m_user || interface != "com.canonical.unity.AccountsService" || !changed.contains("LauncherItems")) {
274+ return;
275+ }
276+ refresh();
277+}
plugins/Greeter/Unity/Launcher/launchermodelas.hnew file+75
@@ -0,0 +1,75 @@
1+/*
2+ * Copyright 2014-2015 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef LAUNCHERMODEL_H
18+#define LAUNCHERMODEL_H
19+
20+#include <unity/shell/launcher/LauncherModelInterface.h>
21+#include <unity/shell/application/ApplicationManagerInterface.h>
22+
23+#include <QAbstractListModel>
24+
25+class LauncherItem;
26+class GSettings;
27+class AccountsServiceDBusAdaptor;
28+
29+using namespace unity::shell::launcher;
30+using namespace unity::shell::application;
31+
32+class LauncherModel: public LauncherModelInterface
33+{
34+ Q_OBJECT
35+
36+public:
37+ LauncherModel(QObject *parent = 0);
38+ ~LauncherModel();
39+
40+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
41+
42+ QVariant data(const QModelIndex &index, int role) const;
43+
44+ Q_INVOKABLE unity::shell::launcher::LauncherItemInterface* get(int index) const;
45+ Q_INVOKABLE void move(int oldIndex, int newIndex);
46+ Q_INVOKABLE void pin(const QString &appId, int index = -1);
47+ Q_INVOKABLE void quickListActionInvoked(const QString &appId, int actionIndex);
48+ Q_INVOKABLE void setUser(const QString &username);
49+ Q_INVOKABLE QString getUrlForAppId(const QString &appId) const;
50+
51+ unity::shell::application::ApplicationManagerInterface* applicationManager() const;
52+ void setApplicationManager(unity::shell::application::ApplicationManagerInterface *appManager);
53+
54+ bool onlyPinned() const override;
55+ void setOnlyPinned(bool onlyPinned) override;
56+
57+ int findApplication(const QString &appId);
58+
59+public Q_SLOTS:
60+ void requestRemove(const QString &appId) override;
61+ Q_INVOKABLE void refresh();
62+
63+private Q_SLOTS:
64+ void propertiesChanged(const QString &user, const QString &interface, const QStringList &changed);
65+
66+private:
67+ QString m_user;
68+ QList<LauncherItem*> m_list;
69+ AccountsServiceDBusAdaptor *m_accounts;
70+ bool m_onlyPinned;
71+
72+ friend class LauncherModelASTest;
73+};
74+
75+#endif // LAUNCHERMODEL_H
plugins/Greeter/Unity/Launcher/plugin.cppnew file+46
@@ -0,0 +1,46 @@
1+/*
2+ * Copyright 2014-2015 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+// Qt
18+#include <QtQml/qqml.h>
19+
20+// self
21+#include "plugin.h"
22+
23+// local
24+#include "launchermodelas.h"
25+#include "launcheritem.h"
26+
27+
28+using namespace unity::shell::launcher;
29+
30+static QObject* modelProvider(QQmlEngine* /* engine */, QJSEngine* /* scriptEngine */)
31+{
32+ return new LauncherModel();
33+}
34+
35+void UnityLauncherPlugin::registerTypes(const char *uri)
36+{
37+ Q_ASSERT(uri == QLatin1String("Unity.Launcher"));
38+
39+ qmlRegisterUncreatableType<LauncherModelInterface>(uri, 0, 1, "LauncherModelInterface", "Abstract Interface. Cannot be instantiated.");
40+ qmlRegisterUncreatableType<LauncherItemInterface>(uri, 0, 1, "LauncherItemInterface", "Abstract Interface. Cannot be instantiated.");
41+ qmlRegisterUncreatableType<QuickListModelInterface>(uri, 0, 1, "QuickListInterface", "Abstract Interface. Cannot be instantiated.");
42+
43+ qmlRegisterSingletonType<LauncherModel>(uri, 0, 1, "LauncherModel", modelProvider);
44+ qmlRegisterUncreatableType<LauncherItem>(uri, 0, 1, "LauncherItem", "Can't create new Launcher Items in QML. Get them from the LauncherModel.");
45+ qmlRegisterUncreatableType<QuickListModel>(uri, 0, 1, "QuickListModel", "Can't create a QuickListModel in QML. Get them from the LauncherItems.");
46+}
plugins/Greeter/Unity/Launcher/plugin.hnew file+32
@@ -0,0 +1,32 @@
1+/*
2+ * Copyright (C) 2014-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 UNITY_LAUNCHER_PLUGIN_H
18+#define UNITY_LAUNCHER_PLUGIN_H
19+
20+#include <QtQml/QQmlEngine>
21+#include <QtQml/QQmlExtensionPlugin>
22+
23+class UnityLauncherPlugin : public QQmlExtensionPlugin
24+{
25+ Q_OBJECT
26+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
27+
28+public:
29+ void registerTypes(const char *uri);
30+};
31+
32+#endif
plugins/Greeter/Unity/Launcher/qmldirnew file+3
@@ -0,0 +1,3 @@
1+module Unity.Launcher
2+plugin UnityLauncherAS-qml
3+typeinfo Launcher.qmltypes
plugins/Greeter/Unity/Launcher/quicklistentry.cppnew file+56
@@ -0,0 +1,56 @@
1+/* Copyright (C) 2014-2015 Canonical, Ltd.
2+ *
3+ * This program is free software; you can redistribute it and/or modify
4+ * it under the terms of the GNU General Public License as published by
5+ * the Free Software Foundation; version 3.
6+ *
7+ * This program is distributed in the hope that it will be useful,
8+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
9+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10+ * GNU General Public License for more details.
11+ *
12+ * You should have received a copy of the GNU General Public License
13+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
14+ */
15+
16+#include "quicklistentry.h"
17+
18+QuickListEntry::QuickListEntry()
19+{
20+
21+}
22+
23+QString QuickListEntry::actionId() const
24+{
25+ return m_actionId;
26+}
27+
28+void QuickListEntry::setActionId(const QString &actionId)
29+{
30+ m_actionId = actionId;
31+}
32+
33+QString QuickListEntry::text() const
34+{
35+ return m_text;
36+}
37+
38+void QuickListEntry::setText(const QString &text)
39+{
40+ m_text = text;
41+}
42+
43+QString QuickListEntry::icon() const
44+{
45+ return m_icon;
46+}
47+
48+void QuickListEntry::setIcon(const QString &icon)
49+{
50+ m_icon = icon;
51+}
52+
53+bool QuickListEntry::clickable() const
54+{
55+ return !m_actionId.isEmpty();
56+}
plugins/Greeter/Unity/Launcher/quicklistentry.hnew file+43
@@ -0,0 +1,43 @@
1+/* Copyright (C) 2014-2015 Canonical, Ltd.
2+ *
3+ * This program is free software; you can redistribute it and/or modify
4+ * it under the terms of the GNU General Public License as published by
5+ * the Free Software Foundation; version 3.
6+ *
7+ * This program is distributed in the hope that it will be useful,
8+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
9+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10+ * GNU General Public License for more details.
11+ *
12+ * You should have received a copy of the GNU General Public License
13+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
14+ */
15+
16+#ifndef QUICKLISTENTRY_H
17+#define QUICKLISTENTRY_H
18+
19+#include <QString>
20+
21+class QuickListEntry
22+{
23+public:
24+ QuickListEntry();
25+
26+ QString actionId() const;
27+ void setActionId(const QString &actionId);
28+
29+ QString text() const;
30+ void setText(const QString &text);
31+
32+ QString icon() const;
33+ void setIcon(const QString &icon);
34+
35+ bool clickable() const;
36+
37+private:
38+ QString m_actionId;
39+ QString m_text;
40+ QString m_icon;
41+};
42+
43+#endif // QUICKLISTENTRY
plugins/Greeter/Unity/Launcher/quicklistmodel.cppnew file+70
@@ -0,0 +1,70 @@
1+/*
2+ * Copyright 2014-2015 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "quicklistmodel.h"
18+
19+QuickListModel::QuickListModel(QObject *parent) :
20+ QuickListModelInterface(parent)
21+{
22+
23+}
24+
25+QuickListModel::~QuickListModel()
26+{
27+
28+}
29+
30+void QuickListModel::appendAction(const QuickListEntry &entry)
31+{
32+ beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
33+ m_list.append(entry);
34+ endInsertRows();
35+}
36+
37+void QuickListModel::updateAction(const QuickListEntry &entry)
38+{
39+ for (int i = 0; i < m_list.count(); ++i) {
40+ if (m_list.at(i).actionId() == entry.actionId()) {
41+ m_list.replace(i, entry);
42+ Q_EMIT dataChanged(index(i), index(i));
43+ return;
44+ }
45+ }
46+}
47+
48+QuickListEntry QuickListModel::get(int index) const
49+{
50+ return m_list.at(index);
51+}
52+
53+int QuickListModel::rowCount(const QModelIndex &index) const
54+{
55+ Q_UNUSED(index)
56+ return m_list.count();
57+}
58+
59+QVariant QuickListModel::data(const QModelIndex &index, int role) const
60+{
61+ switch (role) {
62+ case RoleLabel:
63+ return m_list.at(index.row()).text();
64+ case RoleIcon:
65+ return m_list.at(index.row()).icon();
66+ case RoleClickable:
67+ return m_list.at(index.row()).clickable();
68+ }
69+ return QVariant();
70+}
plugins/Greeter/Unity/Launcher/quicklistmodel.hnew file+55
@@ -0,0 +1,55 @@
1+/*
2+ * Copyright 2014-2015 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef QUICKLISTMODEL_H
18+#define QUICKLISTMODEL_H
19+
20+#include "quicklistentry.h"
21+
22+#include <unity/shell/launcher/QuickListModelInterface.h>
23+
24+using namespace unity::shell::launcher;
25+
26+class QuickListModel: public QuickListModelInterface
27+{
28+ Q_OBJECT
29+
30+public:
31+ explicit QuickListModel(QObject *parent = 0);
32+
33+ ~QuickListModel();
34+
35+ void appendAction(const QuickListEntry &entry);
36+
37+ /**
38+ * @brief Update an existing action
39+ * @param entry The new, updated entry
40+ *
41+ * This will only do something if entry.actionId is found in the model.
42+ * To add a new entry, use appendAction().
43+ */
44+ void updateAction(const QuickListEntry &entry);
45+
46+ QuickListEntry get(int index) const;
47+
48+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
49+ QVariant data(const QModelIndex &index, int role) const;
50+
51+private:
52+ QList<QuickListEntry> m_list;
53+};
54+
55+#endif // QUICKLISTMODEL_H
plugins/Ubuntu/Gestures/TouchGate.cpp+2
@@ -53,6 +53,8 @@ void TouchGate::touchEvent(QTouchEvent *event)
53 m_touchInfoMap[touchPoint.id()].ownership = OwnershipRequested;
53 m_touchInfoMap[touchPoint.id()].ownership = OwnershipRequested;
54 m_touchInfoMap[touchPoint.id()].ended = false;
54 m_touchInfoMap[touchPoint.id()].ended = false;
55 TouchRegistry::instance()->requestTouchOwnership(touchPoint.id(), this);
55 TouchRegistry::instance()->requestTouchOwnership(touchPoint.id(), this);
56+
57+ Q_EMIT pressed();
56 }
58 }
57
59
58 goodToGo &= m_touchInfoMap.contains(touchPoint.id())
60 goodToGo &= m_touchInfoMap.contains(touchPoint.id())
plugins/Ubuntu/Gestures/TouchGate.h+2
@@ -53,6 +53,8 @@ public:
53Q_SIGNALS:
53Q_SIGNALS:
54 void targetItemChanged(QQuickItem *item);
54 void targetItemChanged(QQuickItem *item);
55
55
56+ void pressed();
57+
56protected:
58protected:
57 void touchEvent(QTouchEvent *event) override;
59 void touchEvent(QTouchEvent *event) override;
58private:
60private:
plugins/Unity/Indicators/indicator.cpp+23−8
@@ -31,24 +31,26 @@ Indicator::~Indicator()
31{
31{
32}
32}
33
33
34−void Indicator::init(const QString &profile, const QString& busName, const QSettings& settings)
34+void Indicator::init(const QString& busName, const QSettings& settings)
35{
35{
36− setId(settings.value("Indicator Service/Name").toString());
36+ // Save all keys we care about from the QSettings object. It's annoying
37+ // that we can't just copy the object.
38+ m_settings.clear();
39+ Q_FOREACH(const QString& key, settings.allKeys()) {
40+ if (key.endsWith("/Position") || key.endsWith("/ObjectPath")) {
41+ m_settings.insert(key, settings.value(key));
42+ }
43+ }
37
44
38− QVariant pos = settings.value(profile + "/Position");
39− if (!pos.isValid())
40− pos = settings.value("Indicator Service/Position", QVariant::fromValue(0));
45+ setId(settings.value("Indicator Service/Name").toString());
41− setPosition(pos.toInt());
42
46
43 QString actionObjectPath = settings.value("Indicator Service/ObjectPath").toString();
47 QString actionObjectPath = settings.value("Indicator Service/ObjectPath").toString();
44− QString menuObjectPath = settings.value(profile + "/ObjectPath").toString();
45
48
46 QVariantMap properties;
49 QVariantMap properties;
47 properties.clear();
50 properties.clear();
48 properties.insert("busType", 1);
51 properties.insert("busType", 1);
49 properties.insert("busName", busName);
52 properties.insert("busName", busName);
50 properties.insert("actionsObjectPath", actionObjectPath);
53 properties.insert("actionsObjectPath", actionObjectPath);
51− properties.insert("menuObjectPath", menuObjectPath);
52 setIndicatorProperties(properties);
54 setIndicatorProperties(properties);
53}
55}
54
56
@@ -78,6 +80,19 @@ void Indicator::setPosition(int position)
78 }
80 }
79}
81}
80
82
83+void Indicator::setProfile(const QString& profile)
84+{
85+ QVariant pos = m_settings.value(profile + "/Position");
86+ if (!pos.isValid())
87+ pos = m_settings.value("Indicator Service/Position", QVariant::fromValue(0));
88+ setPosition(pos.toInt());
89+
90+ QString menuObjectPath = m_settings.value(profile + "/ObjectPath").toString();
91+ QVariantMap map = m_properties.toMap();
92+ map.insert("menuObjectPath", menuObjectPath);
93+ setIndicatorProperties(map);
94+}
95+
81QVariant Indicator::indicatorProperties() const
96QVariant Indicator::indicatorProperties() const
82{
97{
83 return m_properties;
98 return m_properties;
plugins/Unity/Indicators/indicator.h+5−1
@@ -35,12 +35,15 @@ public:
35 Indicator(QObject *parent = 0);
35 Indicator(QObject *parent = 0);
36 virtual ~Indicator();
36 virtual ~Indicator();
37
37
38− void init(const QString &profile, const QString& busName, const QSettings& settings);
38+ void init(const QString& busName, const QSettings& settings);
39
39
40 QString identifier() const;
40 QString identifier() const;
41 int position() const;
41 int position() const;
42 QVariant indicatorProperties() const;
42 QVariant indicatorProperties() const;
43
43
44+public Q_SLOTS:
45+ void setProfile(const QString& profile);
46+
44Q_SIGNALS:
47Q_SIGNALS:
45 void identifierChanged(const QString &identifier);
48 void identifierChanged(const QString &identifier);
46 void positionChanged(int position);
49 void positionChanged(int position);
@@ -55,6 +58,7 @@ private:
55 QString m_identifier;
58 QString m_identifier;
56 int m_position;
59 int m_position;
57 QVariant m_properties;
60 QVariant m_properties;
61+ QVariantMap m_settings;
58};
62};
59
63
60#endif // INDICATOR_H
64#endif // INDICATOR_H
plugins/Unity/Indicators/indicatorsmanager.cpp+17−3
@@ -54,10 +54,9 @@ IndicatorsManager::~IndicatorsManager()
54
54
55}
55}
56
56
57−void IndicatorsManager::load(const QString& profile)
57+void IndicatorsManager::load()
58{
58{
59 unload();
59 unload();
60− m_profile = profile;
61
60
62 QStringList xdgLocations = shellDataDirs();//QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
61 QStringList xdgLocations = shellDataDirs();//QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
63
62
@@ -216,6 +215,19 @@ void IndicatorsManager::setLoaded(bool loaded)
216 }
215 }
217}
216}
218
217
218+QString IndicatorsManager::profile() const
219+{
220+ return m_profile;
221+}
222+
223+void IndicatorsManager::setProfile(const QString& profile)
224+{
225+ if (m_profile != profile) {
226+ m_profile = profile;
227+ Q_EMIT profileChanged(m_profile);
228+ }
229+}
230+
219void IndicatorsManager::startVerify(const QString& path)
231void IndicatorsManager::startVerify(const QString& path)
220{
232{
221 QHashIterator<QString, IndicatorData*> iter(m_indicatorsData);
233 QHashIterator<QString, IndicatorData*> iter(m_indicatorsData);
@@ -268,7 +280,9 @@ Indicator::Ptr IndicatorsManager::indicator(const QString& indicator_name)
268 Indicator::Ptr new_indicator(new Indicator(this));
280 Indicator::Ptr new_indicator(new Indicator(this));
269 data->m_indicator = new_indicator;
281 data->m_indicator = new_indicator;
270 QSettings settings(data->m_fileInfo.absoluteFilePath(), QSettings::IniFormat, this);
282 QSettings settings(data->m_fileInfo.absoluteFilePath(), QSettings::IniFormat, this);
271− new_indicator->init(m_profile, data->m_fileInfo.fileName(), settings);
283+ new_indicator->init(data->m_fileInfo.fileName(), settings);
284+ new_indicator->setProfile(m_profile);
285+ QObject::connect(this, SIGNAL(profileChanged(const QString&)), new_indicator.data(), SLOT(setProfile(const QString&)));
272 return new_indicator;
286 return new_indicator;
273}
287}
274
288
plugins/Unity/Indicators/indicatorsmanager.h+6−1
@@ -32,13 +32,17 @@ class UNITYINDICATORS_EXPORT IndicatorsManager : public QObject
32{
32{
33 Q_OBJECT
33 Q_OBJECT
34 Q_PROPERTY(bool loaded READ isLoaded NOTIFY loadedChanged)
34 Q_PROPERTY(bool loaded READ isLoaded NOTIFY loadedChanged)
35+ Q_PROPERTY(QString profile READ profile WRITE setProfile NOTIFY profileChanged)
35public:
36public:
36 explicit IndicatorsManager(QObject* parent = 0);
37 explicit IndicatorsManager(QObject* parent = 0);
37 ~IndicatorsManager();
38 ~IndicatorsManager();
38
39
39− Q_INVOKABLE void load(const QString& profile);
40+ Q_INVOKABLE void load();
40 Q_INVOKABLE void unload();
41 Q_INVOKABLE void unload();
41
42
43+ QString profile() const;
44+ void setProfile(const QString& profile);
45+
42 Indicator::Ptr indicator(const QString& indicator_name);
46 Indicator::Ptr indicator(const QString& indicator_name);
43
47
44 QList<Indicator::Ptr> indicators();
48 QList<Indicator::Ptr> indicators();
@@ -47,6 +51,7 @@ public:
47
51
48Q_SIGNALS:
52Q_SIGNALS:
49 void loadedChanged(bool);
53 void loadedChanged(bool);
54+ void profileChanged(const QString&);
50
55
51 void indicatorLoaded(const QString& indicator_name);
56 void indicatorLoaded(const QString& indicator_name);
52 void indicatorAboutToBeUnloaded(const QString& indicator_name);
57 void indicatorAboutToBeUnloaded(const QString& indicator_name);
plugins/Unity/Indicators/indicatorsmodel.cpp+25−2
@@ -56,6 +56,7 @@ IndicatorsModel::IndicatorsModel(QObject *parent)
56 m_manager = new IndicatorsManager(this);
56 m_manager = new IndicatorsManager(this);
57 QObject::connect(m_manager, SIGNAL(indicatorLoaded(const QString&)), this, SLOT(onIndicatorLoaded(const QString&)));
57 QObject::connect(m_manager, SIGNAL(indicatorLoaded(const QString&)), this, SLOT(onIndicatorLoaded(const QString&)));
58 QObject::connect(m_manager, SIGNAL(indicatorAboutToBeUnloaded(const QString&)), this, SLOT(onIndicatorAboutToBeUnloaded(const QString&)));
58 QObject::connect(m_manager, SIGNAL(indicatorAboutToBeUnloaded(const QString&)), this, SLOT(onIndicatorAboutToBeUnloaded(const QString&)));
59+ QObject::connect(m_manager, SIGNAL(profileChanged(const QString&)), this, SIGNAL(profileChanged()));
59
60
60 QObject::connect(this, SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SIGNAL(countChanged()));
61 QObject::connect(this, SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SIGNAL(countChanged()));
61 QObject::connect(this, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SIGNAL(countChanged()));
62 QObject::connect(this, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SIGNAL(countChanged()));
@@ -80,15 +81,37 @@ int IndicatorsModel::count() const
80 return rowCount();
81 return rowCount();
81}
82}
82
83
84+/*!
85+ \qmlproperty IndicatorsModel::profile
86+ The indicator profile to use for the model.
87+
88+ \b Note: methods should only be called after the Component has completed.
89+*/
90+QString IndicatorsModel::profile() const
91+{
92+ return m_manager->profile();
93+}
94+
95+/*!
96+ \qmlproperty IndicatorsModel::setProfile
97+ Set the indicator profile to use for the model.
98+
99+ \b Note: methods should only be called after the Component has completed.
100+*/
101+void IndicatorsModel::setProfile(const QString &profile)
102+{
103+ m_manager->setProfile(profile);
104+}
105+
83/*!
106/*!
84 \qmlmethod IndicatorsModel::unload()
107 \qmlmethod IndicatorsModel::unload()
85
108
86 Load all indicators.
109 Load all indicators.
87*/
110*/
88−void IndicatorsModel::load(const QString& profile)
111+void IndicatorsModel::load()
89{
112{
90 m_indicators.clear();
113 m_indicators.clear();
91− m_manager->load(profile);
114+ m_manager->load();
92}
115}
93
116
94/*!
117/*!
plugins/Unity/Indicators/indicatorsmodel.h+6−1
@@ -34,17 +34,21 @@ class UNITYINDICATORS_EXPORT IndicatorsModel : public QAbstractListModel
34 Q_OBJECT
34 Q_OBJECT
35 Q_ENUMS(Roles)
35 Q_ENUMS(Roles)
36 Q_PROPERTY(int count READ count NOTIFY countChanged)
36 Q_PROPERTY(int count READ count NOTIFY countChanged)
37+ Q_PROPERTY(QString profile READ profile WRITE setProfile NOTIFY profileChanged)
37
38
38public:
39public:
39
40
40 IndicatorsModel(QObject *parent=0);
41 IndicatorsModel(QObject *parent=0);
41 ~IndicatorsModel();
42 ~IndicatorsModel();
42
43
43− Q_INVOKABLE void load(const QString& profile);
44+ Q_INVOKABLE void load();
44 Q_INVOKABLE void unload();
45 Q_INVOKABLE void unload();
45
46
46 Q_INVOKABLE QVariant data(int row, int role) const;
47 Q_INVOKABLE QVariant data(int row, int role) const;
47
48
49+ QString profile() const;
50+ void setProfile(const QString& profile);
51+
48 /* QAbstractItemModel */
52 /* QAbstractItemModel */
49 QHash<int, QByteArray> roleNames() const;
53 QHash<int, QByteArray> roleNames() const;
50 int columnCount(const QModelIndex &parent = QModelIndex()) const;
54 int columnCount(const QModelIndex &parent = QModelIndex()) const;
@@ -55,6 +59,7 @@ public:
55
59
56Q_SIGNALS:
60Q_SIGNALS:
57 void countChanged();
61 void countChanged();
62+ void profileChanged();
58 void indicatorDataChanged(const QVariant& data);
63 void indicatorDataChanged(const QVariant& data);
59
64
60private Q_SLOTS:
65private Q_SLOTS:
plugins/Unity/Indicators/modelactionrootstate.cpp+4−2
@@ -71,7 +71,7 @@ void ModelActionRootState::setMenu(UnityMenuModel* menu)
71
71
72bool ModelActionRootState::valid() const
72bool ModelActionRootState::valid() const
73{
73{
74− return m_menu && m_menu->rowCount() > 0;
74+ return !currentState().empty();
75}
75}
76
76
77void ModelActionRootState::onModelRowsAdded(const QModelIndex& parent, int start, int end)
77void ModelActionRootState::onModelRowsAdded(const QModelIndex& parent, int start, int end)
@@ -121,7 +121,9 @@ void ModelActionRootState::updateActionState()
121 m_menu->setActionStateParser(oldParser);
121 m_menu->setActionStateParser(oldParser);
122
122
123 setCurrentState(state);
123 setCurrentState(state);
124− } else {
124+ } else if (!m_menu) {
125 setCurrentState(QVariantMap());
125 setCurrentState(QVariantMap());
126 }
126 }
127+ // else if m_menu->rowCount() == 0, let's leave existing cache in place
128+ // until the new menu comes in, to avoid flashing the UI empty for a moment
127}
129}
plugins/Unity/Indicators/unitymenumodelcache.cpp+8−11
@@ -46,17 +46,14 @@ QSharedPointer<UnityMenuModel> UnityMenuModelCache::model(const QByteArray& path
46 QQmlEngine::setObjectOwnership(model, QQmlEngine::CppOwnership);
46 QQmlEngine::setObjectOwnership(model, QQmlEngine::CppOwnership);
47
47
48 QSharedPointer<UnityMenuModel> menuModel(model);
48 QSharedPointer<UnityMenuModel> menuModel(model);
49− connect(model, &QObject::destroyed, this, [this] {
49+
50− QMutableHashIterator<QByteArray, QWeakPointer<UnityMenuModel>> iter(m_registry);
50+ // Keep a shared pointer (rather than weak pointer which would cause the
51− while(iter.hasNext()) {
51+ // model to be deleted when all shared pointers we give out are deleted).
52− auto keyVal = iter.next();
52+ // We want to keep all models cached because when we switch indicator
53− if (keyVal.value().isNull()) {
53+ // profiles, we will be switching paths often. And we want to keep the
54− iter.remove();
54+ // old model around, ready to be used. Otherwise the UI might momentarily
55− break;
55+ // wait as we populate the model from DBus yet again.
56− }
57− }
58− });
59− m_registry[path] = menuModel.toWeakRef();
56+ m_registry[path] = menuModel;
60
57
61 menuModel->setMenuObjectPath(path);
58 menuModel->setMenuObjectPath(path);
62 return menuModel;
59 return menuModel;
plugins/Unity/Indicators/unitymenumodelcache.h+2−2
@@ -25,7 +25,7 @@
25#include <QObject>
25#include <QObject>
26#include <QHash>
26#include <QHash>
27#include <QPointer>
27#include <QPointer>
28−#include <QWeakPointer>
28+#include <QSharedPointer>
29
29
30class UnityMenuModel;
30class UnityMenuModel;
31
31
@@ -43,7 +43,7 @@ public:
43 Q_INVOKABLE virtual bool contains(const QByteArray& path);
43 Q_INVOKABLE virtual bool contains(const QByteArray& path);
44
44
45protected:
45protected:
46− QHash<QByteArray, QWeakPointer<UnityMenuModel>> m_registry;
46+ QHash<QByteArray, QSharedPointer<UnityMenuModel>> m_registry;
47 static QPointer<UnityMenuModelCache> theCache;
47 static QPointer<UnityMenuModelCache> theCache;
48};
48};
49
49
plugins/Unity/Launcher/CMakeLists.txt+2−1
@@ -1,4 +1,4 @@
1−pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=5)
1+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=6)
2pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=5)
2pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=5)
3pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
3pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
4
4
@@ -20,6 +20,7 @@ set(QMLLAUNCHERPLUGIN_SRC
20 dbusinterface.cpp
20 dbusinterface.cpp
21 gsettings.cpp
21 gsettings.cpp
22 desktopfilehandler.cpp
22 desktopfilehandler.cpp
23+ asadapter.cpp
23 ${CMAKE_SOURCE_DIR}/plugins/AccountsService/AccountsServiceDBusAdaptor.cpp
24 ${CMAKE_SOURCE_DIR}/plugins/AccountsService/AccountsServiceDBusAdaptor.cpp
24 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherItemInterface.h
25 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherItemInterface.h
25 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherModelInterface.h
26 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherModelInterface.h
plugins/Unity/Launcher/asadapter.cppnew file+60
@@ -0,0 +1,60 @@
1+/*
2+ * Copyright 2014-2015 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "asadapter.h"
18+#include "launcheritem.h"
19+#include "AccountsServiceDBusAdaptor.h"
20+
21+#include <QDebug>
22+
23+ASAdapter::ASAdapter()
24+{
25+ m_accounts = new AccountsServiceDBusAdaptor();
26+ m_user = qgetenv("USER");
27+ if (m_user.isEmpty()) {
28+ qWarning() << "$USER not valid. Account Service integration will not work.";
29+ }
30+}
31+
32+ASAdapter::~ASAdapter()
33+{
34+ m_accounts->deleteLater();
35+}
36+
37+void ASAdapter::syncItems(QList<LauncherItem *> m_list)
38+{
39+ if (m_accounts && !m_user.isEmpty()) {
40+ QList<QVariantMap> items;
41+
42+ Q_FOREACH(LauncherItem *item, m_list) {
43+ items << itemToVariant(item);
44+ }
45+
46+ m_accounts->setUserPropertyAsync(m_user, "com.canonical.unity.AccountsService", "LauncherItems", QVariant::fromValue(items));
47+ }
48+}
49+
50+QVariantMap ASAdapter::itemToVariant(LauncherItem *item) const
51+{
52+ QVariantMap details;
53+ details.insert("id", item->appId());
54+ details.insert("name", item->name());
55+ details.insert("icon", item->icon());
56+ details.insert("count", item->count());
57+ details.insert("countVisible", item->countVisible());
58+ details.insert("pinned", item->pinned());
59+ return details;
60+}
tests/mocks/Unity/Notifications/MockNotificationTypes.h → plugins/Unity/Launcher/asadapter.hrenamed+24−14
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright 2014 Canonical Ltd.
2+ * Copyright 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 Lesser General Public License as published by
5 * it under the terms of the GNU Lesser General Public License as published by
@@ -12,25 +12,35 @@
12 *
12 *
13 * You should have received a copy of the GNU Lesser General Public License
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Authors:
17− * Mirco Mueller <mirco.mueller@canonical.com>
18 */
15 */
19
16
20−#ifndef MOCK_NOTIFICATION_TYPES_H
17+#ifndef ASADAPTER_H
21−#define MOCK_NOTIFICATION_TYPES_H
18+#define ASADAPTER_H
22
19
23−#include <QObject>
20+#include <QVariantMap>
24
21
25−class MockNotification : public QObject {
22+class LauncherItem;
26− Q_OBJECT
23+class AccountsServiceDBusAdaptor;
27− Q_ENUMS(Type)
24+class QDBusInterface;
28
25
26+class ASAdapter
27+{
29public:
28public:
30− MockNotification(QObject *parent=nullptr);
29+ ASAdapter();
31− virtual ~MockNotification();
30+ ~ASAdapter();
31+
32+ void syncItems(QList<LauncherItem*> m_list);
33+
34+private:
35+ QVariantMap itemToVariant(LauncherItem *item) const;
36+
37+private:
38+ AccountsServiceDBusAdaptor *m_accounts;
39+ QString m_user;
40+
41+ QDBusInterface *m_userInterface;
32
42
33− enum Type { PlaceHolder, Confirmation, Ephemeral, Interactive, SnapDecision };
43+ friend class LauncherModelTest;
34};
44};
35
45
36−#endif // MOCK_NOTIFICATION_TYPES_H
46+#endif
plugins/Unity/Launcher/launchermodel.cpp+20
@@ -22,6 +22,7 @@
22#include "gsettings.h"
22#include "gsettings.h"
23#include "desktopfilehandler.h"
23#include "desktopfilehandler.h"
24#include "dbusinterface.h"
24#include "dbusinterface.h"
25+#include "asadapter.h"
25
26
26#include <unity/shell/application/ApplicationInfoInterface.h>
27#include <unity/shell/application/ApplicationInfoInterface.h>
27
28
@@ -34,6 +35,7 @@ LauncherModel::LauncherModel(QObject *parent):
34 LauncherModelInterface(parent),
35 LauncherModelInterface(parent),
35 m_settings(new GSettings(this)),
36 m_settings(new GSettings(this)),
36 m_dbusIface(new DBusInterface(this)),
37 m_dbusIface(new DBusInterface(this)),
38+ m_asAdapter(new ASAdapter()),
37 m_appManager(0)
39 m_appManager(0)
38{
40{
39 connect(m_dbusIface, &DBusInterface::countChanged, this, &LauncherModel::countChanged);
41 connect(m_dbusIface, &DBusInterface::countChanged, this, &LauncherModel::countChanged);
@@ -50,6 +52,8 @@ LauncherModel::~LauncherModel()
50 while (!m_list.empty()) {
52 while (!m_list.empty()) {
51 m_list.takeFirst()->deleteLater();
53 m_list.takeFirst()->deleteLater();
52 }
54 }
55+
56+ delete m_asAdapter;
53}
57}
54
58
55int LauncherModel::rowCount(const QModelIndex &parent) const
59int LauncherModel::rowCount(const QModelIndex &parent) const
@@ -261,6 +265,15 @@ void LauncherModel::setApplicationManager(unity::shell::application::Application
261 }
265 }
262}
266}
263
267
268+bool LauncherModel::onlyPinned() const
269+{
270+ return false;
271+}
272+
273+void LauncherModel::setOnlyPinned(bool onlyPinned) {
274+ Q_UNUSED(onlyPinned);
275+ qWarning() << "This launcher implementation does not support showing only pinned apps";
276+}
264
277
265void LauncherModel::storeAppList()
278void LauncherModel::storeAppList()
266{
279{
@@ -271,6 +284,7 @@ void LauncherModel::storeAppList()
271 }
284 }
272 }
285 }
273 m_settings->setStoredApplications(appIds);
286 m_settings->setStoredApplications(appIds);
287+ m_asAdapter->syncItems(m_list);
274}
288}
275
289
276void LauncherModel::unpin(const QString &appId)
290void LauncherModel::unpin(const QString &appId)
@@ -321,6 +335,7 @@ void LauncherModel::countChanged(const QString &appId, int count)
321 LauncherItem *item = m_list.at(idx);
335 LauncherItem *item = m_list.at(idx);
322 item->setCount(count);
336 item->setCount(count);
323 Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleCount);
337 Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleCount);
338+ m_asAdapter->syncItems(m_list);
324 }
339 }
325}
340}
326
341
@@ -352,6 +367,7 @@ void LauncherModel::countVisibleChanged(const QString &appId, int countVisible)
352 Q_EMIT hint();
367 Q_EMIT hint();
353 }
368 }
354 }
369 }
370+ m_asAdapter->syncItems(m_list);
355}
371}
356
372
357void LauncherModel::refresh()
373void LauncherModel::refresh()
@@ -433,6 +449,8 @@ void LauncherModel::refresh()
433 if (changed) {
449 if (changed) {
434 Q_EMIT hint();
450 Q_EMIT hint();
435 }
451 }
452+
453+ m_asAdapter->syncItems(m_list);
436}
454}
437
455
438void LauncherModel::applicationAdded(const QModelIndex &parent, int row)
456void LauncherModel::applicationAdded(const QModelIndex &parent, int row)
@@ -467,6 +485,7 @@ void LauncherModel::applicationAdded(const QModelIndex &parent, int row)
467 beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
485 beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
468 m_list.append(item);
486 m_list.append(item);
469 endInsertRows();
487 endInsertRows();
488+ m_asAdapter->syncItems(m_list);
470 }
489 }
471}
490}
472
491
@@ -486,6 +505,7 @@ void LauncherModel::applicationRemoved(const QModelIndex &parent, int row)
486 beginRemoveRows(QModelIndex(), appIndex, appIndex);
505 beginRemoveRows(QModelIndex(), appIndex, appIndex);
487 m_list.takeAt(appIndex)->deleteLater();
506 m_list.takeAt(appIndex)->deleteLater();
488 endRemoveRows();
507 endRemoveRows();
508+ m_asAdapter->syncItems(m_list);
489 }
509 }
490}
510}
491
511
plugins/Unity/Launcher/launchermodel.h+5
@@ -28,6 +28,7 @@
28class LauncherItem;
28class LauncherItem;
29class GSettings;
29class GSettings;
30class DBusInterface;
30class DBusInterface;
31+class ASAdapter;
31
32
32using namespace unity::shell::launcher;
33using namespace unity::shell::launcher;
33using namespace unity::shell::application;
34using namespace unity::shell::application;
@@ -54,6 +55,9 @@ public:
54 unity::shell::application::ApplicationManagerInterface* applicationManager() const;
55 unity::shell::application::ApplicationManagerInterface* applicationManager() const;
55 void setApplicationManager(unity::shell::application::ApplicationManagerInterface *appManager);
56 void setApplicationManager(unity::shell::application::ApplicationManagerInterface *appManager);
56
57
58+ bool onlyPinned() const override;
59+ void setOnlyPinned(bool onlyPinned) override;
60+
57 int findApplication(const QString &appId);
61 int findApplication(const QString &appId);
58
62
59public Q_SLOTS:
63public Q_SLOTS:
@@ -79,6 +83,7 @@ private:
79
83
80 GSettings *m_settings;
84 GSettings *m_settings;
81 DBusInterface *m_dbusIface;
85 DBusInterface *m_dbusIface;
86+ ASAdapter *m_asAdapter;
82
87
83 ApplicationManagerInterface *m_appManager;
88 ApplicationManagerInterface *m_appManager;
84
89
plugins/Utils/CMakeLists.txt+2−1
@@ -15,6 +15,7 @@ set(QMLPLUGIN_SRC
15 unitymenumodelpaths.cpp
15 unitymenumodelpaths.cpp
16 windowkeysfilter.cpp
16 windowkeysfilter.cpp
17 easingcurve.cpp
17 easingcurve.cpp
18+ windowstatestorage.cpp
18 plugin.cpp
19 plugin.cpp
19 )
20 )
20
21
@@ -30,6 +31,6 @@ target_link_libraries(Utils-qml ${GIO_LDFLAGS})
30# files directly in targets.
31# files directly in targets.
31set_target_properties(Utils-qml PROPERTIES COMPILE_FLAGS -fvisibility=default)
32set_target_properties(Utils-qml PROPERTIES COMPILE_FLAGS -fvisibility=default)
32
33
33−qt5_use_modules(Utils-qml Qml Quick DBus Network Gui)
34+qt5_use_modules(Utils-qml Qml Quick DBus Network Gui Sql Concurrent)
34
35
35add_unity8_plugin(Utils 0.1 Utils TARGETS Utils-qml)
36add_unity8_plugin(Utils 0.1 Utils TARGETS Utils-qml)
plugins/Utils/plugin.cpp+9
@@ -33,6 +33,14 @@
33#include "unitymenumodelpaths.h"
33#include "unitymenumodelpaths.h"
34#include "windowkeysfilter.h"
34#include "windowkeysfilter.h"
35#include "easingcurve.h"
35#include "easingcurve.h"
36+#include "windowstatestorage.h"
37+
38+static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
39+{
40+ Q_UNUSED(engine)
41+ Q_UNUSED(scriptEngine)
42+ return new WindowStateStorage();
43+}
36
44
37void UtilsPlugin::registerTypes(const char *uri)
45void UtilsPlugin::registerTypes(const char *uri)
38{
46{
@@ -46,6 +54,7 @@ void UtilsPlugin::registerTypes(const char *uri)
46 qmlRegisterType<GDateTimeFormatter>(uri, 0, 1, "GDateTimeFormatter");
54 qmlRegisterType<GDateTimeFormatter>(uri, 0, 1, "GDateTimeFormatter");
47 qmlRegisterType<EasingCurve>(uri, 0, 1, "EasingCurve");
55 qmlRegisterType<EasingCurve>(uri, 0, 1, "EasingCurve");
48 qmlRegisterType<RelativeTimeFormatter>(uri, 0, 1, "RelativeTimeFormatter");
56 qmlRegisterType<RelativeTimeFormatter>(uri, 0, 1, "RelativeTimeFormatter");
57+ qmlRegisterSingletonType<WindowStateStorage>(uri, 0, 1, "WindowStateStorage", createWindowStateStorage);
49}
58}
50
59
51void UtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
60void UtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
plugins/Utils/windowstatestorage.cppnew file+96
@@ -0,0 +1,96 @@
1+/*
2+ * Copyright 2015 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "windowstatestorage.h"
18+
19+#include <QtConcurrent>
20+#include <QDebug>
21+#include <QSqlQuery>
22+#include <QSqlError>
23+#include <QSqlResult>
24+#include <QRect>
25+
26+QMutex WindowStateStorage::s_mutex;
27+
28+WindowStateStorage::WindowStateStorage(QObject *parent):
29+ QObject(parent)
30+{
31+ QString dbPath = QDir::homePath() + "/.cache/unity8/";
32+ m_db = QSqlDatabase::addDatabase("QSQLITE");
33+ QDir dir;
34+ dir.mkpath(dbPath);
35+ m_db.setDatabaseName(dbPath + "windowstatestorage.sqlite");
36+ initdb();
37+}
38+
39+void WindowStateStorage::saveGeometry(const QString &windowId, const QRect &rect)
40+{
41+ QString queryString = QString("INSERT OR REPLACE INTO geometry (windowId, x, y, width, height) values ('%1', '%2', '%3', '%4', '%5');")
42+ .arg(windowId)
43+ .arg(rect.x())
44+ .arg(rect.y())
45+ .arg(rect.width())
46+ .arg(rect.height());
47+
48+ QtConcurrent::run(executeAsyncQuery, queryString);
49+}
50+
51+void WindowStateStorage::executeAsyncQuery(const QString &queryString)
52+{
53+ QMutexLocker l(&s_mutex);
54+ QSqlQuery query;
55+
56+ bool ok = query.exec(queryString);
57+ if (!ok) {
58+ qWarning() << "Error executing query" << queryString
59+ << "Driver error:" << query.lastError().driverText()
60+ << "Database error:" << query.lastError().databaseText();
61+ }
62+}
63+
64+QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue)
65+{
66+ QMutexLocker l(&s_mutex);
67+ QString queryString = QString("SELECT * FROM geometry WHERE windowId = '%1';")
68+ .arg(windowId);
69+ QSqlQuery query;
70+
71+ bool ok = query.exec(queryString);
72+ if (!ok) {
73+ qWarning() << "Error retrieving window state for" << windowId
74+ << "Driver error:" << query.lastError().driverText()
75+ << "Database error:" << query.lastError().databaseText();
76+ return defaultValue;
77+ }
78+ if (!query.first()) {
79+ return defaultValue;
80+ }
81+ return QRect(query.value("x").toInt(), query.value("y").toInt(), query.value("width").toInt(), query.value("height").toInt());
82+}
83+
84+void WindowStateStorage::initdb()
85+{
86+ m_db.open();
87+ if (!m_db.open()) {
88+ qWarning() << "Error opening state database:" << m_db.lastError().driverText() << m_db.lastError().databaseText();
89+ return;
90+ }
91+
92+ if (!m_db.tables().contains("geometry")) {
93+ QSqlQuery query;
94+ query.exec("CREATE TABLE geometry(windowId TEXT UNIQUE, x INTEGER, y INTEGER, width INTEGER, height INTEGER);");
95+ }
96+}
plugins/Utils/windowstatestorage.hnew file+38
@@ -0,0 +1,38 @@
1+/*
2+ * Copyright 2015 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include <QObject>
18+#include <QSqlDatabase>
19+#include <QMutex>
20+
21+class WindowStateStorage: public QObject
22+{
23+ Q_OBJECT
24+public:
25+ WindowStateStorage(QObject *parent = 0);
26+
27+ Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect);
28+ Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue);
29+
30+private:
31+ void initdb();
32+
33+ static void executeAsyncQuery(const QString &queryString);
34+ static QMutex s_mutex;
35+
36+ // NB: This is accessed from threads. Make sure to mutex it.
37+ QSqlDatabase m_db;
38+};
po/gd.po+45−40
@@ -8,7 +8,7 @@ msgstr ""
8"Project-Id-Version: unity8\n"
8"Project-Id-Version: unity8\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2015-01-22 11:09+0100\n"
10"POT-Creation-Date: 2015-01-22 11:09+0100\n"
11−"PO-Revision-Date: 2014-11-09 08:18+0000\n"
11+"PO-Revision-Date: 2015-02-11 15:01+0000\n"
12"Last-Translator: GunChleoc <Unknown>\n"
12"Last-Translator: GunChleoc <Unknown>\n"
13"Language-Team: Fòram na Gàidhlig\n"
13"Language-Team: Fòram na Gàidhlig\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
@@ -16,8 +16,8 @@ msgstr ""
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=4; plural=(n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : "
17"Plural-Forms: nplurals=4; plural=(n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : "
18"(n > 2 && n < 20) ? 2 : 3;\n"
18"(n > 2 && n < 20) ? 2 : 3;\n"
19−"X-Launchpad-Export-Date: 2015-01-28 06:30+0000\n"
19+"X-Launchpad-Export-Date: 2015-02-13 06:54+0000\n"
20−"X-Generator: Launchpad (build 17306)\n"
20+"X-Generator: Launchpad (build 17336)\n"
21"Language: gd\n"
21"Language: gd\n"
22
22
23#: plugins/LightDM/Greeter.cpp:130
23#: plugins/LightDM/Greeter.cpp:130
@@ -241,11 +241,11 @@ msgstr "Leig às airson ath-nuadhachadh…"
241
241
242#: qml/Dash/ScopesList.qml:66
242#: qml/Dash/ScopesList.qml:66
243msgid "Manage"
243msgid "Manage"
244−msgstr ""
244+msgstr "Stiùirich"
245
245
246#: qml/Dash/ScopesList.qml:112
246#: qml/Dash/ScopesList.qml:112
247msgid "Home"
247msgid "Home"
248−msgstr ""
248+msgstr "Dhachaigh"
249
249
250#: qml/Dash/ScopesList.qml:113
250#: qml/Dash/ScopesList.qml:113
251msgid "Also installed"
251msgid "Also installed"
@@ -297,7 +297,7 @@ msgstr "Dh'fhàillig leis, thoir gnogag gus feuchainn a-rithist"
297
297
298#: qml/Panel/Indicators/MessageMenuItemFactory.qml:149
298#: qml/Panel/Indicators/MessageMenuItemFactory.qml:149
299msgid "Reply"
299msgid "Reply"
300−msgstr ""
300+msgstr "Freagair"
301
301
302#: qml/Panel/Indicators/ModemInfoItem.qml:105
302#: qml/Panel/Indicators/ModemInfoItem.qml:105
303msgid "Unlock SIM"
303msgid "Unlock SIM"
@@ -369,58 +369,58 @@ msgstr ""
369
369
370#: qml/Wizard/Page.qml:89
370#: qml/Wizard/Page.qml:89
371msgid "Back"
371msgid "Back"
372−msgstr ""
372+msgstr "Air ais"
373
373
374#: qml/Wizard/Pages/10-welcome.qml:27
374#: qml/Wizard/Pages/10-welcome.qml:27
375msgid "Hi!"
375msgid "Hi!"
376−msgstr ""
376+msgstr "Shin thu!"
377
377
378#: qml/Wizard/Pages/10-welcome.qml:44
378#: qml/Wizard/Pages/10-welcome.qml:44
379msgid "Welcome to your Ubuntu phone."
379msgid "Welcome to your Ubuntu phone."
380−msgstr ""
380+msgstr "Fàilte dhan fhòn Ubuntu agad."
381
381
382#: qml/Wizard/Pages/10-welcome.qml:52
382#: qml/Wizard/Pages/10-welcome.qml:52
383msgid "Let’s get started."
383msgid "Let’s get started."
384−msgstr ""
384+msgstr "Tòisicheamaid."
385
385
386#: qml/Wizard/Pages/10-welcome.qml:88 qml/Wizard/Pages/30-passwd-type.qml:128
386#: qml/Wizard/Pages/10-welcome.qml:88 qml/Wizard/Pages/30-passwd-type.qml:128
387#: qml/Wizard/Pages/40-wifi.qml:197 qml/Wizard/Pages/50-location.qml:131
387#: qml/Wizard/Pages/40-wifi.qml:197 qml/Wizard/Pages/50-location.qml:131
388#: qml/Wizard/Pages/60-reporting.qml:50 qml/Wizard/Pages/passwd-confirm.qml:82
388#: qml/Wizard/Pages/60-reporting.qml:50 qml/Wizard/Pages/passwd-confirm.qml:82
389#: qml/Wizard/Pages/passwd-set.qml:90
389#: qml/Wizard/Pages/passwd-set.qml:90
390msgid "Continue"
390msgid "Continue"
391−msgstr ""
391+msgstr "Lean air adhart"
392
392
393#: qml/Wizard/Pages/20-sim.qml:25
393#: qml/Wizard/Pages/20-sim.qml:25
394msgid "Add a SIM card and restart your device"
394msgid "Add a SIM card and restart your device"
395−msgstr ""
395+msgstr "Cuir cairt SIM ris 's tòisich an uidheam agad às ùr"
396
396
397#: qml/Wizard/Pages/20-sim.qml:55
397#: qml/Wizard/Pages/20-sim.qml:55
398msgid "Without it, you won’t be able to make calls or use text messaging."
398msgid "Without it, you won’t be able to make calls or use text messaging."
399−msgstr ""
399+msgstr "Chan urrainn dhut gairm no teachdaireachd a chur às a h-aonais."
400
400
401#: qml/Wizard/Pages/20-sim.qml:69 qml/Wizard/Pages/40-wifi.qml:197
401#: qml/Wizard/Pages/20-sim.qml:69 qml/Wizard/Pages/40-wifi.qml:197
402msgid "Skip"
402msgid "Skip"
403−msgstr ""
403+msgstr "Leum thairis air"
404
404
405#: qml/Wizard/Pages/30-passwd-type.qml:39
405#: qml/Wizard/Pages/30-passwd-type.qml:39
406msgid "Lock security"
406msgid "Lock security"
407−msgstr ""
407+msgstr "Glas tèarainteachd"
408
408
409#: qml/Wizard/Pages/30-passwd-type.qml:74
409#: qml/Wizard/Pages/30-passwd-type.qml:74
410msgid "Please select how you’d like to unlock your phone."
410msgid "Please select how you’d like to unlock your phone."
411−msgstr ""
411+msgstr "Tagh an dòigh sa tha thu airson a' ghlas a thoirt far an fhòn agad."
412
412
413#: qml/Wizard/Pages/30-passwd-type.qml:97
413#: qml/Wizard/Pages/30-passwd-type.qml:97
414msgid "Swipe"
414msgid "Swipe"
415−msgstr ""
415+msgstr "Grad-shlaighd"
416
416
417#: qml/Wizard/Pages/30-passwd-type.qml:98
417#: qml/Wizard/Pages/30-passwd-type.qml:98
418msgid "No security"
418msgid "No security"
419−msgstr ""
419+msgstr "Gun tèarainteachd"
420
420
421#: qml/Wizard/Pages/30-passwd-type.qml:100
421#: qml/Wizard/Pages/30-passwd-type.qml:100
422msgid "Passcode"
422msgid "Passcode"
423−msgstr ""
423+msgstr "Còd-faire"
424
424
425#: qml/Wizard/Pages/30-passwd-type.qml:101
425#: qml/Wizard/Pages/30-passwd-type.qml:101
426msgid "4 digits only"
426msgid "4 digits only"
@@ -428,31 +428,31 @@ msgstr ""
428
428
429#: qml/Wizard/Pages/30-passwd-type.qml:103
429#: qml/Wizard/Pages/30-passwd-type.qml:103
430msgid "Passphrase"
430msgid "Passphrase"
431−msgstr ""
431+msgstr "Abairt-fhaire"
432
432
433#: qml/Wizard/Pages/30-passwd-type.qml:104
433#: qml/Wizard/Pages/30-passwd-type.qml:104
434msgid "Numbers and letters"
434msgid "Numbers and letters"
435−msgstr ""
435+msgstr "Àireamhan 's litrichean"
436
436
437#: qml/Wizard/Pages/40-wifi.qml:28
437#: qml/Wizard/Pages/40-wifi.qml:28
438msgid "Connect to Wi‑Fi"
438msgid "Connect to Wi‑Fi"
439−msgstr ""
439+msgstr "Ceangail ri Wi-Fi"
440
440
441#: qml/Wizard/Pages/40-wifi.qml:147
441#: qml/Wizard/Pages/40-wifi.qml:147
442msgid "Available networks…"
442msgid "Available networks…"
443−msgstr ""
443+msgstr "Lìonraidhean ri làimh…"
444
444
445#: qml/Wizard/Pages/40-wifi.qml:148
445#: qml/Wizard/Pages/40-wifi.qml:148
446msgid "No available networks."
446msgid "No available networks."
447−msgstr ""
447+msgstr "Chan eil lìonra ri làimh."
448
448
449#: qml/Wizard/Pages/50-location.qml:27
449#: qml/Wizard/Pages/50-location.qml:27
450msgid "Location"
450msgid "Location"
451−msgstr ""
451+msgstr "Àite"
452
452
453#: qml/Wizard/Pages/50-location.qml:62
453#: qml/Wizard/Pages/50-location.qml:62
454msgid "Let the phone detect your location:"
454msgid "Let the phone detect your location:"
455−msgstr ""
455+msgstr "Faodaidh am fòn aithneachadh far a bheil thu:"
456
456
457#: qml/Wizard/Pages/50-location.qml:69
457#: qml/Wizard/Pages/50-location.qml:69
458msgid "Using GPS only (less accurate)"
458msgid "Using GPS only (less accurate)"
@@ -471,7 +471,7 @@ msgstr ""
471
471
472#: qml/Wizard/Pages/50-location.qml:112
472#: qml/Wizard/Pages/50-location.qml:112
473msgid "Not at all"
473msgid "Not at all"
474−msgstr ""
474+msgstr "Chan fhaod idir"
475
475
476#: qml/Wizard/Pages/50-location.qml:124
476#: qml/Wizard/Pages/50-location.qml:124
477msgid "You can change your mind later in <b>System Settings</b>."
477msgid "You can change your mind later in <b>System Settings</b>."
@@ -479,70 +479,75 @@ msgstr ""
479
479
480#: qml/Wizard/Pages/60-reporting.qml:24
480#: qml/Wizard/Pages/60-reporting.qml:24
481msgid "Improving your experience"
481msgid "Improving your experience"
482−msgstr ""
482+msgstr "A' cur piseach air a' ghnàth-eòlas agad"
483
483
484#: qml/Wizard/Pages/60-reporting.qml:36
484#: qml/Wizard/Pages/60-reporting.qml:36
485msgid ""
485msgid ""
486"Your phone is set up to automatically report errors to Canonical and its "
486"Your phone is set up to automatically report errors to Canonical and its "
487"partners, the makers of the operating system."
487"partners, the makers of the operating system."
488msgstr ""
488msgstr ""
489+"Chaidh am fòn agad a shuidheachadh gus innse mu mhearachdan gu fèin-"
490+"obrachail do Chanonical 's a chom-pàirtichean a rinn an siostam-obrachaidh "
491+"seo."
489
492
490#: qml/Wizard/Pages/60-reporting.qml:43
493#: qml/Wizard/Pages/60-reporting.qml:43
491msgid ""
494msgid ""
492"This can be disabled in <b>System Settings</b> under <b>Security &amp; "
495"This can be disabled in <b>System Settings</b> under <b>Security &amp; "
493"Privacy</b>"
496"Privacy</b>"
494msgstr ""
497msgstr ""
498+"Gabhaidh seo cur à comas ann an <b>Roghainnean an t-siostaim</b> fo "
499+"<b>Thèarainteachd ⁊ prìobhaideachd</b>"
495
500
496#: qml/Wizard/Pages/80-finished.qml:24
501#: qml/Wizard/Pages/80-finished.qml:24
497msgid "All done"
502msgid "All done"
498−msgstr ""
503+msgstr "Coileanta"
499
504
500#: qml/Wizard/Pages/80-finished.qml:39
505#: qml/Wizard/Pages/80-finished.qml:39
501msgid "Nice work!"
506msgid "Nice work!"
502−msgstr ""
507+msgstr "Shin thu!"
503
508
504#: qml/Wizard/Pages/80-finished.qml:46
509#: qml/Wizard/Pages/80-finished.qml:46
505msgid "Your phone is now ready to use."
510msgid "Your phone is now ready to use."
506−msgstr ""
511+msgstr "Gabhaidh am fòn agad cleachdadh a-nis."
507
512
508#: qml/Wizard/Pages/80-finished.qml:53
513#: qml/Wizard/Pages/80-finished.qml:53
509msgid "Finish"
514msgid "Finish"
510−msgstr ""
515+msgstr "Crìochnaich"
511
516
512#: qml/Wizard/Pages/here-terms.qml:27
517#: qml/Wizard/Pages/here-terms.qml:27
513msgid "Terms & Conditions"
518msgid "Terms & Conditions"
514−msgstr ""
519+msgstr "Teirmichean ⁊ cumhaichean"
515
520
516#: qml/Wizard/Pages/passwd-confirm.qml:49
521#: qml/Wizard/Pages/passwd-confirm.qml:49
517msgid "Confirm passphrase"
522msgid "Confirm passphrase"
518−msgstr ""
523+msgstr "Dearbhaich an abairt-fhaire"
519
524
520#: qml/Wizard/Pages/passwd-confirm.qml:50
525#: qml/Wizard/Pages/passwd-confirm.qml:50
521msgid "Confirm passcode"
526msgid "Confirm passcode"
522−msgstr ""
527+msgstr "Dearbhaich an còd-faire"
523
528
524#: qml/Wizard/Pages/passwd-confirm.qml:53
529#: qml/Wizard/Pages/passwd-confirm.qml:53
525#: qml/Wizard/Pages/passwd-confirm.qml:54
530#: qml/Wizard/Pages/passwd-confirm.qml:54
526msgid "Please try again."
531msgid "Please try again."
527−msgstr ""
532+msgstr "Feuch ris a-rithist."
528
533
529#: qml/Wizard/Pages/passwd-set.qml:61
534#: qml/Wizard/Pages/passwd-set.qml:61
530msgid "Choose your passcode"
535msgid "Choose your passcode"
531−msgstr ""
536+msgstr "Tagh an còd-faire agad"
532
537
533#: qml/Wizard/Pages/passwd-set.qml:68
538#: qml/Wizard/Pages/passwd-set.qml:68
534msgid "Passphrase must be 4 characters long"
539msgid "Passphrase must be 4 characters long"
535−msgstr ""
540+msgstr "Feumaidh an abairt-fhaire a bhith 4 caractaran a dh'fhaid"
536
541
537#: qml/Wizard/StackButton.qml:39
542#: qml/Wizard/StackButton.qml:39
538#, qt-format
543#, qt-format
539msgid "〈 %1"
544msgid "〈 %1"
540−msgstr ""
545+msgstr "〈 %1"
541
546
542#: qml/Wizard/StackButton.qml:42
547#: qml/Wizard/StackButton.qml:42
543#, qt-format
548#, qt-format
544msgid "%1 〉"
549msgid "%1 〉"
545−msgstr ""
550+msgstr "%1 〉"
546
551
547#~ msgid "Confirm"
552#~ msgid "Confirm"
548#~ msgstr "Dearbhaich"
553#~ msgstr "Dearbhaich"
po/gl.po+7−7
@@ -8,15 +8,15 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2015-01-22 11:09+0100\n"
10"POT-Creation-Date: 2015-01-22 11:09+0100\n"
11−"PO-Revision-Date: 2015-01-28 10:25+0000\n"
11+"PO-Revision-Date: 2015-02-11 10:36+0000\n"
12"Last-Translator: Marcos Lans <Unknown>\n"
12"Last-Translator: Marcos Lans <Unknown>\n"
13"Language-Team: Galician <gl@li.org>\n"
13"Language-Team: Galician <gl@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2015-01-29 06:00+0000\n"
18+"X-Launchpad-Export-Date: 2015-02-13 06:54+0000\n"
19−"X-Generator: Launchpad (build 17306)\n"
19+"X-Generator: Launchpad (build 17336)\n"
20
20
21#: plugins/LightDM/Greeter.cpp:130
21#: plugins/LightDM/Greeter.cpp:130
22msgid "Password: "
22msgid "Password: "
@@ -56,7 +56,7 @@ msgstr "Saír da sesión"
56
56
57#: qml/Components/Dialogs.qml:87
57#: qml/Components/Dialogs.qml:87
58msgid "Are you sure you want to log out?"
58msgid "Are you sure you want to log out?"
59−msgstr "Está seguro de querer pechar a sesión?"
59+msgstr "Confirma que desexa pechar a sesión?"
60
60
61#: qml/Components/Dialogs.qml:89 qml/Components/Dialogs.qml:111
61#: qml/Components/Dialogs.qml:89 qml/Components/Dialogs.qml:111
62#: qml/Components/Dialogs.qml:134
62#: qml/Components/Dialogs.qml:134
@@ -74,7 +74,7 @@ msgstr "Apagar"
74
74
75#: qml/Components/Dialogs.qml:109
75#: qml/Components/Dialogs.qml:109
76msgid "Are you sure you want to shut down?"
76msgid "Are you sure you want to shut down?"
77−msgstr "Está seguro de querer apagar?"
77+msgstr "Confirma que desexa apagar?"
78
78
79#: qml/Components/Dialogs.qml:131
79#: qml/Components/Dialogs.qml:131
80msgid "Reboot"
80msgid "Reboot"
@@ -82,7 +82,7 @@ msgstr "Reiniciar"
82
82
83#: qml/Components/Dialogs.qml:132
83#: qml/Components/Dialogs.qml:132
84msgid "Are you sure you want to reboot?"
84msgid "Are you sure you want to reboot?"
85−msgstr "Está seguro de querer reiniciar?"
85+msgstr "Confirma que desexa reiniciar?"
86
86
87#: qml/Components/Dialogs.qml:154
87#: qml/Components/Dialogs.qml:154
88msgid "Power"
88msgid "Power"
@@ -92,7 +92,7 @@ msgstr "Enerxía"
92msgid ""
92msgid ""
93"Are you sure you would like\n"
93"Are you sure you would like\n"
94"to power off?"
94"to power off?"
95−msgstr "Está seguro de querer apagar?"
95+msgstr "Confirma que desexa apagar?"
96
96
97#: qml/Components/Dialogs.qml:157
97#: qml/Components/Dialogs.qml:157
98msgid "Power off"
98msgid "Power off"
po/nl.po+4−4
@@ -8,15 +8,15 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2015-01-22 11:09+0100\n"
10"POT-Creation-Date: 2015-01-22 11:09+0100\n"
11−"PO-Revision-Date: 2015-01-17 11:13+0000\n"
11+"PO-Revision-Date: 2015-02-12 20:12+0000\n"
12"Last-Translator: rob <linuxned@gmail.com>\n"
12"Last-Translator: rob <linuxned@gmail.com>\n"
13"Language-Team: Dutch <nl@li.org>\n"
13"Language-Team: Dutch <nl@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2015-01-28 06:30+0000\n"
18+"X-Launchpad-Export-Date: 2015-02-13 06:54+0000\n"
19−"X-Generator: Launchpad (build 17306)\n"
19+"X-Generator: Launchpad (build 17336)\n"
20
20
21#: plugins/LightDM/Greeter.cpp:130
21#: plugins/LightDM/Greeter.cpp:130
22msgid "Password: "
22msgid "Password: "
@@ -287,7 +287,7 @@ msgstr "Mislukt, tik om het opnieuw te proberen"
287
287
288#: qml/Panel/Indicators/MessageMenuItemFactory.qml:149
288#: qml/Panel/Indicators/MessageMenuItemFactory.qml:149
289msgid "Reply"
289msgid "Reply"
290−msgstr ""
290+msgstr "Beantwoorden"
291
291
292#: qml/Panel/Indicators/ModemInfoItem.qml:105
292#: qml/Panel/Indicators/ModemInfoItem.qml:105
293msgid "Unlock SIM"
293msgid "Unlock SIM"
qml/CMakeLists.txt+3−2
@@ -9,10 +9,11 @@ set(QML_DIRS
9 Dash
9 Dash
10 graphics
10 graphics
11 Greeter
11 Greeter
12− Panel
13 Launcher
12 Launcher
14− Stages
15 Notifications
13 Notifications
14+ Panel
15+ Stages
16+ Tutorial
16 Wizard
17 Wizard
17 )
18 )
18
19
qml/Components/EdgeDemo.qmldeleted−254
@@ -1,254 +0,0 @@
1−/*
2− * Copyright (C) 2013 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 AccountsService 0.1
18−import LightDM 0.1 as LightDM
19−import QtQuick 2.0
20−import Ubuntu.Components 1.1
21−
22−Item {
23− id: demo
24−
25− property Item greeter
26− property Item launcher
27− property Item panel
28− property Item stages
29−
30− property bool launcherEnabled: true
31− property bool stagesEnabled: true
32− property bool panelEnabled: true
33− property bool panelContentEnabled: true
34− property bool running: !launcherEnabled || !stagesEnabled || !panelEnabled || !panelContentEnabled
35−
36− property bool paused: false
37−
38− onPausedChanged: {
39− if (d.rightEdgeDemo) d.rightEdgeDemo.paused = paused
40− if (d.topEdgeDemo) d.topEdgeDemo.paused = paused
41− if (d.bottomEdgeDemo) d.bottomEdgeDemo.paused = paused
42− if (d.leftEdgeDemo) d.leftEdgeDemo.paused = paused
43− if (d.finalEdgeDemo) d.finalEdgeDemo.paused = paused
44− }
45−
46− function hideEdgeDemoInShell() {
47− AccountsService.demoEdges = false;
48− stopDemo();
49− }
50−
51− function hideEdgeDemoInGreeter() {
52− // TODO: AccountsService.demoEdges = false as lightdm user
53− }
54−
55− function hideEdgeDemos() {
56− hideEdgeDemoInGreeter();
57− hideEdgeDemoInShell();
58− }
59−
60− function stopDemo() {
61− launcherEnabled = true
62− stagesEnabled = true
63− panelEnabled = true
64− panelContentEnabled = true
65−
66− // Use a tiny delay for these destroy() calls because if a lot is
67− // happening at once (like creating and being destroyed in same event
68− // loop, as might happen when answering a call while demo is open),
69− // the destroy() call will be ignored.
70− if (d.rightEdgeDemo) d.rightEdgeDemo.destroy(1);
71− if (d.topEdgeDemo) d.topEdgeDemo.destroy(1);
72− if (d.bottomEdgeDemo) d.bottomEdgeDemo.destroy(1);
73− if (d.leftEdgeDemo) d.leftEdgeDemo.destroy(1);
74− if (d.finalEdgeDemo) d.finalEdgeDemo.destroy(1);
75− }
76−
77− function startDemo() {
78− if (!d.overlay) {
79− d.overlay = Qt.createComponent("EdgeDemoOverlay.qml")
80− }
81−
82− launcherEnabled = false;
83− stagesEnabled = false;
84− panelEnabled = false;
85− panelContentEnabled = false;
86−
87− // Begin with either greeter or top, depending on which is visible
88− if (greeter && greeter.shown) {
89− startRightEdgeDemo()
90− } else {
91− startTopEdgeDemo()
92− }
93− }
94−
95− QtObject {
96− id: d
97− property Component overlay
98− property QtObject rightEdgeDemo
99− property QtObject topEdgeDemo
100− property QtObject bottomEdgeDemo
101− property QtObject leftEdgeDemo
102− property QtObject finalEdgeDemo
103− property bool showEdgeDemo: AccountsService.demoEdges
104− property bool showEdgeDemoInGreeter: AccountsService.demoEdges // TODO: AccountsService.demoEdges as lightdm user
105−
106− function restartDemo() {
107− stopDemo()
108− if (d.showEdgeDemo) {
109− startDemo()
110− }
111− }
112−
113− onShowEdgeDemoChanged: restartDemo()
114− }
115−
116− Connections {
117− target: i18n
118− onLanguageChanged: d.restartDemo()
119− }
120−
121− function startRightEdgeDemo() {
122− if (demo.greeter) {
123− d.rightEdgeDemo = d.overlay.createObject(demo.greeter, {
124− "edge": "right",
125− "title": i18n.tr("Right edge"),
126− "text": i18n.tr("Try swiping from the right edge to unlock the phone"),
127− "anchors.fill": demo.greeter,
128− });
129− }
130− if (d.rightEdgeDemo) {
131− d.rightEdgeDemo.onSkip.connect(demo.hideEdgeDemos)
132− } else {
133− stopDemo();
134− }
135− }
136−
137− Connections {
138− target: demo.greeter
139−
140− function hide() {
141− if (d.rightEdgeDemo && d.rightEdgeDemo.available) {
142− d.rightEdgeDemo.hide()
143− hideEdgeDemoInGreeter()
144− startTopEdgeDemo()
145− }
146− }
147−
148− onUnlocked: hide()
149− onShownChanged: if (!greeter.shown) hide()
150− }
151−
152− function startTopEdgeDemo() {
153− demo.panelEnabled = true;
154− if (demo.stages) {
155− d.topEdgeDemo = d.overlay.createObject(demo.panel, {
156− "edge": "top",
157− "title": i18n.tr("Top edge"),
158− "text": i18n.tr("Try swiping from the top edge to access the indicators"),
159− "anchors.fill": demo.panel,
160− });
161− }
162− if (d.topEdgeDemo) {
163− d.topEdgeDemo.onSkip.connect(demo.hideEdgeDemoInShell)
164− } else {
165− stopDemo();
166− }
167− }
168−
169− Connections {
170− target: demo.panel.indicators
171− onFullyOpenedChanged: {
172− if (d.topEdgeDemo && d.topEdgeDemo.available && demo.panel.indicators.fullyOpened) {
173− d.topEdgeDemo.hideNow()
174− startBottomEdgeDemo()
175− }
176− }
177− }
178−
179− function startBottomEdgeDemo() {
180− if (demo.panel.indicators) {
181− d.bottomEdgeDemo = d.overlay.createObject(demo.panel.indicators, {
182− "edge": "bottom",
183− "title": i18n.tr("Close"),
184− "text": i18n.tr("Swipe up again to close the settings screen"),
185− "anchors.fill": demo.panel.indicators,
186− });
187− }
188− if (d.bottomEdgeDemo) {
189− d.bottomEdgeDemo.onSkip.connect(demo.hideEdgeDemoInShell)
190− } else {
191− stopDemo();
192− }
193− }
194−
195− Connections {
196− target: demo.panel.indicators
197− onPartiallyOpenedChanged: {
198− if (d.bottomEdgeDemo &&
199− d.bottomEdgeDemo.available &&
200− !demo.panel.indicators.partiallyOpened &&
201− !demo.panel.indicators.fullyOpened) {
202− d.bottomEdgeDemo.hideNow()
203− startLeftEdgeDemo()
204− }
205− }
206− }
207−
208− function startLeftEdgeDemo() {
209− demo.panelEnabled = false;
210− demo.launcherEnabled = true;
211− if (demo.stages) {
212− d.leftEdgeDemo = d.overlay.createObject(demo.stages, {
213− "edge": "left",
214− "title": i18n.tr("Left edge"),
215− "text": i18n.tr("Swipe from the left to reveal the launcher for quick access to apps"),
216− "anchors.fill": demo.stages,
217− });
218− }
219− if (d.leftEdgeDemo) {
220− d.leftEdgeDemo.onSkip.connect(demo.hideEdgeDemoInShell)
221− } else {
222− stopDemo();
223− }
224− }
225−
226− Connections {
227− target: demo.launcher
228− onStateChanged: {
229− if (d.leftEdgeDemo && d.leftEdgeDemo.available && launcher.state == "visible") {
230− d.leftEdgeDemo.hide()
231− launcher.hide()
232− startFinalEdgeDemo()
233− }
234− }
235− }
236−
237− function startFinalEdgeDemo() {
238− demo.launcherEnabled = false;
239− if (demo.stages) {
240− d.finalEdgeDemo = d.overlay.createObject(demo.stages, {
241− "edge": "none",
242− "title": i18n.tr("Well done"),
243− "text": i18n.tr("You have now mastered the edge gestures and can start using the phone<br><br>Tap on the screen to start"),
244− "anchors.fill": demo.stages,
245− "showSkip": false,
246− });
247− }
248− if (d.finalEdgeDemo) {
249− d.finalEdgeDemo.onSkip.connect(demo.hideEdgeDemoInShell)
250− } else {
251− stopDemo();
252− }
253− }
254−}
qml/Components/EdgeDemoOverlay.qmldeleted−273
@@ -1,273 +0,0 @@
1−/*
2− * Copyright (C) 2013 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 Powerd 0.1
18−import QtQuick 2.0
19−import QtGraphicalEffects 1.0
20−import Unity.Application 0.1
21−import Ubuntu.Components 0.1
22−
23−Showable {
24− id: overlay
25−
26− /*
27− * Valid values are "left", "right", "top", "bottom", or "none".
28− */
29− property string edge: "top"
30−
31− /*
32− * This is the header displayed, like "Right edge".
33− */
34− property alias title: titleLabel.text
35−
36− /*
37− * This is the block of text displayed below the header.
38− */
39− property alias text: textLabel.text
40−
41− /*
42− * This is the text for the skip button.
43− */
44− property alias skipText: skipLabel.text
45−
46− /*
47− * This is the visible status of the skip button.
48− */
49− property alias showSkip: skipLabel.visible
50−
51− /*
52− * Whether this demo is running currently.
53− */
54− readonly property bool active: available && visible
55−
56− /*
57− * Whether animations are paused.
58− */
59− property alias paused: wholeAnimation.paused
60−
61− /*
62− * Whether animations are running.
63− */
64− readonly property alias running: wholeAnimation.running
65−
66− signal skip()
67−
68− function doSkip() {
69− d.skipOnHide = true;
70− hide();
71− }
72−
73− function hideNow() {
74− overlay.visible = false;
75− overlay.available = false;
76− if (d.skipOnHide) {
77− overlay.skip();
78− }
79− }
80−
81− showAnimation: StandardAnimation {
82− property: "opacity"
83− to: 1
84− onRunningChanged: if (running) overlay.visible = true
85− }
86− hideAnimation: StandardAnimation {
87− property: "opacity"
88− to: 0
89− duration: UbuntuAnimation.BriskDuration
90− onRunningChanged: if (!running) overlay.hideNow()
91− }
92−
93− QtObject {
94− id: d
95− property bool skipOnHide: false
96− property int edgeMargin: units.gu(4)
97− }
98−
99− Rectangle {
100− objectName: "backgroundShade"
101−
102− anchors.fill: parent
103− color: "black"
104− opacity: 0.8
105− visible: overlay.active
106−
107− MouseArea {
108− objectName: "backgroundShadeMouseArea"
109−
110− anchors.fill: parent
111− enabled: overlay.edge == "none" && overlay.opacity == 1.0
112− onClicked: overlay.doSkip()
113− }
114− }
115−
116− Item {
117− id: hintGroup
118− x: 0
119− y: 0
120− width: parent.width
121− height: parent.height
122− visible: overlay.active
123−
124− Column {
125− id: labelGroup
126− spacing: units.gu(3)
127−
128− anchors {
129− margins: d.edgeMargin
130− left: parent.left
131− top: overlay.edge == "bottom" ? undefined : parent.top
132− bottom: overlay.edge == "bottom" ? parent.bottom : undefined
133− }
134−
135− Label {
136− id: titleLabel
137− fontSize: "x-large"
138− width: units.gu(25)
139− wrapMode: Text.WordWrap
140− }
141−
142− Label {
143− id: textLabel
144− width: units.gu(25)
145− wrapMode: Text.WordWrap
146− }
147−
148− Label {
149− id: skipLabel
150− objectName: "skipLabel"
151− text: i18n.tr("Skip intro")
152− color: UbuntuColors.orange
153− fontSize: "small"
154−
155− Icon {
156− anchors.left: parent.right
157− anchors.verticalCenter: parent.verticalCenter
158− height: units.dp(12)
159− width: units.dp(12)
160− name: "chevron"
161− color: UbuntuColors.orange
162− }
163−
164− MouseArea {
165− // Make clickable area bigger than just the link because
166− // otherwise, the edge demo will feel hard to dismiss.
167− anchors.fill: parent
168− anchors.margins: -units.gu(5)
169− onClicked: overlay.doSkip()
170− }
171− }
172− }
173−
174− LinearGradient {
175− id: edgeHint
176− property int size: 1
177− cached: false
178− visible: overlay.edge != "none"
179− gradient: Gradient {
180− GradientStop {
181− position: 0.0
182− color: Qt.hsla(16.0/360.0, 0.83, 0.47, 0.4) // UbuntuColors.orange, but transparent
183− }
184− GradientStop {
185− position: 1.0
186− color: "transparent"
187− }
188− }
189− anchors.fill: parent
190− start: {
191− if (overlay.edge == "right") {
192− return Qt.point(width, 0);
193− } else if (overlay.edge == "left") {
194− return Qt.point(0, 0);
195− } else if (overlay.edge == "top") {
196− return Qt.point(0, 0);
197− } else {
198− return Qt.point(0, height);
199− }
200− }
201− end: {
202− if (overlay.edge == "right") {
203− return Qt.point(width - size, 0);
204− } else if (overlay.edge == "left") {
205− return Qt.point(size, 0);
206− } else if (overlay.edge == "top") {
207− return Qt.point(0, size);
208− } else {
209− return Qt.point(0, height - size);
210− }
211− }
212− }
213− }
214−
215− SequentialAnimation {
216− id: wholeAnimation
217− objectName: "wholeAnimation"
218− running: overlay.active
219−
220− ParallelAnimation {
221− id: fadeInAnimation
222−
223− StandardAnimation {
224− target: labelGroup
225− property: {
226− if (overlay.edge == "right" || overlay.edge == "left") {
227− return "anchors.leftMargin";
228− } else if (overlay.edge == "bottom") {
229− return "anchors.bottomMargin";
230− } else {
231− return "anchors.topMargin";
232− }
233− }
234− from: {
235− if (overlay.edge == "right") {
236− return d.edgeMargin + units.gu(3)
237− } else {
238− return d.edgeMargin - units.gu(3)
239− }
240− }
241− to: d.edgeMargin
242− duration: overlay.edge == "none" ? 0 : UbuntuAnimation.SleepyDuration
243− }
244− StandardAnimation {
245− target: overlay
246− property: "opacity"
247− from: 0.0
248− to: 1.0
249− duration: UbuntuAnimation.SleepyDuration
250− }
251− }
252−
253− SequentialAnimation {
254− id: hintAnimation
255− loops: Animation.Infinite
256− property string prop: (overlay.edge == "left" || overlay.edge == "right") ? "x" : "y"
257− property double endVal: units.dp(5) * ((overlay.edge == "left" || overlay.edge == "top") ? 1 : -1)
258− property double maxGlow: units.dp(20)
259− property int duration: overlay.edge == "none" ? 0 : UbuntuAnimation.SleepyDuration
260−
261− ParallelAnimation {
262− StandardAnimation { target: hintGroup; property: hintAnimation.prop; from: 0; to: hintAnimation.endVal; duration: hintAnimation.duration }
263− StandardAnimation { target: edgeHint; property: "size"; from: 1; to: hintAnimation.maxGlow; duration: hintAnimation.duration }
264− }
265−
266− // Undo the above
267− ParallelAnimation {
268− StandardAnimation { target: hintGroup; property: hintAnimation.prop; from: hintAnimation.endVal; to: 0; duration: hintAnimation.duration }
269− StandardAnimation { target: edgeHint; property: "size"; from: hintAnimation.maxGlow; to: 1; duration: hintAnimation.duration }
270− }
271− }
272− }
273−}
qml/Dash/GenericScopeView.qml+1
@@ -608,6 +608,7 @@ FocusScope {
608 onBackClicked: scopeView.backClicked()
608 onBackClicked: scopeView.backClicked()
609 onSettingsClicked: subPageLoader.openSubPage("settings")
609 onSettingsClicked: subPageLoader.openSubPage("settings")
610 onFavoriteClicked: scopeView.scope.favorite = !scopeView.scope.favorite
610 onFavoriteClicked: scopeView.scope.favorite = !scopeView.scope.favorite
611+ onSearchTextFieldFocused: scopeView.showHeader()
611 }
612 }
612 }
613 }
613 }
614 }
qml/Dash/PageHeader.qml+2
@@ -51,6 +51,7 @@ Item {
51 signal storeClicked()
51 signal storeClicked()
52 signal settingsClicked()
52 signal settingsClicked()
53 signal favoriteClicked()
53 signal favoriteClicked()
54+ signal searchTextFieldFocused()
54
55
55 onScopeStyleChanged: refreshLogo()
56 onScopeStyleChanged: refreshLogo()
56 onSearchQueryChanged: {
57 onSearchQueryChanged: {
@@ -228,6 +229,7 @@ Item {
228
229
229 onActiveFocusChanged: {
230 onActiveFocusChanged: {
230 if (activeFocus) {
231 if (activeFocus) {
232+ root.searchTextFieldFocused();
231 root.openSearchHistory();
233 root.openSearchHistory();
232 }
234 }
233 }
235 }
qml/Greeter/GreeterContent.qml+1
@@ -121,6 +121,7 @@ Item {
121 objectName: "infographics"
121 objectName: "infographics"
122 height: narrowMode ? parent.height : 0.75 * parent.height
122 height: narrowMode ? parent.height : 0.75 * parent.height
123 model: greeterContentLoader.infographicModel
123 model: greeterContentLoader.infographicModel
124+ clip: true // clip large data bubbles
124
125
125 property string selectedUser
126 property string selectedUser
126 property string infographicUser: AccountsService.statsWelcomeScreen ? selectedUser : ""
127 property string infographicUser: AccountsService.statsWelcomeScreen ? selectedUser : ""
qml/Launcher/Launcher.qml+13−7
@@ -26,6 +26,7 @@ Item {
26 property bool autohideEnabled: false
26 property bool autohideEnabled: false
27 property bool available: true // can be used to disable all interactions
27 property bool available: true // can be used to disable all interactions
28 property alias inverted: panel.inverted
28 property alias inverted: panel.inverted
29+ property bool shadeBackground: true // can be used to disable background shade when launcher is visible
29
30
30 property int panelWidth: units.gu(8)
31 property int panelWidth: units.gu(8)
31 property int dragAreaWidth: units.gu(1)
32 property int dragAreaWidth: units.gu(1)
@@ -33,6 +34,10 @@ Item {
33 property real progress: dragArea.dragging && dragArea.touchX > panelWidth ?
34 property real progress: dragArea.dragging && dragArea.touchX > panelWidth ?
34 (width * (dragArea.touchX-panelWidth) / (width - panelWidth)) : 0
35 (width * (dragArea.touchX-panelWidth) / (width - panelWidth)) : 0
35
36
37+ readonly property bool dragging: dragArea.dragging
38+ readonly property real dragDistance: dragArea.dragging ? dragArea.touchX : 0
39+ readonly property real visibleWidth: panel.width + panel.x
40+
36 readonly property bool shown: panel.x > -panel.width
41 readonly property bool shown: panel.x > -panel.width
37
42
38 // emitted when an application is selected
43 // emitted when an application is selected
@@ -154,7 +159,7 @@ Item {
154
159
155 MouseArea {
160 MouseArea {
156 id: launcherDragArea
161 id: launcherDragArea
157− enabled: root.state == "visible"
162+ enabled: root.available && root.state == "visible"
158 anchors.fill: panel
163 anchors.fill: panel
159 anchors.rightMargin: -units.gu(2)
164 anchors.rightMargin: -units.gu(2)
160 drag {
165 drag {
@@ -180,7 +185,7 @@ Item {
180 right: parent.right
185 right: parent.right
181 bottom: parent.bottom
186 bottom: parent.bottom
182 }
187 }
183− enabled: root.state == "visible"
188+ enabled: root.shadeBackground && root.state == "visible"
184 onPressed: {
189 onPressed: {
185 root.state = ""
190 root.state = ""
186 }
191 }
@@ -190,7 +195,7 @@ Item {
190 id: backgroundShade
195 id: backgroundShade
191 anchors.fill: parent
196 anchors.fill: parent
192 color: "black"
197 color: "black"
193− opacity: root.state == "visible" ? 0.6 : 0
198+ opacity: root.shadeBackground && root.state == "visible" ? 0.6 : 0
194
199
195 Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.BriskDuration } }
200 Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.BriskDuration } }
196 }
201 }
@@ -198,14 +203,14 @@ Item {
198 LauncherPanel {
203 LauncherPanel {
199 id: panel
204 id: panel
200 objectName: "launcherPanel"
205 objectName: "launcherPanel"
201− enabled: root.available
206+ enabled: root.available && root.state == "visible"
202 width: root.panelWidth
207 width: root.panelWidth
203 anchors {
208 anchors {
204 top: parent.top
209 top: parent.top
205 bottom: parent.bottom
210 bottom: parent.bottom
206 }
211 }
207 x: -width
212 x: -width
208− visible: x > -width || dragArea.status === DirectionalDragArea.Undecided
213+ visible: root.x > 0 || x > -width || dragArea.status === DirectionalDragArea.Undecided
209 model: LauncherModel
214 model: LauncherModel
210
215
211 property bool animate: true
216 property bool animate: true
@@ -247,6 +252,7 @@ Item {
247 direction: Direction.Rightwards
252 direction: Direction.Rightwards
248
253
249 enabled: root.available
254 enabled: root.available
255+ x: -root.x // so if launcher is adjusted relative to screen, we stay put (like tutorial does when teasing)
250 width: root.dragAreaWidth
256 width: root.dragAreaWidth
251 height: root.height
257 height: root.height
252
258
@@ -260,7 +266,7 @@ Item {
260 // would appear right next to the user's finger out of nowhere.
266 // would appear right next to the user's finger out of nowhere.
261 // Instead, we make the panel go towards the user's finger in several
267 // Instead, we make the panel go towards the user's finger in several
262 // steps. ie., in an animated way.
268 // steps. ie., in an animated way.
263− var targetPanelX = Math.min(0, touchX - panel.width)
269+ var targetPanelX = Math.min(0, touchX - panel.width) - root.x
264 var delta = targetPanelX - panel.x
270 var delta = targetPanelX - panel.x
265 // the trick is not to go all the way (1.0) as it would cause a sudden jump
271 // the trick is not to go all the way (1.0) as it would cause a sudden jump
266 panel.x += 0.4 * delta
272 panel.x += 0.4 * delta
@@ -292,7 +298,7 @@ Item {
292 name: "visible"
298 name: "visible"
293 PropertyChanges {
299 PropertyChanges {
294 target: panel
300 target: panel
295− x: 0
301+ x: -root.x // so we never go past panelWidth, even when teased by tutorial
296 }
302 }
297 },
303 },
298 State {
304 State {
qml/Launcher/LauncherPanel.qml+7−5
@@ -149,14 +149,14 @@ Rectangle {
149 id: snapToBottomAnimation
149 id: snapToBottomAnimation
150 target: launcherListView
150 target: launcherListView
151 property: "contentY"
151 property: "contentY"
152− to: launcherListView.originY
152+ to: launcherListView.originY + launcherListView.topMargin
153 }
153 }
154
154
155 UbuntuNumberAnimation {
155 UbuntuNumberAnimation {
156 id: snapToTopAnimation
156 id: snapToTopAnimation
157 target: launcherListView
157 target: launcherListView
158 property: "contentY"
158 property: "contentY"
159− to: launcherListView.contentHeight - launcherListView.height + launcherListView.originY
159+ to: launcherListView.contentHeight - launcherListView.height + launcherListView.originY - launcherListView.topMargin
160 }
160 }
161
161
162 displaced: Transition {
162 displaced: Transition {
@@ -521,12 +521,12 @@ Rectangle {
521 left: parent.left
521 left: parent.left
522 leftMargin: (quickList.item.width - units.gu(1)) / 2 - width / 2
522 leftMargin: (quickList.item.width - units.gu(1)) / 2 - width / 2
523 verticalCenter: parent.verticalCenter
523 verticalCenter: parent.verticalCenter
524− verticalCenterOffset: (parent.height / 2 + units.dp(3)) * (quickList.offset > 0 ? 1 : -1)
524+ verticalCenterOffset: (parent.height / 2 + units.dp(3)) * (quickList.offset > 0 ? 1 : -1) * (root.inverted ? 1 : -1)
525 }
525 }
526 height: units.gu(1)
526 height: units.gu(1)
527 width: units.gu(2)
527 width: units.gu(2)
528 source: "graphics/quicklist_tooltip.png"
528 source: "graphics/quicklist_tooltip.png"
529− rotation: quickList.offset > 0 ? 0 : 180
529+ rotation: (quickList.offset > 0 ? 0 : 180) + (root.inverted ? 0 : 180)
530 }
530 }
531
531
532 InverseMouseArea {
532 InverseMouseArea {
@@ -543,7 +543,9 @@ Rectangle {
543 id: quickList
543 id: quickList
544 objectName: "quickList"
544 objectName: "quickList"
545 color: "#f5f5f5"
545 color: "#f5f5f5"
546− width: units.gu(30)
546+ // Because we're setting left/right anchors depending on orientation, it will break the
547+ // width setting after rotating twice. This makes sure we also re-apply width on rotation
548+ width: root.inverted ? units.gu(30) : units.gu(30)
547 height: quickListColumn.height
549 height: quickListColumn.height
548 visible: quickListShape.visible
550 visible: quickListShape.visible
549 anchors {
551 anchors {
qml/Notifications/Notification.qml+36−6
@@ -40,6 +40,7 @@ Item {
40 property bool fullscreen: false
40 property bool fullscreen: false
41 property int maxHeight
41 property int maxHeight
42 property int margins
42 property int margins
43+ readonly property bool draggable: (type === Notification.SnapDecision && state === "contracted") || type === Notification.Interactive || type === Notification.Ephemeral
43 readonly property bool darkOnBright: panel.indicators.shown || type === Notification.SnapDecision
44 readonly property bool darkOnBright: panel.indicators.shown || type === Notification.SnapDecision
44 readonly property color red: "#fc4949"
45 readonly property color red: "#fc4949"
45 readonly property color green: "#3fb24f"
46 readonly property color green: "#3fb24f"
@@ -52,7 +53,7 @@ Item {
52 implicitHeight: type !== Notification.PlaceHolder ? (fullscreen ? maxHeight : outterColumn.height - shapedBack.anchors.topMargin + contentSpacing * 2) : 0
53 implicitHeight: type !== Notification.PlaceHolder ? (fullscreen ? maxHeight : outterColumn.height - shapedBack.anchors.topMargin + contentSpacing * 2) : 0
53
54
54 color: (type === Notification.Confirmation && notificationList.useModal && !greeter.shown) || darkOnBright ? sdLightGrey : Qt.rgba(0.132, 0.117, 0.109, 0.97)
55 color: (type === Notification.Confirmation && notificationList.useModal && !greeter.shown) || darkOnBright ? sdLightGrey : Qt.rgba(0.132, 0.117, 0.109, 0.97)
55− opacity: 1 // FIXME: 1 because of LP: #1354406 workaround, has to be 0 really
56+ opacity: 1 - (x / notification.width) // FIXME: non-zero initially because of LP: #1354406 workaround, we want this to start at 0 upon creation eventually
56
57
57 state: {
58 state: {
58 var result = "";
59 var result = "";
@@ -80,18 +81,28 @@ Item {
80 id: sound
81 id: sound
81 objectName: "sound"
82 objectName: "sound"
82 audioRole: MediaPlayer.alert
83 audioRole: MediaPlayer.alert
83− source: hints["suppress-sound"] != "true" && hints["sound-file"] != undefined ? hints["sound-file"] : ""
84+ source: hints["suppress-sound"] !== "true" && hints["sound-file"] !== undefined ? hints["sound-file"] : ""
84 }
85 }
85
86
86 // FIXME: using onCompleted because of LP: #1354406 workaround, has to be onOpacityChanged really
87 // FIXME: using onCompleted because of LP: #1354406 workaround, has to be onOpacityChanged really
87 Component.onCompleted: {
88 Component.onCompleted: {
88− if (opacity == 1.0 && hints["suppress-sound"] != "true" && sound.source != "") {
89+ if (opacity == 1.0 && hints["suppress-sound"] !== "true" && sound.source !== "") {
89 sound.play();
90 sound.play();
90 }
91 }
91 }
92 }
92
93
94+ Behavior on x {
95+ id: normalXBehavior
96+
97+ enabled: draggable
98+ UbuntuNumberAnimation {
99+ duration: UbuntuAnimation.FastDuration
100+ easing.type: Easing.OutBounce
101+ }
102+ }
103+
93 onHintsChanged: {
104 onHintsChanged: {
94− if (type === Notification.Confirmation && opacity == 1.0 && hints["suppress-sound"] != "true" && sound.source != "") {
105+ if (type === Notification.Confirmation && opacity == 1.0 && hints["suppress-sound"] !== "true" && sound.source !== "") {
95 sound.play();
106 sound.play();
96 }
107 }
97 }
108 }
@@ -146,6 +157,12 @@ Item {
146 opacity: parent.opacity
157 opacity: parent.opacity
147 }
158 }
148
159
160+ onXChanged: {
161+ if (draggable && notification.x > 0.75 * notification.width) {
162+ notification.notification.close()
163+ }
164+ }
165+
149 Item {
166 Item {
150 id: contents
167 id: contents
151 anchors.fill: fullscreen ? nonShapedBack : shapedBack
168 anchors.fill: fullscreen ? nonShapedBack : shapedBack
@@ -169,7 +186,7 @@ Item {
169 actions: paths.actions
186 actions: paths.actions
170 menuObjectPath: paths.menuObjectPath
187 menuObjectPath: paths.menuObjectPath
171 onNameOwnerChanged: {
188 onNameOwnerChanged: {
172− if (lastNameOwner != "" && nameOwner == "" && notification.notification != undefined) {
189+ if (lastNameOwner !== "" && nameOwner === "" && notification.notification !== undefined) {
173 notification.notification.close()
190 notification.notification.close()
174 }
191 }
175 lastNameOwner = nameOwner
192 lastNameOwner = nameOwner
@@ -181,6 +198,12 @@ Item {
181
198
182 anchors.fill: parent
199 anchors.fill: parent
183 objectName: "interactiveArea"
200 objectName: "interactiveArea"
201+
202+ drag.target: draggable ? notification : undefined
203+ drag.axis: Drag.XAxis
204+ drag.minimumX: 0
205+ drag.maximumX: notification.width
206+
184 onClicked: {
207 onClicked: {
185 if (notification.type == Notification.Interactive) {
208 if (notification.type == Notification.Interactive) {
186 notification.notification.invokeAction(actionRepeater.itemAt(0).actionId)
209 notification.notification.invokeAction(actionRepeater.itemAt(0).actionId)
@@ -188,6 +211,13 @@ Item {
188 notificationList.currentIndex = index;
211 notificationList.currentIndex = index;
189 }
212 }
190 }
213 }
214+ onReleased: {
215+ if (notification.x < notification.width / 2) {
216+ notification.x = 0
217+ } else {
218+ notification.x = notification.width
219+ }
220+ }
191 }
221 }
192
222
193 Column {
223 Column {
@@ -447,7 +477,7 @@ Item {
447 right: parent.right
477 right: parent.right
448 margins: contentSpacing
478 margins: contentSpacing
449 }
479 }
450− visible: notification.type == Notification.SnapDecision && actionRepeater.count > 0 && !oneOverTwoCase.visible
480+ visible: notification.type === Notification.SnapDecision && actionRepeater.count > 0 && !oneOverTwoCase.visible
451 spacing: units.gu(2)
481 spacing: units.gu(2)
452 layoutDirection: Qt.RightToLeft
482 layoutDirection: Qt.RightToLeft
453
483
qml/Notifications/Notifications.qml+2−6
@@ -41,15 +41,11 @@ ListView {
41 property bool topmostIsFullscreen: false
41 property bool topmostIsFullscreen: false
42 spacing: topmostIsFullscreen ? 0 : units.gu(.5)
42 spacing: topmostIsFullscreen ? 0 : units.gu(.5)
43
43
44− // FIXME: This doesn't make any sense and results in a binding loop
45− currentIndex: (currentIndex < 1 && count > 1) ? 1 : -1
44+ currentIndex: count > 1 ? 1 : -1
46
45
47 delegate: Notification {
46 delegate: Notification {
48 objectName: "notification" + index
47 objectName: "notification" + index
49− anchors {
50− left: parent.left
48+ width: parent.width
51− right: parent.right
52− }
53 type: model.type
49 type: model.type
54 hints: model.hints
50 hints: model.hints
55 iconSource: model.icon
51 iconSource: model.icon
qml/Panel/Panel.qml+5−3
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013-2014 Canonical, Ltd.
2+ * Copyright (C) 2013-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
@@ -90,6 +90,9 @@ Item {
90 right: indicators.left
90 right: indicators.left
91 }
91 }
92 saturation: 1 - indicators.unitProgress
92 saturation: 1 - indicators.unitProgress
93+
94+ // Don't let input event pass trough
95+ MouseArea { anchors.fill: parent }
93 }
96 }
94
97
95 Image {
98 Image {
@@ -110,8 +113,7 @@ Item {
110 right: indicators.left
113 right: indicators.left
111 }
114 }
112 height: indicators.minimizedPanelHeight
115 height: indicators.minimizedPanelHeight
113− enabled: callHint.visible
114− onClicked: callHint.showLiveCall()
116+ onClicked: { if (callHint.visible) { callHint.showLiveCall(); } }
115 }
117 }
116
118
117 IndicatorsMenu {
119 IndicatorsMenu {
qml/Shell.qml+49−24
@@ -36,6 +36,7 @@ import "Panel"
36import "Components"
36import "Components"
37import "Notifications"
37import "Notifications"
38import "Stages"
38import "Stages"
39+import "Tutorial"
39import "Wizard"
40import "Wizard"
40import Unity.Notifications 1.0 as NotificationBackend
41import Unity.Notifications 1.0 as NotificationBackend
41import Unity.Session 0.1
42import Unity.Session 0.1
@@ -62,7 +63,7 @@ Item {
62
63
63 readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated && !forcedUnlock
64 readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated && !forcedUnlock
64 readonly property alias hasLockedApp: greeter.hasLockedApp
65 readonly property alias hasLockedApp: greeter.hasLockedApp
65− readonly property bool forcedUnlock: edgeDemo.running
66+ readonly property bool forcedUnlock: tutorial.running
66 onForcedUnlockChanged: if (forcedUnlock) lockscreen.hide()
67 onForcedUnlockChanged: if (forcedUnlock) lockscreen.hide()
67
68
68 property bool sideStageEnabled: shell.width >= units.gu(100)
69 property bool sideStageEnabled: shell.width >= units.gu(100)
@@ -161,7 +162,7 @@ Item {
161
162
162 ScreenGrabber {
163 ScreenGrabber {
163 id: screenGrabber
164 id: screenGrabber
164− z: edgeDemo.z + 10
165+ z: dialogs.z + 10
165 enabled: Powerd.status === Powerd.On
166 enabled: Powerd.status === Powerd.On
166 }
167 }
167
168
@@ -252,14 +253,16 @@ Item {
252 }
253 }
253
254
254 onApplicationAdded: {
255 onApplicationAdded: {
255− if (greeter.shown && appId != "unity8-dash") {
256+ if (appId != "unity8-dash") {
257+ if (greeter.shown) {
256− greeter.startUnlock()
258+ greeter.startUnlock();
259+ }
257
260
258 // If this happens on first boot, we may be in edge
261 // If this happens on first boot, we may be in edge
259 // tutorial or wizard while receiving a call. But a call
262 // tutorial or wizard while receiving a call. But a call
260 // is more important than wizard so just bail out of those.
263 // is more important than wizard so just bail out of those.
261− if (edgeDemo.running) {
264+ if (tutorial.running) {
262− edgeDemo.hideEdgeDemos();
265+ tutorial.finish();
263 wizard.hide();
266 wizard.hide();
264 }
267 }
265 }
268 }
@@ -287,6 +290,15 @@ Item {
287 source: usageModeSettings.usageMode === "Windowed" ? "Stages/DesktopStage.qml"
290 source: usageModeSettings.usageMode === "Windowed" ? "Stages/DesktopStage.qml"
288 : tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
291 : tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
289
292
293+ property bool interactive: tutorial.spreadEnabled
294+ && !greeter.shown
295+ && !lockscreen.shown
296+ && panel.indicators.fullyClosed
297+ && launcher.progress == 0
298+ && !notifications.useModal
299+
300+ onInteractiveChanged: { if (interactive) { focus = true; } }
301+
290 Binding {
302 Binding {
291 target: applicationsDisplayLoader.item
303 target: applicationsDisplayLoader.item
292 property: "objectName"
304 property: "objectName"
@@ -306,12 +318,12 @@ Item {
306 Binding {
318 Binding {
307 target: applicationsDisplayLoader.item
319 target: applicationsDisplayLoader.item
308 property: "interactive"
320 property: "interactive"
309− value: edgeDemo.stagesEnabled && !greeter.shown && !lockscreen.shown && panel.indicators.fullyClosed && launcher.progress == 0 && !notifications.useModal
321+ value: applicationsDisplayLoader.interactive
310 }
322 }
311 Binding {
323 Binding {
312 target: applicationsDisplayLoader.item
324 target: applicationsDisplayLoader.item
313 property: "spreadEnabled"
325 property: "spreadEnabled"
314− value: edgeDemo.stagesEnabled && !greeter.hasLockedApp
326+ value: tutorial.spreadEnabled && !greeter.hasLockedApp
315 }
327 }
316 Binding {
328 Binding {
317 target: applicationsDisplayLoader.item
329 target: applicationsDisplayLoader.item
@@ -644,9 +656,13 @@ Item {
644 LauncherModel.setUser(user);
656 LauncherModel.setUser(user);
645 }
657 }
646
658
659+ onTapped: {
660+ if (!tutorial.running) {
647− onTapped: launcher.tease()
661+ launcher.tease();
662+ }
663+ }
648 onDraggingChanged: {
664 onDraggingChanged: {
649− if (dragging) {
665+ if (dragging && !tutorial.running) {
650 launcher.tease();
666 launcher.tease();
651 }
667 }
652 }
668 }
@@ -684,7 +700,7 @@ Item {
684
700
685 onStatusChanged: {
701 onStatusChanged: {
686 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
702 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
687− !callManager.hasCalls && !edgeDemo.running) {
703+ !callManager.hasCalls && !tutorial.running) {
688 // We don't want to simply call greeter.showNow() here, because
704 // We don't want to simply call greeter.showNow() here, because
689 // that will take too long. Qt will delay button event
705 // that will take too long. Qt will delay button event
690 // handling until the greeter is done loading and may think the
706 // handling until the greeter is done loading and may think the
@@ -700,7 +716,7 @@ Item {
700 }
716 }
701
717
702 function showHome() {
718 function showHome() {
703− if (edgeDemo.running) {
719+ if (tutorial.running) {
704 return
720 return
705 }
721 }
706
722
@@ -742,15 +758,17 @@ Item {
742 anchors.fill: parent //because this draws indicator menus
758 anchors.fill: parent //because this draws indicator menus
743 indicators {
759 indicators {
744 hides: [launcher]
760 hides: [launcher]
745− available: edgeDemo.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp
761+ available: tutorial.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp
746− contentEnabled: edgeDemo.panelContentEnabled
762+ contentEnabled: tutorial.panelContentEnabled
747 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
763 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
748
764
749 minimizedPanelHeight: units.gu(3)
765 minimizedPanelHeight: units.gu(3)
750 expandedPanelHeight: units.gu(7)
766 expandedPanelHeight: units.gu(7)
751
767
752 indicatorsModel: Indicators.IndicatorsModel {
768 indicatorsModel: Indicators.IndicatorsModel {
769+ // TODO: This should be sourced by device type (e.g. "desktop", "tablet", "phone"...)
770+ profile: indicatorProfile
753− Component.onCompleted: load(indicatorProfile);
771+ Component.onCompleted: load()
754 }
772 }
755 }
773 }
756 callHint {
774 callHint {
@@ -776,8 +794,9 @@ Item {
776 anchors.bottom: parent.bottom
794 anchors.bottom: parent.bottom
777 width: parent.width
795 width: parent.width
778 dragAreaWidth: shell.edgeSize
796 dragAreaWidth: shell.edgeSize
779− available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && !greeter.hasLockedApp
797+ available: tutorial.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && !greeter.hasLockedApp
780 inverted: usageModeSettings.usageMode === "Staged"
798 inverted: usageModeSettings.usageMode === "Staged"
799+ shadeBackground: !tutorial.running
781
800
782 onShowDashHome: showHome()
801 onShowDashHome: showHome()
783 onDash: showDash()
802 onDash: showDash()
@@ -790,7 +809,7 @@ Item {
790 if (greeter.hasLockedApp) {
809 if (greeter.hasLockedApp) {
791 greeter.startUnlock()
810 greeter.startUnlock()
792 }
811 }
793− if (!edgeDemo.running)
812+ if (!tutorial.running)
794 shell.activateApplication(appId)
813 shell.activateApplication(appId)
795 }
814 }
796 onShownChanged: {
815 onShownChanged: {
@@ -863,15 +882,21 @@ Item {
863 }
882 }
864 }
883 }
865
884
866− EdgeDemo {
885+ Tutorial {
867− id: edgeDemo
886+ id: tutorial
868− objectName: "edgeDemo"
887+ objectName: "tutorial"
869− z: dialogs.z + 10
888+ active: AccountsService.demoEdges
870− paused: Powerd.status === Powerd.Off || wizard.active // Saves power
889+ paused: LightDM.Greeter.active
871− greeter: greeter
872 launcher: launcher
890 launcher: launcher
873 panel: panel
891 panel: panel
874 stages: stages
892 stages: stages
893+ overlay: overlay
894+ edgeSize: shell.edgeSize
895+
896+ onFinished: {
897+ AccountsService.demoEdges = false;
898+ active = false; // for immediate response / if AS is having problems
899+ }
875 }
900 }
876
901
877 Connections {
902 Connections {
@@ -881,7 +906,7 @@ Item {
881
906
882 Rectangle {
907 Rectangle {
883 id: shutdownFadeOutRectangle
908 id: shutdownFadeOutRectangle
884− z: edgeDemo.z + 10
909+ z: screenGrabber.z + 10
885 enabled: false
910 enabled: false
886 visible: false
911 visible: false
887 color: "black"
912 color: "black"
qml/Stages/ApplicationWindow.qml+17−2
@@ -18,7 +18,7 @@ import QtQuick 2.0
18import Ubuntu.Components 1.1
18import Ubuntu.Components 1.1
19import Unity.Application 0.1
19import Unity.Application 0.1
20
20
21−Item {
21+FocusScope {
22 id: root
22 id: root
23
23
24 // to be read from outside
24 // to be read from outside
@@ -43,6 +43,7 @@ Item {
43 readonly property color splashColor: root.application ? root.application.splashColor : "#00000000"
43 readonly property color splashColor: root.application ? root.application.splashColor : "#00000000"
44 readonly property color splashColorHeader: root.application ? root.application.splashColorHeader : "#00000000"
44 readonly property color splashColorHeader: root.application ? root.application.splashColorHeader : "#00000000"
45 readonly property color splashColorFooter: root.application ? root.application.splashColorFooter : "#00000000"
45 readonly property color splashColorFooter: root.application ? root.application.splashColorFooter : "#00000000"
46+ readonly property url defaultScreenshot: root.application ? root.application.defaultScreenshot : ""
46
47
47 // Whether the Application had a surface before but lost it.
48 // Whether the Application had a surface before but lost it.
48 property bool hadSurface: sessionContainer.surfaceContainer.hadSurface
49 property bool hadSurface: sessionContainer.surfaceContainer.hadSurface
@@ -72,7 +73,7 @@ Item {
72 Image {
73 Image {
73 id: screenshotImage
74 id: screenshotImage
74 objectName: "screenshotImage"
75 objectName: "screenshotImage"
75− source: ""
76+ source: d.defaultScreenshot
76 anchors.fill: parent
77 anchors.fill: parent
77 antialiasing: !root.interactive
78 antialiasing: !root.interactive
78
79
@@ -120,9 +121,12 @@ Item {
120 d.surfaceInitialized = false;
121 d.surfaceInitialized = false;
121 }
122 }
122 }
123 }
124+
125+ focus: true
123 }
126 }
124
127
125 StateGroup {
128 StateGroup {
129+ id: stateGroup
126 objectName: "applicationWindowStateGroup"
130 objectName: "applicationWindowStateGroup"
127 states: [
131 states: [
128 State {
132 State {
@@ -215,6 +219,17 @@ Item {
215 PropertyAction { target: screenshotImage; property: "source"; value: "" }
219 PropertyAction { target: screenshotImage; property: "source"; value: "" }
216 }
220 }
217 },
221 },
222+ Transition {
223+ from: "splashScreen"; to: "screenshot"
224+ SequentialAnimation {
225+ PropertyAction { target: screenshotImage
226+ property: "visible"; value: true }
227+ UbuntuNumberAnimation { target: screenshotImage; property: "opacity";
228+ from: 0.0; to: 1.0
229+ duration: UbuntuAnimation.BriskDuration }
230+ PropertyAction { target: splashLoader; property: "active"; value: false }
231+ }
232+ },
218 Transition {
233 Transition {
219 from: "surface"; to: "void"
234 from: "surface"; to: "void"
220 SequentialAnimation {
235 SequentialAnimation {
qml/Stages/DesktopStage.qml+2−2
@@ -38,8 +38,6 @@ Item {
38 Connections {
38 Connections {
39 target: ApplicationManager
39 target: ApplicationManager
40 onApplicationAdded: {
40 onApplicationAdded: {
41− // Initial placement to avoid having the window decoration behind the panel
42− appRepeater.itemAt(ApplicationManager.count-1).y = units.gu(3)
43 ApplicationManager.requestFocusApplication(ApplicationManager.get(ApplicationManager.count-1).appId)
41 ApplicationManager.requestFocusApplication(ApplicationManager.get(ApplicationManager.count-1).appId)
44 }
42 }
45
43
@@ -91,6 +89,7 @@ Item {
91 delegate: Item {
89 delegate: Item {
92 id: appDelegate
90 id: appDelegate
93 z: ApplicationManager.count - index
91 z: ApplicationManager.count - index
92+ y: units.gu(3)
94 width: units.gu(60)
93 width: units.gu(60)
95 height: units.gu(50)
94 height: units.gu(50)
96
95
@@ -121,6 +120,7 @@ Item {
121 minWidth: appDelegate.minWidth
120 minWidth: appDelegate.minWidth
122 minHeight: appDelegate.minHeight
121 minHeight: appDelegate.minHeight
123 resizeHandleWidth: units.gu(0.5)
122 resizeHandleWidth: units.gu(0.5)
123+ windowId: model.appId // FIXME: Change this to point to windowId once we have such a thing
124
124
125 onPressed: ApplicationManager.requestFocusApplication(model.appId)
125 onPressed: ApplicationManager.requestFocusApplication(model.appId)
126 }
126 }
qml/Stages/PhoneStage.qml+64−34
@@ -32,6 +32,21 @@ Rectangle {
32 property bool spreadEnabled: true // If false, animations and right edge will be disabled
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.
33 property real inverseProgress: 0 // This is the progress for left edge drags, in pixels.
34 property int orientation: Qt.PortraitOrientation
34 property int orientation: Qt.PortraitOrientation
35+ property QtObject applicationManager: ApplicationManager
36+ property bool focusFirstApp: true // If false, focused app will appear on right edge like other apps
37+ property bool altTabEnabled: true
38+ property real startScale: 1.1
39+ property real endScale: 0.7
40+
41+ // How far left the stage has been dragged
42+ readonly property real dragProgress: spreadRepeater.count > 0 ? -spreadRepeater.itemAt(0).xTranslate : 0
43+
44+ readonly property alias dragging: spreadDragArea.dragging
45+
46+ // Only used by the tutorial right now, when it is teasing the right edge
47+ property real dragAreaOverlap
48+
49+ signal opened()
35
50
36 color: "#111111"
51 color: "#111111"
37
52
@@ -52,30 +67,30 @@ Rectangle {
52 if (inverseProgress == 0 && priv.oldInverseProgress > 0) {
67 if (inverseProgress == 0 && priv.oldInverseProgress > 0) {
53 // left edge drag released. Minimum distance is given by design.
68 // left edge drag released. Minimum distance is given by design.
54 if (priv.oldInverseProgress > units.gu(22)) {
69 if (priv.oldInverseProgress > units.gu(22)) {
55− ApplicationManager.requestFocusApplication("unity8-dash");
70+ applicationManager.requestFocusApplication("unity8-dash");
56 }
71 }
57 }
72 }
58 priv.oldInverseProgress = inverseProgress;
73 priv.oldInverseProgress = inverseProgress;
59 }
74 }
60
75
61 Connections {
76 Connections {
62− target: ApplicationManager
77+ target: applicationManager
63
78
64 onFocusRequested: {
79 onFocusRequested: {
65 if (spreadView.phase > 0) {
80 if (spreadView.phase > 0) {
66 spreadView.snapTo(priv.indexOf(appId));
81 spreadView.snapTo(priv.indexOf(appId));
67 } else {
82 } else {
68− ApplicationManager.focusApplication(appId);
83+ applicationManager.focusApplication(appId);
69 }
84 }
70 }
85 }
71
86
72 onApplicationAdded: {
87 onApplicationAdded: {
73 if (spreadView.phase == 2) {
88 if (spreadView.phase == 2) {
74− spreadView.snapTo(ApplicationManager.count - 1);
89+ spreadView.snapTo(applicationManager.count - 1);
75 } else {
90 } else {
76 spreadView.phase = 0;
91 spreadView.phase = 0;
77 spreadView.contentX = -spreadView.shift;
92 spreadView.contentX = -spreadView.shift;
78− ApplicationManager.focusApplication(appId);
93+ applicationManager.focusApplication(appId);
79 }
94 }
80 }
95 }
81
96
@@ -90,9 +105,9 @@ Rectangle {
90 }
105 }
91
106
92 function focusTopMostApp() {
107 function focusTopMostApp() {
93− if (ApplicationManager.count > 0) {
108+ if (applicationManager.count > 0) {
94− var topmostApp = ApplicationManager.get(0);
109+ var topmostApp = applicationManager.get(0);
95− ApplicationManager.focusApplication(topmostApp.appId);
110+ applicationManager.focusApplication(topmostApp.appId);
96 }
111 }
97 }
112 }
98 }
113 }
@@ -100,18 +115,21 @@ Rectangle {
100 QtObject {
115 QtObject {
101 id: priv
116 id: priv
102
117
118+ readonly property int firstSpreadIndex: root.focusFirstApp ? 1 : 0
103− property string focusedAppId: ApplicationManager.focusedApplicationId
119+ property string focusedAppId: applicationManager.focusedApplicationId
104− property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
120+ property var focusedApplication: applicationManager.findApplication(focusedAppId)
105 property var focusedAppDelegate: null
121 property var focusedAppDelegate: null
106
122
107 property real oldInverseProgress: 0
123 property real oldInverseProgress: 0
108− property bool animateX: true
124+ property bool animateX: false
109
125
110 onFocusedAppIdChanged: focusedAppDelegate = spreadRepeater.itemAt(0);
126 onFocusedAppIdChanged: focusedAppDelegate = spreadRepeater.itemAt(0);
111
127
128+ onFocusedAppDelegateChanged: focusedAppDelegate.focus = true;
129+
112 function indexOf(appId) {
130 function indexOf(appId) {
113− for (var i = 0; i < ApplicationManager.count; i++) {
131+ for (var i = 0; i < applicationManager.count; i++) {
114− if (ApplicationManager.get(i).appId == appId) {
132+ if (applicationManager.get(i).appId == appId) {
115 return i;
133 return i;
116 }
134 }
117 }
135 }
@@ -131,7 +149,7 @@ Rectangle {
131
149
132 // This indicates when the spreadView is active. That means, all the animations
150 // This indicates when the spreadView is active. That means, all the animations
133 // are activated and tiles need to line up for the spread.
151 // are activated and tiles need to line up for the spread.
134− readonly property bool active: shiftedContentX > 0 || spreadDragArea.status === DirectionalDragArea.Recognized
152+ readonly property bool active: shiftedContentX > 0 || spreadDragArea.status === DirectionalDragArea.Recognized || !root.focusFirstApp
135
153
136 // The flickable needs to fill the screen in order to get touch events all over.
154 // The flickable needs to fill the screen in order to get touch events all over.
137 // However, we don't want to the user to be able to scroll back all the way. For
155 // However, we don't want to the user to be able to scroll back all the way. For
@@ -197,10 +215,17 @@ Rectangle {
197 // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
215 // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
198 snapAnimation.targetContentX = width * positionMarker4 + 1 - shift;
216 snapAnimation.targetContentX = width * positionMarker4 + 1 - shift;
199 snapAnimation.start();
217 snapAnimation.start();
218+ root.opened();
200 }
219 }
201 }
220 }
202 function snapTo(index) {
221 function snapTo(index) {
203− if (ApplicationManager.count <= index) {
222+ if (!root.altTabEnabled) {
223+ // Reset to start instead
224+ snapAnimation.targetContentX = -shift;
225+ snapAnimation.start();
226+ return;
227+ }
228+ if (applicationManager.count <= index) {
204 // In case we're trying to snap to some non existing app, lets snap back to the first one
229 // In case we're trying to snap to some non existing app, lets snap back to the first one
205 index = 0;
230 index = 0;
206 }
231 }
@@ -215,10 +240,12 @@ Rectangle {
215 snapAnimation.start();
240 snapAnimation.start();
216 }
241 }
217
242
218− // In case the ApplicationManager already holds an app when starting up we're missing animations
243+ // In case the applicationManager already holds an app when starting up we're missing animations
219 // Make sure we end up in the same state
244 // Make sure we end up in the same state
220 Component.onCompleted: {
245 Component.onCompleted: {
221 spreadView.contentX = -spreadView.shift
246 spreadView.contentX = -spreadView.shift
247+ priv.animateX = true;
248+ snapAnimation.complete();
222 }
249 }
223
250
224 SequentialAnimation {
251 SequentialAnimation {
@@ -235,7 +262,7 @@ Rectangle {
235 ScriptAction {
262 ScriptAction {
236 script: {
263 script: {
237 if (spreadView.selectedIndex >= 0) {
264 if (spreadView.selectedIndex >= 0) {
238− ApplicationManager.focusApplication(ApplicationManager.get(spreadView.selectedIndex).appId);
265+ applicationManager.focusApplication(applicationManager.get(spreadView.selectedIndex).appId);
239
266
240 spreadView.selectedIndex = -1;
267 spreadView.selectedIndex = -1;
241 spreadView.phase = 0;
268 spreadView.phase = 0;
@@ -250,7 +277,7 @@ Rectangle {
250 // This width controls how much the spread can be flicked left/right. It's composed of:
277 // This width controls how much the spread can be flicked left/right. It's composed of:
251 // tileDistance * app count (with a minimum of 3 apps, in order to also allow moving 1 and 2 apps a bit)
278 // tileDistance * app count (with a minimum of 3 apps, in order to also allow moving 1 and 2 apps a bit)
252 // + some constant value (still scales with the screen width) which looks good and somewhat fills the screen
279 // + some constant value (still scales with the screen width) which looks good and somewhat fills the screen
253− width: Math.max(3, ApplicationManager.count) * spreadView.tileDistance + (spreadView.width - spreadView.tileDistance) * 1.5
280+ width: Math.max(3, applicationManager.count) * spreadView.tileDistance + (spreadView.width - spreadView.tileDistance) * 1.5
254 height: parent.height
281 height: parent.height
255 Behavior on width {
282 Behavior on width {
256 enabled: spreadView.closingIndex >= 0
283 enabled: spreadView.closingIndex >= 0
@@ -265,20 +292,22 @@ Rectangle {
265 x: spreadView.contentX
292 x: spreadView.contentX
266
293
267 onClicked: {
294 onClicked: {
295+ if (root.altTabEnabled) {
268− spreadView.snapTo(0);
296+ spreadView.snapTo(0);
297+ }
269 }
298 }
270
299
271 Repeater {
300 Repeater {
272 id: spreadRepeater
301 id: spreadRepeater
273 objectName: "spreadRepeater"
302 objectName: "spreadRepeater"
274− model: ApplicationManager
303+ model: applicationManager
275 delegate: TransformedSpreadDelegate {
304 delegate: TransformedSpreadDelegate {
276 id: appDelegate
305 id: appDelegate
277 objectName: "appDelegate" + index
306 objectName: "appDelegate" + index
278 startAngle: 45
307 startAngle: 45
279 endAngle: 5
308 endAngle: 5
280− startScale: 1.1
309+ startScale: root.startScale
281− endScale: 0.7
310+ endScale: root.endScale
282 startDistance: spreadView.tileDistance
311 startDistance: spreadView.tileDistance
283 endDistance: units.gu(.5)
312 endDistance: units.gu(.5)
284 width: spreadView.width
313 width: spreadView.width
@@ -286,11 +315,12 @@ Rectangle {
286 selected: spreadView.selectedIndex == index
315 selected: spreadView.selectedIndex == index
287 otherSelected: spreadView.selectedIndex >= 0 && !selected
316 otherSelected: spreadView.selectedIndex >= 0 && !selected
288 interactive: !spreadView.interactive && spreadView.phase === 0
317 interactive: !spreadView.interactive && spreadView.phase === 0
289− && spreadView.shiftedContentX === 0 && root.interactive && index === 0
318+ && spreadView.shiftedContentX === 0 && root.interactive && isFocused
290− swipeToCloseEnabled: spreadView.interactive && !snapAnimation.running
319+ swipeToCloseEnabled: spreadView.interactive && root.interactive && !snapAnimation.running
291 maximizedAppTopMargin: root.maximizedAppTopMargin
320 maximizedAppTopMargin: root.maximizedAppTopMargin
292 dropShadow: spreadView.active ||
321 dropShadow: spreadView.active ||
293 (priv.focusedAppDelegate && priv.focusedAppDelegate.x !== 0)
322 (priv.focusedAppDelegate && priv.focusedAppDelegate.x !== 0)
323+ focusFirstApp: root.focusFirstApp
294
324
295 readonly property bool isDash: model.appId == "unity8-dash"
325 readonly property bool isDash: model.appId == "unity8-dash"
296
326
@@ -298,7 +328,7 @@ Rectangle {
298
328
299 x: {
329 x: {
300 // focused app is always positioned at 0 except when following left edge drag
330 // focused app is always positioned at 0 except when following left edge drag
301− if (index == 0) {
331+ if (isFocused) {
302 if (!isDash && root.inverseProgress > 0 && spreadView.phase === 0) {
332 if (!isDash && root.inverseProgress > 0 && spreadView.phase === 0) {
303 return root.inverseProgress;
333 return root.inverseProgress;
304 }
334 }
@@ -309,10 +339,10 @@ Rectangle {
309 }
339 }
310
340
311 // Otherwise line up for the spread
341 // Otherwise line up for the spread
312− return spreadView.width + (index - 1) * spreadView.tileDistance;
342+ return spreadView.width + spreadIndex * spreadView.tileDistance;
313 }
343 }
314
344
315− application: ApplicationManager.get(index)
345+ application: applicationManager.get(index)
316 closeable: !isDash
346 closeable: !isDash
317
347
318 property real behavioredIndex: index
348 property real behavioredIndex: index
@@ -350,7 +380,7 @@ Rectangle {
350 progress: {
380 progress: {
351 var tileProgress = (spreadView.shiftedContentX - behavioredIndex * spreadView.tileDistance) / spreadView.width;
381 var tileProgress = (spreadView.shiftedContentX - behavioredIndex * spreadView.tileDistance) / spreadView.width;
352 // Tile 1 needs to move directly from the beginning...
382 // Tile 1 needs to move directly from the beginning...
353− if (behavioredIndex == 1 && spreadView.phase < 2) {
383+ if (root.focusFirstApp && behavioredIndex == 1 && spreadView.phase < 2) {
354 tileProgress += spreadView.tileDistance / spreadView.width;
384 tileProgress += spreadView.tileDistance / spreadView.width;
355 }
385 }
356 // Limiting progress to ~0 and 1.7 to avoid binding calculations when tiles are not
386 // Limiting progress to ~0 and 1.7 to avoid binding calculations when tiles are not
@@ -365,7 +395,7 @@ Rectangle {
365
395
366 // This mostly is the same as progress, just adds the snapping to phase 1 for tiles 0 and 1
396 // This mostly is the same as progress, just adds the snapping to phase 1 for tiles 0 and 1
367 animatedProgress: {
397 animatedProgress: {
368− if (spreadView.phase == 0 && index < 2) {
398+ if (spreadView.phase == 0 && index <= priv.firstSpreadIndex) {
369 if (progress < spreadView.positionMarker1) {
399 if (progress < spreadView.positionMarker1) {
370 return progress;
400 return progress;
371 } else if (progress < spreadView.positionMarker1 + 0.05){
401 } else if (progress < spreadView.positionMarker1 + 0.05){
@@ -390,11 +420,11 @@ Rectangle {
390 }
420 }
391
421
392 onClicked: {
422 onClicked: {
393− if (spreadView.phase == 2) {
423+ if (root.altTabEnabled && spreadView.phase == 2) {
394− if (ApplicationManager.focusedApplicationId == ApplicationManager.get(index).appId) {
424+ if (applicationManager.focusedApplicationId == applicationManager.get(index).appId) {
395 spreadView.snapTo(index);
425 spreadView.snapTo(index);
396 } else {
426 } else {
397− ApplicationManager.requestFocusApplication(ApplicationManager.get(index).appId);
427+ applicationManager.requestFocusApplication(applicationManager.get(index).appId);
398 }
428 }
399 }
429 }
400 }
430 }
@@ -409,7 +439,7 @@ Rectangle {
409
439
410 onClosed: {
440 onClosed: {
411 spreadView.closingIndex = index;
441 spreadView.closingIndex = index;
412− ApplicationManager.stopApplication(ApplicationManager.get(index).appId);
442+ applicationManager.stopApplication(applicationManager.get(index).appId);
413 }
443 }
414 }
444 }
415 }
445 }
@@ -422,7 +452,7 @@ Rectangle {
422 direction: Direction.Leftwards
452 direction: Direction.Leftwards
423 enabled: (spreadView.phase != 2 && root.spreadEnabled) || dragging
453 enabled: (spreadView.phase != 2 && root.spreadEnabled) || dragging
424
454
425− anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
455+ anchors { top: parent.top; right: parent.right; bottom: parent.bottom; rightMargin: -root.dragAreaOverlap }
426 width: root.dragAreaWidth
456 width: root.dragAreaWidth
427
457
428 property var gesturePoints: new Array()
458 property var gesturePoints: new Array()
qml/Stages/SessionContainer.qml+22−7
@@ -17,7 +17,7 @@
17import QtQuick 2.0
17import QtQuick 2.0
18import "Animations"
18import "Animations"
19
19
20−Item {
20+FocusScope {
21 id: root
21 id: root
22 objectName: "sessionContainer"
22 objectName: "sessionContainer"
23 property QtObject session
23 property QtObject session
@@ -34,8 +34,8 @@ Item {
34 orientation: root.orientation
34 orientation: root.orientation
35 }
35 }
36
36
37−
38 Repeater {
37 Repeater {
38+ id: childSessionsRepeater
39 model: root.childSessions
39 model: root.childSessions
40
40
41 delegate: Loader {
41 delegate: Loader {
@@ -45,9 +45,17 @@ Item {
45 // Only way to do recursive qml items.
45 // Only way to do recursive qml items.
46 source: Qt.resolvedUrl("SessionContainer.qml")
46 source: Qt.resolvedUrl("SessionContainer.qml")
47
47
48+ z: index
49+
50+ // Since a Loader is a FocusScope, propagate its focus to the loaded Item
51+ Binding {
52+ target: item; when: item
53+ property: "focus"; value: focus
54+ }
55+
48 Binding {
56 Binding {
49 target: item; when: item
57 target: item; when: item
50− property: "interactive"; value: root.interactive
58+ property: "interactive"; value: index == (childSessionsRepeater.count - 1) && root.interactive
51 }
59 }
52
60
53 Binding {
61 Binding {
@@ -69,10 +77,6 @@ Item {
69 target: item; when: item
77 target: item; when: item
70 property: "orientation"; value: root.orientation
78 property: "orientation"; value: root.orientation
71 }
79 }
72−
73− Component.onDestruction: {
74− root.session.surface.forceActiveFocus();
75− }
76 }
80 }
77 }
81 }
78
82
@@ -138,5 +142,16 @@ Item {
138 QtObject {
142 QtObject {
139 id: d
143 id: d
140 property var animations: []
144 property var animations: []
145+
146+ property var focusedChild: {
147+ if (childSessionsRepeater.count == 0) {
148+ return _surfaceContainer;
149+ } else {
150+ return childSessionsRepeater.itemAt(childSessionsRepeater.count - 1);
151+ }
152+ }
153+ onFocusedChildChanged: {
154+ focusedChild.focus = true;
155+ }
141 }
156 }
142}
157}
qml/Stages/SpreadDelegate.qml+2−1
@@ -21,7 +21,7 @@ import QtQuick 2.0
21import Ubuntu.Components 1.1
21import Ubuntu.Components 1.1
22import "../Components"
22import "../Components"
23
23
24−Item {
24+FocusScope {
25 id: root
25 id: root
26
26
27 // to be read from outside
27 // to be read from outside
@@ -65,6 +65,7 @@ Item {
65 ApplicationWindow {
65 ApplicationWindow {
66 id: appWindow
66 id: appWindow
67 objectName: application ? "appWindow_" + application.appId : "appWindow_null"
67 objectName: application ? "appWindow_" + application.appId : "appWindow_null"
68+ focus: true
68 anchors {
69 anchors {
69 fill: parent
70 fill: parent
70 topMargin: appWindow.fullscreen ? 0 : maximizedAppTopMargin
71 topMargin: appWindow.fullscreen ? 0 : maximizedAppTopMargin
qml/Stages/SurfaceContainer.qml+11−25
@@ -18,7 +18,7 @@ import QtQuick 2.0
18import Ubuntu.Components 1.1
18import Ubuntu.Components 1.1
19import Ubuntu.Gestures 0.1 // For TouchGate
19import Ubuntu.Gestures 0.1 // For TouchGate
20
20
21−Item {
21+FocusScope {
22 id: root
22 id: root
23 objectName: "surfaceContainer"
23 objectName: "surfaceContainer"
24 property Item surface: null
24 property Item surface: null
@@ -26,10 +26,15 @@ Item {
26 property int orientation
26 property int orientation
27 property bool interactive
27 property bool interactive
28
28
29+ signal surfacePressed()
30+
29 onSurfaceChanged: {
31 onSurfaceChanged: {
30 if (surface) {
32 if (surface) {
33+ // Set the surface focus *after* it is added to the scene to
34+ // ensure an update to the scene's active focus.
35+ surface.focus = false;
31 surface.parent = root;
36 surface.parent = root;
32− d.forceSurfaceActiveFocusIfReady();
37+ surface.focus = true;
33 } else {
38 } else {
34 hadSurface = true;
39 hadSurface = true;
35 }
40 }
@@ -38,7 +43,6 @@ Item {
38 Binding { target: surface; property: "orientation"; value: root.orientation }
43 Binding { target: surface; property: "orientation"; value: root.orientation }
39 Binding { target: surface; property: "z"; value: 1 }
44 Binding { target: surface; property: "z"; value: 1 }
40 Binding { target: surface; property: "enabled"; value: root.interactive; when: surface }
45 Binding { target: surface; property: "enabled"; value: root.interactive; when: surface }
41− Binding { target: surface; property: "focus"; value: root.interactive; when: surface }
42 Binding { target: surface; property: "antialiasing"; value: !root.interactive; when: surface }
46 Binding { target: surface; property: "antialiasing"; value: !root.interactive; when: surface }
43
47
44 TouchGate {
48 TouchGate {
@@ -46,28 +50,10 @@ Item {
46 anchors.fill: root
50 anchors.fill: root
47 enabled: root.surface ? root.surface.enabled : false
51 enabled: root.surface ? root.surface.enabled : false
48 z: 2
52 z: 2
49− }
53+ onPressed: {
50−
54+ root.focus = true;
51− Connections {
55+ if (root.interactive) {
52− target: root.surface
53− // FIXME: I would rather not need to do this, but currently it doesn't get
54− // active focus without it and I don't know why.
55− // Possibly because if an item get focus=true before it has a parent, once
56− // it gets a parent QQuickWindow won't check its focus and update its activeFocus
57− // accordingly. Unlike when you focus=true after the item already has a parent.
58− onFocusChanged: d.forceSurfaceActiveFocusIfReady();
59− onParentChanged: d.forceSurfaceActiveFocusIfReady();
60− onEnabledChanged: d.forceSurfaceActiveFocusIfReady();
61− }
62−
63− QtObject {
64− id: d
65− function forceSurfaceActiveFocusIfReady() {
66− if (root.surface !== null &&
67− root.surface.focus &&
68− root.surface.parent === root &&
69− root.surface.enabled) {
70− root.surface.forceActiveFocus();
56+ root.forceActiveFocus();
71 }
57 }
72 }
58 }
73 }
59 }
qml/Stages/TabletStage.qml+14
@@ -76,6 +76,20 @@ Rectangle {
76
76
77 appId0 = ApplicationManager.count >= 1 ? ApplicationManager.get(0).appId : "";
77 appId0 = ApplicationManager.count >= 1 ? ApplicationManager.get(0).appId : "";
78 appId1 = ApplicationManager.count > 1 ? ApplicationManager.get(1).appId : "";
78 appId1 = ApplicationManager.count > 1 ? ApplicationManager.get(1).appId : "";
79+
80+ // Update the QML focus accordingly
81+ updateSpreadDelegateFocus();
82+ }
83+
84+ function updateSpreadDelegateFocus() {
85+ if (priv.focusedAppId) {
86+ var focusedAppIndex = priv.indexOf(priv.focusedAppId);
87+ if (focusedAppIndex !== -1) {
88+ spreadRepeater.itemAt(focusedAppIndex).focus = true;
89+ } else {
90+ console.warn("TabletStage: Failed to find the SpreadDelegate for appID=" + priv.focusedAppId);
91+ }
92+ }
79 }
93 }
80
94
81 function indexOf(appId) {
95 function indexOf(appId) {
qml/Stages/TransformedSpreadDelegate.qml+27−21
@@ -46,6 +46,18 @@ SpreadDelegate {
46 property real startDistance: units.gu(5)
46 property real startDistance: units.gu(5)
47 property real endDistance: units.gu(.5)
47 property real endDistance: units.gu(.5)
48
48
49+ // Whether first app is displayed entirely vs just on the spread edge
50+ property bool focusFirstApp: true
51+
52+ // Whether this delegate is displayed to the user outside of the spread
53+ readonly property bool isFocused: focusFirstApp && index === 0
54+
55+ // Adjusted index for any spread animation calculations
56+ readonly property int spreadIndex: index - (focusFirstApp ? 1 : 0)
57+
58+ // How far left this delegate has been translated
59+ readonly property alias xTranslate: priv.xTranslate
60+
49 onSelectedChanged: {
61 onSelectedChanged: {
50 if (selected) {
62 if (selected) {
51 priv.snapshot();
63 priv.snapshot();
@@ -63,8 +75,8 @@ SpreadDelegate {
63 Connections {
75 Connections {
64 target: spreadView
76 target: spreadView
65 onPhaseChanged: {
77 onPhaseChanged: {
66− if (spreadView.phase == 1) {
78+ if (root.focusFirstApp && spreadView.phase == 1) {
67− if (index == 0) {
79+ if (index === 0) {
68 priv.phase2startTranslate = priv.easingAnimation(0, spreadView.positionMarker4, 0, -spreadView.width, spreadView.positionMarker4) + spreadView.width;
80 priv.phase2startTranslate = priv.easingAnimation(0, spreadView.positionMarker4, 0, -spreadView.width, spreadView.positionMarker4) + spreadView.width;
69 priv.phase2startAngle = priv.easingAnimation(0, spreadView.positionMarker4, root.startAngle, root.endAngle, spreadView.positionMarker4);
81 priv.phase2startAngle = priv.easingAnimation(0, spreadView.positionMarker4, root.startAngle, root.endAngle, spreadView.positionMarker4);
70 priv.phase2startScale = priv.easingAnimation(0, spreadView.positionMarker4, root.startScale, root.endScale, spreadView.positionMarker4);
82 priv.phase2startScale = priv.easingAnimation(0, spreadView.positionMarker4, root.startScale, root.endScale, spreadView.positionMarker4);
@@ -134,7 +146,7 @@ SpreadDelegate {
134 }
146 }
135
147
136 if (otherSelected) {
148 if (otherSelected) {
137− if (spreadView.phase < 2 && index == 0) {
149+ if (spreadView.phase < 2 && root.isFocused) {
138 return linearAnimation(selectedProgress, 0, selectedXTranslate,
150 return linearAnimation(selectedProgress, 0, selectedXTranslate,
139 selectedXTranslate - spreadView.tileDistance, root.progress);
151 selectedXTranslate - spreadView.tileDistance, root.progress);
140 }
152 }
@@ -142,8 +154,7 @@ SpreadDelegate {
142 return selectedXTranslate;
154 return selectedXTranslate;
143 }
155 }
144
156
145− switch (index) {
157+ if (root.focusFirstApp && index === 0) {
146− case 0:
147 if (spreadView.phase == 0) {
158 if (spreadView.phase == 0) {
148 return Math.min(0, linearAnimation(0, spreadView.positionMarker2,
159 return Math.min(0, linearAnimation(0, spreadView.positionMarker2,
149 0, -spreadView.width * .25, root.animatedProgress));
160 0, -spreadView.width * .25, root.animatedProgress));
@@ -153,11 +164,10 @@ SpreadDelegate {
153 } else if (!priv.isSelected){ // phase 2
164 } else if (!priv.isSelected){ // phase 2
154 // Apply the same animation as with the rest but add spreadView.width to align it with the others.
165 // Apply the same animation as with the rest but add spreadView.width to align it with the others.
155 return -easingCurve.value * spreadView.width + spreadView.width;
166 return -easingCurve.value * spreadView.width + spreadView.width;
156− } else if (priv.isSelected) {
167+ } else {
157 return linearAnimation(selectedProgress, 0, selectedXTranslate, 0, root.progress);
168 return linearAnimation(selectedProgress, 0, selectedXTranslate, 0, root.progress);
158 }
169 }
159−
170+ } else if (root.focusFirstApp && index === 1) {
160− case 1:
161 if (spreadView.phase == 0 && !priv.isSelected) {
171 if (spreadView.phase == 0 && !priv.isSelected) {
162 return linearAnimation(0, spreadView.positionMarker2,
172 return linearAnimation(0, spreadView.positionMarker2,
163 0, -spreadView.width * spreadView.snapPosition, root.animatedProgress);
173 0, -spreadView.width * spreadView.snapPosition, root.animatedProgress);
@@ -170,13 +180,13 @@ SpreadDelegate {
170
180
171 if (priv.isSelected) {
181 if (priv.isSelected) {
172 // Distance to left edge
182 // Distance to left edge
173− var targetTranslate = -spreadView.width - ((index - 1) * root.startDistance);
183+ var targetTranslate = -spreadView.width - (root.spreadIndex * root.startDistance);
174 return linearAnimation(selectedProgress, 0,
184 return linearAnimation(selectedProgress, 0,
175 selectedXTranslate, targetTranslate, root.progress);
185 selectedXTranslate, targetTranslate, root.progress);
176 }
186 }
177
187
178 // Fix it at the right edge...
188 // Fix it at the right edge...
179− var rightEdgeOffset = -((index - 1) * root.startDistance);
189+ var rightEdgeOffset = -(root.spreadIndex * root.startDistance);
180 // ...and use our easing to move them to the left. Stop a bit earlier for each tile
190 // ...and use our easing to move them to the left. Stop a bit earlier for each tile
181 var animatedEndDistance = linearAnimation(0, 2, root.endDistance, 0, root.progress);
191 var animatedEndDistance = linearAnimation(0, 2, root.endDistance, 0, root.progress);
182 return -easingCurve.value * spreadView.width + (index * animatedEndDistance) + rightEdgeOffset;
192 return -easingCurve.value * spreadView.width + (index * animatedEndDistance) + rightEdgeOffset;
@@ -194,8 +204,7 @@ SpreadDelegate {
194 if (priv.isSelected) {
204 if (priv.isSelected) {
195 return linearAnimation(selectedProgress, 0, selectedAngle, 0, root.progress);
205 return linearAnimation(selectedProgress, 0, selectedAngle, 0, root.progress);
196 }
206 }
197− switch (index) {
207+ if (root.focusFirstApp && index === 0) {
198− case 0:
199 if (spreadView.phase == 0) {
208 if (spreadView.phase == 0) {
200 return Math.max(0, linearAnimation(0, spreadView.positionMarker2,
209 return Math.max(0, linearAnimation(0, spreadView.positionMarker2,
201 0, root.tile0SnapAngle, root.animatedProgress));
210 0, root.tile0SnapAngle, root.animatedProgress));
@@ -203,7 +212,7 @@ SpreadDelegate {
203 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
212 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
204 root.tile0SnapAngle, phase2startAngle, root.progress);
213 root.tile0SnapAngle, phase2startAngle, root.progress);
205 }
214 }
206− case 1:
215+ } else if (root.focusFirstApp && index === 1) {
207 if (spreadView.phase == 0) {
216 if (spreadView.phase == 0) {
208 return linearAnimation(0, spreadView.positionMarker2, root.startAngle,
217 return linearAnimation(0, spreadView.positionMarker2, root.startAngle,
209 root.startAngle * (1-spreadView.snapPosition), root.animatedProgress);
218 root.startAngle * (1-spreadView.snapPosition), root.animatedProgress);
@@ -227,15 +236,14 @@ SpreadDelegate {
227 return linearAnimation(selectedProgress, 0, selectedScale, 1, root.progress);
236 return linearAnimation(selectedProgress, 0, selectedScale, 1, root.progress);
228 }
237 }
229
238
230− switch (index) {
239+ if (root.focusFirstApp && index === 0) {
231− case 0:
232 if (spreadView.phase == 0) {
240 if (spreadView.phase == 0) {
233 return 1;
241 return 1;
234 } else if (spreadView.phase == 1) {
242 } else if (spreadView.phase == 1) {
235 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
243 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
236 1, phase2startScale, root.progress);
244 1, phase2startScale, root.progress);
237 }
245 }
238− case 1:
246+ } else if (root.focusFirstApp && index === 1) {
239 if (spreadView.phase == 0) {
247 if (spreadView.phase == 0) {
240 var targetScale = tile1StartScale - ((tile1StartScale - 1) * spreadView.snapPosition);
248 var targetScale = tile1StartScale - ((tile1StartScale - 1) * spreadView.snapPosition);
241 return linearAnimation(0, spreadView.positionMarker2,
249 return linearAnimation(0, spreadView.positionMarker2,
@@ -254,7 +262,7 @@ SpreadDelegate {
254 return linearAnimation (selectedProgress, Math.max(0, selectedProgress - .5),
262 return linearAnimation (selectedProgress, Math.max(0, selectedProgress - .5),
255 selectedOpacity, 0, root.progress);
263 selectedOpacity, 0, root.progress);
256 }
264 }
257− if (index == 0) {
265+ if (root.isFocused) {
258 switch (spreadView.phase) {
266 switch (spreadView.phase) {
259 case 0:
267 case 0:
260 return linearAnimation(0, spreadView.positionMarker2, 1, .7, root.animatedProgress);
268 return linearAnimation(0, spreadView.positionMarker2, 1, .7, root.animatedProgress);
@@ -272,16 +280,14 @@ SpreadDelegate {
272 return linearAnimation(selectedProgress, 0, selectedTopMarginProgress, 0, root.progress);
280 return linearAnimation(selectedProgress, 0, selectedTopMarginProgress, 0, root.progress);
273 }
281 }
274
282
275− switch (index) {
283+ if (root.focusFirstApp && index === 0) {
276− case 0:
277 if (spreadView.phase == 0) {
284 if (spreadView.phase == 0) {
278 return 0;
285 return 0;
279 } else if (spreadView.phase == 1) {
286 } else if (spreadView.phase == 1) {
280 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
287 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
281 0, priv.phase2startTopMarginProgress, root.progress);
288 0, priv.phase2startTopMarginProgress, root.progress);
282 }
289 }
283− break;
290+ } else if (root.focusFirstApp && index === 1) {
284− case 1:
285 if (spreadView.phase == 0) {
291 if (spreadView.phase == 0) {
286 return 0;
292 return 0;
287 } else if (spreadView.phase == 1) {
293 } else if (spreadView.phase == 1) {
qml/Stages/TransformedTabletSpreadDelegate.qml+1−1
@@ -366,7 +366,7 @@ SpreadDelegate {
366 Scale {
366 Scale {
367 origin { x: 0; y: (spreadView.height * priv.scale) + maximizedAppTopMargin * 3 }
367 origin { x: 0; y: (spreadView.height * priv.scale) + maximizedAppTopMargin * 3 }
368 xScale: 1
368 xScale: 1
369− yScale: isFullscreen ? 1 - priv.topMarginProgress * maximizedAppTopMargin / spreadView.height : 1
369+ yScale: fullscreen ? 1 - priv.topMarginProgress * maximizedAppTopMargin / spreadView.height : 1
370 },
370 },
371 Translate {
371 Translate {
372 x: priv.xTranslate
372 x: priv.xTranslate
qml/Stages/WindowMoveResizeArea.qml+33−24
@@ -18,6 +18,7 @@
18
18
19import QtQuick 2.3
19import QtQuick 2.3
20import Ubuntu.Components 1.1
20import Ubuntu.Components 1.1
21+import Utils 0.1
21
22
22MouseArea {
23MouseArea {
23 id: root
24 id: root
@@ -26,7 +27,8 @@ MouseArea {
26
27
27 // The target item managed by this. Must be a parent or a sibling
28 // The target item managed by this. Must be a parent or a sibling
28 // The area will anchor to it and manage move and resize events
29 // The area will anchor to it and manage move and resize events
29− property var target: null
30+ property Item target: null
31+ property string windowId: ""
30 property int resizeHandleWidth: 0
32 property int resizeHandleWidth: 0
31 property int minWidth: 0
33 property int minWidth: 0
32 property int minHeight: 0
34 property int minHeight: 0
@@ -37,8 +39,6 @@ MouseArea {
37 readonly property int windowHeight: root.height - resizeHandleWidth * 2
39 readonly property int windowHeight: root.height - resizeHandleWidth * 2
38
40
39 property var startPoint
41 property var startPoint
40− property int startWidth
41− property int startHeight
42
42
43 property bool resizeTop: false
43 property bool resizeTop: false
44 property bool resizeBottom: false
44 property bool resizeBottom: false
@@ -47,10 +47,18 @@ MouseArea {
47
47
48 }
48 }
49
49
50+ Component.onCompleted: {
51+ var windowState = WindowStateStorage.getGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height))
52+ if (windowState !== undefined) {
53+ target.x = windowState.x
54+ target.y = windowState.y
55+ target.width = windowState.width
56+ target.height = windowState.height
57+ }
58+ }
59+
50 onPressed: {
60 onPressed: {
51− priv.startPoint = mapToItem(null, Qt.point(mouse.x, mouse.y)).x;
61+ priv.startPoint = Qt.point(mouse.x, mouse.y);
52− priv.startWidth = root.width;
53− priv.startHeight = root.height;
54 priv.resizeTop = mouseY < root.resizeHandleWidth;
62 priv.resizeTop = mouseY < root.resizeHandleWidth;
55 priv.resizeBottom = mouseY > (root.height - root.resizeHandleWidth);
63 priv.resizeBottom = mouseY > (root.height - root.resizeHandleWidth);
56 priv.resizeLeft = mouseX < root.resizeHandleWidth;
64 priv.resizeLeft = mouseX < root.resizeHandleWidth;
@@ -58,32 +66,28 @@ MouseArea {
58 }
66 }
59
67
60 onPositionChanged: {
68 onPositionChanged: {
61− var currentPoint = mapToItem(null, Qt.point(mouse.x, mouse.y)).x;
69+ var currentPoint = Qt.point(mouse.x, mouse.y);
62 var mouseDiff = Qt.point(currentPoint.x - priv.startPoint.x, currentPoint.y - priv.startPoint.y);
70 var mouseDiff = Qt.point(currentPoint.x - priv.startPoint.x, currentPoint.y - priv.startPoint.y);
63 var moveDiff = Qt.point(0, 0);
71 var moveDiff = Qt.point(0, 0);
64 var sizeDiff = Qt.point(0, 0);
72 var sizeDiff = Qt.point(0, 0);
73+ var maxSizeDiff = Qt.point(root.minWidth - root.target.width, root.minHeight - root.target.height)
74+
65 if (priv.resizeTop || priv.resizeBottom || priv.resizeLeft || priv.resizeRight) {
75 if (priv.resizeTop || priv.resizeBottom || priv.resizeLeft || priv.resizeRight) {
66 if (priv.resizeTop) {
76 if (priv.resizeTop) {
67− moveDiff.y += mouseDiff.y;
77+ sizeDiff.y = Math.max(maxSizeDiff.y, -currentPoint.y + priv.startPoint.y)
68− sizeDiff.y += -mouseDiff.y;
78+ moveDiff.y = -sizeDiff.y
69− } else if (priv.resizeBottom) {
70− sizeDiff.y += mouse.y - priv.startPoint.y;
71− priv.startPoint.y = mouse.y
72 }
79 }
73− if (priv.resizeLeft) {
80+ if (priv.resizeBottom) {
74− moveDiff.x += mouseDiff.x;
81+ sizeDiff.y = Math.max(maxSizeDiff.y, currentPoint.y - priv.startPoint.y)
75− sizeDiff.x += -mouseDiff.x;
82+ priv.startPoint.y += sizeDiff.y
76− } else if (priv.resizeRight) {
77− sizeDiff.x += mouse.x - priv.startPoint.x;
78− priv.startPoint.x = mouse.x
79 }
83 }
80− if (priv.windowWidth + sizeDiff.x < root.minWidth) {
84+ if (priv.resizeLeft) {
81− sizeDiff.x = root.minWidth - priv.windowWidth;
85+ sizeDiff.x = Math.max(maxSizeDiff.x, -currentPoint.x + priv.startPoint.x)
82− moveDiff.x = moveDiff.x == 0 ? 0 : -sizeDiff.x;
86+ moveDiff.x = -sizeDiff.x
83 }
87 }
84− if (priv.windowHeight + sizeDiff.y < root.minHeight) {
88+ if (priv.resizeRight) {
85− sizeDiff.y = root.minHeight - priv.windowHeight;
89+ sizeDiff.x = Math.max(maxSizeDiff.x, currentPoint.x - priv.startPoint.x)
86− moveDiff.y = moveDiff.y == 0 ? 0 : -sizeDiff.y;
90+ priv.startPoint.x += sizeDiff.x
87 }
91 }
88
92
89 target.x += moveDiff.x;
93 target.x += moveDiff.x;
@@ -94,5 +98,10 @@ MouseArea {
94 target.x += mouseDiff.x;
98 target.x += mouseDiff.x;
95 target.y += mouseDiff.y;
99 target.y += mouseDiff.y;
96 }
100 }
101+
102+ }
103+
104+ Component.onDestruction: {
105+ WindowStateStorage.saveGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height))
97 }
106 }
98}
107}
qml/Tutorial/Arrow.qmlnew file+56
@@ -0,0 +1,56 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.3
18+import Ubuntu.Components 1.1
19+
20+Item {
21+ id: root
22+
23+ property alias color: circle.color
24+
25+ // Will make whole arrow darker
26+ property real darkenBy: 0
27+
28+ property alias chevronOpacity: chevron.opacity
29+
30+ ////
31+
32+ Rectangle {
33+ id: circle
34+ anchors.fill: parent
35+ radius: width / 2
36+ }
37+
38+ Image {
39+ id: chevron
40+ anchors.centerIn: parent
41+ source: Qt.resolvedUrl("graphics/chevron.png")
42+ fillMode: Image.PreserveAspectFit
43+ sourceSize.width: 152
44+ sourceSize.height: 152
45+ width: parent.width / 2
46+ height: parent.height / 2
47+ }
48+
49+ Rectangle {
50+ id: darkCircle
51+ anchors.fill: parent
52+ radius: width / 2
53+ color: "black"
54+ opacity: root.darkenBy
55+ }
56+}
qml/Tutorial/Slider.qmlnew file+123
@@ -0,0 +1,123 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.3
18+import Ubuntu.Components 1.1
19+
20+Item {
21+ id: root
22+
23+ // Whether this slider is short or long
24+ property bool shortSwipe
25+
26+ // How far the user has slid
27+ property real offset
28+
29+ // Set to true when slider is being used
30+ property bool active
31+
32+ // How far in percentage terms
33+ readonly property real percent: d.slideOffset / target.x
34+
35+ QtObject {
36+ id: d
37+ readonly property color trayColor: "#424141"
38+ readonly property real margin: units.gu(0.5)
39+ readonly property real arrowSize: root.height - margin * 2
40+ readonly property real dotSize: units.dp(1)
41+ readonly property real slideOffset: MathUtils.clamp(root.offset - offscreenOffset, -offscreenOffset, target.x)
42+ readonly property real offscreenOffset: units.gu(2)
43+ }
44+
45+ implicitWidth: shortSwipe ? units.gu(15) : units.gu(27.5)
46+ implicitHeight: units.gu(6.5)
47+
48+ Rectangle {
49+ color: d.trayColor
50+ anchors.fill: parent
51+ anchors.rightMargin: clipBox.width - 1
52+ }
53+
54+ // We want to have a circular border around the target. But we can't just
55+ // do a radius on two of a rectangle's corners. So we clip a full circle.
56+ Item {
57+ id: clipBox
58+
59+ clip: true
60+ anchors.top: parent.top
61+ anchors.bottom: parent.bottom
62+ anchors.right: parent.right
63+ width: parent.height / 2
64+
65+ Rectangle {
66+ color: d.trayColor
67+ anchors.top: parent.top
68+ anchors.bottom: parent.bottom
69+ anchors.right: parent.right
70+ width: parent.width * 2
71+ radius: parent.width
72+ }
73+ }
74+
75+ Arrow {
76+ id: target
77+ width: d.arrowSize
78+ height: d.arrowSize
79+ color: "#73000000"
80+ chevronOpacity: 0.52
81+ anchors.right: parent.right
82+ anchors.rightMargin: d.margin
83+ anchors.verticalCenter: parent.verticalCenter
84+ }
85+
86+ Row {
87+ anchors.left: handle.horizontalCenter
88+ anchors.right: target.horizontalCenter
89+ anchors.verticalCenter: parent.verticalCenter
90+
91+ layoutDirection: Qt.RightToLeft
92+ spacing: d.dotSize * 2
93+
94+ Repeater {
95+ model: parent.width / (parent.spacing + d.dotSize)
96+ Rectangle {
97+ anchors.verticalCenter: parent ? parent.verticalCenter : undefined
98+ height: d.dotSize
99+ width: height
100+ radius: width
101+ color: "white"
102+ opacity: 0.2
103+ }
104+ }
105+ }
106+
107+ Arrow {
108+ id: handle
109+ width: d.arrowSize
110+ height: d.arrowSize
111+ color: UbuntuColors.orange
112+ darkenBy: root.active ? 0.5 : 0
113+ anchors.left: parent.left
114+ // We use a Translate transform rather than anchors.leftMargin because
115+ // the latter has weird performance problems on the TutorialRight page.
116+ transform: [
117+ Translate {
118+ x: d.slideOffset
119+ }
120+ ]
121+ anchors.verticalCenter: parent.verticalCenter
122+ }
123+}
qml/Tutorial/Tick.qmlnew file+29
@@ -0,0 +1,29 @@
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.3
18+import Ubuntu.Components 1.1
19+
20+MouseArea {
21+ implicitHeight: tick.height
22+ implicitWidth: tick.width
23+ Image {
24+ id: tick
25+ source: Qt.resolvedUrl("graphics/tick.png")
26+ height: units.gu(6.5)
27+ width: units.gu(6.5)
28+ }
29+}
qml/Tutorial/Tutorial.qmlnew file+92
@@ -0,0 +1,92 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.3
18+import Ubuntu.Components 1.1
19+
20+Item {
21+ id: root
22+
23+ property alias active: loader.active
24+ property bool paused
25+ property real edgeSize
26+
27+ property Item launcher
28+ property Item panel
29+ property Item stages
30+ property Item overlay
31+
32+ readonly property bool launcherEnabled: loader.item ? loader.item.launcherEnabled : true
33+ readonly property bool spreadEnabled: loader.item ? loader.item.spreadEnabled : true
34+ readonly property bool panelEnabled: loader.item ? loader.item.panelEnabled : true
35+ readonly property bool panelContentEnabled: loader.item ? loader.item.panelContentEnabled : true
36+ readonly property bool running: loader.item ? loader.item.running : false
37+
38+ function finish() {
39+ if (loader.item) {
40+ loader.item.finish();
41+ }
42+ }
43+
44+ signal finished()
45+
46+ Loader {
47+ id: loader
48+ anchors.fill: parent
49+ source: "TutorialContent.qml"
50+
51+ Binding {
52+ target: loader.item
53+ property: "paused"
54+ value: root.paused
55+ }
56+
57+ Binding {
58+ target: loader.item
59+ property: "edgeSize"
60+ value: root.edgeSize
61+ }
62+
63+ Binding {
64+ target: loader.item
65+ property: "launcher"
66+ value: root.launcher
67+ }
68+
69+ Binding {
70+ target: loader.item
71+ property: "panel"
72+ value: root.panel
73+ }
74+
75+ Binding {
76+ target: loader.item
77+ property: "stages"
78+ value: root.stages
79+ }
80+
81+ Binding {
82+ target: loader.item
83+ property: "overlay"
84+ value: root.overlay
85+ }
86+
87+ Connections {
88+ target: loader.item
89+ onFinished: root.finished()
90+ }
91+ }
92+}
qml/Tutorial/TutorialBottom.qmlnew file+104
@@ -0,0 +1,104 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.3
18+import Ubuntu.Components 1.1
19+import Ubuntu.Gestures 0.1
20+import "../Components"
21+import "." as LocalComponents
22+
23+TutorialPage {
24+ id: root
25+
26+ property alias edgeSize: dragArea.height
27+
28+ title: i18n.tr("Open special menus")
29+ text: i18n.tr("Swipe up from the bottom edge.")
30+ fullTextWidth: true
31+
32+ SequentialAnimation {
33+ id: teaseAnimation
34+ paused: running && root.paused
35+ running: !dragArea.useTouchY && slider.dragOffset === 0
36+ loops: Animation.Infinite
37+
38+ UbuntuNumberAnimation {
39+ target: slider
40+ property: "teaseOffset"
41+ to: units.gu(1)
42+ duration: UbuntuAnimation.SleepyDuration
43+ }
44+ UbuntuNumberAnimation {
45+ target: slider
46+ property: "teaseOffset"
47+ to: 0
48+ duration: UbuntuAnimation.SleepyDuration
49+ }
50+ }
51+
52+ foreground {
53+ children: [
54+ LocalComponents.Slider {
55+ id: slider
56+ anchors {
57+ bottom: parent.bottom
58+ bottomMargin: width / 2 - height / 2
59+ horizontalCenter: parent.horizontalCenter
60+ }
61+ rotation: -90
62+ offset: teaseOffset + dragOffset
63+ active: dragArea.dragging
64+
65+ property real teaseOffset
66+ property real dragOffset: dragArea.useTouchY ? -dragArea.touchY : 0
67+
68+ Behavior on dragOffset {
69+ id: offsetAnimation
70+ UbuntuNumberAnimation {}
71+ }
72+ }
73+ ]
74+ }
75+
76+ EdgeDragArea {
77+ id: dragArea
78+ direction: Direction.Upwards
79+ anchors {
80+ bottom: parent.bottom
81+ left: parent.left
82+ right: parent.right
83+ }
84+
85+ property bool useTouchY
86+
87+ onDraggingChanged: {
88+ if (!dragging) {
89+ if (slider.percent >= 0.85) {
90+ root.hide();
91+ } else if (slider.percent >= 0.15) {
92+ root.showError();
93+ }
94+ }
95+
96+ // We use a separate vars here rather than just directly looking at
97+ // 'dragging' because we want to preserve our 'slider.offset'
98+ // value during the above percent check. Now that we made it,
99+ // we can have 'slider.offset' go back to zero.
100+ offsetAnimation.enabled = !dragging;
101+ useTouchY = dragging;
102+ }
103+ }
104+}
qml/Tutorial/TutorialBottomFinish.qmlnew file+41
@@ -0,0 +1,41 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.3
18+import Ubuntu.Components 1.1
19+import "." as LocalComponents
20+
21+TutorialPage {
22+ id: root
23+
24+ title: i18n.tr("This action does different things for different apps")
25+ text: i18n.tr("Tap here to finish.")
26+ fullTextWidth: true
27+
28+ foreground {
29+ children: [
30+ LocalComponents.Tick {
31+ objectName: "tick"
32+ anchors {
33+ horizontalCenter: parent.horizontalCenter
34+ top: parent.top
35+ topMargin: root.textBottom + units.gu(3)
36+ }
37+ onClicked: root.hide()
38+ }
39+ ]
40+ }
41+}
qml/Tutorial/TutorialContent.qmlnew file+124
@@ -0,0 +1,124 @@
1+/*
2+ * Copyright (C) 2013,2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.3
18+import Ubuntu.Components 1.1
19+
20+Item {
21+ id: root
22+
23+ property Item launcher
24+ property Item panel
25+ property Item stages
26+ property Item overlay
27+
28+ readonly property bool launcherEnabled: !running ||
29+ (!paused && tutorialLeft.shown)
30+ readonly property bool spreadEnabled: !running
31+ readonly property bool panelEnabled: !running
32+ readonly property bool panelContentEnabled: !running
33+ readonly property alias running: d.running
34+
35+ property bool paused: false
36+ property real edgeSize
37+
38+ signal finished()
39+
40+ function finish() {
41+ d.stop();
42+ finished();
43+ }
44+
45+ ////
46+
47+ Component.onCompleted: {
48+ d.start();
49+ }
50+
51+ QtObject {
52+ id: d
53+
54+ property bool running
55+
56+ function stop() {
57+ running = false;
58+ }
59+
60+ function start() {
61+ running = true;
62+ tutorialLeft.show();
63+ }
64+ }
65+
66+ TutorialLeft {
67+ id: tutorialLeft
68+ objectName: "tutorialLeft"
69+ parent: root.stages
70+ anchors.fill: parent
71+ launcher: root.launcher
72+ paused: !shown || root.paused
73+
74+ onFinished: tutorialLeftFinish.show()
75+ }
76+
77+ TutorialLeftFinish {
78+ id: tutorialLeftFinish
79+ objectName: "tutorialLeftFinish"
80+ parent: root.stages
81+ anchors.fill: parent
82+ textXOffset: root.launcher.panelWidth
83+ paused: !shown || root.paused
84+
85+ onFinished: {
86+ root.launcher.hide();
87+ tutorialRight.show();
88+ }
89+ }
90+
91+ TutorialRight {
92+ id: tutorialRight
93+ objectName: "tutorialRight"
94+ parent: root.stages
95+ anchors.fill: parent
96+ edgeSize: root.edgeSize
97+ panel: root.panel
98+ paused: !shown || root.paused
99+
100+ onFinished: tutorialBottom.show()
101+ }
102+
103+ TutorialBottom {
104+ id: tutorialBottom
105+ objectName: "tutorialBottom"
106+ parent: root.stages
107+ anchors.fill: parent
108+ edgeSize: root.edgeSize
109+ paused: !shown || root.paused
110+
111+ onFinished: tutorialBottomFinish.show()
112+ }
113+
114+ TutorialBottomFinish {
115+ id: tutorialBottomFinish
116+ objectName: "tutorialBottomFinish"
117+ parent: root.stages
118+ anchors.fill: parent
119+ backgroundFadesOut: true
120+ paused: !shown || root.paused
121+
122+ onFinished: root.finish()
123+ }
124+}
qml/Tutorial/TutorialLeft.qmlnew file+91
@@ -0,0 +1,91 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.3
18+import Ubuntu.Components 1.1
19+import "." as LocalComponents
20+
21+TutorialPage {
22+ id: root
23+
24+ property var launcher
25+
26+ title: i18n.tr("Open the launcher")
27+ text: i18n.tr("Short swipe from the left edge.")
28+
29+ textXOffset: root.launcher.x + root.launcher.visibleWidth
30+
31+ Connections {
32+ target: root.launcher
33+
34+ onStateChanged: {
35+ if (root.launcher.state === "visible") {
36+ finishTimer.start();
37+ }
38+ }
39+
40+ onDash: {
41+ finishTimer.stop();
42+ root.showError();
43+ root.launcher.hide();
44+ }
45+ }
46+
47+ SequentialAnimation {
48+ id: teaseAnimation
49+ paused: running && root.paused
50+ running: !slider.active && root.launcher.visibleWidth === 0 && root.shown
51+ loops: Animation.Infinite
52+
53+ UbuntuNumberAnimation {
54+ target: root.launcher
55+ property: "x"
56+ to: units.gu(2)
57+ duration: UbuntuAnimation.SleepyDuration
58+ }
59+ UbuntuNumberAnimation {
60+ target: root.launcher
61+ property: "x"
62+ to: 0
63+ duration: UbuntuAnimation.SleepyDuration
64+ }
65+ }
66+
67+ Timer {
68+ id: finishTimer
69+ interval: 1
70+ onTriggered: {
71+ root.hide();
72+ root.launcher.x = 0; // make sure to reset launcher before we go
73+ }
74+ }
75+
76+ foreground {
77+ children: [
78+ LocalComponents.Slider {
79+ id: slider
80+ anchors {
81+ left: parent.left
82+ top: parent.top
83+ topMargin: root.textBottom + units.gu(3)
84+ }
85+ offset: root.launcher.x + root.launcher.visibleWidth + root.launcher.progress
86+ active: root.launcher.dragging
87+ shortSwipe: true
88+ }
89+ ]
90+ }
91+}
qml/Tutorial/TutorialLeftFinish.qmlnew file+41
@@ -0,0 +1,41 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.3
18+import Ubuntu.Components 1.1
19+import "." as LocalComponents
20+
21+TutorialPage {
22+ id: root
23+
24+ title: i18n.tr("These are the shortcuts to favorite apps")
25+ text: i18n.tr("Tap here to continue.")
26+ fullTextWidth: true
27+
28+ foreground {
29+ children: [
30+ LocalComponents.Tick {
31+ objectName: "tick"
32+ anchors {
33+ horizontalCenter: parent.horizontalCenter
34+ top: parent.top
35+ topMargin: root.textBottom + units.gu(3)
36+ }
37+ onClicked: root.hide()
38+ }
39+ ]
40+ }
41+}
qml/Tutorial/TutorialPage.qmlnew file+245
@@ -0,0 +1,245 @@
1+/*
2+ * Copyright (C) 2013,2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.3
18+import Ubuntu.Components 1.1
19+import "../Components"
20+
21+Showable {
22+ id: root
23+
24+ // This is the header displayed, like "Right edge"
25+ property alias title: titleLabel.text
26+
27+ // This is the block of text displayed below the header
28+ property alias text: textLabel.text
29+
30+ // Whether animations are paused
31+ property bool paused
32+
33+ // Whether to give the text the full width that the title has
34+ property bool fullTextWidth
35+
36+ // Whether whole page (background + foreground) or just the foreground fades in
37+ property bool backgroundFadesIn: false
38+
39+ // Whether whole page (background + foreground) or just the foreground fades out
40+ property bool backgroundFadesOut: false
41+
42+ // The foreground Item, add children to it that you want to fade in
43+ property alias foreground: foregroundExtra
44+
45+ // The text label bottom, so you can position elements relative to it
46+ readonly property real textBottom: Math.max(textLabel.y + textLabel.height, errorTextLabel.y + errorTextLabel.height)
47+
48+ // The MouseArea that eats events (so you can adjust size as you will)
49+ property alias mouseArea: mouseArea
50+
51+ // X/Y offsets for text
52+ property real textXOffset: 0
53+ property real textYOffset: 0
54+
55+ // Foreground text opacity
56+ property real textOpacity: 1
57+
58+ signal finished()
59+
60+ function showError() {
61+ errorTimer.start();
62+ }
63+
64+ ////
65+
66+ visible: false
67+ shown: false
68+
69+ property real _foregroundHideOpacity
70+
71+ showAnimation: StandardAnimation {
72+ property: root.backgroundFadesIn ? "opacity" : "_foregroundHideOpacity"
73+ from: 0
74+ to: 1
75+ duration: root.backgroundFadesIn ? UbuntuAnimation.SleepyDuration : UbuntuAnimation.BriskDuration
76+ onStarted: root.visible = true
77+ }
78+
79+ hideAnimation: StandardAnimation {
80+ property: root.backgroundFadesOut ? "opacity" : "_foregroundHideOpacity"
81+ to: 0
82+ duration: UbuntuAnimation.BriskDuration
83+ onStopped: {
84+ root.visible = false;
85+ root.finished();
86+ }
87+ }
88+
89+ QtObject {
90+ id: d
91+
92+ readonly property real sideMargin: units.gu(5.5)
93+ readonly property real verticalOffset: -units.gu(9)
94+ readonly property real textXOffset: Math.max(0, root.textXOffset - sideMargin + units.gu(2))
95+
96+ property real fadeInOffset: {
97+ if (showAnimation.running) {
98+ var opacity = root[root.showAnimation.property]
99+ return (1 - opacity) * units.gu(3);
100+ } else {
101+ return 0;
102+ }
103+ }
104+ }
105+
106+ Timer {
107+ id: errorTimer
108+ interval: 3500
109+ }
110+
111+ MouseArea { // eat any errant presses
112+ id: mouseArea
113+ anchors.fill: parent
114+ }
115+
116+ Rectangle {
117+ anchors.fill: parent
118+ color: "black"
119+ opacity: 0.82
120+ }
121+
122+ Item {
123+ id: foreground
124+ anchors.fill: parent
125+ opacity: root._foregroundHideOpacity
126+
127+ Item {
128+ anchors.fill: parent
129+ opacity: root.textOpacity
130+
131+ Label {
132+ id: titleLabel
133+ anchors {
134+ top: parent.verticalCenter
135+ topMargin: d.verticalOffset + root.textYOffset
136+ left: parent.left
137+ leftMargin: d.sideMargin + d.textXOffset
138+ }
139+ width: parent.width - d.sideMargin * 2
140+ horizontalAlignment: Text.AlignLeft
141+ wrapMode: Text.Wrap
142+ font.weight: Font.Light
143+ font.pixelSize: units.gu(3.5)
144+ }
145+
146+ Label {
147+ id: textLabel
148+ anchors {
149+ top: titleLabel.bottom
150+ topMargin: units.gu(2)
151+ left: parent.left
152+ leftMargin: d.sideMargin + d.textXOffset
153+ }
154+ width: (parent.width - d.sideMargin * 2) * (fullTextWidth ? 1 : 0.66)
155+ horizontalAlignment: Text.AlignLeft
156+ wrapMode: Text.Wrap
157+ font.weight: Font.Light
158+ font.pixelSize: units.gu(2.5)
159+ }
160+
161+ // We use two separate labels like this rather than just changing
162+ // the text of the above labels because we want to know where to place
163+ // sliders (via root.textBottom) without having that place change
164+ // as the text changes length.
165+ Label {
166+ id: errorTitleLabel
167+ objectName: "errorTitleLabel"
168+ anchors {
169+ top: titleLabel.top
170+ left: titleLabel.left
171+ }
172+ width: titleLabel.width
173+ horizontalAlignment: titleLabel.horizontalAlignment
174+ wrapMode: titleLabel.wrapMode
175+ font.weight: titleLabel.font.weight
176+ font.pixelSize: titleLabel.font.pixelSize
177+ opacity: 0
178+ text: i18n.tr("You almost got it!")
179+ }
180+
181+ Label {
182+ id: errorTextLabel
183+ objectName: "errorTextLabel"
184+ anchors {
185+ top: errorTitleLabel.bottom
186+ topMargin: textLabel.anchors.topMargin
187+ left: textLabel.left
188+ }
189+ width: textLabel.width
190+ horizontalAlignment: textLabel.horizontalAlignment
191+ wrapMode: textLabel.wrapMode
192+ font.weight: textLabel.font.weight
193+ font.pixelSize: textLabel.font.pixelSize
194+ opacity: 0
195+ text: i18n.tr("Try again.")
196+ }
197+ }
198+
199+ // A place for subclasses to add extra widgets
200+ Item {
201+ id: foregroundExtra
202+ anchors.fill: parent
203+ }
204+ }
205+
206+ states: State {
207+ name: "errorState"
208+ when: errorTimer.running
209+ PropertyChanges { target: titleLabel; opacity: 0 }
210+ PropertyChanges { target: textLabel; opacity: 0 }
211+ PropertyChanges { target: errorTitleLabel; opacity: 1 }
212+ PropertyChanges { target: errorTextLabel; opacity: 1 }
213+ }
214+
215+ transitions: Transition {
216+ to: "errorState"
217+ reversible: true
218+ SequentialAnimation {
219+ ParallelAnimation {
220+ StandardAnimation {
221+ target: titleLabel
222+ property: "opacity"
223+ duration: UbuntuAnimation.BriskDuration
224+ }
225+ StandardAnimation {
226+ target: textLabel
227+ property: "opacity"
228+ duration: UbuntuAnimation.BriskDuration
229+ }
230+ }
231+ ParallelAnimation {
232+ StandardAnimation {
233+ target: errorTitleLabel
234+ property: "opacity"
235+ duration: UbuntuAnimation.BriskDuration
236+ }
237+ StandardAnimation {
238+ target: errorTextLabel
239+ property: "opacity"
240+ duration: UbuntuAnimation.BriskDuration
241+ }
242+ }
243+ }
244+ }
245+}
qml/Tutorial/TutorialRight.qmlnew file+223
@@ -0,0 +1,223 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.3
18+import Ubuntu.Components 1.1
19+import Ubuntu.Gestures 0.1
20+import Unity.Application 0.1
21+import "../Components"
22+import "../Stages"
23+import "." as LocalComponents
24+
25+TutorialPage {
26+ id: root
27+
28+ property var panel
29+ property alias edgeSize: stage.dragAreaWidth
30+
31+ title: i18n.tr("To view open apps")
32+ text: i18n.tr("Long swipe from the right edge.")
33+
34+ textOpacity: 1 - slider.percent
35+
36+ SequentialAnimation {
37+ id: teaseAnimation
38+ paused: running && root.paused
39+ running: !stage.dragging && stage.dragProgress === 0
40+ loops: Animation.Infinite
41+
42+ UbuntuNumberAnimation {
43+ target: stage
44+ property: "x"
45+ to: -stage.dragAreaOverlap
46+ duration: UbuntuAnimation.SleepyDuration
47+ }
48+ UbuntuNumberAnimation {
49+ target: stage
50+ property: "x"
51+ to: 0
52+ duration: UbuntuAnimation.SleepyDuration
53+ }
54+ }
55+
56+ foreground {
57+ children: [
58+ LocalComponents.Slider {
59+ id: slider
60+ anchors {
61+ right: parent.right
62+ top: parent.top
63+ topMargin: root.textBottom + units.gu(3)
64+ }
65+ rotation: 180
66+ offset: stage.dragProgress - stage.x
67+ active: stage.dragging
68+ },
69+
70+ // Just assume PhoneStage for now. The tablet version of the right-edge
71+ // tutorial is still being spec'd by the design team.
72+ PhoneStage {
73+ id: stage
74+ objectName: "stage"
75+ anchors.top: parent.top
76+ width: parent.width
77+ height: parent.height
78+ applicationManager: fakeAppManager
79+ color: "transparent"
80+ interactive: false
81+ altTabEnabled: false
82+ focusFirstApp: false
83+ startScale: 0.8
84+ endScale: 0.6
85+ dragAreaOverlap: units.gu(2)
86+
87+ onOpened: {
88+ overlay.show();
89+ root.textOpacity = 0;
90+ slider.visible = false;
91+ }
92+
93+ onDraggingChanged: {
94+ if (!dragging) {
95+ if (!overlay.shown) {
96+ root.showError();
97+ }
98+ teaseAnimation.complete();
99+ }
100+ }
101+ },
102+
103+ Showable {
104+ id: overlay
105+ objectName: "overlay"
106+ anchors.fill: parent
107+
108+ opacity: 0
109+ shown: false
110+ showAnimation: UbuntuNumberAnimation { property: "opacity"; to: 1 }
111+
112+ Label {
113+ anchors.top: parent.top
114+ anchors.topMargin: root.panel.panelHeight + units.gu(2)
115+ anchors.left: parent.left
116+ anchors.leftMargin: units.gu(2)
117+ anchors.right: parent.right
118+ anchors.rightMargin: units.gu(2)
119+ wrapMode: Text.Wrap
120+ horizontalAlignment: Text.AlignHCenter
121+ fontSize: "large"
122+ text: i18n.tr("View all your running tasks.")
123+ }
124+
125+ LocalComponents.Tick {
126+ objectName: "tick"
127+ anchors.bottom: bottomOverlayText.top
128+ anchors.bottomMargin: units.gu(1)
129+ anchors.horizontalCenter: bottomOverlayText.horizontalCenter
130+ onClicked: root.hide()
131+ }
132+
133+ Label {
134+ id: bottomOverlayText
135+ anchors.bottom: parent.bottom
136+ anchors.bottomMargin: units.gu(2)
137+ anchors.left: parent.left
138+ anchors.leftMargin: units.gu(2)
139+ anchors.right: parent.right
140+ anchors.rightMargin: units.gu(2)
141+ wrapMode: Text.Wrap
142+ horizontalAlignment: Text.AlignHCenter
143+ fontSize: "small"
144+ text: i18n.tr("Tap here to continue.")
145+ }
146+ }
147+ ]
148+ }
149+
150+ ListModel {
151+ id: fakeAppManager
152+
153+ readonly property string focusedApplicationId: "facebook"
154+
155+ function focusApplication(appId) {}
156+ function requestFocusApplication(appId) {}
157+ function findApplication(appId) {return null;}
158+
159+ signal applicationAdded(string appId)
160+ signal applicationRemoved(string appId)
161+ signal focusRequested(string appId)
162+
163+ ListElement {
164+ appId: "facebook"
165+ fullscreen: false
166+ name: ""
167+ icon: ""
168+ state: ApplicationInfoInterface.Stopped
169+ splashTitle: ""
170+ splashImage: ""
171+ splashShowHeader: false
172+ splashColor: "transparent"
173+ splashColorHeader: "transparent"
174+ splashColorFooter: "transparent"
175+ defaultScreenshot: "../Tutorial/graphics/facebook.png"
176+ }
177+
178+ ListElement {
179+ appId: "camera"
180+ fullscreen: false
181+ name: ""
182+ icon: ""
183+ state: ApplicationInfoInterface.Stopped
184+ splashTitle: ""
185+ splashImage: ""
186+ splashShowHeader: false
187+ splashColor: "transparent"
188+ splashColorHeader: "transparent"
189+ splashColorFooter: "transparent"
190+ defaultScreenshot: "../Tutorial/graphics/camera.png"
191+ }
192+
193+ ListElement {
194+ appId: "gallery"
195+ fullscreen: false
196+ name: ""
197+ icon: ""
198+ state: ApplicationInfoInterface.Stopped
199+ splashTitle: ""
200+ splashImage: ""
201+ splashShowHeader: false
202+ splashColor: "transparent"
203+ splashColorHeader: "transparent"
204+ splashColorFooter: "transparent"
205+ defaultScreenshot: "../Tutorial/graphics/gallery.png"
206+ }
207+
208+ ListElement {
209+ appId: "dialer"
210+ fullscreen: false
211+ name: ""
212+ icon: ""
213+ state: ApplicationInfoInterface.Stopped
214+ splashTitle: ""
215+ splashImage: ""
216+ splashShowHeader: false
217+ splashColor: "transparent"
218+ splashColorHeader: "transparent"
219+ splashColorFooter: "transparent"
220+ defaultScreenshot: "../Tutorial/graphics/dialer.png"
221+ }
222+ }
223+}
qml/Tutorial/graphics/camera.pngnew file

Binary file not shown.

qml/Tutorial/graphics/chevron.pngnew file

Binary file not shown.

qml/Tutorial/graphics/dialer.pngnew file

Binary file not shown.

qml/Tutorial/graphics/facebook.pngnew file

Binary file not shown.

qml/Tutorial/graphics/gallery.pngnew file

Binary file not shown.

qml/Tutorial/graphics/tick.pngnew file

Binary file not shown.

tests/autopilot/unity8/shell/emulators/edges_demo.pydeleted−140
@@ -1,140 +0,0 @@
1−# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2−#
3−# Unity Autopilot Test Suite
4−# Copyright (C) 2014 Canonical
5−#
6−# This program is free software: you can redistribute it and/or modify
7−# it under the terms of the GNU General Public License as published by
8−# the Free Software Foundation, either version 3 of the License, or
9−# (at your option) any later version.
10−#
11−# This program is distributed in the hope that it will be useful,
12−# but WITHOUT ANY WARRANTY; without even the implied warranty of
13−# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14−# GNU General Public License for more details.
15−#
16−# You should have received a copy of the GNU General Public License
17−# along with this program. If not, see <http://www.gnu.org/licenses/>.
18−#
19−
20−import logging
21−import time
22−
23−import ubuntuuitoolkit
24−
25−import autopilot
26−from autopilot import introspection
27−
28−
29−logger = logging.getLogger(__name__)
30−
31−
32−class RightEdgeDemoOverlay(
33− ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
34−
35− @classmethod
36− def validate_dbus_object(cls, path, state):
37− name = introspection.get_classname_from_path(path)
38− if name == b'EdgeDemoOverlay':
39− if state['edge'][1] == 'right':
40− return True
41− return False
42−
43− @autopilot.logging.log_action(logger.info)
44− def swipe(self):
45− """Swipe to the left to complete this demo step."""
46− x, y, width, height = self.globalRect
47− start_x = x + width
48− stop_x = x
49− start_y = stop_y = y + height // 2
50− self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
51− return self.get_root_instance().wait_select_single(
52− edge='top', active=True)
53−
54−
55−class TopEdgeDemoOverlay(
56− ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
57−
58− @classmethod
59− def validate_dbus_object(cls, path, state):
60− name = introspection.get_classname_from_path(path)
61− if name == b'EdgeDemoOverlay':
62− if state['edge'][1] == 'top':
63− return True
64− return False
65−
66− @autopilot.logging.log_action(logger.info)
67− def swipe(self):
68− """Swipe to the bottom to complete this demo step."""
69− x, y, width, height = self.globalRect
70− start_x = stop_x = x + width // 2
71− start_y = y
72− stop_y = y + height
73− self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
74− return self.get_root_instance().wait_select_single(
75− edge='bottom', active=True)
76−
77−
78−class BottomEdgeDemoOverlay(
79− ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
80−
81− @classmethod
82− def validate_dbus_object(cls, path, state):
83− name = introspection.get_classname_from_path(path)
84− if name == b'EdgeDemoOverlay':
85− if state['edge'][1] == 'bottom':
86− return True
87− return False
88−
89− @autopilot.logging.log_action(logger.info)
90− def swipe(self):
91− """Swipe to the top to complete this demo step."""
92− x, y, width, height = self.globalRect
93− start_x = stop_x = x + width // 2
94− start_y = y + height
95− stop_y = y
96− self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
97− return self.get_root_instance().wait_select_single(
98− edge='left', active=True)
99−
100−
101−class LeftEdgeDemoOverlay(
102− ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
103−
104− @classmethod
105− def validate_dbus_object(cls, path, state):
106− name = introspection.get_classname_from_path(path)
107− if name == b'EdgeDemoOverlay':
108− if state['edge'][1] == 'left':
109− return True
110− return False
111−
112− @autopilot.logging.log_action(logger.info)
113− def swipe(self):
114− """Swipe to the right to complete this demo step."""
115− x, y, width, height = self.globalRect
116− start_x = x
117− stop_x = x + width
118− start_y = stop_y = y + height // 2
119− self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
120− return self.get_root_instance().wait_select_single(
121− edge='none', active=True)
122−
123−
124−class FinalEdgeDemoOverlay(
125− ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
126−
127− @classmethod
128− def validate_dbus_object(cls, path, state):
129− name = introspection.get_classname_from_path(path)
130− if name == b'EdgeDemoOverlay':
131− if state['edge'][1] == 'none':
132− return True
133− return False
134−
135− @autopilot.logging.log_action(logger.info)
136− def tap_to_start(self):
137− """Tap to finish the demo and start using the Ubuntu Touch."""
138− time.sleep(1)
139− self.pointing_device.click_object(self)
140− self.shown.wait_for(False)
tests/autopilot/unity8/shell/emulators/tutorial.pynew file+74
@@ -0,0 +1,74 @@
1+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2+#
3+# Unity Autopilot Test Suite
4+# Copyright (C) 2014 Canonical
5+#
6+# This program is free software: you can redistribute it and/or modify
7+# it under the terms of the GNU General Public License as published by
8+# the Free Software Foundation, either version 3 of the License, or
9+# (at your option) any later version.
10+#
11+# This program is distributed in the hope that it will be useful,
12+# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+# GNU General Public License for more details.
15+#
16+# You should have received a copy of the GNU General Public License
17+# along with this program. If not, see <http://www.gnu.org/licenses/>.
18+#
19+
20+import logging
21+import time
22+
23+import ubuntuuitoolkit
24+
25+import autopilot
26+from autopilot import introspection
27+
28+
29+logger = logging.getLogger(__name__)
30+
31+
32+class TutorialPage(
33+ ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
34+
35+ @classmethod
36+ def validate_dbus_object(cls, path, state):
37+ name = introspection.get_classname_from_path(path)
38+ return name in (b'TutorialPage', b'TutorialLeft',
39+ b'TutorialLeftFinish', b'TutorialRight',
40+ b'TutorialBottom', b'TutorialBottomFinish')
41+
42+ @autopilot.logging.log_action(logger.info)
43+ def short_swipe_right(self):
44+ self.shown.wait_for(True)
45+ x, y, width, height = self.globalRect
46+ start_x = x
47+ stop_x = x + width // 3
48+ start_y = stop_y = y + height // 2
49+ self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
50+
51+ @autopilot.logging.log_action(logger.info)
52+ def swipe_left(self):
53+ self.shown.wait_for(True)
54+ x, y, width, height = self.globalRect
55+ start_x = width
56+ stop_x = x
57+ start_y = stop_y = y + height // 2
58+ self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
59+
60+ @autopilot.logging.log_action(logger.info)
61+ def swipe_up(self):
62+ self.shown.wait_for(True)
63+ x, y, width, height = self.globalRect
64+ start_y = height
65+ stop_y = y
66+ start_x = stop_x = x + width // 2
67+ self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
68+
69+ @autopilot.logging.log_action(logger.info)
70+ def tap(self):
71+ """Tap the tick button to complete this step."""
72+ self.shown.wait_for(True)
73+ button = self.wait_select_single(objectName="tick")
74+ self.pointing_device.click_object(button)
tests/autopilot/unity8/shell/fixture_setup.py+6−6
@@ -54,7 +54,7 @@ class FakeScopes(fixtures.Fixture):
54 return ld_library_path
54 return ld_library_path
55
55
56
56
57−class EdgesDemo(fixtures.Fixture):
57+class Tutorial(fixtures.Fixture):
58
58
59 def __init__(self, enable):
59 def __init__(self, enable):
60 super().__init__()
60 super().__init__()
@@ -62,12 +62,12 @@ class EdgesDemo(fixtures.Fixture):
62
62
63 def setUp(self):
63 def setUp(self):
64 super().setUp()
64 super().setUp()
65− original_state = self._is_edges_demo_enabled()
65+ original_state = self._is_tutorial_enabled()
66 if self.enable != original_state:
66 if self.enable != original_state:
67− self.addCleanup(self._set_edges_demo, original_state)
67+ self.addCleanup(self._set_tutorial, original_state)
68− self._set_edges_demo(self.enable)
68+ self._set_tutorial(self.enable)
69
69
70− def _is_edges_demo_enabled(self):
70+ def _is_tutorial_enabled(self):
71 command = [
71 command = [
72 'dbus-send', '--system', '--print-reply',
72 'dbus-send', '--system', '--print-reply',
73 '--dest=org.freedesktop.Accounts',
73 '--dest=org.freedesktop.Accounts',
@@ -79,7 +79,7 @@ class EdgesDemo(fixtures.Fixture):
79 output = subprocess.check_output(command, universal_newlines=True)
79 output = subprocess.check_output(command, universal_newlines=True)
80 return True if output.count('true') else False
80 return True if output.count('true') else False
81
81
82− def _set_edges_demo(self, value):
82+ def _set_tutorial(self, value):
83 value_string = 'true' if value else 'false'
83 value_string = 'true' if value else 'false'
84 command = [
84 command = [
85 'dbus-send', '--system', '--print-reply',
85 'dbus-send', '--system', '--print-reply',
tests/autopilot/unity8/shell/tests/test_edges_demo.pydeleted−51
@@ -1,51 +0,0 @@
1−# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2−#
3−# Unity Autopilot Test Suite
4−# Copyright (C) 2014 Canonical
5−#
6−# This program is free software: you can redistribute it and/or modify
7−# it under the terms of the GNU General Public License as published by
8−# the Free Software Foundation, either version 3 of the License, or
9−# (at your option) any later version.
10−#
11−# This program is distributed in the hope that it will be useful,
12−# but WITHOUT ANY WARRANTY; without even the implied warranty of
13−# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14−# GNU General Public License for more details.
15−#
16−# You should have received a copy of the GNU General Public License
17−# along with this program. If not, see <http://www.gnu.org/licenses/>.
18−#
19−
20−from autopilot.matchers import Eventually
21−from testtools.matchers import Equals
22−
23−from unity8.shell import (
24− fixture_setup,
25− tests
26−)
27−# unused import to load the edge emulators custom proxy objects.
28−from unity8.shell.emulators import edges_demo # NOQA
29−
30−
31−class EdgesDemoTestCase(tests.UnityTestCase):
32−
33− def setUp(self):
34− super(EdgesDemoTestCase, self).setUp()
35− self._qml_mock_enabled = False
36− self._data_dirs_mock_enabled = False
37−
38− self.useFixture(fixture_setup.EdgesDemo(True))
39− self.unity = self.launch_unity()
40−
41− def test_complete_edge_demo(self):
42− edge_demo = self.unity.select_single('EdgeDemo')
43− self.assertThat(edge_demo.running, Eventually(Equals(True)))
44− right_edge_overlay = self.unity.wait_select_single(
45− edge='right', active=True)
46− top_edge_overlay = right_edge_overlay.swipe()
47− bottom_edge_overlay = top_edge_overlay.swipe()
48− left_edge_overlay = bottom_edge_overlay.swipe()
49− final_overlay = left_edge_overlay.swipe()
50− final_overlay.tap_to_start()
51− self.assertThat(edge_demo.running, Eventually(Equals(False)))
tests/autopilot/unity8/shell/tests/test_tutorial.pynew file+57
@@ -0,0 +1,57 @@
1+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2+#
3+# Unity Autopilot Test Suite
4+# Copyright (C) 2014 Canonical
5+#
6+# This program is free software: you can redistribute it and/or modify
7+# it under the terms of the GNU General Public License as published by
8+# the Free Software Foundation, either version 3 of the License, or
9+# (at your option) any later version.
10+#
11+# This program is distributed in the hope that it will be useful,
12+# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+# GNU General Public License for more details.
15+#
16+# You should have received a copy of the GNU General Public License
17+# along with this program. If not, see <http://www.gnu.org/licenses/>.
18+#
19+
20+from autopilot.matchers import Eventually
21+from testtools.matchers import Equals
22+
23+from unity8.shell import (
24+ fixture_setup,
25+ tests
26+)
27+# unused import to load the tutorial emulators custom proxy objects.
28+from unity8.shell.emulators import tutorial # NOQA
29+
30+
31+class TutorialTestCase(tests.UnityTestCase):
32+
33+ def setUp(self):
34+ super(TutorialTestCase, self).setUp()
35+ self._qml_mock_enabled = False
36+ self._data_dirs_mock_enabled = False
37+
38+ self.useFixture(fixture_setup.Tutorial(True))
39+ self.unity = self.launch_unity()
40+
41+ def test_complete_tutorial(self):
42+ greeter = self.main_window.get_greeter()
43+ tutorial = self.unity.select_single('Tutorial')
44+ self.assertThat(tutorial.running, Eventually(Equals(True)))
45+ greeter.swipe()
46+ page = self.unity.wait_select_single(objectName='tutorialLeft')
47+ page.short_swipe_right()
48+ page = self.unity.wait_select_single(objectName='tutorialLeftFinish')
49+ page.tap()
50+ page = self.unity.wait_select_single(objectName='tutorialRight')
51+ page.swipe_left()
52+ page.tap()
53+ page = self.unity.wait_select_single(objectName='tutorialBottom')
54+ page.swipe_up()
55+ page = self.unity.wait_select_single(objectName='tutorialBottomFinish')
56+ page.tap()
57+ self.assertThat(tutorial.running, Eventually(Equals(False)))
tests/mocks/Unity/Application/MirSurfaceItem.qml+16−1
@@ -44,9 +44,24 @@ Rectangle {
44 }
44 }
45
45
46 Text {
46 Text {
47+ text: surfaceText.text
48+ color: "black"
49+ font: surfaceText.font
50+ fontSizeMode: Text.Fit
51+ minimumPixelSize: 10
52+ verticalAlignment: Text.AlignVCenter
53+ x: surfaceText.x
54+ y: surfaceText.y
55+ width: surfaceText.width
56+ height: surfaceText.height
57+
58+ transform: Translate { x: -2; y: -2 }
59+ }
60+ Text {
61+ id: surfaceText
47 anchors.fill: parent
62 anchors.fill: parent
48 text: "SURFACE"
63 text: "SURFACE"
49− color: "yellow"
64+ color: root.parent && root.parent.activeFocus ? "yellow" : "blue"
50 font.bold: true
65 font.bold: true
51 fontSizeMode: Text.Fit
66 fontSizeMode: Text.Fit
52 minimumPixelSize: 10; font.pixelSize: 200
67 minimumPixelSize: 10; font.pixelSize: 200
tests/mocks/Unity/Indicators/fakeindicatorsmodel.cpp+13−1
@@ -21,7 +21,8 @@
21#include "indicators.h"
21#include "indicators.h"
22
22
23FakeIndicatorsModel::FakeIndicatorsModel(QObject *parent)
23FakeIndicatorsModel::FakeIndicatorsModel(QObject *parent)
24− : QAbstractListModel(parent)
24+ : QAbstractListModel(parent),
25+ m_profile("phone")
25{
26{
26 QObject::connect(this, SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SIGNAL(countChanged()));
27 QObject::connect(this, SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SIGNAL(countChanged()));
27 QObject::connect(this, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SIGNAL(countChanged()));
28 QObject::connect(this, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SIGNAL(countChanged()));
@@ -38,6 +39,17 @@ int FakeIndicatorsModel::count() const
38 return rowCount();
39 return rowCount();
39}
40}
40
41
42+QString FakeIndicatorsModel::profile() const
43+{
44+ return m_profile;
45+}
46+
47+void FakeIndicatorsModel::setProfile(const QString& profile)
48+{
49+ m_profile = profile;
50+ Q_EMIT profileChanged();
51+}
52+
41void FakeIndicatorsModel::load(const QString&)
53void FakeIndicatorsModel::load(const QString&)
42{
54{
43}
55}
tests/mocks/Unity/Indicators/fakeindicatorsmodel.h+6
@@ -27,7 +27,9 @@ class FakeIndicatorsModel : public QAbstractListModel
27 Q_OBJECT
27 Q_OBJECT
28 Q_ENUMS(Roles)
28 Q_ENUMS(Roles)
29 Q_PROPERTY(int count READ count NOTIFY countChanged)
29 Q_PROPERTY(int count READ count NOTIFY countChanged)
30+ Q_PROPERTY(QString profile READ profile WRITE setProfile NOTIFY profileChanged)
30 Q_PROPERTY(QVariant modelData READ modelData WRITE setModelData NOTIFY modelDataChanged)
31 Q_PROPERTY(QVariant modelData READ modelData WRITE setModelData NOTIFY modelDataChanged)
32+
31public:
33public:
32
34
33 FakeIndicatorsModel(QObject *parent=0);
35 FakeIndicatorsModel(QObject *parent=0);
@@ -42,6 +44,8 @@ public:
42
44
43 Q_INVOKABLE QVariant data(int row, int role) const;
45 Q_INVOKABLE QVariant data(int row, int role) const;
44
46
47+ QString profile() const;
48+ void setProfile(const QString& profile);
45 void setModelData(const QVariant& data);
49 void setModelData(const QVariant& data);
46 QVariant modelData() const { return m_modelData; }
50 QVariant modelData() const { return m_modelData; }
47
51
@@ -54,11 +58,13 @@ public:
54
58
55Q_SIGNALS:
59Q_SIGNALS:
56 void countChanged();
60 void countChanged();
61+ void profileChanged();
57 void modelDataChanged();
62 void modelDataChanged();
58
63
59private:
64private:
60 int count() const;
65 int count() const;
61
66
67+ QString m_profile;
62 QVariant m_modelData;
68 QVariant m_modelData;
63};
69};
64
70
tests/mocks/Unity/Launcher/CMakeLists.txt+1−1
@@ -1,4 +1,4 @@
1−pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=5)
1+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=6)
2
2
3include_directories(
3include_directories(
4 ${CMAKE_CURRENT_SOURCE_DIR}
4 ${CMAKE_CURRENT_SOURCE_DIR}
tests/mocks/Unity/Launcher/MockLauncherModel.cpp+10
@@ -209,6 +209,16 @@ void MockLauncherModel::setApplicationManager(unity::shell::application::Applica
209 Q_UNUSED(applicationManager)
209 Q_UNUSED(applicationManager)
210}
210}
211
211
212+bool MockLauncherModel::onlyPinned() const
213+{
214+ return false;
215+}
216+
217+void MockLauncherModel::setOnlyPinned(bool onlyPinned)
218+{
219+ Q_UNUSED(onlyPinned)
220+}
221+
212void MockLauncherModel::emitHint()
222void MockLauncherModel::emitHint()
213{
223{
214 Q_EMIT hint();
224 Q_EMIT hint();
tests/mocks/Unity/Launcher/MockLauncherModel.h+3
@@ -49,6 +49,9 @@ public:
49 unity::shell::application::ApplicationManagerInterface *applicationManager() const;
49 unity::shell::application::ApplicationManagerInterface *applicationManager() const;
50 void setApplicationManager(unity::shell::application::ApplicationManagerInterface *applicationManager);
50 void setApplicationManager(unity::shell::application::ApplicationManagerInterface *applicationManager);
51
51
52+ bool onlyPinned() const override;
53+ void setOnlyPinned(bool onlyPinned) override;
54+
52 // For testing
55 // For testing
53 Q_INVOKABLE void emitHint();
56 Q_INVOKABLE void emitHint();
54
57
tests/mocks/Unity/Notifications/CMakeLists.txt+2−1
@@ -4,7 +4,8 @@ include_directories(
4
4
5set(MockNotificationsPlugin_SOURCES
5set(MockNotificationsPlugin_SOURCES
6 plugin.cpp
6 plugin.cpp
7− MockNotificationTypes.cpp
7+ MockNotification.cpp
8+ MockNotificationModel.cpp
8 MockActionModel.cpp
9 MockActionModel.cpp
9)
10)
10
11
tests/mocks/Unity/Notifications/MockActionModel.cpp+6−4
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright 2014 Canonical Ltd.
2+ * Copyright 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 Lesser General Public License as published by
5 * it under the terms of the GNU Lesser General Public License as published by
@@ -25,8 +25,6 @@ struct ActionModelPrivate {
25};
25};
26
26
27ActionModel::ActionModel(QObject *parent) : QStringListModel(parent), p(new ActionModelPrivate) {
27ActionModel::ActionModel(QObject *parent) : QStringListModel(parent), p(new ActionModelPrivate) {
28− insertAction("ok_id", "Ok");
29− insertAction("cancel_id", "Cancel");
30}
28}
31
29
32ActionModel::~ActionModel() {
30ActionModel::~ActionModel() {
@@ -66,7 +64,11 @@ Q_INVOKABLE QVariant ActionModel::data(int row, int role) const
66 return data(index(row, 0), role);
64 return data(index(row, 0), role);
67}
65}
68
66
69−void ActionModel::insertAction(const QString &id, const QString &label) {
67+void ActionModel::append(const QString &id, const QString &label) {
70 p->ids.push_back(id);
68 p->ids.push_back(id);
71 p->labels.push_back(label);
69 p->labels.push_back(label);
72}
70}
71+
72+int ActionModel::getCount() const {
73+ return p->labels.size();
74+}
tests/mocks/Unity/Notifications/MockActionModel.h+4−2
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright 2014 Canonical Ltd.
2+ * Copyright 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 Lesser General Public License as published by
5 * it under the terms of the GNU Lesser General Public License as published by
@@ -26,6 +26,7 @@ struct ActionModelPrivate;
26
26
27class ActionModel : public QStringListModel {
27class ActionModel : public QStringListModel {
28 Q_OBJECT
28 Q_OBJECT
29+ Q_PROPERTY(int count READ getCount)
29
30
30public:
31public:
31 ActionModel(QObject *parent=nullptr);
32 ActionModel(QObject *parent=nullptr);
@@ -42,7 +43,8 @@ public:
42 };
43 };
43 Q_INVOKABLE QVariant data(int row, int role) const;
44 Q_INVOKABLE QVariant data(int row, int role) const;
44
45
45− void insertAction(const QString &id, const QString &label);
46+ Q_INVOKABLE void append(const QString &id, const QString &label);
47+ int getCount() const;
46
48
47private:
49private:
48 QScopedPointer<ActionModelPrivate> p;
50 QScopedPointer<ActionModelPrivate> p;
tests/mocks/Unity/Notifications/MockNotification.cppnew file+178
@@ -0,0 +1,178 @@
1+/*
2+ * Copyright 2015 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authors:
17+ * Mirco Mueller <mirco.mueller@canonical.com>
18+ */
19+
20+#include "MockNotification.h"
21+
22+#include <QDebug>
23+
24+struct MockNotificationPrivate {
25+ int id;
26+ QString summary;
27+ QString body;
28+ int value;
29+ MockNotification::Type type;
30+ QString icon;
31+ QString secondaryIcon;
32+ QStringList actions;
33+ ActionModel* actionsModel;
34+ QVariantMap hints;
35+};
36+
37+MockNotification::MockNotification(QObject *parent) : QObject(parent), p(new MockNotificationPrivate()) {
38+ p->actionsModel = new ActionModel();
39+}
40+
41+MockNotification::~MockNotification() {
42+}
43+
44+QString MockNotification::getSummary() const {
45+ return p->summary;
46+}
47+
48+void MockNotification::setSummary(const QString &summary) {
49+ if(p->summary != summary) {
50+ p->summary = summary;
51+ Q_EMIT summaryChanged(p->summary);
52+ Q_EMIT dataChanged(p->id);
53+ }
54+}
55+
56+QString MockNotification::getBody() const {
57+ return p->body;
58+}
59+
60+void MockNotification::setBody(const QString &body) {
61+ if(p->body != body) {
62+ p->body = body;
63+ Q_EMIT bodyChanged(p->body);
64+ Q_EMIT dataChanged(p->id);
65+ }
66+}
67+
68+int MockNotification::getID() const {
69+ return p->id;
70+}
71+
72+void MockNotification::setID(const int id) {
73+ p->id = id;
74+}
75+
76+int MockNotification::getValue() const {
77+ return p->value;
78+}
79+
80+void MockNotification::setValue(int value) {
81+ if(p->value != value) {
82+ p->value = value;
83+ Q_EMIT valueChanged(p->value);
84+ Q_EMIT dataChanged(p->id);
85+ }
86+}
87+
88+QString MockNotification::getIcon() const {
89+ return p->icon;
90+}
91+
92+void MockNotification::setIcon(const QString &icon) {
93+ if (icon.startsWith(" ") || icon.size() == 0) {
94+ p->icon = nullptr;
95+ }
96+ else {
97+ p->icon = icon;
98+
99+ if (icon.indexOf("/") == -1) {
100+ p->icon.prepend("image://theme/");
101+ }
102+ }
103+
104+ Q_EMIT iconChanged(p->icon);
105+ Q_EMIT dataChanged(p->id);
106+}
107+
108+QString MockNotification::getSecondaryIcon() const {
109+ return p->secondaryIcon;
110+}
111+
112+void MockNotification::setSecondaryIcon(const QString &secondaryIcon) {
113+ if (secondaryIcon.startsWith(" ") || secondaryIcon.size() == 0) {
114+ p->secondaryIcon = nullptr;
115+ }
116+ else {
117+ p->secondaryIcon = secondaryIcon;
118+
119+ if (secondaryIcon.indexOf("/") == -1) {
120+ p->secondaryIcon.prepend("image://theme/");
121+ }
122+ }
123+
124+ Q_EMIT secondaryIconChanged(p->secondaryIcon);
125+ Q_EMIT dataChanged(p->id);
126+}
127+
128+MockNotification::Type MockNotification::getType() const {
129+ return p->type;
130+}
131+
132+void MockNotification::setType(Type type) {
133+ if(p->type != type) {
134+ p->type = type;
135+ Q_EMIT typeChanged(p->type);
136+ }
137+}
138+
139+ActionModel* MockNotification::getActions() const {
140+ return p->actionsModel;
141+}
142+
143+void MockNotification::setActions(const QStringList &actions) {
144+ if(p->actions != actions) {
145+ p->actions = actions;
146+ Q_EMIT actionsChanged(p->actions);
147+
148+ for (int i = 0; i < p->actions.size(); i += 2) {
149+ p->actionsModel->append(p->actions[i], p->actions[i+1]);
150+ }
151+ }
152+}
153+
154+QVariantMap MockNotification::getHints() const {
155+ return p->hints;
156+}
157+
158+void MockNotification::setHints(const QVariantMap& hints) {
159+ if (p->hints != hints) {
160+ p->hints = hints;
161+ Q_EMIT hintsChanged(p->hints);
162+ }
163+}
164+
165+void MockNotification::invokeAction(const QString &action) {
166+ for(int i=0; i<p->actions.size(); i++) {
167+ if(p->actions[i] == action) {
168+ Q_EMIT actionInvoked(action);
169+ qDebug() << "Info: invoked action" << action;
170+ return;
171+ }
172+ }
173+ fprintf(stderr, "Error: tried to invoke action not in actionList.\n");
174+}
175+
176+void MockNotification::close() {
177+ Q_EMIT completed(p->id);
178+}
tests/mocks/Unity/Notifications/MockNotification.hnew file+94
@@ -0,0 +1,94 @@
1+/*
2+ * Copyright 2015 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authors:
17+ * Mirco Mueller <mirco.mueller@canonical.com>
18+ */
19+
20+#ifndef MOCK_NOTIFICATION_H
21+#define MOCK_NOTIFICATION_H
22+
23+#include "MockActionModel.h"
24+#include <QObject>
25+#include <QString>
26+#include <QStringList>
27+#include <QScopedPointer>
28+
29+struct MockNotificationPrivate;
30+
31+class MockNotification : public QObject {
32+ Q_OBJECT
33+ Q_ENUMS(Type)
34+ Q_PROPERTY(QString summary READ getSummary WRITE setSummary NOTIFY summaryChanged)
35+ Q_PROPERTY(QString body READ getBody WRITE setBody NOTIFY bodyChanged)
36+ Q_PROPERTY(int nid READ getID WRITE setID NOTIFY idChanged)
37+ Q_PROPERTY(int value READ getValue WRITE setValue NOTIFY valueChanged)
38+ Q_PROPERTY(QString icon READ getIcon WRITE setIcon NOTIFY iconChanged)
39+ Q_PROPERTY(QString secondaryIcon READ getSecondaryIcon WRITE setSecondaryIcon NOTIFY secondaryIconChanged)
40+ Q_PROPERTY(Type type READ getType WRITE setType NOTIFY typeChanged)
41+ Q_PROPERTY(QStringList rawActions WRITE setActions)
42+ Q_PROPERTY(ActionModel* actions READ getActions NOTIFY actionsChanged)
43+ Q_PROPERTY(QVariantMap hints READ getHints WRITE setHints NOTIFY hintsChanged)
44+
45+private:
46+ QScopedPointer<MockNotificationPrivate> p;
47+
48+public:
49+ enum Urgency { Low, Normal, Critical };
50+ enum Type { PlaceHolder, Confirmation, Ephemeral, Interactive, SnapDecision };
51+
52+Q_SIGNALS:
53+ void summaryChanged(const QString &summary);
54+ void bodyChanged(const QString &body);
55+ void idChanged(const int id);
56+ void valueChanged(int value);
57+ void iconChanged(const QString &icon);
58+ void secondaryIconChanged(const QString &secondaryIcon);
59+ void typeChanged(Type type);
60+ void actionsChanged(const QStringList &actions);
61+ void hintsChanged(const QVariantMap& hints);
62+
63+ void dataChanged(int nid);
64+ void completed(int nid);
65+ void actionInvoked(const QString &action);
66+
67+public:
68+ MockNotification(QObject *parent=nullptr);
69+ virtual ~MockNotification();
70+
71+ QString getSummary() const;
72+ void setSummary(const QString &summary);
73+ QString getBody() const;
74+ void setBody(const QString &body);
75+ int getID() const;
76+ void setID(const int id);
77+ int getValue() const;
78+ void setValue(int value);
79+ QString getIcon() const;
80+ void setIcon(const QString &icon);
81+ QString getSecondaryIcon() const;
82+ void setSecondaryIcon(const QString &secondaryIcon);
83+ Type getType() const;
84+ void setType(Type type);
85+ ActionModel* getActions() const;
86+ void setActions(const QStringList &actions);
87+ QVariantMap getHints() const;
88+ void setHints(const QVariantMap& hints);
89+
90+ Q_INVOKABLE void invokeAction(const QString &action);
91+ Q_INVOKABLE void close();
92+};
93+
94+#endif // MOCK_NOTIFICATION_H
tests/mocks/Unity/Notifications/MockNotificationModel.cppnew file+172
@@ -0,0 +1,172 @@
1+/*
2+ * Copyright 2015 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authors:
17+ * Mirco Mueller <mirco.mueller@canonical.com>
18+ */
19+
20+#include "MockNotificationModel.h"
21+#include "MockNotification.h"
22+
23+#include <unity/shell/notifications/ModelInterface.h>
24+
25+#include <QTimer>
26+#include <QList>
27+#include <QVector>
28+#include <QMap>
29+#include <QStringListModel>
30+#include <QQmlEngine>
31+
32+using namespace unity::shell::notifications;
33+
34+MockNotificationModel::MockNotificationModel(QObject *parent) : QAbstractListModel(parent) {
35+}
36+
37+MockNotificationModel::~MockNotificationModel() {
38+}
39+
40+int MockNotificationModel::rowCount(const QModelIndex &parent) const {
41+ return m_queue.size();
42+}
43+
44+int MockNotificationModel::getCount() const {
45+ return m_queue.size();
46+}
47+
48+QVariant MockNotificationModel::data(const QModelIndex &index, int role) const {
49+ if (!index.isValid())
50+ return QVariant();
51+
52+ switch(role) {
53+ case ModelInterface::RoleType:
54+ return QVariant(m_queue[index.row()]->getType());
55+
56+ case ModelInterface::RoleId:
57+ return QVariant(m_queue[index.row()]->getID());
58+
59+ case ModelInterface::RoleSummary:
60+ return QVariant(m_queue[index.row()]->getSummary());
61+
62+ case ModelInterface::RoleBody:
63+ return QVariant(m_queue[index.row()]->getBody());
64+
65+ case ModelInterface::RoleValue:
66+ return QVariant(m_queue[index.row()]->getValue());
67+
68+ case ModelInterface::RoleIcon:
69+ return QVariant(m_queue[index.row()]->getIcon());
70+
71+ case ModelInterface::RoleSecondaryIcon:
72+ return QVariant(m_queue[index.row()]->getSecondaryIcon());
73+
74+ case ModelInterface::RoleActions:
75+ return QVariant::fromValue(m_queue[index.row()]->getActions());
76+
77+ case ModelInterface::RoleHints:
78+ return QVariant(m_queue[index.row()]->getHints());
79+
80+ case ModelInterface::RoleNotification:
81+ return QVariant::fromValue(m_queue[index.row()]);
82+
83+ default:
84+ return QVariant();
85+ }
86+}
87+
88+void MockNotificationModel::append(MockNotification* n) {
89+ int location = m_queue.size();
90+ QModelIndex insertionPoint = QModelIndex();
91+ beginInsertRows(insertionPoint, location, location);
92+ m_queue.insert(location, n);
93+ endInsertRows();
94+}
95+
96+MockNotification* MockNotificationModel::getNotification(int id) const {
97+ for(int i=0; i < m_queue.size(); i++) {
98+ if(m_queue[i]->getID() == id) {
99+ return m_queue[i];
100+ }
101+ }
102+
103+ return nullptr;
104+}
105+
106+void MockNotificationModel::remove(const int id) {
107+ for(int i = 0; i < m_queue.size(); i++) {
108+ if(m_queue[i]->getID() == id) {
109+ removeInternal(i);
110+ return;
111+ }
112+ }
113+}
114+
115+void MockNotificationModel::removeSecond() {
116+ if(m_queue.size() < 2)
117+ return;
118+ removeInternal(1);
119+}
120+
121+void MockNotificationModel::removeInternal(int loc) {
122+ QModelIndex deletePoint = QModelIndex();
123+ beginRemoveRows(deletePoint, loc, loc);
124+ m_queue.erase(m_queue.begin() + loc);
125+ endRemoveRows();
126+}
127+
128+MockNotification* MockNotificationModel::getRaw(const int notificationId) const {
129+ for(int i = 0; i < m_queue.size(); i++) {
130+ if(m_queue[i]->getID() == notificationId) {
131+ MockNotification* n = m_queue[i];
132+ return n;
133+ }
134+ }
135+
136+ return nullptr;
137+}
138+
139+int MockNotificationModel::queued() const {
140+ return m_queue.size();
141+}
142+
143+QHash<int, QByteArray> MockNotificationModel::roleNames() const {
144+ QHash<int, QByteArray> roles;
145+
146+ roles.insert(ModelInterface::RoleType, "type");
147+ roles.insert(ModelInterface::RoleUrgency, "urgency");
148+ roles.insert(ModelInterface::RoleId, "id");
149+ roles.insert(ModelInterface::RoleSummary, "summary");
150+ roles.insert(ModelInterface::RoleBody, "body");
151+ roles.insert(ModelInterface::RoleValue, "value");
152+ roles.insert(ModelInterface::RoleIcon, "icon");
153+ roles.insert(ModelInterface::RoleSecondaryIcon, "secondaryIcon");
154+ roles.insert(ModelInterface::RoleActions, "actions");
155+ roles.insert(ModelInterface::RoleHints, "hints");
156+ roles.insert(ModelInterface::RoleNotification, "notification");
157+
158+ return roles;
159+}
160+
161+void MockNotificationModel::onCompleted(int id) {
162+ remove(id);
163+}
164+
165+void MockNotificationModel::onDataChanged(int id) {
166+ for(int i = 0; i < m_queue.size(); i++) {
167+ if(m_queue[i]->getID() == id) {
168+ Q_EMIT dataChanged(index(i, 0), index(i, 0));
169+ break;
170+ }
171+ }
172+}
tests/mocks/Unity/Notifications/MockNotificationModel.hnew file+72
@@ -0,0 +1,72 @@
1+/*
2+ * Copyright 2015 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authors:
17+ * Mirco Mueller <mirco.mueller@canonical.com>
18+ */
19+
20+#ifndef MOCK_NOTIFICATION_MODEL_H
21+#define MOCK_NOTIFICATION_MODEL_H
22+
23+#include <QAbstractListModel>
24+#include <QSharedPointer>
25+#include <QScopedPointer>
26+#include "MockNotification.h"
27+
28+class MockNotification;
29+
30+class MockNotificationModel : public QAbstractListModel {
31+ Q_OBJECT
32+ Q_PROPERTY(int count READ getCount)
33+
34+public:
35+ MockNotificationModel(QObject *parent=nullptr);
36+ virtual ~MockNotificationModel();
37+
38+ virtual int rowCount(const QModelIndex &parent) const;
39+ virtual QVariant data(const QModelIndex &index, int role) const;
40+ virtual QHash<int, QByteArray> roleNames() const;
41+
42+ Q_INVOKABLE void append(MockNotification* n);
43+ MockNotification* getNotification(int id) const;
44+
45+ // getRaw() is only meant to be used from QML, since QML cannot handle
46+ // QSharedPointers... on C++-side only use getNotification()
47+ Q_INVOKABLE MockNotification* getRaw(const int notificationId) const;
48+
49+ Q_INVOKABLE int queued() const;
50+ Q_INVOKABLE void remove(const int id);
51+ Q_INVOKABLE void removeSecond();
52+
53+ int getCount() const;
54+
55+Q_SIGNALS:
56+ void actionInvoked(const QString &action);
57+
58+public Q_SLOTS:
59+ void onCompleted(int id);
60+
61+private Q_SLOTS:
62+ void onDataChanged(int id);
63+
64+Q_SIGNALS:
65+ void queueSizeChanged(int newSize);
66+
67+private:
68+ QList<MockNotification*> m_queue;
69+ void removeInternal(int loc);
70+};
71+
72+#endif
tests/mocks/Unity/Notifications/plugin.cpp+5−3
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright 2014 Canonical Ltd.
2+ * Copyright 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 Lesser General Public License as published by
5 * it under the terms of the GNU Lesser General Public License as published by
@@ -19,13 +19,15 @@
19
19
20#include "plugin.h"
20#include "plugin.h"
21#include "MockActionModel.h"
21#include "MockActionModel.h"
22−#include "MockNotificationTypes.h"
22+#include "MockNotification.h"
23+#include "MockNotificationModel.h"
23
24
24#include <QtQml/qqml.h>
25#include <QtQml/qqml.h>
25
26
26void TestNotificationPlugin::registerTypes(const char* uri)
27void TestNotificationPlugin::registerTypes(const char* uri)
27{
28{
28 // @uri Unity.Notifications
29 // @uri Unity.Notifications
29− qmlRegisterUncreatableType<MockNotification>(uri, 1, 0, "Notification", "Notification objects can only be created by the plugin");
30+ qmlRegisterType<MockNotification>(uri, 1, 0, "Notification");
31+ qmlRegisterType<MockNotificationModel>(uri, 1, 0, "NotificationModel");
30 qmlRegisterType<ActionModel>(uri, 1, 0, "ActionModel");
32 qmlRegisterType<ActionModel>(uri, 1, 0, "ActionModel");
31}
33}
tests/mocks/Unity/Notifications/plugin.h+1−2
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright 2014 Canonical Ltd.
2+ * Copyright 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 Lesser General Public License as published by
5 * it under the terms of the GNU Lesser General Public License as published by
@@ -17,7 +17,6 @@
17 * Mirco Mueller <mirco.mueller@canonical.com>
17 * Mirco Mueller <mirco.mueller@canonical.com>
18 */
18 */
19
19
20−
21#ifndef TESTNOTIFICATION_PLUGIN_H
20#ifndef TESTNOTIFICATION_PLUGIN_H
22#define TESTNOTIFICATION_PLUGIN_H
21#define TESTNOTIFICATION_PLUGIN_H
23
22
tests/mocks/libusermetrics/UserMetrics.cpp+8
@@ -289,6 +289,7 @@ void UserMetricsPrivate::generateFakeData()
289 new UserMetricsData("<b>52km</b> travelled", first, firstMonth,
289 new UserMetricsData("<b>52km</b> travelled", first, firstMonth,
290 ninth, secondMonth, this));
290 ninth, secondMonth, this));
291 m_fakeData.insert("single", data);
291 m_fakeData.insert("single", data);
292+ m_fakeData.insert("has-pin", data);
292 }
293 }
293
294
294 {
295 {
@@ -304,12 +305,14 @@ void UserMetricsPrivate::generateFakeData()
304 new UserMetricsData("<b>33</b> messages today", second,
305 new UserMetricsData("<b>33</b> messages today", second,
305 firstMonth, eighth, secondMonth, this));
306 firstMonth, eighth, secondMonth, this));
306 m_fakeData.insert("single", data);
307 m_fakeData.insert("single", data);
308+ m_fakeData.insert("has-pin", data);
307 }
309 }
308
310
309 {
311 {
310 QVariantList firstMonth;
312 QVariantList firstMonth;
311 while (firstMonth.size() < 17)
313 while (firstMonth.size() < 17)
312 firstMonth.push_back(QVariant(rand()));
314 firstMonth.push_back(QVariant(rand()));
315+ firstMonth[8] = QVariant(1.0); // oversized 9th day, to test clipping
313 while (firstMonth.size() < 31)
316 while (firstMonth.size() < 31)
314 firstMonth.push_back(QVariant());
317 firstMonth.push_back(QVariant());
315 QVariantList secondMonth;
318 QVariantList secondMonth;
@@ -319,6 +322,7 @@ void UserMetricsPrivate::generateFakeData()
319 new UserMetricsData("<b>19</b> minutes talk time", eighth,
322 new UserMetricsData("<b>19</b> minutes talk time", eighth,
320 firstMonth, second, secondMonth, this));
323 firstMonth, second, secondMonth, this));
321 m_fakeData.insert("single", data);
324 m_fakeData.insert("single", data);
325+ m_fakeData.insert("has-pin", data);
322 // Also use same data for some tablet users
326 // Also use same data for some tablet users
323 m_fakeData.insert("has-password", data);
327 m_fakeData.insert("has-password", data);
324 m_fakeData.insert("no-password", data);
328 m_fakeData.insert("no-password", data);
@@ -386,6 +390,10 @@ void UserMetricsPrivate::nextFakeData()
386 if (m_dataIndex == m_fakeData.constEnd() || m_dataIndex.key() != m_username)
390 if (m_dataIndex == m_fakeData.constEnd() || m_dataIndex.key() != m_username)
387 {
391 {
388 m_dataIndex = m_fakeData.constFind(m_username);
392 m_dataIndex = m_fakeData.constFind(m_username);
393+ if (m_dataIndex == m_fakeData.constEnd())
394+ {
395+ m_dataIndex = m_fakeData.constFind("");
396+ }
389 }
397 }
390
398
391 loadFakeData();
399 loadFakeData();
tests/plugins/CMakeLists.txt+1
@@ -1,4 +1,5 @@
1add_subdirectory(AccountsService)
1add_subdirectory(AccountsService)
2+add_subdirectory(Greeter)
2add_subdirectory(LightDM)
3add_subdirectory(LightDM)
3add_subdirectory(Dash)
4add_subdirectory(Dash)
4add_subdirectory(Ubuntu)
5add_subdirectory(Ubuntu)
tests/plugins/Greeter/CMakeLists.txtnew file+1
@@ -0,0 +1 @@
1+add_subdirectory(Unity)
tests/plugins/Greeter/Unity/CMakeLists.txtnew file+1
@@ -0,0 +1 @@
1+add_subdirectory(Launcher)
tests/plugins/Greeter/Unity/Launcher/AccountsServiceDBusAdaptor.cppnew file+48
@@ -0,0 +1,48 @@
1+/*
2+ * Copyright (C) 2014-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+#include "AccountsServiceDBusAdaptor.h"
18+#include <QDebug>
19+#include <QDBusArgument>
20+#include <QDBusMessage>
21+#include <QDBusMetaType>
22+
23+AccountsServiceDBusAdaptor::AccountsServiceDBusAdaptor(QObject* parent)
24+ : QObject(parent)
25+{
26+ qDBusRegisterMetaType<QList<QVariantMap>>();
27+}
28+
29+QVariant AccountsServiceDBusAdaptor::getUserProperty(const QString &user, const QString &interface, const QString &property)
30+{
31+ Q_UNUSED(interface)
32+ Q_UNUSED(property) // We only fake one property here (LauncherItems)
33+
34+ QDBusMessage msg;
35+ QVariant v = QVariant::fromValue(mockProperties.value(user));
36+ QDBusVariant dv(v);
37+ QVariant packed = QVariant::fromValue(dv);
38+ msg << packed;
39+
40+ return packed.value<QDBusArgument>().asVariant();
41+}
42+
43+void AccountsServiceDBusAdaptor::simulatePropertyChange(const QString &user, const QString &property, const QVariant &value)
44+{
45+ Q_ASSERT(property == "LauncherItems");
46+ mockProperties[user] = value.value<QList<QVariantMap>>();
47+ Q_EMIT propertiesChanged(user, "com.canonical.unity.AccountsService", QStringList() << property);
48+}
tests/plugins/Greeter/Unity/Launcher/AccountsServiceDBusAdaptor.hnew file+53
@@ -0,0 +1,53 @@
1+/*
2+ * Copyright (C) 2014-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 UNITY_ACCOUNTSSERVICEDBUSADAPTOR_H
18+#define UNITY_ACCOUNTSSERVICEDBUSADAPTOR_H
19+
20+#include <QMap>
21+#include <QObject>
22+#include <QString>
23+#include <QHash>
24+#include <QDBusArgument>
25+
26+extern QHash<QString, QList<QVariantMap>> mockProperties;
27+
28+class AccountsServiceDBusAdaptor: public QObject
29+{
30+ Q_OBJECT
31+
32+public:
33+ explicit AccountsServiceDBusAdaptor(QObject *parent = 0);
34+
35+ Q_INVOKABLE QVariant getUserProperty(const QString &user, const QString &interface, const QString &property);
36+ template <typename T>
37+ inline T getUserProperty(const QString &user, const QString &interface, const QString &property) {
38+ Q_UNUSED(interface)
39+ Q_ASSERT(property == "LauncherItems");
40+ T ret = mockProperties[user];
41+ return ret;
42+ }
43+
44+Q_SIGNALS:
45+ void propertiesChanged(const QString &user, const QString &interface, const QStringList &changed);
46+
47+private:
48+ void simulatePropertyChange(const QString &user, const QString &property, const QVariant &value);
49+
50+ friend class LauncherModelASTest;
51+};
52+
53+#endif
tests/plugins/Greeter/Unity/Launcher/CMakeLists.txtnew file+56
@@ -0,0 +1,56 @@
1+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=6)
2+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=5)
3+
4+include_directories(
5+ ${CMAKE_CURRENT_SOURCE_DIR}
6+ ${CMAKE_CURRENT_BINARY_DIR}
7+ ${CMAKE_SOURCE_DIR}/plugins/Greeter/Unity/Launcher
8+ ${libunity8-private_SOURCE_DIR}
9+ )
10+
11+add_definitions(-DSM_BUSNAME=sessionBus)
12+add_definitions(-DSRCDIR="${CMAKE_CURRENT_SOURCE_DIR}")
13+add_definitions(-DLAUNCHER_TESTING)
14+
15+### LauncherModelASTest
16+set(testModelCommand dbus-test-runner --task ${CMAKE_CURRENT_BINARY_DIR}/launchermodelastestExec
17+ --parameter -o --parameter ${CMAKE_BINARY_DIR}/launchermodeltest.xml,xunitxml
18+ --parameter -o --parameter -,txt)
19+add_test(NAME launchermodelastest COMMAND ${testModelCommand})
20+add_custom_target(launchermodelastest ${testModelCommand})
21+add_executable(launchermodelastestExec
22+ launchermodelastest.cpp
23+ AccountsServiceDBusAdaptor.cpp
24+ ${CMAKE_SOURCE_DIR}/plugins/Greeter/Unity/Launcher/launchermodelas.cpp
25+ ${CMAKE_SOURCE_DIR}/plugins/Greeter/Unity/Launcher/launcheritem.cpp
26+ ${CMAKE_SOURCE_DIR}/plugins/Greeter/Unity/Launcher/quicklistmodel.cpp
27+ ${CMAKE_SOURCE_DIR}/plugins/Greeter/Unity/Launcher/quicklistentry.cpp
28+ ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherItemInterface.h
29+ ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherModelInterface.h
30+ ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/QuickListModelInterface.h
31+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
32+ ${LAUNCHER_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
33+ )
34+target_link_libraries(launchermodelastestExec
35+ unity8-private
36+ ${GSETTINGS_QT_LDFLAGS}
37+ )
38+qt5_use_modules(launchermodelastestExec Test Core DBus Gui)
39+
40+# copy .desktop files into build directory for shadow builds
41+file(GLOB DESKTOP_FILES *.desktop)
42+
43+foreach(DESKTOP_FILE ${DESKTOP_FILES})
44+ file(COPY "${DESKTOP_FILE}"
45+ DESTINATION ${CMAKE_CURRENT_BINARY_DIR}
46+ )
47+endforeach()
48+
49+# copy .svg files into build directory for shadow builds
50+file(GLOB DESKTOP_FILES *.svg)
51+
52+foreach(DESKTOP_FILE ${DESKTOP_FILES})
53+ file(COPY "${DESKTOP_FILE}"
54+ DESTINATION ${CMAKE_CURRENT_BINARY_DIR}
55+ )
56+endforeach()
tests/plugins/Greeter/Unity/Launcher/launchermodelastest.cppnew file+169
@@ -0,0 +1,169 @@
1+/*
2+ * Copyright 2014-2015 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+// unity-api
18+#include <unity/shell/launcher/LauncherModelInterface.h>
19+#include <unity/shell/application/ApplicationInfoInterface.h>
20+
21+#include "launcheritem.h"
22+#include "launchermodelas.h"
23+#include "AccountsServiceDBusAdaptor.h"
24+
25+#include <QtTest>
26+
27+QHash<QString, QList<QVariantMap>> mockProperties;
28+
29+class LauncherModelASTest : public QObject
30+{
31+ Q_OBJECT
32+
33+private:
34+ LauncherModel *launcherModel;
35+
36+private Q_SLOTS:
37+
38+ void init() {
39+ // Prepare some fake list
40+ QList<QVariantMap> list;
41+ QVariantMap item;
42+ item.insert("id", "appId1");
43+ item.insert("name", "Item 1");
44+ item.insert("icon", "fake.svg");
45+ item.insert("count", 0);
46+ item.insert("countVisible", false);
47+ item.insert("pinned", true);
48+ list.append(item);
49+ item["id"] = "appId2";
50+ item["name"] = "Item 2";
51+ list.append(item);
52+ mockProperties["user1"] = list;
53+ }
54+
55+ bool isInSync(LauncherModel *model, const QList<QVariantMap> &properties) {
56+ QList<QVariantMap> list;
57+ // Strip unpinned if onlyPinned is set
58+ Q_FOREACH (const QVariantMap &map, properties) {
59+ if (!model->onlyPinned() || map.value("pinned").toBool()) {
60+ list << map;
61+ }
62+ }
63+
64+ bool inSync = model->rowCount() == list.count();
65+ for (int i = 0; inSync && i < model->rowCount(); i++) {
66+ inSync &= model->get(i)->appId() == list.at(i).value("id").toString();
67+ inSync &= model->get(i)->name() == list.at(i).value("name").toString();
68+ inSync &= model->get(i)->icon() == list.at(i).value("icon").toString();
69+ inSync &= model->get(i)->count() == list.at(i).value("count").toInt();
70+ inSync &= model->get(i)->countVisible() == list.at(i).value("countVisible").toBool();
71+ }
72+ return inSync;
73+ }
74+
75+ void testLoadASOnStartup() {
76+ // Load up the model
77+ LauncherModel* model = new LauncherModel(this);
78+
79+ // We didn't set a user yet. model should be empty
80+ QCOMPARE(model->rowCount(), 0);
81+
82+ model->setUser("user1");
83+
84+ QCOMPARE(isInSync(model, mockProperties["user1"]), true);
85+ model->deleteLater();
86+ }
87+
88+ void testASChangedUpdatesModel_data() {
89+ QTest::addColumn<QString>("modelUser");
90+ QTest::addColumn<QString>("changedUser");
91+ QTest::addColumn<bool>("inSync");
92+
93+ QTest::newRow("this user changed") << "user1" << "user1" << true;
94+ QTest::newRow("other user changed") << "user1" << "user2" << false;
95+ }
96+
97+ void testASChangedUpdatesModel() {
98+ QFETCH(QString, modelUser);
99+ QFETCH(QString, changedUser);
100+ QFETCH(bool, inSync);
101+
102+ LauncherModel* model = new LauncherModel(this);
103+ model->setUser(modelUser);
104+
105+ int oldCount = mockProperties[modelUser].count();
106+ QCOMPARE(model->rowCount(), oldCount);
107+
108+ QList<QVariantMap> newList;
109+ QVariantMap newEntry;
110+ newEntry.insert("id", "newappId");
111+ newEntry.insert("name", "New app");
112+ newEntry.insert("icon", "some-icon.svg");
113+ newEntry.insert("count", 0);
114+ newEntry.insert("countVisible", false);
115+ newEntry.insert("pinned", true);
116+ newList.append(newEntry);
117+ model->m_accounts->simulatePropertyChange(changedUser, "LauncherItems", QVariant::fromValue(newList));
118+
119+ QCOMPARE(isInSync(model, mockProperties[modelUser]), true);
120+ QCOMPARE(isInSync(model, mockProperties[changedUser]), inSync);
121+
122+ model->deleteLater();
123+ }
124+
125+ void testUpdateCount() {
126+ LauncherModel* model = new LauncherModel(this);
127+ model->setUser("user1");
128+
129+ QCOMPARE(model->get(0)->countVisible(), false);
130+ QCOMPARE(model->get(0)->count(), 0);
131+
132+ QList<QVariantMap> newList = mockProperties["user1"];
133+ QVariantMap entry = newList.at(0);
134+ entry["countVisible"] = true;
135+ entry["count"] = 55;
136+ newList[0] = entry;
137+ model->m_accounts->simulatePropertyChange("user1", "LauncherItems", QVariant::fromValue(newList));
138+
139+ QCOMPARE(isInSync(model, mockProperties["user1"]), true);
140+
141+ model->deleteLater();
142+ }
143+
144+ void testOnlyPinned() {
145+ LauncherModel *model = new LauncherModel(this);
146+ model->setUser("user1");
147+ model->setOnlyPinned(true);
148+
149+ QCOMPARE(isInSync(model, mockProperties["user1"]), true);
150+
151+ // Let's unpin one item
152+ QList<QVariantMap> newList = mockProperties["user1"];
153+ QVariantMap entry = newList.at(0);
154+ entry["pinned"] = false;
155+ newList[0] = entry;
156+ model->m_accounts->simulatePropertyChange("user1", "LauncherItems", QVariant::fromValue(newList));
157+ QCOMPARE(isInSync(model, mockProperties["user1"]), true);
158+
159+ // Now toggle onlyPinned and make sure the model keeps up
160+ model->setOnlyPinned(false);
161+ QCOMPARE(isInSync(model, mockProperties["user1"]), true);
162+
163+ model->setOnlyPinned(true);
164+ QCOMPARE(isInSync(model, mockProperties["user1"]), true);
165+ }
166+};
167+
168+QTEST_GUILESS_MAIN(LauncherModelASTest)
169+#include "launchermodelastest.moc"
tests/plugins/Unity/Indicators/indicatorsmanagertest.cpp+30−5
@@ -50,7 +50,8 @@ private Q_SLOTS:
50 QVERIFY(!manager.isLoaded());
50 QVERIFY(!manager.isLoaded());
51 QCOMPARE(manager.indicators().count(), 0);
51 QCOMPARE(manager.indicators().count(), 0);
52
52
53− manager.load("test1");
53+ manager.setProfile("test1");
54+ manager.load();
54
55
55 QVERIFY(manager.isLoaded());
56 QVERIFY(manager.isLoaded());
56 QCOMPARE(manager.indicators().count(), 4);
57 QCOMPARE(manager.indicators().count(), 4);
@@ -67,7 +68,8 @@ private Q_SLOTS:
67 void testPluginInterfaceProfile1()
68 void testPluginInterfaceProfile1()
68 {
69 {
69 IndicatorsManager manager;
70 IndicatorsManager manager;
70− manager.load("test1");
71+ manager.setProfile("test1");
72+ manager.load();
71
73
72 Indicator::Ptr indicator = manager.indicator("indicator-fake1");
74 Indicator::Ptr indicator = manager.indicator("indicator-fake1");
73 QVERIFY(indicator ? true : false);
75 QVERIFY(indicator ? true : false);
@@ -90,7 +92,8 @@ private Q_SLOTS:
90 void testPluginInterfaceProfile2()
92 void testPluginInterfaceProfile2()
91 {
93 {
92 IndicatorsManager manager;
94 IndicatorsManager manager;
93− manager.load("test2");
95+ manager.setProfile("test2");
96+ manager.load();
94
97
95 Indicator::Ptr indicator = manager.indicator("indicator-fake1");
98 Indicator::Ptr indicator = manager.indicator("indicator-fake1");
96 QVERIFY(indicator ? true : false);
99 QVERIFY(indicator ? true : false);
@@ -107,13 +110,34 @@ private Q_SLOTS:
107 QCOMPARE(props["menuObjectPath"].toString(), QString("/com/canonical/indicator/fake1/test2"));
110 QCOMPARE(props["menuObjectPath"].toString(), QString("/com/canonical/indicator/fake1/test2"));
108 }
111 }
109
112
113+ /*
114+ * Test switching the indicator profile data
115+ */
116+ void testPluginInterfaceProfileSwitch()
117+ {
118+ IndicatorsManager manager;
119+ manager.setProfile("test1");
120+ manager.load();
121+
122+ Indicator::Ptr indicator = manager.indicator("indicator-fake1");
123+ QVERIFY(indicator ? true : false);
124+
125+ QVariantMap props = indicator->indicatorProperties().toMap();
126+ QCOMPARE(props["menuObjectPath"].toString(), QString("/com/canonical/indicator/fake1/test1"));
127+
128+ manager.setProfile("test2");
129+ props = indicator->indicatorProperties().toMap();
130+ QCOMPARE(props["menuObjectPath"].toString(), QString("/com/canonical/indicator/fake1/test2"));
131+ }
132+
110 /*
133 /*
111 * Test if a new plugin object is create for each different plugin
134 * Test if a new plugin object is create for each different plugin
112 */
135 */
113 void testPluginInstance()
136 void testPluginInstance()
114 {
137 {
115 IndicatorsManager manager;
138 IndicatorsManager manager;
116− manager.load("test2");
139+ manager.setProfile("test2");
140+ manager.load();
117
141
118 Indicator::Ptr i0 = manager.indicator("indicator-fake1");
142 Indicator::Ptr i0 = manager.indicator("indicator-fake1");
119 Indicator::Ptr i1 = manager.indicator("indicator-fake1");
143 Indicator::Ptr i1 = manager.indicator("indicator-fake1");
@@ -133,7 +157,8 @@ private Q_SLOTS:
133 void testPluginInitAndShutdown()
157 void testPluginInitAndShutdown()
134 {
158 {
135 IndicatorsManager manager;
159 IndicatorsManager manager;
136− manager.load("test1");
160+ manager.setProfile("test1");
161+ manager.load();
137
162
138 QWeakPointer<Indicator> wp0;
163 QWeakPointer<Indicator> wp0;
139 QWeakPointer<Indicator> wp1;
164 QWeakPointer<Indicator> wp1;
tests/plugins/Unity/Indicators/indicatorsmodeltest.cpp+6−3
@@ -47,10 +47,11 @@ private Q_SLOTS:
47 void testLoad()
47 void testLoad()
48 {
48 {
49 IndicatorsModel model;
49 IndicatorsModel model;
50+ model.setProfile("test1");
50
51
51 QCOMPARE(model.property("count").toInt(), 0);
52 QCOMPARE(model.property("count").toInt(), 0);
52
53
53− model.load("test1");
54+ model.load();
54
55
55 QCOMPARE(model.property("count").toInt(), 4);
56 QCOMPARE(model.property("count").toInt(), 4);
56
57
@@ -67,7 +68,8 @@ private Q_SLOTS:
67 {
68 {
68 // Priority order. (2, 1, 4, 3)
69 // Priority order. (2, 1, 4, 3)
69 IndicatorsModel model;
70 IndicatorsModel model;
70− model.load("test1");
71+ model.setProfile("test1");
72+ model.load();
71
73
72 // should be in order:
74 // should be in order:
73 // fake3, fake4, fake1, fake2
75 // fake3, fake4, fake1, fake2
@@ -104,7 +106,8 @@ private Q_SLOTS:
104 {
106 {
105 // Priority order. (2, 1, 4, 3)
107 // Priority order. (2, 1, 4, 3)
106 IndicatorsModel model;
108 IndicatorsModel model;
107− model.load("test2");
109+ model.setProfile("test2");
110+ model.load();
108
111
109 // should be in order:
112 // should be in order:
110 // fake3, fake4, fake1, fake2
113 // fake3, fake4, fake1, fake2
tests/plugins/Unity/Indicators/sharedunitymenumodeltest.cpp+2−2
@@ -61,7 +61,7 @@ private Q_SLOTS:
61 QCOMPARE(model1->model(), model2->model());
61 QCOMPARE(model1->model(), model2->model());
62 }
62 }
63
63
64− void testSharedOwnership()
64+ void testSavedData()
65 {
65 {
66 QSharedPointer<SharedUnityMenuModel> model1(createFullModel("test1"));
66 QSharedPointer<SharedUnityMenuModel> model1(createFullModel("test1"));
67 QSharedPointer<SharedUnityMenuModel> model2(createFullModel("test1"));
67 QSharedPointer<SharedUnityMenuModel> model2(createFullModel("test1"));
@@ -70,7 +70,7 @@ private Q_SLOTS:
70 model1.clear();
70 model1.clear();
71 QCOMPARE(UnityMenuModelCache::singleton()->contains("/com/canonical/test1"), true);
71 QCOMPARE(UnityMenuModelCache::singleton()->contains("/com/canonical/test1"), true);
72 model2.clear();
72 model2.clear();
73− QCOMPARE(UnityMenuModelCache::singleton()->contains("/com/canonical/test1"), false);
73+ QCOMPARE(UnityMenuModelCache::singleton()->contains("/com/canonical/test1"), true);
74 }
74 }
75
75
76 // Tests that changing cached model data does not change the model path of others
76 // Tests that changing cached model data does not change the model path of others
tests/plugins/Unity/Launcher/AccountsServiceDBusAdaptor.cppnew file+42
@@ -0,0 +1,42 @@
1+/*
2+ * Copyright (C) 2014-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+#include "AccountsServiceDBusAdaptor.h"
18+#include <QDebug>
19+
20+AccountsServiceDBusAdaptor::AccountsServiceDBusAdaptor(QObject* parent)
21+ : QObject(parent)
22+{
23+}
24+
25+QVariant AccountsServiceDBusAdaptor::getUserProperty(const QString &user, const QString &interface, const QString &property)
26+{
27+ Q_UNUSED(user)
28+ Q_UNUSED(interface)
29+ return m_properties.value(property);
30+}
31+
32+void AccountsServiceDBusAdaptor::setUserProperty(const QString &user, const QString &interface, const QString &property, const QVariant &value)
33+{
34+ Q_UNUSED(user)
35+ Q_UNUSED(interface)
36+ m_properties[property] = value;
37+}
38+
39+void AccountsServiceDBusAdaptor::setUserPropertyAsync(const QString &user, const QString &interface, const QString &property, const QVariant &value)
40+{
41+ setUserProperty(user, interface, property, value);
42+}
tests/plugins/Unity/Launcher/AccountsServiceDBusAdaptor.hnew file+47
@@ -0,0 +1,47 @@
1+/*
2+ * Copyright (C) 2014-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 UNITY_ACCOUNTSSERVICEDBUSADAPTOR_H
18+#define UNITY_ACCOUNTSSERVICEDBUSADAPTOR_H
19+
20+#include <QMap>
21+#include <QObject>
22+#include <QString>
23+#include <QHash>
24+
25+class AccountsServiceDBusAdaptor: public QObject
26+{
27+ Q_OBJECT
28+
29+public:
30+ explicit AccountsServiceDBusAdaptor(QObject *parent = 0);
31+
32+ Q_INVOKABLE QVariant getUserProperty(const QString &user, const QString &interface, const QString &property);
33+ Q_INVOKABLE void setUserProperty(const QString &user, const QString &interface, const QString &property, const QVariant &value);
34+ Q_INVOKABLE void setUserPropertyAsync(const QString &user, const QString &interface, const QString &property, const QVariant &value);
35+
36+Q_SIGNALS:
37+ void propertiesChanged(const QString &user, const QString &interface, const QStringList &changed);
38+ void maybeChanged(const QString &user); // Standard properties might have changed
39+
40+private:
41+ void simulatePropertyChange(const QString &property, const QVariant &value);
42+
43+private:
44+ QHash<QString, QVariant> m_properties;
45+};
46+
47+#endif
tests/plugins/Unity/Launcher/CMakeLists.txt+3−1
@@ -1,5 +1,5 @@
1pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
1pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
2−pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=5)
2+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=6)
3pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=5)
3pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=5)
4
4
5include_directories(
5include_directories(
@@ -23,7 +23,9 @@ add_custom_target(launchermodeltest ${testModelCommand})
23add_executable(launchermodeltestExec
23add_executable(launchermodeltestExec
24 launchermodeltest.cpp
24 launchermodeltest.cpp
25 gsettings.cpp
25 gsettings.cpp
26+ AccountsServiceDBusAdaptor.cpp
26 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/launchermodel.cpp
27 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/launchermodel.cpp
28+ ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/asadapter.cpp
27 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/launcheritem.cpp
29 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/launcheritem.cpp
28 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/quicklistmodel.cpp
30 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/quicklistmodel.cpp
29 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/dbusinterface.cpp
31 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/dbusinterface.cpp
tests/plugins/Unity/Launcher/launchermodeltest.cpp+65−1
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright 2013 Canonical Ltd.
2+ * Copyright 2013-2014 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 Lesser General Public License as published by
5 * it under the terms of the GNU Lesser General Public License as published by
@@ -25,6 +25,8 @@
25#include "launchermodel.h"
25#include "launchermodel.h"
26#include "dbusinterface.h"
26#include "dbusinterface.h"
27#include "gsettings.h"
27#include "gsettings.h"
28+#include "asadapter.h"
29+#include "AccountsServiceDBusAdaptor.h"
28
30
29#include <QtTest>
31#include <QtTest>
30#include <QDBusInterface>
32#include <QDBusInterface>
@@ -129,6 +131,11 @@ private:
129 LauncherModel *launcherModel;
131 LauncherModel *launcherModel;
130 MockAppManager *appManager;
132 MockAppManager *appManager;
131
133
134+ QList<QVariantMap> getASConfig() {
135+ AccountsServiceDBusAdaptor *as = launcherModel->m_asAdapter->m_accounts;
136+ return as->getUserProperty("", "", "LauncherItems").value<QList<QVariantMap>>();
137+ }
138+
132private Q_SLOTS:
139private Q_SLOTS:
133
140
134 void initTestCase() {
141 void initTestCase() {
@@ -467,6 +474,63 @@ private Q_SLOTS:
467 QCOMPARE(launcherModel->get(1)->appId(), QString("abs-icon"));
474 QCOMPARE(launcherModel->get(1)->appId(), QString("abs-icon"));
468 QCOMPARE(spy.count(), 2);
475 QCOMPARE(spy.count(), 2);
469 }
476 }
477+
478+ void testAddSyncsToAS() {
479+ // Make sure launcher and AS are in sync when we start the test
480+ QCOMPARE(launcherModel->rowCount(), getASConfig().count());
481+
482+ int oldCount = launcherModel->rowCount();
483+ appManager->addApplication(new MockApp("rel-icon"));
484+ QCOMPARE(launcherModel->rowCount(), oldCount + 1);
485+ QCOMPARE(launcherModel->rowCount(), getASConfig().count());
486+ }
487+
488+ void testRemoveSyncsToAS() {
489+ // Make sure launcher and AS are in sync when we start the test
490+ QCOMPARE(launcherModel->rowCount(), getASConfig().count());
491+
492+ int oldCount = launcherModel->rowCount();
493+ appManager->stopApplication("abs-icon");
494+ QCOMPARE(launcherModel->rowCount(), oldCount - 1);
495+ QCOMPARE(launcherModel->rowCount(), getASConfig().count());
496+ }
497+
498+ void testMoveSyncsToAS() {
499+ // Make sure launcher and AS are in sync when we start the test
500+ QCOMPARE(launcherModel->rowCount(), getASConfig().count());
501+
502+ for (int i = 0; i < launcherModel->rowCount(); i++) {
503+ QString launcherAppId = launcherModel->get(i)->appId();
504+ QString asAppId = getASConfig().at(i).value("id").toString();
505+ QCOMPARE(launcherAppId, asAppId);
506+ }
507+
508+ launcherModel->move(0, 1);
509+
510+ for (int i = 0; i < launcherModel->rowCount(); i++) {
511+ QString launcherAppId = launcherModel->get(i)->appId();
512+ QString asAppId = getASConfig().at(i).value("id").toString();
513+ QCOMPARE(launcherAppId, asAppId);
514+ }
515+ }
516+
517+ void testCountChangeSyncsToAS() {
518+ // Find the index of the abs-icon app
519+ int index = launcherModel->findApplication("abs-icon");
520+
521+ // Make sure it's invisible and 0 at the beginning
522+ QCOMPARE(getASConfig().at(index).value("countVisible").toBool(), false);
523+ QCOMPARE(getASConfig().at(index).value("count").toInt(), 0);
524+
525+ // Change the count of the abs-icon app through D-Bus
526+ QDBusInterface interface("com.canonical.Unity.Launcher", "/com/canonical/Unity/Launcher/abs_2Dicon", "org.freedesktop.DBus.Properties");
527+ interface.call("Set", "com.canonical.Unity.Launcher.Item", "count", QVariant::fromValue(QDBusVariant(55)));
528+ interface.call("Set", "com.canonical.Unity.Launcher.Item", "countVisible", QVariant::fromValue(QDBusVariant(true)));
529+
530+ // Make sure it changed to visible and 55
531+ QCOMPARE(getASConfig().at(index).value("countVisible").toBool(), true);
532+ QCOMPARE(getASConfig().at(index).value("count").toInt(), 55);
533+ }
470};
534};
471
535
472QTEST_GUILESS_MAIN(LauncherModelTest)
536QTEST_GUILESS_MAIN(LauncherModelTest)
tests/qmltests/CMakeLists.txt+2−1
@@ -25,7 +25,6 @@ add_qml_test(Components Background)
25add_qml_test(Components Carousel)
25add_qml_test(Components Carousel)
26add_qml_test(Components Dialogs)
26add_qml_test(Components Dialogs)
27add_qml_test(Components DraggingArea)
27add_qml_test(Components DraggingArea)
28−add_qml_test(Components EdgeDemoOverlay)
29add_qml_test(Components LazyImage)
28add_qml_test(Components LazyImage)
30add_qml_test(Components Lockscreen)
29add_qml_test(Components Lockscreen)
31add_qml_test(Components Rating)
30add_qml_test(Components Rating)
@@ -88,6 +87,8 @@ add_qml_test(Stages PhoneStage ENVIRONMENT)
88add_qml_test(Stages SpreadDelegate ENVIRONMENT)
87add_qml_test(Stages SpreadDelegate ENVIRONMENT)
89add_qml_test(Stages SurfaceContainer ENVIRONMENT)
88add_qml_test(Stages SurfaceContainer ENVIRONMENT)
90add_qml_test(Stages SessionContainer ENVIRONMENT)
89add_qml_test(Stages SessionContainer ENVIRONMENT)
90+add_qml_test(Stages TabletStage)
91add_qml_test(Stages WindowMoveResizeArea)
91add_qml_test(Stages WindowMoveResizeArea)
92add_qml_test(Stages Splash)
92add_qml_test(Stages Splash)
93+add_qml_test(Tutorial Tutorial)
93add_qml_test(Wizard Wizard)
94add_qml_test(Wizard Wizard)
tests/qmltests/Components/tst_EdgeDemoOverlay.qmldeleted−123
@@ -1,123 +0,0 @@
1−/*
2− * Copyright 2013 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 Unity.Test 0.1 as UT
20−import "../../../qml/Components"
21−
22−Item {
23− id: root
24− width: boxWidth * 3
25− height: boxHeight * 2
26−
27− property int boxWidth: 250
28− property int boxHeight: 250
29−
30− EdgeDemoOverlay {
31− id: top
32− edge: "top"
33− title: "Top"
34− text: "Displayed on top left"
35− anchors.left: parent.left
36− anchors.top: parent.top
37− width: boxWidth
38− height: boxHeight
39− }
40−
41− EdgeDemoOverlay {
42− id: right
43− edge: "right"
44− title: "Right"
45− text: "Displayed on top right"
46− anchors.right: parent.right
47− anchors.top: parent.top
48− width: boxWidth
49− height: boxHeight
50− }
51−
52− EdgeDemoOverlay {
53− id: left
54− edge: "left"
55− title: "Left"
56− text: "Displayed on bottom right"
57− anchors.right: parent.right
58− anchors.bottom: parent.bottom
59− width: boxWidth
60− height: boxHeight
61− available: false
62− }
63−
64− EdgeDemoOverlay {
65− id: bottom
66− edge: "bottom"
67− title: "Bottom"
68− text: "Displayed on bottom left"
69− anchors.left: parent.left
70− anchors.bottom: parent.bottom
71− width: boxWidth
72− height: boxHeight
73− }
74−
75− EdgeDemoOverlay {
76− id: none
77− edge: "none"
78− title: "None"
79− text: "Displayed on top middle"
80− anchors.horizontalCenter: parent.horizontalCenter
81− anchors.top: parent.top
82− width: boxWidth
83− height: boxHeight
84− }
85−
86− SignalSpy {
87− id: signalSpy
88− }
89−
90− UT.UnityTestCase {
91− name: "EdgeDemoOverlay"
92− when: windowShown
93−
94− function test_animations() {
95− compare(right.running, true)
96−
97− compare(left.running, false)
98− left.available = true
99− compare(left.running, true)
100− }
101−
102− function test_skip() {
103− signalSpy.target = bottom
104− signalSpy.signalName = "skip"
105− signalSpy.clear()
106− var bottomSkip = findChild(bottom, "skipLabel")
107− mousePress(bottomSkip, 1, 1)
108− mouseRelease(bottomSkip, 1, 1)
109− signalSpy.wait()
110− compare(bottom.available, false)
111−
112− // Test that the 'none' edge skips anywhere
113− signalSpy.target = none
114− signalSpy.clear()
115− var backgroundShade = findChild(none, "backgroundShadeMouseArea")
116− tryCompare(backgroundShade, "enabled", true)
117− mousePress(backgroundShade, 1, 1)
118− mouseRelease(backgroundShade, 1, 1)
119− signalSpy.wait()
120− compare(none.available, false)
121− }
122− }
123−}
tests/qmltests/Launcher/tst_Launcher.qml+46−19
@@ -15,6 +15,7 @@
15 */
15 */
16
16
17import QtQuick 2.0
17import QtQuick 2.0
18+import QtQuick.Layouts 1.1
18import QtTest 1.0
19import QtTest 1.0
19import Unity.Test 0.1 as UT
20import Unity.Test 0.1 as UT
20import Ubuntu.Components 1.1
21import Ubuntu.Components 1.1
@@ -74,10 +75,22 @@ Item {
74 }
75 }
75 }
76 }
76
77
77− Button {
78+ ColumnLayout {
78 anchors { bottom: parent.bottom; right: parent.right; margins: units.gu(1) }
79 anchors { bottom: parent.bottom; right: parent.right; margins: units.gu(1) }
80+ spacing: units.gu(1)
81+ width: units.gu(20)
82+
83+ Button {
79− text: "emit hinting signal"
84+ text: "emit hinting signal"
80− onClicked: LauncherModel.emitHint()
85+ onClicked: LauncherModel.emitHint()
86+ Layout.fillWidth: true
87+ }
88+
89+ Button {
90+ text: "rotate"
91+ onClicked: launcherLoader.item.inverted = !launcherLoader.item.inverted
92+ Layout.fillWidth: true
93+ }
81 }
94 }
82
95
83 SignalSpy {
96 SignalSpy {
@@ -140,6 +153,18 @@ Item {
140 tryCompare(panel, "x", -panel.width, 1000);
153 tryCompare(panel, "x", -panel.width, 1000);
141 }
154 }
142
155
156+ function positionLauncherListAtBeginning() {
157+ var listView = testCase.findChild(launcherLoader.item, "launcherListView");
158+ listView.contentY = -listView.topMargin;
159+ }
160+ function positionLauncherListAtEnd() {
161+ var listView = testCase.findChild(launcherLoader.item, "launcherListView");
162+ if ((listView.contentHeight + listView.topMargin + listView.bottomMargin) > listView.height) {
163+ listView.contentY = listView.topMargin + listView.contentHeight
164+ - listView.height;
165+ }
166+ }
167+
143 // Drag from the left edge of the screen rightwards and check that the launcher
168 // Drag from the left edge of the screen rightwards and check that the launcher
144 // appears (as if being dragged by the finger/pointer)
169 // appears (as if being dragged by the finger/pointer)
145 function test_dragLeftEdgeToRevealLauncherAndTapCenterToDismiss() {
170 function test_dragLeftEdgeToRevealLauncherAndTapCenterToDismiss() {
@@ -166,22 +191,22 @@ Item {
166 launcherApplicationSelected("[...]dialer-app.desktop") */
191 launcherApplicationSelected("[...]dialer-app.desktop") */
167 function test_clickingOnAppIconCausesSignalEmission() {
192 function test_clickingOnAppIconCausesSignalEmission() {
168 dragLauncherIntoView();
193 dragLauncherIntoView();
169− launcher.lastSelectedApplication = ""
194+ launcher.lastSelectedApplication = "";
195+ launcher.inverted = false;
170
196
171− var listView = findChild(launcher, "launcherListView");
197+ positionLauncherListAtBeginning();
172− listView.positionViewAtEnd();
173
198
174− var appIcon = findChild(launcher, "launcherDelegate0")
199+ var appIcon = findChild(launcher, "launcherDelegate0");
175
200
176− verify(appIcon != undefined)
201+ verify(appIcon != undefined);
177
202
178− mouseClick(appIcon)
203+ mouseClick(appIcon);
179
204
180 tryCompare(launcher, "lastSelectedApplication",
205 tryCompare(launcher, "lastSelectedApplication",
181− "dialer-app")
206+ appIcon.appId);
182
207
183 // Tapping on an application icon also dismisses the launcher
208 // Tapping on an application icon also dismisses the launcher
184− waitUntilLauncherDisappears()
209+ waitUntilLauncherDisappears();
185 }
210 }
186
211
187 /* If I click on the dash icon on the launcher
212 /* If I click on the dash icon on the launcher
@@ -277,25 +302,26 @@ Item {
277 function test_clickFlick_data() {
302 function test_clickFlick_data() {
278 var listView = findChild(launcher, "launcherListView");
303 var listView = findChild(launcher, "launcherListView");
279 return [
304 return [
280− {tag: "unfolded top", positionViewAtBeginning: false,
305+ {tag: "unfolded top", positionViewAtBeginning: true,
281 clickY: listView.topMargin + units.gu(2),
306 clickY: listView.topMargin + units.gu(2),
282 expectFlick: false},
307 expectFlick: false},
283
308
284− {tag: "folded top", positionViewAtBeginning: true,
309+ {tag: "folded top", positionViewAtBeginning: false,
285 clickY: listView.topMargin + units.gu(2),
310 clickY: listView.topMargin + units.gu(2),
286 expectFlick: true},
311 expectFlick: true},
287
312
288− {tag: "unfolded bottom", positionViewAtBeginning: true,
313+ {tag: "unfolded bottom", positionViewAtBeginning: false,
289 clickY: listView.height - listView.topMargin - units.gu(1),
314 clickY: listView.height - listView.topMargin - units.gu(1),
290 expectFlick: false},
315 expectFlick: false},
291
316
292− {tag: "folded bottom", positionViewAtBeginning: false,
317+ {tag: "folded bottom", positionViewAtBeginning: true,
293 clickY: listView.height - listView.topMargin - units.gu(1),
318 clickY: listView.height - listView.topMargin - units.gu(1),
294 expectFlick: true},
319 expectFlick: true},
295 ];
320 ];
296 }
321 }
297
322
298 function test_clickFlick(data) {
323 function test_clickFlick(data) {
324+ launcher.inverted = false;
299 launcher.lastSelectedApplication = "";
325 launcher.lastSelectedApplication = "";
300 dragLauncherIntoView();
326 dragLauncherIntoView();
301 var listView = findChild(launcher, "launcherListView");
327 var listView = findChild(launcher, "launcherListView");
@@ -305,9 +331,9 @@ Item {
305 // So for stability's sake we just put the listView in the position
331 // So for stability's sake we just put the listView in the position
306 // we want to to actually start doing what this tests intends to check.
332 // we want to to actually start doing what this tests intends to check.
307 if (data.positionViewAtBeginning) {
333 if (data.positionViewAtBeginning) {
308− listView.positionViewAtBeginning();
334+ positionLauncherListAtBeginning();
309 } else {
335 } else {
310− listView.positionViewAtEnd();
336+ positionLauncherListAtEnd();
311 }
337 }
312 tryCompare(listView, "flicking", false);
338 tryCompare(listView, "flicking", false);
313
339
@@ -442,9 +468,9 @@ Item {
442 // Position launcher to where we need it
468 // Position launcher to where we need it
443 var listView = findChild(launcher, "launcherListView");
469 var listView = findChild(launcher, "launcherListView");
444 if (data.flickTo == "top") {
470 if (data.flickTo == "top") {
445− listView.positionViewAtEnd();
471+ positionLauncherListAtBeginning();
446 } else {
472 } else {
447− listView.positionViewAtBeginning();
473+ positionLauncherListAtEnd();
448 }
474 }
449
475
450 // Doing longpress
476 // Doing longpress
@@ -454,6 +480,7 @@ Item {
454
480
455 verify(quickList.y >= units.gu(1));
481 verify(quickList.y >= units.gu(1));
456 verify(quickList.y + quickList.height + units.gu(1) <= launcher.height);
482 verify(quickList.y + quickList.height + units.gu(1) <= launcher.height);
483+ compare(quickList.width, units.gu(30));
457
484
458 // Click somewhere in the empty space to dismiss the quicklist
485 // Click somewhere in the empty space to dismiss the quicklist
459 mouseClick(launcher, launcher.width - units.gu(1), units.gu(1));
486 mouseClick(launcher, launcher.width - units.gu(1), units.gu(1));
tests/mocks/Unity/Notifications/MockNotificationTypes.cpp → tests/qmltests/Notifications/Notification.qmlrenamed+11−6
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright 2014 Canonical Ltd.
2+ * Copyright 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 Lesser General Public License as published by
5 * it under the terms of the GNU Lesser General Public License as published by
@@ -17,10 +17,15 @@
17 * Mirco Mueller <mirco.mueller@canonical.com>
17 * Mirco Mueller <mirco.mueller@canonical.com>
18 */
18 */
19
19
20−#include "MockNotificationTypes.h"
20+import Unity.Notifications 1.0
21
21
22−MockNotification::MockNotification(QObject *parent) : QObject(parent) {
22+Notification {
23−}
23+ nid: 0
24−
24+ type: Notification.PlaceHolder
25−MockNotification::~MockNotification() {
25+ summary: ""
26+ body: ""
27+ icon: ""
28+ secondaryIcon: ""
29+ value: 0
30+ rawActions: []
26}
31}
tests/qmltests/Notifications/tst_Notifications.qml+271−213
@@ -1,17 +1,20 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright 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 Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
6 * the Free Software Foundation; version 3.
7 *
7 *
8 * This program is distributed in the hope that it will be useful,
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
11+ * GNU Lesser General Public License for more details.
12 *
12 *
13− * You should have received a copy of the GNU General Public License
13+ * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authors:
17+ * Mirco Mueller <mirco.mueller@canonical.com>
15 */
18 */
16
19
17import QtQuick 2.0
20import QtQuick 2.0
@@ -24,146 +27,134 @@ import Unity.Notifications 1.0
24import QtMultimedia 5.0
27import QtMultimedia 5.0
25
28
26Item {
29Item {
30+ id: foobar
31+
27 width: notificationsRect.width + interactiveControls.width
32 width: notificationsRect.width + interactiveControls.width
28 height: notificationsRect.height
33 height: notificationsRect.height
34+ property int index: 0
29
35
30 Row {
36 Row {
31 id: rootRow
37 id: rootRow
32
38
33− Component {
39+ NotificationModel {
34− id: mockNotification
35−
36− QtObject {
37− function invokeAction(actionId) {
38− mockModel.actionInvoked(actionId)
39− }
40− }
41− }
42−
43− ListModel {
44 id: mockModel
40 id: mockModel
45− dynamicRoles: true
46−
47− signal actionInvoked(string actionId)
48−
49− function getRaw(id) {
50− return mockNotification.createObject(mockModel)
51− }
52
41
53 // add the default/PlaceHolder notification to the model
42 // add the default/PlaceHolder notification to the model
54 Component.onCompleted: {
43 Component.onCompleted: {
55− var n = {
44+ var component = Qt.createComponent("Notification.qml")
45+ var n = component.createObject("notification", {"nid": index++,
56− type: Notification.PlaceHolder,
46+ "type": Notification.PlaceHolder,
57− hints: {},
47+ "hints": {},
58− summary: "",
48+ "summary": "",
59− body: "",
49+ "body": "",
60− icon: "",
50+ "icon": "",
61− secondaryIcon: "",
51+ "secondaryIcon": "",
62− actions: []
52+ "rawActions": []})
63− }
53+ n.completed.connect(mockModel.onCompleted)
64−
65 append(n)
54 append(n)
66 }
55 }
67 }
56 }
68
57
69 function add2over1SnapDecisionNotification() {
58 function add2over1SnapDecisionNotification() {
70− var n = {
59+ var component = Qt.createComponent("Notification.qml")
60+ var n = component.createObject("notification", {"nid": index++,
71− type: Notification.SnapDecision,
61+ "type": Notification.SnapDecision,
72− hints: {"x-canonical-private-affirmative-tint": "true"},
62+ "hints": {"x-canonical-private-affirmative-tint": "true"},
73− summary: "Theatre at Ferria Stadium",
63+ "summary": "Theatre at Ferria Stadium",
74− body: "at Ferria Stadium in Bilbao, Spain\n07578545317",
64+ "body": "at Ferria Stadium in Bilbao, Spain\n07578545317",
75− icon: "",
65+ "icon": "",
76− secondaryIcon: "",
66+ "secondaryIcon": "",
77− actions: [{ id: "ok_id", label: "Ok"},
67+ "rawActions": ["ok_id", "Ok",
78− { id: "snooze_id", label: "Snooze"},
68+ "snooze_id", "Snooze",
79− { id: "view_id", label: "View"}]
69+ "view_id", "View"]})
80− }
70+ n.completed.connect(mockModel.onCompleted)
81−
82 mockModel.append(n)
71 mockModel.append(n)
83 }
72 }
84
73
85 function addEphemeralNotification() {
74 function addEphemeralNotification() {
86− var n = {
75+ var component = Qt.createComponent("Notification.qml")
76+ var n = component.createObject("notification", {"nid": index++,
87− type: Notification.Ephemeral,
77+ "type": Notification.Ephemeral,
78+ "hints": {},
88− summary: "Cole Raby",
79+ "summary": "Cole Raby",
89− body: "I did not expect it to be that late.",
80+ "body": "I did not expect it to be that late.",
90− icon: "../graphics/avatars/amanda.png",
81+ "icon": "../graphics/avatars/amanda.png",
91− secondaryIcon: "../graphics/applicationIcons/facebook.png",
82+ "secondaryIcon": "../graphics/applicationIcons/facebook.png",
92− actions: []
83+ "rawActions": ["reply_id", "Dummy"]})
93− }
84+ n.completed.connect(mockModel.onCompleted)
94−
95 mockModel.append(n)
85 mockModel.append(n)
96 }
86 }
97
87
98 function addEphemeralNonShapedIconNotification() {
88 function addEphemeralNonShapedIconNotification() {
99− var n = {
89+ var component = Qt.createComponent("Notification.qml")
90+ var n = component.createObject("notification", {"nid": index++,
100− type: Notification.Ephemeral,
91+ "type": Notification.Ephemeral,
101− hints: {"x-canonical-non-shaped-icon": "true"},
92+ "hints": {"x-canonical-non-shaped-icon": "true"},
102− summary: "Contacts",
93+ "summary": "Contacts",
103− body: "Synchronised contacts-database with cloud-storage.",
94+ "body": "Synchronised contacts-database with cloud-storage.",
104− icon: "../graphics/applicationIcons/contacts-app.png",
95+ "icon": "../graphics/applicationIcons/contacts-app.png",
105− secondaryIcon: "",
96+ "secondaryIcon": "",
106− actions: []
97+ "rawActions": ["reply_id", "Dummy"]})
107− }
98+ n.completed.connect(mockModel.onCompleted)
108−
109 mockModel.append(n)
99 mockModel.append(n)
110 }
100 }
111
101
112 function addEphemeralIconSummaryNotification() {
102 function addEphemeralIconSummaryNotification() {
113− var n = {
103+ var component = Qt.createComponent("Notification.qml")
104+ var n = component.createObject("notification", {"nid": index++,
114− type: Notification.Ephemeral,
105+ "type": Notification.Ephemeral,
115− hints: {"x-canonical-non-shaped-icon": "false"},
106+ "hints": {"x-canonical-non-shaped-icon": "false"},
116− summary: "Photo upload completed",
107+ "summary": "Photo upload completed",
117− body: "",
108+ "body": "",
118− icon: "../graphics/applicationIcons/facebook.png",
109+ "icon": "../graphics/applicationIcons/facebook.png",
119− secondaryIcon: "",
110+ "secondaryIcon": "",
120− actions: []
111+ "rawActions": ["reply_id", "Dummy"]})
121− }
112+ n.completed.connect(mockModel.onCompleted)
122−
123 mockModel.append(n)
113 mockModel.append(n)
124 }
114 }
125
115
126 function addInteractiveNotification() {
116 function addInteractiveNotification() {
127− var n = {
117+ var component = Qt.createComponent("Notification.qml")
118+ var n = component.createObject("notification", {"nid": index++,
128− type: Notification.Interactive,
119+ "type": Notification.Interactive,
120+ "hints": {},
129− summary: "Interactive notification",
121+ "summary": "Interactive notification",
130− body: "This is a notification that can be clicked",
122+ "body": "This is a notification that can be clicked",
131− icon: "../graphics/avatars/anna_olsson.png",
123+ "icon": "../graphics/avatars/anna_olsson.png",
132− secondaryIcon: "",
124+ "secondaryIcon": "",
133− actions: [{ id: "reply_id", label: "Dummy"}],
125+ "rawActions": ["reply_id", "Dummy"]})
134− }
126+ n.completed.connect(mockModel.onCompleted)
135−
136 mockModel.append(n)
127 mockModel.append(n)
137 }
128 }
138
129
139 function addConfirmationNotification() {
130 function addConfirmationNotification() {
140− var n = {
131+ var component = Qt.createComponent("Notification.qml")
132+ var n = component.createObject("notification", {"nid": index++,
141− type: Notification.Confirmation,
133+ "type": Notification.Confirmation,
142− hints: {"x-canonical-non-shaped-icon": "true"},
134+ "hints": {"x-canonical-non-shaped-icon": "true"},
143− summary: "Confirmation notification",
135+ "summary": "Confirmation notification",
144− body: "",
136+ "body": "",
145− icon: "image://theme/audio-volume-medium",
137+ "icon": "image://theme/audio-volume-medium",
146− secondaryIcon: "",
138+ "secondaryIcon": "",
147− value: 50,
139+ "value": 50,
148− actions: [],
140+ "rawActions": ["reply_id", "Dummy"]})
149− }
141+ n.completed.connect(mockModel.onCompleted)
150−
151 mockModel.append(n)
142 mockModel.append(n)
152 }
143 }
153
144
154 function add2ndConfirmationNotification() {
145 function add2ndConfirmationNotification() {
155− var n = {
146+ var component = Qt.createComponent("Notification.qml")
147+ var n = component.createObject("notification", {"nid": index++,
156− type: Notification.Confirmation,
148+ "type": Notification.Confirmation,
157− hints: {"x-canonical-non-shaped-icon": "true",
149+ "hints": {"x-canonical-non-shaped-icon": "true",
158− "x-canonical-value-bar-tint": "true"},
150+ "x-canonical-value-bar-tint": "true"},
159− summary: "Confirmation notification",
151+ "summary": "Confirmation notification",
160− body: "High Volume",
152+ "body": "High Volume",
161− icon: "image://theme/audio-volume-high",
153+ "icon": "image://theme/audio-volume-high",
162− secondaryIcon: "",
154+ "secondaryIcon": "",
163− value: 85,
155+ "value": 85,
164− actions: [],
156+ "rawActions": ["reply_id", "Dummy"]})
165− }
157+ n.completed.connect(mockModel.onCompleted)
166−
167 mockModel.append(n)
158 mockModel.append(n)
168 }
159 }
169
160
@@ -174,8 +165,9 @@ Item {
174 }
165 }
175
166
176 function remove1stNotification() {
167 function remove1stNotification() {
177− if (mockModel.count > 1)
168+ if (mockModel.count > 1) {
178− mockModel.remove(1)
169+ mockModel.removeSecond()
170+ }
179 }
171 }
180
172
181 Rectangle {
173 Rectangle {
@@ -273,41 +265,122 @@ Item {
273 name: "NotificationRendererTest"
265 name: "NotificationRendererTest"
274 when: windowShown
266 when: windowShown
275
267
268+ property list<Notification> nlist: [
269+ Notification {
270+ nid: 1
271+ type: Notification.Ephemeral
272+ summary: "Photo upload completed"
273+ body: ""
274+ icon: "../graphics/applicationIcons/facebook.png"
275+ secondaryIcon: ""
276+ value: 0
277+ rawActions: []
278+ },
279+ Notification {
280+ nid: 2
281+ type: Notification.Ephemeral
282+ hints: {"x-canonical-private-affirmative-tint": "false",
283+ "sound-file": "dummy.ogg",
284+ "suppress-sound": "true"}
285+ summary: "New comment successfully published"
286+ body: ""
287+ icon: ""
288+ secondaryIcon: "../graphics/applicationIcons/facebook.png"
289+ value: 0
290+ rawActions: []
291+ },
292+ Notification {
293+ nid: 3
294+ type: Notification.Interactive
295+ hints: {"x-canonical-private-affirmative-tint": "false",
296+ "sound-file": "dummy.ogg"}
297+ summary: "Interactive notification"
298+ body: "This is a notification that can be clicked"
299+ icon: "../graphics/avatars/amanda.png"
300+ secondaryIcon: ""
301+ value: 0
302+ rawActions: ["reply_id", "Dummy"]
303+ },
304+ Notification {
305+ nid: 4
306+ type: Notification.SnapDecision
307+ hints: {"x-canonical-private-affirmative-tint": "false",
308+ "sound-file": "dummy.ogg"}
309+ summary: "Bro Coly"
310+ body: "At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."
311+ icon: "../graphics/avatars/anna_olsson.png"
312+ secondaryIcon: ""
313+ value: 0
314+ rawActions: ["accept_id", "Accept",
315+ "reject_id", "Reject"]
316+ },
317+ Notification {
318+ nid: 5
319+ type: Notification.Ephemeral
320+ hints: {"x-canonical-private-affirmative-tint": "false",
321+ "sound-file": "dummy.ogg"}
322+ summary: "Cole Raby"
323+ body: "I did not expect it to be that late."
324+ icon: "../graphics/avatars/funky.png"
325+ secondaryIcon: "../graphics/applicationIcons/facebook.png"
326+ value: 0
327+ rawActions: []
328+ },
329+ Notification {
330+ nid: 6
331+ type: Notification.Ephemeral
332+ hints: {"x-canonical-private-affirmative-tint": "false",
333+ "x-canonical-non-shaped-icon": "true"}
334+ summary: "Contacts"
335+ body: "Synchronised contacts-database with cloud-storage."
336+ icon: "image://theme/contacts-app"
337+ secondaryIcon: ""
338+ value: 0
339+ rawActions: []
340+ },
341+ Notification {
342+ nid: 7
343+ type: Notification.Confirmation
344+ hints: {"x-canonical-non-shaped-icon": "true"}
345+ summary: ""
346+ body: ""
347+ icon: "image://theme/audio-volume-medium"
348+ secondaryIcon: ""
349+ value: 50
350+ rawActions: []
351+ },
352+ Notification {
353+ nid: 8
354+ type: Notification.Confirmation
355+ hints: {"x-canonical-non-shaped-icon": "true",
356+ "x-canonical-value-bar-tint" : "true"}
357+ summary: ""
358+ body: "High Volume"
359+ icon: "image://theme/audio-volume-high"
360+ secondaryIcon: ""
361+ value: 85
362+ rawActions: []
363+ },
364+ Notification {
365+ nid: 9
366+ type: Notification.SnapDecision
367+ hints: {"x-canonical-private-affirmative-tint": "true"}
368+ summary: "Theatre at Ferria Stadium"
369+ body: "at Ferria Stadium in Bilbao, Spain\n07578545317"
370+ icon: ""
371+ secondaryIcon: ""
372+ value: 0
373+ rawActions: ["ok_id", "Ok",
374+ "snooze_id", "Snooze",
375+ "view_id", "View"]
376+ }
377+ ]
378+
276 function test_NotificationRenderer_data() {
379 function test_NotificationRenderer_data() {
277 return [
380 return [
278− {
279− tag: "2-over-1 Snap Decision with button-tint",
280− type: Notification.SnapDecision,
281− hints: {"x-canonical-private-affirmative-tint": "true"},
282− summary: "Theatre at Ferria Stadium",
283− body: "at Ferria Stadium in Bilbao, Spain\n07578545317",
284− icon: "",
285− secondaryIcon: "",
286− actions: [{ id: "ok_id", label: "Ok"},
287− { id: "snooze_id", label: "Snooze"},
288− { id: "view_id", label: "View"}],
289− summaryVisible: true,
290− bodyVisible: true,
291− iconVisible: false,
292− centeredIconVisible: false,
293− shaped: false,
294− secondaryIconVisible: false,
295− buttonRowVisible: false,
296− buttonTinted: true,
297− hasSound: false,
298− valueVisible: false,
299− valueLabelVisible: false,
300− valueTinted: false
301− },
302 {
381 {
303 tag: "Ephemeral notification - icon-summary layout",
382 tag: "Ephemeral notification - icon-summary layout",
304− type: Notification.Ephemeral,
383+ n: nlist[0],
305− hints: {},
306− summary: "Photo upload completed",
307− body: "",
308− icon: "../graphics/applicationIcons/facebook.png",
309− secondaryIcon: "",
310− actions: [],
311 summaryVisible: true,
384 summaryVisible: true,
312 bodyVisible: false,
385 bodyVisible: false,
313 iconVisible: true,
386 iconVisible: true,
@@ -323,15 +396,7 @@ Item {
323 },
396 },
324 {
397 {
325 tag: "Ephemeral notification - check suppression of secondary icon for icon-summary layout",
398 tag: "Ephemeral notification - check suppression of secondary icon for icon-summary layout",
326− type: Notification.Ephemeral,
399+ n: nlist[1],
327− hints: {"x-canonical-private-affirmative-tint": "false",
328− "sound-file": "dummy.ogg",
329− "suppress-sound": "true"},
330− summary: "New comment successfully published",
331− body: "",
332− icon: "",
333− secondaryIcon: "../graphics/applicationIcons/facebook.png",
334− actions: [],
335 summaryVisible: true,
400 summaryVisible: true,
336 bodyVisible: false,
401 bodyVisible: false,
337 interactiveAreaEnabled: false,
402 interactiveAreaEnabled: false,
@@ -348,14 +413,7 @@ Item {
348 },
413 },
349 {
414 {
350 tag: "Interactive notification",
415 tag: "Interactive notification",
351− type: Notification.Interactive,
416+ n: nlist[2],
352− hints: {"x-canonical-private-affirmative-tint": "false",
353− "sound-file": "dummy.ogg"},
354− summary: "Interactive notification",
355− body: "This is a notification that can be clicked",
356− icon: "../graphics/avatars/amanda.png",
357− secondaryIcon: "",
358− actions: [{ id: "reply_id", label: "Dummy"}],
359 summaryVisible: true,
417 summaryVisible: true,
360 bodyVisible: true,
418 bodyVisible: true,
361 iconVisible: true,
419 iconVisible: true,
@@ -371,15 +429,7 @@ Item {
371 },
429 },
372 {
430 {
373 tag: "Snap Decision without secondary icon and no button-tint",
431 tag: "Snap Decision without secondary icon and no button-tint",
374− type: Notification.SnapDecision,
432+ n: nlist[3],
375− hints: {"x-canonical-private-affirmative-tint": "false",
376− "sound-file": "dummy.ogg"},
377− summary: "Bro Coly",
378− body: "At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.",
379− icon: "../graphics/avatars/anna_olsson.png",
380− secondaryIcon: "",
381− actions: [{ id: "accept_id", label: "Accept"},
382− { id: "reject_id", label: "Reject"}],
383 summaryVisible: true,
433 summaryVisible: true,
384 bodyVisible: true,
434 bodyVisible: true,
385 iconVisible: true,
435 iconVisible: true,
@@ -395,14 +445,7 @@ Item {
395 },
445 },
396 {
446 {
397 tag: "Ephemeral notification",
447 tag: "Ephemeral notification",
398− type: Notification.Ephemeral,
448+ n: nlist[4],
399− hints: {"x-canonical-private-affirmative-tint": "false",
400− "sound-file": "dummy.ogg"},
401− summary: "Cole Raby",
402− body: "I did not expect it to be that late.",
403− icon: "../graphics/avatars/funky.png",
404− secondaryIcon: "../graphics/applicationIcons/facebook.png",
405− actions: [],
406 summaryVisible: true,
449 summaryVisible: true,
407 bodyVisible: true,
450 bodyVisible: true,
408 iconVisible: true,
451 iconVisible: true,
@@ -418,14 +461,7 @@ Item {
418 },
461 },
419 {
462 {
420 tag: "Ephemeral notification with non-shaped icon",
463 tag: "Ephemeral notification with non-shaped icon",
421− type: Notification.Ephemeral,
464+ n: nlist[5],
422− hints: {"x-canonical-private-affirmative-tint": "false",
423− "x-canonical-non-shaped-icon": "true"},
424− summary: "Contacts",
425− body: "Synchronised contacts-database with cloud-storage.",
426− icon: "image://theme/contacts-app",
427− secondaryIcon: "",
428− actions: [],
429 summaryVisible: true,
465 summaryVisible: true,
430 bodyVisible: true,
466 bodyVisible: true,
431 iconVisible: true,
467 iconVisible: true,
@@ -441,14 +477,7 @@ Item {
441 },
477 },
442 {
478 {
443 tag: "Confirmation notification with value",
479 tag: "Confirmation notification with value",
444− type: Notification.Confirmation,
480+ n: nlist[6],
445− hints: {"x-canonical-non-shaped-icon": "true"},
446− summary: "",
447− body: "",
448− icon: "image://theme/audio-volume-medium",
449− secondaryIcon: "",
450− value: 50,
451− actions: [],
452 summaryVisible: false,
481 summaryVisible: false,
453 bodyVisible: false,
482 bodyVisible: false,
454 iconVisible: false,
483 iconVisible: false,
@@ -464,15 +493,7 @@ Item {
464 },
493 },
465 {
494 {
466 tag: "Confirmation notification with value, label and tint",
495 tag: "Confirmation notification with value, label and tint",
467− type: Notification.Confirmation,
496+ n: nlist[7],
468− hints: {"x-canonical-non-shaped-icon": "true",
469− "x-canonical-value-bar-tint" : "true"},
470− summary: "",
471− body: "High Volume",
472− icon: "image://theme/audio-volume-high",
473− secondaryIcon: "",
474− value: 85,
475− actions: [],
476 summaryVisible: false,
497 summaryVisible: false,
477 bodyVisible: false,
498 bodyVisible: false,
478 iconVisible: false,
499 iconVisible: false,
@@ -485,6 +506,22 @@ Item {
485 valueVisible: true,
506 valueVisible: true,
486 valueLabelVisible: true,
507 valueLabelVisible: true,
487 valueTinted: true
508 valueTinted: true
509+ },
510+ {
511+ tag: "2-over-1 Snap Decision with button-tint",
512+ n: nlist[8],
513+ summaryVisible: true,
514+ bodyVisible: true,
515+ iconVisible: false,
516+ centeredIconVisible: false,
517+ shaped: false,
518+ secondaryIconVisible: false,
519+ buttonRowVisible: false,
520+ buttonTinted: true,
521+ hasSound: false,
522+ valueVisible: false,
523+ valueLabelVisible: false,
524+ valueTinted: false
488 }
525 }
489 ]
526 ]
490 }
527 }
@@ -509,8 +546,14 @@ Item {
509 }
546 }
510
547
511 function test_NotificationRenderer(data) {
548 function test_NotificationRenderer(data) {
549+ // make sure the clicks on mocked notifications can be checked against by "actionSpy" (mimicking the NotificationServer component)
550+ data.n.actionInvoked.connect(mockModel.actionInvoked)
551+
552+ // hook up notification's completed-signal with model's onCompleted-slot, so that remove() (model) happens on close() (notification)
553+ data.n.completed.connect(mockModel.onCompleted)
554+
512 // populate model with some mock notifications
555 // populate model with some mock notifications
513− mockModel.append(data)
556+ mockModel.append(data.n)
514
557
515 // make sure the view is properly updated before going on
558 // make sure the view is properly updated before going on
516 notifications.forceLayout();
559 notifications.forceLayout();
@@ -548,9 +591,9 @@ Item {
548
591
549 // test input does not fall through
592 // test input does not fall through
550 mouseClick(notification)
593 mouseClick(notification)
551− if(data.type == Notification.Interactive) {
594+ if(data.n.type === Notification.Interactive) {
552 actionSpy.wait()
595 actionSpy.wait()
553− compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id for interactive action")
596+ compare(actionSpy.signalArguments[0][0], data.n.actions.data(0, ActionModel.RoleActionId), "got wrong id for interactive action")
554 }
597 }
555 compare(clickThroughSpy.count, 0, "click on notification fell through")
598 compare(clickThroughSpy.count, 0, "click on notification fell through")
556
599
@@ -559,17 +602,19 @@ Item {
559 compare(bodyLabel.visible, data.bodyVisible, "body-text visibility is incorrect")
602 compare(bodyLabel.visible, data.bodyVisible, "body-text visibility is incorrect")
560 compare(buttonRow.visible, data.buttonRowVisible, "button visibility is incorrect")
603 compare(buttonRow.visible, data.buttonRowVisible, "button visibility is incorrect")
561
604
605+ if (data.hasSound) {
562− var audioItem = findInvisibleChild(notification, "sound")
606+ var audioItem = findInvisibleChild(notification, "sound")
563− compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state")
607+ compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state")
608+ }
564
609
565 if(data.buttonRowVisible) {
610 if(data.buttonRowVisible) {
566 var buttonCancel = findChild(buttonRow, "notify_button1")
611 var buttonCancel = findChild(buttonRow, "notify_button1")
567 var buttonAccept = findChild(buttonRow, "notify_button0")
612 var buttonAccept = findChild(buttonRow, "notify_button0")
568
613
569 // only test the left/cancel-button if two actions have been passed in
614 // only test the left/cancel-button if two actions have been passed in
570− if (data.actions.length == 2) {
615+ if (data.n.actions.count === 2) {
571 tryCompareFunction(function() { mouseClick(buttonCancel); return actionSpy.signalArguments.length > 0; }, true);
616 tryCompareFunction(function() { mouseClick(buttonCancel); return actionSpy.signalArguments.length > 0; }, true);
572− compare(actionSpy.signalArguments[0][0], data.actions[1]["id"], "got wrong id for negative action")
617+ compare(actionSpy.signalArguments[0][0], data.n.actions.data(1, ActionModel.RoleActionId), "got wrong id for negative action")
573 actionSpy.clear()
618 actionSpy.clear()
574 }
619 }
575
620
@@ -578,36 +623,49 @@ Item {
578
623
579 // click the positive/right button
624 // click the positive/right button
580 tryCompareFunction(function() { mouseClick(buttonAccept); return actionSpy.signalArguments.length > 0; }, true);
625 tryCompareFunction(function() { mouseClick(buttonAccept); return actionSpy.signalArguments.length > 0; }, true);
581− compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id positive action")
626+ compare(actionSpy.signalArguments[0][0], data.n.actions.data(0, ActionModel.RoleActionId), "got wrong id positive action")
582 actionSpy.clear()
627 actionSpy.clear()
583− waitForRendering (notification)
584
628
585 // check if there's a ComboButton created due to more actions being passed
629 // check if there's a ComboButton created due to more actions being passed
586− if (data.actions.length > 2) {
630+ if (data.n.actions.count > 3) {
587 var comboButton = findChild(notification, "notify_button2")
631 var comboButton = findChild(notification, "notify_button2")
588− tryCompareFunction(function() { return comboButton.expanded == false; }, true);
632+ tryCompareFunction(function() { return comboButton.expanded === false; }, true);
589
633
590 // click to expand
634 // click to expand
591− tryCompareFunction(function() { mouseClick(comboButton, comboButton.width - comboButton.__styleInstance.dropDownWidth / 2, comboButton.height / 2); return comboButton.expanded == true; }, true);
635+ tryCompareFunction(function() { mouseClick(comboButton, comboButton.width / 2, comboButton.height / 2); return comboButton.expanded === true; }, true);
592
636
593 // try clicking on choices in expanded comboList
637 // try clicking on choices in expanded comboList
594 var choiceButton1 = findChild(notification, "notify_button3")
638 var choiceButton1 = findChild(notification, "notify_button3")
595 tryCompareFunction(function() { mouseClick(choiceButton1); return actionSpy.signalArguments.length > 0; }, true);
639 tryCompareFunction(function() { mouseClick(choiceButton1); return actionSpy.signalArguments.length > 0; }, true);
596− compare(actionSpy.signalArguments[0][0], data.actions[3]["id"], "got wrong id choice action 1")
640+ compare(actionSpy.signalArguments[0][0], data.n.actions.data(3, ActionModel.RoleActionId), "got wrong id choice action 1")
597 actionSpy.clear()
641 actionSpy.clear()
598
642
599 var choiceButton2 = findChild(notification, "notify_button4")
643 var choiceButton2 = findChild(notification, "notify_button4")
600 tryCompareFunction(function() { mouseClick(choiceButton2); return actionSpy.signalArguments.length > 0; }, true);
644 tryCompareFunction(function() { mouseClick(choiceButton2); return actionSpy.signalArguments.length > 0; }, true);
601− compare(actionSpy.signalArguments[0][0], data.actions[4]["id"], "got wrong id choice action 2")
645+ compare(actionSpy.signalArguments[0][0], data.n.actions.data(4, ActionModel.RoleActionId), "got wrong id choice action 2")
602 actionSpy.clear()
646 actionSpy.clear()
603
647
604 // click to collapse
648 // click to collapse
605− //tryCompareFunction(function() { mouseClick(comboButton, comboButton.width - comboButton.__styleInstance.dropDownWidth / 2, comboButton.height / 2); return comboButton.expanded == false; }, true);
649+ tryCompareFunction(function() { mouseClick(comboButton, comboButton.width / 2, comboButton.height / 2); return comboButton.expanded == false; }, true);
606 } else {
650 } else {
607 mouseClick(buttonCancel)
651 mouseClick(buttonCancel)
608− compare(actionSpy.signalArguments[0][0], data.actions[1]["id"], "got wrong id for negative action")
652+ compare(actionSpy.signalArguments[0][0], data.n.actions.data(1, ActionModel.RoleActionId), "got wrong id for negative action")
609 }
653 }
610 }
654 }
655+
656+ // swipe-to-dismiss check
657+ waitForRendering(notification)
658+ var before = mockModel.count
659+ var dragStart = notification.width * 0.25;
660+ var dragEnd = notification.width;
661+ var dragY = notification.height / 2;
662+ touchFlick(notification, dragStart, dragY, dragEnd, dragY)
663+ waitForRendering(notification)
664+ if ((data.n.type === Notification.SnapDecision && notification.state === "expanded") || data.n.type === Notification.Confirmation) {
665+ tryCompare(mockModel, "count", before)
666+ } else {
667+ tryCompare(mockModel, "count", before - 1)
668+ }
611 }
669 }
612 }
670 }
613 }
671 }
tests/qmltests/Notifications/tst_OptionToggle.qml+11−6
@@ -1,17 +1,20 @@
1/*
1/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright 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 Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
6 * the Free Software Foundation; version 3.
7 *
7 *
8 * This program is distributed in the hope that it will be useful,
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
11+ * GNU Lesser General Public License for more details.
12 *
12 *
13− * You should have received a copy of the GNU General Public License
13+ * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authors:
17+ * Mirco Mueller <mirco.mueller@canonical.com>
15 */
18 */
16
19
17import QtQuick 2.0
20import QtQuick 2.0
@@ -235,8 +238,10 @@ Row {
235 compare(bodyLabel.visible, data.bodyVisible, "body-text visibility is incorrect")
238 compare(bodyLabel.visible, data.bodyVisible, "body-text visibility is incorrect")
236 compare(buttonRow.visible, data.buttonRowVisible, "button visibility is incorrect")
239 compare(buttonRow.visible, data.buttonRowVisible, "button visibility is incorrect")
237
240
241+ if (data.hasSound) {
238− var audioItem = findInvisibleChild(notification, "sound")
242+ var audioItem = findInvisibleChild(notification, "sound")
239− compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state")
243+ compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state")
244+ }
240
245
241 if(data.buttonRowVisible) {
246 if(data.buttonRowVisible) {
242 var buttonCancel = findChild(buttonRow, "notify_button1")
247 var buttonCancel = findChild(buttonRow, "notify_button1")
tests/qmltests/Notifications/tst_SwipeToAct.qml+4
@@ -218,6 +218,10 @@ Item {
218 // populate model with some mock notifications
218 // populate model with some mock notifications
219 mockModel.append(data)
219 mockModel.append(data)
220
220
221+ // add actions to action-model to test against
222+ myActionModel.append("ok_id", "Ok")
223+ myActionModel.append("cancel_id", "Cancel")
224+
221 // make sure the view is properly updated before going on
225 // make sure the view is properly updated before going on
222 notifications.forceLayout();
226 notifications.forceLayout();
223 waitForRendering(notifications);
227 waitForRendering(notifications);
tests/qmltests/Panel/tst_Panel.qml+33−2
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright 2013-2014 Canonical Ltd.
2+ * Copyright 2013-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
@@ -41,7 +41,12 @@ IndicatorTest {
41 Layout.fillHeight: true
41 Layout.fillHeight: true
42
42
43 id: itemArea
43 id: itemArea
44− color: "blue"
44+ color: backgroundMouseArea.pressed ? "red" : "blue"
45+
46+ MouseArea {
47+ id: backgroundMouseArea
48+ anchors.fill: parent
49+ }
45
50
46 Panel {
51 Panel {
47 id: panel
52 id: panel
@@ -128,6 +133,12 @@ IndicatorTest {
128 name: "Panel"
133 name: "Panel"
129 when: windowShown
134 when: windowShown
130
135
136+ SignalSpy {
137+ id: backgroundPressedSpy
138+ target: backgroundMouseArea
139+ signalName: "pressedChanged"
140+ }
141+
131 function init() {
142 function init() {
132 panel.fullscreenMode = false;
143 panel.fullscreenMode = false;
133 callManager.foregroundCall = null;
144 callManager.foregroundCall = null;
@@ -140,6 +151,9 @@ IndicatorTest {
140 // (switches between normal and fullscreen modes are animated)
151 // (switches between normal and fullscreen modes are animated)
141 var indicatorArea = findChild(panel, "indicatorArea");
152 var indicatorArea = findChild(panel, "indicatorArea");
142 tryCompare(indicatorArea, "y", 0);
153 tryCompare(indicatorArea, "y", 0);
154+
155+ backgroundPressedSpy.clear();
156+ compare(backgroundPressedSpy.valid, true);
143 }
157 }
144
158
145 function get_indicator_item(index) {
159 function get_indicator_item(index) {
@@ -299,5 +313,22 @@ IndicatorTest {
299
313
300 touchRelease(panel, mappedPosition.x, panel.minimizedPanelHeight / 2);
314 touchRelease(panel, mappedPosition.x, panel.minimizedPanelHeight / 2);
301 }
315 }
316+
317+ /* Checks that no input reaches items behind the indicator bar.
318+ Ie., the indicator bar should eat all input events that hit it.
319+ */
320+ function test_indicatorBarEatsAllEvents() {
321+ // Perform several taps throughout the length of the indicator bar to ensure
322+ // that it doesn't have a "weak spot" from where taps pass through.
323+ var numTaps = 5;
324+ var stepLength = (panel.width / (numTaps + 1));
325+ var tapY = panel.indicators.minimizedPanelHeight / 2;
326+ for (var i = 1; i <= numTaps; ++i) {
327+ tap(panel, stepLength * i, tapY);
328+ tryCompare(panel.indicators, "fullyClosed", true);
329+ }
330+
331+ compare(backgroundPressedSpy.count, 0);
332+ }
302 }
333 }
303}
334}
tests/qmltests/Stages/ApplicationCheckBox.qmlnew file+45
@@ -0,0 +1,45 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import QtQuick.Layouts 1.1
19+import Ubuntu.Components 1.1
20+import Unity.Application 0.1
21+
22+RowLayout {
23+ id: root
24+ property string appId
25+ property alias checked: checkbox.checked
26+
27+ Layout.fillWidth: true
28+ CheckBox {
29+ id: checkbox
30+ checked: false
31+ activeFocusOnPress: false
32+ onCheckedChanged: {
33+ if (checked) {
34+ ApplicationManager.startApplication(root.appId);
35+ } else {
36+ ApplicationManager.stopApplication(root.appId);
37+ }
38+ }
39+ }
40+ Label {
41+ text: root.appId
42+ color: "white"
43+ anchors.verticalCenter: parent.verticalCenter
44+ }
45+}
tests/qmltests/Stages/RecursingChildSessionControl.qml+3
@@ -89,6 +89,7 @@ ColumnLayout {
89 CheckBox {
89 CheckBox {
90 id: _surfaceCheckbox;
90 id: _surfaceCheckbox;
91 checked: false;
91 checked: false;
92+ activeFocusOnPress: false
92 enabled: root.session
93 enabled: root.session
93 onCheckedChanged: {
94 onCheckedChanged: {
94 if (checked) {
95 if (checked) {
@@ -117,6 +118,7 @@ ColumnLayout {
117
118
118 Button {
119 Button {
119 enabled: root.session
120 enabled: root.session
121+ activeFocusOnPress: false
120 text: removable ? "Remove" : "Release"
122 text: removable ? "Remove" : "Release"
121 onClicked: {
123 onClicked: {
122 if (removable) {
124 if (removable) {
@@ -131,6 +133,7 @@ ColumnLayout {
131
133
132 Button {
134 Button {
133 enabled: root.session !== null
135 enabled: root.session !== null
136+ activeFocusOnPress: false
134 text: "Add Child"
137 text: "Add Child"
135 onClicked: {
138 onClicked: {
136 var screenshot = Math.round(Math.random()*100 % (screenshotIds.length-1));
139 var screenshot = Math.round(Math.random()*100 % (screenshotIds.length-1));
tests/qmltests/Stages/tst_ApplicationWindow.qml+6−1
@@ -52,10 +52,12 @@ Rectangle {
52 application: fakeApplication
52 application: fakeApplication
53 orientation: Qt.PortraitOrientation
53 orientation: Qt.PortraitOrientation
54 interactive: true
54 interactive: true
55+ focus: true
55 }
56 }
56 }
57 }
57 Loader {
58 Loader {
58 id: applicationWindowLoader
59 id: applicationWindowLoader
60+ focus: true
59 anchors {
61 anchors {
60 top: parent.top
62 top: parent.top
61 bottom: parent.bottom
63 bottom: parent.bottom
@@ -83,6 +85,7 @@ Rectangle {
83
85
84 CheckBox {
86 CheckBox {
85 id: sessionCheckbox; checked: false
87 id: sessionCheckbox; checked: false
88+ activeFocusOnPress: false
86 onCheckedChanged: {
89 onCheckedChanged: {
87 if (applicationWindowLoader.status !== Loader.Ready)
90 if (applicationWindowLoader.status !== Loader.Ready)
88 return;
91 return;
@@ -121,6 +124,7 @@ Rectangle {
121
124
122 ListItem.ItemSelector {
125 ListItem.ItemSelector {
123 id: appStateSelector
126 id: appStateSelector
127+ activeFocusOnPress: false
124 anchors { left: parent.left; right: parent.right }
128 anchors { left: parent.left; right: parent.right }
125 text: "Application state"
129 text: "Application state"
126 model: ["Starting",
130 model: ["Starting",
@@ -146,6 +150,7 @@ Rectangle {
146
150
147 Button {
151 Button {
148 anchors { left: parent.left; right: parent.right }
152 anchors { left: parent.left; right: parent.right }
153+ activeFocusOnPress: false
149 text: "Rotate device \u27F3"
154 text: "Rotate device \u27F3"
150 onClicked: {
155 onClicked: {
151 var orientation = applicationWindowLoader.item.orientation
156 var orientation = applicationWindowLoader.item.orientation
@@ -398,7 +403,7 @@ Rectangle {
398 verify(stateGroup.state === "void");
403 verify(stateGroup.state === "void");
399 }
404 }
400
405
401− function test_forceActiveFocusFollowsInterative() {
406+ function test_surfaceActiveFocusFollowsAppWindowInterative() {
402 fakeApplication.createSession();
407 fakeApplication.createSession();
403 applicationWindowLoader.item.interactive = false;
408 applicationWindowLoader.item.interactive = false;
404 applicationWindowLoader.item.interactive = true;
409 applicationWindowLoader.item.interactive = true;
tests/qmltests/Stages/tst_PhoneStage.qml+4
@@ -29,6 +29,7 @@ Item {
29 PhoneStage {
29 PhoneStage {
30 id: phoneStage
30 id: phoneStage
31 anchors { fill: parent; rightMargin: units.gu(30) }
31 anchors { fill: parent; rightMargin: units.gu(30) }
32+ focus: true
32 dragAreaWidth: units.gu(2)
33 dragAreaWidth: units.gu(2)
33 maximizedAppTopMargin: units.gu(3) + units.dp(2)
34 maximizedAppTopMargin: units.gu(3) + units.dp(2)
34 interactive: true
35 interactive: true
@@ -51,6 +52,7 @@ Item {
51 Button {
52 Button {
52 anchors { left: parent.left; right: parent.right }
53 anchors { left: parent.left; right: parent.right }
53 text: "Add App"
54 text: "Add App"
55+ activeFocusOnPress: false
54 onClicked: {
56 onClicked: {
55 testCase.addApps();
57 testCase.addApps();
56 }
58 }
@@ -58,6 +60,7 @@ Item {
58 Button {
60 Button {
59 anchors { left: parent.left; right: parent.right }
61 anchors { left: parent.left; right: parent.right }
60 text: "Remove Selected"
62 text: "Remove Selected"
63+ activeFocusOnPress: false
61 onClicked: {
64 onClicked: {
62 ApplicationManager.stopApplication(ApplicationManager.get(appList.selectedAppIndex).appId);
65 ApplicationManager.stopApplication(ApplicationManager.get(appList.selectedAppIndex).appId);
63 }
66 }
@@ -65,6 +68,7 @@ Item {
65 Button {
68 Button {
66 anchors { left: parent.left; right: parent.right }
69 anchors { left: parent.left; right: parent.right }
67 text: "Stop Selected"
70 text: "Stop Selected"
71+ activeFocusOnPress: false
68 onClicked: {
72 onClicked: {
69 ApplicationManager.get(appList.selectedAppIndex).setState(ApplicationInfoInterface.Stopped);
73 ApplicationManager.get(appList.selectedAppIndex).setState(ApplicationInfoInterface.Stopped);
70 }
74 }
tests/qmltests/Stages/tst_SessionContainer.qml+14−7
@@ -42,11 +42,14 @@ Rectangle {
42 id: sessionContainer
42 id: sessionContainer
43 anchors.fill: parent
43 anchors.fill: parent
44 orientation: Qt.PortraitOrientation
44 orientation: Qt.PortraitOrientation
45+ focus: true
46+ interactive: true
45 }
47 }
46 }
48 }
47
49
48 Loader {
50 Loader {
49 id: sessionContainerLoader
51 id: sessionContainerLoader
52+ focus: true
50 anchors {
53 anchors {
51 top: parent.top
54 top: parent.top
52 bottom: parent.bottom
55 bottom: parent.bottom
@@ -75,6 +78,7 @@ Rectangle {
75 CheckBox {
78 CheckBox {
76 id: sessionCheckbox;
79 id: sessionCheckbox;
77 checked: false;
80 checked: false;
81+ activeFocusOnPress: false
78 onCheckedChanged: {
82 onCheckedChanged: {
79 if (sessionContainerLoader.status !== Loader.Ready)
83 if (sessionContainerLoader.status !== Loader.Ready)
80 return;
84 return;
@@ -190,8 +194,6 @@ Rectangle {
190 var sessionContainer = sessionContainerLoader.item;
194 var sessionContainer = sessionContainerLoader.item;
191 compare(sessionContainer.childSessions.count(), 0);
195 compare(sessionContainer.childSessions.count(), 0);
192
196
193− sessionContainer.interactive = true;
194−
195 var i;
197 var i;
196 var sessions = [];
198 var sessions = [];
197 // 3 sessions should cover all edge cases
199 // 3 sessions should cover all edge cases
@@ -209,14 +211,19 @@ Rectangle {
209
211
210 var a_session;
212 var a_session;
211 while(a_session = sessions.pop()) {
213 while(a_session = sessions.pop()) {
212− a_session.surface.forceActiveFocus();
213 compare(a_session.surface.activeFocus, true);
214 compare(a_session.surface.activeFocus, true);
214
215
215− var parentSession = a_session.parentSession;
216+ ApplicationTest.removeSurface(a_session.surface);
216− sessionContainer.session.removeChildSession(a_session);
217+ ApplicationTest.removeSession(a_session);
217− compare(a_session.surface.activeFocus, false);
218
218
219− compare(parentSession.surface.activeFocus, true);
219+ if (sessions.length > 0) {
220+ // active focus should have gone to the yongest remaining sibling
221+ var previousSiblingSurface = sessions[sessions.length - 1].surface
222+ tryCompare(previousSiblingSurface, "activeFocus", true);
223+ } else {
224+ // active focus should have gone to the parent surface
225+ tryCompare(sessionContainer.session.surface, "activeFocus", true);
226+ }
220 }
227 }
221 }
228 }
222
229
tests/qmltests/Stages/tst_TabletStage.qmlnew file+154
@@ -0,0 +1,154 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import QtTest 1.0
19+import Ubuntu.Components 1.1
20+import Ubuntu.Components.ListItems 1.0 as ListItem
21+import Unity.Application 0.1
22+import Unity.Test 0.1
23+
24+import "../../../qml/Stages"
25+
26+Rectangle {
27+ id: root
28+ color: "grey"
29+ width: tabletStageLoader.width + controls.width
30+ height: tabletStageLoader.height
31+
32+ Loader {
33+ id: tabletStageLoader
34+
35+ x: ((root.width - controls.width) - width) / 2
36+ y: (root.height - height) / 2
37+ width: units.gu(160*0.7)
38+ height: units.gu(100*0.7)
39+
40+ focus: true
41+
42+ property bool itemDestroyed: false
43+ sourceComponent: Component {
44+ TabletStage {
45+ anchors.fill: parent
46+ Component.onDestruction: {
47+ tabletStageLoader.itemDestroyed = true;
48+ }
49+ dragAreaWidth: units.gu(2)
50+ maximizedAppTopMargin: units.gu(3) + units.dp(2)
51+ interactive: true
52+ focus: true
53+ }
54+ }
55+ }
56+
57+ Rectangle {
58+ id: controls
59+ color: "darkgrey"
60+ width: units.gu(30)
61+ anchors {
62+ top: parent.top
63+ bottom: parent.bottom
64+ right: parent.right
65+ }
66+
67+ Column {
68+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
69+ spacing: units.gu(1)
70+ ApplicationCheckBox {
71+ id: webbrowserCheckBox
72+ appId: "webbrowser-app"
73+ }
74+ ApplicationCheckBox {
75+ id: dialerCheckBox
76+ appId: "dialer-app"
77+ }
78+ }
79+ }
80+
81+ UnityTestCase {
82+ id: testCase
83+ name: "TabletStage"
84+ when: windowShown
85+
86+ property Item tabletStage: tabletStageLoader.status === Loader.Ready ? tabletStageLoader.item : null
87+
88+ function init() {
89+ tabletStageLoader.active = true;
90+ tryCompare(tabletStageLoader, "status", Loader.Ready);
91+ }
92+
93+ function cleanup() {
94+ tabletStageLoader.itemDestroyed = false;
95+ tabletStageLoader.active = false;
96+
97+ tryCompare(tabletStageLoader, "status", Loader.Null);
98+ tryCompare(tabletStageLoader, "item", null);
99+ // Loader.status might be Loader.Null and Loader.item might be null but the Loader
100+ // actually took place. Likely because Loader waits until the next event loop
101+ // iteration to do its work. So to ensure the reload, we will wait until the
102+ // Shell instance gets destroyed.
103+ tryCompare(tabletStageLoader, "itemDestroyed", true);
104+
105+ // kill all (fake) running apps
106+ webbrowserCheckBox.checked = false;
107+ dialerCheckBox.checked = false;
108+ }
109+
110+ function waitUntilAppSurfaceShowsUp(appId) {
111+ var appWindow = findChild(tabletStage, "appWindow_" + appId);
112+ verify(appWindow);
113+ var appWindowStates = findInvisibleChild(appWindow, "applicationWindowStateGroup");
114+ verify(appWindowStates);
115+ tryCompare(appWindowStates, "state", "surface");
116+ }
117+
118+ function test_tappingSwitchesFocusBetweenStages() {
119+ webbrowserCheckBox.checked = true;
120+ waitUntilAppSurfaceShowsUp(webbrowserCheckBox.appId);
121+ var webbrowserApp = ApplicationManager.findApplication(webbrowserCheckBox.appId);
122+ compare(webbrowserApp.stage, ApplicationInfoInterface.MainStage);
123+ tryCompare(webbrowserApp.session.surface, "activeFocus", true);
124+
125+ dialerCheckBox.checked = true;
126+ waitUntilAppSurfaceShowsUp(dialerCheckBox.appId);
127+ var dialerApp = ApplicationManager.findApplication(dialerCheckBox.appId);
128+ compare(dialerApp.stage, ApplicationInfoInterface.SideStage);
129+ tryCompare(dialerApp.session.surface, "activeFocus", true);
130+ tryCompare(webbrowserApp.session.surface, "activeFocus", false);
131+
132+ // Tap on the main stage application and check if the focus
133+ // has been passed to it.
134+
135+ var webbrowserWindow = findChild(tabletStage, "appWindow_" + webbrowserApp.appId);
136+ verify(webbrowserWindow);
137+ tap(webbrowserWindow);
138+
139+ tryCompare(dialerApp.session.surface, "activeFocus", false);
140+ tryCompare(webbrowserApp.session.surface, "activeFocus", true);
141+
142+ // Now tap on the side stage application and check if the focus
143+ // has been passed back to it.
144+
145+ var dialerWindow = findChild(tabletStage, "appWindow_" + dialerApp.appId);
146+ verify(dialerWindow);
147+ tap(dialerWindow);
148+
149+ tryCompare(dialerApp.session.surface, "activeFocus", true);
150+ tryCompare(webbrowserApp.session.surface, "activeFocus", false);
151+ }
152+ }
153+
154+}
tests/qmltests/Stages/tst_WindowMoveResizeArea.qml+117−18
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright 2014 Canonical Ltd.
2+ * Copyright 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
@@ -29,20 +29,46 @@ Item {
29 height: units.gu(60)
29 height: units.gu(60)
30 width: units.gu(60)
30 width: units.gu(60)
31
31
32− Rectangle {
32+ property var fakeWindow: windowLoader.item
33+
34+ Component {
35+ id: fakeWindowComponent
36+
37+ Item {
33− id: fakeWindow
38+ id: fakeWindow
39+ property alias minWidth: moveResizeArea.minWidth
40+ property alias minHeight: moveResizeArea.minHeight
41+ x: units.gu(20)
42+ y: units.gu(20)
34− height: units.gu(20)
43+ height: units.gu(20)
35− width: units.gu(20)
44+ width: units.gu(20)
36− color: "khaki"
45+
37− WindowMoveResizeArea {
46+ WindowMoveResizeArea {
38− id: moveResizeArea
47+ id: moveResizeArea
39− target: fakeWindow
48+ target: fakeWindow
40− resizeHandleWidth: units.gu(0.5)
49+ resizeHandleWidth: units.gu(0.5)
41− minWidth: units.gu(15)
50+ minWidth: units.gu(15)
42− minHeight: units.gu(10)
51+ minHeight: units.gu(10)
52+ windowId: "test-window-id"
53+ }
54+
55+ Rectangle {
56+ anchors.fill: moveResizeArea
57+ color: "red"
58+ }
59+
60+ Rectangle {
61+ anchors.fill: fakeWindow
62+ color: "blue"
63+ }
43 }
64 }
44 }
65 }
45
66
67+ Loader {
68+ id: windowLoader
69+ sourceComponent: fakeWindowComponent
70+ }
71+
46 UT.UnityTestCase {
72 UT.UnityTestCase {
47 when: windowShown
73 when: windowShown
48
74
@@ -100,8 +126,8 @@ Item {
100 var startDragY = initialWindowY + initialWindowHeight + 1
126 var startDragY = initialWindowY + initialWindowHeight + 1
101 mouseFlick(root, startDragX, startDragY, startDragX + data.dx, startDragY + data.dy, true, true, units.gu(.5), 10);
127 mouseFlick(root, startDragX, startDragY, startDragX + data.dx, startDragY + data.dy, true, true, units.gu(.5), 10);
102
128
103− tryCompare(fakeWindow, "width", Math.max(initialWindowWidth + data.dx, moveResizeArea.minWidth));
129+ tryCompare(fakeWindow, "width", Math.max(initialWindowWidth + data.dx, fakeWindow.minWidth));
104− tryCompare(fakeWindow, "height", Math.max(initialWindowHeight + data.dy, moveResizeArea.minHeight));
130+ tryCompare(fakeWindow, "height", Math.max(initialWindowHeight + data.dy, fakeWindow.minHeight));
105
131
106 compare(fakeWindow.x, initialWindowX);
132 compare(fakeWindow.x, initialWindowX);
107 compare(fakeWindow.y, initialWindowY);
133 compare(fakeWindow.y, initialWindowY);
@@ -126,13 +152,86 @@ Item {
126 var startDragY = initialWindowY - 1
152 var startDragY = initialWindowY - 1
127 mouseFlick(root, startDragX, startDragY, startDragX + data.dx, startDragY + data.dy, true, true, units.gu(.5), 10);
153 mouseFlick(root, startDragX, startDragY, startDragX + data.dx, startDragY + data.dy, true, true, units.gu(.5), 10);
128
154
129− tryCompare(fakeWindow, "width", Math.max(initialWindowWidth - data.dx, moveResizeArea.minWidth));
155+ tryCompare(fakeWindow, "width", Math.max(initialWindowWidth - data.dx, fakeWindow.minWidth));
130− tryCompare(fakeWindow, "height", Math.max(initialWindowHeight - data.dy, moveResizeArea.minHeight));
156+ tryCompare(fakeWindow, "height", Math.max(initialWindowHeight - data.dy, fakeWindow.minHeight));
131
157
132− var maxMoveX = initialWindowWidth - moveResizeArea.minWidth;
158+ var maxMoveX = initialWindowWidth - fakeWindow.minWidth;
133− var maxMoveY = initialWindowHeight - moveResizeArea.minHeight;
159+ var maxMoveY = initialWindowHeight - fakeWindow.minHeight;
134 compare(fakeWindow.x, Math.min(initialWindowX + data.dx, initialWindowX + maxMoveX));
160 compare(fakeWindow.x, Math.min(initialWindowX + data.dx, initialWindowX + maxMoveX));
135 compare(fakeWindow.y, Math.min(initialWindowY + data.dy, initialWindowY + maxMoveY));
161 compare(fakeWindow.y, Math.min(initialWindowY + data.dy, initialWindowY + maxMoveY));
136 }
162 }
163+
164+ function test_saveRestorePosition() {
165+ var initialWindowX = fakeWindow.x;
166+ var initialWindowY = fakeWindow.y;
167+ var initialWindowWidth = fakeWindow.width;
168+ var initialWindowHeight = fakeWindow.height;
169+
170+ var moveDelta = units.gu(5);
171+ var startDragX = initialWindowX + fakeWindow.width / 2;
172+ var startDragY = initialWindowY + fakeWindow.height / 2;
173+ mouseFlick(root, startDragX, startDragY, startDragX + moveDelta, startDragY + moveDelta, true, true, units.gu(.5), 10)
174+
175+ tryCompare(fakeWindow, "x", initialWindowX + moveDelta)
176+ tryCompare(fakeWindow, "y", initialWindowX + moveDelta)
177+
178+ // This will destroy the window and recreate it
179+ windowLoader.active = false;
180+ waitForRendering(root);
181+ windowLoader.active = true;
182+
183+ // Make sure it's again where we left it before destroying
184+ tryCompare(fakeWindow, "x", initialWindowX + moveDelta)
185+ tryCompare(fakeWindow, "y", initialWindowX + moveDelta)
186+ }
187+
188+ function test_saveRestoreSize() {
189+ var initialWindowX = fakeWindow.x;
190+ var initialWindowY = fakeWindow.y;
191+ var initialWindowWidth = fakeWindow.width
192+ var initialWindowHeight = fakeWindow.height
193+
194+ var resizeDelta = units.gu(5)
195+ var startDragX = initialWindowX + initialWindowWidth + 1
196+ var startDragY = initialWindowY + initialWindowHeight + 1
197+ mouseFlick(root, startDragX, startDragY, startDragX + resizeDelta, startDragY + resizeDelta, true, true, units.gu(.5), 10);
198+
199+ tryCompare(fakeWindow, "width", Math.max(initialWindowWidth + resizeDelta, fakeWindow.minWidth));
200+ tryCompare(fakeWindow, "height", Math.max(initialWindowHeight + resizeDelta, fakeWindow.minHeight));
201+
202+ // This will destroy the window and recreate it
203+ windowLoader.active = false;
204+ waitForRendering(root);
205+ windowLoader.active = true;
206+
207+ // Make sure its size is again the same as before
208+ tryCompare(fakeWindow, "width", Math.max(initialWindowWidth + resizeDelta, fakeWindow.minWidth));
209+ tryCompare(fakeWindow, "height", Math.max(initialWindowHeight + resizeDelta, fakeWindow.minHeight));
210+ }
211+
212+ // This tests if dragging smaller than minSize and then larger again, will keep the edge sticking
213+ // to the mouse, instead of immediately making the window grow again when switching direction
214+ function test_resizeSmallerAndLarger_data() {
215+ return [
216+ { tag: "topLeft", startX: -1, startY: -1, dx: units.gu(15), dy: units.gu(15) },
217+ { tag: "bottomRight", startX: fakeWindow.width + 1, startY: fakeWindow.height + 1, dx: -units.gu(15), dy: -units.gu(15) }
218+ ]
219+ }
220+
221+ function test_resizeSmallerAndLarger(data) {
222+ var initialWindowX = fakeWindow.x;
223+ var initialWindowY = fakeWindow.y;
224+ var initialWindowWidth = fakeWindow.width
225+ var initialWindowHeight = fakeWindow.height
226+
227+ var startDragX = initialWindowX + data.startX
228+ var startDragY = initialWindowY + data.startY
229+ mouseFlick(root, startDragX, startDragY, startDragX + data.dx, startDragY + data.dy, true, false, units.gu(.05), 10);
230+ tryCompare(fakeWindow, "width", Math.max(initialWindowWidth - Math.abs(data.dx), fakeWindow.minWidth));
231+ tryCompare(fakeWindow, "height", Math.max(initialWindowHeight - Math.abs(data.dy), fakeWindow.minHeight));
232+ mouseFlick(root, startDragX + data.dx, startDragY + data.dy, startDragX, startDragY, false, true, units.gu(.05), 10);
233+ tryCompare(fakeWindow, "width", initialWindowWidth);
234+ tryCompare(fakeWindow, "height", initialWindowHeight);
235+ }
137 }
236 }
138}
237}
tests/qmltests/Tutorial/tst_Tutorial.qmlnew file+348
@@ -0,0 +1,348 @@
1+/*
2+ * Copyright (C) 2013,2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import QtTest 1.0
19+import AccountsService 0.1
20+import LightDM 0.1 as LightDM
21+import Ubuntu.Components 1.1
22+import Unity.Application 0.1
23+import Unity.Test 0.1 as UT
24+
25+import "../../../qml"
26+
27+Item {
28+ id: root
29+ width: shellLoader.width + buttons.width
30+ height: shellLoader.height
31+
32+ QtObject {
33+ id: applicationArguments
34+
35+ function hasGeometry() {
36+ return false;
37+ }
38+
39+ function width() {
40+ return 0;
41+ }
42+
43+ function height() {
44+ return 0;
45+ }
46+ }
47+
48+ Component.onCompleted: {
49+ // must set the mock mode before loading the Shell
50+ LightDM.Greeter.mockMode = "single-pin";
51+ LightDM.Users.mockMode = "single-pin";
52+ shellLoader.active = true;
53+ }
54+
55+ Row {
56+ spacing: 0
57+ anchors.fill: parent
58+
59+ Loader {
60+ id: shellLoader
61+
62+ active: false
63+ width: units.gu(40)
64+ height: units.gu(71)
65+
66+ property bool itemDestroyed: false
67+ sourceComponent: Component {
68+ Shell {
69+ property string indicatorProfile: "phone"
70+
71+ Component.onDestruction: {
72+ shellLoader.itemDestroyed = true;
73+ }
74+ }
75+ }
76+ }
77+
78+ Rectangle {
79+ id: buttons
80+ color: "white"
81+ width: units.gu(30)
82+ height: shellLoader.height
83+
84+ Column {
85+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
86+ spacing: units.gu(1)
87+ Row {
88+ anchors { left: parent.left; right: parent.right }
89+ Button {
90+ text: "Restart Tutorial"
91+ onClicked: {
92+ if (shellLoader.status !== Loader.Ready)
93+ return;
94+
95+ AccountsService.demoEdges = false;
96+ AccountsService.demoEdges = true;
97+ }
98+ }
99+ }
100+ }
101+ }
102+ }
103+
104+ UT.UnityTestCase {
105+ id: testCase
106+ name: "Tutorial"
107+ when: windowShown
108+
109+ property Item shell: shellLoader.status === Loader.Ready ? shellLoader.item : null
110+ property real halfWidth: shell ? shell.width / 2 : 0
111+ property real halfHeight: shell ? shell.height / 2 : 0
112+
113+ function init() {
114+ tryCompare(shell, "enabled", true); // enabled by greeter when ready
115+ swipeAwayGreeter();
116+ AccountsService.demoEdges = false;
117+ AccountsService.demoEdges = true;
118+ }
119+
120+ function cleanup() {
121+ shellLoader.itemDestroyed = false;
122+
123+ shellLoader.active = false;
124+
125+ tryCompare(shellLoader, "status", Loader.Null);
126+ tryCompare(shellLoader, "item", null);
127+ // Loader.status might be Loader.Null and Loader.item might be null but the Loader
128+ // item might still be alive. So if we set Loader.active back to true
129+ // again right now we will get the very same Shell instance back. So no reload
130+ // actually took place. Likely because Loader waits until the next event loop
131+ // iteration to do its work. So to ensure the reload, we will wait until the
132+ // Shell instance gets destroyed.
133+ tryCompare(shellLoader, "itemDestroyed", true);
134+
135+ // kill all (fake) running apps
136+ killApps();
137+
138+ // reload our test subject to get it in a fresh state once again
139+ shellLoader.active = true;
140+
141+ tryCompare(shellLoader, "status", Loader.Ready);
142+ removeTimeConstraintsFromDirectionalDragAreas(shellLoader.item);
143+ }
144+
145+ function killApps() {
146+ while (ApplicationManager.count > 1) {
147+ var appIndex = ApplicationManager.get(0).appId == "unity8-dash" ? 1 : 0
148+ ApplicationManager.stopApplication(ApplicationManager.get(appIndex).appId);
149+ }
150+ compare(ApplicationManager.count, 1)
151+ }
152+
153+ function swipeAwayGreeter() {
154+ var greeter = findChild(shell, "greeter");
155+ tryCompare(greeter, "showProgress", 1);
156+
157+ touchFlick(shell, halfWidth, halfHeight, shell.width, halfHeight);
158+
159+ // wait until the animation has finished
160+ tryCompare(greeter, "showProgress", 0);
161+ waitForRendering(greeter);
162+ }
163+
164+ function waitForPage(name) {
165+ waitForRendering(findChild(shell, name));
166+ var page = findChild(shell, name);
167+ tryCompare(page, "shown", true);
168+ tryCompare(page.showAnimation, "running", false);
169+ return page;
170+ }
171+
172+ function checkTopEdge() {
173+ touchFlick(shell, halfWidth, 0, halfWidth, halfHeight);
174+
175+ var panel = findChild(shell, "panel");
176+ tryCompare(panel.indicators, "fullyClosed", true);
177+ }
178+
179+ function checkLeftEdge() {
180+ touchFlick(shell, 0, halfHeight, halfWidth, halfHeight);
181+
182+ var launcher = findChild(shell, "launcher");
183+ tryCompare(launcher, "state", "");
184+ }
185+
186+ function checkRightEdge() {
187+ touchFlick(shell, shell.width, halfHeight, halfWidth, halfHeight);
188+
189+ var stage = findChild(shell, "stage");
190+ var spreadView = findChild(stage, "spreadView");
191+ tryCompare(spreadView, "phase", 0);
192+ }
193+
194+ function checkBottomEdge() {
195+ // Can't actually check effect of swipe, since dash isn't really loaded
196+ var applicationsDisplayLoader = findChild(shell, "applicationsDisplayLoader");
197+ tryCompare(applicationsDisplayLoader.item, "interactive", false);
198+ }
199+
200+ function checkFinished() {
201+ tryCompare(AccountsService, "demoEdges", false);
202+
203+ var tutorial = findChild(shell, "tutorial");
204+ tryCompare(tutorial, "running", false);
205+
206+ var launcher = findChild(shell, "launcher");
207+ tryCompare(launcher, "shown", false);
208+ }
209+
210+ function goToPage(name) {
211+ var page = waitForPage("tutorialLeft");
212+ checkTopEdge();
213+ checkRightEdge();
214+ checkBottomEdge();
215+ if (name === "tutorialLeft") return page;
216+ touchFlick(shell, 0, halfHeight, halfWidth, halfHeight);
217+
218+ page = waitForPage("tutorialLeftFinish");
219+ if (name === "tutorialLeftFinish") return page;
220+ var tick = findChild(page, "tick");
221+ tap(tick);
222+
223+ page = waitForPage("tutorialRight");
224+ checkTopEdge();
225+ checkLeftEdge();
226+ checkBottomEdge();
227+ if (name === "tutorialRight") return page;
228+ touchFlick(shell, shell.width, halfHeight, halfWidth, halfHeight);
229+ var overlay = findChild(page, "overlay");
230+ tryCompare(overlay, "shown", true);
231+ var tick = findChild(page, "tick");
232+ tap(tick);
233+
234+ var page = waitForPage("tutorialBottom");
235+ checkTopEdge();
236+ checkLeftEdge();
237+ checkRightEdge();
238+ if (name === "tutorialBottom") return page;
239+ touchFlick(shell, halfWidth, shell.height, halfWidth, halfHeight);
240+
241+ var page = waitForPage("tutorialBottomFinish");
242+ checkTopEdge();
243+ checkLeftEdge();
244+ checkRightEdge();
245+ checkBottomEdge();
246+ if (name === "tutorialBottomFinish") return page;
247+ var tick = findChild(page, "tick");
248+ tap(tick);
249+
250+ checkFinished();
251+ return null;
252+ }
253+
254+ function test_walkthrough() {
255+ goToPage(null);
256+ }
257+
258+ function test_launcherShortDrag() {
259+ // goToPage does a normal launcher pull. But here we want to test
260+ // just barely pulling the launcher out and letting go (i.e. not
261+ // triggering the "progress" property of Launcher).
262+
263+ var left = goToPage("tutorialLeft");
264+
265+ // Make sure we don't do anything if we don't pull the launcher
266+ // out much.
267+ var launcher = findChild(shell, "launcher");
268+ touchFlick(shell, 0, halfHeight, launcher.panelWidth * 0.4, halfHeight);
269+ tryCompare(launcher, "state", ""); // should remain hidden
270+ tryCompare(left, "shown", true); // and we should still be on left
271+
272+ // Now drag out but not past launcher itself
273+ touchFlick(shell, 0, halfHeight, launcher.panelWidth * 0.9, halfHeight);
274+
275+ waitForPage("tutorialLeftFinish");
276+ }
277+
278+ function test_launcherLongDrag() {
279+ // goToPage does a normal launcher pull. But here we want to test
280+ // a full pull across the page.
281+
282+ var left = goToPage("tutorialLeft");
283+
284+ var launcher = findChild(shell, "launcher");
285+ touchFlick(shell, 0, halfHeight, shell.width, halfHeight);
286+
287+ var errorTextLabel = findChild(left, "errorTextLabel");
288+ var errorTitleLabel = findChild(left, "errorTitleLabel");
289+ tryCompare(launcher, "state", ""); // launcher goes away
290+ tryCompare(left, "shown", true); // still on left page
291+ tryCompare(errorTextLabel, "opacity", 1); // show error
292+ tryCompare(errorTitleLabel, "opacity", 1); // show error
293+ }
294+
295+ function test_launcherDragBack() {
296+ // goToPage does a full launcher pull. But here we test pulling
297+ // all the way out, then dragging back into place.
298+
299+ var left = goToPage("tutorialLeft");
300+ touchFlick(shell, 0, halfHeight, halfWidth, halfHeight, true, false);
301+ touchFlick(shell, halfWidth, halfHeight, 0, halfHeight, false, true);
302+
303+ tryCompare(left, "shown", true); // and we should still be on left
304+ }
305+
306+ function test_spread() {
307+ // Unfortunately, most of what we want to test of the spread is
308+ // "did it render correctly?" but that's hard to test. So instead,
309+ // just poke and prod it a little bit to see if some of the values
310+ // we'd expect to be correct, are so.
311+
312+ var right = goToPage("tutorialRight");
313+ var stage = findChild(right, "stage");
314+ var delegate0 = findChild(right, "appDelegate0");
315+
316+ tryCompare(stage, "dragProgress", 0);
317+ touchFlick(shell, shell.width, halfHeight, shell.width * 0.8, halfHeight, true, false);
318+ verify(stage.dragProgress > 0);
319+ compare(stage.dragProgress, -delegate0.xTranslate);
320+ touchFlick(shell, shell.width * 0.8, halfHeight, shell.width, halfHeight, false, true);
321+ tryCompare(stage, "dragProgress", 0);
322+
323+ tryCompare(delegate0, "x", shell.width);
324+
325+ var screenshotImage = findChild(right, "screenshotImage");
326+ tryCompare(screenshotImage, "source", Qt.resolvedUrl("../../../qml/Tutorial/graphics/facebook.png"));
327+ tryCompare(screenshotImage, "visible", true);
328+ }
329+
330+ function test_bottomShortDrag() {
331+ var bottom = goToPage("tutorialBottom");
332+
333+ touchFlick(shell, halfWidth, shell.height, halfWidth, shell.height * 0.8);
334+
335+ var errorTextLabel = findChild(bottom, "errorTextLabel");
336+ var errorTitleLabel = findChild(bottom, "errorTitleLabel");
337+ tryCompare(bottom, "shown", true); // still on bottom page
338+ tryCompare(errorTextLabel, "opacity", 1); // show error
339+ tryCompare(errorTitleLabel, "opacity", 1); // show error
340+ }
341+
342+ function test_interrupted() {
343+ goToPage("tutorialLeft");
344+ ApplicationManager.startApplication("dialer-app");
345+ checkFinished();
346+ }
347+ }
348+}
tests/qmltests/tst_Shell.qml+55−5
@@ -25,6 +25,7 @@ import Ubuntu.Components 1.1
25import Ubuntu.Telephony 0.1 as Telephony
25import Ubuntu.Telephony 0.1 as Telephony
26import Unity.Application 0.1
26import Unity.Application 0.1
27import Unity.Connectivity 0.1
27import Unity.Connectivity 0.1
28+import Unity.Indicators 0.1
28import Unity.Notifications 1.0
29import Unity.Notifications 1.0
29import Unity.Test 0.1 as UT
30import Unity.Test 0.1 as UT
30import Powerd 0.1
31import Powerd 0.1
@@ -63,6 +64,8 @@ Item {
63 anchors.fill: parent
64 anchors.fill: parent
64 Loader {
65 Loader {
65 id: shellLoader
66 id: shellLoader
67+ focus: true
68+
66 active: false
69 active: false
67 property bool itemDestroyed: false
70 property bool itemDestroyed: false
68 sourceComponent: Component {
71 sourceComponent: Component {
@@ -88,6 +91,7 @@ Item {
88 anchors { left: parent.left; right: parent.right }
91 anchors { left: parent.left; right: parent.right }
89 Button {
92 Button {
90 text: "Show Greeter"
93 text: "Show Greeter"
94+ activeFocusOnPress: false
91 onClicked: {
95 onClicked: {
92 if (shellLoader.status !== Loader.Ready)
96 if (shellLoader.status !== Loader.Ready)
93 return;
97 return;
@@ -169,6 +173,7 @@ Item {
169
173
170 function init() {
174 function init() {
171 tryCompare(shell, "enabled", true); // enabled by greeter when ready
175 tryCompare(shell, "enabled", true); // enabled by greeter when ready
176+ shell.indicatorProfile = "phone";
172
177
173 swipeAwayGreeter();
178 swipeAwayGreeter();
174
179
@@ -434,11 +439,11 @@ Item {
434 waitUntilTransitionsEnd(appWindowStateGroup);
439 waitUntilTransitionsEnd(appWindowStateGroup);
435 }
440 }
436
441
437− function test_surfaceLosesFocusWhilePanelIsOpen() {
442+ function test_surfaceLosesActiveFocusWhilePanelIsOpen() {
438 var app = ApplicationManager.startApplication("dialer-app");
443 var app = ApplicationManager.startApplication("dialer-app");
439 waitUntilAppWindowIsFullyLoaded(app);
444 waitUntilAppWindowIsFullyLoaded(app);
440
445
441− tryCompare(app.session.surface, "focus", true);
446+ tryCompare(app.session.surface, "activeFocus", true);
442
447
443 // Drag the indicators panel half-open
448 // Drag the indicators panel half-open
444 var touchX = shell.width / 2;
449 var touchX = shell.width / 2;
@@ -449,7 +454,7 @@ Item {
449 true /* beginTouch */, false /* endTouch */);
454 true /* beginTouch */, false /* endTouch */);
450 verify(indicators.partiallyOpened);
455 verify(indicators.partiallyOpened);
451
456
452− tryCompare(app.session.surface, "focus", false);
457+ tryCompare(app.session.surface, "activeFocus", false);
453
458
454 // And finish getting it open
459 // And finish getting it open
455 touchFlick(indicators,
460 touchFlick(indicators,
@@ -458,11 +463,21 @@ Item {
458 false /* beginTouch */, true /* endTouch */);
463 false /* beginTouch */, true /* endTouch */);
459 tryCompare(indicators, "fullyOpened", true);
464 tryCompare(indicators, "fullyOpened", true);
460
465
461− tryCompare(app.session.surface, "focus", false);
466+ tryCompare(app.session.surface, "activeFocus", false);
462
467
463 dragToCloseIndicatorsPanel();
468 dragToCloseIndicatorsPanel();
464
469
465− tryCompare(app.session.surface, "focus", true);
470+ tryCompare(app.session.surface, "activeFocus", true);
471+ }
472+
473+ function test_launchedAppHasActiveFocus() {
474+ var dialerApp = ApplicationManager.startApplication("dialer-app");
475+ verify(dialerApp);
476+ waitUntilAppSurfaceShowsUp("dialer-app")
477+
478+ verify(dialerApp.session.surface);
479+
480+ tryCompare(dialerApp.session.surface, "activeFocus", true);
466 }
481 }
467
482
468 // Wait for the whole UI to settle down
483 // Wait for the whole UI to settle down
@@ -477,6 +492,14 @@ Item {
477 waitForRendering(shell)
492 waitForRendering(shell)
478 }
493 }
479
494
495+ function waitUntilAppSurfaceShowsUp(appId) {
496+ var appWindow = findChild(shell, "appWindow_" + appId);
497+ verify(appWindow);
498+ var appWindowStates = findInvisibleChild(appWindow, "applicationWindowStateGroup");
499+ verify(appWindowStates);
500+ tryCompare(appWindowStates, "state", "surface");
501+ }
502+
480 function dragToCloseIndicatorsPanel() {
503 function dragToCloseIndicatorsPanel() {
481 var indicators = findChild(shell, "indicators");
504 var indicators = findChild(shell, "indicators");
482
505
@@ -546,6 +569,33 @@ Item {
546 && itemRectInShell.y + itemRectInShell.height <= shell.height;
569 && itemRectInShell.y + itemRectInShell.height <= shell.height;
547 }
570 }
548
571
572+ function test_greeterDoesNotChangeIndicatorProfile() {
573+ var panel = findChild(shell, "panel");
574+ tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile);
575+
576+ LightDM.Greeter.showGreeter();
577+ tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile);
578+
579+ LightDM.Greeter.hideGreeter();
580+ tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile);
581+ }
582+
583+ function test_shellProfileChangesReachIndicators() {
584+ var panel = findChild(shell, "panel");
585+
586+ shell.indicatorProfile = "test1";
587+ for (var i = 0; i < panel.indicators.indicatorsModel.count; ++i) {
588+ var properties = panel.indicators.indicatorsModel.data(i, IndicatorsModelRole.IndicatorProperties);
589+ verify(properties["menuObjectPath"].substr(-5), "test1");
590+ }
591+
592+ shell.indicatorProfile = "test2";
593+ for (var i = 0; i < panel.indicators.indicatorsModel.count; ++i) {
594+ var properties = panel.indicators.indicatorsModel.data(i, IndicatorsModelRole.IndicatorProperties);
595+ verify(properties["menuObjectPath"].substr(-5), "test2");
596+ }
597+ }
598+
549 function test_focusRequestedHidesGreeter() {
599 function test_focusRequestedHidesGreeter() {
550 var greeter = findChild(shell, "greeter");
600 var greeter = findChild(shell, "greeter");
551
601
tests/qmltests/tst_ShellWithPin.qml+16
@@ -232,6 +232,22 @@ Item {
232 tryCompare(ApplicationManager, "focusedApplicationId", app)
232 tryCompare(ApplicationManager, "focusedApplicationId", app)
233 }
233 }
234
234
235+ function test_greeterChangesIndicatorProfile() {
236+ skip("Not supported yet, waiting on design for new settings panel");
237+
238+ var panel = findChild(shell, "panel");
239+ tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile + "_greeter");
240+
241+ LightDM.Greeter.hideGreeter();
242+ tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile);
243+
244+ LightDM.Greeter.showGreeter();
245+ tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile + "_greeter");
246+
247+ LightDM.Greeter.hideGreeter();
248+ tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile);
249+ }
250+
235 function test_login() {
251 function test_login() {
236 sessionSpy.clear()
252 sessionSpy.clear()
237 tryCompare(sessionSpy, "count", 0)
253 tryCompare(sessionSpy, "count", 0)
tests/qmltests/tst_TabletShell.qml+2−2
@@ -303,8 +303,8 @@ Row {
303 selectUser(data.user)
303 selectUser(data.user)
304
304
305 AccountsService.demoEdges = data.demo
305 AccountsService.demoEdges = data.demo
306− var edgeDemo = findChild(shell, "edgeDemo")
306+ var tutorial = findChild(shell, "tutorial");
307− tryCompare(edgeDemo, "running", data.demo)
307+ tryCompare(tutorial, "running", data.demo);
308
308
309 swipeFromLeftEdge(shell.width * 0.75)
309 swipeFromLeftEdge(shell.width * 0.75)
310 wait(500) // to give time to handle dash() signal from Launcher
310 wait(500) // to give time to handle dash() signal from Launcher
tests/uqmlscene/ActiveFocusLogger.cppnew file+48
@@ -0,0 +1,48 @@
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+#include "ActiveFocusLogger.h"
18+
19+#include <QDebug>
20+#include <QQuickItem>
21+
22+void ActiveFocusLogger::setWindow(QQuickWindow *window)
23+{
24+ m_window = window;
25+ QObject::connect(window, &QQuickWindow::activeFocusItemChanged,
26+ this, &ActiveFocusLogger::printActiveFocusInfo);
27+}
28+
29+void ActiveFocusLogger::printActiveFocusInfo()
30+{
31+ if (!m_window) {
32+ return;
33+ }
34+
35+ qDebug() << "============== Active focus info START ================";
36+ if (m_window->activeFocusItem()) {
37+ qDebug() << m_window->activeFocusItem();
38+ qDebug() << "Ancestry:";
39+ QQuickItem *item = m_window->activeFocusItem()->parentItem();
40+ while (item != nullptr) {
41+ qDebug() << item << ", isFocusScope =" << item->isFocusScope();
42+ item = item->parentItem();
43+ }
44+ } else {
45+ qDebug() << "NULL";
46+ }
47+ qDebug() << "============== Active focus info END ================";
48+}
tests/uqmlscene/ActiveFocusLogger.hnew file+37
@@ -0,0 +1,37 @@
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 ACTIVE_FOCUS_LOGGER_H
18+#define ACTIVE_FOCUS_LOGGER_H
19+
20+#include <QObject>
21+#include <QQuickWindow>
22+#include <QPointer>
23+
24+class ActiveFocusLogger : public QObject {
25+ Q_OBJECT
26+
27+public:
28+ void setWindow(QQuickWindow *window);
29+
30+private Q_SLOTS:
31+ void printActiveFocusInfo();
32+
33+private:
34+ QPointer<QQuickWindow> m_window;
35+};
36+
37+#endif // ACTIVE_FOCUS_LOGGER_H
tests/uqmlscene/CMakeLists.txt+1
@@ -1,5 +1,6 @@
1add_executable(uqmlscene
1add_executable(uqmlscene
2 ${shellapplication_MOC_SRCS}
2 ${shellapplication_MOC_SRCS}
3+ ActiveFocusLogger.cpp
3 main.cpp
4 main.cpp
4 ${CMAKE_SOURCE_DIR}/src/MouseTouchAdaptor.cpp
5 ${CMAKE_SOURCE_DIR}/src/MouseTouchAdaptor.cpp
5 )
6 )
tests/uqmlscene/main.cpp+13
@@ -68,6 +68,12 @@
68// UbuntuGestures lib
68// UbuntuGestures lib
69#include <TouchRegistry.h>
69#include <TouchRegistry.h>
70
70
71+#define UQMLSCENE_DEBUG_ACTIVE_FOCUS 0
72+
73+#if UQMLSCENE_DEBUG_ACTIVE_FOCUS
74+ #include "ActiveFocusLogger.h"
75+#endif
76+
71#ifdef QML_RUNTIME_TESTING
77#ifdef QML_RUNTIME_TESTING
72class RenderStatistics
78class RenderStatistics
73{
79{
@@ -378,6 +384,10 @@ int main(int argc, char ** argv)
378{
384{
379 Options options;
385 Options options;
380
386
387+ #if UQMLSCENE_DEBUG_ACTIVE_FOCUS
388+ ActiveFocusLogger activeFocusLogger;
389+ #endif
390+
381 QStringList imports;
391 QStringList imports;
382 QList<QPair<QString, QString> > bundles;
392 QList<QPair<QString, QString> > bundles;
383 for (int i = 1; i < argc; ++i) {
393 for (int i = 1; i < argc; ++i) {
@@ -503,6 +513,9 @@ int main(int argc, char ** argv)
503 QQuickItem *contentItem = qobject_cast<QQuickItem *>(topLevel);
513 QQuickItem *contentItem = qobject_cast<QQuickItem *>(topLevel);
504 if (contentItem) {
514 if (contentItem) {
505 qxView = new QQuickView(&engine, nullptr);
515 qxView = new QQuickView(&engine, nullptr);
516+ #if UQMLSCENE_DEBUG_ACTIVE_FOCUS
517+ activeFocusLogger.setWindow(qxView);
518+ #endif
506 TouchRegistry::instance()->setParent(qxView);
519 TouchRegistry::instance()->setParent(qxView);
507 qxView->installEventFilter(TouchRegistry::instance());
520 qxView->installEventFilter(TouchRegistry::instance());
508 window = qxView;
521 window = qxView;