on xenial help sign in
Merge from trunk
commit7b1fb78ff4a6a9d1c0824e29d014cf171ab8ab04 authorMichael Terry <michael.terry@canonical.com> dateThu 12 Feb 2015, 10:45 (12 years ago) parent0c32f9fe0f1af4
92 files changed+3835−462
debian/changelog+30−21 debian/control+1−1 debian/unity8-private.install+1 plugins/AccountsService/AccountsServiceDBusAdaptor.cpp+9 plugins/AccountsService/AccountsServiceDBusAdaptor.h+12 plugins/AccountsService/com.canonical.unity.AccountsService.xml+1−2 plugins/CMakeLists.txt+1 plugins/Greeter/CMakeLists.txt+6 plugins/Greeter/Unity/CMakeLists.txt+1 plugins/Greeter/Unity/Launcher/CMakeLists.txt+38 plugins/Greeter/Unity/Launcher/Launcher.qmltypes+179 plugins/Greeter/Unity/Launcher/launcheritem.cpp+171 plugins/Greeter/Unity/Launcher/launcheritem.h+74 plugins/Greeter/Unity/Launcher/launchermodelas.cpp+277 plugins/Greeter/Unity/Launcher/launchermodelas.h+75 plugins/Greeter/Unity/Launcher/plugin.cpp+46 plugins/Greeter/Unity/Launcher/plugin.h+32 plugins/Greeter/Unity/Launcher/qmldir+3 plugins/Greeter/Unity/Launcher/quicklistentry.cpp+56 plugins/Greeter/Unity/Launcher/quicklistentry.h+43 plugins/Greeter/Unity/Launcher/quicklistmodel.cpp+70 plugins/Greeter/Unity/Launcher/quicklistmodel.h+55 plugins/Unity/Indicators/indicator.cpp+23−8 plugins/Unity/Indicators/indicator.h+5−1 plugins/Unity/Indicators/indicatorsmanager.cpp+17−3 plugins/Unity/Indicators/indicatorsmanager.h+6−1 plugins/Unity/Indicators/indicatorsmodel.cpp+25−2 plugins/Unity/Indicators/indicatorsmodel.h+6−1 plugins/Unity/Indicators/modelactionrootstate.cpp+4−2 plugins/Unity/Indicators/unitymenumodelcache.cpp+8−11 plugins/Unity/Indicators/unitymenumodelcache.h+2−2 plugins/Unity/Launcher/CMakeLists.txt+2−1 plugins/Unity/Launcher/asadapter.cpp+60 tests/mocks/Unity/Notifications/MockNotificationTypes.h → plugins/Unity/Launcher/asadapter.h+24−14 plugins/Unity/Launcher/launchermodel.cpp+20 plugins/Unity/Launcher/launchermodel.h+5 plugins/Utils/CMakeLists.txt+2−1 plugins/Utils/plugin.cpp+9 plugins/Utils/windowstatestorage.cpp+96 plugins/Utils/windowstatestorage.h+38 po/be.po+7−7 po/gd.po+45−40 po/gl.po+7−7 po/oc.po+521 qml/Dash/GenericScopeView.qml+1 qml/Dash/PageHeader.qml+2 qml/Greeter/CoverPage.qml+1 qml/Launcher/LauncherPanel.qml+7−5 qml/Notifications/Notification.qml+36−6 qml/Notifications/Notifications.qml+2−6 qml/Panel/Panel.qml+5−3 qml/Shell.qml+3−1 qml/Stages/DesktopStage.qml+2−2 qml/Stages/WindowMoveResizeArea.qml+33−24 tests/mocks/Unity/Indicators/fakeindicatorsmodel.cpp+13−1 tests/mocks/Unity/Indicators/fakeindicatorsmodel.h+6 tests/mocks/Unity/Launcher/CMakeLists.txt+1−1 tests/mocks/Unity/Launcher/MockLauncherModel.cpp+10 tests/mocks/Unity/Launcher/MockLauncherModel.h+3 tests/mocks/Unity/Notifications/CMakeLists.txt+2−1 tests/mocks/Unity/Notifications/MockActionModel.cpp+6−4 tests/mocks/Unity/Notifications/MockActionModel.h+4−2 tests/mocks/Unity/Notifications/MockNotification.cpp+178 tests/mocks/Unity/Notifications/MockNotification.h+94 tests/mocks/Unity/Notifications/MockNotificationModel.cpp+172 tests/mocks/Unity/Notifications/MockNotificationModel.h+72 tests/mocks/Unity/Notifications/plugin.cpp+5−3 tests/mocks/Unity/Notifications/plugin.h+1−2 tests/mocks/libusermetrics/UserMetrics.cpp+1 tests/plugins/CMakeLists.txt+1 tests/plugins/Greeter/CMakeLists.txt+1 tests/plugins/Greeter/Unity/CMakeLists.txt+1 tests/plugins/Greeter/Unity/Launcher/AccountsServiceDBusAdaptor.cpp+48 tests/plugins/Greeter/Unity/Launcher/AccountsServiceDBusAdaptor.h+53 tests/plugins/Greeter/Unity/Launcher/CMakeLists.txt+56 tests/plugins/Greeter/Unity/Launcher/launchermodelastest.cpp+169 tests/plugins/Unity/Indicators/indicatorsmanagertest.cpp+30−5 tests/plugins/Unity/Indicators/indicatorsmodeltest.cpp+6−3 tests/plugins/Unity/Indicators/sharedunitymenumodeltest.cpp+2−2 tests/plugins/Unity/Launcher/AccountsServiceDBusAdaptor.cpp+42 tests/plugins/Unity/Launcher/AccountsServiceDBusAdaptor.h+47 tests/plugins/Unity/Launcher/CMakeLists.txt+3−1 tests/plugins/Unity/Launcher/launchermodeltest.cpp+65−1 tests/qmltests/Launcher/tst_Launcher.qml+46−19 tests/mocks/Unity/Notifications/MockNotificationTypes.cpp → tests/qmltests/Notifications/Notification.qml+11−6 tests/qmltests/Notifications/tst_Notifications.qml+271−213 tests/qmltests/Notifications/tst_OptionToggle.qml+11−6 tests/qmltests/Notifications/tst_SwipeToAct.qml+4 tests/qmltests/Panel/tst_Panel.qml+33−2 tests/qmltests/Stages/tst_WindowMoveResizeArea.qml+117−18 tests/qmltests/tst_Shell.qml+29 tests/qmltests/tst_ShellWithPin.qml+16
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
22
3− [ Andrea Cimitan ]
4− * Fix continue button in wifi wizard page, adds qml-module-
5− qtsysteminfo as unity8 dep (LP: #1363400)
6− * Background needs to be specified to be visible in horizontal cards
7− (LP: #1411748)
8−
9− [ CI Train Bot ]
10− * Resync trunk
3+ [ Michael Terry ]
4+ * Redesign tutorial to match latest spec (just removing obsolete pages
5+ and redesigning look, no new pages yet)
6+ * Add new right-edge and bottom-edge screens to the first-boot edge
7+ tutorial (LP: #1383297)
8+ * Support switching the indicator profile on the fly, as the greeter
9+ appears and disappears. (But don't use it yet, not until the
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).
1114
12− [ Michał Sawicz ]
13− * Add workaround for gcc ICE.
15+ [ Michael Zanetti ]
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.
1422
15− [ Albert Astals ]
16− * Implement proper updateRanges for horizontal items (i.e. Carousel,
17− Horizontal List)
23+ [ handsome_feng<445865575@qq.com> ]
24+ * makes the header fully reveal when tapping the search icon. (LP:
25+ #1379327)
1826
19− [ Michael Terry ]
20− * Don't accept multiple "Finish" clicks during the last step of the
21− wizard
27+ [ Mirco Müller ]
28+ * Allow swipe-to-dismiss for contracted snap-decision notifications,
29+ interactive notifications and ephemeral notifications. (LP:
30+ #1355422, #1334855)
2231
2332 [ Daniel d'Andrada ]
24− * Unify all liblightdm mocks
25− * Ensure the greeter password field is not covered by the keyboard
26− * Don't show() the lockscreen if it's already being shown
33+ * Make indicators bar eat all events
34+ * Improve Launcher tests, making them more reliable
35+ * Tapping on a surface gives it active focus
2736
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
2938
3039unity8 (8.02+15.04.20150123.3-0ubuntu1) vivid; urgency=low
3140
debian/control+1−1
@@ -27,7 +27,7 @@ Build-Depends: cmake,
2727 libqmenumodel-dev (>= 0.2.9),
2828 libqt5xmlpatterns5-dev,
2929 libsystemsettings-dev,
30− libunity-api-dev (>= 7.94),
30+ libunity-api-dev (>= 7.95),
3131 libusermetricsoutput1-dev,
3232 libxcb1-dev,
3333 pkg-config,
debian/unity8-private.install+1
@@ -1,6 +1,7 @@
11usr/lib/*/libunity8-private.*
22usr/lib/*/unity8/qml/AccountsService
33usr/lib/*/unity8/qml/Dash
4+usr/lib/*/unity8/qml/Greeter
45usr/lib/*/unity8/qml/LightDM
56usr/lib/*/unity8/qml/Lights
67usr/lib/*/unity8/qml/Powerd
plugins/AccountsService/AccountsServiceDBusAdaptor.cpp+9
@@ -57,6 +57,15 @@ void AccountsServiceDBusAdaptor::setUserProperty(const QString &user, const QStr
5757 }
5858}
5959
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+
6069void AccountsServiceDBusAdaptor::propertiesChangedSlot(const QString &interface, const QVariantMap &changed, const QStringList &invalid)
6170{
6271 // Merge changed and invalidated together
plugins/AccountsService/AccountsServiceDBusAdaptor.h+12
@@ -19,6 +19,7 @@
1919#ifndef UNITY_ACCOUNTSSERVICEDBUSADAPTOR_H
2020#define UNITY_ACCOUNTSSERVICEDBUSADAPTOR_H
2121
22+#include <QDBusArgument>
2223#include <QDBusContext>
2324#include <QDBusInterface>
2425#include <QMap>
@@ -33,7 +34,18 @@ public:
3334 explicit AccountsServiceDBusAdaptor(QObject *parent = 0);
3435
3536 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+
3647 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);
3749
3850Q_SIGNALS:
3951 void propertiesChanged(const QString &user, const QString &interface, const QStringList &changed);
plugins/AccountsService/com.canonical.unity.AccountsService.xml+1−2
@@ -14,8 +14,7 @@
1414 <annotation name="org.freedesktop.Accounts.DefaultValue" value="true"/>
1515 </property>
1616
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">
1918 <annotation name="org.freedesktop.Accounts.DefaultValue" value="[{'defaults': <true>}]"/>
2019 </property>
2120
plugins/CMakeLists.txt+1
@@ -12,6 +12,7 @@ macro(add_unity8_plugin PLUGIN VERSION PATH)
1212endmacro()
1313
1414add_subdirectory(AccountsService)
15+add_subdirectory(Greeter)
1516add_subdirectory(LightDM)
1617add_subdirectory(Lights)
1718add_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/Unity/Indicators/indicator.cpp+23−8
@@ -31,24 +31,26 @@ Indicator::~Indicator()
3131{
3232}
3333
34−void Indicator::init(const QString &profile, const QString& busName, const QSettings& settings)
34+void Indicator::init(const QString& busName, const QSettings& settings)
3535{
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+ }
3744
38− QVariant pos = settings.value(profile + "/Position");
39− if (!pos.isValid())
40− pos = settings.value("Indicator Service/Position", QVariant::fromValue(0));
41− setPosition(pos.toInt());
45+ setId(settings.value("Indicator Service/Name").toString());
4246
4347 QString actionObjectPath = settings.value("Indicator Service/ObjectPath").toString();
44− QString menuObjectPath = settings.value(profile + "/ObjectPath").toString();
4548
4649 QVariantMap properties;
4750 properties.clear();
4851 properties.insert("busType", 1);
4952 properties.insert("busName", busName);
5053 properties.insert("actionsObjectPath", actionObjectPath);
51− properties.insert("menuObjectPath", menuObjectPath);
5254 setIndicatorProperties(properties);
5355}
5456
@@ -78,6 +80,19 @@ void Indicator::setPosition(int position)
7880 }
7981}
8082
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+
8196QVariant Indicator::indicatorProperties() const
8297{
8398 return m_properties;
plugins/Unity/Indicators/indicator.h+5−1
@@ -35,12 +35,15 @@ public:
3535 Indicator(QObject *parent = 0);
3636 virtual ~Indicator();
3737
38− void init(const QString &profile, const QString& busName, const QSettings& settings);
38+ void init(const QString& busName, const QSettings& settings);
3939
4040 QString identifier() const;
4141 int position() const;
4242 QVariant indicatorProperties() const;
4343
44+public Q_SLOTS:
45+ void setProfile(const QString& profile);
46+
4447Q_SIGNALS:
4548 void identifierChanged(const QString &identifier);
4649 void positionChanged(int position);
@@ -55,6 +58,7 @@ private:
5558 QString m_identifier;
5659 int m_position;
5760 QVariant m_properties;
61+ QVariantMap m_settings;
5862};
5963
6064#endif // INDICATOR_H
plugins/Unity/Indicators/indicatorsmanager.cpp+17−3
@@ -54,10 +54,9 @@ IndicatorsManager::~IndicatorsManager()
5454
5555}
5656
57−void IndicatorsManager::load(const QString& profile)
57+void IndicatorsManager::load()
5858{
5959 unload();
60− m_profile = profile;
6160
6261 QStringList xdgLocations = shellDataDirs();//QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
6362
@@ -216,6 +215,19 @@ void IndicatorsManager::setLoaded(bool loaded)
216215 }
217216}
218217
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+
219231void IndicatorsManager::startVerify(const QString& path)
220232{
221233 QHashIterator<QString, IndicatorData*> iter(m_indicatorsData);
@@ -268,7 +280,9 @@ Indicator::Ptr IndicatorsManager::indicator(const QString& indicator_name)
268280 Indicator::Ptr new_indicator(new Indicator(this));
269281 data->m_indicator = new_indicator;
270282 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&)));
272286 return new_indicator;
273287}
274288
plugins/Unity/Indicators/indicatorsmanager.h+6−1
@@ -32,13 +32,17 @@ class UNITYINDICATORS_EXPORT IndicatorsManager : public QObject
3232{
3333 Q_OBJECT
3434 Q_PROPERTY(bool loaded READ isLoaded NOTIFY loadedChanged)
35+ Q_PROPERTY(QString profile READ profile WRITE setProfile NOTIFY profileChanged)
3536public:
3637 explicit IndicatorsManager(QObject* parent = 0);
3738 ~IndicatorsManager();
3839
39− Q_INVOKABLE void load(const QString& profile);
40+ Q_INVOKABLE void load();
4041 Q_INVOKABLE void unload();
4142
43+ QString profile() const;
44+ void setProfile(const QString& profile);
45+
4246 Indicator::Ptr indicator(const QString& indicator_name);
4347
4448 QList<Indicator::Ptr> indicators();
@@ -47,6 +51,7 @@ public:
4751
4852Q_SIGNALS:
4953 void loadedChanged(bool);
54+ void profileChanged(const QString&);
5055
5156 void indicatorLoaded(const QString& indicator_name);
5257 void indicatorAboutToBeUnloaded(const QString& indicator_name);
plugins/Unity/Indicators/indicatorsmodel.cpp+25−2
@@ -56,6 +56,7 @@ IndicatorsModel::IndicatorsModel(QObject *parent)
5656 m_manager = new IndicatorsManager(this);
5757 QObject::connect(m_manager, SIGNAL(indicatorLoaded(const QString&)), this, SLOT(onIndicatorLoaded(const QString&)));
5858 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()));
5960
6061 QObject::connect(this, SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SIGNAL(countChanged()));
6162 QObject::connect(this, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SIGNAL(countChanged()));
@@ -80,15 +81,37 @@ int IndicatorsModel::count() const
8081 return rowCount();
8182}
8283
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+
83106/*!
84107 \qmlmethod IndicatorsModel::unload()
85108
86109 Load all indicators.
87110*/
88−void IndicatorsModel::load(const QString& profile)
111+void IndicatorsModel::load()
89112{
90113 m_indicators.clear();
91− m_manager->load(profile);
114+ m_manager->load();
92115}
93116
94117/*!
plugins/Unity/Indicators/indicatorsmodel.h+6−1
@@ -34,17 +34,21 @@ class UNITYINDICATORS_EXPORT IndicatorsModel : public QAbstractListModel
3434 Q_OBJECT
3535 Q_ENUMS(Roles)
3636 Q_PROPERTY(int count READ count NOTIFY countChanged)
37+ Q_PROPERTY(QString profile READ profile WRITE setProfile NOTIFY profileChanged)
3738
3839public:
3940
4041 IndicatorsModel(QObject *parent=0);
4142 ~IndicatorsModel();
4243
43− Q_INVOKABLE void load(const QString& profile);
44+ Q_INVOKABLE void load();
4445 Q_INVOKABLE void unload();
4546
4647 Q_INVOKABLE QVariant data(int row, int role) const;
4748
49+ QString profile() const;
50+ void setProfile(const QString& profile);
51+
4852 /* QAbstractItemModel */
4953 QHash<int, QByteArray> roleNames() const;
5054 int columnCount(const QModelIndex &parent = QModelIndex()) const;
@@ -55,6 +59,7 @@ public:
5559
5660Q_SIGNALS:
5761 void countChanged();
62+ void profileChanged();
5863 void indicatorDataChanged(const QVariant& data);
5964
6065private Q_SLOTS:
plugins/Unity/Indicators/modelactionrootstate.cpp+4−2
@@ -71,7 +71,7 @@ void ModelActionRootState::setMenu(UnityMenuModel* menu)
7171
7272bool ModelActionRootState::valid() const
7373{
74− return m_menu && m_menu->rowCount() > 0;
74+ return !currentState().empty();
7575}
7676
7777void ModelActionRootState::onModelRowsAdded(const QModelIndex& parent, int start, int end)
@@ -121,7 +121,9 @@ void ModelActionRootState::updateActionState()
121121 m_menu->setActionStateParser(oldParser);
122122
123123 setCurrentState(state);
124− } else {
124+ } else if (!m_menu) {
125125 setCurrentState(QVariantMap());
126126 }
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
127129}
plugins/Unity/Indicators/unitymenumodelcache.cpp+8−11
@@ -46,17 +46,14 @@ QSharedPointer<UnityMenuModel> UnityMenuModelCache::model(const QByteArray& path
4646 QQmlEngine::setObjectOwnership(model, QQmlEngine::CppOwnership);
4747
4848 QSharedPointer<UnityMenuModel> menuModel(model);
49− connect(model, &QObject::destroyed, this, [this] {
50− QMutableHashIterator<QByteArray, QWeakPointer<UnityMenuModel>> iter(m_registry);
51− while(iter.hasNext()) {
52− auto keyVal = iter.next();
53− if (keyVal.value().isNull()) {
54− iter.remove();
55− break;
56− }
57− }
58− });
59− m_registry[path] = menuModel.toWeakRef();
49+
50+ // Keep a shared pointer (rather than weak pointer which would cause the
51+ // model to be deleted when all shared pointers we give out are deleted).
52+ // We want to keep all models cached because when we switch indicator
53+ // profiles, we will be switching paths often. And we want to keep the
54+ // old model around, ready to be used. Otherwise the UI might momentarily
55+ // wait as we populate the model from DBus yet again.
56+ m_registry[path] = menuModel;
6057
6158 menuModel->setMenuObjectPath(path);
6259 return menuModel;
plugins/Unity/Indicators/unitymenumodelcache.h+2−2
@@ -25,7 +25,7 @@
2525#include <QObject>
2626#include <QHash>
2727#include <QPointer>
28−#include <QWeakPointer>
28+#include <QSharedPointer>
2929
3030class UnityMenuModel;
3131
@@ -43,7 +43,7 @@ public:
4343 Q_INVOKABLE virtual bool contains(const QByteArray& path);
4444
4545protected:
46− QHash<QByteArray, QWeakPointer<UnityMenuModel>> m_registry;
46+ QHash<QByteArray, QSharedPointer<UnityMenuModel>> m_registry;
4747 static QPointer<UnityMenuModelCache> theCache;
4848};
4949
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)
22pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=5)
33pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
44
@@ -20,6 +20,7 @@ set(QMLLAUNCHERPLUGIN_SRC
2020 dbusinterface.cpp
2121 gsettings.cpp
2222 desktopfilehandler.cpp
23+ asadapter.cpp
2324 ${CMAKE_SOURCE_DIR}/plugins/AccountsService/AccountsServiceDBusAdaptor.cpp
2425 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherItemInterface.h
2526 ${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 @@
11/*
2− * Copyright 2014 Canonical Ltd.
2+ * Copyright 2014-2015 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU Lesser General Public License as published by
@@ -12,25 +12,35 @@
1212 *
1313 * You should have received a copy of the GNU Lesser General Public License
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Authors:
17− * Mirco Mueller <mirco.mueller@canonical.com>
1815 */
1916
20−#ifndef MOCK_NOTIFICATION_TYPES_H
21−#define MOCK_NOTIFICATION_TYPES_H
17+#ifndef ASADAPTER_H
18+#define ASADAPTER_H
2219
23−#include <QObject>
20+#include <QVariantMap>
2421
25−class MockNotification : public QObject {
26− Q_OBJECT
27− Q_ENUMS(Type)
22+class LauncherItem;
23+class AccountsServiceDBusAdaptor;
24+class QDBusInterface;
2825
26+class ASAdapter
27+{
2928public:
30− MockNotification(QObject *parent=nullptr);
31− virtual ~MockNotification();
29+ ASAdapter();
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;
3242
33− enum Type { PlaceHolder, Confirmation, Ephemeral, Interactive, SnapDecision };
43+ friend class LauncherModelTest;
3444};
3545
36−#endif // MOCK_NOTIFICATION_TYPES_H
46+#endif
plugins/Unity/Launcher/launchermodel.cpp+20
@@ -22,6 +22,7 @@
2222#include "gsettings.h"
2323#include "desktopfilehandler.h"
2424#include "dbusinterface.h"
25+#include "asadapter.h"
2526
2627#include <unity/shell/application/ApplicationInfoInterface.h>
2728
@@ -34,6 +35,7 @@ LauncherModel::LauncherModel(QObject *parent):
3435 LauncherModelInterface(parent),
3536 m_settings(new GSettings(this)),
3637 m_dbusIface(new DBusInterface(this)),
38+ m_asAdapter(new ASAdapter()),
3739 m_appManager(0)
3840{
3941 connect(m_dbusIface, &DBusInterface::countChanged, this, &LauncherModel::countChanged);
@@ -50,6 +52,8 @@ LauncherModel::~LauncherModel()
5052 while (!m_list.empty()) {
5153 m_list.takeFirst()->deleteLater();
5254 }
55+
56+ delete m_asAdapter;
5357}
5458
5559int LauncherModel::rowCount(const QModelIndex &parent) const
@@ -261,6 +265,15 @@ void LauncherModel::setApplicationManager(unity::shell::application::Application
261265 }
262266}
263267
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+}
264277
265278void LauncherModel::storeAppList()
266279{
@@ -271,6 +284,7 @@ void LauncherModel::storeAppList()
271284 }
272285 }
273286 m_settings->setStoredApplications(appIds);
287+ m_asAdapter->syncItems(m_list);
274288}
275289
276290void LauncherModel::unpin(const QString &appId)
@@ -321,6 +335,7 @@ void LauncherModel::countChanged(const QString &appId, int count)
321335 LauncherItem *item = m_list.at(idx);
322336 item->setCount(count);
323337 Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleCount);
338+ m_asAdapter->syncItems(m_list);
324339 }
325340}
326341
@@ -352,6 +367,7 @@ void LauncherModel::countVisibleChanged(const QString &appId, int countVisible)
352367 Q_EMIT hint();
353368 }
354369 }
370+ m_asAdapter->syncItems(m_list);
355371}
356372
357373void LauncherModel::refresh()
@@ -433,6 +449,8 @@ void LauncherModel::refresh()
433449 if (changed) {
434450 Q_EMIT hint();
435451 }
452+
453+ m_asAdapter->syncItems(m_list);
436454}
437455
438456void LauncherModel::applicationAdded(const QModelIndex &parent, int row)
@@ -467,6 +485,7 @@ void LauncherModel::applicationAdded(const QModelIndex &parent, int row)
467485 beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
468486 m_list.append(item);
469487 endInsertRows();
488+ m_asAdapter->syncItems(m_list);
470489 }
471490}
472491
@@ -486,6 +505,7 @@ void LauncherModel::applicationRemoved(const QModelIndex &parent, int row)
486505 beginRemoveRows(QModelIndex(), appIndex, appIndex);
487506 m_list.takeAt(appIndex)->deleteLater();
488507 endRemoveRows();
508+ m_asAdapter->syncItems(m_list);
489509 }
490510}
491511
plugins/Unity/Launcher/launchermodel.h+5
@@ -28,6 +28,7 @@
2828class LauncherItem;
2929class GSettings;
3030class DBusInterface;
31+class ASAdapter;
3132
3233using namespace unity::shell::launcher;
3334using namespace unity::shell::application;
@@ -54,6 +55,9 @@ public:
5455 unity::shell::application::ApplicationManagerInterface* applicationManager() const;
5556 void setApplicationManager(unity::shell::application::ApplicationManagerInterface *appManager);
5657
58+ bool onlyPinned() const override;
59+ void setOnlyPinned(bool onlyPinned) override;
60+
5761 int findApplication(const QString &appId);
5862
5963public Q_SLOTS:
@@ -79,6 +83,7 @@ private:
7983
8084 GSettings *m_settings;
8185 DBusInterface *m_dbusIface;
86+ ASAdapter *m_asAdapter;
8287
8388 ApplicationManagerInterface *m_appManager;
8489
plugins/Utils/CMakeLists.txt+2−1
@@ -15,6 +15,7 @@ set(QMLPLUGIN_SRC
1515 unitymenumodelpaths.cpp
1616 windowkeysfilter.cpp
1717 easingcurve.cpp
18+ windowstatestorage.cpp
1819 plugin.cpp
1920 )
2021
@@ -30,6 +31,6 @@ target_link_libraries(Utils-qml ${GIO_LDFLAGS})
3031# files directly in targets.
3132set_target_properties(Utils-qml PROPERTIES COMPILE_FLAGS -fvisibility=default)
3233
33−qt5_use_modules(Utils-qml Qml Quick DBus Network Gui)
34+qt5_use_modules(Utils-qml Qml Quick DBus Network Gui Sql Concurrent)
3435
3536add_unity8_plugin(Utils 0.1 Utils TARGETS Utils-qml)
plugins/Utils/plugin.cpp+9
@@ -33,6 +33,14 @@
3333#include "unitymenumodelpaths.h"
3434#include "windowkeysfilter.h"
3535#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+}
3644
3745void UtilsPlugin::registerTypes(const char *uri)
3846{
@@ -46,6 +54,7 @@ void UtilsPlugin::registerTypes(const char *uri)
4654 qmlRegisterType<GDateTimeFormatter>(uri, 0, 1, "GDateTimeFormatter");
4755 qmlRegisterType<EasingCurve>(uri, 0, 1, "EasingCurve");
4856 qmlRegisterType<RelativeTimeFormatter>(uri, 0, 1, "RelativeTimeFormatter");
57+ qmlRegisterSingletonType<WindowStateStorage>(uri, 0, 1, "WindowStateStorage", createWindowStateStorage);
4958}
5059
5160void 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/be.po+7−7
@@ -8,15 +8,15 @@ msgstr ""
88"Project-Id-Version: unity8\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2015-01-22 11:09+0100\n"
11−"PO-Revision-Date: 2015-02-03 22:02+0000\n"
12−"Last-Translator: Arsień Šachalevič <semen.shah@gmail.com>\n"
11+"PO-Revision-Date: 2015-02-07 19:04+0000\n"
12+"Last-Translator: Mikhail_SaTuRn <mikhail_saturn@tut.by>\n"
1313"Language-Team: Belarusian <be@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
1717"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
1818"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
19−"X-Launchpad-Export-Date: 2015-02-04 07:13+0000\n"
19+"X-Launchpad-Export-Date: 2015-02-08 06:11+0000\n"
2020"X-Generator: Launchpad (build 17331)\n"
2121
2222#: plugins/LightDM/Greeter.cpp:130
@@ -87,7 +87,7 @@ msgstr "Перазагрузіць"
8787
8888#: qml/Components/Dialogs.qml:132
8989msgid "Are you sure you want to reboot?"
90−msgstr "Вы ўпэўненыя, што жадаеце перазагрузіць?"
90+msgstr "Вы ўпэўнены, што жадаеце перазагрузіць?"
9191
9292#: qml/Components/Dialogs.qml:154
9393msgid "Power"
@@ -166,11 +166,11 @@ msgstr ""
166166
167167#: qml/Components/Lockscreen.qml:238
168168msgid "Return to Call"
169−msgstr "Вярнуцца да Выкліку"
169+msgstr "Вярнуцца да выкліку"
170170
171171#: qml/Components/Lockscreen.qml:238
172172msgid "Emergency Call"
173−msgstr "Терміновы Выклік"
173+msgstr "Терміновы выклік"
174174
175175#: qml/Components/Lockscreen.qml:270
176176msgid "OK"
@@ -518,7 +518,7 @@ msgstr "Пацвердзіце пароль"
518518
519519#: qml/Wizard/Pages/passwd-confirm.qml:50
520520msgid "Confirm passcode"
521−msgstr "Пацвердзіце код-пароль"
521+msgstr "Пацвердзіце код"
522522
523523#: qml/Wizard/Pages/passwd-confirm.qml:53
524524#: qml/Wizard/Pages/passwd-confirm.qml:54
po/gd.po+45−40
@@ -8,7 +8,7 @@ msgstr ""
88"Project-Id-Version: unity8\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"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"
1212"Last-Translator: GunChleoc <Unknown>\n"
1313"Language-Team: Fòram na Gàidhlig\n"
1414"MIME-Version: 1.0\n"
@@ -16,8 +16,8 @@ msgstr ""
1616"Content-Transfer-Encoding: 8bit\n"
1717"Plural-Forms: nplurals=4; plural=(n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : "
1818"(n > 2 && n < 20) ? 2 : 3;\n"
19−"X-Launchpad-Export-Date: 2015-01-28 06:30+0000\n"
20−"X-Generator: Launchpad (build 17306)\n"
19+"X-Launchpad-Export-Date: 2015-02-12 07:06+0000\n"
20+"X-Generator: Launchpad (build 17336)\n"
2121"Language: gd\n"
2222
2323#: plugins/LightDM/Greeter.cpp:130
@@ -241,11 +241,11 @@ msgstr "Leig às airson ath-nuadhachadh…"
241241
242242#: qml/Dash/ScopesList.qml:66
243243msgid "Manage"
244−msgstr ""
244+msgstr "Stiùirich"
245245
246246#: qml/Dash/ScopesList.qml:112
247247msgid "Home"
248−msgstr ""
248+msgstr "Dhachaigh"
249249
250250#: qml/Dash/ScopesList.qml:113
251251msgid "Also installed"
@@ -297,7 +297,7 @@ msgstr "Dh'fhàillig leis, thoir gnogag gus feuchainn a-rithist"
297297
298298#: qml/Panel/Indicators/MessageMenuItemFactory.qml:149
299299msgid "Reply"
300−msgstr ""
300+msgstr "Freagair"
301301
302302#: qml/Panel/Indicators/ModemInfoItem.qml:105
303303msgid "Unlock SIM"
@@ -369,58 +369,58 @@ msgstr ""
369369
370370#: qml/Wizard/Page.qml:89
371371msgid "Back"
372−msgstr ""
372+msgstr "Air ais"
373373
374374#: qml/Wizard/Pages/10-welcome.qml:27
375375msgid "Hi!"
376−msgstr ""
376+msgstr "Shin thu!"
377377
378378#: qml/Wizard/Pages/10-welcome.qml:44
379379msgid "Welcome to your Ubuntu phone."
380−msgstr ""
380+msgstr "Fàilte dhan fhòn Ubuntu agad."
381381
382382#: qml/Wizard/Pages/10-welcome.qml:52
383383msgid "Let’s get started."
384−msgstr ""
384+msgstr "Tòisicheamaid."
385385
386386#: qml/Wizard/Pages/10-welcome.qml:88 qml/Wizard/Pages/30-passwd-type.qml:128
387387#: qml/Wizard/Pages/40-wifi.qml:197 qml/Wizard/Pages/50-location.qml:131
388388#: qml/Wizard/Pages/60-reporting.qml:50 qml/Wizard/Pages/passwd-confirm.qml:82
389389#: qml/Wizard/Pages/passwd-set.qml:90
390390msgid "Continue"
391−msgstr ""
391+msgstr "Lean air adhart"
392392
393393#: qml/Wizard/Pages/20-sim.qml:25
394394msgid "Add a SIM card and restart your device"
395−msgstr ""
395+msgstr "Cuir cairt SIM ris 's tòisich an uidheam agad às ùr"
396396
397397#: qml/Wizard/Pages/20-sim.qml:55
398398msgid "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."
400400
401401#: qml/Wizard/Pages/20-sim.qml:69 qml/Wizard/Pages/40-wifi.qml:197
402402msgid "Skip"
403−msgstr ""
403+msgstr "Leum thairis air"
404404
405405#: qml/Wizard/Pages/30-passwd-type.qml:39
406406msgid "Lock security"
407−msgstr ""
407+msgstr "Glas tèarainteachd"
408408
409409#: qml/Wizard/Pages/30-passwd-type.qml:74
410410msgid "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."
412412
413413#: qml/Wizard/Pages/30-passwd-type.qml:97
414414msgid "Swipe"
415−msgstr ""
415+msgstr "Grad-shlaighd"
416416
417417#: qml/Wizard/Pages/30-passwd-type.qml:98
418418msgid "No security"
419−msgstr ""
419+msgstr "Gun tèarainteachd"
420420
421421#: qml/Wizard/Pages/30-passwd-type.qml:100
422422msgid "Passcode"
423−msgstr ""
423+msgstr "Còd-faire"
424424
425425#: qml/Wizard/Pages/30-passwd-type.qml:101
426426msgid "4 digits only"
@@ -428,31 +428,31 @@ msgstr ""
428428
429429#: qml/Wizard/Pages/30-passwd-type.qml:103
430430msgid "Passphrase"
431−msgstr ""
431+msgstr "Abairt-fhaire"
432432
433433#: qml/Wizard/Pages/30-passwd-type.qml:104
434434msgid "Numbers and letters"
435−msgstr ""
435+msgstr "Àireamhan 's litrichean"
436436
437437#: qml/Wizard/Pages/40-wifi.qml:28
438438msgid "Connect to Wi‑Fi"
439−msgstr ""
439+msgstr "Ceangail ri Wi-Fi"
440440
441441#: qml/Wizard/Pages/40-wifi.qml:147
442442msgid "Available networks…"
443−msgstr ""
443+msgstr "Lìonraidhean ri làimh…"
444444
445445#: qml/Wizard/Pages/40-wifi.qml:148
446446msgid "No available networks."
447−msgstr ""
447+msgstr "Chan eil lìonra ri làimh."
448448
449449#: qml/Wizard/Pages/50-location.qml:27
450450msgid "Location"
451−msgstr ""
451+msgstr "Àite"
452452
453453#: qml/Wizard/Pages/50-location.qml:62
454454msgid "Let the phone detect your location:"
455−msgstr ""
455+msgstr "Faodaidh am fòn aithneachadh far a bheil thu:"
456456
457457#: qml/Wizard/Pages/50-location.qml:69
458458msgid "Using GPS only (less accurate)"
@@ -471,7 +471,7 @@ msgstr ""
471471
472472#: qml/Wizard/Pages/50-location.qml:112
473473msgid "Not at all"
474−msgstr ""
474+msgstr "Chan fhaod idir"
475475
476476#: qml/Wizard/Pages/50-location.qml:124
477477msgid "You can change your mind later in <b>System Settings</b>."
@@ -479,70 +479,75 @@ msgstr ""
479479
480480#: qml/Wizard/Pages/60-reporting.qml:24
481481msgid "Improving your experience"
482−msgstr ""
482+msgstr "A' cur piseach air a' ghnàth-eòlas agad"
483483
484484#: qml/Wizard/Pages/60-reporting.qml:36
485485msgid ""
486486"Your phone is set up to automatically report errors to Canonical and its "
487487"partners, the makers of the operating system."
488488msgstr ""
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."
489492
490493#: qml/Wizard/Pages/60-reporting.qml:43
491494msgid ""
492495"This can be disabled in <b>System Settings</b> under <b>Security &amp; "
493496"Privacy</b>"
494497msgstr ""
498+"Gabhaidh seo cur à comas ann an <b>Roghainnean an t-siostaim</b> fo "
499+"<b>Thèarainteachd ⁊ prìobhaideachd</b>"
495500
496501#: qml/Wizard/Pages/80-finished.qml:24
497502msgid "All done"
498−msgstr ""
503+msgstr "Coileanta"
499504
500505#: qml/Wizard/Pages/80-finished.qml:39
501506msgid "Nice work!"
502−msgstr ""
507+msgstr "Shin thu!"
503508
504509#: qml/Wizard/Pages/80-finished.qml:46
505510msgid "Your phone is now ready to use."
506−msgstr ""
511+msgstr "Gabhaidh am fòn agad cleachdadh a-nis."
507512
508513#: qml/Wizard/Pages/80-finished.qml:53
509514msgid "Finish"
510−msgstr ""
515+msgstr "Crìochnaich"
511516
512517#: qml/Wizard/Pages/here-terms.qml:27
513518msgid "Terms & Conditions"
514−msgstr ""
519+msgstr "Teirmichean ⁊ cumhaichean"
515520
516521#: qml/Wizard/Pages/passwd-confirm.qml:49
517522msgid "Confirm passphrase"
518−msgstr ""
523+msgstr "Dearbhaich an abairt-fhaire"
519524
520525#: qml/Wizard/Pages/passwd-confirm.qml:50
521526msgid "Confirm passcode"
522−msgstr ""
527+msgstr "Dearbhaich an còd-faire"
523528
524529#: qml/Wizard/Pages/passwd-confirm.qml:53
525530#: qml/Wizard/Pages/passwd-confirm.qml:54
526531msgid "Please try again."
527−msgstr ""
532+msgstr "Feuch ris a-rithist."
528533
529534#: qml/Wizard/Pages/passwd-set.qml:61
530535msgid "Choose your passcode"
531−msgstr ""
536+msgstr "Tagh an còd-faire agad"
532537
533538#: qml/Wizard/Pages/passwd-set.qml:68
534539msgid "Passphrase must be 4 characters long"
535−msgstr ""
540+msgstr "Feumaidh an abairt-fhaire a bhith 4 caractaran a dh'fhaid"
536541
537542#: qml/Wizard/StackButton.qml:39
538543#, qt-format
539544msgid "〈 %1"
540−msgstr ""
545+msgstr "〈 %1"
541546
542547#: qml/Wizard/StackButton.qml:42
543548#, qt-format
544549msgid "%1 〉"
545−msgstr ""
550+msgstr "%1 〉"
546551
547552#~ msgid "Confirm"
548553#~ msgstr "Dearbhaich"
po/gl.po+7−7
@@ -8,15 +8,15 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"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"
1212"Last-Translator: Marcos Lans <Unknown>\n"
1313"Language-Team: Galician <gl@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
1717"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2015-01-29 06:00+0000\n"
19−"X-Generator: Launchpad (build 17306)\n"
18+"X-Launchpad-Export-Date: 2015-02-12 07:06+0000\n"
19+"X-Generator: Launchpad (build 17336)\n"
2020
2121#: plugins/LightDM/Greeter.cpp:130
2222msgid "Password: "
@@ -56,7 +56,7 @@ msgstr "Saír da sesión"
5656
5757#: qml/Components/Dialogs.qml:87
5858msgid "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?"
6060
6161#: qml/Components/Dialogs.qml:89 qml/Components/Dialogs.qml:111
6262#: qml/Components/Dialogs.qml:134
@@ -74,7 +74,7 @@ msgstr "Apagar"
7474
7575#: qml/Components/Dialogs.qml:109
7676msgid "Are you sure you want to shut down?"
77−msgstr "Está seguro de querer apagar?"
77+msgstr "Confirma que desexa apagar?"
7878
7979#: qml/Components/Dialogs.qml:131
8080msgid "Reboot"
@@ -82,7 +82,7 @@ msgstr "Reiniciar"
8282
8383#: qml/Components/Dialogs.qml:132
8484msgid "Are you sure you want to reboot?"
85−msgstr "Está seguro de querer reiniciar?"
85+msgstr "Confirma que desexa reiniciar?"
8686
8787#: qml/Components/Dialogs.qml:154
8888msgid "Power"
@@ -92,7 +92,7 @@ msgstr "Enerxía"
9292msgid ""
9393"Are you sure you would like\n"
9494"to power off?"
95−msgstr "Está seguro de querer apagar?"
95+msgstr "Confirma que desexa apagar?"
9696
9797#: qml/Components/Dialogs.qml:157
9898msgid "Power off"
po/oc.ponew file+521
@@ -0,0 +1,521 @@
1+# Occitan (post 1500) translation for unity8
2+# Copyright (c) 2015 Rosetta Contributors and Canonical Ltd 2015
3+# This file is distributed under the same license as the unity8 package.
4+# FIRST AUTHOR <EMAIL@ADDRESS>, 2015.
5+#
6+msgid ""
7+msgstr ""
8+"Project-Id-Version: unity8\n"
9+"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10+"POT-Creation-Date: 2015-01-22 11:09+0100\n"
11+"PO-Revision-Date: 2015-02-05 20:35+0000\n"
12+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13+"Language-Team: Occitan (post 1500) <oc@li.org>\n"
14+"MIME-Version: 1.0\n"
15+"Content-Type: text/plain; charset=UTF-8\n"
16+"Content-Transfer-Encoding: 8bit\n"
17+"Plural-Forms: nplurals=2; plural=n > 1;\n"
18+"X-Launchpad-Export-Date: 2015-02-07 07:31+0000\n"
19+"X-Generator: Launchpad (build 17331)\n"
20+
21+#: plugins/LightDM/Greeter.cpp:130
22+msgid "Password: "
23+msgstr "Senhal : "
24+
25+#: plugins/Unity/Launcher/launcheritem.cpp:45
26+#: plugins/Unity/Launcher/launcheritem.cpp:100
27+msgid "Pin shortcut"
28+msgstr ""
29+
30+#: plugins/Unity/Launcher/launcheritem.cpp:100
31+msgid "Unpin shortcut"
32+msgstr ""
33+
34+#: qml/Components/DelayedLockscreen.qml:47
35+msgid "Device Locked"
36+msgstr ""
37+
38+#: qml/Components/DelayedLockscreen.qml:62
39+msgid "You have been locked out due to too many failed passphrase attempts."
40+msgstr ""
41+
42+#: qml/Components/DelayedLockscreen.qml:63
43+msgid "You have been locked out due to too many failed passcode attempts."
44+msgstr ""
45+
46+#: qml/Components/DelayedLockscreen.qml:72
47+#, qt-format
48+msgid "Please wait %1 minute and then try again…"
49+msgid_plural "Please wait %1 minutes and then try again…"
50+msgstr[0] ""
51+msgstr[1] ""
52+
53+#: qml/Components/Dialogs.qml:86
54+msgid "Log out"
55+msgstr ""
56+
57+#: qml/Components/Dialogs.qml:87
58+msgid "Are you sure you want to log out?"
59+msgstr ""
60+
61+#: qml/Components/Dialogs.qml:89 qml/Components/Dialogs.qml:111
62+#: qml/Components/Dialogs.qml:134
63+msgid "No"
64+msgstr "Non"
65+
66+#: qml/Components/Dialogs.qml:95 qml/Components/Dialogs.qml:117
67+#: qml/Components/Dialogs.qml:140
68+msgid "Yes"
69+msgstr ""
70+
71+#: qml/Components/Dialogs.qml:108
72+msgid "Shut down"
73+msgstr ""
74+
75+#: qml/Components/Dialogs.qml:109
76+msgid "Are you sure you want to shut down?"
77+msgstr ""
78+
79+#: qml/Components/Dialogs.qml:131
80+msgid "Reboot"
81+msgstr ""
82+
83+#: qml/Components/Dialogs.qml:132
84+msgid "Are you sure you want to reboot?"
85+msgstr ""
86+
87+#: qml/Components/Dialogs.qml:154
88+msgid "Power"
89+msgstr ""
90+
91+#: qml/Components/Dialogs.qml:155
92+msgid ""
93+"Are you sure you would like\n"
94+"to power off?"
95+msgstr ""
96+
97+#: qml/Components/Dialogs.qml:157
98+msgid "Power off"
99+msgstr ""
100+
101+#: qml/Components/Dialogs.qml:166
102+msgid "Restart"
103+msgstr ""
104+
105+#: qml/Components/Dialogs.qml:175
106+msgid "Cancel"
107+msgstr ""
108+
109+#: qml/Components/EdgeDemoOverlay.qml:151
110+msgid "Skip intro"
111+msgstr ""
112+
113+#: qml/Components/EdgeDemo.qml:125
114+msgid "Right edge"
115+msgstr ""
116+
117+#: qml/Components/EdgeDemo.qml:126
118+msgid "Try swiping from the right edge to unlock the phone"
119+msgstr ""
120+
121+#: qml/Components/EdgeDemo.qml:157
122+msgid "Top edge"
123+msgstr ""
124+
125+#: qml/Components/EdgeDemo.qml:158
126+msgid "Try swiping from the top edge to access the indicators"
127+msgstr ""
128+
129+#: qml/Components/EdgeDemo.qml:183
130+msgid "Close"
131+msgstr ""
132+
133+#: qml/Components/EdgeDemo.qml:184
134+msgid "Swipe up again to close the settings screen"
135+msgstr ""
136+
137+#: qml/Components/EdgeDemo.qml:214
138+msgid "Left edge"
139+msgstr ""
140+
141+#: qml/Components/EdgeDemo.qml:215
142+msgid "Swipe from the left to reveal the launcher for quick access to apps"
143+msgstr ""
144+
145+#: qml/Components/EdgeDemo.qml:242
146+msgid "Well done"
147+msgstr ""
148+
149+#: qml/Components/EdgeDemo.qml:243
150+msgid ""
151+"You have now mastered the edge gestures and can start using the "
152+"phone<br><br>Tap on the screen to start"
153+msgstr ""
154+
155+#: qml/Components/Lockscreen.qml:238
156+msgid "Return to Call"
157+msgstr ""
158+
159+#: qml/Components/Lockscreen.qml:238
160+msgid "Emergency Call"
161+msgstr ""
162+
163+#: qml/Components/Lockscreen.qml:270
164+msgid "OK"
165+msgstr ""
166+
167+#: qml/Dash/GenericScopeView.qml:485 qml/Dash/GenericScopeView.qml:642
168+msgid "See less"
169+msgstr ""
170+
171+#: qml/Dash/GenericScopeView.qml:485
172+msgid "See all"
173+msgstr ""
174+
175+#: qml/Dash/GenericScopeView.qml:547 qml/Dash/PageHeader.qml:274
176+#: qml/Panel/SearchIndicator.qml:27
177+msgid "Search"
178+msgstr ""
179+
180+#: qml/Dash/PageHeader.qml:267
181+msgid "Store"
182+msgstr ""
183+
184+#: qml/Dash/PageHeader.qml:284
185+msgid "Settings"
186+msgstr ""
187+
188+#: qml/Dash/PageHeader.qml:291
189+msgid "Remove from Favorites"
190+msgstr ""
191+
192+#: qml/Dash/PageHeader.qml:291
193+msgid "Add to Favorites"
194+msgstr ""
195+
196+#: qml/Dash/Previews/PreviewActionCombo.qml:35
197+msgid "More..."
198+msgstr ""
199+
200+#: qml/Dash/Previews/PreviewActionCombo.qml:35
201+msgid "Less..."
202+msgstr ""
203+
204+#: qml/Dash/Previews/PreviewRatingInput.qml:81
205+msgid "Rate this"
206+msgstr ""
207+
208+#: qml/Dash/Previews/PreviewRatingInput.qml:126
209+msgid "Add a review"
210+msgstr ""
211+
212+#: qml/Dash/Previews/PreviewRatingInput.qml:166
213+msgid "Send"
214+msgstr ""
215+
216+#: qml/Dash/PullToRefreshScopeStyle.qml:55
217+msgid "Pull to refresh…"
218+msgstr ""
219+
220+#: qml/Dash/PullToRefreshScopeStyle.qml:60
221+msgid "Release to refresh…"
222+msgstr ""
223+
224+#: qml/Dash/ScopesList.qml:66
225+msgid "Manage"
226+msgstr ""
227+
228+#: qml/Dash/ScopesList.qml:112
229+msgid "Home"
230+msgstr ""
231+
232+#: qml/Dash/ScopesList.qml:113
233+msgid "Also installed"
234+msgstr ""
235+
236+#: qml/Greeter/Greeter.qml:133
237+msgid "Unlock"
238+msgstr ""
239+
240+#: qml/Notifications/NotificationMenuItemFactory.qml:128
241+msgid "Show password"
242+msgstr ""
243+
244+#: qml/Panel/ActiveCallHint.qml:79
245+msgid "Tap to return to call..."
246+msgstr ""
247+
248+#: qml/Panel/ActiveCallHint.qml:92
249+msgid "Conference"
250+msgstr ""
251+
252+#: qml/Panel/Indicators/MenuItemFactory.qml:643
253+msgid "Nothing is playing"
254+msgstr ""
255+
256+#: qml/Panel/Indicators/MenuItemFactory.qml:791
257+msgid "In queue…"
258+msgstr ""
259+
260+#: qml/Panel/Indicators/MenuItemFactory.qml:795
261+msgid "Downloading"
262+msgstr ""
263+
264+#: qml/Panel/Indicators/MenuItemFactory.qml:797
265+msgid "Paused, tap to resume"
266+msgstr ""
267+
268+#: qml/Panel/Indicators/MenuItemFactory.qml:799
269+msgid "Canceled"
270+msgstr ""
271+
272+#: qml/Panel/Indicators/MenuItemFactory.qml:801
273+msgid "Finished"
274+msgstr ""
275+
276+#: qml/Panel/Indicators/MenuItemFactory.qml:803
277+msgid "Failed, tap to retry"
278+msgstr ""
279+
280+#: qml/Panel/Indicators/MessageMenuItemFactory.qml:149
281+msgid "Reply"
282+msgstr ""
283+
284+#: qml/Panel/Indicators/ModemInfoItem.qml:105
285+msgid "Unlock SIM"
286+msgstr ""
287+
288+#: qml/Panel/Indicators/RoamingIndication.qml:27
289+msgid "Roaming"
290+msgstr ""
291+
292+#: qml/Shell.qml:390
293+#, qt-format
294+msgid "Enter %1"
295+msgstr ""
296+
297+#: qml/Shell.qml:391 qml/Wizard/Pages/passwd-set.qml:60
298+msgid "Enter passphrase"
299+msgstr ""
300+
301+#: qml/Shell.qml:392
302+msgid "Enter passcode"
303+msgstr ""
304+
305+#: qml/Shell.qml:393
306+#, qt-format
307+msgid "Sorry, incorrect %1"
308+msgstr ""
309+
310+#: qml/Shell.qml:394
311+msgid "Sorry, incorrect passphrase"
312+msgstr ""
313+
314+#: qml/Shell.qml:395
315+msgid "Please re-enter"
316+msgstr ""
317+
318+#: qml/Shell.qml:396
319+msgid "Sorry, incorrect passcode"
320+msgstr ""
321+
322+#: qml/Shell.qml:490 qml/Wizard/Pages/passwd-confirm.qml:53
323+msgid "Sorry, incorrect passphrase."
324+msgstr ""
325+
326+#: qml/Shell.qml:491 qml/Wizard/Pages/passwd-confirm.qml:54
327+msgid "Sorry, incorrect passcode."
328+msgstr ""
329+
330+#: qml/Shell.qml:492
331+msgid "This will be your last attempt."
332+msgstr ""
333+
334+#: qml/Shell.qml:494
335+msgid ""
336+"If passphrase is entered incorrectly, your phone will conduct a factory "
337+"reset and all personal data will be deleted."
338+msgstr ""
339+
340+#: qml/Shell.qml:495
341+msgid ""
342+"If passcode is entered incorrectly, your phone will conduct a factory reset "
343+"and all personal data will be deleted."
344+msgstr ""
345+
346+#: qml/Wizard/Page.qml:89
347+msgid "Back"
348+msgstr ""
349+
350+#: qml/Wizard/Pages/10-welcome.qml:27
351+msgid "Hi!"
352+msgstr ""
353+
354+#: qml/Wizard/Pages/10-welcome.qml:44
355+msgid "Welcome to your Ubuntu phone."
356+msgstr ""
357+
358+#: qml/Wizard/Pages/10-welcome.qml:52
359+msgid "Let’s get started."
360+msgstr ""
361+
362+#: qml/Wizard/Pages/10-welcome.qml:88 qml/Wizard/Pages/30-passwd-type.qml:128
363+#: qml/Wizard/Pages/40-wifi.qml:197 qml/Wizard/Pages/50-location.qml:131
364+#: qml/Wizard/Pages/60-reporting.qml:50 qml/Wizard/Pages/passwd-confirm.qml:82
365+#: qml/Wizard/Pages/passwd-set.qml:90
366+msgid "Continue"
367+msgstr ""
368+
369+#: qml/Wizard/Pages/20-sim.qml:25
370+msgid "Add a SIM card and restart your device"
371+msgstr ""
372+
373+#: qml/Wizard/Pages/20-sim.qml:55
374+msgid "Without it, you won’t be able to make calls or use text messaging."
375+msgstr ""
376+
377+#: qml/Wizard/Pages/20-sim.qml:69 qml/Wizard/Pages/40-wifi.qml:197
378+msgid "Skip"
379+msgstr ""
380+
381+#: qml/Wizard/Pages/30-passwd-type.qml:39
382+msgid "Lock security"
383+msgstr ""
384+
385+#: qml/Wizard/Pages/30-passwd-type.qml:74
386+msgid "Please select how you’d like to unlock your phone."
387+msgstr ""
388+
389+#: qml/Wizard/Pages/30-passwd-type.qml:97
390+msgid "Swipe"
391+msgstr ""
392+
393+#: qml/Wizard/Pages/30-passwd-type.qml:98
394+msgid "No security"
395+msgstr ""
396+
397+#: qml/Wizard/Pages/30-passwd-type.qml:100
398+msgid "Passcode"
399+msgstr ""
400+
401+#: qml/Wizard/Pages/30-passwd-type.qml:101
402+msgid "4 digits only"
403+msgstr ""
404+
405+#: qml/Wizard/Pages/30-passwd-type.qml:103
406+msgid "Passphrase"
407+msgstr ""
408+
409+#: qml/Wizard/Pages/30-passwd-type.qml:104
410+msgid "Numbers and letters"
411+msgstr ""
412+
413+#: qml/Wizard/Pages/40-wifi.qml:28
414+msgid "Connect to Wi‑Fi"
415+msgstr ""
416+
417+#: qml/Wizard/Pages/40-wifi.qml:147
418+msgid "Available networks…"
419+msgstr ""
420+
421+#: qml/Wizard/Pages/40-wifi.qml:148
422+msgid "No available networks."
423+msgstr ""
424+
425+#: qml/Wizard/Pages/50-location.qml:27
426+msgid "Location"
427+msgstr ""
428+
429+#: qml/Wizard/Pages/50-location.qml:62
430+msgid "Let the phone detect your location:"
431+msgstr ""
432+
433+#: qml/Wizard/Pages/50-location.qml:69
434+msgid "Using GPS only (less accurate)"
435+msgstr ""
436+
437+#: qml/Wizard/Pages/50-location.qml:86
438+msgid "Using GPS, anonymized Wi-Fi and cellular network info (recommended)"
439+msgstr ""
440+
441+#. TRANSLATORS: HERE is a trademark for Nokia's location service, you probably shouldn't translate it
442+#: qml/Wizard/Pages/50-location.qml:103
443+msgid ""
444+"By selecting this option you agree to the Nokia HERE <a href='#'>terms and "
445+"conditions</a>."
446+msgstr ""
447+
448+#: qml/Wizard/Pages/50-location.qml:112
449+msgid "Not at all"
450+msgstr ""
451+
452+#: qml/Wizard/Pages/50-location.qml:124
453+msgid "You can change your mind later in <b>System Settings</b>."
454+msgstr ""
455+
456+#: qml/Wizard/Pages/60-reporting.qml:24
457+msgid "Improving your experience"
458+msgstr ""
459+
460+#: qml/Wizard/Pages/60-reporting.qml:36
461+msgid ""
462+"Your phone is set up to automatically report errors to Canonical and its "
463+"partners, the makers of the operating system."
464+msgstr ""
465+
466+#: qml/Wizard/Pages/60-reporting.qml:43
467+msgid ""
468+"This can be disabled in <b>System Settings</b> under <b>Security &amp; "
469+"Privacy</b>"
470+msgstr ""
471+
472+#: qml/Wizard/Pages/80-finished.qml:24
473+msgid "All done"
474+msgstr ""
475+
476+#: qml/Wizard/Pages/80-finished.qml:39
477+msgid "Nice work!"
478+msgstr ""
479+
480+#: qml/Wizard/Pages/80-finished.qml:46
481+msgid "Your phone is now ready to use."
482+msgstr ""
483+
484+#: qml/Wizard/Pages/80-finished.qml:53
485+msgid "Finish"
486+msgstr ""
487+
488+#: qml/Wizard/Pages/here-terms.qml:27
489+msgid "Terms & Conditions"
490+msgstr ""
491+
492+#: qml/Wizard/Pages/passwd-confirm.qml:49
493+msgid "Confirm passphrase"
494+msgstr ""
495+
496+#: qml/Wizard/Pages/passwd-confirm.qml:50
497+msgid "Confirm passcode"
498+msgstr ""
499+
500+#: qml/Wizard/Pages/passwd-confirm.qml:53
501+#: qml/Wizard/Pages/passwd-confirm.qml:54
502+msgid "Please try again."
503+msgstr ""
504+
505+#: qml/Wizard/Pages/passwd-set.qml:61
506+msgid "Choose your passcode"
507+msgstr ""
508+
509+#: qml/Wizard/Pages/passwd-set.qml:68
510+msgid "Passphrase must be 4 characters long"
511+msgstr ""
512+
513+#: qml/Wizard/StackButton.qml:39
514+#, qt-format
515+msgid "〈 %1"
516+msgstr ""
517+
518+#: qml/Wizard/StackButton.qml:42
519+#, qt-format
520+msgid "%1 〉"
521+msgstr ""
qml/Dash/GenericScopeView.qml+1
@@ -608,6 +608,7 @@ FocusScope {
608608 onBackClicked: scopeView.backClicked()
609609 onSettingsClicked: subPageLoader.openSubPage("settings")
610610 onFavoriteClicked: scopeView.scope.favorite = !scopeView.scope.favorite
611+ onSearchTextFieldFocused: scopeView.showHeader()
611612 }
612613 }
613614 }
qml/Dash/PageHeader.qml+2
@@ -51,6 +51,7 @@ Item {
5151 signal storeClicked()
5252 signal settingsClicked()
5353 signal favoriteClicked()
54+ signal searchTextFieldFocused()
5455
5556 onScopeStyleChanged: refreshLogo()
5657 onSearchQueryChanged: {
@@ -228,6 +229,7 @@ Item {
228229
229230 onActiveFocusChanged: {
230231 if (activeFocus) {
232+ root.searchTextFieldFocused();
231233 root.openSearchHistory();
232234 }
233235 }
qml/Greeter/CoverPage.qml+1
@@ -89,6 +89,7 @@ Showable {
8989 objectName: "infographics"
9090 height: parent.height
9191 model: root.infographicModel
92+ clip: true // clip large data bubbles
9293
9394 anchors {
9495 verticalCenter: parent.verticalCenter
qml/Launcher/LauncherPanel.qml+7−5
@@ -149,14 +149,14 @@ Rectangle {
149149 id: snapToBottomAnimation
150150 target: launcherListView
151151 property: "contentY"
152− to: launcherListView.originY
152+ to: launcherListView.originY + launcherListView.topMargin
153153 }
154154
155155 UbuntuNumberAnimation {
156156 id: snapToTopAnimation
157157 target: launcherListView
158158 property: "contentY"
159− to: launcherListView.contentHeight - launcherListView.height + launcherListView.originY
159+ to: launcherListView.contentHeight - launcherListView.height + launcherListView.originY - launcherListView.topMargin
160160 }
161161
162162 displaced: Transition {
@@ -521,12 +521,12 @@ Rectangle {
521521 left: parent.left
522522 leftMargin: (quickList.item.width - units.gu(1)) / 2 - width / 2
523523 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)
525525 }
526526 height: units.gu(1)
527527 width: units.gu(2)
528528 source: "graphics/quicklist_tooltip.png"
529− rotation: quickList.offset > 0 ? 0 : 180
529+ rotation: (quickList.offset > 0 ? 0 : 180) + (root.inverted ? 0 : 180)
530530 }
531531
532532 InverseMouseArea {
@@ -543,7 +543,9 @@ Rectangle {
543543 id: quickList
544544 objectName: "quickList"
545545 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)
547549 height: quickListColumn.height
548550 visible: quickListShape.visible
549551 anchors {
qml/Notifications/Notification.qml+36−6
@@ -40,6 +40,7 @@ Item {
4040 property bool fullscreen: false
4141 property int maxHeight
4242 property int margins
43+ readonly property bool draggable: (type === Notification.SnapDecision && state === "contracted") || type === Notification.Interactive || type === Notification.Ephemeral
4344 readonly property bool darkOnBright: panel.indicators.shown || type === Notification.SnapDecision
4445 readonly property color red: "#fc4949"
4546 readonly property color green: "#3fb24f"
@@ -52,7 +53,7 @@ Item {
5253 implicitHeight: type !== Notification.PlaceHolder ? (fullscreen ? maxHeight : outterColumn.height - shapedBack.anchors.topMargin + contentSpacing * 2) : 0
5354
5455 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
5657
5758 state: {
5859 var result = "";
@@ -80,18 +81,28 @@ Item {
8081 id: sound
8182 objectName: "sound"
8283 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"] : ""
8485 }
8586
8687 // FIXME: using onCompleted because of LP: #1354406 workaround, has to be onOpacityChanged really
8788 Component.onCompleted: {
88− if (opacity == 1.0 && hints["suppress-sound"] != "true" && sound.source != "") {
89+ if (opacity == 1.0 && hints["suppress-sound"] !== "true" && sound.source !== "") {
8990 sound.play();
9091 }
9192 }
9293
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+
93104 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 !== "") {
95106 sound.play();
96107 }
97108 }
@@ -146,6 +157,12 @@ Item {
146157 opacity: parent.opacity
147158 }
148159
160+ onXChanged: {
161+ if (draggable && notification.x > 0.75 * notification.width) {
162+ notification.notification.close()
163+ }
164+ }
165+
149166 Item {
150167 id: contents
151168 anchors.fill: fullscreen ? nonShapedBack : shapedBack
@@ -169,7 +186,7 @@ Item {
169186 actions: paths.actions
170187 menuObjectPath: paths.menuObjectPath
171188 onNameOwnerChanged: {
172− if (lastNameOwner != "" && nameOwner == "" && notification.notification != undefined) {
189+ if (lastNameOwner !== "" && nameOwner === "" && notification.notification !== undefined) {
173190 notification.notification.close()
174191 }
175192 lastNameOwner = nameOwner
@@ -181,6 +198,12 @@ Item {
181198
182199 anchors.fill: parent
183200 objectName: "interactiveArea"
201+
202+ drag.target: draggable ? notification : undefined
203+ drag.axis: Drag.XAxis
204+ drag.minimumX: 0
205+ drag.maximumX: notification.width
206+
184207 onClicked: {
185208 if (notification.type == Notification.Interactive) {
186209 notification.notification.invokeAction(actionRepeater.itemAt(0).actionId)
@@ -188,6 +211,13 @@ Item {
188211 notificationList.currentIndex = index;
189212 }
190213 }
214+ onReleased: {
215+ if (notification.x < notification.width / 2) {
216+ notification.x = 0
217+ } else {
218+ notification.x = notification.width
219+ }
220+ }
191221 }
192222
193223 Column {
@@ -447,7 +477,7 @@ Item {
447477 right: parent.right
448478 margins: contentSpacing
449479 }
450− visible: notification.type == Notification.SnapDecision && actionRepeater.count > 0 && !oneOverTwoCase.visible
480+ visible: notification.type === Notification.SnapDecision && actionRepeater.count > 0 && !oneOverTwoCase.visible
451481 spacing: units.gu(2)
452482 layoutDirection: Qt.RightToLeft
453483
qml/Notifications/Notifications.qml+2−6
@@ -41,15 +41,11 @@ ListView {
4141 property bool topmostIsFullscreen: false
4242 spacing: topmostIsFullscreen ? 0 : units.gu(.5)
4343
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
4645
4746 delegate: Notification {
4847 objectName: "notification" + index
49− anchors {
50− left: parent.left
51− right: parent.right
52− }
48+ width: parent.width
5349 type: model.type
5450 hints: model.hints
5551 iconSource: model.icon
qml/Panel/Panel.qml+5−3
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013-2014 Canonical, Ltd.
2+ * Copyright (C) 2013-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -90,6 +90,9 @@ Item {
9090 right: indicators.left
9191 }
9292 saturation: 1 - indicators.unitProgress
93+
94+ // Don't let input event pass trough
95+ MouseArea { anchors.fill: parent }
9396 }
9497
9598 Image {
@@ -110,8 +113,7 @@ Item {
110113 right: indicators.left
111114 }
112115 height: indicators.minimizedPanelHeight
113− enabled: callHint.visible
114− onClicked: callHint.showLiveCall()
116+ onClicked: { if (callHint.visible) { callHint.showLiveCall(); } }
115117 }
116118
117119 IndicatorsMenu {
qml/Shell.qml+3−1
@@ -477,7 +477,9 @@ Item {
477477 expandedPanelHeight: units.gu(7)
478478
479479 indicatorsModel: Indicators.IndicatorsModel {
480− Component.onCompleted: load(indicatorProfile);
480+ // TODO: This should be sourced by device type (e.g. "desktop", "tablet", "phone"...)
481+ profile: indicatorProfile
482+ Component.onCompleted: load()
481483 }
482484 }
483485 callHint {
qml/Stages/DesktopStage.qml+2−2
@@ -38,8 +38,6 @@ Item {
3838 Connections {
3939 target: ApplicationManager
4040 onApplicationAdded: {
41− // Initial placement to avoid having the window decoration behind the panel
42− appRepeater.itemAt(ApplicationManager.count-1).y = units.gu(3)
4341 ApplicationManager.requestFocusApplication(ApplicationManager.get(ApplicationManager.count-1).appId)
4442 }
4543
@@ -91,6 +89,7 @@ Item {
9189 delegate: Item {
9290 id: appDelegate
9391 z: ApplicationManager.count - index
92+ y: units.gu(3)
9493 width: units.gu(60)
9594 height: units.gu(50)
9695
@@ -121,6 +120,7 @@ Item {
121120 minWidth: appDelegate.minWidth
122121 minHeight: appDelegate.minHeight
123122 resizeHandleWidth: units.gu(0.5)
123+ windowId: model.appId // FIXME: Change this to point to windowId once we have such a thing
124124
125125 onPressed: ApplicationManager.requestFocusApplication(model.appId)
126126 }
qml/Stages/WindowMoveResizeArea.qml+33−24
@@ -18,6 +18,7 @@
1818
1919import QtQuick 2.3
2020import Ubuntu.Components 1.1
21+import Utils 0.1
2122
2223MouseArea {
2324 id: root
@@ -26,7 +27,8 @@ MouseArea {
2627
2728 // The target item managed by this. Must be a parent or a sibling
2829 // 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: ""
3032 property int resizeHandleWidth: 0
3133 property int minWidth: 0
3234 property int minHeight: 0
@@ -37,8 +39,6 @@ MouseArea {
3739 readonly property int windowHeight: root.height - resizeHandleWidth * 2
3840
3941 property var startPoint
40− property int startWidth
41− property int startHeight
4242
4343 property bool resizeTop: false
4444 property bool resizeBottom: false
@@ -47,10 +47,18 @@ MouseArea {
4747
4848 }
4949
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+
5060 onPressed: {
51− priv.startPoint = mapToItem(null, Qt.point(mouse.x, mouse.y)).x;
52− priv.startWidth = root.width;
53− priv.startHeight = root.height;
61+ priv.startPoint = Qt.point(mouse.x, mouse.y);
5462 priv.resizeTop = mouseY < root.resizeHandleWidth;
5563 priv.resizeBottom = mouseY > (root.height - root.resizeHandleWidth);
5664 priv.resizeLeft = mouseX < root.resizeHandleWidth;
@@ -58,32 +66,28 @@ MouseArea {
5866 }
5967
6068 onPositionChanged: {
61− var currentPoint = mapToItem(null, Qt.point(mouse.x, mouse.y)).x;
69+ var currentPoint = Qt.point(mouse.x, mouse.y);
6270 var mouseDiff = Qt.point(currentPoint.x - priv.startPoint.x, currentPoint.y - priv.startPoint.y);
6371 var moveDiff = Qt.point(0, 0);
6472 var sizeDiff = Qt.point(0, 0);
73+ var maxSizeDiff = Qt.point(root.minWidth - root.target.width, root.minHeight - root.target.height)
74+
6575 if (priv.resizeTop || priv.resizeBottom || priv.resizeLeft || priv.resizeRight) {
6676 if (priv.resizeTop) {
67− moveDiff.y += mouseDiff.y;
68− sizeDiff.y += -mouseDiff.y;
69− } else if (priv.resizeBottom) {
70− sizeDiff.y += mouse.y - priv.startPoint.y;
71− priv.startPoint.y = mouse.y
77+ sizeDiff.y = Math.max(maxSizeDiff.y, -currentPoint.y + priv.startPoint.y)
78+ moveDiff.y = -sizeDiff.y
7279 }
73− if (priv.resizeLeft) {
74− moveDiff.x += mouseDiff.x;
75− sizeDiff.x += -mouseDiff.x;
76− } else if (priv.resizeRight) {
77− sizeDiff.x += mouse.x - priv.startPoint.x;
78− priv.startPoint.x = mouse.x
80+ if (priv.resizeBottom) {
81+ sizeDiff.y = Math.max(maxSizeDiff.y, currentPoint.y - priv.startPoint.y)
82+ priv.startPoint.y += sizeDiff.y
7983 }
80− if (priv.windowWidth + sizeDiff.x < root.minWidth) {
81− sizeDiff.x = root.minWidth - priv.windowWidth;
82− moveDiff.x = moveDiff.x == 0 ? 0 : -sizeDiff.x;
84+ if (priv.resizeLeft) {
85+ sizeDiff.x = Math.max(maxSizeDiff.x, -currentPoint.x + priv.startPoint.x)
86+ moveDiff.x = -sizeDiff.x
8387 }
84− if (priv.windowHeight + sizeDiff.y < root.minHeight) {
85− sizeDiff.y = root.minHeight - priv.windowHeight;
86− moveDiff.y = moveDiff.y == 0 ? 0 : -sizeDiff.y;
88+ if (priv.resizeRight) {
89+ sizeDiff.x = Math.max(maxSizeDiff.x, currentPoint.x - priv.startPoint.x)
90+ priv.startPoint.x += sizeDiff.x
8791 }
8892
8993 target.x += moveDiff.x;
@@ -94,5 +98,10 @@ MouseArea {
9498 target.x += mouseDiff.x;
9599 target.y += mouseDiff.y;
96100 }
101+
102+ }
103+
104+ Component.onDestruction: {
105+ WindowStateStorage.saveGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height))
97106 }
98107}
tests/mocks/Unity/Indicators/fakeindicatorsmodel.cpp+13−1
@@ -21,7 +21,8 @@
2121#include "indicators.h"
2222
2323FakeIndicatorsModel::FakeIndicatorsModel(QObject *parent)
24− : QAbstractListModel(parent)
24+ : QAbstractListModel(parent),
25+ m_profile("phone")
2526{
2627 QObject::connect(this, SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SIGNAL(countChanged()));
2728 QObject::connect(this, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SIGNAL(countChanged()));
@@ -38,6 +39,17 @@ int FakeIndicatorsModel::count() const
3839 return rowCount();
3940}
4041
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+
4153void FakeIndicatorsModel::load(const QString&)
4254{
4355}
tests/mocks/Unity/Indicators/fakeindicatorsmodel.h+6
@@ -27,7 +27,9 @@ class FakeIndicatorsModel : public QAbstractListModel
2727 Q_OBJECT
2828 Q_ENUMS(Roles)
2929 Q_PROPERTY(int count READ count NOTIFY countChanged)
30+ Q_PROPERTY(QString profile READ profile WRITE setProfile NOTIFY profileChanged)
3031 Q_PROPERTY(QVariant modelData READ modelData WRITE setModelData NOTIFY modelDataChanged)
32+
3133public:
3234
3335 FakeIndicatorsModel(QObject *parent=0);
@@ -42,6 +44,8 @@ public:
4244
4345 Q_INVOKABLE QVariant data(int row, int role) const;
4446
47+ QString profile() const;
48+ void setProfile(const QString& profile);
4549 void setModelData(const QVariant& data);
4650 QVariant modelData() const { return m_modelData; }
4751
@@ -54,11 +58,13 @@ public:
5458
5559Q_SIGNALS:
5660 void countChanged();
61+ void profileChanged();
5762 void modelDataChanged();
5863
5964private:
6065 int count() const;
6166
67+ QString m_profile;
6268 QVariant m_modelData;
6369};
6470
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)
22
33include_directories(
44 ${CMAKE_CURRENT_SOURCE_DIR}
tests/mocks/Unity/Launcher/MockLauncherModel.cpp+10
@@ -209,6 +209,16 @@ void MockLauncherModel::setApplicationManager(unity::shell::application::Applica
209209 Q_UNUSED(applicationManager)
210210}
211211
212+bool MockLauncherModel::onlyPinned() const
213+{
214+ return false;
215+}
216+
217+void MockLauncherModel::setOnlyPinned(bool onlyPinned)
218+{
219+ Q_UNUSED(onlyPinned)
220+}
221+
212222void MockLauncherModel::emitHint()
213223{
214224 Q_EMIT hint();
tests/mocks/Unity/Launcher/MockLauncherModel.h+3
@@ -49,6 +49,9 @@ public:
4949 unity::shell::application::ApplicationManagerInterface *applicationManager() const;
5050 void setApplicationManager(unity::shell::application::ApplicationManagerInterface *applicationManager);
5151
52+ bool onlyPinned() const override;
53+ void setOnlyPinned(bool onlyPinned) override;
54+
5255 // For testing
5356 Q_INVOKABLE void emitHint();
5457
tests/mocks/Unity/Notifications/CMakeLists.txt+2−1
@@ -4,7 +4,8 @@ include_directories(
44
55set(MockNotificationsPlugin_SOURCES
66 plugin.cpp
7− MockNotificationTypes.cpp
7+ MockNotification.cpp
8+ MockNotificationModel.cpp
89 MockActionModel.cpp
910)
1011
tests/mocks/Unity/Notifications/MockActionModel.cpp+6−4
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2014 Canonical Ltd.
2+ * Copyright 2015 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU Lesser General Public License as published by
@@ -25,8 +25,6 @@ struct ActionModelPrivate {
2525};
2626
2727ActionModel::ActionModel(QObject *parent) : QStringListModel(parent), p(new ActionModelPrivate) {
28− insertAction("ok_id", "Ok");
29− insertAction("cancel_id", "Cancel");
3028}
3129
3230ActionModel::~ActionModel() {
@@ -66,7 +64,11 @@ Q_INVOKABLE QVariant ActionModel::data(int row, int role) const
6664 return data(index(row, 0), role);
6765}
6866
69−void ActionModel::insertAction(const QString &id, const QString &label) {
67+void ActionModel::append(const QString &id, const QString &label) {
7068 p->ids.push_back(id);
7169 p->labels.push_back(label);
7270}
71+
72+int ActionModel::getCount() const {
73+ return p->labels.size();
74+}
tests/mocks/Unity/Notifications/MockActionModel.h+4−2
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2014 Canonical Ltd.
2+ * Copyright 2015 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU Lesser General Public License as published by
@@ -26,6 +26,7 @@ struct ActionModelPrivate;
2626
2727class ActionModel : public QStringListModel {
2828 Q_OBJECT
29+ Q_PROPERTY(int count READ getCount)
2930
3031public:
3132 ActionModel(QObject *parent=nullptr);
@@ -42,7 +43,8 @@ public:
4243 };
4344 Q_INVOKABLE QVariant data(int row, int role) const;
4445
45− void insertAction(const QString &id, const QString &label);
46+ Q_INVOKABLE void append(const QString &id, const QString &label);
47+ int getCount() const;
4648
4749private:
4850 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 @@
11/*
2− * Copyright 2014 Canonical Ltd.
2+ * Copyright 2015 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU Lesser General Public License as published by
@@ -19,13 +19,15 @@
1919
2020#include "plugin.h"
2121#include "MockActionModel.h"
22−#include "MockNotificationTypes.h"
22+#include "MockNotification.h"
23+#include "MockNotificationModel.h"
2324
2425#include <QtQml/qqml.h>
2526
2627void TestNotificationPlugin::registerTypes(const char* uri)
2728{
2829 // @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");
3032 qmlRegisterType<ActionModel>(uri, 1, 0, "ActionModel");
3133}
tests/mocks/Unity/Notifications/plugin.h+1−2
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2014 Canonical Ltd.
2+ * Copyright 2015 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU Lesser General Public License as published by
@@ -17,7 +17,6 @@
1717 * Mirco Mueller <mirco.mueller@canonical.com>
1818 */
1919
20−
2120#ifndef TESTNOTIFICATION_PLUGIN_H
2221#define TESTNOTIFICATION_PLUGIN_H
2322
tests/mocks/libusermetrics/UserMetrics.cpp+1
@@ -312,6 +312,7 @@ void UserMetricsPrivate::generateFakeData()
312312 QVariantList firstMonth;
313313 while (firstMonth.size() < 17)
314314 firstMonth.push_back(QVariant(rand()));
315+ firstMonth[8] = QVariant(1.0); // oversized 9th day, to test clipping
315316 while (firstMonth.size() < 31)
316317 firstMonth.push_back(QVariant());
317318 QVariantList secondMonth;
tests/plugins/CMakeLists.txt+1
@@ -1,4 +1,5 @@
11add_subdirectory(AccountsService)
2+add_subdirectory(Greeter)
23add_subdirectory(LightDM)
34add_subdirectory(Dash)
45add_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:
5050 QVERIFY(!manager.isLoaded());
5151 QCOMPARE(manager.indicators().count(), 0);
5252
53− manager.load("test1");
53+ manager.setProfile("test1");
54+ manager.load();
5455
5556 QVERIFY(manager.isLoaded());
5657 QCOMPARE(manager.indicators().count(), 4);
@@ -67,7 +68,8 @@ private Q_SLOTS:
6768 void testPluginInterfaceProfile1()
6869 {
6970 IndicatorsManager manager;
70− manager.load("test1");
71+ manager.setProfile("test1");
72+ manager.load();
7173
7274 Indicator::Ptr indicator = manager.indicator("indicator-fake1");
7375 QVERIFY(indicator ? true : false);
@@ -90,7 +92,8 @@ private Q_SLOTS:
9092 void testPluginInterfaceProfile2()
9193 {
9294 IndicatorsManager manager;
93− manager.load("test2");
95+ manager.setProfile("test2");
96+ manager.load();
9497
9598 Indicator::Ptr indicator = manager.indicator("indicator-fake1");
9699 QVERIFY(indicator ? true : false);
@@ -107,13 +110,34 @@ private Q_SLOTS:
107110 QCOMPARE(props["menuObjectPath"].toString(), QString("/com/canonical/indicator/fake1/test2"));
108111 }
109112
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+
110133 /*
111134 * Test if a new plugin object is create for each different plugin
112135 */
113136 void testPluginInstance()
114137 {
115138 IndicatorsManager manager;
116− manager.load("test2");
139+ manager.setProfile("test2");
140+ manager.load();
117141
118142 Indicator::Ptr i0 = manager.indicator("indicator-fake1");
119143 Indicator::Ptr i1 = manager.indicator("indicator-fake1");
@@ -133,7 +157,8 @@ private Q_SLOTS:
133157 void testPluginInitAndShutdown()
134158 {
135159 IndicatorsManager manager;
136− manager.load("test1");
160+ manager.setProfile("test1");
161+ manager.load();
137162
138163 QWeakPointer<Indicator> wp0;
139164 QWeakPointer<Indicator> wp1;
tests/plugins/Unity/Indicators/indicatorsmodeltest.cpp+6−3
@@ -47,10 +47,11 @@ private Q_SLOTS:
4747 void testLoad()
4848 {
4949 IndicatorsModel model;
50+ model.setProfile("test1");
5051
5152 QCOMPARE(model.property("count").toInt(), 0);
5253
53− model.load("test1");
54+ model.load();
5455
5556 QCOMPARE(model.property("count").toInt(), 4);
5657
@@ -67,7 +68,8 @@ private Q_SLOTS:
6768 {
6869 // Priority order. (2, 1, 4, 3)
6970 IndicatorsModel model;
70− model.load("test1");
71+ model.setProfile("test1");
72+ model.load();
7173
7274 // should be in order:
7375 // fake3, fake4, fake1, fake2
@@ -104,7 +106,8 @@ private Q_SLOTS:
104106 {
105107 // Priority order. (2, 1, 4, 3)
106108 IndicatorsModel model;
107− model.load("test2");
109+ model.setProfile("test2");
110+ model.load();
108111
109112 // should be in order:
110113 // fake3, fake4, fake1, fake2
tests/plugins/Unity/Indicators/sharedunitymenumodeltest.cpp+2−2
@@ -61,7 +61,7 @@ private Q_SLOTS:
6161 QCOMPARE(model1->model(), model2->model());
6262 }
6363
64− void testSharedOwnership()
64+ void testSavedData()
6565 {
6666 QSharedPointer<SharedUnityMenuModel> model1(createFullModel("test1"));
6767 QSharedPointer<SharedUnityMenuModel> model2(createFullModel("test1"));
@@ -70,7 +70,7 @@ private Q_SLOTS:
7070 model1.clear();
7171 QCOMPARE(UnityMenuModelCache::singleton()->contains("/com/canonical/test1"), true);
7272 model2.clear();
73− QCOMPARE(UnityMenuModelCache::singleton()->contains("/com/canonical/test1"), false);
73+ QCOMPARE(UnityMenuModelCache::singleton()->contains("/com/canonical/test1"), true);
7474 }
7575
7676 // 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 @@
11pkg_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)
33pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=5)
44
55include_directories(
@@ -23,7 +23,9 @@ add_custom_target(launchermodeltest ${testModelCommand})
2323add_executable(launchermodeltestExec
2424 launchermodeltest.cpp
2525 gsettings.cpp
26+ AccountsServiceDBusAdaptor.cpp
2627 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/launchermodel.cpp
28+ ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/asadapter.cpp
2729 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/launcheritem.cpp
2830 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/quicklistmodel.cpp
2931 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/dbusinterface.cpp
tests/plugins/Unity/Launcher/launchermodeltest.cpp+65−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2013 Canonical Ltd.
2+ * Copyright 2013-2014 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU Lesser General Public License as published by
@@ -25,6 +25,8 @@
2525#include "launchermodel.h"
2626#include "dbusinterface.h"
2727#include "gsettings.h"
28+#include "asadapter.h"
29+#include "AccountsServiceDBusAdaptor.h"
2830
2931#include <QtTest>
3032#include <QDBusInterface>
@@ -129,6 +131,11 @@ private:
129131 LauncherModel *launcherModel;
130132 MockAppManager *appManager;
131133
134+ QList<QVariantMap> getASConfig() {
135+ AccountsServiceDBusAdaptor *as = launcherModel->m_asAdapter->m_accounts;
136+ return as->getUserProperty("", "", "LauncherItems").value<QList<QVariantMap>>();
137+ }
138+
132139private Q_SLOTS:
133140
134141 void initTestCase() {
@@ -467,6 +474,63 @@ private Q_SLOTS:
467474 QCOMPARE(launcherModel->get(1)->appId(), QString("abs-icon"));
468475 QCOMPARE(spy.count(), 2);
469476 }
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+ }
470534};
471535
472536QTEST_GUILESS_MAIN(LauncherModelTest)
tests/qmltests/Launcher/tst_Launcher.qml+46−19
@@ -15,6 +15,7 @@
1515 */
1616
1717import QtQuick 2.0
18+import QtQuick.Layouts 1.1
1819import QtTest 1.0
1920import Unity.Test 0.1 as UT
2021import Ubuntu.Components 1.1
@@ -74,10 +75,22 @@ Item {
7475 }
7576 }
7677
77− Button {
78+ ColumnLayout {
7879 anchors { bottom: parent.bottom; right: parent.right; margins: units.gu(1) }
79− text: "emit hinting signal"
80− onClicked: LauncherModel.emitHint()
80+ spacing: units.gu(1)
81+ width: units.gu(20)
82+
83+ Button {
84+ text: "emit hinting signal"
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+ }
8194 }
8295
8396 SignalSpy {
@@ -140,6 +153,18 @@ Item {
140153 tryCompare(panel, "x", -panel.width, 1000);
141154 }
142155
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+
143168 // Drag from the left edge of the screen rightwards and check that the launcher
144169 // appears (as if being dragged by the finger/pointer)
145170 function test_dragLeftEdgeToRevealLauncherAndTapCenterToDismiss() {
@@ -166,22 +191,22 @@ Item {
166191 launcherApplicationSelected("[...]dialer-app.desktop") */
167192 function test_clickingOnAppIconCausesSignalEmission() {
168193 dragLauncherIntoView();
169− launcher.lastSelectedApplication = ""
194+ launcher.lastSelectedApplication = "";
195+ launcher.inverted = false;
170196
171− var listView = findChild(launcher, "launcherListView");
172− listView.positionViewAtEnd();
197+ positionLauncherListAtBeginning();
173198
174− var appIcon = findChild(launcher, "launcherDelegate0")
199+ var appIcon = findChild(launcher, "launcherDelegate0");
175200
176− verify(appIcon != undefined)
201+ verify(appIcon != undefined);
177202
178− mouseClick(appIcon)
203+ mouseClick(appIcon);
179204
180205 tryCompare(launcher, "lastSelectedApplication",
181− "dialer-app")
206+ appIcon.appId);
182207
183208 // Tapping on an application icon also dismisses the launcher
184− waitUntilLauncherDisappears()
209+ waitUntilLauncherDisappears();
185210 }
186211
187212 /* If I click on the dash icon on the launcher
@@ -277,25 +302,26 @@ Item {
277302 function test_clickFlick_data() {
278303 var listView = findChild(launcher, "launcherListView");
279304 return [
280− {tag: "unfolded top", positionViewAtBeginning: false,
305+ {tag: "unfolded top", positionViewAtBeginning: true,
281306 clickY: listView.topMargin + units.gu(2),
282307 expectFlick: false},
283308
284− {tag: "folded top", positionViewAtBeginning: true,
309+ {tag: "folded top", positionViewAtBeginning: false,
285310 clickY: listView.topMargin + units.gu(2),
286311 expectFlick: true},
287312
288− {tag: "unfolded bottom", positionViewAtBeginning: true,
313+ {tag: "unfolded bottom", positionViewAtBeginning: false,
289314 clickY: listView.height - listView.topMargin - units.gu(1),
290315 expectFlick: false},
291316
292− {tag: "folded bottom", positionViewAtBeginning: false,
317+ {tag: "folded bottom", positionViewAtBeginning: true,
293318 clickY: listView.height - listView.topMargin - units.gu(1),
294319 expectFlick: true},
295320 ];
296321 }
297322
298323 function test_clickFlick(data) {
324+ launcher.inverted = false;
299325 launcher.lastSelectedApplication = "";
300326 dragLauncherIntoView();
301327 var listView = findChild(launcher, "launcherListView");
@@ -305,9 +331,9 @@ Item {
305331 // So for stability's sake we just put the listView in the position
306332 // we want to to actually start doing what this tests intends to check.
307333 if (data.positionViewAtBeginning) {
308− listView.positionViewAtBeginning();
334+ positionLauncherListAtBeginning();
309335 } else {
310− listView.positionViewAtEnd();
336+ positionLauncherListAtEnd();
311337 }
312338 tryCompare(listView, "flicking", false);
313339
@@ -442,9 +468,9 @@ Item {
442468 // Position launcher to where we need it
443469 var listView = findChild(launcher, "launcherListView");
444470 if (data.flickTo == "top") {
445− listView.positionViewAtEnd();
471+ positionLauncherListAtBeginning();
446472 } else {
447− listView.positionViewAtBeginning();
473+ positionLauncherListAtEnd();
448474 }
449475
450476 // Doing longpress
@@ -454,6 +480,7 @@ Item {
454480
455481 verify(quickList.y >= units.gu(1));
456482 verify(quickList.y + quickList.height + units.gu(1) <= launcher.height);
483+ compare(quickList.width, units.gu(30));
457484
458485 // Click somewhere in the empty space to dismiss the quicklist
459486 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 @@
11/*
2− * Copyright 2014 Canonical Ltd.
2+ * Copyright 2015 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU Lesser General Public License as published by
@@ -17,10 +17,15 @@
1717 * Mirco Mueller <mirco.mueller@canonical.com>
1818 */
1919
20−#include "MockNotificationTypes.h"
20+import Unity.Notifications 1.0
2121
22−MockNotification::MockNotification(QObject *parent) : QObject(parent) {
23−}
24−
25−MockNotification::~MockNotification() {
22+Notification {
23+ nid: 0
24+ type: Notification.PlaceHolder
25+ summary: ""
26+ body: ""
27+ icon: ""
28+ secondaryIcon: ""
29+ value: 0
30+ rawActions: []
2631}
tests/qmltests/Notifications/tst_Notifications.qml+271−213
@@ -1,17 +1,20 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright 2015 Canonical Ltd.
33 *
44 * 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
66 * the Free Software Foundation; version 3.
77 *
88 * This program is distributed in the hope that it will be useful,
99 * but WITHOUT ANY WARRANTY; without even the implied warranty of
1010 * 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.
1212 *
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
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authors:
17+ * Mirco Mueller <mirco.mueller@canonical.com>
1518 */
1619
1720import QtQuick 2.0
@@ -24,146 +27,134 @@ import Unity.Notifications 1.0
2427import QtMultimedia 5.0
2528
2629Item {
30+ id: foobar
31+
2732 width: notificationsRect.width + interactiveControls.width
2833 height: notificationsRect.height
34+ property int index: 0
2935
3036 Row {
3137 id: rootRow
3238
33− Component {
34− id: mockNotification
35−
36− QtObject {
37− function invokeAction(actionId) {
38− mockModel.actionInvoked(actionId)
39− }
40− }
41− }
42−
43− ListModel {
39+ NotificationModel {
4440 id: mockModel
45− dynamicRoles: true
46−
47− signal actionInvoked(string actionId)
48−
49− function getRaw(id) {
50− return mockNotification.createObject(mockModel)
51− }
5241
5342 // add the default/PlaceHolder notification to the model
5443 Component.onCompleted: {
55− var n = {
56− type: Notification.PlaceHolder,
57− hints: {},
58− summary: "",
59− body: "",
60− icon: "",
61− secondaryIcon: "",
62− actions: []
63− }
64−
44+ var component = Qt.createComponent("Notification.qml")
45+ var n = component.createObject("notification", {"nid": index++,
46+ "type": Notification.PlaceHolder,
47+ "hints": {},
48+ "summary": "",
49+ "body": "",
50+ "icon": "",
51+ "secondaryIcon": "",
52+ "rawActions": []})
53+ n.completed.connect(mockModel.onCompleted)
6554 append(n)
6655 }
6756 }
6857
6958 function add2over1SnapDecisionNotification() {
70− var n = {
71− type: Notification.SnapDecision,
72− hints: {"x-canonical-private-affirmative-tint": "true"},
73− summary: "Theatre at Ferria Stadium",
74− body: "at Ferria Stadium in Bilbao, Spain\n07578545317",
75− icon: "",
76− secondaryIcon: "",
77− actions: [{ id: "ok_id", label: "Ok"},
78− { id: "snooze_id", label: "Snooze"},
79− { id: "view_id", label: "View"}]
80− }
81−
59+ var component = Qt.createComponent("Notification.qml")
60+ var n = component.createObject("notification", {"nid": index++,
61+ "type": Notification.SnapDecision,
62+ "hints": {"x-canonical-private-affirmative-tint": "true"},
63+ "summary": "Theatre at Ferria Stadium",
64+ "body": "at Ferria Stadium in Bilbao, Spain\n07578545317",
65+ "icon": "",
66+ "secondaryIcon": "",
67+ "rawActions": ["ok_id", "Ok",
68+ "snooze_id", "Snooze",
69+ "view_id", "View"]})
70+ n.completed.connect(mockModel.onCompleted)
8271 mockModel.append(n)
8372 }
8473
8574 function addEphemeralNotification() {
86− var n = {
87− type: Notification.Ephemeral,
88− summary: "Cole Raby",
89− body: "I did not expect it to be that late.",
90− icon: "../graphics/avatars/amanda.png",
91− secondaryIcon: "../graphics/applicationIcons/facebook.png",
92− actions: []
93− }
94−
75+ var component = Qt.createComponent("Notification.qml")
76+ var n = component.createObject("notification", {"nid": index++,
77+ "type": Notification.Ephemeral,
78+ "hints": {},
79+ "summary": "Cole Raby",
80+ "body": "I did not expect it to be that late.",
81+ "icon": "../graphics/avatars/amanda.png",
82+ "secondaryIcon": "../graphics/applicationIcons/facebook.png",
83+ "rawActions": ["reply_id", "Dummy"]})
84+ n.completed.connect(mockModel.onCompleted)
9585 mockModel.append(n)
9686 }
9787
9888 function addEphemeralNonShapedIconNotification() {
99− var n = {
100− type: Notification.Ephemeral,
101− hints: {"x-canonical-non-shaped-icon": "true"},
102− summary: "Contacts",
103− body: "Synchronised contacts-database with cloud-storage.",
104− icon: "../graphics/applicationIcons/contacts-app.png",
105− secondaryIcon: "",
106− actions: []
107− }
108−
89+ var component = Qt.createComponent("Notification.qml")
90+ var n = component.createObject("notification", {"nid": index++,
91+ "type": Notification.Ephemeral,
92+ "hints": {"x-canonical-non-shaped-icon": "true"},
93+ "summary": "Contacts",
94+ "body": "Synchronised contacts-database with cloud-storage.",
95+ "icon": "../graphics/applicationIcons/contacts-app.png",
96+ "secondaryIcon": "",
97+ "rawActions": ["reply_id", "Dummy"]})
98+ n.completed.connect(mockModel.onCompleted)
10999 mockModel.append(n)
110100 }
111101
112102 function addEphemeralIconSummaryNotification() {
113− var n = {
114− type: Notification.Ephemeral,
115− hints: {"x-canonical-non-shaped-icon": "false"},
116− summary: "Photo upload completed",
117− body: "",
118− icon: "../graphics/applicationIcons/facebook.png",
119− secondaryIcon: "",
120− actions: []
121− }
122−
103+ var component = Qt.createComponent("Notification.qml")
104+ var n = component.createObject("notification", {"nid": index++,
105+ "type": Notification.Ephemeral,
106+ "hints": {"x-canonical-non-shaped-icon": "false"},
107+ "summary": "Photo upload completed",
108+ "body": "",
109+ "icon": "../graphics/applicationIcons/facebook.png",
110+ "secondaryIcon": "",
111+ "rawActions": ["reply_id", "Dummy"]})
112+ n.completed.connect(mockModel.onCompleted)
123113 mockModel.append(n)
124114 }
125115
126116 function addInteractiveNotification() {
127− var n = {
128− type: Notification.Interactive,
129− summary: "Interactive notification",
130− body: "This is a notification that can be clicked",
131− icon: "../graphics/avatars/anna_olsson.png",
132− secondaryIcon: "",
133− actions: [{ id: "reply_id", label: "Dummy"}],
134− }
135−
117+ var component = Qt.createComponent("Notification.qml")
118+ var n = component.createObject("notification", {"nid": index++,
119+ "type": Notification.Interactive,
120+ "hints": {},
121+ "summary": "Interactive notification",
122+ "body": "This is a notification that can be clicked",
123+ "icon": "../graphics/avatars/anna_olsson.png",
124+ "secondaryIcon": "",
125+ "rawActions": ["reply_id", "Dummy"]})
126+ n.completed.connect(mockModel.onCompleted)
136127 mockModel.append(n)
137128 }
138129
139130 function addConfirmationNotification() {
140− var n = {
141− type: Notification.Confirmation,
142− hints: {"x-canonical-non-shaped-icon": "true"},
143− summary: "Confirmation notification",
144− body: "",
145− icon: "image://theme/audio-volume-medium",
146− secondaryIcon: "",
147− value: 50,
148− actions: [],
149− }
150−
131+ var component = Qt.createComponent("Notification.qml")
132+ var n = component.createObject("notification", {"nid": index++,
133+ "type": Notification.Confirmation,
134+ "hints": {"x-canonical-non-shaped-icon": "true"},
135+ "summary": "Confirmation notification",
136+ "body": "",
137+ "icon": "image://theme/audio-volume-medium",
138+ "secondaryIcon": "",
139+ "value": 50,
140+ "rawActions": ["reply_id", "Dummy"]})
141+ n.completed.connect(mockModel.onCompleted)
151142 mockModel.append(n)
152143 }
153144
154145 function add2ndConfirmationNotification() {
155− var n = {
156− type: Notification.Confirmation,
157− hints: {"x-canonical-non-shaped-icon": "true",
158− "x-canonical-value-bar-tint": "true"},
159− summary: "Confirmation notification",
160− body: "High Volume",
161− icon: "image://theme/audio-volume-high",
162− secondaryIcon: "",
163− value: 85,
164− actions: [],
165− }
166−
146+ var component = Qt.createComponent("Notification.qml")
147+ var n = component.createObject("notification", {"nid": index++,
148+ "type": Notification.Confirmation,
149+ "hints": {"x-canonical-non-shaped-icon": "true",
150+ "x-canonical-value-bar-tint": "true"},
151+ "summary": "Confirmation notification",
152+ "body": "High Volume",
153+ "icon": "image://theme/audio-volume-high",
154+ "secondaryIcon": "",
155+ "value": 85,
156+ "rawActions": ["reply_id", "Dummy"]})
157+ n.completed.connect(mockModel.onCompleted)
167158 mockModel.append(n)
168159 }
169160
@@ -174,8 +165,9 @@ Item {
174165 }
175166
176167 function remove1stNotification() {
177− if (mockModel.count > 1)
178− mockModel.remove(1)
168+ if (mockModel.count > 1) {
169+ mockModel.removeSecond()
170+ }
179171 }
180172
181173 Rectangle {
@@ -273,41 +265,122 @@ Item {
273265 name: "NotificationRendererTest"
274266 when: windowShown
275267
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+
276379 function test_NotificationRenderer_data() {
277380 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− },
302381 {
303382 tag: "Ephemeral notification - icon-summary layout",
304− type: Notification.Ephemeral,
305− hints: {},
306− summary: "Photo upload completed",
307− body: "",
308− icon: "../graphics/applicationIcons/facebook.png",
309− secondaryIcon: "",
310− actions: [],
383+ n: nlist[0],
311384 summaryVisible: true,
312385 bodyVisible: false,
313386 iconVisible: true,
@@ -323,15 +396,7 @@ Item {
323396 },
324397 {
325398 tag: "Ephemeral notification - check suppression of secondary icon for icon-summary layout",
326− type: Notification.Ephemeral,
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: [],
399+ n: nlist[1],
335400 summaryVisible: true,
336401 bodyVisible: false,
337402 interactiveAreaEnabled: false,
@@ -348,14 +413,7 @@ Item {
348413 },
349414 {
350415 tag: "Interactive notification",
351− type: Notification.Interactive,
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"}],
416+ n: nlist[2],
359417 summaryVisible: true,
360418 bodyVisible: true,
361419 iconVisible: true,
@@ -371,15 +429,7 @@ Item {
371429 },
372430 {
373431 tag: "Snap Decision without secondary icon and no button-tint",
374− type: Notification.SnapDecision,
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"}],
432+ n: nlist[3],
383433 summaryVisible: true,
384434 bodyVisible: true,
385435 iconVisible: true,
@@ -395,14 +445,7 @@ Item {
395445 },
396446 {
397447 tag: "Ephemeral notification",
398− type: Notification.Ephemeral,
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: [],
448+ n: nlist[4],
406449 summaryVisible: true,
407450 bodyVisible: true,
408451 iconVisible: true,
@@ -418,14 +461,7 @@ Item {
418461 },
419462 {
420463 tag: "Ephemeral notification with non-shaped icon",
421− type: Notification.Ephemeral,
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: [],
464+ n: nlist[5],
429465 summaryVisible: true,
430466 bodyVisible: true,
431467 iconVisible: true,
@@ -441,14 +477,7 @@ Item {
441477 },
442478 {
443479 tag: "Confirmation notification with value",
444− type: Notification.Confirmation,
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: [],
480+ n: nlist[6],
452481 summaryVisible: false,
453482 bodyVisible: false,
454483 iconVisible: false,
@@ -464,15 +493,7 @@ Item {
464493 },
465494 {
466495 tag: "Confirmation notification with value, label and tint",
467− type: Notification.Confirmation,
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: [],
496+ n: nlist[7],
476497 summaryVisible: false,
477498 bodyVisible: false,
478499 iconVisible: false,
@@ -485,6 +506,22 @@ Item {
485506 valueVisible: true,
486507 valueLabelVisible: true,
487508 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
488525 }
489526 ]
490527 }
@@ -509,8 +546,14 @@ Item {
509546 }
510547
511548 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+
512555 // populate model with some mock notifications
513− mockModel.append(data)
556+ mockModel.append(data.n)
514557
515558 // make sure the view is properly updated before going on
516559 notifications.forceLayout();
@@ -548,9 +591,9 @@ Item {
548591
549592 // test input does not fall through
550593 mouseClick(notification)
551− if(data.type == Notification.Interactive) {
594+ if(data.n.type === Notification.Interactive) {
552595 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")
554597 }
555598 compare(clickThroughSpy.count, 0, "click on notification fell through")
556599
@@ -559,17 +602,19 @@ Item {
559602 compare(bodyLabel.visible, data.bodyVisible, "body-text visibility is incorrect")
560603 compare(buttonRow.visible, data.buttonRowVisible, "button visibility is incorrect")
561604
562− var audioItem = findInvisibleChild(notification, "sound")
563− compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state")
605+ if (data.hasSound) {
606+ var audioItem = findInvisibleChild(notification, "sound")
607+ compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state")
608+ }
564609
565610 if(data.buttonRowVisible) {
566611 var buttonCancel = findChild(buttonRow, "notify_button1")
567612 var buttonAccept = findChild(buttonRow, "notify_button0")
568613
569614 // 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) {
571616 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")
573618 actionSpy.clear()
574619 }
575620
@@ -578,36 +623,49 @@ Item {
578623
579624 // click the positive/right button
580625 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")
582627 actionSpy.clear()
583− waitForRendering (notification)
584628
585629 // 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) {
587631 var comboButton = findChild(notification, "notify_button2")
588− tryCompareFunction(function() { return comboButton.expanded == false; }, true);
632+ tryCompareFunction(function() { return comboButton.expanded === false; }, true);
589633
590634 // 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);
592636
593637 // try clicking on choices in expanded comboList
594638 var choiceButton1 = findChild(notification, "notify_button3")
595639 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")
597641 actionSpy.clear()
598642
599643 var choiceButton2 = findChild(notification, "notify_button4")
600644 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")
602646 actionSpy.clear()
603647
604648 // 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);
606650 } else {
607651 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")
609653 }
610654 }
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+ }
611669 }
612670 }
613671 }
tests/qmltests/Notifications/tst_OptionToggle.qml+11−6
@@ -1,17 +1,20 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright 2015 Canonical Ltd.
33 *
44 * 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
66 * the Free Software Foundation; version 3.
77 *
88 * This program is distributed in the hope that it will be useful,
99 * but WITHOUT ANY WARRANTY; without even the implied warranty of
1010 * 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.
1212 *
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
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authors:
17+ * Mirco Mueller <mirco.mueller@canonical.com>
1518 */
1619
1720import QtQuick 2.0
@@ -235,8 +238,10 @@ Row {
235238 compare(bodyLabel.visible, data.bodyVisible, "body-text visibility is incorrect")
236239 compare(buttonRow.visible, data.buttonRowVisible, "button visibility is incorrect")
237240
238− var audioItem = findInvisibleChild(notification, "sound")
239− compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state")
241+ if (data.hasSound) {
242+ var audioItem = findInvisibleChild(notification, "sound")
243+ compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state")
244+ }
240245
241246 if(data.buttonRowVisible) {
242247 var buttonCancel = findChild(buttonRow, "notify_button1")
tests/qmltests/Notifications/tst_SwipeToAct.qml+4
@@ -218,6 +218,10 @@ Item {
218218 // populate model with some mock notifications
219219 mockModel.append(data)
220220
221+ // add actions to action-model to test against
222+ myActionModel.append("ok_id", "Ok")
223+ myActionModel.append("cancel_id", "Cancel")
224+
221225 // make sure the view is properly updated before going on
222226 notifications.forceLayout();
223227 waitForRendering(notifications);
tests/qmltests/Panel/tst_Panel.qml+33−2
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2013-2014 Canonical Ltd.
2+ * Copyright 2013-2015 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -41,7 +41,12 @@ IndicatorTest {
4141 Layout.fillHeight: true
4242
4343 id: itemArea
44− color: "blue"
44+ color: backgroundMouseArea.pressed ? "red" : "blue"
45+
46+ MouseArea {
47+ id: backgroundMouseArea
48+ anchors.fill: parent
49+ }
4550
4651 Panel {
4752 id: panel
@@ -128,6 +133,12 @@ IndicatorTest {
128133 name: "Panel"
129134 when: windowShown
130135
136+ SignalSpy {
137+ id: backgroundPressedSpy
138+ target: backgroundMouseArea
139+ signalName: "pressedChanged"
140+ }
141+
131142 function init() {
132143 panel.fullscreenMode = false;
133144 callManager.foregroundCall = null;
@@ -140,6 +151,9 @@ IndicatorTest {
140151 // (switches between normal and fullscreen modes are animated)
141152 var indicatorArea = findChild(panel, "indicatorArea");
142153 tryCompare(indicatorArea, "y", 0);
154+
155+ backgroundPressedSpy.clear();
156+ compare(backgroundPressedSpy.valid, true);
143157 }
144158
145159 function get_indicator_item(index) {
@@ -299,5 +313,22 @@ IndicatorTest {
299313
300314 touchRelease(panel, mappedPosition.x, panel.minimizedPanelHeight / 2);
301315 }
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+ }
302333 }
303334}
tests/qmltests/Stages/tst_WindowMoveResizeArea.qml+117−18
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2014 Canonical Ltd.
2+ * Copyright 2014-2015 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -29,20 +29,46 @@ Item {
2929 height: units.gu(60)
3030 width: units.gu(60)
3131
32− Rectangle {
33− id: fakeWindow
34− height: units.gu(20)
35− width: units.gu(20)
36− color: "khaki"
37− WindowMoveResizeArea {
38− id: moveResizeArea
39− target: fakeWindow
40− resizeHandleWidth: units.gu(0.5)
41− minWidth: units.gu(15)
42− minHeight: units.gu(10)
32+ property var fakeWindow: windowLoader.item
33+
34+ Component {
35+ id: fakeWindowComponent
36+
37+ Item {
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)
43+ height: units.gu(20)
44+ width: units.gu(20)
45+
46+ WindowMoveResizeArea {
47+ id: moveResizeArea
48+ target: fakeWindow
49+ resizeHandleWidth: units.gu(0.5)
50+ minWidth: units.gu(15)
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+ }
4364 }
4465 }
4566
67+ Loader {
68+ id: windowLoader
69+ sourceComponent: fakeWindowComponent
70+ }
71+
4672 UT.UnityTestCase {
4773 when: windowShown
4874
@@ -100,8 +126,8 @@ Item {
100126 var startDragY = initialWindowY + initialWindowHeight + 1
101127 mouseFlick(root, startDragX, startDragY, startDragX + data.dx, startDragY + data.dy, true, true, units.gu(.5), 10);
102128
103− tryCompare(fakeWindow, "width", Math.max(initialWindowWidth + data.dx, moveResizeArea.minWidth));
104− tryCompare(fakeWindow, "height", Math.max(initialWindowHeight + data.dy, moveResizeArea.minHeight));
129+ tryCompare(fakeWindow, "width", Math.max(initialWindowWidth + data.dx, fakeWindow.minWidth));
130+ tryCompare(fakeWindow, "height", Math.max(initialWindowHeight + data.dy, fakeWindow.minHeight));
105131
106132 compare(fakeWindow.x, initialWindowX);
107133 compare(fakeWindow.y, initialWindowY);
@@ -126,13 +152,86 @@ Item {
126152 var startDragY = initialWindowY - 1
127153 mouseFlick(root, startDragX, startDragY, startDragX + data.dx, startDragY + data.dy, true, true, units.gu(.5), 10);
128154
129− tryCompare(fakeWindow, "width", Math.max(initialWindowWidth - data.dx, moveResizeArea.minWidth));
130− tryCompare(fakeWindow, "height", Math.max(initialWindowHeight - data.dy, moveResizeArea.minHeight));
155+ tryCompare(fakeWindow, "width", Math.max(initialWindowWidth - data.dx, fakeWindow.minWidth));
156+ tryCompare(fakeWindow, "height", Math.max(initialWindowHeight - data.dy, fakeWindow.minHeight));
131157
132− var maxMoveX = initialWindowWidth - moveResizeArea.minWidth;
133− var maxMoveY = initialWindowHeight - moveResizeArea.minHeight;
158+ var maxMoveX = initialWindowWidth - fakeWindow.minWidth;
159+ var maxMoveY = initialWindowHeight - fakeWindow.minHeight;
134160 compare(fakeWindow.x, Math.min(initialWindowX + data.dx, initialWindowX + maxMoveX));
135161 compare(fakeWindow.y, Math.min(initialWindowY + data.dy, initialWindowY + maxMoveY));
136162 }
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+ }
137236 }
138237}
tests/qmltests/tst_Shell.qml+29
@@ -26,6 +26,7 @@ import Ubuntu.Components 1.1
2626import Ubuntu.Telephony 0.1 as Telephony
2727import Unity.Application 0.1
2828import Unity.Connectivity 0.1
29+import Unity.Indicators 0.1
2930import Unity.Notifications 1.0
3031import Unity.Test 0.1 as UT
3132import Powerd 0.1
@@ -173,6 +174,7 @@ Item {
173174
174175 function init() {
175176 tryCompare(shell, "enabled", true); // enabled by greeter when ready
177+ shell.indicatorProfile = "phone";
176178
177179 swipeAwayGreeter();
178180
@@ -568,6 +570,33 @@ Item {
568570 && itemRectInShell.y + itemRectInShell.height <= shell.height;
569571 }
570572
573+ function test_greeterDoesNotChangeIndicatorProfile() {
574+ var panel = findChild(shell, "panel");
575+ tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile);
576+
577+ LightDM.Greeter.showGreeter();
578+ tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile);
579+
580+ LightDM.Greeter.hideGreeter();
581+ tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile);
582+ }
583+
584+ function test_shellProfileChangesReachIndicators() {
585+ var panel = findChild(shell, "panel");
586+
587+ shell.indicatorProfile = "test1";
588+ for (var i = 0; i < panel.indicators.indicatorsModel.count; ++i) {
589+ var properties = panel.indicators.indicatorsModel.data(i, IndicatorsModelRole.IndicatorProperties);
590+ verify(properties["menuObjectPath"].substr(-5), "test1");
591+ }
592+
593+ shell.indicatorProfile = "test2";
594+ for (var i = 0; i < panel.indicators.indicatorsModel.count; ++i) {
595+ var properties = panel.indicators.indicatorsModel.data(i, IndicatorsModelRole.IndicatorProperties);
596+ verify(properties["menuObjectPath"].substr(-5), "test2");
597+ }
598+ }
599+
571600 function test_focusRequestedHidesGreeter() {
572601 var greeter = findChild(shell, "greeter");
573602
tests/qmltests/tst_ShellWithPin.qml+16
@@ -230,6 +230,22 @@ Item {
230230 tryCompare(ApplicationManager, "focusedApplicationId", app)
231231 }
232232
233+ function test_greeterChangesIndicatorProfile() {
234+ skip("Not supported yet, waiting on design for new settings panel");
235+
236+ var panel = findChild(shell, "panel");
237+ tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile + "_greeter");
238+
239+ LightDM.Greeter.hideGreeter();
240+ tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile);
241+
242+ LightDM.Greeter.showGreeter();
243+ tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile + "_greeter");
244+
245+ LightDM.Greeter.hideGreeter();
246+ tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile);
247+ }
248+
233249 function test_login() {
234250 sessionSpy.clear()
235251 tryCompare(sessionSpy, "count", 0)