on xenial help sign in
launcher parity: close apps from quicklist
3848075Lukáš Tinkl
plugins/Unity/Launcher/launcheritem.cpp+10
@@ -40,10 +40,15 @@ LauncherItem::LauncherItem(const QString &appId, const QString &name, const QStr
40 nameAction.setActionId("launch_item");
40 nameAction.setActionId("launch_item");
41 nameAction.setText(m_name);
41 nameAction.setText(m_name);
42 m_quickList->appendAction(nameAction);
42 m_quickList->appendAction(nameAction);
43+
43 QuickListEntry pinningAction;
44 QuickListEntry pinningAction;
44 pinningAction.setActionId("pin_item");
45 pinningAction.setActionId("pin_item");
45 pinningAction.setText(gettext("Pin shortcut"));
46 pinningAction.setText(gettext("Pin shortcut"));
46 m_quickList->appendAction(pinningAction);
47 m_quickList->appendAction(pinningAction);
48+
49+ m_quitAction.setActionId("stop_item");
50+ m_quitAction.setIcon("application-exit");
51+ m_quitAction.setText(gettext("Quit"));
47}
52}
48
53
49QString LauncherItem::appId() const
54QString LauncherItem::appId() const
@@ -110,6 +115,11 @@ void LauncherItem::setRunning(bool running)
110{
115{
111 if (m_running != running) {
116 if (m_running != running) {
112 m_running = running;
117 m_running = running;
118+ if (m_running) { // add the quit action
119+ m_quickList->appendAction(m_quitAction);
120+ } else { // remove the quit action
121+ m_quickList->removeAction(m_quitAction);
122+ }
113 Q_EMIT runningChanged(running);
123 Q_EMIT runningChanged(running);
114 }
124 }
115}
125}
plugins/Unity/Launcher/launcheritem.h+1
@@ -70,6 +70,7 @@ private:
70 bool m_countVisible;
70 bool m_countVisible;
71 bool m_focused;
71 bool m_focused;
72 QuickListModel *m_quickList;
72 QuickListModel *m_quickList;
73+ QuickListEntry m_quitAction;
73
74
74 friend class LauncherModel;
75 friend class LauncherModel;
75};
76};
plugins/Unity/Launcher/launchermodel.cpp+29−14
@@ -82,9 +82,14 @@ QVariant LauncherModel::data(const QModelIndex &index, int role) const
82 return item->progress();
82 return item->progress();
83 case RoleFocused:
83 case RoleFocused:
84 return item->focused();
84 return item->focused();
85+ case RoleRunning:
86+ return item->running();
87+ default:
88+ qWarning() << Q_FUNC_INFO << "missing role, implement me";
89+ return QVariant();
85 }
90 }
86
91
87− return QVariant();
92+ return LauncherModelInterface::data(index, role);
88}
93}
89
94
90unity::shell::launcher::LauncherItemInterface *LauncherModel::get(int index) const
95unity::shell::launcher::LauncherItemInterface *LauncherModel::get(int index) const
@@ -134,7 +139,7 @@ void LauncherModel::pin(const QString &appId, int index)
134 if (index == -1 || index == currentIndex) {
139 if (index == -1 || index == currentIndex) {
135 m_list.at(currentIndex)->setPinned(true);
140 m_list.at(currentIndex)->setPinned(true);
136 QModelIndex modelIndex = this->index(currentIndex);
141 QModelIndex modelIndex = this->index(currentIndex);
137− Q_EMIT dataChanged(modelIndex, modelIndex, QVector<int>() << RolePinned);
142+ Q_EMIT dataChanged(modelIndex, modelIndex, {RolePinned});
138 } else {
143 } else {
139 move(currentIndex, index);
144 move(currentIndex, index);
140 // move() will store the list to the backend itself, so just exit at this point.
145 // move() will store the list to the backend itself, so just exit at this point.
@@ -147,7 +152,7 @@ void LauncherModel::pin(const QString &appId, int index)
147
152
148 DesktopFileHandler desktopFile(appId);
153 DesktopFileHandler desktopFile(appId);
149 if (!desktopFile.isValid()) {
154 if (!desktopFile.isValid()) {
150− qWarning() << "Can't pin this application, there is no .destkop file available.";
155+ qWarning() << "Can't pin this application, there is no .desktop file available.";
151 return;
156 return;
152 }
157 }
153
158
@@ -172,7 +177,7 @@ void LauncherModel::requestRemove(const QString &appId)
172
177
173void LauncherModel::quickListActionInvoked(const QString &appId, int actionIndex)
178void LauncherModel::quickListActionInvoked(const QString &appId, int actionIndex)
174{
179{
175− int index = findApplication(appId);
180+ const int index = findApplication(appId);
176 if (index < 0) {
181 if (index < 0) {
177 return;
182 return;
178 }
183 }
@@ -273,6 +278,7 @@ bool LauncherModel::onlyPinned() const
273
278
274void LauncherModel::setOnlyPinned(bool onlyPinned) {
279void LauncherModel::setOnlyPinned(bool onlyPinned) {
275 Q_UNUSED(onlyPinned);
280 Q_UNUSED(onlyPinned);
281+ // FIXME implement showing only pinned apps
276 qWarning() << "This launcher implementation does not support showing only pinned apps";
282 qWarning() << "This launcher implementation does not support showing only pinned apps";
277}
283}
278
284
@@ -299,7 +305,7 @@ void LauncherModel::unpin(const QString &appId)
299 if (m_list.at(index)->pinned()) {
305 if (m_list.at(index)->pinned()) {
300 m_list.at(index)->setPinned(false);
306 m_list.at(index)->setPinned(false);
301 QModelIndex modelIndex = this->index(index);
307 QModelIndex modelIndex = this->index(index);
302− Q_EMIT dataChanged(modelIndex, modelIndex, QVector<int>() << RolePinned);
308+ Q_EMIT dataChanged(modelIndex, modelIndex, {RolePinned});
303 }
309 }
304 } else {
310 } else {
305 beginRemoveRows(QModelIndex(), index, index);
311 beginRemoveRows(QModelIndex(), index, index);
@@ -325,7 +331,7 @@ void LauncherModel::progressChanged(const QString &appId, int progress)
325 if (idx >= 0) {
331 if (idx >= 0) {
326 LauncherItem *item = m_list.at(idx);
332 LauncherItem *item = m_list.at(idx);
327 item->setProgress(progress);
333 item->setProgress(progress);
328− Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleProgress);
334+ Q_EMIT dataChanged(index(idx), index(idx), {RoleProgress});
329 }
335 }
330}
336}
331
337
@@ -335,7 +341,7 @@ void LauncherModel::countChanged(const QString &appId, int count)
335 if (idx >= 0) {
341 if (idx >= 0) {
336 LauncherItem *item = m_list.at(idx);
342 LauncherItem *item = m_list.at(idx);
337 item->setCount(count);
343 item->setCount(count);
338− Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleCount);
344+ Q_EMIT dataChanged(index(idx), index(idx), {RoleCount});
339 m_asAdapter->syncItems(m_list);
345 m_asAdapter->syncItems(m_list);
340 }
346 }
341}
347}
@@ -346,7 +352,7 @@ void LauncherModel::countVisibleChanged(const QString &appId, int countVisible)
346 if (idx >= 0) {
352 if (idx >= 0) {
347 LauncherItem *item = m_list.at(idx);
353 LauncherItem *item = m_list.at(idx);
348 item->setCountVisible(countVisible);
354 item->setCountVisible(countVisible);
349− Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleCountVisible);
355+ Q_EMIT dataChanged(index(idx), index(idx), {RoleCountVisible});
350
356
351 // If countVisible goes to false, and the item is neither pinned nor recent we can drop it
357 // If countVisible goes to false, and the item is neither pinned nor recent we can drop it
352 if (!countVisible && !item->pinned() && !item->recent()) {
358 if (!countVisible && !item->pinned() && !item->recent()) {
@@ -388,7 +394,7 @@ void LauncherModel::refresh()
388 item->setName(desktopFile.displayName());
394 item->setName(desktopFile.displayName());
389 item->setIcon(desktopFile.icon());
395 item->setIcon(desktopFile.icon());
390 item->setPinned(item->pinned()); // update pinned text if needed
396 item->setPinned(item->pinned()); // update pinned text if needed
391− Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleName << RoleIcon);
397+ Q_EMIT dataChanged(index(idx), index(idx), {RoleName, RoleIcon});
392 }
398 }
393 }
399 }
394
400
@@ -475,12 +481,13 @@ void LauncherModel::applicationAdded(const QModelIndex &parent, int row)
475 if (!item->recent()) {
481 if (!item->recent()) {
476 item->setRecent(true);
482 item->setRecent(true);
477 m_asAdapter->syncItems(m_list);
483 m_asAdapter->syncItems(m_list);
478− Q_EMIT dataChanged(index(itemIndex), index(itemIndex), QVector<int>() << RoleRecent);
484+ Q_EMIT dataChanged(index(itemIndex), index(itemIndex), {RoleRecent});
479 }
485 }
480− // Shall we paint some running/recent app highlight? If yes, do it here.
486+ // TODO Shall we paint some running/recent app highlight? If yes, do it here.
481 } else {
487 } else {
482 LauncherItem *item = new LauncherItem(app->appId(), app->name(), app->icon().toString(), this);
488 LauncherItem *item = new LauncherItem(app->appId(), app->name(), app->icon().toString(), this);
483 item->setRecent(true);
489 item->setRecent(true);
490+ item->setRunning(true);
484 item->setFocused(app->focused());
491 item->setFocused(app->focused());
485
492
486 beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
493 beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
@@ -502,7 +509,15 @@ void LauncherModel::applicationRemoved(const QModelIndex &parent, int row)
502 }
509 }
503 }
510 }
504
511
512+ if (appIndex < 0) {
513+ qWarning() << Q_FUNC_INFO << "appIndex not found";
514+ return;
515+ }
516+
505− if (appIndex > -1 && !m_list.at(appIndex)->pinned()) {
517+ LauncherItem * item = m_list.at(appIndex);
518+ item->setRunning(false);
519+
520+ if (!item->pinned()) {
506 beginRemoveRows(QModelIndex(), appIndex, appIndex);
521 beginRemoveRows(QModelIndex(), appIndex, appIndex);
507 m_list.takeAt(appIndex)->deleteLater();
522 m_list.takeAt(appIndex)->deleteLater();
508 endRemoveRows();
523 endRemoveRows();
@@ -517,10 +532,10 @@ void LauncherModel::focusedAppIdChanged()
517 LauncherItem *item = m_list.at(i);
532 LauncherItem *item = m_list.at(i);
518 if (!item->focused() && item->appId() == appId) {
533 if (!item->focused() && item->appId() == appId) {
519 item->setFocused(true);
534 item->setFocused(true);
520− Q_EMIT dataChanged(index(i), index(i), QVector<int>() << RoleFocused);
535+ Q_EMIT dataChanged(index(i), index(i), {RoleFocused});
521 } else if (item->focused() && item->appId() != appId) {
536 } else if (item->focused() && item->appId() != appId) {
522 item->setFocused(false);
537 item->setFocused(false);
523− Q_EMIT dataChanged(index(i), index(i), QVector<int>() << RoleFocused);
538+ Q_EMIT dataChanged(index(i), index(i), {RoleFocused});
524 }
539 }
525 }
540 }
526}
541}
plugins/Unity/Launcher/quicklistentry.cpp+6−2
@@ -1,4 +1,4 @@
1−/* Copyright (C) 2013 Canonical, Ltd.
1+/* Copyright (C) 2013, 2015 Canonical, Ltd.
2 *
2 *
3 * Authors:
3 * Authors:
4 * Michael Zanetti <michael.zanetti@canonical.com>
4 * Michael Zanetti <michael.zanetti@canonical.com>
@@ -20,7 +20,6 @@
20
20
21QuickListEntry::QuickListEntry()
21QuickListEntry::QuickListEntry()
22{
22{
23−
24}
23}
25
24
26QString QuickListEntry::actionId() const
25QString QuickListEntry::actionId() const
@@ -57,3 +56,8 @@ bool QuickListEntry::clickable() const
57{
56{
58 return !m_actionId.isEmpty();
57 return !m_actionId.isEmpty();
59}
58}
59+
60+bool QuickListEntry::operator==(const QuickListEntry &other)
61+{
62+ return !other.actionId().isEmpty() && other.actionId() == m_actionId;
63+}
plugins/Unity/Launcher/quicklistentry.h+3−1
@@ -1,4 +1,4 @@
1−/* Copyright (C) 2013 Canonical, Ltd.
1+/* Copyright (C) 2013, 2015 Canonical, Ltd.
2 *
2 *
3 * Authors:
3 * Authors:
4 * Michael Zanetti <michael.zanetti@canonical.com>
4 * Michael Zanetti <michael.zanetti@canonical.com>
@@ -37,6 +37,8 @@ public:
37
37
38 bool clickable() const;
38 bool clickable() const;
39
39
40+ bool operator==(const QuickListEntry & other);
41+
40private:
42private:
41 QString m_actionId;
43 QString m_actionId;
42 QString m_text;
44 QString m_text;
plugins/Unity/Launcher/quicklistmodel.cpp+12−1
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright 2013 Canonical Ltd.
2+ * Copyright 2013, 2015 Canonical Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
5 * it under the terms of the GNU Lesser General Public License as published by
@@ -48,6 +48,17 @@ void QuickListModel::updateAction(const QuickListEntry &entry)
48 }
48 }
49}
49}
50
50
51+void QuickListModel::removeAction(const QuickListEntry &entry)
52+{
53+ const int start = m_list.indexOf(entry);
54+ if (start > -1) {
55+ beginRemoveRows(QModelIndex(), start, start);
56+ m_list.removeOne(entry);
57+ Q_EMIT dataChanged(index(start), index(start));
58+ endRemoveRows();
59+ }
60+}
61+
51QuickListEntry QuickListModel::get(int index) const
62QuickListEntry QuickListModel::get(int index) const
52{
63{
53 return m_list.at(index);
64 return m_list.at(index);
plugins/Unity/Launcher/quicklistmodel.h+3−1
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright 2013 Canonical Ltd.
2+ * Copyright 201, 2015 Canonical Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
5 * it under the terms of the GNU Lesser General Public License as published by
@@ -46,6 +46,8 @@ public:
46 */
46 */
47 void updateAction(const QuickListEntry &entry);
47 void updateAction(const QuickListEntry &entry);
48
48
49+ void removeAction(const QuickListEntry &entry);
50+
49 QuickListEntry get(int index) const;
51 QuickListEntry get(int index) const;
50
52
51 int rowCount(const QModelIndex &parent = QModelIndex()) const override;
53 int rowCount(const QModelIndex &parent = QModelIndex()) const override;
tests/plugins/Unity/Launcher/launchermodeltest.cpp+41
@@ -66,6 +66,7 @@ class MockAppManager: public unity::shell::application::ApplicationManagerInterf
66 Q_OBJECT
66 Q_OBJECT
67public:
67public:
68 MockAppManager(QObject *parent = 0): ApplicationManagerInterface(parent) {}
68 MockAppManager(QObject *parent = 0): ApplicationManagerInterface(parent) {}
69+ ~MockAppManager() {}
69 int rowCount(const QModelIndex &) const override { return m_list.count(); }
70 int rowCount(const QModelIndex &) const override { return m_list.count(); }
70 QVariant data(const QModelIndex &, int ) const override { return QVariant(); }
71 QVariant data(const QModelIndex &, int ) const override { return QVariant(); }
71 QString focusedApplicationId() const override {
72 QString focusedApplicationId() const override {
@@ -323,6 +324,46 @@ private Q_SLOTS:
323 QCOMPARE(launcherModel->get(0)->appId(), QLatin1String("abs-icon"));
324 QCOMPARE(launcherModel->get(0)->appId(), QLatin1String("abs-icon"));
324 }
325 }
325
326
327+ void testQuitMenuItem() {
328+ // we have 2 apps running, both should have the Quit action in its quick list
329+ QCOMPARE(launcherModel->rowCount(), 2);
330+
331+ // stop the second one keeping it pinned so that it doesn't go away
332+ launcherModel->pin("no-icon");
333+ appManager->stopApplication("no-icon");
334+
335+ // find the first Quit item, should be there
336+ QuickListModel *model = qobject_cast<QuickListModel*>(launcherModel->get(0)->quickList());
337+ int quitActionIndex = -1;
338+ for (int i = 0; i < model->rowCount(); ++i) {
339+ if (model->get(i).actionId() == "stop_item") {
340+ quitActionIndex = i;
341+ break;
342+ }
343+ }
344+ QVERIFY(quitActionIndex >= 0);
345+
346+ // find the second Quit item, should NOT be there, the app is stopped
347+ QuickListModel *model2 = qobject_cast<QuickListModel*>(launcherModel->get(1)->quickList());
348+ int quitActionIndex2 = -1;
349+ for (int i = 0; i < model2->rowCount(); ++i) {
350+ if (model2->get(i).actionId() == "stop_item") {
351+ quitActionIndex2 = i;
352+ break;
353+ }
354+ }
355+ QVERIFY(quitActionIndex2 == -1);
356+
357+ // trigger the first quit item quicklist action
358+ launcherModel->quickListActionInvoked(launcherModel->get(0)->appId(), quitActionIndex);
359+ // first app should be gone...
360+ QCOMPARE(launcherModel->rowCount(QModelIndex()), 1);
361+ // ... the second app (now at index 0) should still be there, pinned and stopped
362+ QCOMPARE(launcherModel->get(0)->appId(), QStringLiteral("no-icon"));
363+ QCOMPARE(launcherModel->get(0)->pinned(), true);
364+ QCOMPARE(launcherModel->get(0)->running(), false);
365+ }
366+
326 void testGetUrlForAppId() {
367 void testGetUrlForAppId() {
327 QCOMPARE(launcherModel->getUrlForAppId(QString()), QString());
368 QCOMPARE(launcherModel->getUrlForAppId(QString()), QString());
328 QCOMPARE(launcherModel->getUrlForAppId(""), QString());
369 QCOMPARE(launcherModel->getUrlForAppId(""), QString());