diff --git a/applets/systemtray/package/contents/config/main.xml b/applets/systemtray/package/contents/config/main.xml --- a/applets/systemtray/package/contents/config/main.xml +++ b/applets/systemtray/package/contents/config/main.xml @@ -39,6 +39,10 @@ + + + org.kde.plasma.notifications + false diff --git a/applets/systemtray/package/contents/ui/ConfigEntries.qml b/applets/systemtray/package/contents/ui/ConfigEntries.qml --- a/applets/systemtray/package/contents/ui/ConfigEntries.qml +++ b/applets/systemtray/package/contents/ui/ConfigEntries.qml @@ -34,8 +34,18 @@ property var cfg_shownItems: [] property var cfg_hiddenItems: [] + property var cfg_itemOrder: [] property alias cfg_showAllItems: showAllCheckBox.checked + property var itemInfo + + onConfigurationChanged: { + // trigger updates of dependent properties + tableView.modelChanged() + cfg_shownItems = cfg_shownItems + cfg_hiddenItems = cfg_hiddenItems + } + columns: 2 // so we can indent the entries below... function saveConfig () { @@ -65,183 +75,257 @@ visible: false } - function retrieveAllItems() { - print(plasmoid) - print(plasmoid.rootItem.statusNotifierModel) - var list = []; - for (var i = 0; i < plasmoid.rootItem.statusNotifierModel.count; ++i) { + function arrayTryRemove(arr, item) { + var i = arr.indexOf(item) + if (i !== -1) { + arr.splice(i, 1) + } + } + + function arrayTryAdd(arr, item) { + var i = arr.indexOf(item) + if (i === -1) { + arr.push(item) + } + } + + function moveItem(from, to) { + var tmp = cfg_itemOrder.splice(from, 1) + cfg_itemOrder.splice(to, 0, tmp[0]) + } + + function retrieveItemInfo() { + // print(plasmoid) + // print(plasmoid.rootItem.statusNotifierModel) + print(cfg_itemOrder) + + var itemInfo = {} + + for (var i = 0; i < cfg_itemOrder.length; ++i) { + var itemId = cfg_itemOrder[i] + itemInfo[itemId] = { + "taskId": itemId, + "name": itemId, + "inactive": true + } + } + + /*for (var i = 0; i < plasmoid.rootItem.statusNotifierModel.count; ++i) { var item = plasmoid.rootItem.statusNotifierModel.get(i); - list.push({ - "index": i, + itemInfo[item.Id] = { "taskId": item.Id, "name": item.Title, "iconName": item.IconName, - "icon": item.Icon - }); - } - var lastIndex = list.length; + "icon": item.Icon, + "inactive": false + } + }*/ + for (var i = 0; i < plasmoid.applets.length; ++i) { var item = plasmoid.applets[i] - list.push({ - "index": (i + lastIndex), + itemInfo[item.pluginName] = { + "index": i, "applet": item, "taskId": item.pluginName, "name": item.title, "iconName": item.icon, - "shortcut": item.globalShortcut - }); + "shortcut": item.globalShortcut, + "inactive": false + } } - list.sort(function(a, b) { - return a.name.localeCompare(b.name); - }); - return list; + + iconsPage.itemInfo = itemInfo + } + + Component.onCompleted: { + retrieveItemInfo() + configurationChanged() } - QtControls.TableView { - id: tableView + Item { + id: dragArea + property var dragAxis: Drag.XandYAxis + property bool dragActive: false + QtLayouts.Layout.fillWidth: true QtLayouts.Layout.fillHeight: true - QtLayouts.Layout.row: 2 - QtLayouts.Layout.column: 1 + QtLayouts.Layout.columnSpan: iconsPage.columns - model: retrieveAllItems() - horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff - flickableItem.boundsBehavior: Flickable.StopAtBounds + QtControls.TableView { + id: tableView + anchors.fill: parent - Component.onCompleted: { - visibilityColumn.resizeToContents() - shortcutColumn.resizeToContents() - } + model: cfg_itemOrder + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff + flickableItem.boundsBehavior: Flickable.StopAtBounds - // Taken from QtQuickControls BasicTableViewStyle, just to make its height sensible... - rowDelegate: BorderImage { - visible: styleData.selected || styleData.alternate - source: "image://__tablerow/" + (styleData.alternate ? "alternate_" : "") - + (tableView.activeFocus ? "active" : "") - height: measureButton.height - border.left: 4 ; border.right: 4 - } + Component.onCompleted: { + visibilityColumn.resizeToContents() + shortcutColumn.resizeToContents() + } - QtControls.TableViewColumn { - id: entryColumn - width: tableView.viewport.width - visibilityColumn.width - shortcutColumn.width - title: i18nc("Name of the system tray entry", "Entry") - movable: false - resizable: false - - delegate: QtLayouts.RowLayout { - Item { // spacer - QtLayouts.Layout.preferredWidth: 1 - QtLayouts.Layout.fillHeight: true - } + // Taken from QtQuickControls BasicTableViewStyle, just to make its height sensible... + rowDelegate: BorderImage { + visible: styleData.selected || styleData.alternate + source: "image://__tablerow/" + (styleData.alternate ? "alternate_" : "") + + (tableView.activeFocus ? "active" : "") + height: measureButton.height + border.left: 4 ; border.right: 4 + } - QIconItem { - width: units.iconSizes.small - height: width - icon: modelData.iconName || modelData.icon || "" - } + QtControls.TableViewColumn { + id: entryColumn + width: tableView.viewport.width - visibilityColumn.width - shortcutColumn.width + title: i18nc("Name of the system tray entry", "Entry") + movable: false + resizable: false - QtControls.Label { + delegate: OrderableItem { + dragParent: dragArea + onMoveItemRequested: { + if (to == -1) { // delete item + arrayTryRemove(cfg_itemOrder, modelData) + arrayTryRemove(cfg_shownItems, modelData) + arrayTryRemove(cfg_hiddenItems, modelData) + } else { + moveItem(from, to) + } + iconsPage.configurationChanged(); + } + property var item: iconsPage.itemInfo[modelData] QtLayouts.Layout.fillWidth: true - text: modelData.name - elide: Text.ElideRight - wrapMode: Text.NoWrap + + QtLayouts.RowLayout { + Item { // spacer + QtLayouts.Layout.preferredWidth: 1 + QtLayouts.Layout.fillHeight: true + } + + QIconItem { + width: units.iconSizes.small + height: width + icon: item.iconName || item.icon || "" + } + + QtControls.Label { + text: item.name + elide: Text.ElideRight + wrapMode: Text.NoWrap + font.italic: !!item.inactive + } + } } } - } - QtControls.TableViewColumn { - id: visibilityColumn - title: i18n("Visibility") - movable: false - resizable: false - - delegate: QtControls.ComboBox { - implicitWidth: Math.round(units.gridUnit * 6.5) // ComboBox sizing is broken - - enabled: !showAllCheckBox.checked - currentIndex: { - if (cfg_shownItems.indexOf(modelData.taskId) != -1) { - return 1; - } else if (cfg_hiddenItems.indexOf(modelData.taskId) != -1) { - return 2; - } else { - return 0; + QtControls.TableViewColumn { + id: visibilityColumn + title: i18n("Visibility") + movable: false + resizable: false + + delegate: QtControls.ComboBox { + QtLayouts.Layout.leftMargin: 30 + QtControls.Label { + text: (cfg_shownItems.indexOf(modelData) != -1 ? "S" : "") + + (cfg_hiddenItems.indexOf(modelData) != -1 ? "H" : "") + wrapMode: Text.NoWrap } - } - // activated, in contrast to currentIndexChanged, only fires if the user himself changed the value - onActivated: { - var shownIndex = cfg_shownItems.indexOf(modelData.taskId); - var hiddenIndex = cfg_hiddenItems.indexOf(modelData.taskId); + implicitWidth: Math.round(units.gridUnit * 6.5) // ComboBox sizing is broken - switch (index) { - case 0: { - if (shownIndex > -1) { - cfg_shownItems.splice(shownIndex, 1); - } - if (hiddenIndex > -1) { - cfg_hiddenItems.splice(hiddenIndex, 1); + enabled: !showAllCheckBox.checked + + currentIndex: { + if (cfg_shownItems.indexOf(modelData) != -1) { + return 1 + } else if (cfg_hiddenItems.indexOf(modelData) != -1) { + return 2 + } else { + return 0 } - break; } - case 1: { - if (shownIndex == -1) { - cfg_shownItems.push(modelData.taskId); + + // activated, in contrast to currentIndexChanged, only fires if the user himself changed the value + onActivated: { + if (index == 1) { + arrayTryAdd(cfg_shownItems, modelData) + } else { + arrayTryRemove(cfg_shownItems, modelData); } - if (hiddenIndex > -1) { - cfg_hiddenItems.splice(hiddenIndex, 1); + + if (index == 2) { + arrayTryAdd(cfg_hiddenItems, modelData) + } else { + arrayTryRemove(cfg_hiddenItems, modelData); } - break; + + iconsPage.configurationChanged(); } - case 2: { - if (shownIndex > -1) { - cfg_shownItems.splice(shownIndex, 1); - } - if (hiddenIndex == -1) { - cfg_hiddenItems.push(modelData.taskId); + model: [i18n("Auto"), i18n("Shown"), i18n("Hidden")] + } + } + + QtControls.TableViewColumn { + id: shortcutColumn + title: i18n("Keyboard Shortcut") // FIXME doesn't fit + movable: false + resizable: false + + // this Item wrapper prevents TableView from ripping apart the two KeySequenceItem buttons + delegate: Item { + property var item: iconsPage.itemInfo[modelData] + + implicitWidth: Math.max(shortcutColumnMeasureLabel.width, keySequenceItem.width) + 10 + height: keySequenceItem.height + + KQC.KeySequenceItem { + id: keySequenceItem + anchors.right: parent.right + + keySequence: item.shortcut + // only Plasmoids have that + visible: item.hasOwnProperty("shortcut") + onKeySequenceChanged: { + if (keySequence && keySequence != item.shortcut) { + // both SNIs and plasmoids are listed in the same TableView + // but they come from two separate models, so we need to subtract + // the SNI model count to get the actual plasmoid index + var index = item.index + plasmoid.applets[index].globalShortcut = keySequence + + iconsPage.configurationChanged() + } + + shortcutColumn.resizeToContents() } - break; - } } - iconsPage.configurationChanged(); } - model: [i18n("Auto"), i18n("Shown"), i18n("Hidden")] } } - QtControls.TableViewColumn { - id: shortcutColumn - title: i18n("Keyboard Shortcut") // FIXME doesn't fit - movable: false - resizable: false - - // this Item wrapper prevents TableView from ripping apart the two KeySequenceItem buttons - delegate: Item { - implicitWidth: Math.max(shortcutColumnMeasureLabel.width, keySequenceItem.width) + 10 - height: keySequenceItem.height - - KQC.KeySequenceItem { - id: keySequenceItem - anchors.right: parent.right - - keySequence: modelData.shortcut - // only Plasmoids have that - visible: modelData.hasOwnProperty("shortcut") - onKeySequenceChanged: { - if (keySequence != modelData.shortcut) { - // both SNIs and plasmoids are listed in the same TableView - // but they come from two separate models, so we need to subtract - // the SNI model count to get the actual plasmoid index - var index = modelData.index - plasmoid.rootItem.statusNotifierModel.count - plasmoid.applets[index].globalShortcut = keySequence - - iconsPage.configurationChanged() - } + OrderableItem { + id: discardTarget + dropIndex: -1 + draggable: false + visible: dragArea.dragActive - shortcutColumn.resizeToContents() - } - } + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.bottomMargin: Math.round(units.gridUnit * 0.5) + anchors.leftMargin: Math.round(units.gridUnit * 0.5) + width: Math.round(units.gridUnit * 2.5) + height: width + + Rectangle { + color: "black" + opacity: 0.7 + anchors.fill: parent + } + + QIconItem { + anchors.fill: parent + anchors.margins: Math.round(units.gridUnit * 0.2) + icon: "trash-empty" } } } diff --git a/applets/systemtray/package/contents/ui/OrderableItem.qml b/applets/systemtray/package/contents/ui/OrderableItem.qml new file mode 100644 --- /dev/null +++ b/applets/systemtray/package/contents/ui/OrderableItem.qml @@ -0,0 +1,106 @@ +import QtQuick 2.0 +import QtQuick.Layouts 1.1 + +Item { + id: root + + default property Item contentItem + + property int dropIndex: model.index + property bool draggable: true + + // This item will become the parent of the dragged item during the drag operation + property Item dragParent + + signal moveItemRequested(int from, int to) + + Layout.preferredWidth: contentItem.width + Layout.preferredHeight: contentItem.height + + // Make contentItem a child of contentItemWrapper + onContentItemChanged: { + contentItem.parent = contentItemWrapper; + } + + Item { + id: contentItemWrapper + anchors.fill: parent + Drag.active: dragArea.drag.active + Drag.hotSpot { + x: dragArea.mouseX + y: dragArea.mouseY + } + + MouseArea { + id: dragArea + visible: root.draggable + anchors.fill: parent + drag.target: parent + // Keep the dragged item at the same X position. Nice for lists, but not mandatory + drag.axis: dragParent.dragAxis + // Disable smoothed so that the Item pixel from where we started the drag remains + // under the mouse cursor + drag.smoothed: false + + cursorShape: Qt.OpenHandCursor + + onReleased: { + if (drag.active) { + emitMoveItemRequested(); + } + } + } + } + + states: [ + State { + when: dragArea.drag.active + name: 'dragging' + + ParentChange { + target: contentItemWrapper + parent: dragParent + } + PropertyChanges { + target: contentItemWrapper + opacity: 0.9 + anchors.fill: undefined + width: contentItem.width + height: contentItem.height + } + PropertyChanges { + target: dragParent + dragActive: true + } + } + ] + + DropArea { + id: itemDropArea + anchors.fill: parent + + property alias dropIndex: root.dropIndex + + Rectangle { + id: dropIndicator + anchors.fill: parent + border.width: 1 + border.color: theme.highlightColor + color: 'transparent' + opacity: itemDropArea.containsDrag ? 1 : 0 + } + } + + function emitMoveItemRequested() { + var dropArea = contentItemWrapper.Drag.target; + if (!dropArea) { + return; + } + var dropIndex = dropArea.dropIndex; + + if (model.index === dropIndex) { + return; + } + root.moveItemRequested(model.index, dropIndex); + } +} diff --git a/applets/systemtray/package/contents/ui/items/AbstractItem.qml b/applets/systemtray/package/contents/ui/items/AbstractItem.qml --- a/applets/systemtray/package/contents/ui/items/AbstractItem.qml +++ b/applets/systemtray/package/contents/ui/items/AbstractItem.qml @@ -57,6 +57,18 @@ } } + property int position: { + var pos = plasmoid.configuration.itemOrder.indexOf(itemId) + if (pos === -1 && !!itemId) { + // yes we must use temp variable otherwise push() does nothing + var tmp = plasmoid.configuration.itemOrder + tmp.push(itemId) + plasmoid.configuration.itemOrder = tmp + pos = plasmoid.configuration.itemOrder.indexOf(itemId) + } + return pos + } + /* subclasses need to assign to this tiiltip properties mainText: subText: @@ -77,18 +89,22 @@ //BEGIN CONNECTIONS - onEffectiveStatusChanged: updateItemVisibility(abstractItem); + function updateVisibility() { + updateItemVisibility(abstractItem) + } + + onEffectiveStatusChanged: Qt.callLater(updateVisibility) + + onPositionChanged: Qt.callLater(updateVisibility) onContainsMouseChanged: { if (hidden && containsMouse) { root.hiddenLayout.hoveredItem = abstractItem } } - Component.onCompleted: updateItemVisibility(abstractItem); - //dangerous but needed due how repeater reparents - onParentChanged: updateItemVisibility(abstractItem); + onParentChanged: Qt.callLater(updateVisibility) //END CONNECTIONS diff --git a/applets/systemtray/package/contents/ui/main.qml b/applets/systemtray/package/contents/ui/main.qml --- a/applets/systemtray/package/contents/ui/main.qml +++ b/applets/systemtray/package/contents/ui/main.qml @@ -58,39 +58,48 @@ } } + function getFixedItemId(itemId, context) { + return itemId; + } + + function reorderItem(item, container) { + if (container.children.length == 0) { + item.parent = container; + } else { + var i = 0; + while (i < container.children.length && + container.children[i].position < item.position) { + i++ + } + + if (i == container.children.length) { + var other = container.children[i - 1] + if (item != other) { + plasmoid.nativeInterface.reorderItemAfter(item, other) + } + } else { + var other = container.children[i] + if (item != other) { + plasmoid.nativeInterface.reorderItemBefore(item, other) + } + } + } + } + function updateItemVisibility(item) { switch (item.effectiveStatus) { case PlasmaCore.Types.HiddenStatus: - if (item.parent == invisibleEntriesContainer) { - return; + if (item.parent != invisibleEntriesContainer) { + item.parent = invisibleEntriesContainer; } - - item.parent = invisibleEntriesContainer; break; case PlasmaCore.Types.ActiveStatus: - if (visibleLayout.children.length == 0) { - item.parent = visibleLayout; - //notifications is always the first - } else if (visibleLayout.children[0].itemId == "org.kde.plasma.notifications" && - item.itemId != "org.kde.plasma.notifications") { - plasmoid.nativeInterface.reorderItemAfter(item, visibleLayout.children[0]); - } else if (visibleLayout.children[0] != item) { - plasmoid.nativeInterface.reorderItemBefore(item, visibleLayout.children[0]); - } + reorderItem(item, visibleLayout) break; case PlasmaCore.Types.PassiveStatus: - - if (hiddenLayout.children.length == 0) { - item.parent = hiddenLayout; - //notifications is always the first - } else if (hiddenLayout.children[0].itemId == "org.kde.plasma.notifications" && - item.itemId != "org.kde.plasma.notifications") { - plasmoid.nativeInterface.reorderItemAfter(item, hiddenLayout.children[0]); - } else if (hiddenLayout.children[0] != item) { - plasmoid.nativeInterface.reorderItemBefore(item, hiddenLayout.children[0]); - } + reorderItem(item, hiddenLayout) item.x = 0; break; }