diff --git a/applets/taskmanager/package/contents/ui/GroupDialog.qml b/applets/taskmanager/package/contents/ui/GroupDialog.qml index 0b04545b8..e8f788748 100644 --- a/applets/taskmanager/package/contents/ui/GroupDialog.qml +++ b/applets/taskmanager/package/contents/ui/GroupDialog.qml @@ -1,299 +1,299 @@ /*************************************************************************** * Copyright (C) 2012-2013 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 QtQuick.Window 2.2 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.extras 2.0 as PlasmaExtras import org.kde.draganddrop 2.0 import "code/layout.js" as LayoutManager PlasmaCore.Dialog { id: groupDialog visible: false type: PlasmaCore.Dialog.PopupMenu flags: Qt.WindowStaysOnTopHint hideOnWindowDeactivate: true location: plasmoid.location readonly property int preferredWidth: Screen.width / (3 * Screen.devicePixelRatio) readonly property int preferredHeight: Screen.height / (2 * Screen.devicePixelRatio) readonly property int contentWidth: scrollArea.overflowing ? mainItem.width - (units.smallSpacing * 3) : mainItem.width readonly property TextMetrics textMetrics: TextMetrics {} property alias overflowing: scrollArea.overflowing property alias activeTask: focusActiveTaskTimer.targetIndex property var _oldAppletStatus: PlasmaCore.Types.UnknownStatus function selectTask(task) { if (!task) { return; } task.forceActiveFocus(); scrollArea.ensureItemVisible(task); } mainItem: MouseHandler { id: mouseHandler target: taskList handleWheelEvents: !scrollArea.overflowing Timer { id: focusActiveTaskTimer property var targetIndex: null interval: 0 repeat: false onTriggered: { // Now we can home in on the previously active task // collected in groupDialog.onVisibleChanged. if (targetIndex != null) { for (var i = 0; i < groupRepeater.count; ++i) { var task = groupRepeater.itemAt(i); if (task.modelIndex() == targetIndex) { selectTask(task); return; } } } } } PlasmaExtras.ScrollArea { id: scrollArea anchors.fill: parent readonly property bool overflowing: (viewport.height < contentItem.height) horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff function ensureItemVisible(item) { var itemTop = item.y; var itemBottom = (item.y + item.height); if (itemTop < flickableItem.contentY) { flickableItem.contentY = itemTop; } if ((itemBottom - flickableItem.contentY) > viewport.height) { flickableItem.contentY = Math.abs(viewport.height - itemBottom); } } TaskList { id: taskList width: parent.width add: Transition { // We trigger a null-interval timer in the first add // transition after setting the model so onTriggered // will run after the Flow has positioned items. ScriptAction { script: { if (groupRepeater.aboutToPopulate) { focusActiveTaskTimer.restart(); groupRepeater.aboutToPopulate = false; } } } } onAnimatingChanged: { if (!animating) { - updateSize(); + Qt.callLater(updateSize); } } Repeater { id: groupRepeater property bool aboutToPopulate: false function currentIndex() { for (var i = 0; i < count; ++i) { if (itemAt(i).activeFocus) { return i; } } return -1; } - onItemAdded: updateSize() + onItemAdded: Qt.callLater(updateSize) onItemRemoved: { if (groupDialog.visible && index > 0 && index == count) { - updateSize(); + Qt.callLater(updateSize); } } } } Component.onCompleted: { flickableItem.boundsBehavior = Flickable.StopAtBounds; } } Keys.onUpPressed: { var currentIndex = groupRepeater.currentIndex(); // In doubt focus the last item, so we start at the bottom when user // initially presses up. if (currentIndex === -1) { selectTask(groupRepeater.itemAt(groupRepeater.count - 1)); return; } var previousIndex = currentIndex - 1; if (previousIndex < 0) { previousIndex = groupRepeater.count - 1; } selectTask(groupRepeater.itemAt(previousIndex)); } Keys.onDownPressed: { var currentIndex = groupRepeater.currentIndex(); // In doubt focus the first item, also wrap around. if (currentIndex === -1 || currentIndex + 1 >= groupRepeater.count) { selectTask(groupRepeater.itemAt(0)); return; } selectTask(groupRepeater.itemAt(currentIndex + 1)); } Keys.onEscapePressed: groupDialog.visible = false; } data: [ VisualDataModel { id: groupFilter delegate: Task { visible: true inPopup: true } } ] onVisualParentChanged: { if (visible && visualParent) { attachModel(); } else { visible = false; } } onVisibleChanged: { if (visible && visualParent) { _oldAppletStatus = plasmoid.status; plasmoid.status = PlasmaCore.Types.RequiresAttentionStatus; attachModel(); groupDialog.requestActivate(); mouseHandler.forceActiveFocus(); } else { plasmoid.status = _oldAppletStatus; visualParent = null; groupRepeater.model = undefined; groupFilter.model = undefined; groupFilter.rootIndex = undefined; } } function attachModel() { if (!visualParent) { return; } if (!groupFilter.model) { groupFilter.model = tasksModel; } groupRepeater.aboutToPopulate = true; groupFilter.rootIndex = tasksModel.makeModelIndex(visualParent.itemIndex); if (!groupRepeater.model) { groupRepeater.model = groupFilter; } } function updateSize() { if (!visible) { return; } if (!visualParent) { visible = false; return; } if (!visualParent.childCount) { visible = false; // Setting VisualDataModel.rootIndex drops groupRepeater.count to 0 // before the actual row count. updateSize is therefore invoked twice; // only update size once the repeater count matches the model role. - } else if (!groupRepeater.aboutToPopulate || visualParent.childCount == groupRepeater.count) { + } else if (!groupRepeater.aboutToPopulate && visualParent.childCount == groupRepeater.count) { var task; var maxWidth = 0; var maxHeight = 0; backend.cancelHighlightWindows(); for (var i = 0; i < taskList.children.length - 1; ++i) { task = taskList.children[i]; textMetrics.text = task.labelText; var textWidth = textMetrics.boundingRect.width; if (textWidth > maxWidth) { maxWidth = textWidth; } task.labelTextChanged.connect(updateSize); } maxHeight = groupRepeater.count * (LayoutManager.verticalMargins() + Math.max(theme.mSize(theme.defaultFont).height, units.iconSizes.medium)); maxWidth += LayoutManager.horizontalMargins() + units.iconSizes.medium + 2 * units.smallSpacing; // Add horizontal space for scrollbar if needed. // FIXME TODO HACK: Use actual scrollbar width instead of a good guess. if (maxHeight > preferredHeight) { maxWidth += (units.smallSpacing * 3); } mainItem.height = Math.min(preferredHeight, maxHeight); mainItem.width = Math.min(preferredWidth, (tasks.vertical ? Math.max(maxWidth, tasks.width) : Math.min(maxWidth, tasks.width))); } } } diff --git a/containments/desktop/package/contents/ui/FolderItemDelegate.qml b/containments/desktop/package/contents/ui/FolderItemDelegate.qml index bb6258063..ab1333205 100644 --- a/containments/desktop/package/contents/ui/FolderItemDelegate.qml +++ b/containments/desktop/package/contents/ui/FolderItemDelegate.qml @@ -1,501 +1,501 @@ /*************************************************************************** * Copyright (C) 2014-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.8 import QtGraphicalEffects 1.0 import org.kde.plasma.plasmoid 2.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.kquickcontrolsaddons 2.0 Item { id: main property int index: model.index property string name: model.blank ? "" : model.display property bool blank: model.blank property bool isDir: loader.item ? loader.item.isDir : false property QtObject popupDialog: loader.item ? loader.item.popupDialog : null property Item iconArea: loader.item ? loader.item.iconArea : null property Item label: loader.item ? loader.item.label : null property Item labelArea: loader.item ? loader.item.labelArea : null property Item actionsOverlay: loader.item ? loader.item.actionsOverlay : null property Item hoverArea: loader.item ? loader.item.hoverArea : null property Item frame: loader.item ? loader.item.frame : null property Item toolTip: loader.item ? loader.item.toolTip : null Accessible.name: name Accessible.role: Accessible.Canvas function openPopup() { if (isDir) { loader.item.openPopup(); } } function closePopup() { if (popupDialog) { popupDialog.requestDestroy(); loader.item.popupDialog = null; } } Loader { id: loader // On the desktop we pad our cellSize to avoid a gap at the right/bottom of the screen. // The padding per item is quite small and causes the delegate to be positioned on fractional pixels // leading to blurry rendering. The Loader is offset to account for this. x: -main.x % 1 y: -main.y % 1 width: parent.width height: parent.height visible: status === Loader.Ready active: !model.blank sourceComponent: delegateImplementation asynchronous: true } Component { id: delegateImplementation Item { id: impl anchors.fill: parent property bool blank: model.blank property bool selected: model.blank ? false : model.selected property bool isDir: model.blank ? false : model.isDir property bool hovered: (main.GridView.view.hoveredItem == main) property QtObject popupDialog: null property Item iconArea: icon property Item label: label property Item labelArea: frameLoader.textShadow || label property Item actionsOverlay: actions property Item hoverArea: toolTip property Item frame: frameLoader property Item toolTip: toolTip property Item selectionButton: null property Item popupButton: null onSelectedChanged: { if (selected && !blank) { frameLoader.grabToImage(function(result) { dir.addItemDragImage(positioner.map(index), main.x + frameLoader.x, main.y + frameLoader.y, frameLoader.width, frameLoader.height, result.image); }); } } onHoveredChanged: { if (hovered) { if (plasmoid.configuration.selectionMarkers && Qt.styleHints.singleClickActivation) { selectionButton = selectionButtonComponent.createObject(actions); } if (model.isDir) { if (!main.GridView.view.isRootView || root.containsDrag) { hoverActivateTimer.restart(); } if (plasmoid.configuration.popups && !root.useListViewMode) { popupButton = popupButtonComponent.createObject(actions); } } } else if (!hovered) { if (popupDialog != null) { closePopup(); } if (selectionButton) { selectionButton.destroy(); selectionButton = null; } if (popupButton) { popupButton.destroy(); popupButton = null; } } } function openPopup() { if (folderViewDialogComponent.status == Component.Ready) { impl.popupDialog = folderViewDialogComponent.createObject(impl); impl.popupDialog.visualParent = icon; impl.popupDialog.url = model.linkDestinationUrl; impl.popupDialog.visible = true; } } PlasmaCore.ToolTipArea { id: toolTip active: (plasmoid.configuration.toolTips && popupDialog == null && !model.blank) interactive: false location: root.useListViewMode ? (plasmoid.location == PlasmaCore.Types.LeftEdge ? PlasmaCore.Types.LeftEdge : PlasmaCore.Types.RightEdge) : plasmoid.location onContainsMouseChanged: { if (containsMouse && !model.blank) { toolTip.icon = model.decoration; toolTip.mainText = model.display; if (model.size != undefined) { toolTip.subText = model.type + "\n" + model.size; } else { toolTip.subText = model.type; } main.GridView.view.hoveredItem = main; } } states: [ State { // icon view when: !root.useListViewMode AnchorChanges { target: toolTip anchors.horizontalCenter: parent.horizontalCenter } PropertyChanges { target: toolTip y: frameLoader.y + icon.y width: Math.max(icon.paintedWidth, label.paintedWidth) height: (label.y + label.paintedHeight) - y } }, State { // list view when: root.useListViewMode AnchorChanges { target: toolTip anchors.horizontalCenter: undefined } PropertyChanges { target: toolTip x: frameLoader.x y: frameLoader.y width: frameLoader.width height: frameLoader.height } } ] } Loader { id: frameLoader x: root.useListViewMode ? 0 : units.smallSpacing y: root.useListViewMode ? 0 : units.smallSpacing property Item textShadow: null property string prefix: "" sourceComponent: frameComponent active: state !== "" asynchronous: true width: { if (root.useListViewMode) { if (main.GridView.view.overflowing) { return parent.width - units.smallSpacing; } else { return parent.width; } } return parent.width - (units.smallSpacing * 2); } height: { if (root.useListViewMode) { return parent.height; } // Note: frameLoader.y = units.smallSpacing (acts as top margin) return (units.smallSpacing // icon.anchors.topMargin (acts as top padding) + icon.height + units.smallSpacing // label.anchors.topMargin (acts as spacing between icon and label) + (label.lineCount * theme.mSize(theme.defaultFont).height) + units.smallSpacing); // leftover (acts as bottom padding) } PlasmaCore.IconItem { id: icon z: 2 states: [ State { // icon view when: !root.useListViewMode AnchorChanges { target: icon anchors.top: parent.top anchors.horizontalCenter: parent.horizontalCenter } }, State { // list view when: root.useListViewMode AnchorChanges { target: icon anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter } } ] anchors { topMargin: units.smallSpacing leftMargin: units.smallSpacing } width: root.useListViewMode ? main.GridView.view.iconSize : (parent.width - 2 * units.smallSpacing) height: main.GridView.view.iconSize opacity: { if (root.useListViewMode && selectionButton) { return 0.3; } if (model.isHidden) { return 0.6; } return 1.0; } animated: false usesPlasmaTheme: false smooth: true source: model.decoration overlays: model.overlays } PlasmaComponents.Label { id: label z: 2 // So we can position a textShadowComponent below if needed. states: [ State { // icon view when: !root.useListViewMode AnchorChanges { target: label anchors.top: icon.bottom anchors.horizontalCenter: parent.horizontalCenter } PropertyChanges { target: label anchors.topMargin: units.smallSpacing width: Math.min(label.implicitWidth + units.smallSpacing, parent.width - units.smallSpacing) maximumLineCount: plasmoid.configuration.textLines horizontalAlignment: Text.AlignHCenter } }, State { // list view when: root.useListViewMode AnchorChanges { target: label anchors.left: icon.right anchors.verticalCenter: parent.verticalCenter } PropertyChanges { target: label anchors.leftMargin: units.smallSpacing * 2 anchors.rightMargin: units.smallSpacing * 2 width: parent.width - icon.width - (units.smallSpacing * 4) maximumLineCount: 1 horizontalAlignment: Text.AlignLeft } } ] height: undefined // Unset PlasmaComponents.Label's default. textFormat: Text.PlainText wrapMode: (maximumLineCount == 1) ? Text.NoWrap : Text.Wrap elide: Text.ElideRight color: (frameLoader.textShadow && frameLoader.textShadow.visible ? "#fff" : PlasmaCore.ColorScope.textColor) opacity: model.isHidden ? 0.6 : 1 text: model.blank ? "" : model.display font.italic: model.isLink visible: { if (editor && editor.targetItem === main) { return false; } // DropShadow renders the Label already. if (frameLoader.textShadow && frameLoader.textShadow.visible) { return false; } return true; } } Component { id: frameComponent PlasmaCore.FrameSvgItem { prefix: frameLoader.prefix imagePath: "widgets/viewitem" } } Component { id: selectionButtonComponent FolderItemActionButton { element: model.selected ? "remove" : "add" onClicked: dir.toggleSelected(positioner.map(index)) } } Component { id: popupButtonComponent FolderItemActionButton { visible: main.GridView.view.isRootView && (popupDialog == null) element: "open" onClicked: { dir.setSelected(positioner.map(index)) openPopup(); } } } Component { id: textShadowComponent DropShadow { anchors.fill: label z: 1 horizontalOffset: 2 verticalOffset: 2 radius: 9.0 samples: 18 spread: 0.15 color: "black" opacity: model.isHidden ? 0.6 : 1 source: label visible: !editor || editor.targetItem != main } } states: [ State { name: "selected" when: model.selected PropertyChanges { target: frameLoader prefix: "selected" } }, State { name: "hover" when: hovered && !model.selected && plasmoid.configuration.iconHoverEffect PropertyChanges { target: frameLoader prefix: "hover" } }, State { name: "selected+hover" when: hovered && model.selected && plasmoid.configuration.iconHoverEffect PropertyChanges { target: frameLoader prefix: "selected+hover" } } ] } Column { id: actions visible: { if (main.GridView.view.isRootView && root.containsDrag) { return false; } - if (!main.GridView.view.isRootView && main.GridView.view.dialog.containsDrag) { + if (!main.GridView.view.isRootView && main.GridView.view.dialog && main.GridView.view.dialog.containsDrag) { return false; } if (popupDialog) { return false; } return true; } anchors { left: frameLoader.left top: frameLoader.top leftMargin: root.useListViewMode ? (icon.x + (icon.width / 2)) - (width / 2) : 0 topMargin: root.useListViewMode ? (icon.y + (icon.height / 2)) - (height / 2) : 0 } width: implicitWidth height: implicitHeight } Component.onCompleted: { if (root.isContainment && main.GridView.view.isRootView && root.GraphicsInfo.api === GraphicsInfo.OpenGL) { frameLoader.textShadow = textShadowComponent.createObject(frameLoader); } } } } }