on xenial help sign in
Make the launcher update on dconf changes Fixes: 1376707

Approved by: Daniel d'Andrada

commit324c3e71a08cd128260ecd88a4aa780259a7b2d1 authorMichael Zanetti <michael.zanetti@canonical.com> dateThu 30 Oct 2014, 21:40 (12 years ago) parent76225853a3879f
8 files changed+205−57
plugins/Unity/Launcher/desktopfilehandler.cpp+1−1 plugins/Unity/Launcher/gsettings.cpp+11−7 plugins/Unity/Launcher/gsettings.h+10−1 plugins/Unity/Launcher/launchermodel.cpp+75−32 plugins/Unity/Launcher/launchermodel.h+5−1 tests/plugins/Unity/Launcher/gsettings.cpp+8−4 tests/plugins/Unity/Launcher/gsettings.h+8 tests/plugins/Unity/Launcher/launchermodeltest.cpp+87−11
plugins/Unity/Launcher/desktopfilehandler.cpp+1−1
@@ -70,7 +70,7 @@ void DesktopFileHandler::load()
7070
7171 QStringList searchDirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation);
7272#ifdef LAUNCHER_TESTING
73− searchDirs << "";
73+ searchDirs << ".";
7474#endif
7575
7676 QString path;
plugins/Unity/Launcher/gsettings.cpp+11−7
@@ -19,22 +19,20 @@
1919
2020#include "gsettings.h"
2121
22−#include <QGSettings>
2322#include <QVariant>
2423
2524GSettings::GSettings(QObject *parent):
2625 QObject(parent)
2726{
28−
27+ m_gSettings = new QGSettings("com.canonical.Unity.Launcher", "/com/canonical/unity/launcher/", this);
28+ connect(m_gSettings, &QGSettings::changed, this, &GSettings::onSettingsChanged);
2929}
3030
3131QStringList GSettings::storedApplications() const
3232{
3333 QStringList storedApps;
3434
35− QGSettings gSettings("com.canonical.Unity.Launcher", "/com/canonical/unity/launcher/");
36−
37− Q_FOREACH(const QString &entry, gSettings.get("items").toStringList()) {
35+ Q_FOREACH(const QString &entry, m_gSettings->get("items").toStringList()) {
3836 if (entry.startsWith("application:///")) {
3937 // convert legacy entries to new world appids
4038 QString appId = entry;
@@ -57,10 +55,16 @@ QStringList GSettings::storedApplications() const
5755
5856void GSettings::setStoredApplications(const QStringList &storedApplications)
5957{
60− QGSettings gSettings("com.canonical.Unity.Launcher", "/com/canonical/unity/launcher/");
6158 QStringList gSettingsList;
6259 Q_FOREACH(const QString &entry, storedApplications) {
6360 gSettingsList << QString("appid://%1").arg(entry);
6461 }
65− gSettings.set("items", gSettingsList);
62+ m_gSettings->set("items", gSettingsList);
63+}
64+
65+void GSettings::onSettingsChanged(const QString &key)
66+{
67+ if (key == "items") {
68+ Q_EMIT changed();
69+ }
6670}
plugins/Unity/Launcher/gsettings.h+10−1
@@ -22,7 +22,7 @@
2222
2323#include <QObject>
2424#include <QStringList>
25−
25+#include <QGSettings>
2626
2727class GSettings: public QObject
2828{
@@ -32,6 +32,15 @@ public:
3232
3333 QStringList storedApplications() const;
3434 void setStoredApplications(const QStringList &storedApplications);
35+
36+Q_SIGNALS:
37+ void changed();
38+
39+private Q_SLOTS:
40+ void onSettingsChanged(const QString &key);
41+
42+private:
43+ QGSettings *m_gSettings;
3544};
3645
3746#endif
plugins/Unity/Launcher/launchermodel.cpp+75−32
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2013 Canonical Ltd.
2+ * Copyright 2013-2014 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU Lesser General Public License as published by
@@ -40,20 +40,9 @@ LauncherModel::LauncherModel(QObject *parent):
4040 connect(m_dbusIface, &DBusInterface::countVisibleChanged, this, &LauncherModel::countVisibleChanged);
4141 connect(m_dbusIface, &DBusInterface::refreshCalled, this, &LauncherModel::refresh);
4242
43− Q_FOREACH (const QString &entry, m_settings->storedApplications()) {
44− DesktopFileHandler desktopFile(entry);
45− if (!desktopFile.isValid()) {
46− qWarning() << "Couldn't find a .desktop file for" << entry << ". Skipping...";
47− continue;
48− }
43+ connect(m_settings, &GSettings::changed, this, &LauncherModel::refresh);
4944
50− LauncherItem *item = new LauncherItem(entry,
51− desktopFile.displayName(),
52− desktopFile.icon(),
53− this);
54− item->setPinned(true);
55− m_list.append(item);
56− }
45+ refresh();
5746}
5847
5948LauncherModel::~LauncherModel()
@@ -173,22 +162,7 @@ void LauncherModel::pin(const QString &appId, int index)
173162
174163void LauncherModel::requestRemove(const QString &appId)
175164{
176− int index = findApplication(appId);
177− if (index < 0) {
178− return;
179− }
180−
181− if (m_appManager->findApplication(appId)) {
182− m_list.at(index)->setPinned(false);
183− QModelIndex modelIndex = this->index(index);
184− Q_EMIT dataChanged(modelIndex, modelIndex, QVector<int>() << RolePinned);
185− return;
186− }
187−
188− beginRemoveRows(QModelIndex(), index, index);
189− m_list.takeAt(index)->deleteLater();
190− endRemoveRows();
191−
165+ unpin(appId);
192166 storeAppList();
193167}
194168
@@ -299,6 +273,24 @@ void LauncherModel::storeAppList()
299273 m_settings->setStoredApplications(appIds);
300274}
301275
276+void LauncherModel::unpin(const QString &appId)
277+{
278+ int index = findApplication(appId);
279+ if (index < 0) {
280+ return;
281+ }
282+
283+ if (m_appManager->findApplication(appId)) {
284+ m_list.at(index)->setPinned(false);
285+ QModelIndex modelIndex = this->index(index);
286+ Q_EMIT dataChanged(modelIndex, modelIndex, QVector<int>() << RolePinned);
287+ } else {
288+ beginRemoveRows(QModelIndex(), index, index);
289+ m_list.takeAt(index)->deleteLater();
290+ endRemoveRows();
291+ }
292+}
293+
302294int LauncherModel::findApplication(const QString &appId)
303295{
304296 for (int i = 0; i < m_list.count(); ++i) {
@@ -320,7 +312,6 @@ void LauncherModel::progressChanged(const QString &appId, int progress)
320312 }
321313}
322314
323−
324315void LauncherModel::countChanged(const QString &appId, int count)
325316{
326317 int idx = findApplication(appId);
@@ -362,10 +353,15 @@ void LauncherModel::countVisibleChanged(const QString &appId, int countVisible)
362353
363354void LauncherModel::refresh()
364355{
356+ // First walk through all the existing items and see if we need to remove something
365357 QList<LauncherItem*> toBeRemoved;
366358 Q_FOREACH (LauncherItem* item, m_list) {
367359 DesktopFileHandler desktopFile(item->appId());
368360 if (!desktopFile.isValid()) {
361+ // Desktop file not available for this app => drop it!
362+ toBeRemoved << item;
363+ } else if (!m_settings->storedApplications().contains(item->appId())) {
364+ // Item not in settings any more => drop it!
369365 toBeRemoved << item;
370366 } else {
371367 int idx = m_list.indexOf(item);
@@ -376,7 +372,54 @@ void LauncherModel::refresh()
376372 }
377373
378374 Q_FOREACH (LauncherItem* item, toBeRemoved) {
379− requestRemove(item->appId());
375+ unpin(item->appId());
376+ }
377+
378+ // This brings the Launcher into sync with the settings backend again. There's an issue though:
379+ // If we can't find a .desktop file for an entry we need to skip it. That makes our settingsIndex
380+ // go out of sync with the actual index of items. So let's also use an addedIndex which reflects
381+ // the settingsIndex minus the skipped items.
382+ int addedIndex = 0;
383+
384+ // Now walk through settings and see if we need to add something
385+ for (int settingsIndex = 0; settingsIndex < m_settings->storedApplications().count(); ++settingsIndex) {
386+ QString entry = m_settings->storedApplications().at(settingsIndex);
387+ int itemIndex = -1;
388+ for (int i = 0; i < m_list.count(); ++i) {
389+ if (m_list.at(i)->appId() == entry) {
390+ itemIndex = i;
391+ break;
392+ }
393+ }
394+
395+ if (itemIndex == -1) {
396+ // Need to add it. Just add it into the addedIndex to keep same ordering as the list
397+ // in the settings.
398+ DesktopFileHandler desktopFile(entry);
399+ if (!desktopFile.isValid()) {
400+ qWarning() << "Couldn't find a .desktop file for" << entry << ". Skipping...";
401+ continue;
402+ }
403+
404+ LauncherItem *item = new LauncherItem(entry,
405+ desktopFile.displayName(),
406+ desktopFile.icon(),
407+ this);
408+ item->setPinned(true);
409+ beginInsertRows(QModelIndex(), addedIndex, addedIndex);
410+ m_list.insert(addedIndex, item);
411+ endInsertRows();
412+ } else if (itemIndex != addedIndex) {
413+ // The item is already there, but it is in a different place then in the settings.
414+ // Move it to the addedIndex
415+ beginMoveRows(QModelIndex(), itemIndex, itemIndex, QModelIndex(), addedIndex);
416+ m_list.move(itemIndex, addedIndex);
417+ endMoveRows();
418+ }
419+
420+ // Just like settingsIndex, this will increase with every item, except the ones we
421+ // skipped with the "continue" call above.
422+ addedIndex++;
380423 }
381424}
382425
plugins/Unity/Launcher/launchermodel.h+5−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2013 Canonical Ltd.
2+ * Copyright 2013-2014 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU Lesser General Public License as published by
@@ -62,6 +62,8 @@ public Q_SLOTS:
6262private:
6363 void storeAppList();
6464
65+ void unpin(const QString &appId);
66+
6567private Q_SLOTS:
6668 void countChanged(const QString &appId, int count);
6769 void countVisibleChanged(const QString &appId, int count);
@@ -79,6 +81,8 @@ private:
7981 DBusInterface *m_dbusIface;
8082
8183 ApplicationManagerInterface *m_appManager;
84+
85+ friend class LauncherModelTest;
8286};
8387
8488#endif // LAUNCHERMODEL_H
tests/plugins/Unity/Launcher/gsettings.cpp+8−4
@@ -19,8 +19,6 @@
1919
2020#include "gsettings.h"
2121
22−#include <QDebug>
23−
2422// This is a mock implementation to not touch GSettings for real during tests
2523
2624GSettings::GSettings(QObject *parent):
@@ -31,10 +29,16 @@ GSettings::GSettings(QObject *parent):
3129
3230QStringList GSettings::storedApplications() const
3331{
34− return QStringList();
32+ return m_entries;
3533}
3634
3735void GSettings::setStoredApplications(const QStringList &storedApplications)
3836{
39− Q_UNUSED(storedApplications)
37+ m_entries = storedApplications;
38+}
39+
40+void GSettings::simulateDConfChanged(const QStringList &storedApplications)
41+{
42+ setStoredApplications(storedApplications);
43+ Q_EMIT changed();
4044}
tests/plugins/Unity/Launcher/gsettings.h+8
@@ -33,6 +33,14 @@ public:
3333
3434 QStringList storedApplications() const;
3535 void setStoredApplications(const QStringList &storedApplications);
36+
37+ void simulateDConfChanged(const QStringList &storedApplications);
38+
39+Q_SIGNALS:
40+ void changed();
41+
42+private:
43+ QStringList m_entries;
3644};
3745
3846#endif
tests/plugins/Unity/Launcher/launchermodeltest.cpp+87−11
@@ -24,6 +24,7 @@
2424#include "launcheritem.h"
2525#include "launchermodel.h"
2626#include "dbusinterface.h"
27+#include "gsettings.h"
2728
2829#include <QtTest>
2930#include <QDBusInterface>
@@ -81,7 +82,15 @@ public:
8182 return nullptr;
8283 }
8384 unity::shell::application::ApplicationInfoInterface *startApplication(const QString &, const QStringList &) { return nullptr; }
84− bool stopApplication(const QString &) { return false; }
85+ bool stopApplication(const QString &appId) {
86+ Q_FOREACH(MockApp* app, m_list) {
87+ if (app->appId() == appId) {
88+ removeApplication(m_list.indexOf(app));
89+ return true;
90+ }
91+ }
92+ return false;
93+ }
8594 bool focusApplication(const QString &appId) {
8695 Q_FOREACH(MockApp* app, m_list) {
8796 app->setFocused(app->appId() == appId);
@@ -137,6 +146,8 @@ private Q_SLOTS:
137146
138147 appManager->addApplication(new MockApp("no-icon"));
139148 QCOMPARE(launcherModel->rowCount(QModelIndex()), 2);
149+
150+ launcherModel->m_settings->setStoredApplications(QStringList());
140151 }
141152
142153 // Removing apps from appmanager and launcher as pinned ones would stick
@@ -172,14 +183,15 @@ private Q_SLOTS:
172183 launcherModel->pin(launcherModel->get(0)->appId());
173184 QCOMPARE(launcherModel->get(0)->pinned(), true);
174185 QCOMPARE(launcherModel->get(1)->pinned(), false);
175− QCOMPARE(spy.count(), 1);
186+ QCOMPARE(spy.count() > 0, true);
176187 QCOMPARE(spy.at(0).at(2).value<QVector<int>>().first(), (int)LauncherModelInterface::RolePinned);
177188
189+ spy.clear();
178190 launcherModel->requestRemove(launcherModel->get(0)->appId());
179191 QCOMPARE(launcherModel->get(0)->pinned(), false);
180192 QCOMPARE(launcherModel->get(1)->pinned(), false);
181− QCOMPARE(spy.count(), 2);
182− QCOMPARE(spy.at(1).at(2).value<QVector<int>>().first(), (int)LauncherModelInterface::RolePinned);
193+ QCOMPARE(spy.count(), 1);
194+ QCOMPARE(spy.at(0).at(2).value<QVector<int>>().first(), (int)LauncherModelInterface::RolePinned);
183195 }
184196
185197 void testRemove_data() {
@@ -188,7 +200,7 @@ private Q_SLOTS:
188200 QTest::addColumn<bool>("running");
189201
190202 QTest::newRow("non-pinned, running") << false << true;
191− QTest::newRow("pinned, running") << true << false;
203+ QTest::newRow("pinned, running") << true << true;
192204 QTest::newRow("pinned, non-running") << true << false;
193205 }
194206
@@ -201,23 +213,24 @@ private Q_SLOTS:
201213
202214 // pin one if required
203215 if (pinned) {
204− launcherModel->pin(launcherModel->get(1)->appId());
216+ launcherModel->pin("abs-icon");
205217 }
206218
207219 // stop it if required
208220 if (!running) {
209− appManager->removeApplication(1);
221+ appManager->stopApplication("abs-icon");
210222 }
211223
212224 // Now remove it
213− launcherModel->requestRemove(launcherModel->get(1)->appId());
225+ launcherModel->requestRemove("abs-icon");
214226
215227 if (running) {
216228 // both apps are running, both apps must still be here
217229 QCOMPARE(launcherModel->rowCount(QModelIndex()), 2);
218230
219231 // Item must be unpinned now
220− QCOMPARE(launcherModel->get(1)->pinned(), false);
232+ int index = launcherModel->findApplication("abs-icon");
233+ QCOMPARE(launcherModel->get(index)->pinned(), false);
221234
222235 } else if (pinned) {
223236 // Item 1 must go away, item 0 is here to stay
@@ -226,7 +239,7 @@ private Q_SLOTS:
226239
227240 // done our checks. now stop the app if was still running
228241 if (running) {
229− appManager->removeApplication(1);
242+ appManager->stopApplication("abs-icon");
230243 }
231244
232245 // It needs to go away in any case now
@@ -371,12 +384,75 @@ private Q_SLOTS:
371384 QCOMPARE(launcherModel->get(index)->count(), 55);
372385 }
373386
374− void testRefresh() {
387+ void testRefreshAfterDeletedDesktopFiles_data() {
388+ QTest::addColumn<bool>("deleted");
389+ QTest::newRow("have .desktop files") << false;
390+ QTest::newRow("deleted .desktop files") << true;
391+ }
392+
393+ void testRefreshAfterDeletedDesktopFiles() {
394+ QFETCH(bool, deleted);
395+
396+ // pin both apps
397+ launcherModel->pin("abs-icon");
398+ launcherModel->pin("no-icon");
399+ // close both apps
400+ appManager->removeApplication(0);
401+ appManager->removeApplication(0);
402+
403+ // "delete" the .desktop files
404+ QString oldCurrent = QDir::currentPath();
405+ if (deleted) {
406+ // In testing mode, the launcher searches the current dir for the sample .desktop file
407+ // We can make that fail by changing the current dir
408+ QDir::setCurrent("..");
409+ }
410+
411+ // Call refresh
375412 QDBusInterface interface("com.canonical.Unity.Launcher", "/com/canonical/Unity/Launcher", "com.canonical.Unity.Launcher");
376413 QDBusReply<void> reply = interface.call("Refresh");
377414
378415 // Make sure the call to Refresh returned without error.
379416 QCOMPARE(reply.isValid(), true);
417+
418+ QCOMPARE(launcherModel->rowCount(), deleted ? 0 : 2);
419+
420+ // Restoring current dir
421+ QDir::setCurrent(oldCurrent);
422+ }
423+
424+ void testSettings() {
425+ GSettings *settings = launcherModel->m_settings;
426+
427+ // Nothing pinned at startup
428+ QCOMPARE(settings->storedApplications().count(), 0);
429+
430+ // pin both apps
431+ launcherModel->pin("abs-icon");
432+ launcherModel->pin("no-icon");
433+
434+ // Now settings should have 2 apps
435+ QCOMPARE(settings->storedApplications().count(), 2);
436+
437+ // close both apps
438+ appManager->removeApplication(0);
439+ appManager->removeApplication(0);
440+
441+ // Now settings should have 2 apps
442+ QCOMPARE(settings->storedApplications().count(), 2);
443+
444+ // Now remove 1 app through the backend, make sure one is still there
445+ settings->simulateDConfChanged(QStringList() << "abs-icon");
446+ QCOMPARE(settings->storedApplications().count(), 1);
447+
448+ // Check if it disappeared from the frontend too
449+ QCOMPARE(launcherModel->rowCount(), 1);
450+
451+ // Add them back but in reverse order
452+ settings->simulateDConfChanged(QStringList() << "no-icon" << "abs-icon");
453+ QCOMPARE(launcherModel->rowCount(), 2);
454+ QCOMPARE(launcherModel->get(0)->appId(), QString("no-icon"));
455+ QCOMPARE(launcherModel->get(1)->appId(), QString("abs-icon"));
380456 }
381457};
382458