diff --git a/applets/kicker/package/contents/ui/DashboardRepresentation.qml b/applets/kicker/package/contents/ui/DashboardRepresentation.qml index 76c72fca5..a9149f047 100644 --- a/applets/kicker/package/contents/ui/DashboardRepresentation.qml +++ b/applets/kicker/package/contents/ui/DashboardRepresentation.qml @@ -1,973 +1,975 @@ /*************************************************************************** * Copyright (C) 2015 by Eike Hein * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ import QtQuick 2.4 import QtGraphicalEffects 1.0 import org.kde.plasma.core 2.1 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.extras 2.0 as PlasmaExtras import org.kde.kquickcontrolsaddons 2.0 import org.kde.kwindowsystem 1.0 import org.kde.plasma.private.shell 2.0 import org.kde.plasma.private.kicker 0.1 as Kicker import "../code/tools.js" as Tools /* TODO * Reverse middleRow layout + keyboard nav + filter list text alignment in rtl locales. * Keep cursor column when arrow'ing down past non-full trailing rows into a lower grid. * Make DND transitions cleaner by performing an item swap instead of index reinsertion. */ Kicker.DashboardWindow { id: root property bool smallScreen: ((Math.floor(width / units.iconSizes.huge) <= 22) || (Math.floor(height / units.iconSizes.huge) <= 14)) property int iconSize: smallScreen ? units.iconSizes.large : units.iconSizes.huge property int cellSize: iconSize + theme.mSize(theme.defaultFont).height + (2 * units.smallSpacing) + (2 * Math.max(highlightItemSvg.margins.top + highlightItemSvg.margins.bottom, highlightItemSvg.margins.left + highlightItemSvg.margins.right)) property int columns: Math.floor(((smallScreen ? 85 : 80)/100) * Math.ceil(width / cellSize)) property bool searching: (searchField.text != "") property var widgetExplorer: null keyEventProxy: searchField backgroundColor: Qt.rgba(0, 0, 0, 0.737) onKeyEscapePressed: { if (searching) { searchField.clear(); } else { root.toggle(); } } onVisibleChanged: { tabBar.activeTab = 0; reset(); if (visible) { preloadAllAppsTimer.restart(); } } onSearchingChanged: { if (!searching) { reset(); } else { filterList.currentIndex = -1; if (tabBar.activeTab == 1) { widgetExplorer.widgetsModel.filterQuery = ""; widgetExplorer.widgetsModel.filterType = ""; } } } function reset() { searchField.clear(); globalFavoritesGrid.currentIndex = -1; systemFavoritesGrid.currentIndex = -1; filterList.currentIndex = 0; funnelModel.sourceModel = rootModel.modelForRow(0); mainGrid.model = (tabBar.activeTab == 0) ? funnelModel : root.widgetExplorer.widgetsModel; mainGrid.currentIndex = -1; filterListScrollArea.focus = true; filterList.model = (tabBar.activeTab == 0) ? rootModel : root.widgetExplorer.filterModel; } function updateWidgetExplorer() { if (tabBar.activeTab == 1 /* Widgets */ || tabBar.hoveredTab == 1) { if (!root.widgetExplorer) { root.widgetExplorer = widgetExplorerComponent.createObject(root, { containment: containmentInterface.screenContainment(plasmoid) }); } } else if (root.widgetExplorer) { root.widgetExplorer.destroy(); root.widgetExplorer = null; } } mainItem: MouseArea { + id: rootItem + anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton LayoutMirroring.enabled: Qt.application.layoutDirection == Qt.RightToLeft LayoutMirroring.childrenInherit: true Connections { target: kicker onReset: { if (!searching) { filterList.applyFilter(); if (tabBar.activeTab == 0) { funnelModel.reset(); } } } onDragSourceChanged: { if (!dragSource) { // FIXME TODO HACK: Reset all views post-DND to work around // mouse grab bug despite QQuickWindow::mouseGrabberItem==0x0. // Needs a more involved hunt through Qt Quick sources later since // it's not happening with near-identical code in the menu repr. rootModel.refresh(); } else if (tabBar.activeTab == 1) { root.toggle(); containmentInterface.ensureMutable(containmentInterface.screenContainment(plasmoid)); kwindowsystem.showingDesktop = true; } } } KWindowSystem { id: kwindowsystem } Component { id: widgetExplorerComponent WidgetExplorer { showSpecialFilters: false } } Connections { target: plasmoid onUserConfiguringChanged: { if (plasmoid.userConfiguring) { root.hide() } } } PlasmaComponents.Menu { id: contextMenu PlasmaComponents.MenuItem { action: plasmoid.action("configure") } } PlasmaExtras.Heading { id: dummyHeading visible: false width: 0 level: 1 } TextMetrics { id: headingMetrics font: dummyHeading.font } Kicker.FunnelModel { id: funnelModel onSourceModelChanged: { if (mainColumn.visible) { mainGrid.currentIndex = -1; mainGrid.forceLayout(); } } } Timer { id: preloadAllAppsTimer property bool done: false interval: 1000 repeat: false onTriggered: { if (done || searching) { return; } for (var i = 0; i < rootModel.count; ++i) { var model = rootModel.modelForRow(i); if (model.description == "KICKER_ALL_MODEL") { allAppsGrid.model = model; done = true; break; } } } function defer() { if (running && !done) { restart(); } } } Kicker.ContainmentInterface { id: containmentInterface } DashboardTabBar { id: tabBar y: 0 anchors.horizontalCenter: parent.horizontalCenter visible: (plasmoid.immutability !== PlasmaCore.Types.SystemImmutable) onActiveTabChanged: { updateWidgetExplorer(); reset(); } onHoveredTabChanged: updateWidgetExplorer() Keys.onDownPressed: { mainColumn.tryActivate(0, 0); } } PlasmaComponents.TextField { id: searchField width: 0 height: 0 visible: false onTextChanged: { if (tabBar.activeTab == 0) { runnerModel.query = searchField.text; } else { widgetExplorer.widgetsModel.searchTerm = searchField.text; } } function clear() { text = ""; } } PlasmaExtras.Heading { id: searchHeading anchors { horizontalCenter: parent.horizontalCenter } y: (middleRow.anchors.topMargin / 2) - (smallScreen ? (height/10) : 0) font.pointSize: dummyHeading.font.pointSize * 1.5 elide: Text.ElideRight wrapMode: Text.NoWrap opacity: 1.0 color: "white" level: 1 text: searching ? i18n("Searching for '%1'", searchField.text) : i18n("Type to search.") } PlasmaComponents.ToolButton { id: cancelSearchButton anchors { left: searchHeading.right leftMargin: units.largeSpacing verticalCenter: searchHeading.verticalCenter } width: units.iconSizes.large height: width visible: (searchField.text != "") iconName: "dialog-close" flat: false onClicked: searchField.clear(); } Row { id: middleRow anchors { top: parent.top topMargin: units.gridUnit * (smallScreen ? 8 : 10) bottom: parent.bottom bottomMargin: (units.gridUnit * 2) horizontalCenter: parent.horizontalCenter } width: (root.columns * cellSize) + (2 * spacing) spacing: units.gridUnit * 2 Item { id: favoritesColumn anchors { top: parent.top bottom: parent.bottom } width: (columns * cellSize) + units.gridUnit property int columns: 3 PlasmaExtras.Heading { id: favoritesColumnLabel enabled: (tabBar.activeTab == 0) anchors { top: parent.top } x: units.smallSpacing width: parent.width - x elide: Text.ElideRight wrapMode: Text.NoWrap color: "white" level: 1 text: i18n("Favorites") opacity: (enabled ? 1.0 : 0.3) Behavior on opacity { SmoothedAnimation { duration: units.longDuration; velocity: 0.01 } } } PlasmaCore.SvgItem { id: favoritesColumnLabelUnderline enabled: (tabBar.activeTab == 0) anchors { top: favoritesColumnLabel.bottom } width: parent.width - units.gridUnit height: lineSvg.horLineHeight svg: lineSvg elementId: "horizontal-line" opacity: (enabled ? 1.0 : 0.3) Behavior on opacity { SmoothedAnimation { duration: units.longDuration; velocity: 0.01 } } } ItemGridView { id: globalFavoritesGrid enabled: (tabBar.activeTab == 0) anchors { top: favoritesColumnLabelUnderline.bottom topMargin: units.largeSpacing } property int rows: (Math.floor((parent.height - favoritesColumnLabel.height - favoritesColumnLabelUnderline.height - units.largeSpacing) / cellSize) - systemFavoritesGrid.rows) width: parent.width height: rows * cellSize cellWidth: cellSize cellHeight: cellSize iconSize: root.iconSize model: globalFavorites dropEnabled: true usesPlasmaTheme: false opacity: (enabled ? 1.0 : 0.3) Behavior on opacity { SmoothedAnimation { duration: units.longDuration; velocity: 0.01 } } onCurrentIndexChanged: { preloadAllAppsTimer.defer(); } onKeyNavRight: { mainColumn.tryActivate(currentRow(), 0); } onKeyNavDown: { systemFavoritesGrid.tryActivate(0, currentCol()); } Binding { target: globalFavorites property: "iconSize" value: root.iconSize } } ItemGridView { id: systemFavoritesGrid anchors { top: globalFavoritesGrid.bottom } property int rows: Math.ceil(count / Math.floor(width / cellSize)) width: parent.width height: rows * cellSize cellWidth: cellSize cellHeight: cellSize iconSize: root.iconSize model: systemFavorites dropEnabled: true usesPlasmaTheme: true onCurrentIndexChanged: { preloadAllAppsTimer.defer(); } onKeyNavRight: { mainColumn.tryActivate(globalFavoritesGrid.rows + currentRow(), 0); } onKeyNavUp: { globalFavoritesGrid.tryActivate(globalFavoritesGrid.rows - 1, currentCol()); } } } Item { id: mainColumn anchors.top: parent.top width: (columns * cellSize) + units.gridUnit height: Math.floor(parent.height / cellSize) * cellSize + mainGridContainer.headerHeight property int columns: root.columns - favoritesColumn.columns - filterListColumn.columns property Item visibleGrid: mainGrid function tryActivate(row, col) { if (visibleGrid) { visibleGrid.tryActivate(row, col); } } Item { id: mainGridContainer anchors.fill: parent z: (opacity == 1.0) ? 1 : 0 enabled: (opacity == 1.0) ? 1 : 0 property int headerHeight: mainColumnLabel.height + mainColumnLabelUnderline.height + units.largeSpacing opacity: { if (tabBar.activeTab == 0 && searching) { return 0.0; } if (filterList.allApps) { return 0.0; } return 1.0; } onOpacityChanged: { if (opacity == 1.0) { mainColumn.visibleGrid = mainGrid; } } PlasmaExtras.Heading { id: mainColumnLabel anchors { top: parent.top } x: units.smallSpacing width: parent.width - x elide: Text.ElideRight wrapMode: Text.NoWrap opacity: 1.0 color: "white" level: 1 text: (tabBar.activeTab == 0) ? funnelModel.description : i18n("Widgets") } PlasmaCore.SvgItem { id: mainColumnLabelUnderline visible: mainGrid.count anchors { top: mainColumnLabel.bottom } width: parent.width - units.gridUnit height: lineSvg.horLineHeight svg: lineSvg elementId: "horizontal-line" } ItemGridView { id: mainGrid anchors { top: mainColumnLabelUnderline.bottom topMargin: units.largeSpacing } width: parent.width height: systemFavoritesGrid.y + systemFavoritesGrid.height - mainGridContainer.headerHeight cellWidth: (tabBar.activeTab == 0 ? cellSize : cellSize * 2) cellHeight: cellWidth iconSize: (tabBar.activeTab == 0 ? root.iconSize : cellWidth - (units.largeSpacing * 2)) model: funnelModel onCurrentIndexChanged: { preloadAllAppsTimer.defer(); } onKeyNavLeft: { if (tabBar.activeTab == 0) { var row = currentRow(); var target = row + 1 > globalFavoritesGrid.rows ? systemFavoritesGrid : globalFavoritesGrid; var targetRow = row + 1 > globalFavoritesGrid.rows ? row - globalFavoritesGrid.rows : row; target.tryActivate(targetRow, favoritesColumn.columns - 1); } } onKeyNavRight: { filterListScrollArea.focus = true; } onKeyNavUp: { if (tabBar.visible) { tabBar.focus = true; } } onItemActivated: { if (tabBar.activeTab == 1) { containmentInterface.ensureMutable(containmentInterface.screenContainment(plasmoid)); root.widgetExplorer.addApplet(currentItem.m.pluginName); root.toggle(); kwindowsystem.showingDesktop = true; } } } } ItemMultiGridView { id: allAppsGrid anchors { top: parent.top } z: (opacity == 1.0) ? 1 : 0 width: parent.width height: systemFavoritesGrid.y + systemFavoritesGrid.height enabled: (opacity == 1.0) ? 1 : 0 opacity: filterList.allApps ? 1.0 : 0.0 onOpacityChanged: { if (opacity == 1.0) { allAppsGrid.flickableItem.contentY = 0; mainColumn.visibleGrid = allAppsGrid; } } onKeyNavLeft: { var row = 0; for (var i = 0; i < subGridIndex; i++) { row += subGridAt(i).lastRow() + 2; // Header counts as one. } row += subGridAt(subGridIndex).currentRow(); var target = row + 1 > globalFavoritesGrid.rows ? systemFavoritesGrid : globalFavoritesGrid; var targetRow = row + 1 > globalFavoritesGrid.rows ? row - globalFavoritesGrid.rows : row; target.tryActivate(targetRow, favoritesColumn.columns - 1); } onKeyNavRight: { filterListScrollArea.focus = true; } } ItemMultiGridView { id: runnerGrid anchors { top: parent.top } z: (opacity == 1.0) ? 1 : 0 width: parent.width height: systemFavoritesGrid.y + systemFavoritesGrid.height enabled: (opacity == 1.0) ? 1 : 0 model: runnerModel grabFocus: true opacity: (tabBar.activeTab == 0 && searching) ? 1.0 : 0.0 onOpacityChanged: { if (opacity == 1.0) { mainColumn.visibleGrid = runnerGrid; } } onKeyNavLeft: { var row = 0; for (var i = 0; i < subGridIndex; i++) { row += subGridAt(i).lastRow() + 2; // Header counts as one. } row += subGridAt(subGridIndex).currentRow(); var target = row + 1 > globalFavoritesGrid.rows ? systemFavoritesGrid : globalFavoritesGrid; var targetRow = row + 1 > globalFavoritesGrid.rows ? row - globalFavoritesGrid.rows : row; target.tryActivate(targetRow, favoritesColumn.columns - 1); } } } Item { id: filterListColumn anchors { top: parent.top topMargin: mainColumnLabelUnderline.y + mainColumnLabelUnderline.height + units.largeSpacing bottom: parent.bottom } width: columns * cellSize property int columns: 3 PlasmaExtras.ScrollArea { id: filterListScrollArea x: root.visible ? 0 : units.gridUnit Behavior on x { SmoothedAnimation { duration: units.longDuration; velocity: 0.01 } } width: parent.width height: mainGrid.height enabled: !searching property alias currentIndex: filterList.currentIndex opacity: root.visible ? (searching ? 0.30 : 1.0) : 0.3 Behavior on opacity { SmoothedAnimation { duration: units.longDuration; velocity: 0.01 } } verticalScrollBarPolicy: (opacity == 1.0) ? Qt.ScrollBarAsNeeded : Qt.ScrollBarAlwaysOff onEnabledChanged: { if (!enabled) { filterList.currentIndex = -1; } } onCurrentIndexChanged: { focus = (currentIndex != -1); } ListView { id: filterList focus: true property bool allApps: false property int eligibleWidth: width property int hItemMargins: Math.max(highlightItemSvg.margins.left + highlightItemSvg.margins.right, listItemSvg.margins.left + listItemSvg.margins.right) model: rootModel boundsBehavior: Flickable.StopAtBounds snapMode: ListView.SnapToItem spacing: 0 keyNavigationWraps: true delegate: MouseArea { id: item signal actionTriggered(string actionId, variant actionArgument) signal aboutToShowActionMenu(variant actionMenu) property var m: model property int textWidth: label.contentWidth property int mouseCol property bool hasActionList: ((model.favoriteId != null) || (("hasActionList" in model) && (model.hasActionList == true))) property Item menu: actionMenu width: parent.width height: Math.ceil((label.paintedHeight + Math.max(highlightItemSvg.margins.top + highlightItemSvg.margins.bottom, listItemSvg.margins.top + listItemSvg.margins.bottom)) / 2) * 2 Accessible.role: Accessible.MenuItem Accessible.name: model.display acceptedButtons: Qt.LeftButton | Qt.RightButton hoverEnabled: true onContainsMouseChanged: { if (!containsMouse) { updateCurrentItemTimer.stop(); } } onPositionChanged: { // Lazy menu implementation. mouseCol = mouse.x; if (justOpenedTimer.running || ListView.view.currentIndex == 0 || index == ListView.view.currentIndex) { updateCurrentItem(); } else if ((index == ListView.view.currentIndex - 1) && mouse.y < (height - 6) || (index == ListView.view.currentIndex + 1) && mouse.y > 5) { if (mouse.x > ListView.view.eligibleWidth - 5) { updateCurrentItem(); } } else if (mouse.x > ListView.view.eligibleWidth) { updateCurrentItem(); } updateCurrentItemTimer.restart(); } onPressed: { if (mouse.buttons & Qt.RightButton) { if (hasActionList) { openActionMenu(item, mouse.x, mouse.y); } } } onClicked: { if (mouse.button == Qt.LeftButton) { updateCurrentItem(); } } onAboutToShowActionMenu: { var actionList = hasActionList ? model.actionList : []; Tools.fillActionMenu(actionMenu, actionList, ListView.view.model.favoritesModel, model.favoriteId); } onActionTriggered: { Tools.triggerAction(ListView.view.model, model.index, actionId, actionArgument); } function openActionMenu(visualParent, x, y) { aboutToShowActionMenu(actionMenu); actionMenu.visualParent = visualParent; actionMenu.open(x, y); } function updateCurrentItem() { ListView.view.currentIndex = index; ListView.view.eligibleWidth = Math.min(width, mouseCol); } ActionMenu { id: actionMenu onActionClicked: { actionTriggered(actionId, actionArgument); } } Timer { id: updateCurrentItemTimer interval: 50 repeat: false onTriggered: parent.updateCurrentItem() } PlasmaExtras.Heading { id: label anchors { fill: parent leftMargin: highlightItemSvg.margins.left rightMargin: highlightItemSvg.margins.right } elide: Text.ElideRight wrapMode: Text.NoWrap opacity: 1.0 color: "white" level: 1 text: model.display } } highlight: PlasmaComponents.Highlight { anchors { top: filterList.currentItem ? filterList.currentItem.top : undefined left: filterList.currentItem ? filterList.currentItem.left : undefined bottom: filterList.currentItem ? filterList.currentItem.bottom : undefined } opacity: filterListScrollArea.focus ? 1.0 : 0.7 width: (highlightItemSvg.margins.left + filterList.currentItem.textWidth + highlightItemSvg.margins.right + units.smallSpacing) visible: filterList.currentItem } highlightFollowsCurrentItem: false highlightMoveDuration: 0 highlightResizeDuration: 0 onCurrentIndexChanged: applyFilter() onCountChanged: { var width = 0; for (var i = 0; i < rootModel.count; ++i) { headingMetrics.text = rootModel.labelForRow(i); if (headingMetrics.width > width) { width = headingMetrics.width; } } filterListColumn.columns = Math.ceil(width / cellSize); filterListScrollArea.width = width + hItemMargins + (units.gridUnit * 2); } function applyFilter() { if (!searching && currentIndex >= 0) { if (tabBar.activeTab == 1) { root.widgetExplorer.widgetsModel.filterQuery = currentItem.m.filterData; root.widgetExplorer.widgetsModel.filterType = currentItem.m.filterType; allApps = false; funnelModel.sourceModel = model; return; } if (preloadAllAppsTimer.running) { preloadAllAppsTimer.stop(); } var model = rootModel.modelForRow(currentIndex); if (model.description == "KICKER_ALL_MODEL") { allAppsGrid.model = model; allApps = true; funnelModel.sourceModel = null; preloadAllAppsTimer.done = true; } else { funnelModel.sourceModel = model; allApps = false; } } else { funnelModel.sourceModel = null; allApps = false; } } Keys.onPressed: { if (event.key == Qt.Key_Left) { event.accepted = true; var currentRow = Math.max(0, Math.ceil(currentItem.y / mainGrid.cellHeight) - 1); mainColumn.tryActivate(currentRow, mainColumn.columns - 1); } } } } } } onPressed: { if (mouse.button == Qt.RightButton) { contextMenu.open(mouse.x, mouse.y); } } onClicked: { if (mouse.button == Qt.LeftButton) { root.toggle(); } } } } diff --git a/applets/kicker/package/contents/ui/ItemGridDelegate.qml b/applets/kicker/package/contents/ui/ItemGridDelegate.qml index 266e18ba9..df0b45ea2 100644 --- a/applets/kicker/package/contents/ui/ItemGridDelegate.qml +++ b/applets/kicker/package/contents/ui/ItemGridDelegate.qml @@ -1,174 +1,129 @@ /*************************************************************************** * Copyright (C) 2015 by Eike Hein * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ import QtQuick 2.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import "../code/tools.js" as Tools -MouseArea { +Item { id: item width: GridView.view.cellWidth height: width - signal actionTriggered(string actionId, variant actionArgument) - signal aboutToShowActionMenu(variant actionMenu) - property bool showLabel: true property int itemIndex: model.index property string favoriteId: model.favoriteId != undefined ? model.favoriteId : "" property url url: model.url != undefined ? model.url : "" property variant icon: model.decoration != undefined ? model.decoration : "" property var m: model - property bool pressed: false property bool hasActionList: ((model.favoriteId != null) || (("hasActionList" in model) && (model.hasActionList == true))) - property Item view: GridView.view - property Item menu: actionMenu Accessible.role: Accessible.MenuItem Accessible.name: model.display - acceptedButtons: root.visible ? (Qt.LeftButton | Qt.RightButton) : Qt.NoButton - - onPressed: { - if (mouse.buttons & Qt.RightButton) { - if (hasActionList) { - openActionMenu(item, mouse.x, mouse.y); - } - } else { - pressed = true; - } - } - - onReleased: { - if (pressed && GridView.view.currentItem == item) { - hoverArea.hoverEnabled = false; - - if ("trigger" in GridView.view.model) { - GridView.view.model.trigger(index, "", null); - root.toggle(); - } - - itemGrid.itemActivated(index, "", null); - } - - pressed = false; - } - - onAboutToShowActionMenu: { + function openActionMenu(x, y) { var actionList = hasActionList ? model.actionList : []; Tools.fillActionMenu(actionMenu, actionList, GridView.view.model.favoritesModel, model.favoriteId); + actionMenu.visualParent = item; + actionMenu.open(x, y); } - onActionTriggered: { + function actionTriggered() { var close = Tools.triggerAction(GridView.view.model, model.index, actionId, actionArgument); if (close) { root.toggle(); } } - function openActionMenu(visualParent, x, y) { - aboutToShowActionMenu(actionMenu); - actionMenu.visualParent = visualParent; - actionMenu.open(x, y); - } - - ActionMenu { - id: actionMenu - - onActionClicked: { - actionTriggered(actionId, actionArgument); - } - } - PlasmaCore.IconItem { id: icon y: showLabel ? (2 * highlightItemSvg.margins.top) : undefined anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: showLabel ? undefined : parent.verticalCenter width: iconSize height: width colorGroup: PlasmaCore.Theme.ComplementaryColorGroup animated: false - usesPlasmaTheme: view.usesPlasmaTheme + usesPlasmaTheme: GridView.view.usesPlasmaTheme source: model.decoration } PlasmaComponents.Label { id: label visible: showLabel anchors { top: icon.bottom topMargin: units.smallSpacing left: parent.left leftMargin: highlightItemSvg.margins.left right: parent.right rightMargin: highlightItemSvg.margins.right } horizontalAlignment: Text.AlignHCenter elide: Text.ElideRight wrapMode: Text.NoWrap color: "white" // FIXME TODO: Respect theming? text: ("name" in model ? model.name : model.display) } PlasmaCore.ToolTipArea { id: toolTip property string text: model.display anchors.fill: parent active: root.visible && label.truncated mainItem: toolTipDelegate } Keys.onPressed: { if (event.key == Qt.Key_Menu && hasActionList) { event.accepted = true; openActionMenu(item); } else if ((event.key == Qt.Key_Enter || event.key == Qt.Key_Return)) { event.accepted = true; if ("trigger" in GridView.view.model) { GridView.view.model.trigger(index, "", null); root.toggle(); } itemGrid.itemActivated(index, "", null); } } } diff --git a/applets/kicker/package/contents/ui/ItemGridView.qml b/applets/kicker/package/contents/ui/ItemGridView.qml index 8faf242b4..a8992ff58 100644 --- a/applets/kicker/package/contents/ui/ItemGridView.qml +++ b/applets/kicker/package/contents/ui/ItemGridView.qml @@ -1,413 +1,450 @@ /*************************************************************************** * Copyright (C) 2015 by Eike Hein * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ import QtQuick 2.4 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.extras 2.0 as PlasmaExtras import org.kde.kquickcontrolsaddons 2.0 import org.kde.draganddrop 2.0 FocusScope { id: itemGrid signal keyNavLeft signal keyNavRight signal keyNavUp signal keyNavDown signal itemActivated(int index, string actionId, string argument) property bool dragEnabled: true property bool dropEnabled: false property bool showLabels: true property alias usesPlasmaTheme: gridView.usesPlasmaTheme - property int pressX: -1 - property int pressY: -1 - property alias currentIndex: gridView.currentIndex property alias currentItem: gridView.currentItem property alias contentItem: gridView.contentItem property alias count: gridView.count property alias model: gridView.model property alias cellWidth: gridView.cellWidth property alias cellHeight: gridView.cellHeight property alias iconSize: gridView.iconSize property alias horizontalScrollBarPolicy: scrollArea.horizontalScrollBarPolicy property alias verticalScrollBarPolicy: scrollArea.verticalScrollBarPolicy onDropEnabledChanged: { if (!dropEnabled && "dropPlaceHolderIndex" in model) { model.dropPlaceHolderIndex = -1; } } onFocusChanged: { if (!focus && !root.keyEventProxy.activeFocus) { currentIndex = -1; } } function currentRow() { if (currentIndex == -1) { return -1; } return Math.floor(currentIndex / Math.floor(width / cellWidth)); } function currentCol() { if (currentIndex == -1) { return -1; } return currentIndex - (currentRow() * Math.floor(width / cellWidth)); } function lastRow() { var columns = Math.floor(width / cellWidth); return Math.ceil(count / columns) - 1; } function tryActivate(row, col) { if (count) { var columns = Math.floor(width / cellWidth); var rows = Math.ceil(count / columns); row = Math.min(row, rows - 1); col = Math.min(col, columns - 1); currentIndex = Math.min(row ? ((Math.max(1, row) * columns) + col) : col, count - 1); focus = true; } } function forceLayout() { gridView.forceLayout(); } + ActionMenu { + id: actionMenu + + onActionClicked: { + visualParent.actionTriggered(actionId, actionArgument); + } + } + DropArea { id: dropArea anchors.fill: parent onDragMove: { if (!dropEnabled || gridView.animating || !kicker.dragSource) { return; } var x = Math.max(0, event.x - (width % cellWidth)); var cPos = mapToItem(gridView.contentItem, x, event.y); var item = gridView.itemAt(cPos.x, cPos.y); if (item) { if (kicker.dragSource.parent == gridView.contentItem) { if (item != kicker.dragSource) { item.GridView.view.model.moveRow(dragSource.itemIndex, item.itemIndex); } } else if (kicker.dragSource.view.model.favoritesModel == model && !model.isFavorite(kicker.dragSource.favoriteId)) { var hasPlaceholder = (model.dropPlaceholderIndex != -1); model.dropPlaceholderIndex = item.itemIndex; if (!hasPlaceholder) { gridView.currentIndex = (item.itemIndex - 1); } } } else if (kicker.dragSource.parent != gridView.contentItem && kicker.dragSource.view.model.favoritesModel == model && !model.isFavorite(kicker.dragSource.favoriteId)) { var hasPlaceholder = (model.dropPlaceholderIndex != -1); model.dropPlaceholderIndex = hasPlaceholder ? model.count - 1 : model.count; if (!hasPlaceholder) { gridView.currentIndex = (model.count - 1); } } else { model.dropPlaceholderIndex = -1; gridView.currentIndex = -1; } } onDragLeave: { if ("dropPlaceholderIndex" in model) { model.dropPlaceholderIndex = -1; gridView.currentIndex = -1; } } onDrop: { if (kicker.dragSource && kicker.dragSource.parent != gridView.contentItem && kicker.dragSource.view.model.favoritesModel == model) { model.addFavorite(kicker.dragSource.favoriteId, model.dropPlaceholderIndex); gridView.currentIndex = -1; } } - MouseEventListener { - id: hoverArea - - anchors.fill: parent + Timer { + id: resetAnimationDurationTimer - hoverEnabled: true + interval: 120 + repeat: false - onPressed: { - pressX = mouse.x; - pressY = mouse.y; + onTriggered: { + gridView.animationDuration = interval - 20; } + } - onReleased: { - pressX = -1; - pressY = -1; - } + PlasmaExtras.ScrollArea { + id: scrollArea - onClicked: { - var cPos = mapToItem(gridView.contentItem, mouse.x, mouse.y); - var item = gridView.itemAt(cPos.x, cPos.y); + anchors.fill: parent - if (!item) { - root.toggle(); - } - } + focus: true - onPositionChanged: { - var cPos = mapToItem(gridView.contentItem, mouse.x, mouse.y); - var item = gridView.itemAt(cPos.x, cPos.y); + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff - if (!item) { - gridView.currentIndex = -1; - } else { - gridView.currentIndex = item.itemIndex; - itemGrid.focus = (currentIndex != -1) + GridView { + id: gridView - if (dragEnabled && pressX != -1 && dragHelper.isDrag(pressX, pressY, mouse.x, mouse.y)) { - if ("pluginName" in item.m) { - dragHelper.startDrag(kicker, item.url, item.icon, - "text/x-plasmoidservicename", item.m.pluginName); - } else { - dragHelper.startDrag(kicker, item.url, item.icon); - } + property bool usesPlasmaTheme: false - kicker.dragSource = item; + property int iconSize: units.iconSizes.huge - pressX = -1; - pressY = -1; - } - } - } + property bool animating: false + property int animationDuration: dropEnabled ? resetAnimationDurationTimer.interval : 0 - onContainsMouseChanged: { - if (!containsMouse) { - gridView.currentIndex = -1; - pressX = -1; - pressY = -1; - } - } + focus: true - Connections { - target: root + currentIndex: -1 - onVisibleChanged: { - if (root.visible) { - hoverArea.hoverEnabled = true; - } - } - } + move: Transition { + enabled: itemGrid.dropEnabled - Timer { - id: resetAnimationDurationTimer + SequentialAnimation { + PropertyAction { target: gridView; property: "animating"; value: true } - interval: 120 - repeat: false + NumberAnimation { + duration: gridView.animationDuration + properties: "x, y" + easing.type: Easing.OutQuad + } - onTriggered: { - gridView.animationDuration = interval - 20; + PropertyAction { target: gridView; property: "animating"; value: false } + } } - } - PlasmaExtras.ScrollArea { - id: scrollArea + moveDisplaced: Transition { + enabled: itemGrid.dropEnabled - anchors.fill: parent + SequentialAnimation { + PropertyAction { target: gridView; property: "animating"; value: true } - focus: true + NumberAnimation { + duration: gridView.animationDuration + properties: "x, y" + easing.type: Easing.OutQuad + } - horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff + PropertyAction { target: gridView; property: "animating"; value: false } + } + } - GridView { - id: gridView + keyNavigationWraps: false + boundsBehavior: Flickable.StopAtBounds - property bool usesPlasmaTheme: false + delegate: ItemGridDelegate { + showLabel: showLabels + } + + highlight: Item { + property bool isDropPlaceHolder: "dropPlaceholderIndex" in model && currentIndex == model.dropPlaceholderIndex - property int iconSize: units.iconSizes.huge + PlasmaComponents.Highlight { + visible: gridView.currentItem && !isDropPlaceHolder - property bool animating: false - property int animationDuration: dropEnabled ? resetAnimationDurationTimer.interval : 0 + anchors.fill: parent + } - focus: true + PlasmaCore.FrameSvgItem { + visible: gridView.currentItem && isDropPlaceHolder - currentIndex: -1 + anchors.fill: parent - move: Transition { - enabled: itemGrid.dropEnabled + imagePath: "widgets/viewitem" + prefix: "selected" - SequentialAnimation { - PropertyAction { target: gridView; property: "animating"; value: true } + opacity: 0.5 - NumberAnimation { - duration: gridView.animationDuration - properties: "x, y" - easing.type: Easing.OutQuad + PlasmaCore.IconItem { + anchors { + right: parent.right + rightMargin: parent.margins.right + bottom: parent.bottom + bottomMargin: parent.margins.bottom } - PropertyAction { target: gridView; property: "animating"; value: false } + width: units.iconSizes.smallMedium + height: width + + source: "list-add" + active: false } } + } - moveDisplaced: Transition { - enabled: itemGrid.dropEnabled + highlightFollowsCurrentItem: true + highlightMoveDuration: 0 - SequentialAnimation { - PropertyAction { target: gridView; property: "animating"; value: true } + onCurrentIndexChanged: { + if (currentIndex != -1) { + focus = true; + } + } - NumberAnimation { - duration: gridView.animationDuration - properties: "x, y" - easing.type: Easing.OutQuad - } + onCountChanged: { + animationDuration = 0; + resetAnimationDurationTimer.start(); + } - PropertyAction { target: gridView; property: "animating"; value: false } - } + onModelChanged: { + currentIndex = -1; + } + + Keys.onLeftPressed: { + if (currentCol() != 0) { + event.accepted = true; + moveCurrentIndexLeft(); + } else { + itemGrid.keyNavLeft(); } + } - keyNavigationWraps: false - boundsBehavior: Flickable.StopAtBounds + Keys.onRightPressed: { + var columns = Math.floor(width / cellWidth); - delegate: ItemGridDelegate { - showLabel: showLabels + if (currentCol() != columns - 1 && currentIndex != count -1) { + event.accepted = true; + moveCurrentIndexRight(); + } else { + itemGrid.keyNavRight(); } + } - highlight: Item { - property bool isDropPlaceHolder: "dropPlaceholderIndex" in model && currentIndex == model.dropPlaceholderIndex + Keys.onUpPressed: { + if (currentRow() != 0) { + event.accepted = true; + moveCurrentIndexUp(); + positionViewAtIndex(currentIndex, GridView.Contain); + } else { + itemGrid.keyNavUp(); + } + } - PlasmaComponents.Highlight { - visible: gridView.currentItem && !isDropPlaceHolder + Keys.onDownPressed: { + if (currentRow() < itemGrid.lastRow()) { + // Fix moveCurrentIndexDown()'s lack of proper spatial nav down + // into partial columns. + event.accepted = true; + var columns = Math.floor(width / cellWidth); + var newIndex = currentIndex + columns; + currentIndex = Math.min(newIndex, count - 1); + positionViewAtIndex(currentIndex, GridView.Contain); + } else { + itemGrid.keyNavDown(); + } + } + } + } - anchors.fill: parent - } + MouseArea { + id: hoverArea - PlasmaCore.FrameSvgItem { - visible: gridView.currentItem && isDropPlaceHolder + anchors.fill: parent - anchors.fill: parent + property int pressX: -1 + property int pressY: -1 + property int lastX: -1 + property int lastY: -1 + property Item pressedItem: null - imagePath: "widgets/viewitem" - prefix: "selected" + acceptedButtons: Qt.LeftButton | Qt.RightButton - opacity: 0.5 + hoverEnabled: true - PlasmaCore.IconItem { - anchors { - right: parent.right - rightMargin: parent.margins.right - bottom: parent.bottom - bottomMargin: parent.margins.bottom - } + onPressed: { + pressX = mouse.x; + pressY = mouse.y; - width: units.iconSizes.smallMedium - height: width + mouse.accepted = true; - source: "list-add" - active: false - } + if (mouse.button == Qt.RightButton) { + if (gridView.currentItem) { + if (gridView.currentItem.hasActionList) { + var mapped = mapToItem(gridView.currentItem, mouse.x, mouse.y); + gridView.currentItem.openActionMenu(mapped.x, mapped.y); } + } else { + var mapped = mapToItem(rootItem, mouse.x, mouse.y); + contextMenu.open(mapped.x, mapped.y); } + } else { + pressedItem = gridView.currentItem; + } + } - highlightFollowsCurrentItem: true - highlightMoveDuration: 0 + onReleased: { + mouse.accepted = true; - onCurrentIndexChanged: { - if (currentIndex != -1) { - focus = true; - } + if (gridView.currentItem && gridView.currentItem == pressedItem) { + if ("trigger" in gridView.model) { + gridView.model.trigger(pressedItem.itemIndex, "", null); + root.toggle(); } - onCountChanged: { - animationDuration = 0; - resetAnimationDurationTimer.start(); - } + itemGrid.itemActivated(pressedItem.itemIndex, "", null); + } else if (!pressedItem && mouse.button == Qt.LeftButton) { + root.toggle(); + } - onModelChanged: { - currentIndex = -1; - } + pressX = -1; + pressY = -1; + pressedItem = null; + } - Keys.onLeftPressed: { - if (currentCol() != 0) { - event.accepted = true; - moveCurrentIndexLeft(); - } else { - itemGrid.keyNavLeft(); - } - } + onPositionChanged: { + // Prevent hover event synthesis in QQuickWindow interfering + // with keyboard navigation by ignoring repeated events with + // identical coordinates. As the work done here would be re- + // dundant in any case, these are safe to ignore. + if (mouse.x == lastX && mouse.y == lastY) { + return; + } - Keys.onRightPressed: { - var columns = Math.floor(width / cellWidth); + lastX = mouse.x; + lastY = mouse.y; - if (currentCol() != columns - 1 && currentIndex != count -1) { - event.accepted = true; - moveCurrentIndexRight(); - } else { - itemGrid.keyNavRight(); - } - } + var cPos = mapToItem(gridView.contentItem, mouse.x, mouse.y); + var item = gridView.itemAt(cPos.x, cPos.y); - Keys.onUpPressed: { - if (currentRow() != 0) { - event.accepted = true; - moveCurrentIndexUp(); - positionViewAtIndex(currentIndex, GridView.Contain); - } else { - itemGrid.keyNavUp(); - } - } + if (!item) { + gridView.currentIndex = -1; + pressedItem = null; + } else { + gridView.currentIndex = item.itemIndex; + itemGrid.focus = (currentIndex != -1) - Keys.onDownPressed: { - if (currentRow() < itemGrid.lastRow()) { - // Fix moveCurrentIndexDown()'s lack of proper spatial nav down - // into partial columns. - event.accepted = true; - var columns = Math.floor(width / cellWidth); - var newIndex = currentIndex + columns; - currentIndex = Math.min(newIndex, count - 1); - positionViewAtIndex(currentIndex, GridView.Contain); + if (dragEnabled && pressX != -1 && dragHelper.isDrag(pressX, pressY, mouse.x, mouse.y)) { + if ("pluginName" in item.m) { + dragHelper.startDrag(kicker, item.url, item.icon, + "text/x-plasmoidservicename", item.m.pluginName); } else { - itemGrid.keyNavDown(); + dragHelper.startDrag(kicker, item.url, item.icon); } + + kicker.dragSource = item; + + pressX = -1; + pressY = -1; } } } + + onContainsMouseChanged: { + if (!containsMouse) { + gridView.currentIndex = -1; + pressX = -1; + pressY = -1; + pressedItem = null; + } + } } } }