/* SPDX-FileCopyrightText: 2015 Eike Hein SPDX-License-Identifier: GPL-2.0-or-later */ pragma ComponentBehavior: Bound import QtQuick import org.kde.kirigami as Kirigami import org.kde.ksvg as KSvg import org.kde.plasma.components as PlasmaComponents import org.kde.plasma.private.kicker as Kicker PlasmaComponents.ScrollView { id: itemMultiGrid signal interactionConcluded() anchors { top: parent.top } width: parent.width implicitHeight: itemColumn.implicitHeight signal keyNavLeft(int subGridIndex) signal keyNavRight(int subGridIndex) signal keyNavUp() signal keyNavDown() property int cellSize property int iconSize property alias model: repeater.model property alias count: repeater.count property alias flickableItem: flickable property ItemGridView firstGrid function subGridAt(index) { let subgrid = repeater.itemAt(index) if (subgrid) { return subgrid.itemGrid } } function selectFirstElement() { let foundAny = false for (var i = 0; i < repeater.count; i++) { let grid = subGridAt(i) if (grid && grid.count > 0) { if (!foundAny) { itemMultiGrid.firstGrid = grid grid.currentIndex = 0 foundAny = true } else { grid.currentIndex = -1 } } } } function tryActivate(row, col) { // FIXME TODO: Cleanup messy algo. if (flickable.contentY > 0) { row = 0; } var target = null; var rows = 0; for (var i = 0; i < repeater.count; i++) { var grid = subGridAt(i); if (rows <= row) { target = grid; rows += grid.lastRow() + 2; // Header counts as one. } else { break; } } if (target) { rows -= (target.lastRow() + 2); target.tryActivate(row - rows, col); } } onFocusChanged: { if (!focus) { for (var i = 0; i < repeater.count; i++) { subGridAt(i).focus = false; } } } Flickable { id: flickable flickableDirection: Flickable.VerticalFlick contentHeight: itemColumn.implicitHeight Column { id: itemColumn width: itemMultiGrid.width - Kirigami.Units.gridUnit Repeater { id: repeater delegate: Item { id: gridDelegate required property int index width: itemColumn.width - Kirigami.Units.gridUnit height: headerHeight + gridView.height + (index == repeater.count - 1 ? 0 : footerHeight) readonly property int headerHeight: (gridViewLabel.height + gridViewLabelUnderline.height + Kirigami.Units.gridUnit) readonly property int footerHeight: (Math.ceil(headerHeight / itemMultiGrid.cellSize) * itemMultiGrid.cellSize) - headerHeight property alias itemGrid: gridView visible: gridView.count > 0 Kirigami.Heading { id: gridViewLabel anchors.top: parent.top x: Kirigami.Units.smallSpacing width: parent.width - x height: dummyHeading.height elide: Text.ElideRight wrapMode: Text.NoWrap opacity: 1.0 color: "white" // FIXME TODO: Respect theming? level: 1 text: repeater.model.modelForRow(gridDelegate.index).description textFormat: Text.PlainText } KSvg.SvgItem { id: gridViewLabelUnderline anchors.top: gridViewLabel.bottom width: parent.width - Kirigami.Units.gridUnit imagePath: "widgets/line" elementId: "horizontal-line" } TapHandler { onTapped: itemMultiGrid.interactionConcluded() } ItemGridView { id: gridView anchors { top: gridViewLabelUnderline.bottom topMargin: Kirigami.Units.gridUnit } width: parent.width height: Math.ceil(count / Math.floor(width / itemMultiGrid.cellSize)) * itemMultiGrid.cellSize cellWidth: itemMultiGrid.cellSize cellHeight: itemMultiGrid.cellSize iconSize: itemMultiGrid.iconSize hoverEnabled: itemMultiGrid.hoverEnabled model: repeater.model.modelForRow(gridDelegate.index) onFocusChanged: { if (focus) { itemMultiGrid.focus = true; } } onInteractionConcluded: itemMultiGrid.interactionConcluded() onCountChanged: itemMultiGrid.selectFirstElement() onCurrentItemChanged: { if (!currentItem) { return; } if (gridDelegate.index == 0 && currentRow() === 0) { flickable.contentY = 0; return; } var y = currentItem.y; y = contentItem.mapToItem(flickable.contentItem, 0, y).y; if (y < flickable.contentY) { flickable.contentY = y; } else { y += itemMultiGrid.cellSize; y -= flickable.contentY; y -= itemMultiGrid.height; if (y > 0) { flickable.contentY += y; } } } onKeyNavLeft: { itemMultiGrid.keyNavLeft(gridDelegate.index); currentIndex = -1 } onKeyNavRight: { itemMultiGrid.keyNavRight(gridDelegate.index); currentIndex = -1 } onKeyNavUp: { if (gridDelegate.index > 0) { for (var i = gridDelegate.index - 1; i >= 0; i--) { if (itemMultiGrid.subGridAt(i).count > 0) { itemMultiGrid.subGridAt(i).tryActivate(itemMultiGrid.subGridAt(i).lastRow(), currentCol()); break; } } } else { itemMultiGrid.keyNavUp(); } currentIndex = -1 } onKeyNavDown: { if (gridDelegate.index < repeater.count - 1) { for (let i = gridDelegate.index + 1; i < repeater.count; i++) { if (itemMultiGrid.subGridAt(i).count > 0) { itemMultiGrid.subGridAt(i).tryActivate(0, currentCol()); break; } } } else { itemMultiGrid.keyNavDown(); } currentIndex = -1 } Keys.onBacktabPressed: event => { currentIndex = -1 event.accepted = false // pass to mainColumn handler } } // HACK: Steal wheel events from the nested grid view and forward them to // the ScrollView's internal WheelArea. Kicker.WheelInterceptor { anchors.fill: gridView z: 1 destination: findWheelArea(itemMultiGrid) } } } } } }