on xenial help sign in
launcher parity: close apps from quicklist Fixes: #1457201
02feab5Lukáš Tinkl
plugins/Greeter/Unity/Launcher/launchermodelas.cpp+13−14
@@ -67,6 +67,8 @@ QVariant LauncherModel::data(const QModelIndex &index, int role) const
67 return item->progress();
67 return item->progress();
68 case RoleFocused:
68 case RoleFocused:
69 return item->focused();
69 return item->focused();
70+ case RoleRunning:
71+ return item->running();
70 }
72 }
71
73
72 return QVariant();
74 return QVariant();
@@ -217,14 +219,10 @@ void LauncherModel::refresh()
217 item->setCount(cachedMap.value("count").toInt());
219 item->setCount(cachedMap.value("count").toInt());
218 item->setCountVisible(cachedMap.value("countVisible").toBool());
220 item->setCountVisible(cachedMap.value("countVisible").toBool());
219 item->setProgress(cachedMap.value("progress").toInt());
221 item->setProgress(cachedMap.value("progress").toInt());
222+ item->setRunning(cachedMap.value("running").toBool());
223+
220 int idx = m_list.indexOf(item);
224 int idx = m_list.indexOf(item);
221− Q_EMIT dataChanged(index(idx),
225+ Q_EMIT dataChanged(index(idx), index(idx), {RoleName, RoleIcon, RoleCount, RoleCountVisible, RoleRunning, RoleProgress});
222− index(idx),
223− {RoleName,
224− RoleIcon,
225− RoleCount,
226− RoleCountVisible,
227− RoleProgress});
228 }
226 }
229 break;
227 break;
230 }
228 }
@@ -262,16 +260,17 @@ void LauncherModel::refresh()
262 }
260 }
263
261
264 if (itemIndex == -1) {
262 if (itemIndex == -1) {
265− QVariantMap chachedMap = entry.toMap();
263+ QVariantMap cachedMap = entry.toMap();
266 // Need to add it. Just add it into the addedIndex to keep same ordering as the list in AS.
264 // Need to add it. Just add it into the addedIndex to keep same ordering as the list in AS.
267− LauncherItem *item = new LauncherItem(chachedMap.value("id").toString(),
265+ LauncherItem *item = new LauncherItem(cachedMap.value("id").toString(),
268− chachedMap.value("name").toString(),
266+ cachedMap.value("name").toString(),
269− chachedMap.value("icon").toString(),
267+ cachedMap.value("icon").toString(),
270 this);
268 this);
271 item->setPinned(true);
269 item->setPinned(true);
272− item->setCount(chachedMap.value("count").toInt());
270+ item->setCount(cachedMap.value("count").toInt());
273− item->setCountVisible(chachedMap.value("countVisible").toBool());
271+ item->setCountVisible(cachedMap.value("countVisible").toBool());
274− item->setProgress(chachedMap.value("progress").toInt());
272+ item->setProgress(cachedMap.value("progress").toInt());
273+ item->setRunning(cachedMap.value("running").toBool());
275 beginInsertRows(QModelIndex(), newPosition, newPosition);
274 beginInsertRows(QModelIndex(), newPosition, newPosition);
276 m_list.insert(newPosition, item);
275 m_list.insert(newPosition, item);
277 endInsertRows();
276 endInsertRows();
plugins/Unity/Launcher/asadapter.cpp+3−2
@@ -34,12 +34,12 @@ ASAdapter::~ASAdapter()
34 m_accounts->deleteLater();
34 m_accounts->deleteLater();
35}
35}
36
36
37−void ASAdapter::syncItems(QList<LauncherItem *> m_list)
37+void ASAdapter::syncItems(const QList<LauncherItem*> &list)
38{
38{
39 if (m_accounts && !m_user.isEmpty()) {
39 if (m_accounts && !m_user.isEmpty()) {
40 QList<QVariantMap> items;
40 QList<QVariantMap> items;
41
41
42− Q_FOREACH(LauncherItem *item, m_list) {
42+ Q_FOREACH(LauncherItem *item, list) {
43 items << itemToVariant(item);
43 items << itemToVariant(item);
44 }
44 }
45
45
@@ -56,6 +56,7 @@ QVariantMap ASAdapter::itemToVariant(LauncherItem *item) const
56 details.insert("count", item->count());
56 details.insert("count", item->count());
57 details.insert("countVisible", item->countVisible());
57 details.insert("countVisible", item->countVisible());
58 details.insert("pinned", item->pinned());
58 details.insert("pinned", item->pinned());
59+ details.insert("running", item->running());
59 details.insert("progress", item->progress());
60 details.insert("progress", item->progress());
60 return details;
61 return details;
61}
62}
plugins/Unity/Launcher/asadapter.h+1−4
@@ -21,7 +21,6 @@
21
21
22class LauncherItem;
22class LauncherItem;
23class AccountsServiceDBusAdaptor;
23class AccountsServiceDBusAdaptor;
24−class QDBusInterface;
25
24
26class ASAdapter
25class ASAdapter
27{
26{
@@ -29,7 +28,7 @@ public:
29 ASAdapter();
28 ASAdapter();
30 ~ASAdapter();
29 ~ASAdapter();
31
30
32− void syncItems(QList<LauncherItem*> m_list);
31+ void syncItems(const QList<LauncherItem*> &list);
33
32
34private:
33private:
35 QVariantMap itemToVariant(LauncherItem *item) const;
34 QVariantMap itemToVariant(LauncherItem *item) const;
@@ -38,8 +37,6 @@ private:
38 AccountsServiceDBusAdaptor *m_accounts;
37 AccountsServiceDBusAdaptor *m_accounts;
39 QString m_user;
38 QString m_user;
40
39
41− QDBusInterface *m_userInterface;
42−
43 friend class LauncherModelTest;
40 friend class LauncherModelTest;
44};
41};
45
42
plugins/Unity/Launcher/gsettings.cpp+1−1
@@ -68,7 +68,7 @@ void GSettings::setStoredApplications(const QStringList &storedApplications)
68void GSettings::onSettingsChanged(const QString &key)
68void GSettings::onSettingsChanged(const QString &key)
69{
69{
70 if (key == "items") {
70 if (key == "items") {
71− QStringList cachedItems = m_gSettings->get("items").toStringList();
71+ const QStringList cachedItems = m_gSettings->get("items").toStringList();
72 if (m_cachedItems != cachedItems) {
72 if (m_cachedItems != cachedItems) {
73 m_cachedItems = cachedItems;
73 m_cachedItems = cachedItems;
74 Q_EMIT changed();
74 Q_EMIT changed();
plugins/Unity/Launcher/launcheritem.cpp+10
@@ -41,10 +41,15 @@ LauncherItem::LauncherItem(const QString &appId, const QString &name, const QStr
41 nameAction.setActionId("launch_item");
41 nameAction.setActionId("launch_item");
42 nameAction.setText(m_name);
42 nameAction.setText(m_name);
43 m_quickList->appendAction(nameAction);
43 m_quickList->appendAction(nameAction);
44+
44 QuickListEntry pinningAction;
45 QuickListEntry pinningAction;
45 pinningAction.setActionId("pin_item");
46 pinningAction.setActionId("pin_item");
46 pinningAction.setText(gettext("Pin shortcut"));
47 pinningAction.setText(gettext("Pin shortcut"));
47 m_quickList->appendAction(pinningAction);
48 m_quickList->appendAction(pinningAction);
49+
50+ m_quitAction.setActionId("stop_item");
51+ m_quitAction.setIcon("application-exit");
52+ m_quitAction.setText(gettext("Quit"));
48}
53}
49
54
50QString LauncherItem::appId() const
55QString LauncherItem::appId() const
@@ -111,6 +116,11 @@ void LauncherItem::setRunning(bool running)
111{
116{
112 if (m_running != running) {
117 if (m_running != running) {
113 m_running = running;
118 m_running = running;
119+ if (m_running) { // add the quit action
120+ m_quickList->appendAction(m_quitAction);
121+ } else { // remove the quit action
122+ m_quickList->removeAction(m_quitAction);
123+ }
114 Q_EMIT runningChanged(running);
124 Q_EMIT runningChanged(running);
115 }
125 }
116}
126}
plugins/Unity/Launcher/launcheritem.h+1
@@ -73,6 +73,7 @@ private:
73 bool m_focused;
73 bool m_focused;
74 bool m_alerting;
74 bool m_alerting;
75 QuickListModel *m_quickList;
75 QuickListModel *m_quickList;
76+ QuickListEntry m_quitAction;
76
77
77 friend class LauncherModel;
78 friend class LauncherModel;
78};
79};
plugins/Unity/Launcher/launchermodel.cpp+45−24
@@ -36,7 +36,7 @@ LauncherModel::LauncherModel(QObject *parent):
36 m_settings(new GSettings(this)),
36 m_settings(new GSettings(this)),
37 m_dbusIface(new DBusInterface(this)),
37 m_dbusIface(new DBusInterface(this)),
38 m_asAdapter(new ASAdapter()),
38 m_asAdapter(new ASAdapter()),
39− m_appManager(0)
39+ m_appManager(nullptr)
40{
40{
41 connect(m_dbusIface, &DBusInterface::countChanged, this, &LauncherModel::countChanged);
41 connect(m_dbusIface, &DBusInterface::countChanged, this, &LauncherModel::countChanged);
42 connect(m_dbusIface, &DBusInterface::countVisibleChanged, this, &LauncherModel::countVisibleChanged);
42 connect(m_dbusIface, &DBusInterface::countVisibleChanged, this, &LauncherModel::countVisibleChanged);
@@ -86,6 +86,11 @@ QVariant LauncherModel::data(const QModelIndex &index, int role) const
86 return item->focused();
86 return item->focused();
87 case RoleAlerting:
87 case RoleAlerting:
88 return item->alerting();
88 return item->alerting();
89+ case RoleRunning:
90+ return item->running();
91+ default:
92+ qWarning() << Q_FUNC_INFO << "missing role, implement me";
93+ return QVariant();
89 }
94 }
90
95
91 return QVariant();
96 return QVariant();
@@ -150,7 +155,7 @@ void LauncherModel::pin(const QString &appId, int index)
150 if (index == -1 || index == currentIndex) {
155 if (index == -1 || index == currentIndex) {
151 m_list.at(currentIndex)->setPinned(true);
156 m_list.at(currentIndex)->setPinned(true);
152 QModelIndex modelIndex = this->index(currentIndex);
157 QModelIndex modelIndex = this->index(currentIndex);
153− Q_EMIT dataChanged(modelIndex, modelIndex, QVector<int>() << RolePinned);
158+ Q_EMIT dataChanged(modelIndex, modelIndex, {RolePinned});
154 } else {
159 } else {
155 move(currentIndex, index);
160 move(currentIndex, index);
156 // move() will store the list to the backend itself, so just exit at this point.
161 // move() will store the list to the backend itself, so just exit at this point.
@@ -163,7 +168,7 @@ void LauncherModel::pin(const QString &appId, int index)
163
168
164 DesktopFileHandler desktopFile(appId);
169 DesktopFileHandler desktopFile(appId);
165 if (!desktopFile.isValid()) {
170 if (!desktopFile.isValid()) {
166− qWarning() << "Can't pin this application, there is no .destkop file available.";
171+ qWarning() << "Can't pin this application, there is no .desktop file available.";
167 return;
172 return;
168 }
173 }
169
174
@@ -188,7 +193,7 @@ void LauncherModel::requestRemove(const QString &appId)
188
193
189void LauncherModel::quickListActionInvoked(const QString &appId, int actionIndex)
194void LauncherModel::quickListActionInvoked(const QString &appId, int actionIndex)
190{
195{
191− int index = findApplication(appId);
196+ const int index = findApplication(appId);
192 if (index < 0) {
197 if (index < 0) {
193 return;
198 return;
194 }
199 }
@@ -196,7 +201,7 @@ void LauncherModel::quickListActionInvoked(const QString &appId, int actionIndex
196 LauncherItem *item = m_list.at(index);
201 LauncherItem *item = m_list.at(index);
197 QuickListModel *model = qobject_cast<QuickListModel*>(item->quickList());
202 QuickListModel *model = qobject_cast<QuickListModel*>(item->quickList());
198 if (model) {
203 if (model) {
199− QString actionId = model->get(actionIndex).actionId();
204+ const QString actionId = model->get(actionIndex).actionId();
200
205
201 // Check if this is one of the launcher actions we handle ourselves
206 // Check if this is one of the launcher actions we handle ourselves
202 if (actionId == "pin_item") {
207 if (actionId == "pin_item") {
@@ -207,7 +212,10 @@ void LauncherModel::quickListActionInvoked(const QString &appId, int actionIndex
207 }
212 }
208 } else if (actionId == "launch_item") {
213 } else if (actionId == "launch_item") {
209 QDesktopServices::openUrl(getUrlForAppId(appId));
214 QDesktopServices::openUrl(getUrlForAppId(appId));
210−
215+ } else if (actionId == "stop_item") { // Quit
216+ if (m_appManager) {
217+ m_appManager->stopApplication(appId);
218+ }
211 // Nope, we don't know this action, let the backend forward it to the application
219 // Nope, we don't know this action, let the backend forward it to the application
212 } else {
220 } else {
213 // TODO: forward quicklist action to app, possibly via m_dbusIface
221 // TODO: forward quicklist action to app, possibly via m_dbusIface
@@ -305,7 +313,7 @@ void LauncherModel::storeAppList()
305
313
306void LauncherModel::unpin(const QString &appId)
314void LauncherModel::unpin(const QString &appId)
307{
315{
308− int index = findApplication(appId);
316+ const int index = findApplication(appId);
309 if (index < 0) {
317 if (index < 0) {
310 return;
318 return;
311 }
319 }
@@ -314,7 +322,7 @@ void LauncherModel::unpin(const QString &appId)
314 if (m_list.at(index)->pinned()) {
322 if (m_list.at(index)->pinned()) {
315 m_list.at(index)->setPinned(false);
323 m_list.at(index)->setPinned(false);
316 QModelIndex modelIndex = this->index(index);
324 QModelIndex modelIndex = this->index(index);
317− Q_EMIT dataChanged(modelIndex, modelIndex, QVector<int>() << RolePinned);
325+ Q_EMIT dataChanged(modelIndex, modelIndex, {RolePinned});
318 }
326 }
319 } else {
327 } else {
320 beginRemoveRows(QModelIndex(), index, index);
328 beginRemoveRows(QModelIndex(), index, index);
@@ -336,25 +344,25 @@ int LauncherModel::findApplication(const QString &appId)
336
344
337void LauncherModel::progressChanged(const QString &appId, int progress)
345void LauncherModel::progressChanged(const QString &appId, int progress)
338{
346{
339− int idx = findApplication(appId);
347+ const int idx = findApplication(appId);
340 if (idx >= 0) {
348 if (idx >= 0) {
341 LauncherItem *item = m_list.at(idx);
349 LauncherItem *item = m_list.at(idx);
342 item->setProgress(progress);
350 item->setProgress(progress);
343− Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleProgress);
351+ Q_EMIT dataChanged(index(idx), index(idx), {RoleProgress});
344 }
352 }
345}
353}
346
354
347void LauncherModel::countChanged(const QString &appId, int count)
355void LauncherModel::countChanged(const QString &appId, int count)
348{
356{
349− int idx = findApplication(appId);
357+ const int idx = findApplication(appId);
350 if (idx >= 0) {
358 if (idx >= 0) {
351 LauncherItem *item = m_list.at(idx);
359 LauncherItem *item = m_list.at(idx);
352 item->setCount(count);
360 item->setCount(count);
353 if (item->countVisible()) {
361 if (item->countVisible()) {
354 setAlerting(item->appId(), true);
362 setAlerting(item->appId(), true);
355 }
363 }
356− Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleCount);
357 m_asAdapter->syncItems(m_list);
364 m_asAdapter->syncItems(m_list);
365+ Q_EMIT dataChanged(index(idx), index(idx), {RoleCount});
358 }
366 }
359}
367}
360
368
@@ -367,7 +375,7 @@ void LauncherModel::countVisibleChanged(const QString &appId, bool countVisible)
367 if (countVisible) {
375 if (countVisible) {
368 setAlerting(item->appId(), true);
376 setAlerting(item->appId(), true);
369 }
377 }
370− Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleCountVisible);
378+ Q_EMIT dataChanged(index(idx), index(idx), {RoleCountVisible});
371
379
372 // If countVisible goes to false, and the item is neither pinned nor recent we can drop it
380 // If countVisible goes to false, and the item is neither pinned nor recent we can drop it
373 if (!countVisible && !item->pinned() && !item->recent()) {
381 if (!countVisible && !item->pinned() && !item->recent()) {
@@ -408,7 +416,8 @@ void LauncherModel::refresh()
408 item->setName(desktopFile.displayName());
416 item->setName(desktopFile.displayName());
409 item->setIcon(desktopFile.icon());
417 item->setIcon(desktopFile.icon());
410 item->setPinned(item->pinned()); // update pinned text if needed
418 item->setPinned(item->pinned()); // update pinned text if needed
419+ item->setRunning(item->running());
411− Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleName << RoleIcon);
420+ Q_EMIT dataChanged(index(idx), index(idx), {RoleName, RoleIcon, RoleRunning});
412 }
421 }
413 }
422 }
414
423
@@ -426,7 +435,7 @@ void LauncherModel::refresh()
426
435
427 // Now walk through settings and see if we need to add something
436 // Now walk through settings and see if we need to add something
428 for (int settingsIndex = 0; settingsIndex < m_settings->storedApplications().count(); ++settingsIndex) {
437 for (int settingsIndex = 0; settingsIndex < m_settings->storedApplications().count(); ++settingsIndex) {
429− QString entry = m_settings->storedApplications().at(settingsIndex);
438+ const QString entry = m_settings->storedApplications().at(settingsIndex);
430 int itemIndex = -1;
439 int itemIndex = -1;
431 for (int i = 0; i < m_list.count(); ++i) {
440 for (int i = 0; i < m_list.count(); ++i) {
432 if (m_list.at(i)->appId() == entry) {
441 if (m_list.at(i)->appId() == entry) {
@@ -499,25 +508,27 @@ void LauncherModel::applicationAdded(const QModelIndex &parent, int row)
499 return;
508 return;
500 }
509 }
501
510
502− int itemIndex = findApplication(app->appId());
511+ const int itemIndex = findApplication(app->appId());
503 if (itemIndex != -1) {
512 if (itemIndex != -1) {
504 LauncherItem *item = m_list.at(itemIndex);
513 LauncherItem *item = m_list.at(itemIndex);
505 if (!item->recent()) {
514 if (!item->recent()) {
506 item->setRecent(true);
515 item->setRecent(true);
507− m_asAdapter->syncItems(m_list);
508− Q_EMIT dataChanged(index(itemIndex), index(itemIndex), QVector<int>() << RoleRecent);
516+ Q_EMIT dataChanged(index(itemIndex), index(itemIndex), {RoleRecent});
509 }
517 }
518+ item->setRunning(true);
510− // Shall we paint some running/recent app highlight? If yes, do it here.
519+ // TODO Shall we paint some running/recent app highlight? If yes, do it here.
511 } else {
520 } else {
512 LauncherItem *item = new LauncherItem(app->appId(), app->name(), app->icon().toString(), this);
521 LauncherItem *item = new LauncherItem(app->appId(), app->name(), app->icon().toString(), this);
513 item->setRecent(true);
522 item->setRecent(true);
523+ item->setRunning(true);
514 item->setFocused(app->focused());
524 item->setFocused(app->focused());
515
525
516 beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
526 beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
517 m_list.append(item);
527 m_list.append(item);
518 endInsertRows();
528 endInsertRows();
519− m_asAdapter->syncItems(m_list);
520 }
529 }
530+ m_asAdapter->syncItems(m_list);
531+ Q_EMIT dataChanged(index(itemIndex), index(itemIndex), {RoleRunning});
521}
532}
522
533
523void LauncherModel::applicationRemoved(const QModelIndex &parent, int row)
534void LauncherModel::applicationRemoved(const QModelIndex &parent, int row)
@@ -532,25 +543,35 @@ void LauncherModel::applicationRemoved(const QModelIndex &parent, int row)
532 }
543 }
533 }
544 }
534
545
546+ if (appIndex < 0) {
547+ qWarning() << Q_FUNC_INFO << "appIndex not found";
548+ return;
549+ }
550+
535− if (appIndex > -1 && !m_list.at(appIndex)->pinned()) {
551+ LauncherItem * item = m_list.at(appIndex);
552+ item->setRunning(false);
553+
554+ if (!item->pinned()) {
536 beginRemoveRows(QModelIndex(), appIndex, appIndex);
555 beginRemoveRows(QModelIndex(), appIndex, appIndex);
537 m_list.takeAt(appIndex)->deleteLater();
556 m_list.takeAt(appIndex)->deleteLater();
538 endRemoveRows();
557 endRemoveRows();
539 m_asAdapter->syncItems(m_list);
558 m_asAdapter->syncItems(m_list);
559+ Q_EMIT dataChanged(index(appIndex), index(appIndex), {RolePinned});
540 }
560 }
561+ Q_EMIT dataChanged(index(appIndex), index(appIndex), {RoleRunning});
541}
562}
542
563
543void LauncherModel::focusedAppIdChanged()
564void LauncherModel::focusedAppIdChanged()
544{
565{
545− QString appId = m_appManager->focusedApplicationId();
566+ const QString appId = m_appManager->focusedApplicationId();
546 for (int i = 0; i < m_list.count(); ++i) {
567 for (int i = 0; i < m_list.count(); ++i) {
547 LauncherItem *item = m_list.at(i);
568 LauncherItem *item = m_list.at(i);
548 if (!item->focused() && item->appId() == appId) {
569 if (!item->focused() && item->appId() == appId) {
549 item->setFocused(true);
570 item->setFocused(true);
550− Q_EMIT dataChanged(index(i), index(i), QVector<int>() << RoleFocused);
571+ Q_EMIT dataChanged(index(i), index(i), {RoleFocused});
551 } else if (item->focused() && item->appId() != appId) {
572 } else if (item->focused() && item->appId() != appId) {
552 item->setFocused(false);
573 item->setFocused(false);
553− Q_EMIT dataChanged(index(i), index(i), QVector<int>() << RoleFocused);
574+ Q_EMIT dataChanged(index(i), index(i), {RoleFocused});
554 }
575 }
555 }
576 }
556}
577}
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;
qml/Launcher/LauncherPanel.qml+22−13
@@ -15,7 +15,7 @@
15 */
15 */
16
16
17import QtQuick 2.3
17import QtQuick 2.3
18−import Ubuntu.Components 1.1
18+import Ubuntu.Components 1.2
19import Ubuntu.Components.ListItems 1.0 as ListItems
19import Ubuntu.Components.ListItems 1.0 as ListItems
20import Unity.Launcher 0.1
20import Unity.Launcher 0.1
21import Ubuntu.Components.Popups 0.1
21import Ubuntu.Components.Popups 0.1
@@ -358,6 +358,7 @@ Rectangle {
358 MouseArea {
358 MouseArea {
359 id: dndArea
359 id: dndArea
360 objectName: "dndArea"
360 objectName: "dndArea"
361+ acceptedButtons: Qt.LeftButton | Qt.RightButton
361 anchors {
362 anchors {
362 fill: parent
363 fill: parent
363 topMargin: launcherListView.topMargin
364 topMargin: launcherListView.topMargin
@@ -369,7 +370,7 @@ Rectangle {
369 property int draggedIndex: -1
370 property int draggedIndex: -1
370 property var selectedItem
371 property var selectedItem
371 property bool preDragging: false
372 property bool preDragging: false
372− property bool dragging: selectedItem !== undefined && selectedItem !== null && selectedItem.dragging
373+ property bool dragging: !!selectedItem && selectedItem.dragging
373 property bool postDragging: false
374 property bool postDragging: false
374 property int startX
375 property int startX
375 property int startY
376 property int startY
@@ -387,6 +388,15 @@ Rectangle {
387 return;
388 return;
388 }
389 }
389
390
391+ if (mouse.button & Qt.RightButton) { // context menu
392+ // Opening QuickList
393+ quickList.item = clickedItem;
394+ quickList.model = launcherListView.model.get(index).quickList;
395+ quickList.appId = launcherListView.model.get(index).appId;
396+ quickList.state = "open";
397+ return
398+ }
399+
390 // First/last item do the scrolling at more than 12 degrees
400 // First/last item do the scrolling at more than 12 degrees
391 if (index == 0 || index == launcherListView.count - 1) {
401 if (index == 0 || index == launcherListView.count - 1) {
392 if (clickedItem.angle > 12) {
402 if (clickedItem.angle > 12) {
@@ -580,7 +590,7 @@ Rectangle {
580 id: quickListShape
590 id: quickListShape
581 objectName: "quickListShape"
591 objectName: "quickListShape"
582 anchors.fill: quickList
592 anchors.fill: quickList
583− opacity: quickList.state === "open" ? 0.96 : 0
593+ opacity: quickList.state === "open" ? 0.8 : 0
584 visible: opacity > 0
594 visible: opacity > 0
585 rotation: root.rotation
595 rotation: root.rotation
586
596
@@ -592,15 +602,15 @@ Rectangle {
592
602
593 Image {
603 Image {
594 anchors {
604 anchors {
595− left: parent.left
605+ right: parent.left
596− leftMargin: quickList.item !== undefined ? (quickList.item.width - units.gu(1)) / 2 - width / 2 : 0
606+ rightMargin: -units.dp(4)
597 verticalCenter: parent.verticalCenter
607 verticalCenter: parent.verticalCenter
598− verticalCenterOffset: (parent.height / 2 + units.dp(3)) * (quickList.offset > 0 ? 1 : -1) * (root.inverted ? 1 : -1)
608+ verticalCenterOffset: -quickList.offset * (root.inverted ? -1 : 1)
599 }
609 }
600 height: units.gu(1)
610 height: units.gu(1)
601 width: units.gu(2)
611 width: units.gu(2)
602 source: "graphics/quicklist_tooltip.png"
612 source: "graphics/quicklist_tooltip.png"
603− rotation: (quickList.offset > 0 ? 0 : 180) + (root.inverted ? 0 : 180)
613+ rotation: 90
604 }
614 }
605
615
606 InverseMouseArea {
616 InverseMouseArea {
@@ -623,11 +633,11 @@ Rectangle {
623 height: quickListColumn.height
633 height: quickListColumn.height
624 visible: quickListShape.visible
634 visible: quickListShape.visible
625 anchors {
635 anchors {
626− left: root.inverted ? undefined : parent.left
636+ left: root.inverted ? undefined : parent.right
627− right: root.inverted ? parent.right : undefined
637+ right: root.inverted ? parent.left : undefined
628 margins: units.gu(1)
638 margins: units.gu(1)
629 }
639 }
630− y: itemCenter + offset
640+ y: itemCenter - (height / 2) + offset
631 rotation: root.rotation
641 rotation: root.rotation
632
642
633 property var model
643 property var model
@@ -636,9 +646,8 @@ Rectangle {
636
646
637 // internal
647 // internal
638 property int itemCenter: item ? root.mapFromItem(quickList.item).y + (item.height / 2) : units.gu(1)
648 property int itemCenter: item ? root.mapFromItem(quickList.item).y + (item.height / 2) : units.gu(1)
639− property int offset: item !== undefined ? (itemCenter + (item.height/2) + height + units.gu(1) > parent.height ?
649+ property int offset: itemCenter + (height/2) + units.gu(1) > parent.height ? -itemCenter - (height/2) - units.gu(1) + parent.height :
640− -(item.height/2) - height - units.gu(.5) :
641− (item.height/2) + units.gu(.5)) : 0
650+ itemCenter - (height/2) < units.gu(1) ? (height/2) - itemCenter + units.gu(1) : 0
642
651
643 Column {
652 Column {
644 id: quickListColumn
653 id: quickListColumn
tests/mocks/Unity/Launcher/MockLauncherItem.cpp+3
@@ -40,7 +40,10 @@ MockLauncherItem::MockLauncherItem(const QString &appId, const QString& desktopF
40 m_alerting(false),
40 m_alerting(false),
41 m_quickList(new MockQuickListModel(this))
41 m_quickList(new MockQuickListModel(this))
42{
42{
43+}
43
44
45+MockLauncherItem::~MockLauncherItem()
46+{
44}
47}
45
48
46QString MockLauncherItem::appId() const
49QString MockLauncherItem::appId() const
tests/mocks/Unity/Launcher/MockLauncherItem.h+1
@@ -31,6 +31,7 @@ class MockLauncherItem: public LauncherItemInterface
31 Q_OBJECT
31 Q_OBJECT
32public:
32public:
33 MockLauncherItem(const QString &appId, const QString& desktopFile, const QString& name, const QString& icon, QObject* parent = 0);
33 MockLauncherItem(const QString &appId, const QString& desktopFile, const QString& name, const QString& icon, QObject* parent = 0);
34+ ~MockLauncherItem();
34
35
35 QString appId() const override;
36 QString appId() const override;
36 QString desktopFile() const;
37 QString desktopFile() const;
tests/mocks/Unity/Launcher/MockLauncherModel.cpp+4−1
@@ -44,6 +44,7 @@ MockLauncherModel::MockLauncherModel(QObject* parent): LauncherModelInterface(pa
44 item = new MockLauncherItem("webbrowser-app", "/usr/share/applications/webbrowser-app.desktop", "Browser", "browser", this);
44 item = new MockLauncherItem("webbrowser-app", "/usr/share/applications/webbrowser-app.desktop", "Browser", "browser", this);
45 item->setCount(1);
45 item->setCount(1);
46 item->setCountVisible(true);
46 item->setCountVisible(true);
47+ item->setRunning(true);
47 item->setAlerting(false);
48 item->setAlerting(false);
48 m_list.append(item);
49 m_list.append(item);
49 item = new MockLauncherItem("twitter-webapp", "/usr/share/applications/twitter-webapp.desktop", "Twitter", "twitter", this);
50 item = new MockLauncherItem("twitter-webapp", "/usr/share/applications/twitter-webapp.desktop", "Twitter", "twitter", this);
@@ -194,7 +195,9 @@ void MockLauncherModel::requestRemove(const QString &appId)
194 int index = findApp(appId);
195 int index = findApp(appId);
195 if (index >= 0) {
196 if (index >= 0) {
196 beginRemoveRows(QModelIndex(), index, 0);
197 beginRemoveRows(QModelIndex(), index, 0);
197− m_list.takeAt(index)->deleteLater();
198+ MockLauncherItem * item = m_list.takeAt(index);
199+ item->setRunning(false);
200+ item->deleteLater();
198 endRemoveRows();
201 endRemoveRows();
199 }
202 }
200}
203}
tests/plugins/Unity/Launcher/launchermodeltest.cpp+41
@@ -68,6 +68,7 @@ class MockAppManager: public unity::shell::application::ApplicationManagerInterf
68 Q_OBJECT
68 Q_OBJECT
69public:
69public:
70 MockAppManager(QObject *parent = 0): ApplicationManagerInterface(parent) {}
70 MockAppManager(QObject *parent = 0): ApplicationManagerInterface(parent) {}
71+ ~MockAppManager() {}
71 int rowCount(const QModelIndex &) const override { return m_list.count(); }
72 int rowCount(const QModelIndex &) const override { return m_list.count(); }
72 QVariant data(const QModelIndex &, int ) const override { return QVariant(); }
73 QVariant data(const QModelIndex &, int ) const override { return QVariant(); }
73 QString focusedApplicationId() const override {
74 QString focusedApplicationId() const override {
@@ -325,6 +326,46 @@ private Q_SLOTS:
325 QCOMPARE(launcherModel->get(0)->appId(), QLatin1String("abs-icon"));
326 QCOMPARE(launcherModel->get(0)->appId(), QLatin1String("abs-icon"));
326 }
327 }
327
328
329+ void testQuitMenuItem() {
330+ // we have 2 apps running, both should have the Quit action in its quick list
331+ QCOMPARE(launcherModel->rowCount(), 2);
332+
333+ // stop the second one keeping it pinned so that it doesn't go away
334+ launcherModel->pin("no-icon");
335+ appManager->stopApplication("no-icon");
336+
337+ // find the first Quit item, should be there
338+ QuickListModel *model = qobject_cast<QuickListModel*>(launcherModel->get(0)->quickList());
339+ int quitActionIndex = -1;
340+ for (int i = 0; i < model->rowCount(); ++i) {
341+ if (model->get(i).actionId() == "stop_item") {
342+ quitActionIndex = i;
343+ break;
344+ }
345+ }
346+ QVERIFY(quitActionIndex >= 0);
347+
348+ // find the second Quit item, should NOT be there, the app is stopped
349+ QuickListModel *model2 = qobject_cast<QuickListModel*>(launcherModel->get(1)->quickList());
350+ int quitActionIndex2 = -1;
351+ for (int i = 0; i < model2->rowCount(); ++i) {
352+ if (model2->get(i).actionId() == "stop_item") {
353+ quitActionIndex2 = i;
354+ break;
355+ }
356+ }
357+ QVERIFY(quitActionIndex2 == -1);
358+
359+ // trigger the first quit item quicklist action
360+ launcherModel->quickListActionInvoked(launcherModel->get(0)->appId(), quitActionIndex);
361+ // first app should be gone...
362+ QCOMPARE(launcherModel->rowCount(QModelIndex()), 1);
363+ // ... the second app (now at index 0) should still be there, pinned and stopped
364+ QCOMPARE(launcherModel->get(0)->appId(), QStringLiteral("no-icon"));
365+ QCOMPARE(launcherModel->get(0)->pinned(), true);
366+ QCOMPARE(launcherModel->get(0)->running(), false);
367+ }
368+
328 void testGetUrlForAppId() {
369 void testGetUrlForAppId() {
329 QCOMPARE(launcherModel->getUrlForAppId(QString()), QString());
370 QCOMPARE(launcherModel->getUrlForAppId(QString()), QString());
330 QCOMPARE(launcherModel->getUrlForAppId(""), QString());
371 QCOMPARE(launcherModel->getUrlForAppId(""), QString());
tests/qmltests/Launcher/tst_Launcher.qml+29−7
@@ -197,7 +197,7 @@ Item {
197 startX+units.gu(8), startY);
197 startX+units.gu(8), startY);
198
198
199 var panel = findChild(launcher, "launcherPanel");
199 var panel = findChild(launcher, "launcherPanel");
200− verify(panel != undefined);
200+ verify(!!panel);
201
201
202 // wait until it gets fully extended
202 // wait until it gets fully extended
203 tryCompare(panel, "x", 0);
203 tryCompare(panel, "x", 0);
@@ -208,7 +208,7 @@ Item {
208 mouseMove(root, 1, root.height / 2);
208 mouseMove(root, 1, root.height / 2);
209
209
210 var panel = findChild(launcher, "launcherPanel");
210 var panel = findChild(launcher, "launcherPanel");
211− verify(panel != undefined);
211+ verify(!!panel);
212
212
213 // wait until it gets fully extended
213 // wait until it gets fully extended
214 tryCompare(panel, "x", 0);
214 tryCompare(panel, "x", 0);
@@ -248,7 +248,7 @@ Item {
248 waitUntilLauncherDisappears();
248 waitUntilLauncherDisappears();
249
249
250 var panel = findChild(launcher, "launcherPanel")
250 var panel = findChild(launcher, "launcherPanel")
251− verify(panel != undefined)
251+ verify(!!panel)
252
252
253 // it starts out hidden just left of the left launcher edge
253 // it starts out hidden just left of the left launcher edge
254 compare(panel.x, -panel.width)
254 compare(panel.x, -panel.width)
@@ -286,7 +286,7 @@ Item {
286
286
287 var appIcon = findChild(launcher, "launcherDelegate0");
287 var appIcon = findChild(launcher, "launcherDelegate0");
288
288
289− verify(appIcon != undefined);
289+ verify(!!appIcon);
290
290
291 if (data.mouse) {
291 if (data.mouse) {
292 mouseClick(appIcon);
292 mouseClick(appIcon);
@@ -309,7 +309,7 @@ Item {
309 dragLauncherIntoView()
309 dragLauncherIntoView()
310
310
311 var dashIcon = findChild(launcher, "dashItem")
311 var dashIcon = findChild(launcher, "dashItem")
312− verify(dashIcon != undefined)
312+ verify(!!dashIcon)
313
313
314 mouseClick(dashIcon)
314 mouseClick(dashIcon)
315
315
@@ -599,7 +599,7 @@ Item {
599
599
600 // Doing longpress
600 // Doing longpress
601 mousePress(draggedItem);
601 mousePress(draggedItem);
602− tryCompare(quickListShape, "opacity", 0.96);
602+ tryCompare(quickListShape, "opacity", 0.8);
603 mouseRelease(draggedItem);
603 mouseRelease(draggedItem);
604
604
605 verify(quickList.y >= units.gu(1));
605 verify(quickList.y >= units.gu(1));
@@ -684,9 +684,31 @@ Item {
684 tryCompare(quickList, "state", "");
684 tryCompare(quickList, "state", "");
685 }
685 }
686
686
687+ function test_quickListMenuOnRMB() {
688+ dragLauncherIntoView();
689+ var clickedItem = findChild(launcher, "launcherDelegate5")
690+ var quickList = findChild(launcher, "quickList")
691+ var quickListShape = findChild(launcher, "quickListShape")
692+ var dndArea = findChild(launcher, "dndArea");
693+
694+ // Initial state
695+ tryCompare(quickListShape, "visible", false)
696+
697+ // Doing RMB click
698+ mouseClick(clickedItem, clickedItem.width / 2, clickedItem.height / 2, Qt.RightButton)
699+ tryCompare(quickListShape, "visible", true)
700+ verify(quickList, "state", "open")
701+ verify(dndArea, "dragging", false)
702+
703+ // Click somewhere in the empty space to dismiss the quicklist
704+ mouseClick(launcher, launcher.width - units.gu(1), units.gu(1));
705+ tryCompare(quickListShape, "visible", false);
706+ verify(quickList, "state", "")
707+ }
708+
687 function test_revealByHover() {
709 function test_revealByHover() {
688 var panel = findChild(launcher, "launcherPanel");
710 var panel = findChild(launcher, "launcherPanel");
689− verify(panel != undefined);
711+ verify(!!panel);
690
712
691 revealByHover();
713 revealByHover();
692 tryCompare(launcher, "state", "visibleTemporary");
714 tryCompare(launcher, "state", "visibleTemporary");