on xenial help sign in
launcher parity: close apps from quicklist Fixes: #1457201

Approved by: Michael Zanetti

commit02feab5e8a5fe631b260ad04b6a217747426e386 authorLukáš Tinkl <lukas.tinkl@canonical.com> dateWed 29 Jul 2015, 12:37 (11 years ago) parent9f578c0708d834
17 files changed+198−71
plugins/Greeter/Unity/Launcher/launchermodelas.cpp+13−14 plugins/Unity/Launcher/asadapter.cpp+3−2 plugins/Unity/Launcher/asadapter.h+1−4 plugins/Unity/Launcher/gsettings.cpp+1−1 plugins/Unity/Launcher/launcheritem.cpp+10 plugins/Unity/Launcher/launcheritem.h+1 plugins/Unity/Launcher/launchermodel.cpp+45−24 plugins/Unity/Launcher/quicklistentry.cpp+6−2 plugins/Unity/Launcher/quicklistentry.h+3−1 plugins/Unity/Launcher/quicklistmodel.cpp+12−1 plugins/Unity/Launcher/quicklistmodel.h+3−1 qml/Launcher/LauncherPanel.qml+22−13 tests/mocks/Unity/Launcher/MockLauncherItem.cpp+3 tests/mocks/Unity/Launcher/MockLauncherItem.h+1 tests/mocks/Unity/Launcher/MockLauncherModel.cpp+4−1 tests/plugins/Unity/Launcher/launchermodeltest.cpp+41 tests/qmltests/Launcher/tst_Launcher.qml+29−7
plugins/Greeter/Unity/Launcher/launchermodelas.cpp+13−14
@@ -67,6 +67,8 @@ QVariant LauncherModel::data(const QModelIndex &index, int role) const
6767 return item->progress();
6868 case RoleFocused:
6969 return item->focused();
70+ case RoleRunning:
71+ return item->running();
7072 }
7173
7274 return QVariant();
@@ -217,14 +219,10 @@ void LauncherModel::refresh()
217219 item->setCount(cachedMap.value("count").toInt());
218220 item->setCountVisible(cachedMap.value("countVisible").toBool());
219221 item->setProgress(cachedMap.value("progress").toInt());
222+ item->setRunning(cachedMap.value("running").toBool());
223+
220224 int idx = m_list.indexOf(item);
221− Q_EMIT dataChanged(index(idx),
222− index(idx),
223− {RoleName,
224− RoleIcon,
225− RoleCount,
226− RoleCountVisible,
227− RoleProgress});
225+ Q_EMIT dataChanged(index(idx), index(idx), {RoleName, RoleIcon, RoleCount, RoleCountVisible, RoleRunning, RoleProgress});
228226 }
229227 break;
230228 }
@@ -262,16 +260,17 @@ void LauncherModel::refresh()
262260 }
263261
264262 if (itemIndex == -1) {
265− QVariantMap chachedMap = entry.toMap();
263+ QVariantMap cachedMap = entry.toMap();
266264 // 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(),
268− chachedMap.value("name").toString(),
269− chachedMap.value("icon").toString(),
265+ LauncherItem *item = new LauncherItem(cachedMap.value("id").toString(),
266+ cachedMap.value("name").toString(),
267+ cachedMap.value("icon").toString(),
270268 this);
271269 item->setPinned(true);
272− item->setCount(chachedMap.value("count").toInt());
273− item->setCountVisible(chachedMap.value("countVisible").toBool());
274− item->setProgress(chachedMap.value("progress").toInt());
270+ item->setCount(cachedMap.value("count").toInt());
271+ item->setCountVisible(cachedMap.value("countVisible").toBool());
272+ item->setProgress(cachedMap.value("progress").toInt());
273+ item->setRunning(cachedMap.value("running").toBool());
275274 beginInsertRows(QModelIndex(), newPosition, newPosition);
276275 m_list.insert(newPosition, item);
277276 endInsertRows();
plugins/Unity/Launcher/asadapter.cpp+3−2
@@ -34,12 +34,12 @@ ASAdapter::~ASAdapter()
3434 m_accounts->deleteLater();
3535}
3636
37−void ASAdapter::syncItems(QList<LauncherItem *> m_list)
37+void ASAdapter::syncItems(const QList<LauncherItem*> &list)
3838{
3939 if (m_accounts && !m_user.isEmpty()) {
4040 QList<QVariantMap> items;
4141
42− Q_FOREACH(LauncherItem *item, m_list) {
42+ Q_FOREACH(LauncherItem *item, list) {
4343 items << itemToVariant(item);
4444 }
4545
@@ -56,6 +56,7 @@ QVariantMap ASAdapter::itemToVariant(LauncherItem *item) const
5656 details.insert("count", item->count());
5757 details.insert("countVisible", item->countVisible());
5858 details.insert("pinned", item->pinned());
59+ details.insert("running", item->running());
5960 details.insert("progress", item->progress());
6061 return details;
6162}
plugins/Unity/Launcher/asadapter.h+1−4
@@ -21,7 +21,6 @@
2121
2222class LauncherItem;
2323class AccountsServiceDBusAdaptor;
24−class QDBusInterface;
2524
2625class ASAdapter
2726{
@@ -29,7 +28,7 @@ public:
2928 ASAdapter();
3029 ~ASAdapter();
3130
32− void syncItems(QList<LauncherItem*> m_list);
31+ void syncItems(const QList<LauncherItem*> &list);
3332
3433private:
3534 QVariantMap itemToVariant(LauncherItem *item) const;
@@ -38,8 +37,6 @@ private:
3837 AccountsServiceDBusAdaptor *m_accounts;
3938 QString m_user;
4039
41− QDBusInterface *m_userInterface;
42−
4340 friend class LauncherModelTest;
4441};
4542
plugins/Unity/Launcher/gsettings.cpp+1−1
@@ -68,7 +68,7 @@ void GSettings::setStoredApplications(const QStringList &storedApplications)
6868void GSettings::onSettingsChanged(const QString &key)
6969{
7070 if (key == "items") {
71− QStringList cachedItems = m_gSettings->get("items").toStringList();
71+ const QStringList cachedItems = m_gSettings->get("items").toStringList();
7272 if (m_cachedItems != cachedItems) {
7373 m_cachedItems = cachedItems;
7474 Q_EMIT changed();
plugins/Unity/Launcher/launcheritem.cpp+10
@@ -41,10 +41,15 @@ LauncherItem::LauncherItem(const QString &appId, const QString &name, const QStr
4141 nameAction.setActionId("launch_item");
4242 nameAction.setText(m_name);
4343 m_quickList->appendAction(nameAction);
44+
4445 QuickListEntry pinningAction;
4546 pinningAction.setActionId("pin_item");
4647 pinningAction.setText(gettext("Pin shortcut"));
4748 m_quickList->appendAction(pinningAction);
49+
50+ m_quitAction.setActionId("stop_item");
51+ m_quitAction.setIcon("application-exit");
52+ m_quitAction.setText(gettext("Quit"));
4853}
4954
5055QString LauncherItem::appId() const
@@ -111,6 +116,11 @@ void LauncherItem::setRunning(bool running)
111116{
112117 if (m_running != running) {
113118 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+ }
114124 Q_EMIT runningChanged(running);
115125 }
116126}
plugins/Unity/Launcher/launcheritem.h+1
@@ -73,6 +73,7 @@ private:
7373 bool m_focused;
7474 bool m_alerting;
7575 QuickListModel *m_quickList;
76+ QuickListEntry m_quitAction;
7677
7778 friend class LauncherModel;
7879};
plugins/Unity/Launcher/launchermodel.cpp+45−24
@@ -36,7 +36,7 @@ LauncherModel::LauncherModel(QObject *parent):
3636 m_settings(new GSettings(this)),
3737 m_dbusIface(new DBusInterface(this)),
3838 m_asAdapter(new ASAdapter()),
39− m_appManager(0)
39+ m_appManager(nullptr)
4040{
4141 connect(m_dbusIface, &DBusInterface::countChanged, this, &LauncherModel::countChanged);
4242 connect(m_dbusIface, &DBusInterface::countVisibleChanged, this, &LauncherModel::countVisibleChanged);
@@ -86,6 +86,11 @@ QVariant LauncherModel::data(const QModelIndex &index, int role) const
8686 return item->focused();
8787 case RoleAlerting:
8888 return item->alerting();
89+ case RoleRunning:
90+ return item->running();
91+ default:
92+ qWarning() << Q_FUNC_INFO << "missing role, implement me";
93+ return QVariant();
8994 }
9095
9196 return QVariant();
@@ -150,7 +155,7 @@ void LauncherModel::pin(const QString &appId, int index)
150155 if (index == -1 || index == currentIndex) {
151156 m_list.at(currentIndex)->setPinned(true);
152157 QModelIndex modelIndex = this->index(currentIndex);
153− Q_EMIT dataChanged(modelIndex, modelIndex, QVector<int>() << RolePinned);
158+ Q_EMIT dataChanged(modelIndex, modelIndex, {RolePinned});
154159 } else {
155160 move(currentIndex, index);
156161 // 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)
163168
164169 DesktopFileHandler desktopFile(appId);
165170 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.";
167172 return;
168173 }
169174
@@ -188,7 +193,7 @@ void LauncherModel::requestRemove(const QString &appId)
188193
189194void LauncherModel::quickListActionInvoked(const QString &appId, int actionIndex)
190195{
191− int index = findApplication(appId);
196+ const int index = findApplication(appId);
192197 if (index < 0) {
193198 return;
194199 }
@@ -196,7 +201,7 @@ void LauncherModel::quickListActionInvoked(const QString &appId, int actionIndex
196201 LauncherItem *item = m_list.at(index);
197202 QuickListModel *model = qobject_cast<QuickListModel*>(item->quickList());
198203 if (model) {
199− QString actionId = model->get(actionIndex).actionId();
204+ const QString actionId = model->get(actionIndex).actionId();
200205
201206 // Check if this is one of the launcher actions we handle ourselves
202207 if (actionId == "pin_item") {
@@ -207,7 +212,10 @@ void LauncherModel::quickListActionInvoked(const QString &appId, int actionIndex
207212 }
208213 } else if (actionId == "launch_item") {
209214 QDesktopServices::openUrl(getUrlForAppId(appId));
210−
215+ } else if (actionId == "stop_item") { // Quit
216+ if (m_appManager) {
217+ m_appManager->stopApplication(appId);
218+ }
211219 // Nope, we don't know this action, let the backend forward it to the application
212220 } else {
213221 // TODO: forward quicklist action to app, possibly via m_dbusIface
@@ -305,7 +313,7 @@ void LauncherModel::storeAppList()
305313
306314void LauncherModel::unpin(const QString &appId)
307315{
308− int index = findApplication(appId);
316+ const int index = findApplication(appId);
309317 if (index < 0) {
310318 return;
311319 }
@@ -314,7 +322,7 @@ void LauncherModel::unpin(const QString &appId)
314322 if (m_list.at(index)->pinned()) {
315323 m_list.at(index)->setPinned(false);
316324 QModelIndex modelIndex = this->index(index);
317− Q_EMIT dataChanged(modelIndex, modelIndex, QVector<int>() << RolePinned);
325+ Q_EMIT dataChanged(modelIndex, modelIndex, {RolePinned});
318326 }
319327 } else {
320328 beginRemoveRows(QModelIndex(), index, index);
@@ -336,25 +344,25 @@ int LauncherModel::findApplication(const QString &appId)
336344
337345void LauncherModel::progressChanged(const QString &appId, int progress)
338346{
339− int idx = findApplication(appId);
347+ const int idx = findApplication(appId);
340348 if (idx >= 0) {
341349 LauncherItem *item = m_list.at(idx);
342350 item->setProgress(progress);
343− Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleProgress);
351+ Q_EMIT dataChanged(index(idx), index(idx), {RoleProgress});
344352 }
345353}
346354
347355void LauncherModel::countChanged(const QString &appId, int count)
348356{
349− int idx = findApplication(appId);
357+ const int idx = findApplication(appId);
350358 if (idx >= 0) {
351359 LauncherItem *item = m_list.at(idx);
352360 item->setCount(count);
353361 if (item->countVisible()) {
354362 setAlerting(item->appId(), true);
355363 }
356− Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleCount);
357364 m_asAdapter->syncItems(m_list);
365+ Q_EMIT dataChanged(index(idx), index(idx), {RoleCount});
358366 }
359367}
360368
@@ -367,7 +375,7 @@ void LauncherModel::countVisibleChanged(const QString &appId, bool countVisible)
367375 if (countVisible) {
368376 setAlerting(item->appId(), true);
369377 }
370− Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleCountVisible);
378+ Q_EMIT dataChanged(index(idx), index(idx), {RoleCountVisible});
371379
372380 // If countVisible goes to false, and the item is neither pinned nor recent we can drop it
373381 if (!countVisible && !item->pinned() && !item->recent()) {
@@ -408,7 +416,8 @@ void LauncherModel::refresh()
408416 item->setName(desktopFile.displayName());
409417 item->setIcon(desktopFile.icon());
410418 item->setPinned(item->pinned()); // update pinned text if needed
411− Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleName << RoleIcon);
419+ item->setRunning(item->running());
420+ Q_EMIT dataChanged(index(idx), index(idx), {RoleName, RoleIcon, RoleRunning});
412421 }
413422 }
414423
@@ -426,7 +435,7 @@ void LauncherModel::refresh()
426435
427436 // Now walk through settings and see if we need to add something
428437 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);
430439 int itemIndex = -1;
431440 for (int i = 0; i < m_list.count(); ++i) {
432441 if (m_list.at(i)->appId() == entry) {
@@ -499,25 +508,27 @@ void LauncherModel::applicationAdded(const QModelIndex &parent, int row)
499508 return;
500509 }
501510
502− int itemIndex = findApplication(app->appId());
511+ const int itemIndex = findApplication(app->appId());
503512 if (itemIndex != -1) {
504513 LauncherItem *item = m_list.at(itemIndex);
505514 if (!item->recent()) {
506515 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});
509517 }
510− // Shall we paint some running/recent app highlight? If yes, do it here.
518+ item->setRunning(true);
519+ // TODO Shall we paint some running/recent app highlight? If yes, do it here.
511520 } else {
512521 LauncherItem *item = new LauncherItem(app->appId(), app->name(), app->icon().toString(), this);
513522 item->setRecent(true);
523+ item->setRunning(true);
514524 item->setFocused(app->focused());
515525
516526 beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
517527 m_list.append(item);
518528 endInsertRows();
519− m_asAdapter->syncItems(m_list);
520529 }
530+ m_asAdapter->syncItems(m_list);
531+ Q_EMIT dataChanged(index(itemIndex), index(itemIndex), {RoleRunning});
521532}
522533
523534void LauncherModel::applicationRemoved(const QModelIndex &parent, int row)
@@ -532,25 +543,35 @@ void LauncherModel::applicationRemoved(const QModelIndex &parent, int row)
532543 }
533544 }
534545
535− if (appIndex > -1 && !m_list.at(appIndex)->pinned()) {
546+ if (appIndex < 0) {
547+ qWarning() << Q_FUNC_INFO << "appIndex not found";
548+ return;
549+ }
550+
551+ LauncherItem * item = m_list.at(appIndex);
552+ item->setRunning(false);
553+
554+ if (!item->pinned()) {
536555 beginRemoveRows(QModelIndex(), appIndex, appIndex);
537556 m_list.takeAt(appIndex)->deleteLater();
538557 endRemoveRows();
539558 m_asAdapter->syncItems(m_list);
559+ Q_EMIT dataChanged(index(appIndex), index(appIndex), {RolePinned});
540560 }
561+ Q_EMIT dataChanged(index(appIndex), index(appIndex), {RoleRunning});
541562}
542563
543564void LauncherModel::focusedAppIdChanged()
544565{
545− QString appId = m_appManager->focusedApplicationId();
566+ const QString appId = m_appManager->focusedApplicationId();
546567 for (int i = 0; i < m_list.count(); ++i) {
547568 LauncherItem *item = m_list.at(i);
548569 if (!item->focused() && item->appId() == appId) {
549570 item->setFocused(true);
550− Q_EMIT dataChanged(index(i), index(i), QVector<int>() << RoleFocused);
571+ Q_EMIT dataChanged(index(i), index(i), {RoleFocused});
551572 } else if (item->focused() && item->appId() != appId) {
552573 item->setFocused(false);
553− Q_EMIT dataChanged(index(i), index(i), QVector<int>() << RoleFocused);
574+ Q_EMIT dataChanged(index(i), index(i), {RoleFocused});
554575 }
555576 }
556577}
plugins/Unity/Launcher/quicklistentry.cpp+6−2
@@ -1,4 +1,4 @@
1−/* Copyright (C) 2013 Canonical, Ltd.
1+/* Copyright (C) 2013, 2015 Canonical, Ltd.
22 *
33 * Authors:
44 * Michael Zanetti <michael.zanetti@canonical.com>
@@ -20,7 +20,6 @@
2020
2121QuickListEntry::QuickListEntry()
2222{
23−
2423}
2524
2625QString QuickListEntry::actionId() const
@@ -57,3 +56,8 @@ bool QuickListEntry::clickable() const
5756{
5857 return !m_actionId.isEmpty();
5958}
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.
22 *
33 * Authors:
44 * Michael Zanetti <michael.zanetti@canonical.com>
@@ -37,6 +37,8 @@ public:
3737
3838 bool clickable() const;
3939
40+ bool operator==(const QuickListEntry & other);
41+
4042private:
4143 QString m_actionId;
4244 QString m_text;
plugins/Unity/Launcher/quicklistmodel.cpp+12−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2013 Canonical Ltd.
2+ * Copyright 2013, 2015 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU Lesser General Public License as published by
@@ -48,6 +48,17 @@ void QuickListModel::updateAction(const QuickListEntry &entry)
4848 }
4949}
5050
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+
5162QuickListEntry QuickListModel::get(int index) const
5263{
5364 return m_list.at(index);
plugins/Unity/Launcher/quicklistmodel.h+3−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2013 Canonical Ltd.
2+ * Copyright 201, 2015 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU Lesser General Public License as published by
@@ -46,6 +46,8 @@ public:
4646 */
4747 void updateAction(const QuickListEntry &entry);
4848
49+ void removeAction(const QuickListEntry &entry);
50+
4951 QuickListEntry get(int index) const;
5052
5153 int rowCount(const QModelIndex &parent = QModelIndex()) const override;
qml/Launcher/LauncherPanel.qml+22−13
@@ -15,7 +15,7 @@
1515 */
1616
1717import QtQuick 2.3
18−import Ubuntu.Components 1.1
18+import Ubuntu.Components 1.2
1919import Ubuntu.Components.ListItems 1.0 as ListItems
2020import Unity.Launcher 0.1
2121import Ubuntu.Components.Popups 0.1
@@ -358,6 +358,7 @@ Rectangle {
358358 MouseArea {
359359 id: dndArea
360360 objectName: "dndArea"
361+ acceptedButtons: Qt.LeftButton | Qt.RightButton
361362 anchors {
362363 fill: parent
363364 topMargin: launcherListView.topMargin
@@ -369,7 +370,7 @@ Rectangle {
369370 property int draggedIndex: -1
370371 property var selectedItem
371372 property bool preDragging: false
372− property bool dragging: selectedItem !== undefined && selectedItem !== null && selectedItem.dragging
373+ property bool dragging: !!selectedItem && selectedItem.dragging
373374 property bool postDragging: false
374375 property int startX
375376 property int startY
@@ -387,6 +388,15 @@ Rectangle {
387388 return;
388389 }
389390
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+
390400 // First/last item do the scrolling at more than 12 degrees
391401 if (index == 0 || index == launcherListView.count - 1) {
392402 if (clickedItem.angle > 12) {
@@ -580,7 +590,7 @@ Rectangle {
580590 id: quickListShape
581591 objectName: "quickListShape"
582592 anchors.fill: quickList
583− opacity: quickList.state === "open" ? 0.96 : 0
593+ opacity: quickList.state === "open" ? 0.8 : 0
584594 visible: opacity > 0
585595 rotation: root.rotation
586596
@@ -592,15 +602,15 @@ Rectangle {
592602
593603 Image {
594604 anchors {
595− left: parent.left
596− leftMargin: quickList.item !== undefined ? (quickList.item.width - units.gu(1)) / 2 - width / 2 : 0
605+ right: parent.left
606+ rightMargin: -units.dp(4)
597607 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)
599609 }
600610 height: units.gu(1)
601611 width: units.gu(2)
602612 source: "graphics/quicklist_tooltip.png"
603− rotation: (quickList.offset > 0 ? 0 : 180) + (root.inverted ? 0 : 180)
613+ rotation: 90
604614 }
605615
606616 InverseMouseArea {
@@ -623,11 +633,11 @@ Rectangle {
623633 height: quickListColumn.height
624634 visible: quickListShape.visible
625635 anchors {
626− left: root.inverted ? undefined : parent.left
627− right: root.inverted ? parent.right : undefined
636+ left: root.inverted ? undefined : parent.right
637+ right: root.inverted ? parent.left : undefined
628638 margins: units.gu(1)
629639 }
630− y: itemCenter + offset
640+ y: itemCenter - (height / 2) + offset
631641 rotation: root.rotation
632642
633643 property var model
@@ -636,9 +646,8 @@ Rectangle {
636646
637647 // internal
638648 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 ?
640− -(item.height/2) - height - units.gu(.5) :
641− (item.height/2) + units.gu(.5)) : 0
649+ property int offset: itemCenter + (height/2) + units.gu(1) > parent.height ? -itemCenter - (height/2) - units.gu(1) + parent.height :
650+ itemCenter - (height/2) < units.gu(1) ? (height/2) - itemCenter + units.gu(1) : 0
642651
643652 Column {
644653 id: quickListColumn
tests/mocks/Unity/Launcher/MockLauncherItem.cpp+3
@@ -40,7 +40,10 @@ MockLauncherItem::MockLauncherItem(const QString &appId, const QString& desktopF
4040 m_alerting(false),
4141 m_quickList(new MockQuickListModel(this))
4242{
43+}
4344
45+MockLauncherItem::~MockLauncherItem()
46+{
4447}
4548
4649QString MockLauncherItem::appId() const
tests/mocks/Unity/Launcher/MockLauncherItem.h+1
@@ -31,6 +31,7 @@ class MockLauncherItem: public LauncherItemInterface
3131 Q_OBJECT
3232public:
3333 MockLauncherItem(const QString &appId, const QString& desktopFile, const QString& name, const QString& icon, QObject* parent = 0);
34+ ~MockLauncherItem();
3435
3536 QString appId() const override;
3637 QString desktopFile() const;
tests/mocks/Unity/Launcher/MockLauncherModel.cpp+4−1
@@ -44,6 +44,7 @@ MockLauncherModel::MockLauncherModel(QObject* parent): LauncherModelInterface(pa
4444 item = new MockLauncherItem("webbrowser-app", "/usr/share/applications/webbrowser-app.desktop", "Browser", "browser", this);
4545 item->setCount(1);
4646 item->setCountVisible(true);
47+ item->setRunning(true);
4748 item->setAlerting(false);
4849 m_list.append(item);
4950 item = new MockLauncherItem("twitter-webapp", "/usr/share/applications/twitter-webapp.desktop", "Twitter", "twitter", this);
@@ -194,7 +195,9 @@ void MockLauncherModel::requestRemove(const QString &appId)
194195 int index = findApp(appId);
195196 if (index >= 0) {
196197 beginRemoveRows(QModelIndex(), index, 0);
197− m_list.takeAt(index)->deleteLater();
198+ MockLauncherItem * item = m_list.takeAt(index);
199+ item->setRunning(false);
200+ item->deleteLater();
198201 endRemoveRows();
199202 }
200203}
tests/plugins/Unity/Launcher/launchermodeltest.cpp+41
@@ -68,6 +68,7 @@ class MockAppManager: public unity::shell::application::ApplicationManagerInterf
6868 Q_OBJECT
6969public:
7070 MockAppManager(QObject *parent = 0): ApplicationManagerInterface(parent) {}
71+ ~MockAppManager() {}
7172 int rowCount(const QModelIndex &) const override { return m_list.count(); }
7273 QVariant data(const QModelIndex &, int ) const override { return QVariant(); }
7374 QString focusedApplicationId() const override {
@@ -325,6 +326,46 @@ private Q_SLOTS:
325326 QCOMPARE(launcherModel->get(0)->appId(), QLatin1String("abs-icon"));
326327 }
327328
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+
328369 void testGetUrlForAppId() {
329370 QCOMPARE(launcherModel->getUrlForAppId(QString()), QString());
330371 QCOMPARE(launcherModel->getUrlForAppId(""), QString());
tests/qmltests/Launcher/tst_Launcher.qml+29−7
@@ -197,7 +197,7 @@ Item {
197197 startX+units.gu(8), startY);
198198
199199 var panel = findChild(launcher, "launcherPanel");
200− verify(panel != undefined);
200+ verify(!!panel);
201201
202202 // wait until it gets fully extended
203203 tryCompare(panel, "x", 0);
@@ -208,7 +208,7 @@ Item {
208208 mouseMove(root, 1, root.height / 2);
209209
210210 var panel = findChild(launcher, "launcherPanel");
211− verify(panel != undefined);
211+ verify(!!panel);
212212
213213 // wait until it gets fully extended
214214 tryCompare(panel, "x", 0);
@@ -248,7 +248,7 @@ Item {
248248 waitUntilLauncherDisappears();
249249
250250 var panel = findChild(launcher, "launcherPanel")
251− verify(panel != undefined)
251+ verify(!!panel)
252252
253253 // it starts out hidden just left of the left launcher edge
254254 compare(panel.x, -panel.width)
@@ -286,7 +286,7 @@ Item {
286286
287287 var appIcon = findChild(launcher, "launcherDelegate0");
288288
289− verify(appIcon != undefined);
289+ verify(!!appIcon);
290290
291291 if (data.mouse) {
292292 mouseClick(appIcon);
@@ -309,7 +309,7 @@ Item {
309309 dragLauncherIntoView()
310310
311311 var dashIcon = findChild(launcher, "dashItem")
312− verify(dashIcon != undefined)
312+ verify(!!dashIcon)
313313
314314 mouseClick(dashIcon)
315315
@@ -599,7 +599,7 @@ Item {
599599
600600 // Doing longpress
601601 mousePress(draggedItem);
602− tryCompare(quickListShape, "opacity", 0.96);
602+ tryCompare(quickListShape, "opacity", 0.8);
603603 mouseRelease(draggedItem);
604604
605605 verify(quickList.y >= units.gu(1));
@@ -684,9 +684,31 @@ Item {
684684 tryCompare(quickList, "state", "");
685685 }
686686
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+
687709 function test_revealByHover() {
688710 var panel = findChild(launcher, "launcherPanel");
689− verify(panel != undefined);
711+ verify(!!panel);
690712
691713 revealByHover();
692714 tryCompare(launcher, "state", "visibleTemporary");