on xenial help sign in
initial support for quicklists

For now they support pinning and removing of items. Approved by Michał Sawicz, PS Jenkins bot.

commit25632536dcfc7d25ff4a00d58fbe804b7d8a0dd9 authorMichael Zanetti <michael.zanetti@canonical.com> dateTue 20 Aug 2013, 09:18 (13 years ago) parenta18a1884457f39
11 files changed+157−6
Launcher/Launcher.qml+2−2 Launcher/LauncherPanel.qml+55−1 plugins/Unity/Launcher/common/quicklistentry.cpp+5 plugins/Unity/Launcher/common/quicklistentry.h+2 plugins/Unity/Launcher/launcheritem.cpp+3 plugins/Unity/Launcher/quicklistmodel.cpp+2 tests/mocks/Unity/Launcher/MockLauncherModel.cpp+1−3 tests/mocks/Unity/Launcher/MockLauncherModel.h+3 tests/mocks/Unity/Launcher/MockQuickListModel.cpp+2 tests/mocks/Unity/Launcher/plugin.cpp+3 tests/qmltests/Launcher/tst_Launcher.qml+79
Launcher/Launcher.qml+2−2
@@ -73,7 +73,7 @@ Item {
7373 id: dismissTimer
7474 interval: 5000
7575 onTriggered: {
76− if (!panel.moving) {
76+ if (!panel.preventHiding) {
7777 root.state = ""
7878 } else {
7979 dismissTimer.restart()
@@ -162,7 +162,7 @@ Item {
162162 root.dashItemSelected(index)
163163 }
164164
165− onMovingChanged: {
165+ onPreventHidingChanged: {
166166 if (dismissTimer.running) {
167167 dismissTimer.restart();
168168 }
Launcher/LauncherPanel.qml+55−1
@@ -19,6 +19,7 @@ import Ubuntu.Components 0.1
1919import Ubuntu.Components.ListItems 0.1 as ListItems
2020import Unity 0.1
2121import Unity.Launcher 0.1
22+import Ubuntu.Components.Popups 0.1
2223import "../Components/ListItems"
2324
2425Item {
@@ -29,7 +30,8 @@ Item {
2930 property var model
3031 property bool inverted: true
3132 property bool dragging: false
32− property bool moving: launcherListView.moving || launcherListView.flicking || dndArea.draggedIndex >= 0
33+ property bool moving: launcherListView.moving || launcherListView.flicking
34+ property bool preventHiding: moving || dndArea.draggedIndex >= 0 || dndArea.quickListPopover !== null || dndArea.pressed
3335 property int highlightIndex: -1
3436
3537 signal applicationSelected(string desktopFile)
@@ -219,6 +221,7 @@ Item {
219221
220222 MouseArea {
221223 id: dndArea
224+ objectName: "dndArea"
222225 anchors {
223226 fill: parent
224227 topMargin: launcherListView.topMargin
@@ -234,6 +237,7 @@ Item {
234237 property bool postDragging: false
235238 property int startX
236239 property int startY
240+ property var quickListPopover: null
237241
238242 onPressed: {
239243 selectedItem = launcherListView.itemAt(mouseX, mouseY + launcherListView.realContentY)
@@ -302,6 +306,13 @@ Item {
302306
303307 draggedIndex = Math.floor((mouseY + launcherListView.realContentY) / launcherListView.realItemHeight);
304308
309+ // Opening QuickList
310+ var quickListModel = launcherListView.model.get(draggedIndex).quickList
311+ var quickListAppId = launcherListView.model.get(draggedIndex).appId
312+
313+ quickListPopover = PopupUtils.open(popoverComponent, selectedItem,
314+ {model: quickListModel, appId: quickListAppId})
315+
305316 launcherListView.interactive = false
306317
307318 var yOffset = draggedIndex > 0 ? (mouseY + launcherListView.realContentY) % (draggedIndex * launcherListView.realItemHeight) : mouseY + launcherListView.realContentY
@@ -324,6 +335,7 @@ Item {
324335 var distance = Math.max(Math.abs(mouseX - startX), Math.abs(mouseY - startY))
325336 if (!preDragging && distance > units.gu(1.5)) {
326337 preDragging = true;
338+ PopupUtils.close(quickListPopover)
327339 }
328340 if (distance > launcherListView.itemHeight) {
329341 selectedItem.dragging = true
@@ -414,4 +426,46 @@ Item {
414426 }
415427 }
416428 }
429+
430+ Component {
431+ id: popoverComponent
432+
433+ Popover {
434+ id: popover
435+ property var model
436+ property string appId
437+ contentWidth: quickListColumn.width
438+
439+ // FIXME: There's a bug in the Popover positioning that it covers the item in case it is rotated.
440+ // https://bugs.launchpad.net/ubuntu-ui-toolkit/+bug/1204470
441+ // For now, let's move the Popover around with callerMargin.
442+ // Remove popupMargin once the bug is fixed.
443+ property int popupMargin: root.inverted ? launcherListView.itemHeight : 0;
444+
445+ callerMargin: units.gu(1) + popupMargin
446+
447+ Column {
448+ id: quickListColumn
449+ width: units.gu(30)
450+ height: childrenRect.height
451+
452+ Repeater {
453+ model: popover.model
454+
455+ ListItems.Standard {
456+ objectName: "quickListEntry" + index
457+ text: (model.clickable ? "" : "<b>") + model.label + (model.clickable ? "" : "</b>")
458+ highlightWhenPressed: model.clickable
459+ onClicked: {
460+ if (!model.clickable) {
461+ return;
462+ }
463+ LauncherModel.quickListActionInvoked(appId, index);
464+ PopupUtils.close(popover);
465+ }
466+ }
467+ }
468+ }
469+ }
470+ }
417471}
plugins/Unity/Launcher/common/quicklistentry.cpp+5
@@ -52,3 +52,8 @@ void QuickListEntry::setIcon(const QString &icon)
5252{
5353 m_icon = icon;
5454}
55+
56+bool QuickListEntry::clickable() const
57+{
58+ return !m_actionId.isEmpty();
59+}
plugins/Unity/Launcher/common/quicklistentry.h+2
@@ -35,6 +35,8 @@ public:
3535 QString icon() const;
3636 void setIcon(const QString &icon);
3737
38+ bool clickable() const;
39+
3840private:
3941 QString m_actionId;
4042 QString m_text;
plugins/Unity/Launcher/launcheritem.cpp+3
@@ -33,6 +33,9 @@ LauncherItem::LauncherItem(const QString &appId, const QString &desktopFile, con
3333 m_count(0),
3434 m_quickList(new QuickListModel(this))
3535{
36+ QuickListEntry nameAction;
37+ nameAction.setText(m_name);
38+ m_quickList->appendAction(nameAction);
3639 QuickListEntry pinningAction;
3740 pinningAction.setActionId("pin_item");
3841 pinningAction.setText("Pin to Launcher");
plugins/Unity/Launcher/quicklistmodel.cpp+2
@@ -66,6 +66,8 @@ QVariant QuickListModel::data(const QModelIndex &index, int role) const
6666 return m_list.at(index.row()).text();
6767 case RoleIcon:
6868 return m_list.at(index.row()).icon();
69+ case RoleClickable:
70+ return m_list.at(index.row()).clickable();
6971 }
7072 return QVariant();
7173}
tests/mocks/Unity/Launcher/MockLauncherModel.cpp+1−3
@@ -131,9 +131,7 @@ void MockLauncherModel::requestRemove(const QString &appId)
131131
132132void MockLauncherModel::quickListActionInvoked(const QString &appId, int actionIndex)
133133{
134− Q_UNUSED(appId)
135− Q_UNUSED(actionIndex)
136− // Nothing to mock yet...
134+ Q_EMIT quickListTriggered(appId, actionIndex);
137135}
138136
139137int MockLauncherModel::findApp(const QString &appId)
tests/mocks/Unity/Launcher/MockLauncherModel.h+3
@@ -44,6 +44,9 @@ public:
4444 Q_INVOKABLE void requestRemove(const QString &appId);
4545 Q_INVOKABLE void quickListActionInvoked(const QString &appId, int actionIndex);
4646
47+Q_SIGNALS:
48+ void quickListTriggered(const QString &appId, int index);
49+
4750private:
4851 int findApp(const QString &appId);
4952
tests/mocks/Unity/Launcher/MockQuickListModel.cpp+2
@@ -35,6 +35,8 @@ QVariant MockQuickListModel::data(const QModelIndex &index, int role) const
3535 return QLatin1String("test menu entry ") + QString::number(index.row());
3636 case RoleIcon:
3737 return QLatin1String("copy.png");
38+ case RoleClickable:
39+ return index.row() == 1 ? false : true;
3840 }
3941 return QVariant();
4042}
tests/mocks/Unity/Launcher/plugin.cpp+3
@@ -20,6 +20,7 @@
2020#include "plugin.h"
2121#include "MockLauncherModel.h"
2222#include "MockLauncherItem.h"
23+#include "MockQuickListModel.h"
2324
2425#include <unity/shell/launcher/LauncherModelInterface.h>
2526#include <unity/shell/launcher/LauncherItemInterface.h>
@@ -39,7 +40,9 @@ void TestLauncherPlugin::registerTypes(const char* uri)
3940 // @uri Unity.Launcher
4041 qmlRegisterUncreatableType<LauncherModelInterface>(uri, 0, 1, "LauncherModelInterface", "Abstract Interface. Cannot be instantiated.");
4142 qmlRegisterUncreatableType<LauncherItemInterface>(uri, 0, 1, "LauncherItemInterface", "Abstract Interface. Cannot be instantiated.");
43+ qmlRegisterUncreatableType<QuickListModelInterface>(uri, 0, 1, "QuickListModelInterface", "Abstract Interface. Cannot be instantiated.");
4244
4345 qmlRegisterSingletonType<MockLauncherModel>(uri, 0, 1, "LauncherModel", modelProvider);
4446 qmlRegisterUncreatableType<MockLauncherItem>(uri, 0, 1, "LauncherItem", "Can't create LauncherItems in QML. Get them from the LauncherModel");
47+ qmlRegisterUncreatableType<MockQuickListModel>(uri, 0, 1, "QuickListModel", "Can't create QuickLists in QML. Get them from the LauncherItems");
4548}
tests/qmltests/Launcher/tst_Launcher.qml+79
@@ -49,6 +49,11 @@ Item {
4949 property int maxPanelX: 0
5050 }
5151
52+ SignalSpy {
53+ id: signalSpy
54+ target: LauncherModel
55+ }
56+
5257 Connections {
5358 target: testCase.findChild(launcher, "launcherPanel")
5459
@@ -306,5 +311,79 @@ Item {
306311 tryCompare(draggedItem, "itemOpacity", 1)
307312 tryCompare(fakeDragItem, "visible", false)
308313 }
314+
315+ function test_quicklist_dismiss() {
316+ revealer.dragLauncherIntoView();
317+ var draggedItem = findChild(launcher, "launcherDelegate5")
318+ var item0 = findChild(launcher, "launcherDelegate0")
319+ var fakeDragItem = findChild(launcher, "fakeDragItem")
320+ var dndArea = findChild(launcher, "dndArea")
321+
322+ // Initial state
323+ compare(dndArea.quickListPopover, null)
324+
325+ // Doing longpress
326+ mousePress(draggedItem, draggedItem.width / 2, draggedItem.height / 2)
327+ tryCompare(fakeDragItem, "visible", true) // Wait longpress happening
328+ verify(dndArea.quickListPopover != null)
329+
330+ // Dragging a bit (> 1.5 gu)
331+ mouseMove(draggedItem, -units.gu(2), draggedItem.height / 2)
332+
333+ // QuickList needs to be closed when a drag operation starts
334+ tryCompare(dndArea, "quickListPopover", null)
335+
336+ mouseRelease(draggedItem);
337+ }
338+
339+ function test_quicklist_click_data() {
340+ return [
341+ {tag: "non-clickable", index: 1, clickable: false },
342+ {tag: "clickable", index: 2, clickable: true },
343+ ];
344+ }
345+
346+ function test_quicklist_click(data) {
347+ revealer.dragLauncherIntoView();
348+ var clickedItem = findChild(launcher, "launcherDelegate5")
349+ var dndArea = findChild(launcher, "dndArea")
350+
351+ // Initial state
352+ compare(dndArea.quickListPopover, null)
353+
354+ // Doing longpress
355+ mousePress(clickedItem, clickedItem.width / 2, clickedItem.height / 2)
356+ tryCompare(clickedItem, "itemOpacity", 0) // Wait for longpress to happen
357+ verify(dndArea.quickListPopover != null)
358+
359+ mouseRelease(clickedItem);
360+
361+ var quickListEntry = findChild(dndArea.quickListPopover, "quickListEntry" + data.index)
362+
363+ signalSpy.clear();
364+ signalSpy.signalName = "quickListTriggered"
365+
366+ mouseClick(quickListEntry, quickListEntry.width / 2, quickListEntry.height / 2)
367+
368+ if (data.clickable) {
369+ // QuickList needs to be closed when some clickable item is clicked
370+ tryCompare(dndArea, "quickListPopover", null)
371+
372+ compare(signalSpy.count, 1, "Quicklist signal wasn't triggered")
373+ compare(signalSpy.signalArguments[0][0], LauncherModel.get(5).appId)
374+ compare(signalSpy.signalArguments[0][1], 2)
375+
376+ } else {
377+
378+ // QuickList must not be closed when a non-clickable item is clicked
379+ verify(dndArea.quickListPopover != null)
380+
381+ compare(signalSpy.count, 0, "Quicklist signal must NOT be triggered when clicking a non-clickable item")
382+
383+ // Click somewhere in the empty space to dismiss the quicklist
384+ mouseClick(launcher, launcher.width - units.gu(1), units.gu(1));
385+ tryCompare(dndArea, "quickListPopover", null)
386+ }
387+ }
309388 }
310389}