Merge lp:~mzanetti/unity8/rework-launcher-backend into lp:unity8

Proposed by Michael Zanetti
Status: Merged
Approved by: Albert Astals Cid
Approved revision: 1221
Merged at revision: 1276
Proposed branch: lp:~mzanetti/unity8/rework-launcher-backend
Merge into: lp:unity8
Prerequisite: lp:~aacid/unity8/gettext_textdomain
Diff against target: 2617 lines (+982/-1126)
29 files modified
data/unity8-filewatcher.conf (+8/-0)
debian/control (+4/-4)
debian/unity8.install (+1/-0)
plugins/Unity/Launcher/CMakeLists.txt (+5/-3)
plugins/Unity/Launcher/backend/launcherbackend.cpp (+0/-591)
plugins/Unity/Launcher/backend/launcherbackend.h (+0/-196)
plugins/Unity/Launcher/dbusinterface.cpp (+232/-0)
plugins/Unity/Launcher/dbusinterface.h (+50/-0)
plugins/Unity/Launcher/desktopfilehandler.cpp (+151/-0)
plugins/Unity/Launcher/desktopfilehandler.h (+56/-0)
plugins/Unity/Launcher/gsettings.cpp (+66/-0)
plugins/Unity/Launcher/gsettings.h (+37/-0)
plugins/Unity/Launcher/launcheritem.cpp (+30/-0)
plugins/Unity/Launcher/launcheritem.h (+20/-21)
plugins/Unity/Launcher/launchermodel.cpp (+86/-16)
plugins/Unity/Launcher/launchermodel.h (+15/-8)
plugins/Unity/Launcher/quicklistmodel.h (+1/-1)
qml/Launcher/LauncherDelegate.qml (+4/-2)
qml/Launcher/LauncherPanel.qml (+1/-0)
tests/mocks/Unity/Launcher/CMakeLists.txt (+1/-1)
tests/mocks/Unity/Launcher/MockLauncherItem.cpp (+14/-0)
tests/mocks/Unity/Launcher/MockLauncherItem.h (+19/-18)
tests/mocks/Unity/Launcher/MockLauncherModel.cpp (+10/-0)
tests/plugins/Unity/Launcher/CMakeLists.txt (+9/-24)
tests/plugins/Unity/Launcher/gsettings.cpp (+40/-0)
tests/plugins/Unity/Launcher/gsettings.h (+38/-0)
tests/plugins/Unity/Launcher/launcherbackendtest.cpp (+0/-239)
tests/plugins/Unity/Launcher/launchermodeltest.cpp (+81/-1)
tests/qmltests/Launcher/tst_Launcher.qml (+3/-1)
To merge this branch: bzr merge lp:~mzanetti/unity8/rework-launcher-backend
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Needs Fixing
Albert Astals Cid (community) Approve
Michał Sawicz packaging Approve
Ubuntu Unity PS integration team packaging Pending
Review via email: mp+232921@code.launchpad.net

This proposal supersedes a proposal from 2014-08-26.

Commit message

Rework LauncherBackend

* This splits the LauncherBackend class into 3 separate classes: DBusInterface, GSettings and DesktopFileHandler in order to reduce complexity
* Fixes all currently known backend related bugs
* Moves storage from AccountService to DConf (drops AS support for now)
* Add support for in .desktop file translations
* This breaks split greeter support in the launcher for now. We need a second LauncherPlugin for the session greeter which doesn't try to access .desktop files and ready its config from AccountsService

Description of the change

* Are there any related MPs required for this MP to build/function as expected? Please list.

https://code.launchpad.net/~mzanetti/unity-api/launcher-v4/+merge/232198
https://code.launchpad.net/~mzanetti/unity/new-key-in-launcher-schema/+merge/232199

 * Did you perform an exploratory manual test run of your code change and any related functionality?

yes (in phone-right-edge ppa)

 * Did you make sure that your branch does not contain spurious tags?

yes

 * If you changed the packaging (debian), did you subscribe the ubuntu-unity team to this MP?

yes

 * If you changed the UI, has there been a design review?

no

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) wrote :

Please add override to all the virtuals of plugins/Unity/Launcher/launcheritem.h that are reimplemented of LauncherItemInterface

review: Needs Fixing
Revision history for this message
Albert Astals Cid (aacid) wrote :

And i guess also for tests/mocks/Unity/Launcher/MockLauncherItem.h

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote :

> Please add override to all the virtuals of
> plugins/Unity/Launcher/launcheritem.h that are reimplemented of
> LauncherItemInterface

> And i guess also for tests/mocks/Unity/Launcher/MockLauncherItem.h

done

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michał Sawicz (saviq) wrote :

You need to bump the unity-launcher-impl- versions in debian/control.

review: Needs Fixing
Revision history for this message
Michael Zanetti (mzanetti) wrote :

> You need to bump the unity-launcher-impl- versions in debian/control.

Done.

Revision history for this message
Michał Sawicz (saviq) :
review: Approve (packaging)
Revision history for this message
Albert Astals Cid (aacid) wrote :

* Did you perform an exploratory manual test run of the code change and any related functionality?
Yes

 * Did CI run pass? If not, please explain why.
No, it needs extra packages, running the tests locally now.

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) wrote :

FAIL! : qmltestrunner::Launcher::test_countEmblems() Compared values are not the same
   Actual (): false
   Expected (): true
   Loc: [/home/tsdgeos_work/phablet/unity8/rework-launcher-backend/tests/qmltests/Launcher/tst_Launcher.qml(197)]

review: Needs Fixing
1220. By Michael Zanetti

improve countEmblem test and fix some warnings along the way

1221. By Michael Zanetti

make some count emblems visible in the mock

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) wrote :

Test fixed

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1222. By Michael Zanetti

don't load unity7 favorites but only work with the new items key

1223. By Michael Zanetti

merge trunk

1224. By Michael Zanetti

fix dependency on unity-schemas

1225. By Michael Zanetti

fix schema to reflect change in unity-schemas

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'data/unity8-filewatcher.conf'
2--- data/unity8-filewatcher.conf 1970-01-01 00:00:00 +0000
3+++ data/unity8-filewatcher.conf 2014-09-18 21:04:46 +0000
4@@ -0,0 +1,8 @@
5+description "File system watcher for unity8"
6+author "Michael Zanetti <michael.zanetti@canonical.com>"
7+
8+# Workaround for bug 1360208. ~ and * in one expression doesn't work currently
9+start on (file FILE=/home/phablet/.local/share/applications/*.desktop) or (file FILE=/usr/share/applications/*.desktop)
10+
11+exec dbus-send --type=method_call --dest=com.canonical.Unity.Launcher /com/canonical/Unity/Launcher com.canonical.Unity.Launcher.Refresh
12+
13
14=== modified file 'debian/control'
15--- debian/control 2014-09-03 07:58:32 +0000
16+++ debian/control 2014-09-18 21:04:46 +0000
17@@ -24,7 +24,7 @@
18 libpulse-dev,
19 libqmenumodel-dev (>= 0.2.8),
20 libqt5xmlpatterns5-dev,
21- libunity-api-dev (>= 7.89),
22+ libunity-api-dev (>= 7.90),
23 libusermetricsoutput1-dev,
24 libxcb1-dev,
25 pkg-config,
26@@ -87,7 +87,7 @@
27 qtdeclarative5-gsettings1.0,
28 qtdeclarative5-qtmir-plugin (>= 0.4.3),
29 qtdeclarative5-ubuntu-telephony0.1,
30- unity-launcher-impl-3,
31+ unity-launcher-impl-4,
32 unity8-common (= ${source:Version}),
33 unity8-private (= ${binary:Version}),
34 unity8-private | unity-launcher-impl,
35@@ -171,12 +171,12 @@
36 Depends: accountsservice-ubuntu-schemas (>= 0.0.3),
37 gsettings-ubuntu-schemas (>= 0.0.2+14.10.20140815),
38 libhardware2,
39- libunity-core-6.0-9,
40+ unity-schemas (>= 7.3.1+14.10.20140915),
41 pay-service,
42 ${misc:Depends},
43 ${shlibs:Depends},
44 Provides: unity-launcher-impl,
45- unity-launcher-impl-3,
46+ unity-launcher-impl-4,
47 Description: Unity 8 private libs
48 The Unity 8 shell is the primary user interface for Ubuntu devices.
49 .
50
51=== modified file 'debian/unity8.install'
52--- debian/unity8.install 2014-08-27 07:57:54 +0000
53+++ debian/unity8.install 2014-09-18 21:04:46 +0000
54@@ -1,5 +1,6 @@
55 data/unity8.conf usr/share/upstart/sessions/
56 data/unity8-dash.conf usr/share/upstart/sessions/
57+data/unity8-filewatcher.conf usr/share/upstart/sessions/
58 usr/bin/unity8
59 usr/bin/unity8-dash
60 usr/share/applications/unity8.desktop
61
62=== modified file 'plugins/Unity/Launcher/CMakeLists.txt'
63--- plugins/Unity/Launcher/CMakeLists.txt 2014-09-02 11:11:01 +0000
64+++ plugins/Unity/Launcher/CMakeLists.txt 2014-09-18 21:04:46 +0000
65@@ -1,5 +1,5 @@
66 include(FindPkgConfig)
67-pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=3)
68+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=4)
69 pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=3)
70 pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
71
72@@ -16,8 +16,10 @@
73 launchermodel.cpp
74 launcheritem.cpp
75 quicklistmodel.cpp
76- common/quicklistentry.cpp
77- backend/launcherbackend.cpp
78+ quicklistentry.cpp
79+ dbusinterface.cpp
80+ gsettings.cpp
81+ desktopfilehandler.cpp
82 ${CMAKE_SOURCE_DIR}/plugins/AccountsService/AccountsServiceDBusAdaptor.cpp
83 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherItemInterface.h
84 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherModelInterface.h
85
86=== removed directory 'plugins/Unity/Launcher/backend'
87=== removed file 'plugins/Unity/Launcher/backend/launcherbackend.cpp'
88--- plugins/Unity/Launcher/backend/launcherbackend.cpp 2014-09-01 15:24:00 +0000
89+++ plugins/Unity/Launcher/backend/launcherbackend.cpp 1970-01-01 00:00:00 +0000
90@@ -1,591 +0,0 @@
91-/*
92- * Copyright (C) 2013 Canonical, Ltd.
93- *
94- * Authors:
95- * Michael Terry <michael.terry@canonical.com>
96- * Michael Zanetti <michael.zanetti@canonical.com>
97- *
98- * This program is free software; you can redistribute it and/or modify
99- * it under the terms of the GNU General Public License as published by
100- * the Free Software Foundation; version 3.
101- *
102- * This program is distributed in the hope that it will be useful,
103- * but WITHOUT ANY WARRANTY; without even the implied warranty of
104- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
105- * GNU General Public License for more details.
106- *
107- * You should have received a copy of the GNU General Public License
108- * along with this program. If not, see <http://www.gnu.org/licenses/>.
109- */
110-
111-#include "AccountsServiceDBusAdaptor.h"
112-#include "launcherbackend.h"
113-
114-#include <QDir>
115-#include <QDBusArgument>
116-#include <QFileInfo>
117-#include <QGSettings>
118-#include <QDebug>
119-#include <QStandardPaths>
120-
121-#include <libintl.h>
122-
123-class LauncherBackendItem
124-{
125-public:
126- QString displayName;
127- QString icon;
128- int count;
129- bool countVisible;
130-};
131-
132-LauncherBackend::LauncherBackend(QObject *parent):
133- QDBusVirtualObject(parent),
134- m_accounts(nullptr)
135-{
136-#ifndef LAUNCHER_TESTING
137- m_accounts = new AccountsServiceDBusAdaptor(this);
138-#endif
139- m_user = qgetenv("USER");
140- syncFromAccounts();
141-
142- /* Set up ourselves on DBus */
143- QDBusConnection con = QDBusConnection::sessionBus();
144- if (!con.registerService("com.canonical.Unity.Launcher")) {
145- qDebug() << "Unable to register launcher name";
146- }
147- if (!con.registerVirtualObject("/com/canonical/Unity/Launcher", this, QDBusConnection::VirtualObjectRegisterOption::SubPath)) {
148- qDebug() << "Unable to register launcher object";
149- }
150-}
151-
152-LauncherBackend::~LauncherBackend()
153-{
154- /* Remove oursevles from DBus */
155- QDBusConnection con = QDBusConnection::sessionBus();
156- con.unregisterService("com.canonical.Unity.Launcher");
157- con.unregisterObject("/com/canonical/Unity/Launcher");
158-
159- /* Clear data */
160- m_storedApps.clear();
161-
162- Q_FOREACH(LauncherBackendItem *item, m_itemCache) {
163- delete item;
164- }
165- m_itemCache.clear();
166-}
167-
168-QStringList LauncherBackend::storedApplications() const
169-{
170- return m_storedApps;
171-}
172-
173-void LauncherBackend::setStoredApplications(const QStringList &appIds)
174-{
175- if (appIds.count() < m_storedApps.count()) {
176- Q_FOREACH(const QString &appId, m_storedApps) {
177- if (!appIds.contains(appId)) {
178- delete m_itemCache.take(appId);
179- }
180- }
181- }
182- m_storedApps = appIds;
183- Q_FOREACH(const QString &appId, appIds) {
184- if (!m_itemCache.contains(appId)) {
185- QString df = findDesktopFile(appId);
186- if (!df.isEmpty()) {
187- LauncherBackendItem *item = parseDesktopFile(df);
188- m_itemCache.insert(appId, item);
189- } else {
190- // Cannot find any data for that app... ignoring it.
191- qWarning() << "cannot find desktop file for" << appId << ". discarding app.";
192- m_storedApps.removeAll(appId);
193- }
194- }
195- }
196- syncToAccounts();
197-}
198-
199-QString LauncherBackend::desktopFile(const QString &appId) const
200-{
201- return findDesktopFile(appId);
202-}
203-
204-QString LauncherBackend::displayName(const QString &appId) const
205-{
206- LauncherBackendItem *item = m_itemCache.value(appId, nullptr);
207- if (item) {
208- return item->displayName;
209- }
210-
211- QString df = findDesktopFile(appId);
212- if (!df.isEmpty()) {
213- LauncherBackendItem *item = parseDesktopFile(df);
214- m_itemCache.insert(appId, item);
215- return item->displayName;
216- }
217-
218- return QString();
219-}
220-
221-QString LauncherBackend::icon(const QString &appId) const
222-{
223- QString iconName;
224- LauncherBackendItem *item = getItem(appId);
225- if (item) {
226- iconName = item->icon;
227- }
228-
229- return iconName;
230-}
231-
232-QList<QuickListEntry> LauncherBackend::quickList(const QString &appId) const
233-{
234- // TODO: Get static (from .desktop file) and dynamic (from the app itself)
235- // entries and return them here. Frontend related entries (like "Pin to launcher")
236- // don't matter here. This is just the backend part.
237- // TODO: emit quickListChanged() when the dynamic part changes
238- Q_UNUSED(appId)
239- return QList<QuickListEntry>();
240-}
241-
242-int LauncherBackend::progress(const QString &appId) const
243-{
244- // TODO: Return value for progress emblem.
245- // TODO: emit progressChanged() when this value changes.
246- Q_UNUSED(appId)
247- return -1;
248-}
249-
250-int LauncherBackend::count(const QString &appId) const
251-{
252- int count = -1;
253- LauncherBackendItem *item = getItem(appId);
254-
255- if (item) {
256- if (item->countVisible) {
257- count = item->count;
258- }
259- }
260-
261- return count;
262-}
263-
264-void LauncherBackend::setCount(const QString &appId, int count) const
265-{
266- LauncherBackendItem *item = getItem(appId);
267-
268- bool emitchange = false;
269- if (item) {
270- emitchange = (item->count != count);
271- item->count = count;
272- }
273-
274- if (emitchange) {
275- /* TODO: This needs to use the accessor to handle the visibility
276- correctly, but when we have the two properties we can just use
277- the local value */
278- Q_EMIT countChanged(appId, this->count(appId));
279- QVariant vcount(item->count);
280- emitPropChangedDbus(appId, "count", vcount);
281- }
282-}
283-
284-bool LauncherBackend::countVisible(const QString &appId) const
285-{
286- bool visible = false;
287- LauncherBackendItem *item = getItem(appId);
288-
289- if (item) {
290- visible = item->countVisible;
291- }
292-
293- return visible;
294-}
295-
296-void LauncherBackend::setCountVisible(const QString &appId, bool visible) const
297-{
298- LauncherBackendItem *item = getItem(appId);
299-
300- bool emitchange = false;
301- if (item) {
302- emitchange = (item->countVisible != visible);
303- item->countVisible = visible;
304- } else {
305- qDebug() << "Unable to find:" << appId;
306- }
307-
308- if (emitchange) {
309- /* TODO: Because we're using visible in determining the
310- count we need to emit a count changed as well */
311- Q_EMIT countChanged(appId, this->count(appId));
312- Q_EMIT countVisibleChanged(appId, item->countVisible);
313- QVariant vCountVisible(item->countVisible);
314- emitPropChangedDbus(appId, "countVisible", vCountVisible);
315- }
316-}
317-
318-void LauncherBackend::setUser(const QString &username)
319-{
320- if (qgetenv("USER") == "lightdm" && m_user != username) {
321- m_user = username;
322- syncFromAccounts();
323- }
324-}
325-
326-void LauncherBackend::triggerQuickListAction(const QString &appId, const QString &quickListId)
327-{
328- // TODO: execute the given quicklist action
329- Q_UNUSED(appId)
330- Q_UNUSED(quickListId)
331-}
332-
333-void LauncherBackend::syncFromAccounts()
334-{
335- QList<QVariantMap> apps;
336- bool defaults = true;
337-
338- m_storedApps.clear();
339-
340- if (m_accounts && !m_user.isEmpty()) {
341- QVariant variant = m_accounts->getUserProperty(m_user, "com.canonical.unity.AccountsService", "launcher-items");
342- if (variant.isValid() && variant.canConvert<QDBusArgument>()) {
343- apps = qdbus_cast<QList<QVariantMap>>(variant.value<QDBusArgument>());
344- defaults = isDefaultsItem(apps);
345- }
346- }
347-
348- if (m_accounts && defaults) { // Checking accounts as it'll be null when !useStorage
349- QGSettings gSettings("com.canonical.Unity.Launcher", "/com/canonical/unity/launcher/");
350- Q_FOREACH(const QString &entry, gSettings.get("favorites").toStringList()) {
351- if (entry.startsWith("application://")) {
352- QString appId = entry;
353- // Transform "application://foobar.desktop" to "application://foobar"
354- appId.remove("application://");
355- if (appId.endsWith(".desktop")) {
356- appId.chop(8);
357- }
358- QString df = findDesktopFile(appId);
359-
360- if (!df.isEmpty()) {
361- m_storedApps << appId;
362-
363- if (!m_itemCache.contains(appId)) {
364- m_itemCache.insert(appId, parseDesktopFile(df));
365- }
366- }
367- }
368- if (entry.startsWith("appid://")) {
369- QString appId = entry;
370- appId.remove("appid://");
371- // Strip hook name and current-user-version in case its there
372-
373- if (appId.split('/').count() != 3) {
374- qWarning() << "ignoring entry " + appId + ". Not a valid appId.";
375- continue;
376- }
377- appId = appId.split('/').first() + "_" + appId.split('/').at(1);
378- QString df = findDesktopFile(appId);
379-
380- if (!df.isEmpty()) {
381- m_storedApps << appId;
382-
383- if (!m_itemCache.contains(appId)) {
384- m_itemCache.insert(appId, parseDesktopFile(df));
385- }
386- }
387- }
388- }
389- } else {
390- for (const QVariant &app: apps) {
391- loadFromVariant(app.toMap());
392- }
393- }
394-}
395-
396-void LauncherBackend::syncToAccounts()
397-{
398- if (m_accounts && !m_user.isEmpty()) {
399- QList<QVariantMap> items;
400-
401- Q_FOREACH(const QString &appId, m_storedApps) {
402- items << itemToVariant(appId);
403- }
404-
405- m_accounts->setUserProperty(m_user, "com.canonical.unity.AccountsService", "launcher-items", QVariant::fromValue(items));
406- }
407-}
408-
409-QString LauncherBackend::findDesktopFile(const QString &appId) const
410-{
411- int dashPos = -1;
412- QString helper = appId;
413-
414- QStringList searchDirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation);
415-#ifdef LAUNCHER_TESTING
416- searchDirs << "";
417-#endif
418-
419- do {
420- if (dashPos != -1) {
421- helper = helper.replace(dashPos, 1, '/');
422- }
423-
424- Q_FOREACH(const QString &searchDirName, searchDirs) {
425- QDir searchDir(searchDirName);
426- Q_FOREACH(const QString &desktopFile, searchDir.entryList(QStringList() << "*.desktop")) {
427- if (desktopFile.startsWith(helper)) {
428- QFileInfo fileInfo(searchDir, desktopFile);
429- return fileInfo.absoluteFilePath();
430- }
431- }
432- }
433-
434- dashPos = helper.indexOf("-");
435- } while (dashPos != -1);
436-
437- return QString();
438-}
439-
440-LauncherBackendItem* LauncherBackend::parseDesktopFile(const QString &desktopFile) const
441-{
442- QSettings settings(desktopFile, QSettings::IniFormat);
443-
444- LauncherBackendItem* item = new LauncherBackendItem();
445-
446- item->displayName = settings.value("Desktop Entry/Name").toString();
447- const QString domain = settings.value("Desktop Entry/X-Ubuntu-Gettext-Domain").toString();
448- if (!domain.isEmpty()) {
449- item->displayName = dgettext(domain.toUtf8().constData(), item->displayName.toUtf8().constData());
450- }
451-
452- QString iconString = settings.value("Desktop Entry/Icon").toString();
453- QString pathString = settings.value("Desktop Entry/Path").toString();
454- if (QFileInfo(iconString).exists()) {
455- item->icon = QFileInfo(iconString).absoluteFilePath();
456- } else if (QFileInfo(pathString + '/' + iconString).exists()) {
457- item->icon = pathString + '/' + iconString;
458- } else {
459- item->icon = "image://theme/" + iconString;
460- }
461-
462- /* TODO: These should be looked up in a cache somewhere */
463- item->count = 0;
464- item->countVisible = false;
465-
466- return item;
467-}
468-
469-/* Gets an item, and tries to create a new one if we need it to */
470-LauncherBackendItem* LauncherBackend::getItem(const QString &appId) const
471-{
472- LauncherBackendItem *item = m_itemCache.value(appId, nullptr);
473- if (!item) {
474- QString df = findDesktopFile(appId);
475- if (!df.isEmpty()) {
476- item = parseDesktopFile(df);
477- if (item) {
478- m_itemCache[appId] = item;
479- } else {
480- qWarning() << "Unable to parse desktop file for" << appId << "path" << df;
481- }
482- } else {
483- qWarning() << "Unable to find desktop file for:" << appId;
484- }
485- }
486-
487- if (!item)
488- qWarning() << "Unable to find item for: " << appId;
489-
490- return item;
491-}
492-
493-void LauncherBackend::loadFromVariant(const QVariantMap &details)
494-{
495- if (!details.contains("id")) {
496- return;
497- }
498- QString appId = details.value("id").toString();
499-
500- LauncherBackendItem *item = m_itemCache.value(appId, nullptr);
501- if (item) {
502- delete item;
503- }
504-
505- item = new LauncherBackendItem();
506-
507- item->displayName = details.value("name").toString();
508- item->icon = details.value("icon").toString();
509- item->count = details.value("count").toInt();
510- item->countVisible = details.value("countVisible").toBool();
511-
512- m_itemCache.insert(appId, item);
513- m_storedApps.append(appId);
514-}
515-
516-QVariantMap LauncherBackend::itemToVariant(const QString &appId) const
517-{
518- LauncherBackendItem *item = m_itemCache.value(appId);
519- QVariantMap details;
520- details.insert("id", appId);
521- details.insert("name", item->displayName);
522- details.insert("icon", item->icon);
523- details.insert("count", item->count);
524- details.insert("countVisible", item->countVisible);
525- return details;
526-}
527-
528-bool LauncherBackend::isDefaultsItem(const QList<QVariantMap> &apps) const
529-{
530- // To differentiate between an empty list and a list that hasn't been set
531- // yet (and should thus be populated with the defaults), we use a special
532- // list of one item with the 'defaults' field set to true.
533- return (apps.size() == 1 && apps[0].value("defaults").toBool());
534-}
535-
536-bool LauncherBackend::handleMessage(const QDBusMessage& message, const QDBusConnection& connection)
537-{
538- /* Check to make sure we're getting properties on our interface */
539- if (message.type() != QDBusMessage::MessageType::MethodCallMessage) {
540- return false;
541- }
542- if (message.interface() != "org.freedesktop.DBus.Properties") {
543- return false;
544- }
545- if (message.arguments()[0].toString() != "com.canonical.Unity.Launcher.Item") {
546- return false;
547- }
548-
549- /* Break down the path to just the app id */
550- QString pathtemp = message.path();
551- if (!pathtemp.startsWith("/com/canonical/Unity/Launcher/")) {
552- return false;
553- }
554- pathtemp.remove("/com/canonical/Unity/Launcher/");
555- if (pathtemp.indexOf('/') >= 0) {
556- return false;
557- }
558-
559- /* Find ourselves an appid */
560- QString appid = decodeAppId(pathtemp);
561- QVariantList retval;
562-
563- if (message.member() == "Get") {
564- if (message.arguments()[1].toString() == "count") {
565- retval.append(QVariant::fromValue(QDBusVariant(this->count(appid))));
566- } else if (message.arguments()[1].toString() == "countVisible") {
567- retval.append(QVariant::fromValue(QDBusVariant(this->countVisible(appid))));
568- }
569- } else if (message.member() == "Set") {
570- if (message.arguments()[1].toString() == "count") {
571- this->setCount(appid, message.arguments()[2].value<QDBusVariant>().variant().toInt());
572- } else if (message.arguments()[1].toString() == "countVisible") {
573- this->setCountVisible(appid, message.arguments()[2].value<QDBusVariant>().variant().toBool());
574- }
575- } else if (message.member() == "GetAll") {
576- retval.append(this->itemToVariant(appid));
577- } else {
578- return false;
579- }
580-
581- QDBusMessage reply = message.createReply(retval);
582- return connection.send(reply);
583-}
584-
585-QString LauncherBackend::introspect(const QString &path) const
586-{
587- /* This case we should just list the nodes */
588- if (path == "/com/canonical/Unity/Launcher/" || path == "/com/canonical/Unity/Launcher") {
589- QString nodes;
590-
591- Q_FOREACH(const QString &appId, m_itemCache.keys()) {
592- nodes.append("<node name=\"");
593- nodes.append(encodeAppId(appId));
594- nodes.append("\"/>\n");
595- }
596-
597- return nodes;
598- }
599-
600- /* Should not happen, but let's handle it */
601- if (!path.startsWith("/com/canonical/Unity/Launcher")) {
602- return "";
603- }
604-
605- /* Now we should be looking at a node */
606- QString nodeiface =
607- "<interface name=\"com.canonical.Unity.Launcher.Item\">"
608- "<property name=\"count\" type=\"i\" access=\"readwrite\" />"
609- "<property name=\"countVisible\" type=\"b\" access=\"readwrite\" />"
610- "</interface>";
611- return nodeiface;
612-}
613-
614-QString LauncherBackend::decodeAppId(const QString& path)
615-{
616- QByteArray bytes = path.toUtf8();
617- QByteArray decoded;
618-
619- for (int i = 0; i < bytes.size(); ++i) {
620- char chr = bytes.at(i);
621-
622- if (chr == '_') {
623- QString number;
624- number.append(bytes.at(i+1));
625- number.append(bytes.at(i+2));
626-
627- bool okay;
628- char newchar = number.toUInt(&okay, 16);
629- if (okay)
630- decoded.append(newchar);
631-
632- i += 2;
633- } else {
634- decoded.append(chr);
635- }
636- }
637-
638- return QString::fromUtf8(decoded);
639-}
640-
641-QString LauncherBackend::encodeAppId(const QString& appId)
642-{
643- QByteArray bytes = appId.toUtf8();
644- QString encoded;
645-
646- for (int i = 0; i < bytes.size(); ++i) {
647- uchar chr = bytes.at(i);
648-
649- if ((chr >= 'a' && chr <= 'z') ||
650- (chr >= 'A' && chr <= 'Z') ||
651- (chr >= '0' && chr <= '9'&& i != 0)) {
652- encoded.append(chr);
653- } else {
654- QString hexval = QString("_%1").arg(chr, 2, 16, QChar('0'));
655- encoded.append(hexval.toUpper());
656- }
657- }
658-
659- return encoded;
660-}
661-
662-void LauncherBackend::emitPropChangedDbus(const QString& appId, const QString& property, QVariant &value) const
663-{
664- QString path("/com/canonical/Unity/Launcher/");
665- path.append(encodeAppId(appId));
666-
667- QDBusMessage message = QDBusMessage::createSignal(path, "org.freedesktop.DBus.Properties", "PropertiesChanged");
668-
669- QList<QVariant> arguments;
670- QVariantHash changedprops;
671- changedprops[property] = QVariant::fromValue(QDBusVariant(value));
672- QVariantList deletedprops;
673-
674- arguments.append(changedprops);
675- arguments.append(deletedprops);
676-
677- message.setArguments(arguments);
678-
679- QDBusConnection con = QDBusConnection::sessionBus();
680- con.send(message);
681-}
682
683=== removed file 'plugins/Unity/Launcher/backend/launcherbackend.h'
684--- plugins/Unity/Launcher/backend/launcherbackend.h 2014-06-11 15:36:51 +0000
685+++ plugins/Unity/Launcher/backend/launcherbackend.h 1970-01-01 00:00:00 +0000
686@@ -1,196 +0,0 @@
687-/* Copyright (C) 2013 Canonical, Ltd.
688- *
689- * Authors:
690- * Michael Zanetti <michael.zanetti@canonical.com>
691- *
692- * This program is free software; you can redistribute it and/or modify
693- * it under the terms of the GNU General Public License as published by
694- * the Free Software Foundation; version 3.
695- *
696- * This program is distributed in the hope that it will be useful,
697- * but WITHOUT ANY WARRANTY; without even the implied warranty of
698- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
699- * GNU General Public License for more details.
700- *
701- * You should have received a copy of the GNU General Public License
702- * along with this program. If not, see <http://www.gnu.org/licenses/>.
703- */
704-
705-#ifndef LAUNCHERBACKEND_H
706-#define LAUNCHERBACKEND_H
707-
708-#include "common/quicklistentry.h"
709-
710-#include <QObject>
711-#include <QSettings>
712-#include <QStringList>
713-#include <QDBusVirtualObject>
714-
715-class AccountsServiceDBusAdaptor;
716-
717-/**
718- * @brief An interface that provides all the data needed by the launcher.
719- */
720-
721-class LauncherBackendItem;
722-class LauncherBackendTest;
723-
724-class LauncherBackend : public QDBusVirtualObject
725-{
726- Q_OBJECT
727-
728- friend LauncherBackendTest;
729-
730-public:
731- LauncherBackend(QObject *parent = 0);
732- virtual ~LauncherBackend();
733-
734- /**
735- * @brief Returns a list of stored applications.
736- * @returns A list of application IDs.
737- */
738- QStringList storedApplications() const;
739-
740- /**
741- * @brief Set the list of stored applications.
742- *
743- * Any previously stored information for apps not contained
744- * in the list any more, e.g. the pinned state, will be
745- * discarded.
746- *
747- * @param appIds The new list of stored applications.
748- */
749- void setStoredApplications(const QStringList &appIds);
750-
751- /**
752- * @brief Get the full path to the .desktop file.
753- *
754- * The application does not need to be in the list of stored applications.
755- *
756- * @returns The full path to the .dekstop file.
757- */
758- QString desktopFile(const QString &appId) const;
759-
760- /**
761- * @brief Get the user friendly name of an application.
762- *
763- * The application does not need to be in the list of stored applications.
764- *
765- * @param appId The ID of the application.
766- * @returns The user friendly name of the application.
767- */
768- QString displayName(const QString &appId) const;
769-
770- /**
771- * @brief Get the icon of an application.
772- *
773- * The application does not need to be in the list of stored applications.
774- *
775- * @param appId The ID of the application.
776- * @returns The full path to the icon for the application.
777- */
778- QString icon(const QString &appId) const;
779-
780- /**
781- * @brief Get the QuickList for an application.
782- * @param appId The ID of the application.
783- * @returns A QuickListModelInterface containing the QuickList.
784- */
785- QList<QuickListEntry> quickList(const QString &appId) const;
786-
787- /**
788- * @brief Execute an action from the quickList
789- * @param appId The app ID for which the action was triggered
790- * @param the quicklist ID of the action that was triggered
791- */
792- void triggerQuickListAction(const QString &appId, const QString &entryId);
793-
794- /**
795- * @brief Get the progress for the progress overlay of an application.
796- * @param appId The ID of the application.
797- * @returns The percentage of the overlay progress bar. -1 if no progress bar available.
798- */
799- int progress(const QString &appId) const;
800-
801- /**
802- * @brief Get the count of the count overlay of an application.
803- * @param appId The ID of the application.
804- * @returns The number to be displayed in the overlay. -1 if no count overlay is available.
805- */
806- int count(const QString &appId) const;
807-
808- /**
809- * @brief Set the count on an item
810- * @param appId The ID of the application
811- * @param count Count to show on the application
812- */
813- void setCount(const QString &appId, int count) const;
814-
815- /**
816- * @brief Get whether the count should be visible
817- * @param appId The ID of the application.
818- * @returns Whether to show a count on the launcher
819- */
820- bool countVisible(const QString &appId) const;
821-
822- /**
823- * @brief Set the visibility of the count item
824- * @param appId The ID of the application
825- * @param visible Whether the count should be visible
826- */
827- void setCountVisible(const QString &appId, bool visible) const;
828-
829- /**
830- * @brief Sets the username for which to look up launcher items.
831- * @param username The username to use.
832- */
833- void setUser(const QString &username);
834-
835- /**
836- * @brief Handle a message to an application node
837- * @param message DBus message to handle
838- * @param connection DBus connection that we're using
839- * @returns whether the message was handled
840- */
841- virtual bool handleMessage(const QDBusMessage& message, const QDBusConnection& connection);
842-
843- /**
844- * @brief Get introspection information on the objects we're exporting
845- * @param path the dbus path containing the appid
846- * @returns Introspection information for that node in the tree
847- */
848- virtual QString introspect (const QString &path) const;
849-
850-Q_SIGNALS:
851- void quickListChanged(const QString &appId, const QList<QuickListEntry> &quickList) const;
852- void progressChanged(const QString &appId, int progress) const;
853- void countChanged(const QString &appId, int count) const;
854- void countVisibleChanged(const QString &appId, bool visible) const;
855-
856-private:
857- QString findDesktopFile(const QString &appId) const;
858- LauncherBackendItem* parseDesktopFile(const QString &desktopFile) const;
859-
860- QVariantMap itemToVariant(const QString &appId) const;
861- void loadFromVariant(const QVariantMap &details);
862-
863- bool isDefaultsItem(const QList<QVariantMap> &apps) const;
864- void syncFromAccounts();
865- void syncToAccounts();
866-
867- QList<QString> m_storedApps;
868- mutable QHash<QString, LauncherBackendItem*> m_itemCache;
869-
870- AccountsServiceDBusAdaptor *m_accounts;
871- QString m_user;
872-
873- void emitPropChangedDbus(const QString& appId, const QString& property, QVariant &value) const;
874-
875-protected: /* Protected to allow testing */
876- LauncherBackendItem* getItem(const QString& appId) const;
877-
878- static QString decodeAppId(const QString& path);
879- static QString encodeAppId(const QString& appId);
880-};
881-
882-#endif // LAUNCHERBACKEND_H
883
884=== removed directory 'plugins/Unity/Launcher/common'
885=== added file 'plugins/Unity/Launcher/dbusinterface.cpp'
886--- plugins/Unity/Launcher/dbusinterface.cpp 1970-01-01 00:00:00 +0000
887+++ plugins/Unity/Launcher/dbusinterface.cpp 2014-09-18 21:04:46 +0000
888@@ -0,0 +1,232 @@
889+/*
890+ * Copyright 2014 Canonical Ltd.
891+ *
892+ * This program is free software; you can redistribute it and/or modify
893+ * it under the terms of the GNU Lesser General Public License as published by
894+ * the Free Software Foundation; version 3.
895+ *
896+ * This program is distributed in the hope that it will be useful,
897+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
898+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
899+ * GNU Lesser General Public License for more details.
900+ *
901+ * You should have received a copy of the GNU Lesser General Public License
902+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
903+ *
904+ * Authors:
905+ * Michael Zanetti <michael.zanetti@canonical.com>
906+ */
907+
908+#include "dbusinterface.h"
909+#include "launchermodel.h"
910+#include "launcheritem.h"
911+
912+#include <QDBusArgument>
913+#include <QDBusConnection>
914+#include <QDBusMessage>
915+#include <QDebug>
916+
917+DBusInterface::DBusInterface(LauncherModel *parent):
918+ QDBusVirtualObject(parent),
919+ m_launcherModel(parent)
920+{
921+ /* Set up ourselves on DBus */
922+ QDBusConnection con = QDBusConnection::sessionBus();
923+ if (!con.registerService("com.canonical.Unity.Launcher")) {
924+ qWarning() << "Unable to register launcher name";
925+ }
926+ if (!con.registerVirtualObject("/com/canonical/Unity/Launcher", this, QDBusConnection::VirtualObjectRegisterOption::SubPath)) {
927+ qWarning() << "Unable to register launcher object";
928+ }
929+}
930+
931+DBusInterface::~DBusInterface()
932+{
933+ /* Remove oursevles from DBus */
934+ QDBusConnection con = QDBusConnection::sessionBus();
935+ con.unregisterService("com.canonical.Unity.Launcher");
936+ con.unregisterObject("/com/canonical/Unity/Launcher");
937+}
938+
939+QString DBusInterface::introspect(const QString &path) const
940+{
941+ /* This case we should just list the nodes */
942+ if (path == "/com/canonical/Unity/Launcher/" || path == "/com/canonical/Unity/Launcher") {
943+ QString nodes;
944+
945+ // Add Refresh to introspect
946+ nodes = "<interface name=\"com.canonical.Unity.Launcher\">"
947+ "<method name=\"Refresh\"/>"
948+ "</interface>";
949+
950+ // Add dynamic properties for launcher emblems
951+ for (int i = 0; i < m_launcherModel->rowCount(); i++) {
952+ nodes.append("<node name=\"");
953+ nodes.append(encodeAppId(m_launcherModel->get(i)->appId()));
954+ nodes.append("\"/>\n");
955+ }
956+ return nodes;
957+ }
958+
959+ /* Should not happen, but let's handle it */
960+ if (!path.startsWith("/com/canonical/Unity/Launcher")) {
961+ return "";
962+ }
963+
964+ /* Now we should be looking at a node */
965+ QString nodeiface =
966+ "<interface name=\"com.canonical.Unity.Launcher.Item\">"
967+ "<property name=\"count\" type=\"i\" access=\"readwrite\" />"
968+ "<property name=\"countVisible\" type=\"b\" access=\"readwrite\" />"
969+ "</interface>";
970+ return nodeiface;
971+}
972+
973+
974+QString DBusInterface::decodeAppId(const QString& path)
975+{
976+ QByteArray bytes = path.toUtf8();
977+ QByteArray decoded;
978+
979+ for (int i = 0; i < bytes.size(); ++i) {
980+ char chr = bytes.at(i);
981+
982+ if (chr == '_') {
983+ QString number;
984+ number.append(bytes.at(i+1));
985+ number.append(bytes.at(i+2));
986+
987+ bool okay;
988+ char newchar = number.toUInt(&okay, 16);
989+ if (okay)
990+ decoded.append(newchar);
991+
992+ i += 2;
993+ } else {
994+ decoded.append(chr);
995+ }
996+ }
997+
998+ return QString::fromUtf8(decoded);
999+}
1000+
1001+QString DBusInterface::encodeAppId(const QString& appId)
1002+{
1003+ QByteArray bytes = appId.toUtf8();
1004+ QString encoded;
1005+
1006+ for (int i = 0; i < bytes.size(); ++i) {
1007+ uchar chr = bytes.at(i);
1008+
1009+ if ((chr >= 'a' && chr <= 'z') ||
1010+ (chr >= 'A' && chr <= 'Z') ||
1011+ (chr >= '0' && chr <= '9'&& i != 0)) {
1012+ encoded.append(chr);
1013+ } else {
1014+ QString hexval = QString("_%1").arg(chr, 2, 16, QChar('0'));
1015+ encoded.append(hexval.toUpper());
1016+ }
1017+ }
1018+
1019+ return encoded;
1020+}
1021+
1022+bool DBusInterface::handleMessage(const QDBusMessage& message, const QDBusConnection& connection)
1023+{
1024+ /* Check to make sure we're getting properties on our interface */
1025+ if (message.type() != QDBusMessage::MessageType::MethodCallMessage) {
1026+ return false;
1027+ }
1028+
1029+ // First handle methods of the Launcher interface
1030+ if (message.interface() == "com.canonical.Unity.Launcher") {
1031+ if (message.member() == "Refresh") {
1032+ QDBusMessage reply = message.createReply();
1033+ Q_EMIT refreshCalled();
1034+ return connection.send(reply);
1035+ }
1036+ }
1037+
1038+ // Now handle dynamic properties (for launcher emblems)
1039+ if (message.interface() != "org.freedesktop.DBus.Properties") {
1040+ return false;
1041+ }
1042+
1043+ if (message.member() != "GetAll" && message.arguments()[0].toString() != "com.canonical.Unity.Launcher.Item") {
1044+ return false;
1045+ }
1046+
1047+ /* Break down the path to just the app id */
1048+ QString pathtemp = message.path();
1049+ if (!pathtemp.startsWith("/com/canonical/Unity/Launcher/")) {
1050+ return false;
1051+ }
1052+ pathtemp.remove("/com/canonical/Unity/Launcher/");
1053+ if (pathtemp.indexOf('/') >= 0) {
1054+ return false;
1055+ }
1056+
1057+ /* Find ourselves an appid */
1058+ QString appid = decodeAppId(pathtemp);
1059+ int index = m_launcherModel->findApplication(appid);
1060+ LauncherItem *item = static_cast<LauncherItem*>(m_launcherModel->get(index));
1061+
1062+ QVariantList retval;
1063+ if (message.member() == "Get") {
1064+ if (!item) {
1065+ return false;
1066+ }
1067+ if (message.arguments()[1].toString() == "count") {
1068+ retval.append(QVariant::fromValue(QDBusVariant(item->count())));
1069+ } else if (message.arguments()[1].toString() == "countVisible") {
1070+ retval.append(QVariant::fromValue(QDBusVariant(item->countVisible())));
1071+ }
1072+ } else if (message.member() == "Set") {
1073+ if (message.arguments()[1].toString() == "count") {
1074+ int newCount = message.arguments()[2].toInt();
1075+ if (!item || newCount != item->count()) {
1076+ Q_EMIT countChanged(appid, newCount);
1077+ emitPropChangedDbus(appid, "count", QVariant(newCount));
1078+ }
1079+ } else if (message.arguments()[1].toString() == "countVisible") {
1080+ bool newVisible = message.arguments()[2].toBool();
1081+ if (!item || newVisible != item->countVisible()) {
1082+ Q_EMIT countVisibleChanged(appid, newVisible);
1083+ emitPropChangedDbus(appid, "countVisible", newVisible);
1084+ }
1085+ }
1086+ } else if (message.member() == "GetAll") {
1087+ if (item) {
1088+ QVariantMap all;
1089+ all.insert("count", item->count());
1090+ all.insert("countVisible", item->countVisible());
1091+ retval.append(all);
1092+ }
1093+ } else {
1094+ return false;
1095+ }
1096+
1097+ QDBusMessage reply = message.createReply(retval);
1098+ return connection.send(reply);
1099+}
1100+
1101+void DBusInterface::emitPropChangedDbus(const QString& appId, const QString& property, const QVariant &value)
1102+{
1103+ QString path("/com/canonical/Unity/Launcher/");
1104+ path.append(encodeAppId(appId));
1105+
1106+ QDBusMessage message = QDBusMessage::createSignal(path, "org.freedesktop.DBus.Properties", "PropertiesChanged");
1107+
1108+ QList<QVariant> arguments;
1109+ QVariantHash changedprops;
1110+ changedprops[property] = QVariant::fromValue(QDBusVariant(value));
1111+ QVariantList deletedprops;
1112+
1113+ arguments.append(changedprops);
1114+ arguments.append(deletedprops);
1115+
1116+ message.setArguments(arguments);
1117+
1118+ QDBusConnection con = QDBusConnection::sessionBus();
1119+ con.send(message);
1120+}
1121
1122=== added file 'plugins/Unity/Launcher/dbusinterface.h'
1123--- plugins/Unity/Launcher/dbusinterface.h 1970-01-01 00:00:00 +0000
1124+++ plugins/Unity/Launcher/dbusinterface.h 2014-09-18 21:04:46 +0000
1125@@ -0,0 +1,50 @@
1126+/*
1127+ * Copyright 2014 Canonical Ltd.
1128+ *
1129+ * This program is free software; you can redistribute it and/or modify
1130+ * it under the terms of the GNU Lesser General Public License as published by
1131+ * the Free Software Foundation; version 3.
1132+ *
1133+ * This program is distributed in the hope that it will be useful,
1134+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1135+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1136+ * GNU Lesser General Public License for more details.
1137+ *
1138+ * You should have received a copy of the GNU Lesser General Public License
1139+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1140+ *
1141+ * Authors:
1142+ * Michael Zanetti <michael.zanetti@canonical.com>
1143+ */
1144+
1145+#include "launcheritem.h"
1146+
1147+#include <QDBusVirtualObject>
1148+
1149+class LauncherModel;
1150+
1151+class DBusInterface: public QDBusVirtualObject
1152+{
1153+ Q_OBJECT
1154+public:
1155+ DBusInterface(LauncherModel *parent);
1156+ ~DBusInterface();
1157+
1158+ // QDBusVirtualObject implementaition
1159+ QString introspect (const QString &path) const override;
1160+ bool handleMessage(const QDBusMessage& message, const QDBusConnection& connection) override;
1161+
1162+Q_SIGNALS:
1163+ void countChanged(const QString &appId, int count);
1164+ void countVisibleChanged(const QString &appId, bool countVisible);
1165+ void refreshCalled();
1166+
1167+private:
1168+ static QString decodeAppId(const QString& path);
1169+ static QString encodeAppId(const QString& appId);
1170+
1171+ void emitPropChangedDbus(const QString& appId, const QString& property, const QVariant &value);
1172+
1173+ LauncherModel *m_launcherModel;
1174+
1175+};
1176
1177=== added file 'plugins/Unity/Launcher/desktopfilehandler.cpp'
1178--- plugins/Unity/Launcher/desktopfilehandler.cpp 1970-01-01 00:00:00 +0000
1179+++ plugins/Unity/Launcher/desktopfilehandler.cpp 2014-09-18 21:04:46 +0000
1180@@ -0,0 +1,151 @@
1181+/*
1182+ * Copyright 2014 Canonical Ltd.
1183+ *
1184+ * This program is free software; you can redistribute it and/or modify
1185+ * it under the terms of the GNU Lesser General Public License as published by
1186+ * the Free Software Foundation; version 3.
1187+ *
1188+ * This program is distributed in the hope that it will be useful,
1189+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1190+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1191+ * GNU Lesser General Public License for more details.
1192+ *
1193+ * You should have received a copy of the GNU Lesser General Public License
1194+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1195+ *
1196+ * Authors:
1197+ * Michael Zanetti <michael.zanetti@canonical.com>
1198+ */
1199+
1200+#include "desktopfilehandler.h"
1201+
1202+#include <QStringList>
1203+#include <QStandardPaths>
1204+#include <QDir>
1205+#include <QSettings>
1206+#include <QLocale>
1207+
1208+#include <libintl.h>
1209+
1210+DesktopFileHandler::DesktopFileHandler(const QString &appId, QObject *parent):
1211+ QObject(parent),
1212+ m_appId(appId)
1213+{
1214+ load();
1215+}
1216+
1217+QString DesktopFileHandler::appId() const
1218+{
1219+ return m_appId;
1220+}
1221+
1222+void DesktopFileHandler::setAppId(const QString &appId)
1223+{
1224+ if (m_appId != appId) {
1225+ m_appId = appId;
1226+ load();
1227+ }
1228+}
1229+
1230+QString DesktopFileHandler::filename() const
1231+{
1232+ return m_filename;
1233+}
1234+
1235+bool DesktopFileHandler::isValid() const
1236+{
1237+ return !m_filename.isEmpty();
1238+}
1239+
1240+void DesktopFileHandler::load()
1241+{
1242+ m_filename.clear();
1243+
1244+ if (m_appId.isEmpty()) {
1245+ return;
1246+ }
1247+
1248+ int dashPos = -1;
1249+ QString helper = m_appId;
1250+
1251+ QStringList searchDirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation);
1252+#ifdef LAUNCHER_TESTING
1253+ searchDirs << "";
1254+#endif
1255+
1256+ QString path;
1257+ do {
1258+ if (dashPos != -1) {
1259+ helper.replace(dashPos, 1, '/');
1260+ }
1261+
1262+ if (helper.contains("/")) {
1263+ path += helper.split('/').first() + '/';
1264+ helper.remove(QRegExp("^" + path));
1265+ }
1266+
1267+ Q_FOREACH(const QString &searchDirName, searchDirs) {
1268+ QDir searchDir(searchDirName + "/" + path);
1269+ Q_FOREACH(const QString &desktopFile, searchDir.entryList(QStringList() << "*.desktop")) {
1270+ if (desktopFile.startsWith(helper)) {
1271+ QFileInfo fileInfo(searchDir, desktopFile);
1272+ m_filename = fileInfo.absoluteFilePath();
1273+ return;
1274+ }
1275+ }
1276+ }
1277+
1278+ dashPos = helper.indexOf("-");
1279+ } while (dashPos != -1);
1280+}
1281+
1282+QString DesktopFileHandler::displayName() const
1283+{
1284+ if (!isValid()) {
1285+ return QString();
1286+ }
1287+
1288+ QSettings settings(m_filename, QSettings::IniFormat);
1289+ settings.beginGroup("Desktop Entry");
1290+
1291+ // First try to find Name[xx_YY] and Name[xx] in .desktop file
1292+ QString locale = QLocale::system().name();
1293+ QString shortLocale = locale.split('_').first();
1294+
1295+ if (locale != shortLocale && settings.contains(QString("Name[%1]").arg(locale))) {
1296+ return settings.value(QString("Name[%1]").arg(locale)).toString();
1297+ }
1298+
1299+ if (settings.contains(QString("Name[%1]").arg(shortLocale))) {
1300+ return settings.value(QString("Name[%1]").arg(shortLocale)).toString();
1301+ }
1302+
1303+ // No translation found in desktop file. Get the untranslated one and have a go with gettext.
1304+ QString displayName = settings.value("Name").toString();
1305+
1306+ if (settings.contains("X-Ubuntu-Gettext-Domain")) {
1307+ const QString domain = settings.value("X-Ubuntu-Gettext-Domain").toString();
1308+ return dgettext(domain.toUtf8().constData(), displayName.toUtf8().constData());
1309+ }
1310+
1311+ return displayName;
1312+}
1313+
1314+QString DesktopFileHandler::icon() const
1315+{
1316+ if (!isValid()) {
1317+ return QString();
1318+ }
1319+
1320+ QSettings settings(m_filename, QSettings::IniFormat);
1321+ settings.beginGroup("Desktop Entry");
1322+ QString iconString = settings.value("Icon").toString();
1323+ QString pathString = settings.value("Path").toString();
1324+
1325+ if (QFileInfo(iconString).exists()) {
1326+ return QFileInfo(iconString).absoluteFilePath();
1327+ } else if (QFileInfo(pathString + '/' + iconString).exists()) {
1328+ return pathString + '/' + iconString;
1329+ }
1330+ return "image://theme/" + iconString;
1331+}
1332
1333=== added file 'plugins/Unity/Launcher/desktopfilehandler.h'
1334--- plugins/Unity/Launcher/desktopfilehandler.h 1970-01-01 00:00:00 +0000
1335+++ plugins/Unity/Launcher/desktopfilehandler.h 2014-09-18 21:04:46 +0000
1336@@ -0,0 +1,56 @@
1337+/*
1338+ * Copyright 2014 Canonical Ltd.
1339+ *
1340+ * This program is free software; you can redistribute it and/or modify
1341+ * it under the terms of the GNU Lesser General Public License as published by
1342+ * the Free Software Foundation; version 3.
1343+ *
1344+ * This program is distributed in the hope that it will be useful,
1345+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1346+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1347+ * GNU Lesser General Public License for more details.
1348+ *
1349+ * You should have received a copy of the GNU Lesser General Public License
1350+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1351+ *
1352+ * Authors:
1353+ * Michael Zanetti <michael.zanetti@canonical.com>
1354+ */
1355+
1356+#ifndef DESKTOPFILEHANDLER_H
1357+#define DESKTOPFILEHANDLER_H
1358+
1359+#include <QObject>
1360+
1361+/**
1362+ * When an object of this class is created or whenever setAppId(appId) is called,
1363+ * this will search for a .desktop file matching the give appId. If a file is
1364+ * found, isValid() will return true and the other methods return the contents
1365+ * of the .desktop file.
1366+ *
1367+ * Note that this class will consider the user's locale and do a best effort
1368+ * to return localized values.
1369+ */
1370+
1371+class DesktopFileHandler: public QObject
1372+{
1373+ Q_OBJECT
1374+public:
1375+ DesktopFileHandler(const QString &appId = QString(), QObject *parent = nullptr);
1376+
1377+ QString appId() const;
1378+ void setAppId(const QString &appId);
1379+
1380+ bool isValid() const;
1381+ QString filename() const;
1382+ QString displayName() const;
1383+ QString icon() const;
1384+
1385+private:
1386+ void load();
1387+
1388+ QString m_appId;
1389+ QString m_filename;
1390+};
1391+
1392+#endif
1393
1394=== added file 'plugins/Unity/Launcher/gsettings.cpp'
1395--- plugins/Unity/Launcher/gsettings.cpp 1970-01-01 00:00:00 +0000
1396+++ plugins/Unity/Launcher/gsettings.cpp 2014-09-18 21:04:46 +0000
1397@@ -0,0 +1,66 @@
1398+/*
1399+ * Copyright 2014 Canonical Ltd.
1400+ *
1401+ * This program is free software; you can redistribute it and/or modify
1402+ * it under the terms of the GNU Lesser General Public License as published by
1403+ * the Free Software Foundation; version 3.
1404+ *
1405+ * This program is distributed in the hope that it will be useful,
1406+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1407+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1408+ * GNU Lesser General Public License for more details.
1409+ *
1410+ * You should have received a copy of the GNU Lesser General Public License
1411+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1412+ *
1413+ * Authors:
1414+ * Michael Zanetti <michael.zanetti@canonical.com>
1415+ */
1416+
1417+#include "gsettings.h"
1418+
1419+#include <QGSettings>
1420+#include <QVariant>
1421+
1422+GSettings::GSettings(QObject *parent):
1423+ QObject(parent)
1424+{
1425+
1426+}
1427+
1428+QStringList GSettings::storedApplications() const
1429+{
1430+ QStringList storedApps;
1431+
1432+ QGSettings gSettings("com.canonical.Unity.Launcher", "/com/canonical/unity/launcher/");
1433+
1434+ Q_FOREACH(const QString &entry, gSettings.get("items").toStringList()) {
1435+ if (entry.startsWith("application:///")) {
1436+ // convert legacy entries to new world appids
1437+ QString appId = entry;
1438+ // Transform "application://foobar.desktop" to "foobar"
1439+ appId.remove(QRegExp("^application:///"));
1440+ appId.remove(QRegExp(".desktop$"));
1441+ storedApps << appId;
1442+ } else if (entry.startsWith("appid://")) {
1443+ QString appId = entry;
1444+ appId.remove("appid://");
1445+ if (appId.split('/').count() == 3) {
1446+ // Strip current-user-version in case its there
1447+ appId = appId.split('/').first() + "_" + appId.split('/').at(1);
1448+ }
1449+ storedApps << appId;
1450+ }
1451+ }
1452+ return storedApps;
1453+}
1454+
1455+void GSettings::setStoredApplications(const QStringList &storedApplications)
1456+{
1457+ QGSettings gSettings("com.canonical.Unity.Launcher", "/com/canonical/unity/launcher/");
1458+ QStringList gSettingsList;
1459+ Q_FOREACH(const QString &entry, storedApplications) {
1460+ gSettingsList << QString("appid://%1").arg(entry);
1461+ }
1462+ gSettings.set("items", gSettingsList);
1463+}
1464
1465=== added file 'plugins/Unity/Launcher/gsettings.h'
1466--- plugins/Unity/Launcher/gsettings.h 1970-01-01 00:00:00 +0000
1467+++ plugins/Unity/Launcher/gsettings.h 2014-09-18 21:04:46 +0000
1468@@ -0,0 +1,37 @@
1469+/*
1470+ * Copyright 2014 Canonical Ltd.
1471+ *
1472+ * This program is free software; you can redistribute it and/or modify
1473+ * it under the terms of the GNU Lesser General Public License as published by
1474+ * the Free Software Foundation; version 3.
1475+ *
1476+ * This program is distributed in the hope that it will be useful,
1477+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1478+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1479+ * GNU Lesser General Public License for more details.
1480+ *
1481+ * You should have received a copy of the GNU Lesser General Public License
1482+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1483+ *
1484+ * Authors:
1485+ * Michael Zanetti <michael.zanetti@canonical.com>
1486+ */
1487+
1488+#ifndef GSETTINGS_H
1489+#define GSETTINGS_H
1490+
1491+#include <QObject>
1492+#include <QStringList>
1493+
1494+
1495+class GSettings: public QObject
1496+{
1497+ Q_OBJECT
1498+public:
1499+ GSettings(QObject *parent = nullptr);
1500+
1501+ QStringList storedApplications() const;
1502+ void setStoredApplications(const QStringList &storedApplications);
1503+};
1504+
1505+#endif
1506
1507=== modified file 'plugins/Unity/Launcher/launcheritem.cpp'
1508--- plugins/Unity/Launcher/launcheritem.cpp 2014-09-02 11:11:01 +0000
1509+++ plugins/Unity/Launcher/launcheritem.cpp 2014-09-18 21:04:46 +0000
1510@@ -32,6 +32,7 @@
1511 m_recent(false),
1512 m_progress(-1),
1513 m_count(0),
1514+ m_countVisible(false),
1515 m_focused(false),
1516 m_quickList(new QuickListModel(this))
1517 {
1518@@ -55,11 +56,27 @@
1519 return m_name;
1520 }
1521
1522+void LauncherItem::setName(const QString &name)
1523+{
1524+ if (m_name != name) {
1525+ m_name = name;
1526+ Q_EMIT nameChanged(name);
1527+ }
1528+}
1529+
1530 QString LauncherItem::icon() const
1531 {
1532 return m_icon;
1533 }
1534
1535+void LauncherItem::setIcon(const QString &icon)
1536+{
1537+ if (m_icon != icon) {
1538+ m_icon = icon;
1539+ Q_EMIT iconChanged(icon);
1540+ }
1541+}
1542+
1543 bool LauncherItem::pinned() const
1544 {
1545 return m_pinned;
1546@@ -129,6 +146,19 @@
1547 }
1548 }
1549
1550+bool LauncherItem::countVisible() const
1551+{
1552+ return m_countVisible;
1553+}
1554+
1555+void LauncherItem::setCountVisible(bool countVisible)
1556+{
1557+ if (m_countVisible != countVisible) {
1558+ m_countVisible = countVisible;
1559+ Q_EMIT countVisibleChanged(countVisible);
1560+ }
1561+}
1562+
1563 bool LauncherItem::focused() const
1564 {
1565 return m_focused;
1566
1567=== modified file 'plugins/Unity/Launcher/launcheritem.h'
1568--- plugins/Unity/Launcher/launcheritem.h 2014-06-11 15:36:51 +0000
1569+++ plugins/Unity/Launcher/launcheritem.h 2014-09-18 21:04:46 +0000
1570@@ -34,34 +34,30 @@
1571 public:
1572 LauncherItem(const QString &appId, const QString &name, const QString &icon, QObject *parent = 0);
1573
1574- QString appId() const;
1575- QString name() const;
1576- QString icon() const;
1577-
1578- bool pinned() const;
1579+ QString appId() const override;
1580+ QString name() const override;
1581+ QString icon() const override;
1582+ bool pinned() const override;
1583+ bool running() const override;
1584+ bool recent() const override;
1585+ int progress() const override;
1586+ int count() const override;
1587+ bool countVisible() const override;
1588+ bool focused() const override;
1589+
1590+ unity::shell::launcher::QuickListModelInterface *quickList() const override;
1591+
1592+private:
1593+ void setName(const QString &name);
1594+ void setIcon(const QString &icon);
1595 void setPinned(bool pinned);
1596-
1597- bool running() const;
1598 void setRunning(bool running);
1599-
1600- bool recent() const;
1601 void setRecent(bool recent);
1602-
1603- int progress() const;
1604 void setProgress(int progress);
1605-
1606- int count() const;
1607 void setCount(int count);
1608-
1609- bool focused() const;
1610+ void setCountVisible(bool countVisible);
1611 void setFocused(bool focused);
1612
1613- unity::shell::launcher::QuickListModelInterface *quickList() const;
1614-
1615-Q_SIGNALS:
1616- void favoriteChanged(bool favorite);
1617- void runningChanged(bool running);
1618-
1619 private:
1620 QString m_appId;
1621 QString m_name;
1622@@ -71,8 +67,11 @@
1623 bool m_recent;
1624 int m_progress;
1625 int m_count;
1626+ bool m_countVisible;
1627 bool m_focused;
1628 QuickListModel *m_quickList;
1629+
1630+ friend class LauncherModel;
1631 };
1632
1633 #endif // LAUNCHERITEM_H
1634
1635=== modified file 'plugins/Unity/Launcher/launchermodel.cpp'
1636--- plugins/Unity/Launcher/launchermodel.cpp 2014-09-02 11:11:01 +0000
1637+++ plugins/Unity/Launcher/launchermodel.cpp 2014-09-18 21:04:46 +0000
1638@@ -19,7 +19,9 @@
1639
1640 #include "launchermodel.h"
1641 #include "launcheritem.h"
1642-#include "backend/launcherbackend.h"
1643+#include "gsettings.h"
1644+#include "desktopfilehandler.h"
1645+#include "dbusinterface.h"
1646
1647 #include <unity/shell/application/ApplicationInfoInterface.h>
1648
1649@@ -30,16 +32,24 @@
1650
1651 LauncherModel::LauncherModel(QObject *parent):
1652 LauncherModelInterface(parent),
1653- m_backend(new LauncherBackend(this)),
1654+ m_settings(new GSettings(this)),
1655+ m_dbusIface(new DBusInterface(this)),
1656 m_appManager(0)
1657 {
1658- connect(m_backend, SIGNAL(countChanged(QString,int)), SLOT(countChanged(QString,int)));
1659- connect(m_backend, SIGNAL(progressChanged(QString,int)), SLOT(progressChanged(QString,int)));
1660-
1661- Q_FOREACH (const QString &entry, m_backend->storedApplications()) {
1662+ connect(m_dbusIface, &DBusInterface::countChanged, this, &LauncherModel::countChanged);
1663+ connect(m_dbusIface, &DBusInterface::countVisibleChanged, this, &LauncherModel::countVisibleChanged);
1664+ connect(m_dbusIface, &DBusInterface::refreshCalled, this, &LauncherModel::refresh);
1665+
1666+ Q_FOREACH (const QString &entry, m_settings->storedApplications()) {
1667+ DesktopFileHandler desktopFile(entry);
1668+ if (!desktopFile.isValid()) {
1669+ qWarning() << "Couldn't find a .desktop file for" << entry << ". Skipping...";
1670+ continue;
1671+ }
1672+
1673 LauncherItem *item = new LauncherItem(entry,
1674- m_backend->displayName(entry),
1675- m_backend->icon(entry),
1676+ desktopFile.displayName(),
1677+ desktopFile.icon(),
1678 this);
1679 item->setPinned(true);
1680 m_list.append(item);
1681@@ -73,6 +83,8 @@
1682 return item->pinned();
1683 case RoleCount:
1684 return item->count();
1685+ case RoleCountVisible:
1686+ return item->countVisible();
1687 case RoleProgress:
1688 return item->progress();
1689 case RoleFocused:
1690@@ -139,10 +151,17 @@
1691 if (index == -1) {
1692 index = m_list.count();
1693 }
1694+
1695+ DesktopFileHandler desktopFile(appId);
1696+ if (!desktopFile.isValid()) {
1697+ qWarning() << "Can't pin this application, there is no .destkop file available.";
1698+ return;
1699+ }
1700+
1701 beginInsertRows(QModelIndex(), index, index);
1702 LauncherItem *item = new LauncherItem(appId,
1703- m_backend->displayName(appId),
1704- m_backend->icon(appId),
1705+ desktopFile.displayName(),
1706+ desktopFile.icon(),
1707 this);
1708 item->setPinned(true);
1709 m_list.insert(index, item);
1710@@ -197,25 +216,27 @@
1711
1712 // Nope, we don't know this action, let the backend forward it to the application
1713 } else {
1714- m_backend->triggerQuickListAction(appId, actionId);
1715+ // TODO: forward quicklist action to app, possibly via m_dbusIface
1716 }
1717 }
1718 }
1719
1720 void LauncherModel::setUser(const QString &username)
1721 {
1722- m_backend->setUser(username);
1723+ Q_UNUSED(username)
1724+ qWarning() << "This backend doesn't support multiple users";
1725 }
1726
1727 QString LauncherModel::getUrlForAppId(const QString &appId) const
1728 {
1729 // appId is either an appId or a legacy app name. Let's find out which
1730- if (appId.isEmpty())
1731+ if (appId.isEmpty()) {
1732 return QString();
1733+ }
1734
1735- QString df = m_backend->desktopFile(appId + ".desktop");
1736- if (!df.isEmpty())
1737+ if (!appId.contains("_")) {
1738 return "application:///" + appId + ".desktop";
1739+ }
1740
1741 QStringList parts = appId.split('_');
1742 QString package = parts.value(0);
1743@@ -275,7 +296,7 @@
1744 appIds << item->appId();
1745 }
1746 }
1747- m_backend->setStoredApplications(appIds);
1748+ m_settings->setStoredApplications(appIds);
1749 }
1750
1751 int LauncherModel::findApplication(const QString &appId)
1752@@ -310,6 +331,55 @@
1753 }
1754 }
1755
1756+void LauncherModel::countVisibleChanged(const QString &appId, int countVisible)
1757+{
1758+ int idx = findApplication(appId);
1759+ if (idx >= 0) {
1760+ LauncherItem *item = m_list.at(idx);
1761+ item->setCountVisible(countVisible);
1762+ Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleCountVisible);
1763+
1764+ // If countVisible goes to false, and the item is neither pinned nor recent we can drop it
1765+ if (!countVisible && !item->pinned() && !item->recent()) {
1766+ beginRemoveRows(QModelIndex(), idx, idx);
1767+ m_list.takeAt(idx)->deleteLater();
1768+ endRemoveRows();
1769+ }
1770+ } else {
1771+ // Need to create a new LauncherItem and show the highlight
1772+ DesktopFileHandler desktopFile(appId);
1773+ if (countVisible && desktopFile.isValid()) {
1774+ LauncherItem *item = new LauncherItem(appId,
1775+ desktopFile.displayName(),
1776+ desktopFile.icon());
1777+ item->setCountVisible(true);
1778+ beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
1779+ m_list.append(item);
1780+ endInsertRows();
1781+ }
1782+ }
1783+}
1784+
1785+void LauncherModel::refresh()
1786+{
1787+ QList<LauncherItem*> toBeRemoved;
1788+ Q_FOREACH (LauncherItem* item, m_list) {
1789+ DesktopFileHandler desktopFile(item->appId());
1790+ if (!desktopFile.isValid()) {
1791+ toBeRemoved << item;
1792+ } else {
1793+ int idx = m_list.indexOf(item);
1794+ item->setName(desktopFile.displayName());
1795+ item->setIcon(desktopFile.icon());
1796+ Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleName << RoleIcon);
1797+ }
1798+ }
1799+
1800+ Q_FOREACH (LauncherItem* item, toBeRemoved) {
1801+ requestRemove(item->appId());
1802+ }
1803+}
1804+
1805 void LauncherModel::applicationAdded(const QModelIndex &parent, int row)
1806 {
1807 Q_UNUSED(parent);
1808
1809=== modified file 'plugins/Unity/Launcher/launchermodel.h'
1810--- plugins/Unity/Launcher/launchermodel.h 2014-06-11 15:36:51 +0000
1811+++ plugins/Unity/Launcher/launchermodel.h 2014-09-18 21:04:46 +0000
1812@@ -20,15 +20,14 @@
1813 #ifndef LAUNCHERMODEL_H
1814 #define LAUNCHERMODEL_H
1815
1816-// unity-api
1817 #include <unity/shell/launcher/LauncherModelInterface.h>
1818 #include <unity/shell/application/ApplicationManagerInterface.h>
1819
1820-// Qt
1821 #include <QAbstractListModel>
1822
1823 class LauncherItem;
1824-class LauncherBackend;
1825+class GSettings;
1826+class DBusInterface;
1827
1828 using namespace unity::shell::launcher;
1829 using namespace unity::shell::application;
1830@@ -41,14 +40,13 @@
1831 LauncherModel(QObject *parent = 0);
1832 ~LauncherModel();
1833
1834- int rowCount(const QModelIndex &parent) const;
1835+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
1836
1837 QVariant data(const QModelIndex &index, int role) const;
1838
1839 Q_INVOKABLE unity::shell::launcher::LauncherItemInterface* get(int index) const;
1840 Q_INVOKABLE void move(int oldIndex, int newIndex);
1841 Q_INVOKABLE void pin(const QString &appId, int index = -1);
1842- Q_INVOKABLE void requestRemove(const QString &appId);
1843 Q_INVOKABLE void quickListActionInvoked(const QString &appId, int actionIndex);
1844 Q_INVOKABLE void setUser(const QString &username);
1845 Q_INVOKABLE QString getUrlForAppId(const QString &appId) const;
1846@@ -56,13 +54,19 @@
1847 unity::shell::application::ApplicationManagerInterface* applicationManager() const;
1848 void setApplicationManager(unity::shell::application::ApplicationManagerInterface *appManager);
1849
1850+ int findApplication(const QString &appId);
1851+
1852+public Q_SLOTS:
1853+ void requestRemove(const QString &appId);
1854+
1855 private:
1856 void storeAppList();
1857- int findApplication(const QString &appId);
1858
1859 private Q_SLOTS:
1860+ void countChanged(const QString &appId, int count);
1861+ void countVisibleChanged(const QString &appId, int count);
1862 void progressChanged(const QString &appId, int progress);
1863- void countChanged(const QString &appId, int count);
1864+ void refresh();
1865
1866 void applicationAdded(const QModelIndex &parent, int row);
1867 void applicationRemoved(const QModelIndex &parent, int row);
1868@@ -70,7 +74,10 @@
1869
1870 private:
1871 QList<LauncherItem*> m_list;
1872- LauncherBackend *m_backend;
1873+
1874+ GSettings *m_settings;
1875+ DBusInterface *m_dbusIface;
1876+
1877 ApplicationManagerInterface *m_appManager;
1878 };
1879
1880
1881=== renamed file 'plugins/Unity/Launcher/common/quicklistentry.cpp' => 'plugins/Unity/Launcher/quicklistentry.cpp'
1882=== renamed file 'plugins/Unity/Launcher/common/quicklistentry.h' => 'plugins/Unity/Launcher/quicklistentry.h'
1883=== modified file 'plugins/Unity/Launcher/quicklistmodel.h'
1884--- plugins/Unity/Launcher/quicklistmodel.h 2013-07-24 10:42:00 +0000
1885+++ plugins/Unity/Launcher/quicklistmodel.h 2014-09-18 21:04:46 +0000
1886@@ -20,7 +20,7 @@
1887 #ifndef QUICKLISTMODEL_H
1888 #define QUICKLISTMODEL_H
1889
1890-#include "common/quicklistentry.h"
1891+#include "quicklistentry.h"
1892
1893 #include <unity/shell/launcher/QuickListModelInterface.h>
1894
1895
1896=== modified file 'qml/Launcher/LauncherDelegate.qml'
1897--- qml/Launcher/LauncherDelegate.qml 2014-08-11 15:54:10 +0000
1898+++ qml/Launcher/LauncherDelegate.qml 2014-09-18 21:04:46 +0000
1899@@ -21,7 +21,8 @@
1900 id: root
1901
1902 property string iconName
1903- property int count: -1
1904+ property int count: 0
1905+ property bool countVisible: false
1906 property int progress: -1
1907 property bool itemFocused: false
1908 property real maxAngle: 0
1909@@ -80,11 +81,12 @@
1910 width: Math.min(root.itemWidth, Math.max(units.gu(2), countLabel.implicitWidth + units.gu(1)))
1911 height: units.gu(2)
1912 color: UbuntuColors.orange
1913- visible: root.count > 0
1914+ visible: root.countVisible
1915 borderSource: "none"
1916
1917 Label {
1918 id: countLabel
1919+ objectName: "countLabel"
1920 text: root.count
1921 anchors.centerIn: parent
1922 // FIXME: verticalCenter seems to be off wee bit and QML doesn't have a centerLine
1923
1924=== modified file 'qml/Launcher/LauncherPanel.qml'
1925--- qml/Launcher/LauncherPanel.qml 2014-08-12 16:21:21 +0000
1926+++ qml/Launcher/LauncherPanel.qml 2014-09-18 21:04:46 +0000
1927@@ -156,6 +156,7 @@
1928 height: itemHeight
1929 iconName: model.icon
1930 count: model.count
1931+ countVisible: model.countVisible
1932 progress: model.progress
1933 clipCorner: model.pinned
1934 itemFocused: model.focused
1935
1936=== modified file 'tests/mocks/Unity/Launcher/CMakeLists.txt'
1937--- tests/mocks/Unity/Launcher/CMakeLists.txt 2014-06-17 18:23:44 +0000
1938+++ tests/mocks/Unity/Launcher/CMakeLists.txt 2014-09-18 21:04:46 +0000
1939@@ -1,4 +1,4 @@
1940-pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=3)
1941+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=4)
1942
1943 include_directories(
1944 ${CMAKE_CURRENT_SOURCE_DIR}
1945
1946=== modified file 'tests/mocks/Unity/Launcher/MockLauncherItem.cpp'
1947--- tests/mocks/Unity/Launcher/MockLauncherItem.cpp 2014-06-17 18:23:44 +0000
1948+++ tests/mocks/Unity/Launcher/MockLauncherItem.cpp 2014-09-18 21:04:46 +0000
1949@@ -35,6 +35,7 @@
1950 m_recent(false),
1951 m_progress(-1),
1952 m_count(0),
1953+ m_countVisible(false),
1954 m_focused(false),
1955 m_quickList(new MockQuickListModel(this))
1956 {
1957@@ -131,6 +132,19 @@
1958 }
1959 }
1960
1961+bool MockLauncherItem::countVisible() const
1962+{
1963+ return m_countVisible;
1964+}
1965+
1966+void MockLauncherItem::setCountVisible(bool countVisible)
1967+{
1968+ if (m_countVisible != countVisible) {
1969+ m_countVisible = countVisible;
1970+ Q_EMIT countVisibleChanged(countVisible);
1971+ }
1972+}
1973+
1974 bool MockLauncherItem::focused() const
1975 {
1976 return m_focused;
1977
1978=== modified file 'tests/mocks/Unity/Launcher/MockLauncherItem.h'
1979--- tests/mocks/Unity/Launcher/MockLauncherItem.h 2013-09-04 22:44:01 +0000
1980+++ tests/mocks/Unity/Launcher/MockLauncherItem.h 2014-09-18 21:04:46 +0000
1981@@ -32,32 +32,30 @@
1982 public:
1983 MockLauncherItem(const QString &appId, const QString& desktopFile, const QString& name, const QString& icon, QObject* parent = 0);
1984
1985- QString appId() const;
1986+ QString appId() const override;
1987 QString desktopFile() const;
1988- QString name() const;
1989- QString icon() const;
1990-
1991- bool pinned() const;
1992+ QString name() const override;
1993+ QString icon() const override;
1994+
1995+ bool pinned() const override;
1996+ bool running() const override;
1997+ bool recent() const override;
1998+ int progress() const override;
1999+ int count() const override;
2000+ bool countVisible() const override;
2001+ bool focused() const;
2002+
2003+ unity::shell::launcher::QuickListModelInterface *quickList() const;
2004+
2005+private:
2006 void setPinned(bool pinned);
2007-
2008- bool running() const;
2009 void setRunning(bool running);
2010-
2011- bool recent() const;
2012 void setRecent(bool recent);
2013-
2014- int progress() const;
2015 void setProgress(int progress);
2016-
2017- int count() const;
2018 void setCount(int count);
2019-
2020- bool focused() const;
2021+ void setCountVisible(bool countVisible);
2022 void setFocused(bool focused);
2023
2024- unity::shell::launcher::QuickListModelInterface *quickList() const;
2025-
2026-private:
2027 QString m_appId;
2028 QString m_desktopFile;
2029 QString m_name;
2030@@ -67,8 +65,11 @@
2031 bool m_recent;
2032 int m_progress;
2033 int m_count;
2034+ bool m_countVisible;
2035 bool m_focused;
2036 MockQuickListModel *m_quickList;
2037+
2038+ friend class MockLauncherModel;
2039 };
2040
2041 #endif // MOCKLAUNCHERITEM_H
2042
2043=== modified file 'tests/mocks/Unity/Launcher/MockLauncherModel.cpp'
2044--- tests/mocks/Unity/Launcher/MockLauncherModel.cpp 2014-07-11 16:01:44 +0000
2045+++ tests/mocks/Unity/Launcher/MockLauncherModel.cpp 2014-09-18 21:04:46 +0000
2046@@ -33,26 +33,32 @@
2047 m_list.append(item);
2048 item = new MockLauncherItem("gallery-app", "/usr/share/applications/gallery-app.desktop", "Gallery", "gallery", this);
2049 item->setProgress(50);
2050+ item->setCountVisible(true);
2051 m_list.append(item);
2052 item = new MockLauncherItem("facebook-webapp", "/usr/share/applications/facebook-webapp.desktop", "Facebook", "facebook", this);
2053 item->setProgress(150);
2054 m_list.append(item);
2055 item = new MockLauncherItem("webbrowser-app", "/usr/share/applications/webbrowser-app.desktop", "Browser", "browser", this);
2056 item->setCount(1);
2057+ item->setCountVisible(true);
2058 m_list.append(item);
2059 item = new MockLauncherItem("twitter-webapp", "/usr/share/applications/twitter-webapp.desktop", "Twitter", "twitter", this);
2060 item->setCount(12);
2061+ item->setCountVisible(true);
2062 item->setPinned(true);
2063 m_list.append(item);
2064 item = new MockLauncherItem("gmail-webapp", "/usr/share/applications/gmail-webapp.desktop", "GMail", "gmail", this);
2065 item->setCount(123);
2066+ item->setCountVisible(true);
2067 m_list.append(item);
2068 item = new MockLauncherItem("ubuntu-weather-app", "/usr/share/applications/ubuntu-weather-app.desktop", "Weather", "weather", this);
2069 item->setCount(1234567890);
2070+ item->setCountVisible(true);
2071 m_list.append(item);
2072 item = new MockLauncherItem("notes-app", "/usr/share/applications/notes-app.desktop", "Notepad", "notepad", this);
2073 item->setProgress(50);
2074 item->setCount(5);
2075+ item->setCountVisible(true);
2076 item->setFocused(true);
2077 item->setPinned(true);
2078 m_list.append(item);
2079@@ -76,6 +82,8 @@
2080 LauncherItemInterface *item = m_list.at(index.row());
2081 switch(role)
2082 {
2083+ case RoleAppId:
2084+ return item->appId();
2085 case RoleName:
2086 return item->name();
2087 case RoleIcon:
2088@@ -90,6 +98,8 @@
2089 return item->progress();
2090 case RoleCount:
2091 return item->count();
2092+ case RoleCountVisible:
2093+ return item->countVisible();
2094 case RoleFocused:
2095 return item->focused();
2096 }
2097
2098=== modified file 'tests/plugins/Unity/Launcher/CMakeLists.txt'
2099--- tests/plugins/Unity/Launcher/CMakeLists.txt 2014-09-02 11:11:01 +0000
2100+++ tests/plugins/Unity/Launcher/CMakeLists.txt 2014-09-18 21:04:46 +0000
2101@@ -1,12 +1,11 @@
2102 pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
2103-pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=3)
2104+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=4)
2105 pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=3)
2106
2107 include_directories(
2108 ${CMAKE_CURRENT_BINARY_DIR}
2109 ${CMAKE_SOURCE_DIR}/plugins/AccountsService
2110 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher
2111- ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/backend
2112 ${GSETTINGS_QT_INCLUDE_DIRS}
2113 )
2114
2115@@ -14,35 +13,21 @@
2116 add_definitions(-DSRCDIR="${CMAKE_CURRENT_SOURCE_DIR}")
2117 add_definitions(-DLAUNCHER_TESTING)
2118
2119-### LauncherBackendTest
2120-set(testBackendCommand dbus-test-runner --task ${CMAKE_CURRENT_BINARY_DIR}/launcherbackendtestExec
2121- --parameter -o --parameter ${CMAKE_BINARY_DIR}/launcherbackendtest.xml,xunitxml
2122+### LauncherModelTest
2123+set(testModelCommand dbus-test-runner --task ${CMAKE_CURRENT_BINARY_DIR}/launchermodeltestExec
2124+ --parameter -o --parameter ${CMAKE_BINARY_DIR}/launchermodeltest.xml,xunitxml
2125 --parameter -o --parameter -,txt)
2126-add_test(NAME launcherbackendtest COMMAND ${testBackendCommand})
2127-add_custom_target(launcherbackendtest ${testBackendCommand})
2128-add_executable(launcherbackendtestExec
2129- launcherbackendtest.cpp
2130- ${CMAKE_SOURCE_DIR}/plugins/AccountsService/AccountsServiceDBusAdaptor.cpp
2131- ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/backend/launcherbackend.cpp
2132- ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/common/quicklistentry.cpp
2133- )
2134-target_link_libraries(launcherbackendtestExec ${GSETTINGS_QT_LDFLAGS})
2135-qt5_use_modules(launcherbackendtestExec Test Core DBus)
2136-
2137-### LauncherModelTest
2138-set(testModelCommand ${CMAKE_CURRENT_BINARY_DIR}/launchermodeltestExec
2139- -o ${CMAKE_BINARY_DIR}/launchermodeltest.xml,xunitxml
2140- -o -,txt)
2141 add_test(NAME launchermodeltest COMMAND ${testModelCommand})
2142 add_custom_target(launchermodeltest ${testModelCommand})
2143 add_executable(launchermodeltestExec
2144 launchermodeltest.cpp
2145+ gsettings.cpp
2146 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/launchermodel.cpp
2147 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/launcheritem.cpp
2148 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/quicklistmodel.cpp
2149- ${CMAKE_SOURCE_DIR}/plugins/AccountsService/AccountsServiceDBusAdaptor.cpp
2150- ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/backend/launcherbackend.cpp
2151- ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/common/quicklistentry.cpp
2152+ ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/dbusinterface.cpp
2153+ ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/desktopfilehandler.cpp
2154+ ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/quicklistentry.cpp
2155 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherItemInterface.h
2156 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherModelInterface.h
2157 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/QuickListModelInterface.h
2158@@ -50,7 +35,7 @@
2159 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
2160 )
2161 target_link_libraries(launchermodeltestExec ${GSETTINGS_QT_LDFLAGS})
2162-qt5_use_modules(launchermodeltestExec Test Core DBus Gui)
2163+qt5_use_modules(launchermodeltestExec Test Core DBus Xml Gui)
2164
2165 # copy .desktop files into build directory for shadow builds
2166 file(GLOB DESKTOP_FILES *.desktop)
2167
2168=== added file 'tests/plugins/Unity/Launcher/gsettings.cpp'
2169--- tests/plugins/Unity/Launcher/gsettings.cpp 1970-01-01 00:00:00 +0000
2170+++ tests/plugins/Unity/Launcher/gsettings.cpp 2014-09-18 21:04:46 +0000
2171@@ -0,0 +1,40 @@
2172+/*
2173+ * Copyright 2014 Canonical Ltd.
2174+ *
2175+ * This program is free software; you can redistribute it and/or modify
2176+ * it under the terms of the GNU Lesser General Public License as published by
2177+ * the Free Software Foundation; version 3.
2178+ *
2179+ * This program is distributed in the hope that it will be useful,
2180+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2181+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2182+ * GNU Lesser General Public License for more details.
2183+ *
2184+ * You should have received a copy of the GNU Lesser General Public License
2185+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2186+ *
2187+ * Authors:
2188+ * Michael Zanetti <michael.zanetti@canonical.com>
2189+ */
2190+
2191+#include "gsettings.h"
2192+
2193+#include <QDebug>
2194+
2195+// This is a mock implementation to not touch GSettings for real during tests
2196+
2197+GSettings::GSettings(QObject *parent):
2198+ QObject(parent)
2199+{
2200+
2201+}
2202+
2203+QStringList GSettings::storedApplications() const
2204+{
2205+ return QStringList();
2206+}
2207+
2208+void GSettings::setStoredApplications(const QStringList &storedApplications)
2209+{
2210+ Q_UNUSED(storedApplications)
2211+}
2212
2213=== added file 'tests/plugins/Unity/Launcher/gsettings.h'
2214--- tests/plugins/Unity/Launcher/gsettings.h 1970-01-01 00:00:00 +0000
2215+++ tests/plugins/Unity/Launcher/gsettings.h 2014-09-18 21:04:46 +0000
2216@@ -0,0 +1,38 @@
2217+/*
2218+ * Copyright 2014 Canonical Ltd.
2219+ *
2220+ * This program is free software; you can redistribute it and/or modify
2221+ * it under the terms of the GNU Lesser General Public License as published by
2222+ * the Free Software Foundation; version 3.
2223+ *
2224+ * This program is distributed in the hope that it will be useful,
2225+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2226+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2227+ * GNU Lesser General Public License for more details.
2228+ *
2229+ * You should have received a copy of the GNU Lesser General Public License
2230+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2231+ *
2232+ * Authors:
2233+ * Michael Zanetti <michael.zanetti@canonical.com>
2234+ */
2235+
2236+#ifndef GSETTINGS_H
2237+#define GSETTINGS_H
2238+
2239+#include <QObject>
2240+#include <QStringList>
2241+
2242+// This is a mock implementation to not touch GSettings for real during tests
2243+
2244+class GSettings: public QObject
2245+{
2246+ Q_OBJECT
2247+public:
2248+ GSettings(QObject *parent = nullptr);
2249+
2250+ QStringList storedApplications() const;
2251+ void setStoredApplications(const QStringList &storedApplications);
2252+};
2253+
2254+#endif
2255
2256=== removed file 'tests/plugins/Unity/Launcher/launcherbackendtest.cpp'
2257--- tests/plugins/Unity/Launcher/launcherbackendtest.cpp 2014-04-29 14:19:41 +0000
2258+++ tests/plugins/Unity/Launcher/launcherbackendtest.cpp 1970-01-01 00:00:00 +0000
2259@@ -1,239 +0,0 @@
2260-/*
2261- * Copyright 2013 Canonical Ltd.
2262- *
2263- * This program is free software; you can redistribute it and/or modify
2264- * it under the terms of the GNU Lesser General Public License as published by
2265- * the Free Software Foundation; version 3.
2266- *
2267- * This program is distributed in the hope that it will be useful,
2268- * but WITHOUT ANY WARRANTY; without even the implied warranty of
2269- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2270- * GNU Lesser General Public License for more details.
2271- *
2272- * You should have received a copy of the GNU Lesser General Public License
2273- * along with this program. If not, see <http://www.gnu.org/licenses/>.
2274- *
2275- * Authors:
2276- * Michael Terry <michael.terry@canonical.com>
2277- */
2278-
2279-#include "launcherbackend.h"
2280-
2281-#include <QtTest>
2282-#include <QDBusConnection>
2283-#include <QDBusMessage>
2284-#include <QDBusVariant>
2285-
2286-
2287-class LauncherBackendTest : public QObject
2288-{
2289- Q_OBJECT
2290-
2291-private Q_SLOTS:
2292-
2293- void initTestCase()
2294- {
2295- // As we want to test absolute file paths in the .desktop file, we need to modify the
2296- // sample file and replace the path in there with the current build dir.
2297- QSettings settings(QDir::currentPath() + "/click-icon.desktop", QSettings::IniFormat);
2298- settings.setValue("Desktop Entry/Path", QDir::currentPath());
2299- }
2300-
2301- void testFileNames()
2302- {
2303- LauncherBackend backend;
2304-
2305- backend.setStoredApplications(QStringList() << "rel-icon" << "abs-icon" << "invalid");
2306- QCOMPARE(backend.storedApplications(), QStringList() << "rel-icon" << "abs-icon");
2307- }
2308-
2309- void testIcon_data() {
2310- QTest::addColumn<QString>("appId");
2311- QTest::addColumn<QString>("expectedIcon");
2312-
2313- // Needs to expand a relative icon to the absolute path
2314- QTest::newRow("relative icon path") << "rel-icon" << QDir::currentPath() + "/rel-icon.svg";
2315-
2316- // In case an icon is not found on disk, it needs to fallback on image://theme/ for it
2317- QTest::newRow("fallback on theme") << "abs-icon" << "image://theme//path/to/icon.png";
2318-
2319- // Click packages have a relative icon path but an absolute path as a separate entry
2320- QTest::newRow("click package icon") << "click-icon" << QDir::currentPath() + "/click-icon.svg";
2321- }
2322-
2323- void testIcon() {
2324- QFETCH(QString, appId);
2325- QFETCH(QString, expectedIcon);
2326-
2327- LauncherBackend backend;
2328- backend.setStoredApplications(QStringList() << appId);
2329-
2330- QCOMPARE(backend.icon(appId), expectedIcon);
2331- }
2332-
2333- void testGetItem_data() {
2334- QTest::addColumn<QString>("appId");
2335- QTest::addColumn<bool>("exists");
2336-
2337- QTest::newRow("Exists") << "rel-icon" << true;
2338- QTest::newRow("Doesn't Exist") << "does-not-exist" << false;
2339- }
2340-
2341- void testGetItem() {
2342- QFETCH(QString, appId);
2343- QFETCH(bool, exists);
2344-
2345- LauncherBackend backend;
2346- auto item = backend.getItem(appId);
2347-
2348- if (exists) {
2349- QVERIFY(item != nullptr);
2350- } else {
2351- QVERIFY(item == nullptr);
2352- }
2353- }
2354-
2355- void testCount_data() {
2356- QTest::addColumn<QString>("appId");
2357- QTest::addColumn<bool>("setCount");
2358- QTest::addColumn<int>("inCount");
2359- QTest::addColumn<bool>("countVisible");
2360- QTest::addColumn<int>("expectedCount");
2361-
2362- /* Get baseline data on things working */
2363- QTest::newRow("Baseline") << "rel-icon" << false << 0 << false << -1;
2364-
2365- /* Valid count, but not visible */
2366- QTest::newRow("Not visible") << "rel-icon" << true << 42 << false << -1;
2367-
2368- /* Turn it on */
2369- QTest::newRow("Visible Count") << "rel-icon" << true << 42 << true << 42;
2370-
2371- /* Invalide app to load */
2372- QTest::newRow("Invalid App ID") << "this-app-doesnt-exist" << true << 42 << true << -1;
2373- }
2374-
2375- void testCount() {
2376- QFETCH(QString, appId);
2377- QFETCH(bool, setCount);
2378- QFETCH(int, inCount);
2379- QFETCH(bool, countVisible);
2380- QFETCH(int, expectedCount);
2381-
2382- LauncherBackend backend;
2383-
2384- if (setCount)
2385- backend.setCount(appId, inCount);
2386- backend.setCountVisible(appId, countVisible);
2387-
2388- QCOMPARE(backend.count(appId), expectedCount);
2389- }
2390-
2391- void testDbusName_data() {
2392- QTest::addColumn<QString>("decoded");
2393- QTest::addColumn<QString>("encoded");
2394-
2395- /* Passthrough test */
2396- QTest::newRow("Passthrough") << "fine" << "fine";
2397-
2398- /* Number as first characeter */
2399- QTest::newRow("Number first") << "31337" << "_331337";
2400-
2401- /* Underscore test */
2402- QTest::newRow("Underscore test") << "this_is_c_style_namespacing" << "this_5Fis_5Fc_5Fstyle_5Fnamespacing";
2403-
2404- /* Hyphen test */
2405- QTest::newRow("Hyphen test") << "typical-application" << "typical_2Dapplication";
2406-
2407- /* Japanese test */
2408- QTest::newRow("日本語 test") << "日本語" << "_E6_97_A5_E6_9C_AC_E8_AA_9E";
2409- }
2410-
2411- void testDbusName() {
2412- QFETCH(QString, decoded);
2413- QFETCH(QString, encoded);
2414-
2415- QString encodeOut = LauncherBackend::encodeAppId(decoded);
2416- QCOMPARE(encoded, encodeOut);
2417-
2418- QString decodeOut = LauncherBackend::decodeAppId(encoded);
2419- QCOMPARE(decoded, decodeOut);
2420- }
2421-
2422- void testDbusIface_data() {
2423- QTest::addColumn<QString>("appId");
2424- QTest::addColumn<bool>("setCount");
2425- QTest::addColumn<int>("inCount");
2426- QTest::addColumn<bool>("countVisible");
2427- QTest::addColumn<int>("expectedCount");
2428-
2429- /* Get baseline data on things working */
2430- QTest::newRow("Baseline") << "rel-icon" << false << 0 << false << -1;
2431-
2432- /* Turn it on */
2433- QTest::newRow("Visible Count") << "rel-icon" << true << 42 << true << 42;
2434-
2435- /* Invalide app to load */
2436- QTest::newRow("Invalid App ID") << "this-app-doesnt-exist" << true << 42 << true << -1;
2437- }
2438-
2439- void testDbusIface() {
2440- QFETCH(QString, appId);
2441- QFETCH(bool, setCount);
2442- QFETCH(int, inCount);
2443- QFETCH(bool, countVisible);
2444- QFETCH(int, expectedCount);
2445-
2446- QDBusConnection con = QDBusConnection::sessionBus();
2447- QDBusMessage message;
2448- QDBusMessage reply;
2449-
2450- LauncherBackend backend;
2451-
2452- if (setCount) {
2453- message = QDBusMessage::createMethodCall("com.canonical.Unity.Launcher",
2454- "/com/canonical/Unity/Launcher/" + LauncherBackend::encodeAppId(appId),
2455- "org.freedesktop.DBus.Properties",
2456- "Set");
2457- QVariantList cargs;
2458- cargs.append(QString("com.canonical.Unity.Launcher.Item"));
2459- cargs.append(QString("count"));
2460- cargs.append(QVariant::fromValue(QDBusVariant(inCount)));
2461-
2462- message.setArguments(cargs);
2463- reply = con.call(message);
2464- QCOMPARE(reply.type(), QDBusMessage::ReplyMessage);
2465- }
2466-
2467- /* Set countVisible */
2468- message = QDBusMessage::createMethodCall("com.canonical.Unity.Launcher",
2469- "/com/canonical/Unity/Launcher/" + LauncherBackend::encodeAppId(appId),
2470- "org.freedesktop.DBus.Properties",
2471- "Set");
2472- QVariantList cvargs;
2473- cvargs.append(QString("com.canonical.Unity.Launcher.Item"));
2474- cvargs.append(QString("countVisible"));
2475- cvargs.append(QVariant::fromValue(QDBusVariant(countVisible)));
2476-
2477- message.setArguments(cvargs);
2478- reply = con.call(message);
2479- QCOMPARE(reply.type(), QDBusMessage::ReplyMessage);
2480-
2481- /* Get value */
2482- message = QDBusMessage::createMethodCall("com.canonical.Unity.Launcher",
2483- "/com/canonical/Unity/Launcher/" + LauncherBackend::encodeAppId(appId),
2484- "org.freedesktop.DBus.Properties",
2485- "Get");
2486- QVariantList getargs;
2487- getargs.append(QString("com.canonical.Unity.Launcher.Item"));
2488- getargs.append(QString("count"));
2489-
2490- message.setArguments(getargs);
2491- reply = con.call(message);
2492- QCOMPARE(reply.type(), QDBusMessage::ReplyMessage);
2493- QCOMPARE(reply.arguments()[0].value<QDBusVariant>().variant().toInt(), expectedCount);
2494- }
2495-};
2496-
2497-QTEST_GUILESS_MAIN(LauncherBackendTest)
2498-#include "launcherbackendtest.moc"
2499
2500=== modified file 'tests/plugins/Unity/Launcher/launchermodeltest.cpp'
2501--- tests/plugins/Unity/Launcher/launchermodeltest.cpp 2014-08-13 19:50:09 +0000
2502+++ tests/plugins/Unity/Launcher/launchermodeltest.cpp 2014-09-18 21:04:46 +0000
2503@@ -23,8 +23,12 @@
2504
2505 #include "launcheritem.h"
2506 #include "launchermodel.h"
2507+#include "dbusinterface.h"
2508
2509 #include <QtTest>
2510+#include <QDBusInterface>
2511+#include <QDBusReply>
2512+#include <QDomDocument>
2513
2514 // This is a mock, specifically to test the LauncherModel
2515 class MockApp: public unity::shell::application::ApplicationInfoInterface
2516@@ -287,10 +291,86 @@
2517 QCOMPARE(launcherModel->getUrlForAppId(QString()), QString());
2518 QCOMPARE(launcherModel->getUrlForAppId(""), QString());
2519 QCOMPARE(launcherModel->getUrlForAppId("no-name"), QString("application:///no-name.desktop"));
2520- QCOMPARE(launcherModel->getUrlForAppId("com.test.good"), QString("appid://com.test.good/first-listed-app/current-user-version"));
2521+ QCOMPARE(launcherModel->getUrlForAppId("com.test.good"), QString("application:///com.test.good.desktop"));
2522 QCOMPARE(launcherModel->getUrlForAppId("com.test.good_application"), QString("appid://com.test.good/application/current-user-version"));
2523 QCOMPARE(launcherModel->getUrlForAppId("com.test.good_application_1.2.3"), QString("appid://com.test.good/application/current-user-version"));
2524 }
2525+
2526+ void testIntrospection() {
2527+ QDBusInterface interface("com.canonical.Unity.Launcher", "/com/canonical/Unity/Launcher", "org.freedesktop.DBus.Introspectable");
2528+ QDBusReply<QString> reply = interface.call("Introspect");
2529+ QStringList nodes = extractNodes(reply.value());
2530+ QCOMPARE(nodes.count(), launcherModel->rowCount());
2531+
2532+ appManager->addApplication(new MockApp("foobar"));
2533+ reply = interface.call("Introspect");
2534+ nodes = extractNodes(reply.value());
2535+ QCOMPARE(nodes.contains("foobar"), true);
2536+
2537+ appManager->removeApplication(2);
2538+ reply = interface.call("Introspect");
2539+ nodes = extractNodes(reply.value());
2540+ QCOMPARE(nodes.contains("foobar"), false);
2541+ }
2542+
2543+ QStringList extractNodes(const QString &introspectionXml) {
2544+ QXmlStreamReader introspectReply(introspectionXml);
2545+
2546+ QStringList ret;
2547+ while (!introspectReply.atEnd() && !introspectReply.hasError()) {
2548+ QXmlStreamReader::TokenType token = introspectReply.readNext();
2549+
2550+ if (token == QXmlStreamReader::StartElement) {
2551+ if (introspectReply.name() == "node" && introspectReply.attributes().count() > 0) {
2552+ ret << introspectReply.attributes().value("name").toString();
2553+ }
2554+ }
2555+ }
2556+ return ret;
2557+ }
2558+
2559+ void testCountEmblems() {
2560+ // Call GetAll on abs-icon
2561+ QDBusInterface interface("com.canonical.Unity.Launcher", "/com/canonical/Unity/Launcher/abs_2Dicon", "org.freedesktop.DBus.Properties");
2562+ QDBusReply<QVariantMap> reply = interface.call("GetAll");
2563+ QVariantMap map = reply.value();
2564+
2565+ // Make sure GetAll returns a map with count and countVisible props
2566+ QCOMPARE(map.contains("count"), true);
2567+ QCOMPARE(map.contains("countVisible"), true);
2568+
2569+ // Make sure count is intitilized to 0 and non-visible
2570+ QCOMPARE(map.value("count").toInt(), 0);
2571+ QCOMPARE(map.value("countVisible").toBool(), false);
2572+
2573+ // Now make it visible and set it to 55 through D-Bus
2574+ interface.call("Set", "com.canonical.Unity.Launcher.Item", "count", 55);
2575+ interface.call("Set", "com.canonical.Unity.Launcher.Item", "countVisible", true);
2576+
2577+ // Fetch it again using GetAll
2578+ reply = interface.call("GetAll");
2579+ map = reply.value();
2580+
2581+ // Make sure values have changed on the D-Bus interface
2582+ QCOMPARE(map.value("count").toInt(), 55);
2583+ QCOMPARE(map.value("countVisible").toBool(), true);
2584+
2585+ // Now the item on the upper side of the API
2586+ int index = launcherModel->findApplication("abs-icon");
2587+ QCOMPARE(index >= 0, true);
2588+
2589+ // And make sure values have changed there as well
2590+ QCOMPARE(launcherModel->get(index)->countVisible(), true);
2591+ QCOMPARE(launcherModel->get(index)->count(), 55);
2592+ }
2593+
2594+ void testRefresh() {
2595+ QDBusInterface interface("com.canonical.Unity.Launcher", "/com/canonical/Unity/Launcher", "com.canonical.Unity.Launcher");
2596+ QDBusReply<void> reply = interface.call("Refresh");
2597+
2598+ // Make sure the call to Refresh returned without error.
2599+ QCOMPARE(reply.isValid(), true);
2600+ }
2601 };
2602
2603 QTEST_GUILESS_MAIN(LauncherModelTest)
2604
2605=== modified file 'tests/qmltests/Launcher/tst_Launcher.qml'
2606--- tests/qmltests/Launcher/tst_Launcher.qml 2014-07-24 22:37:26 +0000
2607+++ tests/qmltests/Launcher/tst_Launcher.qml 2014-09-18 21:04:46 +0000
2608@@ -194,7 +194,9 @@
2609 var launcherListView = findChild(launcher, "launcherListView");
2610 for (var i = 0; i < launcherListView.count; ++i) {
2611 var delegate = findChild(launcherListView, "launcherDelegate" + i)
2612- compare(findChild(delegate, "countEmblem").visible, LauncherModel.get(i).count > 0)
2613+ compare(findChild(delegate, "countEmblem").visible, LauncherModel.get(i).countVisible)
2614+ // Intentionally allow type coercion (string/number)
2615+ compare(findChild(delegate, "countLabel").text == LauncherModel.get(i).count, true)
2616 }
2617 }
2618

Subscribers

People subscribed via source and target branches