diff --git a/containments/desktop/package/contents/ui/ConfigOverlay.qml b/containments/desktop/package/contents/ui/ConfigOverlay.qml index f556adaac..16e79417b 100644 --- a/containments/desktop/package/contents/ui/ConfigOverlay.qml +++ b/containments/desktop/package/contents/ui/ConfigOverlay.qml @@ -1,251 +1,255 @@ /* * Copyright 2019 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 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 Library General Public License for more details * * You should have received a copy of the GNU Library 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.12 import QtQuick.Layouts 1.1 import org.kde.plasma.plasmoid 2.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 3.0 as PlasmaComponents import org.kde.plasma.private.containmentlayoutmanager 1.0 as ContainmentLayoutManager ContainmentLayoutManager.ConfigOverlayWithHandles { id: overlay readonly property int iconSize: touchInteraction ? units.iconSizes.medium : units.iconSizes.small PlasmaCore.Svg { id: configIconsSvg imagePath: "widgets/configuration-icons" } PlasmaComponents.Label { id: toolTipDelegate width: contentWidth height: undefined property Item toolTip text: (toolTip != null) ? toolTip.mainText : "" } SequentialAnimation { id: removeAnim NumberAnimation { target: overlay.itemContainer property: "scale" from: 1 to: 0 duration: units.longDuration easing.type: Easing.InOutQuad } ScriptAction { script: { appletContainer.applet.action("remove").trigger(); appletContainer.editMode = false; } } } PlasmaCore.FrameSvgItem { id: frame anchors.verticalCenter: parent.verticalCenter x: rightAvailableSpace > width + units.gridUnit ? parent.width + units.gridUnit : -width - units.gridUnit transform: Translate { x: open ? 0 : (overlay.rightAvailableSpace > frame.width + units.gridUnit ? -frame.width : frame.width) Behavior on x { NumberAnimation { duration: units.longDuration easing.type: Easing.InOutQuad } } } width: layout.implicitWidth + margins.left + margins.right height: Math.max(layout.implicitHeight + margins.top + margins.bottom, parent.height) imagePath: "widgets/background" ColumnLayout { id: layout anchors { fill: parent topMargin: parent.margins.top leftMargin: parent.margins.left bottomMargin: parent.margins.bottom rightMargin: parent.margins.right } ActionButton { id: rotateButton svg: configIconsSvg elementId: "rotate" mainText: i18n("Rotate") iconSize: overlay.iconSize action: (applet) ? applet.action("rotate") : null active: !rotateHandle.pressed Component.onCompleted: { if (action && typeof(action) != "undefined") { action.enabled = true } } MouseArea { id: rotateHandle anchors.fill: parent property int startRotation property real startCenterRelativeAngle; function pointAngle(pos) { var r = Math.sqrt(pos.x * pos.x + pos.y * pos.y); var cosine = pos.x / r; if (pos.y >= 0) { return Math.acos(cosine) * (180/Math.PI); } else { return -Math.acos(cosine) * (180/Math.PI); } } function centerRelativePos(x, y) { var mousePos = overlay.itemContainer.parent.mapFromItem(rotateButton, x, y); var centerPos = overlay.itemContainer.parent.mapFromItem(overlay.itemContainer, overlay.itemContainer.width/2, overlay.itemContainer.height/2); mousePos.x -= centerPos.x; mousePos.y -= centerPos.y; return mousePos; } onPressed: { parent.hideToolTip(); mouse.accepted = true startRotation = overlay.itemContainer.rotation; startCenterRelativeAngle = pointAngle(centerRelativePos(mouse.x, mouse.y)); } onPositionChanged: { var rot = startRotation%360; var snap = 4; var newRotation = Math.round(pointAngle(centerRelativePos(mouse.x, mouse.y)) - startCenterRelativeAngle + startRotation); if (newRotation < 0) { newRotation = newRotation + 360; } else if (newRotation >= 360) { newRotation = newRotation % 360; } snapIt(0); snapIt(90); snapIt(180); snapIt(270); function snapIt(snapTo) { if (newRotation > (snapTo - snap) && newRotation < (snapTo + snap)) { newRotation = snapTo; } } //print("Start: " + startRotation + " new: " + newRotation); overlay.itemContainer.rotation = newRotation; } onReleased: { // save rotation // print("saving..."); appletsLayout.save(); } } } ActionButton { svg: configIconsSvg elementId: "configure" iconSize: overlay.iconSize visible: (action && typeof(action) != "undefined") ? action.enabled : false action: (applet) ? applet.action("configure") : null Component.onCompleted: { if (action && typeof(action) != "undefined") { action.enabled = true } } } ActionButton { svg: configIconsSvg elementId: "maximize" iconSize: overlay.iconSize visible: (action && typeof(action) != "undefined") ? action.enabled : false action: (applet) ? applet.action("run associated application") : null Component.onCompleted: { if (action && typeof(action) != "undefined") { action.enabled = true } } } MouseArea { drag.target: overlay.itemContainer Layout.minimumHeight: units.gridUnit * 3 Layout.fillHeight: true Layout.fillWidth: true cursorShape: Qt.DragMoveCursor + hoverEnabled: true onPressed: appletsLayout.releaseSpace(overlay.itemContainer); onPositionChanged: { + if (!pressed) { + return; + } appletsLayout.showPlaceHolderForItem(overlay.itemContainer); var dragPos = mapToItem(overlay.itemContainer, mouse.x, mouse.y); overlay.itemContainer.userDrag(Qt.point(overlay.itemContainer.x, overlay.itemContainer.y), dragPos); } onReleased: { appletsLayout.hidePlaceHolder(); appletsLayout.positionItem(overlay.itemContainer); } } ActionButton { id: closeButton svg: configIconsSvg elementId: "delete" mainText: i18n("Remove") iconSize: overlay.iconSize visible: { if (!applet) { return false; } var a = applet.action("remove"); return (a && typeof(a) != "undefined") ? a.enabled : false; } // we don't set action, since we want to catch the button click, // animate, and then trigger the "remove" action // Triggering the action is handled in the overlay.itemContainer, we just // emit a signal here to avoid the applet-gets-removed-before-we- // can-animate it race condition. onClicked: { removeAnim.restart(); } Component.onCompleted: { var a = applet.action("remove"); if (a && typeof(a) != "undefined") { a.enabled = true } } } } } } diff --git a/containments/desktop/package/contents/ui/FolderView.qml b/containments/desktop/package/contents/ui/FolderView.qml index 54a10d8b3..273671be0 100644 --- a/containments/desktop/package/contents/ui/FolderView.qml +++ b/containments/desktop/package/contents/ui/FolderView.qml @@ -1,1396 +1,1387 @@ /*************************************************************************** * 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.4 import QtQuick.Layouts 1.1 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.plasma.extras 2.0 as PlasmaExtras import org.kde.kquickcontrolsaddons 2.0 import org.kde.private.desktopcontainment.folder 0.1 as Folder import "code/FolderTools.js" as FolderTools FocusScope { id: main signal pressed property QtObject model: dir property Item rubberBand: null + property alias view: gridView property alias isRootView: gridView.isRootView property alias currentIndex: gridView.currentIndex property alias url: dir.url property alias status: dir.status property alias positions: positioner.positions property alias errorString: dir.errorString property alias dragging: dir.dragging property alias locked: dir.locked property alias sortMode: dir.sortMode property alias filterMode: dir.filterMode property alias filterPattern: dir.filterPattern property alias filterMimeTypes: dir.filterMimeTypes property alias flow: gridView.flow property alias layoutDirection: gridView.layoutDirection property alias cellWidth: gridView.cellWidth property alias cellHeight: gridView.cellHeight property alias overflowing: gridView.overflowing property alias scrollLeft: gridView.scrollLeft property alias scrollRight: gridView.scrollRight property alias scrollUp: gridView.scrollUp property alias scrollDown: gridView.scrollDown property alias hoveredItem: gridView.hoveredItem property var history: [] property var lastPosition: null property bool goingBack: false property Item backButton: null property var dialog: null property Item editor: null function positionViewAtBeginning() { gridView.positionViewAtBeginning(); } function rename() { if (gridView.currentIndex != -1) { var renameAction = folderView.model.action("rename"); if (renameAction && !renameAction.enabled) { return; } if (!editor) { editor = editorComponent.createObject(listener); } editor.targetItem = gridView.currentItem; } } function cancelRename() { if (editor) { editor.targetItem = null; } } function linkHere(sourceUrl) { dir.linkHere(sourceUrl); } function handleDragMove(x, y) { var child = childAt(x, y); if (child !== null && child === backButton) { hoveredItem = null; backButton.handleDragMove(); } else { if (backButton && backButton.containsDrag) { backButton.endDragMove(); } var pos = mapToItem(gridView.contentItem, x, y); var item = gridView.itemAt(pos.x, pos.y); if (item && item.isDir) { hoveredItem = item; } else { hoveredItem = null; } } } function endDragMove() { if (backButton && backButton.active) { backButton.endDragMove(); } else if (hoveredItem && !hoveredItem.popupDialog) { hoveredItem = null; } } function dropItemAt(pos) { var item = gridView.itemAt(pos.x, pos.y); if (item) { if (item.blank) { return -1; } var hOffset = Math.abs(Math.min(gridView.contentX, gridView.originX)); var hPos = mapToItem(item.hoverArea, pos.x + hOffset, pos.y); if ((hPos.x < 0 || hPos.y < 0 || hPos.x > item.hoverArea.width || hPos.y > item.hoverArea.height)) { return -1; } else { return positioner.map(item.index); } } return -1; } function drop(target, event, pos) { var dropPos = mapToItem(gridView.contentItem, pos.x, pos.y); var dropIndex = gridView.indexAt(dropPos.x, dropPos.y); var dragPos = mapToItem(gridView.contentItem, listener.dragX, listener.dragY); var dragIndex = gridView.indexAt(dragPos.x, dragPos.y); if (listener.dragX == -1 || dragIndex !== dropIndex) { dir.drop(target, event, dropItemAt(dropPos)); } } Connections { target: dir onPopupMenuAboutToShow: { if (!plasmoid.immutable) { plasmoid.processMimeData(mimeData, x, y, dropJob); } } } Connections { target: plasmoid onExpandedChanged: { if (plasmoid.expanded && dir.status === Folder.FolderModel.Ready && !gridView.model) { gridView.model = positioner; } } } - // Lower the toolBox when an item is hovered, so it doesn't interfere with - // its interaction (e.g. the selection button in the top left, cf. Bug 337060) - Binding { - target: toolBox - property: "z" - // 999 is the default "z" for desktop ToolBoxRoot - value: main.hoveredItem ? -100 : 999 - when: toolBox - } - Binding { target: plasmoid property: "busy" value: !gridView.model && dir.status === Folder.FolderModel.Listing } function makeBackButton() { return Qt.createQmlObject("BackButtonItem {}", main); } function doCd(row) { history.push({"url": url, "index": gridView.currentIndex, "yPosition": gridView.visibleArea.yPosition}); updateHistory(); dir.cd(row); gridView.currentIndex = -1; } function doBack() { goingBack = true; gridView.currentIndex = -1; lastPosition = history.pop(); url = lastPosition.url; updateHistory(); } // QML doesn't detect change in the array(history) property, so update it explicitly. function updateHistory() { history = history; } Connections { target: root onIsPopupChanged: { if (backButton == null && root.useListViewMode) { backButton = makeBackButton(); } else if (backButton != null) { backButton.destroy(); } } } MouseEventListener { id: listener anchors { topMargin: backButton != null ? backButton.height : undefined fill: parent } property alias hoveredItem: gridView.hoveredItem property Item pressedItem: null property int pressX: -1 property int pressY: -1 property int dragX: -1 property int dragY: -1 property variant cPress: null property bool doubleClickInProgress: false acceptedButtons: { if (hoveredItem == null && main.isRootView) { return root.isPopup ? (Qt.LeftButton | Qt.MiddleButton | Qt.BackButton) : Qt.LeftButton; } return root.isPopup ? (Qt.LeftButton | Qt.MiddleButton | Qt.RightButton | Qt.BackButton) : (Qt.LeftButton | Qt.RightButton); } hoverEnabled: true onPressXChanged: { cPress = mapToItem(gridView.contentItem, pressX, pressY); } onPressYChanged: { cPress = mapToItem(gridView.contentItem, pressX, pressY); } onPressed: { // Ignore press events outside the viewport (i.e. on scrollbars). if (!scrollArea.viewport.contains(Qt.point(mouse.x,mouse.y))) { return; } scrollArea.focus = true; if (mouse.buttons & Qt.BackButton) { if (root.isPopup && dir.resolvedUrl !== dir.resolve(plasmoid.configuration.url)) { doBack(); mouse.accepted = true; } return; } if (editor && childAt(mouse.x, mouse.y) !== editor) { editor.commit(); } pressX = mouse.x; pressY = mouse.y; if (!hoveredItem || hoveredItem.blank) { if (!gridView.ctrlPressed) { gridView.currentIndex = -1; dir.clearSelection(); } if (mouse.buttons & Qt.RightButton) { clearPressState(); dir.openContextMenu(null, mouse.modifiers); mouse.accepted = true; } } else { pressedItem = hoveredItem; var pos = mapToItem(hoveredItem.actionsOverlay, mouse.x, mouse.y); if (!(pos.x <= hoveredItem.actionsOverlay.width && pos.y <= hoveredItem.actionsOverlay.height)) { if (gridView.shiftPressed && gridView.currentIndex != -1) { positioner.setRangeSelected(gridView.anchorIndex, hoveredItem.index); } else { // FIXME TODO: Clicking one item with others selected should deselect the others, // which doesn't happen right now because initiating a drag after the press should // still drag all of them: The deselect needs to happen on release instead so we // can distinguish. if (!gridView.ctrlPressed && !dir.isSelected(positioner.map(hoveredItem.index))) { dir.clearSelection(); } if (gridView.ctrlPressed) { dir.toggleSelected(positioner.map(hoveredItem.index)); } else { dir.setSelected(positioner.map(hoveredItem.index)); } } gridView.currentIndex = hoveredItem.index; if (mouse.buttons & Qt.RightButton) { if (pressedItem.toolTip && pressedItem.toolTip.active) { pressedItem.toolTip.hideToolTip(); } clearPressState(); dir.openContextMenu(null, mouse.modifiers); mouse.accepted = true; } } } main.pressed(); } onCanceled: pressCanceled() onReleased: pressCanceled() onClicked: { clearPressState(); if (mouse.button === Qt.RightButton || (editor && childAt(mouse.x, mouse.y) === editor)) { return; } if (!hoveredItem || hoveredItem.blank || gridView.currentIndex == -1 || gridView.ctrlPressed || gridView.shiftPressed) { // Bug 357367: Replay mouse event, so containment actions assigned to left mouse button work. eventGenerator.sendMouseEvent(plasmoid, EventGenerator.MouseButtonPress, mouse.x, mouse.y, mouse.button, mouse.buttons, mouse.modifiers); return; } var pos = mapToItem(hoveredItem, mouse.x, mouse.y); // Moving from an item to its preview popup dialog doesn't unset hoveredItem // even though the cursor has left it, so we need to check whether the click // actually occurred inside the item we expect it in before going ahead. If it // didn't, clean up (e.g. dismissing the dialog as a side-effect of unsetting // hoveredItem) and abort. if (pos.x < 0 || pos.x > hoveredItem.width || pos.y < 0 || pos.y > hoveredItem.height) { hoveredItem = null; dir.clearSelection(); return; // If the hoveredItem is clicked while having a preview popup dialog open, // only dismiss the dialog and abort. } else if (hoveredItem.popupDialog) { hoveredItem.closePopup(); return; } pos = mapToItem(hoveredItem.actionsOverlay, mouse.x, mouse.y); if (!(pos.x <= hoveredItem.actionsOverlay.width && pos.y <= hoveredItem.actionsOverlay.height)) { if (Qt.styleHints.singleClickActivation || doubleClickInProgress) { var func = root.useListViewMode && (mouse.button === Qt.LeftButton) && hoveredItem.isDir ? doCd : dir.run; func(positioner.map(gridView.currentIndex)); hoveredItem = null; } else { doubleClickInProgress = true; doubleClickTimer.interval = Qt.styleHints.mouseDoubleClickInterval; doubleClickTimer.start(); } } } onPositionChanged: { gridView.ctrlPressed = (mouse.modifiers & Qt.ControlModifier); gridView.shiftPressed = (mouse.modifiers & Qt.ShiftModifier); var cPos = mapToItem(gridView.contentItem, mouse.x, mouse.y); var item = gridView.itemAt(cPos.x, cPos.y); var leftEdge = Math.min(gridView.contentX, gridView.originX); if (!item || item.blank) { if (gridView.hoveredItem && !root.containsDrag && (!dialog || !dialog.containsDrag) && !gridView.hoveredItem.popupDialog) { gridView.hoveredItem = null; } } else { var fPos = mapToItem(item.frame, mouse.x, mouse.y); if (fPos.x < 0 || fPos.y < 0 || fPos.x > item.frame.width || fPos.y > item.frame.height) { gridView.hoveredItem = null; } } // Trigger autoscroll. if (pressX != -1) { gridView.scrollLeft = (mouse.x <= 0 && gridView.contentX > leftEdge); gridView.scrollRight = (mouse.x >= gridView.width && gridView.contentX < gridView.contentItem.width - gridView.width); gridView.scrollUp = (mouse.y <= 0 && gridView.contentY > 0); gridView.scrollDown = (mouse.y >= gridView.height && gridView.contentY < gridView.contentItem.height - gridView.height); } // Update rubberband geometry. if (main.rubberBand) { var rB = main.rubberBand; if (cPos.x < cPress.x) { rB.x = Math.max(leftEdge, cPos.x); rB.width = Math.abs(rB.x - cPress.x); } else { rB.x = cPress.x; var ceil = Math.max(gridView.width, gridView.contentItem.width) + leftEdge; rB.width = Math.min(ceil - rB.x, Math.abs(rB.x - cPos.x)); } if (cPos.y < cPress.y) { rB.y = Math.max(0, cPos.y); rB.height = Math.abs(rB.y - cPress.y); } else { rB.y = cPress.y; var ceil = Math.max(gridView.height, gridView.contentItem.height); rB.height = Math.min(ceil - rB.y, Math.abs(rB.y - cPos.y)); } // Ensure rubberband is at least 1px in size or else it will become // invisible and not match any items. rB.width = Math.max(1, rB.width); rB.height = Math.max(1, rB.height); gridView.rectangleSelect(rB.x, rB.y, rB.width, rB.height); return; } // Drag initiation. if (pressX != -1 && root.isDrag(pressX, pressY, mouse.x, mouse.y)) { if (pressedItem != null && dir.isSelected(positioner.map(pressedItem.index))) { pressedItem.toolTip.hideToolTip(); dragX = mouse.x; dragY = mouse.y; gridView.verticalDropHitscanOffset = pressedItem.iconArea.y + (pressedItem.iconArea.height / 2) dir.dragSelected(mouse.x, mouse.y); dragX = -1; dragY = -1; clearPressState(); } else { // Disable rubberband in popup list view mode. if (root.useListViewMode) { return; } dir.pinSelection(); main.rubberBand = Qt.createQmlObject("import QtQuick 2.0; import org.kde.private.desktopcontainment.folder 0.1 as Folder;" + "Folder.RubberBand { x: " + cPress.x + "; y: " + cPress.y + "; width: 0; height: 0; z: 99999; }", gridView.contentItem); gridView.interactive = false; } } } onContainsMouseChanged: { if (!containsMouse && !main.rubberBand) { clearPressState(); if (gridView.hoveredItem && !gridView.hoveredItem.popupDialog) { gridView.hoveredItem = null; } } } onHoveredItemChanged: { doubleClickInProgress = false; if (!hoveredItem) { hoverActivateTimer.stop(); } } function pressCanceled() { if (main.rubberBand) { main.rubberBand.visible = false; main.rubberBand.enabled = false; main.rubberBand.destroy(); main.rubberBand = null; gridView.interactive = true; gridView.cachedRectangleSelection = null; dir.unpinSelection(); } clearPressState(); gridView.cancelAutoscroll(); } function clearPressState() { pressedItem = null; pressX = -1; pressY = -1; } Timer { id: doubleClickTimer onTriggered: { listener.doubleClickInProgress = false; } } Timer { id: hoverActivateTimer interval: root.hoverActivateDelay onTriggered: { if (!hoveredItem) { return; } if (root.useListViewMode) { doCd(index); } else { hoveredItem.openPopup(); } } } PlasmaExtras.ScrollArea { id: scrollArea anchors.fill: parent focus: true property bool ready: false readonly property int viewportWidth: scrollArea.ready && viewport ? Math.ceil(viewport.width) : 0 readonly property int viewportHeight: scrollArea.ready && viewport ? Math.ceil(viewport.height) : 0 Component.onCompleted: { scrollArea.ready = true; } GridView { id: gridView property bool isRootView: false property int iconSize: makeIconSize() property int verticalDropHitscanOffset: 0 property Item hoveredItem: null property int anchorIndex: 0 property bool ctrlPressed: false property bool shiftPressed: false property bool overflowing: (visibleArea.heightRatio < 1.0 || visibleArea.widthRatio < 1.0) property bool scrollLeft: false property bool scrollRight: false property bool scrollUp: false property bool scrollDown: false property variant cachedRectangleSelection: null currentIndex: -1 keyNavigationWraps: false boundsBehavior: Flickable.StopAtBounds function calcExtraSpacing(cellSize, containerSize) { var availableColumns = Math.floor(containerSize / cellSize); var extraSpacing = 0; if (availableColumns > 0) { var allColumnSize = availableColumns * cellSize; var extraSpace = Math.max(containerSize - allColumnSize, 0); extraSpacing = extraSpace / availableColumns; } return Math.floor(extraSpacing); } cellWidth: { if (root.useListViewMode) { return gridView.width; } else { var iconWidth = iconSize + (2 * units.largeSpacing) + (2 * units.smallSpacing); if (root.isContainment && isRootView && scrollArea.viewportWidth > 0) { var minIconWidth = Math.max(iconWidth, units.iconSizes.small * ((plasmoid.configuration.labelWidth * 2) + 4)); var extraWidth = calcExtraSpacing(minIconWidth, scrollArea.viewportWidth); return minIconWidth + extraWidth; } else { return iconWidth; } } } cellHeight: { if (root.useListViewMode) { return Math.ceil((Math.max(theme.mSize(theme.defaultFont).height, iconSize) + Math.max(highlightItemSvg.margins.top + highlightItemSvg.margins.bottom, listItemSvg.margins.top + listItemSvg.margins.bottom)) / 2) * 2; } else { var iconHeight = iconSize + (theme.mSize(theme.defaultFont).height * plasmoid.configuration.textLines) + (4 * units.smallSpacing); if (root.isContainment && isRootView && scrollArea.viewportHeight > 0) { var extraHeight = calcExtraSpacing(iconHeight, scrollArea.viewportHeight); return iconHeight + extraHeight; } else { return iconHeight; } } } delegate: FolderItemDelegate { width: gridView.cellWidth height: gridView.cellHeight } onContentXChanged: { if (hoveredItem) { hoverActivateTimer.stop(); } cancelRename(); dir.setDragHotSpotScrollOffset(contentX, contentY); if (contentX == 0) { scrollLeft = false; } if (contentX == contentItem.width - width) { scrollRight = false; } // Update rubberband geometry. if (main.rubberBand) { var rB = main.rubberBand; if (scrollLeft) { rB.x = Math.min(gridView.contentX, gridView.originX); rB.width = listener.cPress.x; } if (scrollRight) { var lastCol = gridView.contentX + gridView.width; rB.width = lastCol - rB.x; } gridView.rectangleSelect(rB.x, rB.y, rB.width, rB.height); } } onContentYChanged: { if (hoveredItem) { hoverActivateTimer.stop(); } cancelRename(); dir.setDragHotSpotScrollOffset(contentX, contentY); if (contentY == 0) { scrollUp = false; } if (contentY == contentItem.height - height) { scrollDown = false; } // Update rubberband geometry. if (main.rubberBand) { var rB = main.rubberBand; if (scrollUp) { rB.y = 0; rB.height = listener.cPress.y; } if (scrollDown) { var lastRow = gridView.contentY + gridView.height; rB.height = lastRow - rB.y; } gridView.rectangleSelect(rB.x, rB.y, rB.width, rB.height); } } onScrollLeftChanged: { if (scrollLeft && gridView.visibleArea.widthRatio < 1.0) { smoothX.enabled = true; contentX = (gridView.flow == GridView.FlowLeftToRight) ? gridView.contentX : gridView.originX; } else { contentX = contentX; smoothX.enabled = false; } } onScrollRightChanged: { if (scrollRight && gridView.visibleArea.widthRatio < 1.0) { smoothX.enabled = true; contentX = ((gridView.flow == GridView.FlowLeftToRight) ? gridView.contentX : gridView.originX) + (contentItem.width - width); } else { contentX = contentX; smoothX.enabled = false; } } onScrollUpChanged: { if (scrollUp && gridView.visibleArea.heightRatio < 1.0) { smoothY.enabled = true; contentY = 0; } else { contentY = contentY; smoothY.enabled = false; } } onScrollDownChanged: { if (scrollDown && gridView.visibleArea.heightRatio < 1.0) { smoothY.enabled = true; contentY = contentItem.height - height; } else { contentY = contentY; smoothY.enabled = false; } } onFlowChanged: { // FIXME TODO: Preserve positions. if (positioner.enabled) { positioner.reset(); } } onLayoutDirectionChanged: { // FIXME TODO: Preserve positions. if (positioner.enabled) { positioner.reset(); } } onCurrentIndexChanged: { positionViewAtIndex(currentIndex, GridView.Contain); } onCachedRectangleSelectionChanged: { if (cachedRectangleSelection == null) { return; } if (cachedRectangleSelection.length) { // Set current index to start of selection. // cachedRectangleSelection is pre-sorted. currentIndex = cachedRectangleSelection[0]; } dir.updateSelection(cachedRectangleSelection.map(positioner.map), gridView.ctrlPressed); } function makeIconSize() { if (root.useListViewMode) { return units.iconSizes.small; } return FolderTools.iconSizeFromTheme(plasmoid.configuration.iconSize); } function updateSelection(modifier) { if (modifier & Qt.ShiftModifier) { positioner.setRangeSelected(anchorIndex, currentIndex); } else { dir.clearSelection(); dir.setSelected(positioner.map(currentIndex)); } } function cancelAutoscroll() { scrollLeft = false; scrollRight = false; scrollUp = false; scrollDown = false; } function rectangleSelect(x, y, width, height) { var rows = (gridView.flow == GridView.FlowLeftToRight); var axis = rows ? gridView.width : gridView.height; var step = rows ? cellWidth : cellHeight; var perStripe = Math.floor(axis / step); var stripes = Math.ceil(gridView.count / perStripe); var cWidth = gridView.cellWidth - (2 * units.smallSpacing); var cHeight = gridView.cellHeight - (2 * units.smallSpacing); var midWidth = gridView.cellWidth / 2; var midHeight = gridView.cellHeight / 2; var indices = []; for (var s = 0; s < stripes; s++) { for (var i = 0; i < perStripe; i++) { var index = (s * perStripe) + i; if (index >= gridView.count) { break; } if (positioner.isBlank(index)) { continue; } var itemX = ((rows ? i : s) * gridView.cellWidth); var itemY = ((rows ? s : i) * gridView.cellHeight); if (gridView.effectiveLayoutDirection == Qt.RightToLeft) { itemX -= (rows ? gridView.contentX : gridView.originX); itemX += cWidth; itemX = (rows ? gridView.width : gridView.contentItem.width) - itemX; } // Check if the rubberband intersects this cell first to avoid doing more // expensive work. if (main.rubberBand.intersects(Qt.rect(itemX + units.smallSpacing, itemY + units.smallSpacing, cWidth, cHeight))) { var item = gridView.contentItem.childAt(itemX + midWidth, itemY + midHeight); // If this is a visible item, check for intersection with the actual // icon or label rects for better feel. if (item && item.iconArea) { var iconRect = Qt.rect(itemX + item.iconArea.x, itemY + item.iconArea.y, item.iconArea.width, item.iconArea.height); if (main.rubberBand.intersects(iconRect)) { indices.push(index); continue; } var labelRect = Qt.rect(itemX + item.labelArea.x, itemY + item.labelArea.y, item.labelArea.width, item.labelArea.height); if (main.rubberBand.intersects(labelRect)) { indices.push(index); continue; } } else { // Otherwise be content with the cell intersection. indices.push(index); } } } } gridView.cachedRectangleSelection = indices; } function runOrCdSelected() { if (currentIndex != -1 && dir.hasSelection()) { if (root.useListViewMode && currentItem.isDir) { doCd(positioner.map(currentIndex)); } else { dir.runSelected(); } } } Behavior on contentX { id: smoothX; enabled: false; SmoothedAnimation { velocity: 700 } } Behavior on contentY { id: smoothY; enabled: false; SmoothedAnimation { velocity: 700 } } Keys.onReturnPressed: { if (event.modifiers === Qt.AltModifier) { dir.openPropertiesDialog(); } else { runOrCdSelected(); } } Keys.onEnterPressed: Keys.returnPressed(event) Keys.onMenuPressed: { if (currentIndex != -1 && dir.hasSelection() && currentItem) { dir.setSelected(positioner.map(currentIndex)); dir.openContextMenu(currentItem.frame, event.modifiers); } else { // Otherwise let the containment handle it. event.accepted = false; } } Keys.onEscapePressed: { if (!editor || !editor.targetItem) { dir.clearSelection(); event.accepted = false; } } Folder.ShortCut { Component.onCompleted: { installAsEventFilterFor(gridView); } onDeleteFile: { dir.deleteSelected(); } onRenameFile: { rename(); } } Keys.onPressed: { event.accepted = true; if (event.matches(StandardKey.Delete)) { if (dir.hasSelection()) { dir.action("trash").trigger(); } } else if (event.key === Qt.Key_Control) { ctrlPressed = true; } else if (event.key === Qt.Key_Shift) { shiftPressed = true; if (currentIndex != -1) { anchorIndex = currentIndex; } } else if (event.key === Qt.Key_Home) { currentIndex = 0; updateSelection(event.modifiers); } else if (event.key === Qt.Key_End) { currentIndex = count - 1; updateSelection(event.modifiers); } else if (event.matches(StandardKey.Copy)) { dir.copy(); } else if (event.matches(StandardKey.Paste)) { dir.paste(); } else if (event.matches(StandardKey.Cut)) { dir.cut(); } else if (event.matches(StandardKey.Undo)) { dir.undo(); } else if (event.matches(StandardKey.Refresh)) { dir.refresh(); } else if (event.matches(StandardKey.SelectAll)) { positioner.setRangeSelected(0, count - 1); } else { event.accepted = false; } } Keys.onReleased: { if (event.key === Qt.Key_Control) { ctrlPressed = false; } else if (event.key === Qt.Key_Shift) { shiftPressed = false; anchorIndex = 0; } } Keys.onLeftPressed: { if (root.isPopup && root.useListViewMode) { if (dir.resolvedUrl !== dir.resolve(plasmoid.configuration.url)) { doBack(); } } else if (positioner.enabled) { var newIndex = positioner.nearestItem(currentIndex, FolderTools.effectiveNavDirection(gridView.flow, gridView.effectiveLayoutDirection, Qt.LeftArrow)); if (newIndex !== -1) { currentIndex = newIndex; updateSelection(event.modifiers); } } else { var oldIndex = currentIndex; moveCurrentIndexLeft(); if (oldIndex === currentIndex) { return; } updateSelection(event.modifiers); } } Keys.onRightPressed: { if (root.isPopup && root.useListViewMode) { if (currentIndex != -1 && dir.hasSelection() && currentItem.isDir) { doCd(positioner.map(currentIndex)); } } else if (positioner.enabled) { var newIndex = positioner.nearestItem(currentIndex, FolderTools.effectiveNavDirection(gridView.flow, gridView.effectiveLayoutDirection, Qt.RightArrow)); if (newIndex !== -1) { currentIndex = newIndex; updateSelection(event.modifiers); } } else { var oldIndex = currentIndex; moveCurrentIndexRight(); if (oldIndex === currentIndex) { return; } updateSelection(event.modifiers); } } Keys.onUpPressed: { if (positioner.enabled) { var newIndex = positioner.nearestItem(currentIndex, FolderTools.effectiveNavDirection(gridView.flow, gridView.effectiveLayoutDirection, Qt.UpArrow)); if (newIndex !== -1) { currentIndex = newIndex; updateSelection(event.modifiers); } } else { var oldIndex = currentIndex; moveCurrentIndexUp(); if (oldIndex === currentIndex) { return; } updateSelection(event.modifiers); } } Keys.onDownPressed: { if (positioner.enabled) { var newIndex = positioner.nearestItem(currentIndex, FolderTools.effectiveNavDirection(gridView.flow, gridView.effectiveLayoutDirection, Qt.DownArrow)); if (newIndex !== -1) { currentIndex = newIndex; updateSelection(event.modifiers); } } else { var oldIndex = currentIndex; moveCurrentIndexDown(); if (oldIndex === currentIndex) { return; } updateSelection(event.modifiers); } } Keys.onBackPressed: { if (root.isPopup && dir.resolvedUrl !== dir.resolve(plasmoid.configuration.url)) { doBack(); } } Connections { target: units onIconSizesChanged: { gridView.iconSize = gridView.makeIconSize(); } } Connections { target: plasmoid.configuration onIconSizeChanged: { gridView.iconSize = gridView.makeIconSize(); } } Connections { target: plasmoid.configuration onUrlChanged: { history = []; updateHistory(); } } } } Folder.WheelInterceptor { anchors.fill: parent enabled: root.isContainment && !gridView.overflowing destination: plasmoid } Folder.FolderModel { id: dir usedByContainment: root.isContainment && main.isRootView sortDesc: plasmoid.configuration.sortDesc sortDirsFirst: plasmoid.configuration.sortDirsFirst parseDesktopFiles: (plasmoid.configuration.url === "desktop:/") previews: plasmoid.configuration.previews previewPlugins: plasmoid.configuration.previewPlugins appletInterface: plasmoid onListingCompleted: { if (!gridView.model && plasmoid.expanded) { gridView.model = positioner; gridView.currentIndex = isPopup ? 0 : -1; } else if (goingBack) { goingBack = false; gridView.currentIndex = Math.min(lastPosition.index, gridView.count - 1); setSelected(positioner.map(gridView.currentIndex)); gridView.contentY = lastPosition.yPosition * gridView.contentHeight; } } onMove: { var rows = (gridView.flow == GridView.FlowLeftToRight); var axis = rows ? gridView.width : gridView.height; var step = rows ? cellWidth : cellHeight; var perStripe = Math.floor(axis / step); var dropPos = mapToItem(gridView.contentItem, x, y); var leftEdge = Math.min(gridView.contentX, gridView.originX); var moves = [] var itemX = -1; var itemY = -1; var col = -1; var row = -1; var from = -1; var to = -1; for (var i = 0; i < urls.length; i++) { from = positioner.indexForUrl(urls[i]); to = -1; if (from === -1) { continue; } var offset = dir.dragCursorOffset(positioner.map(from)); if (offset.x === -1) { continue; } itemX = dropPos.x + offset.x + (listener.dragX % cellWidth) + (cellWidth / 2); itemY = dropPos.y + offset.y + (listener.dragY % cellHeight) + gridView.verticalDropHitscanOffset; if (gridView.effectiveLayoutDirection == Qt.RightToLeft) { itemX -= (rows ? gridView.contentX : gridView.originX); itemX = (rows ? gridView.width : gridView.contentItem.width) - itemX; } col = Math.floor(itemX / gridView.cellWidth); row = Math.floor(itemY / gridView.cellHeight); if ((rows ? col : row) < perStripe) { to = ((rows ? row : col) * perStripe) + (rows ? col : row); if (to < 0) { return; } } if (from !== to) { moves.push(from); moves.push(to); } } if (moves.length) { positioner.move(moves); gridView.forceLayout(); } dir.clearSelection(); } } Folder.Positioner { id: positioner enabled: isContainment && sortMode === -1 folderModel: dir perStripe: Math.floor(((gridView.flow == GridView.FlowLeftToRight) ? gridView.width : gridView.height) / ((gridView.flow == GridView.FlowLeftToRight) ? gridView.cellWidth : gridView.cellHeight)); } Folder.ItemViewAdapter { id: viewAdapter adapterView: gridView adapterModel: positioner adapterIconSize: gridView.iconSize * 2 adapterVisibleArea: Qt.rect(gridView.contentX, gridView.contentY, gridView.contentWidth, gridView.contentHeight) Component.onCompleted: { gridView.movementStarted.connect(viewAdapter.viewScrolled); dir.viewAdapter = viewAdapter; } } Component { id: editorComponent PlasmaComponents.TextArea { id: editor visible: false wrapMode: root.useListViewMode ? TextEdit.NoWrap : TextEdit.Wrap textMargin: 0 horizontalAlignment: root.useListViewMode ? TextEdit.AlignHLeft : TextEdit.AlignHCenter property Item targetItem: null onTargetItemChanged: { if (targetItem != null) { var xy = getXY(); x = xy[0]; y = xy[1]; width = getWidth(); height = getInitHeight(); text = targetItem.label.text; adjustSize(); editor.select(0, dir.fileExtensionBoundary(positioner.map(targetItem.index))); if(isPopup) { flickableItem.contentX = Math.max(flickableItem.contentWidth - contentItem.width, 0); } else { flickableItem.contentY = Math.max(flickableItem.contentHeight - contentItem.height, 0); } visible = true; } else { x: 0 y: 0 visible = false; } } onVisibleChanged: { if (visible) { focus = true; } else { scrollArea.focus = true; } } Keys.onPressed: { switch(event.key) { case Qt.Key_Return: case Qt.Key_Enter: commit(); break; case Qt.Key_Escape: if (targetItem) { targetItem = null; event.accepted = true; } break; case Qt.Key_Home: if (event.modifiers & Qt.ShiftModifier) { editor.select(0, cursorPosition); } else { editor.select(0, 0); } event.accepted = true; break; case Qt.Key_End: if (event.modifiers & Qt.ShiftModifier) { editor.select(cursorPosition, text.length); } else { editor.select(text.length, text.length); } event.accepted = true; break; default: adjustSize(); break; } } Keys.onReleased: { adjustSize(); } function getXY() { var pos = main.mapFromItem(targetItem, targetItem.labelArea.x, targetItem.labelArea.y); var _x, _y; if (root.useListViewMode) { _x = targetItem.labelArea.x - __style.padding.left; _y = pos.y - __style.padding.top; } else { _x = targetItem.x + Math.abs(Math.min(gridView.contentX, gridView.originX)); _x += __style.padding.left; _x += scrollArea.viewport.x; if (verticalScrollBarPolicy == Qt.ScrollBarAlwaysOn && gridView.effectiveLayoutDirection == Qt.RightToLeft) { _x -= __verticalScrollBar.parent.verticalScrollbarOffset; } _y = pos.y + units.smallSpacing - __style.padding.top; } return([ _x, _y ]); } function getWidth(addWidthVerticalScroller) { return(targetItem.label.parent.width - units.smallSpacing + (root.useListViewMode ? -(__style.padding.left + __style.padding.right + units.smallSpacing) : 0) + (addWidthVerticalScroller ? __verticalScrollBar.parent.verticalScrollbarOffset : 0)); } function getHeight(addWidthHoriozontalScroller, init) { var _height; if(isPopup || init) { _height = targetItem.labelArea.height + __style.padding.top + __style.padding.bottom; } else { var realHeight = contentHeight + __style.padding.top + __style.padding.bottom; var maxHeight = theme.mSize(theme.defaultFont).height * (plasmoid.configuration.textLines + 1) + __style.padding.top + __style.padding.bottom; _height = Math.min(realHeight, maxHeight); } return(_height + (addWidthHoriozontalScroller ? __horizontalScrollBar.parent.horizontalScrollbarOffset : 0)); } function getInitHeight() { return(getHeight(false, true)); } function adjustSize() { if(isPopup) { if(contentWidth + __style.padding.left + __style.padding.right > width) { visible = true; horizontalScrollBarPolicy = Qt.ScrollBarAlwaysOn; height = getHeight(true); } else { horizontalScrollBarPolicy = Qt.ScrollBarAlwaysOff; height = getHeight(); } } else { height = getHeight(); if(contentHeight + __style.padding.top + __style.padding.bottom > height) { visible = true; verticalScrollBarPolicy = Qt.ScrollBarAlwaysOn; width = getWidth(true); } else { verticalScrollBarPolicy = Qt.ScrollBarAlwaysOff; width = getWidth(); } } var xy = getXY(); x = xy[0]; y = xy[1]; } function commit() { if (targetItem) { dir.rename(positioner.map(targetItem.index), text); targetItem = null; } } } } Component.onCompleted: { dir.requestRename.connect(rename); } } Component.onCompleted: { if (backButton == null && root.useListViewMode) { backButton = makeBackButton(); } } } diff --git a/containments/desktop/package/contents/ui/main.qml b/containments/desktop/package/contents/ui/main.qml index 3f80f3df5..8a25d4808 100644 --- a/containments/desktop/package/contents/ui/main.qml +++ b/containments/desktop/package/contents/ui/main.qml @@ -1,412 +1,414 @@ /*************************************************************************** * Copyright (C) 2011-2013 Sebastian Kügler * * Copyright (C) 2011-2019 Marco Martin * * 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.10 import QtQuick.Layouts 1.1 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.draganddrop 2.0 as DragDrop import org.kde.kquickcontrolsaddons 2.0 as KQuickControlsAddons import org.kde.private.desktopcontainment.desktop 0.1 as Desktop import org.kde.private.desktopcontainment.folder 0.1 as Folder import org.kde.plasma.private.containmentlayoutmanager 1.0 as ContainmentLayoutManager import "code/FolderTools.js" as FolderTools FolderViewDropArea { id: root objectName: isFolder ? "folder" : "desktop" width: isPopup ? undefined : preferredWidth(false) // Initial size when adding to e.g. desktop. height: isPopup ? undefined : preferredHeight(false) // Initial size when adding to e.g. desktop. Layout.minimumWidth: preferredWidth(!isPopup) Layout.minimumHeight: preferredHeight(!isPopup) Layout.preferredWidth: preferredWidth(false) Layout.preferredHeight: preferredHeight(false) Layout.maximumWidth: isPopup ? preferredWidth(false) : -1 Layout.maximumHeight: isPopup ? preferredHeight(false) : -1 Plasmoid.switchWidth: { // Support expanding into the full representation only on vertical panels. if (isPopup && plasmoid.formFactor === PlasmaCore.Types.Vertical) { return units.iconSizeHints.panel; } return 0; } Plasmoid.switchHeight: { // Support expanding into the full representation only on vertical panels. if (isPopup && plasmoid.formFactor === PlasmaCore.Types.Vertical) { return units.iconSizeHints.panel; } return 0; } LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft LayoutMirroring.childrenInherit: true property bool isFolder: (plasmoid.pluginName === "org.kde.plasma.folder") property bool isContainment: ("containmentType" in plasmoid) property bool isPopup: (plasmoid.location !== PlasmaCore.Types.Floating) property bool useListViewMode: isPopup && plasmoid.configuration.viewMode === 0 property Component appletAppearanceComponent property Item toolBox property int handleDelay: 800 property real haloOpacity: 0.5 property int iconSize: units.iconSizes.small property int iconWidth: iconSize property int iconHeight: iconWidth readonly property int hoverActivateDelay: 750 // Magic number that matches Dolphin's auto-expand folders delay. preventStealing: true // Plasmoid.title is set by a Binding {} in FolderViewLayer Plasmoid.toolTipSubText: "" Plasmoid.icon: (!plasmoid.configuration.useCustomIcon && folderViewLayer.ready) ? folderViewLayer.view.model.iconName : plasmoid.configuration.icon Plasmoid.compactRepresentation: (isFolder && !isContainment) ? compactRepresentation : null Plasmoid.associatedApplicationUrls: folderViewLayer.ready ? folderViewLayer.model.resolvedUrl : null onIconHeightChanged: updateGridSize() anchors { leftMargin: (isContainment && plasmoid.availableScreenRect) ? plasmoid.availableScreenRect.x : 0 topMargin: (isContainment && plasmoid.availableScreenRect) ? plasmoid.availableScreenRect.y : 0 rightMargin: (isContainment && plasmoid.availableScreenRect) && parent ? parent.width - (plasmoid.availableScreenRect.x + plasmoid.availableScreenRect.width) : 0 bottomMargin: (isContainment && plasmoid.availableScreenRect) && parent ? parent.height - (plasmoid.availableScreenRect.y + plasmoid.availableScreenRect.height) : 0 } Behavior on anchors.topMargin { NumberAnimation { duration: units.longDuration; easing.type: Easing.InOutQuad } } Behavior on anchors.leftMargin { NumberAnimation { duration: units.longDuration; easing.type: Easing.InOutQuad } } Behavior on anchors.rightMargin { NumberAnimation { duration: units.longDuration; easing.type: Easing.InOutQuad } } Behavior on anchors.bottomMargin { NumberAnimation { duration: units.longDuration; easing.type: Easing.InOutQuad } } function updateGridSize() { appletsLayout.cellWidth = root.iconWidth + toolBoxSvg.elementSize("left").width + toolBoxSvg.elementSize("right").width appletsLayout.cellHeight = root.iconHeight + toolBoxSvg.elementSize("top").height + toolBoxSvg.elementSize("bottom").height; appletsLayout.defaultItemWidth = appletsLayout.cellWidth * 6; appletsLayout.defaultItemHeight = appletsLayout.cellHeight * 6; } function addLauncher(desktopUrl) { if (!isFolder) { return; } folderViewLayer.view.linkHere(desktopUrl); } function preferredWidth(minimum) { if (isContainment || !folderViewLayer.ready) { return -1; } else if (useListViewMode) { return (minimum ? folderViewLayer.view.cellHeight * 4 : units.gridUnit * 16); } return (folderViewLayer.view.cellWidth * (minimum ? 1 : 3)) + (units.largeSpacing * 2); } function preferredHeight(minimum) { if (isContainment || !folderViewLayer.ready) { return -1; } else if (useListViewMode) { var height = (folderViewLayer.view.cellHeight * (minimum ? 1 : 15)) + units.smallSpacing; } else { var height = (folderViewLayer.view.cellHeight * (minimum ? 1 : 2)) + units.largeSpacing } if (plasmoid.configuration.labelMode !== 0) { height += folderViewLayer.item.labelHeight; } return height; } function isDrag(fromX, fromY, toX, toY) { var length = Math.abs(fromX - toX) + Math.abs(fromY - toY); return length >= Qt.styleHints.startDragDistance; } onFocusChanged: { if (focus && isFolder) { folderViewLayer.item.forceActiveFocus(); } } onDragEnter: { if (isContainment && plasmoid.immutable && !(isFolder && FolderTools.isFileDrag(event))) { event.ignore(); } // Don't allow any drops while listing. if (isFolder && folderViewLayer.view.status === Folder.FolderModel.Listing) { event.ignore(); } // Firefox tabs are regular drags. Since all of our drop handling is asynchronous // we would accept this drop and have Firefox not spawn a new window. (Bug 337711) if (event.mimeData.formats.indexOf("application/x-moz-tabbrowser-tab") > -1) { event.ignore(); } } onDragMove: { // TODO: We should reject drag moves onto file items that don't accept drops // (cf. QAbstractItemModel::flags() here, but DeclarativeDropArea currently // is currently incapable of rejecting drag events. // Trigger autoscroll. if (isFolder && FolderTools.isFileDrag(event)) { handleDragMove(folderViewLayer.view, mapToItem(folderViewLayer.view, event.x, event.y)); } else if (isContainment) { appletsLayout.showPlaceHolderAt( Qt.rect(event.x - appletsLayout.minimumItemWidth / 2, event.y - appletsLayout.minimumItemHeight / 2, appletsLayout.minimumItemWidth, appletsLayout.minimumItemHeight) ); } } onDragLeave: { // Cancel autoscroll. if (isFolder) { handleDragEnd(folderViewLayer.view); } if (isContainment) { appletsLayout.hidePlaceHolder(); } } onDrop: { if (isFolder && FolderTools.isFileDrag(event)) { handleDragEnd(folderViewLayer.view); folderViewLayer.view.drop(root, event, mapToItem(folderViewLayer.view, event.x, event.y)); } else if (isContainment) { plasmoid.processMimeData(event.mimeData, event.x - appletsLayout.placeHolder.width / 2, event.y - appletsLayout.placeHolder.height / 2); event.accept(event.proposedAction); appletsLayout.hidePlaceHolder(); } } Component { id: compactRepresentation CompactRepresentation { folderView: folderViewLayer.view } } Connections { target: plasmoid ignoreUnknownSignals: true onImmutableChanged: { if (root.isContainment && !plasmoid.immutable) { pressToMoveHelp.show(); } } } Connections { target: plasmoid.configuration onPressToMoveChanged: { if (plasmoid.configuration.pressToMove && plasmoid.configuration.pressToMoveHelp && !plasmoid.immutable) { pressToMoveHelp.show(); } } } Binding { target: toolBox property: "visible" value: plasmoid.configuration.showToolbox } Desktop.InfoNotification { id: pressToMoveHelp enabled: plasmoid.configuration.pressToMove && plasmoid.configuration.pressToMoveHelp iconName: "plasma" titleText: i18n("Widgets unlocked") text: i18n("You can press and hold widgets to move them and reveal their handles.") acknowledgeActionText: i18n("Got it") onAcknowledged: { plasmoid.configuration.pressToMoveHelp = false; } } PlasmaCore.FrameSvgItem { id : highlightItemSvg visible: false imagePath: isPopup ? "widgets/viewitem" : "" prefix: "hover" } PlasmaCore.FrameSvgItem { id : listItemSvg visible: false imagePath: isPopup ? "widgets/viewitem" : "" prefix: "normal" } PlasmaCore.Svg { id: toolBoxSvg imagePath: "widgets/toolbox" property int rightBorder: elementSize("right").width property int topBorder: elementSize("top").height property int bottomBorder: elementSize("bottom").height property int leftBorder: elementSize("left").width } // Can be removed? KQuickControlsAddons.EventGenerator { id: eventGenerator } Connections { target: plasmoid ignoreUnknownSignals: true onEditModeChanged: appletsLayout.editMode = plasmoid.editMode } ContainmentLayoutManager.AppletsLayout { id: appletsLayout anchors.fill: parent // NOTE: use plasmoid.availableScreenRect and not own width and height as they are updated not atomically configKey: plasmoid.availableScreenRect.width > plasmoid.availableScreenRect.height ? "ItemGeometriesHorizontal" : "ItemGeometriesVertical" containment: plasmoid editModeCondition: plasmoid.immutable ? ContainmentLayoutManager.AppletsLayout.Locked - : ContainmentLayoutManager.AppletsLayout.Manual + : ContainmentLayoutManager.AppletsLayout.AfterPressAndHold // Sets the containment in edit mode when we go in edit mode as well onEditModeChanged: plasmoid.editMode = editMode minimumItemWidth: units.gridUnit * 3 minimumItemHeight: minimumItemWidth cellWidth: units.iconSizes.small cellHeight: cellWidth + eventManagerToFilter: folderViewLayer.item ? folderViewLayer.item.view.view : null + appletContainerComponent: ContainmentLayoutManager.BasicAppletContainer { id: appletContainer editModeCondition: plasmoid.immutable ? ContainmentLayoutManager.ItemContainer.Locked : (plasmoid.configuration.pressToMove ? ContainmentLayoutManager.ItemContainer.AfterPressAndHold : ContainmentLayoutManager.ItemContainer.AfterMouseOver) configOverlayComponent: ConfigOverlay {} onUserDrag: { var pos = mapToItem(root.parent, dragCenter.x, dragCenter.y); var newCont = plasmoid.containmentAt(pos.x, pos.y); if (newCont && newCont !== plasmoid) { var newPos = newCont.mapFromApplet(plasmoid, pos.x, pos.y); newCont.addApplet(appletContainer.applet, newPos.x, newPos.y); appletsLayout.hidePlaceHolder(); } } } placeHolder: ContainmentLayoutManager.PlaceHolder {} Loader { id: folderViewLayer anchors.fill: parent property bool ready: status == Loader.Ready property Item view: item ? item.view : null property QtObject model: item ? item.model : null focus: true active: isFolder asynchronous: false source: "FolderViewLayer.qml" onFocusChanged: { if (!focus && model) { model.clearSelection(); } } Connections { target: folderViewLayer.view // `FolderViewDropArea` is not a FocusScope. We need to forward manually. onPressed: { folderViewLayer.forceActiveFocus(); } } } } Component.onCompleted: { if (!isContainment) { return; } // Customize the icon and text to improve discoverability plasmoid.setAction("configure", i18n("Configure Desktop..."), "preferences-desktop-wallpaper") // WORKAROUND: that's the only place where we can inject a sensible size. // if root has width defined, it will override the value we set before // the component completes root.width = plasmoid.width; updateGridSize(); } } diff --git a/containments/panel/contents/ui/main.qml b/containments/panel/contents/ui/main.qml index ac7fe8c48..bc988dcb9 100644 --- a/containments/panel/contents/ui/main.qml +++ b/containments/panel/contents/ui/main.qml @@ -1,446 +1,446 @@ /* * Copyright 2013 Marco Martin * * 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 2.010-1301, USA. */ import QtQuick 2.1 import QtQuick.Layouts 1.1 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 import org.kde.draganddrop 2.0 as DragDrop import "LayoutManager.js" as LayoutManager DragDrop.DropArea { id: root width: 640 height: 48 //BEGIN properties Layout.minimumWidth: fixedWidth > 0 ? fixedWidth : (currentLayout.Layout.minimumWidth + (isHorizontal && toolBox ? toolBox.width : 0)) Layout.maximumWidth: fixedWidth > 0 ? fixedWidth : (currentLayout.Layout.maximumWidth + (isHorizontal && toolBox ? toolBox.width : 0)) Layout.preferredWidth: fixedWidth > 0 ? fixedWidth : (currentLayout.Layout.preferredWidth + (isHorizontal && toolBox ? toolBox.width : 0)) Layout.minimumHeight: fixedHeight > 0 ? fixedHeight : (currentLayout.Layout.minimumHeight + (!isHorizontal && toolBox ? toolBox.height : 0)) Layout.maximumHeight: fixedHeight > 0 ? fixedHeight : (currentLayout.Layout.maximumHeight + (!isHorizontal && toolBox ? toolBox.height : 0)) Layout.preferredHeight: fixedHeight > 0 ? fixedHeight : (currentLayout.Layout.preferredHeight + (!isHorizontal && toolBox? toolBox.height : 0)) property Item toolBox property var layoutManager: LayoutManager property Item dragOverlay property bool isHorizontal: plasmoid.formFactor !== PlasmaCore.Types.Vertical property int fixedWidth: 0 property int fixedHeight: 0 //END properties //BEGIN functions function addApplet(applet, x, y) { // don't show applet if it chooses to be hidden but still make it // accessible in the panelcontroller // Due to the nature of how "visible" propagates in QML, we need to // explicitly set it on the container (so the Layout ignores it) // as well as the applet (so it reliably knows about), otherwise it can // happen that an applet erroneously thinks it's visible, or suddenly // starts thinking that way on teardown (virtual desktop pager) // leading to crashes var visibleBinding = Qt.binding(function() { return applet.status !== PlasmaCore.Types.HiddenStatus || (!plasmoid.immutable && plasmoid.userConfiguring); }) var container = appletContainerComponent.createObject(root, { applet: applet, visible: visibleBinding }); applet.parent = container; applet.anchors.fill = container; applet.visible = visibleBinding; // Is there a DND placeholder? Replace it! if (dndSpacer.parent === currentLayout) { LayoutManager.insertBefore(dndSpacer, container); dndSpacer.parent = root; return; // If the provided position is valid, use it. } else if (x >= 0 && y >= 0) { var index = LayoutManager.insertAtCoordinates(container, x , y); // Fall through to determining an appropriate insert position. } else { var before = lastSpacer; container.animationsEnabled = false; // Insert icons to the left of whatever is at the center (usually a Task Manager), // if it exists. // FIXME TODO: This is a real-world fix to produce a sensible initial position for // launcher icons added by launcher menu applets. The basic approach has been used // since Plasma 1. However, "add launcher to X" is a generic-enough concept and // frequent-enough occurrence that we'd like to abstract it further in the future // and get rid of the ugliness of parties external to the containment adding applets // of a specific type, and the containment caring about the applet type. In a better // system the containment would be informed of requested launchers, and determine by // itself what it wants to do with that information. if (!startupTimer.running && applet.pluginName === "org.kde.plasma.icon") { var middle = currentLayout.childAt(root.width / 2, root.height / 2); if (middle) { before = middle; } // lastSpacer is here, enqueue before it. } LayoutManager.insertBefore(before, container); //event compress the enable of animations startupTimer.restart(); } } function checkLastSpacer() { var flexibleFound = false; for (var i = 0; i < currentLayout.children.length; ++i) { var applet = currentLayout.children[i].applet; if (!applet) { continue; } if (!applet.visible || !applet.Layout) { continue; } if ((root.isHorizontal && applet.Layout.fillWidth) || (!root.isHorizontal && applet.Layout.fillHeight)) { flexibleFound = true; break } } lastSpacer.visible= !flexibleFound; } //END functions //BEGIN connections Component.onCompleted: { currentLayout.isLayoutHorizontal = isHorizontal LayoutManager.plasmoid = plasmoid; LayoutManager.root = root; LayoutManager.layout = currentLayout; LayoutManager.lastSpacer = lastSpacer; LayoutManager.restore(); containmentSizeSyncTimer.restart(); plasmoid.action("configure").visible = Qt.binding(function() { return !plasmoid.immutable; }); plasmoid.action("configure").enabled = Qt.binding(function() { return !plasmoid.immutable; }); } onDragEnter: { if (plasmoid.immutable) { event.ignore(); return; } //during drag operations we disable panel auto resize if (root.isHorizontal) { root.fixedWidth = root.width } else { root.fixedHeight = root.height } LayoutManager.insertAtCoordinates(dndSpacer, event.x, event.y) } onDragMove: { LayoutManager.insertAtCoordinates(dndSpacer, event.x, event.y) } onDragLeave: { dndSpacer.parent = root; root.fixedWidth = 0; root.fixedHeight = 0; } onDrop: { plasmoid.processMimeData(event.mimeData, event.x, event.y); event.accept(event.proposedAction); root.fixedWidth = 0; root.fixedHeight = 0; containmentSizeSyncTimer.restart(); } Containment.onAppletAdded: { addApplet(applet, x, y); checkLastSpacer(); LayoutManager.save(); } Containment.onAppletRemoved: { LayoutManager.removeApplet(applet); checkLastSpacer(); LayoutManager.save(); } Plasmoid.onUserConfiguringChanged: { containmentSizeSyncTimer.restart(); if (plasmoid.immutable) { if (dragOverlay) { dragOverlay.destroy(); } return; } if (plasmoid.userConfiguring) { for (var i = 0; i < plasmoid.applets.length; ++i) { plasmoid.applets[i].expanded = false; } if (!dragOverlay) { var component = Qt.createComponent("ConfigOverlay.qml"); if (component.status === Component.Ready) { dragOverlay = component.createObject(root); } else { console.log("Could not create ConfigOverlay:", component.errorString()); } component.destroy(); } else { dragOverlay.visible = true; } } else { dragOverlay.destroy(); } } Plasmoid.onFormFactorChanged: containmentSizeSyncTimer.restart(); Containment.onEditModeChanged: containmentSizeSyncTimer.restart(); onToolBoxChanged: { containmentSizeSyncTimer.restart(); if (startupTimer.running) { startupTimer.restart(); } } //END connections //BEGIN components Component { id: appletContainerComponent // This loader conditionally manages the BusyIndicator, it's not // loading the applet. The applet becomes a regular child item. Loader { id: container visible: false property bool animationsEnabled: true //when the applet moves caused by its resize, don't animate. //this is completely heuristic, but looks way less "jumpy" property bool movingForResize: false Layout.fillWidth: applet && applet.Layout.fillWidth Layout.onFillWidthChanged: { if (plasmoid.formFactor !== PlasmaCore.Types.Vertical) { checkLastSpacer(); } } Layout.fillHeight: applet && applet.Layout.fillHeight Layout.onFillHeightChanged: { if (plasmoid.formFactor === PlasmaCore.Types.Vertical) { checkLastSpacer(); } } Layout.minimumWidth: (currentLayout.isLayoutHorizontal ? (applet && applet.Layout.minimumWidth > 0 ? applet.Layout.minimumWidth : root.height) : root.width) Layout.minimumHeight: (!currentLayout.isLayoutHorizontal ? (applet && applet.Layout.minimumHeight > 0 ? applet.Layout.minimumHeight : root.width) : root.height) Layout.preferredWidth: (currentLayout.isLayoutHorizontal ? (applet && applet.Layout.preferredWidth > 0 ? applet.Layout.preferredWidth : root.height) : root.width) Layout.preferredHeight: (!currentLayout.isLayoutHorizontal ? (applet && applet.Layout.preferredHeight > 0 ? applet.Layout.preferredHeight : root.width) : root.height) Layout.maximumWidth: (currentLayout.isLayoutHorizontal ? (applet && applet.Layout.maximumWidth > 0 ? applet.Layout.maximumWidth : (Layout.fillWidth ? root.width : root.height)) : root.height) Layout.maximumHeight: (!currentLayout.isLayoutHorizontal ? (applet && applet.Layout.maximumHeight > 0 ? applet.Layout.maximumHeight : (Layout.fillHeight ? root.height : root.width)) : root.width) property int oldX: x property int oldY: y property Item applet onAppletChanged: { if (!applet) { destroy(); } } active: applet && applet.busy sourceComponent: PlasmaComponents.BusyIndicator {} Layout.onMinimumWidthChanged: movingForResize = true; Layout.onMinimumHeightChanged: movingForResize = true; Layout.onMaximumWidthChanged: movingForResize = true; Layout.onMaximumHeightChanged: movingForResize = true; onXChanged: { if (movingForResize) { movingForResize = false; return; } if (!animationsEnabled) { startupTimer.restart(); return; } translation.x = oldX - x translation.y = oldY - y translAnim.running = true oldX = x oldY = y } onYChanged: { if (movingForResize) { movingForResize = false; return; } if (!animationsEnabled) { startupTimer.restart(); return; } translation.x = oldX - x translation.y = oldY - y translAnim.running = true oldX = x oldY = y } transform: Translate { id: translation } NumberAnimation { id: translAnim duration: units.longDuration easing.type: Easing.InOutQuad target: translation properties: "x,y" to: 0 } } } //END components //BEGIN UI elements Item { id: lastSpacer parent: currentLayout Layout.fillWidth: true Layout.fillHeight: true } Item { id: dndSpacer Layout.preferredWidth: width Layout.preferredHeight: height width: (plasmoid.formFactor === PlasmaCore.Types.Vertical) ? currentLayout.width : theme.mSize(theme.defaultFont).width * 10 height: (plasmoid.formFactor === PlasmaCore.Types.Vertical) ? theme.mSize(theme.defaultFont).width * 10 : currentLayout.height } // while the user is moving the applet when configuring the panel, the applet is reparented // here so it can be moved freely; previously it was reparented to "root" but this one does not // take into account the toolbox (which is left-of) the layout in right-to-left languages Item { id: moveAppletLayer anchors.fill: currentLayout } GridLayout { id: currentLayout property bool isLayoutHorizontal rowSpacing: units.smallSpacing columnSpacing: units.smallSpacing Layout.preferredWidth: { var width = 0; for (var i = 0, length = currentLayout.children.length; i < length; ++i) { var item = currentLayout.children[i]; if (item.Layout) { width += Math.max(item.Layout.minimumWidth, item.Layout.preferredWidth); } } return width; } Layout.preferredHeight: { var height = 0; for (var i = 0, length = currentLayout.children.length; i < length; ++i) { var item = currentLayout.children[i]; if (item.Layout) { height += Math.max(item.Layout.minimumHeight, item.Layout.preferredHeight); } } return height; } rows: 1 columns: 1 //when horizontal layout top-to-bottom, this way it will obey our limit of one row and actually lay out left to right flow: isHorizontal ? GridLayout.TopToBottom : GridLayout.LeftToRight layoutDirection: Qt.application.layoutDirection } onWidthChanged: { containmentSizeSyncTimer.restart() if (startupTimer.running) { startupTimer.restart(); } } onHeightChanged: { containmentSizeSyncTimer.restart() if (startupTimer.running) { startupTimer.restart(); } } Timer { id: containmentSizeSyncTimer interval: 150 onTriggered: { dndSpacer.parent = root; - currentLayout.x = (isHorizontal && toolBox && Qt.application.layoutDirection === Qt.RightToLeft && (plasmoid.editMode || plasmoid.userConfiguring)) ? toolBox.width : 0; + currentLayout.x = (isHorizontal && toolBox && Qt.application.layoutDirection === Qt.RightToLeft && plasmoid.editMode) ? toolBox.width : 0; currentLayout.y = 0 - currentLayout.width = root.width - (isHorizontal && toolBox && (plasmoid.editMode || plasmoid.userConfiguring) ? toolBox.width : 0) - currentLayout.height = root.height - (!isHorizontal && toolBox && (plasmoid.editMode || plasmoid.userConfiguring) ? toolBox.height : 0) + currentLayout.width = root.width - (isHorizontal && toolBox && plasmoid.editMode ? toolBox.width : 0) + currentLayout.height = root.height - (!isHorizontal && toolBox && plasmoid.editMode ? toolBox.height : 0) currentLayout.isLayoutHorizontal = isHorizontal } } //FIXME: I don't see other ways at the moment a way to see when the UI is REALLY ready Timer { id: startupTimer interval: 4000 onTriggered: { for (var i = 0; i < currentLayout.children.length; ++i) { var item = currentLayout.children[i]; if (item.hasOwnProperty("animationsEnabled")) { item.animationsEnabled = true; } } } } //END UI elements } diff --git a/toolboxes/desktoptoolbox/contents/ui/ToolBoxButton.qml b/toolboxes/desktoptoolbox/contents/ui/ToolBoxButton.qml index 8d3ed4e56..dea8468af 100644 --- a/toolboxes/desktoptoolbox/contents/ui/ToolBoxButton.qml +++ b/toolboxes/desktoptoolbox/contents/ui/ToolBoxButton.qml @@ -1,309 +1,235 @@ /*************************************************************************** * Copyright 2012,2015 by Sebastian Kügler * * Copyright 2015 by Kai Uwe Broulik * * * * 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.Layouts 1.4 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents +import org.kde.plasma.components 3.0 as PlasmaComponents3 import org.kde.kquickcontrolsaddons 2.0 as KQuickControlsAddons import org.kde.plasma.plasmoid 2.0 Item { id: toolBoxButton - property string text: main.Plasmoid.activityName === i18n("Default") ? i18n("Desktop Toolbox") : i18n("Desktop Toolbox — %1 Activity", main.Plasmoid.activityName) - property bool isCorner: !buttonMouse.dragging && - ((state == "topleft") || (state == "topright") || - (state == "bottomright") || (state == "bottomleft")) - property bool isHorizontal: (state != "left" && state != "right") - - rotation: switch(state) { - case "left": - return -90; - case "right": - return 90; - default: - return 0; - } - transform: Translate { - x: state == "left" ? Math.round(-width/2 + height/2) : state == "right" ? + Math.round(width/2 - height/2) : 0 - Behavior on x { + y: plasmoid.editMode ? 0 + : state == "top" || state == "topcenter" ? -height + : state == "bottom" || state == "bottomcenter" ? height + : 0 + + Behavior on y { NumberAnimation { - duration: units.shortDuration * 3; - easing.type: Easing.InOutExpo; + duration: units.longDuration + easing.type: Easing.InOutQuad } } } transformOrigin: Item.Center + opacity: plasmoid.editMode + Behavior on opacity { + OpacityAnimator { + duration: units.longDuration + easing.type: Easing.InOutQuad + } + enabled: visible + } Behavior on rotation { NumberAnimation { duration: units.shortDuration * 3; easing.type: Easing.InOutExpo; } enabled: visible } Behavior on x { NumberAnimation { duration: units.shortDuration * 3; easing.type: Easing.InOutExpo; } enabled: visible } Behavior on y { NumberAnimation { duration: units.shortDuration * 3; easing.type: Easing.InOutExpo; } enabled: visible } - clip: backgroundFrameWidthAnimation.running - width: backgroundFrame.width + backgroundFrame.width % 2 - height: backgroundFrame.height + backgroundFrame.height % 2 + width: buttonLayout.width + height: buttonLayout.height - //x and y default to 0, so top left would be correct - //If the position is anything else it will updated via onXChanged during initialization - state: "topleft" + state: "topcenter" onXChanged: stateTimer.restart() onYChanged: stateTimer.restart() Timer { id: stateTimer - interval: 100 + interval: 0 onTriggered: updateState() } function updateState() { var container = main; //print(" w: " + container.width +"x"+container.height+" : "+x+"/"+y+" tbw: " + toolBoxButton.width); var x = toolBoxButton.x - main.x; var y = toolBoxButton.y - main.y; var cornerSnap = iconWidth - if (x < cornerSnap && y < cornerSnap) { - toolBoxButton.state = "topleft"; - } else if (container.width - x - buttonLayout.width < cornerSnap && y < cornerSnap) { - toolBoxButton.state = "topright"; - } else if (container.width - x - buttonLayout.width < cornerSnap && container.height - y - buttonLayout.width < cornerSnap) { - toolBoxButton.state = "bottomright"; - } else if (x < cornerSnap && container.height - y - buttonLayout.width < cornerSnap) { - toolBoxButton.state = "bottomleft"; - //top diagonal half - } else if (x > (y * (container.width/container.height))) { - //Top edge - if (container.width - x > y ) { - toolBoxButton.state = "top"; - //right edge + //top + if (y + height / 2 < container.height / 2) { + if (Math.abs(container.width/2 - (x + width/2)) < units.gridUnit) { + toolBoxButton.state = "topcenter"; } else { - //toolBoxButton.transformOrigin = Item.BottomRight - toolBoxButton.state = "right"; + toolBoxButton.state = "top"; } - //bottom diagonal half + //bottom } else { - //Left edge - if (container.height - y > x ) { - //toolBoxButton.transformOrigin = Item.TopLeft - toolBoxButton.state = "left"; - //Bottom edge + if (Math.abs(container.width/2 - (x + height/2)) < units.gridUnit) { + toolBoxButton.state = "bottomcenter"; } else { toolBoxButton.state = "bottom"; } } if (!buttonMouse.pressed) { main.placeToolBox(toolBoxButton.state); } stateTimer.running = false; } PlasmaCore.FrameSvgItem { id: backgroundFrame anchors { - left: parent.left - top: parent.top + fill: parent + leftMargin: -backgroundFrame.margins.left + topMargin: -backgroundFrame.margins.top + rightMargin: -backgroundFrame.margins.right + bottomMargin: -backgroundFrame.margins.bottom } - imagePath: "widgets/translucentbackground" - opacity: buttonMouse.containsMouse || (toolBoxLoader.item && toolBoxLoader.item.visible) ? 1.0 : 0.4 - width: Math.round((isCorner ? buttonLayout.height : buttonLayout.width) + margins.horizontal) + imagePath: "widgets/background" + width: Math.round(buttonLayout.width + margins.horizontal) height: Math.round(buttonLayout.height + margins.vertical) - Behavior on width { - NumberAnimation { - id: backgroundFrameWidthAnimation - duration: units.longDuration; - easing.type: Easing.InOutQuad; - } - } - Behavior on opacity { - NumberAnimation { - duration: units.longDuration; - } - } - } - - Row { - id: buttonLayout - anchors.centerIn: parent - height: Math.max(toolBoxIcon.height, fontMetrics.height) - spacing: units.smallSpacing - - Behavior on x { - NumberAnimation { - duration: units.longDuration; - easing.type: Easing.InOutQuad; - } - } - - PlasmaCore.SvgItem { - id: toolBoxIcon - svg: PlasmaCore.Svg { - id: iconSvg - imagePath: "widgets/configuration-icons" - onRepaintNeeded: toolBoxIcon.elementId = iconSvg.hasElement("menu") ? "menu" : "configure" - } - elementId: iconSvg.hasElement("menu") ? "menu" : "configure" - anchors.verticalCenter: parent.verticalCenter - width: iconSize - height: iconSize - opacity: buttonMouse.containsMouse || (toolBoxLoader.item && toolBoxLoader.item.visible) ? 1 : 0.5 - rotation: isHorizontal ? 0 : -90; - transformOrigin: Item.Center - Behavior on opacity { - NumberAnimation { - duration: units.longDuration; - easing.type: Easing.InOutExpo; - } - } - } - - PlasmaComponents.Label { - id: activityName - anchors.verticalCenter: parent.verticalCenter - opacity: isCorner ? 0 : 1 - text: toolBoxButton.text - visible: opacity - Behavior on opacity { - //only have this animation when going from hidden -> shown - enabled: activityName.opacity == 0 - - SequentialAnimation { - //pause to allow the toolbox frame to resize - //otherwise we see the text overflow the box - //whilst that animates - PauseAnimation { - duration: units.longDuration - } - NumberAnimation { - duration: units.shortDuration - easing.type: Easing.InOutExpo - } - } - } - } - - FontMetrics { - id: fontMetrics - font: activityName.font - } } MouseArea { id: buttonMouse property QtObject container: main property int pressedX property int pressedY + property int snapStartX + property bool snapX: false; property bool dragging: false - anchors { - fill: parent - margins: -10 - } + anchors.fill: parent drag { + filterChildren: true target: main.Plasmoid.immutable ? undefined : toolBoxButton minimumX: 0 - maximumX: container.width - toolBoxIcon.width + maximumX: container.width - toolBoxButton.width minimumY: 0 - maximumY: container.height - toolBoxIcon.height + maximumY: container.height } hoverEnabled: true onPressed: { pressedX = toolBoxButton.x pressedY = toolBoxButton.y } onPositionChanged: { if (pressed && (Math.abs(toolBoxButton.x - pressedX) > iconSize || Math.abs(toolBoxButton.y - pressedY) > iconSize)) { dragging = true; } + + // Center snapping X + if (snapX && Math.abs(snapStartX - mouse.x) > units.gridUnit) { + toolBoxButton.anchors.horizontalCenter = undefined; + snapX = false; + } else if (!snapX && Math.abs(main.width/2 - (toolBoxButton.x + toolBoxButton.width/2)) < units.gridUnit) { + toolBoxButton.anchors.horizontalCenter = main.horizontalCenter; + snapStartX = mouse.x; + snapX = true; + } } - onClicked: { - // the dialog auto-closes on losing focus - main.open = !main.dialogWasVisible - plasmoid.focus = true; - } + onReleased: { + toolBoxButton.anchors.horizontalCenter = undefined; + toolBoxButton.anchors.verticalCenter = undefined; + snapX = false; main.Plasmoid.configuration.ToolBoxButtonState = toolBoxButton.state; main.Plasmoid.configuration.ToolBoxButtonX = toolBoxButton.x; main.Plasmoid.configuration.ToolBoxButtonY = toolBoxButton.y; //print("Saved coordinates for ToolBox in config: " + toolBoxButton.x + ", " +toolBoxButton.x); if (dragging) { main.placeToolBox(); } dragging = false; stateTimer.stop(); updateState(); } onCanceled: dragging = false; - } - states: [ - State { - name: "topleft" - }, - State { - name: "top" - }, - State { - name: "topright" - }, - State { - name: "right" - }, - State { - name: "bottomright" - }, - State { - name: "bottom" - }, - State { - name: "bottomleft" - }, - State { - name: "left" + RowLayout { + id: buttonLayout + anchors.centerIn: parent + spacing: units.smallSpacing + + + PlasmaComponents3.ToolButton { + property QtObject qAction: plasmoid.action("add widgets") + text: qAction.text + icon.name: "list-add" + icon.height: units.iconSizes.smallMedium + onClicked: qAction.trigger() + } + PlasmaComponents3.ToolButton { + property QtObject qAction: plasmoid.globalAction("manage activities") + text: qAction.text + icon.name: "activities" + icon.height: units.iconSizes.smallMedium + onClicked: qAction.trigger() + } + PlasmaComponents3.ToolButton { + property QtObject qAction: plasmoid.action("configure") + text: qAction.text + icon.name: "preferences-desktop-wallpaper" + icon.height: units.iconSizes.smallMedium + onClicked: qAction.trigger() + } + PlasmaComponents3.ToolButton { + icon.name: "window-close" + icon.height: units.iconSizes.smallMedium + Layout.preferredWidth: height + onClicked: plasmoid.editMode = false + PlasmaComponents3.ToolTip { + text: i18n("Close Edit Mode") + } + } } - ] + } } diff --git a/toolboxes/desktoptoolbox/contents/ui/ToolBoxItem.qml b/toolboxes/desktoptoolbox/contents/ui/ToolBoxItem.qml deleted file mode 100644 index c0ca5c1ea..000000000 --- a/toolboxes/desktoptoolbox/contents/ui/ToolBoxItem.qml +++ /dev/null @@ -1,158 +0,0 @@ -/*************************************************************************** - * Copyright 2012 by Sebastian Kügler * - * Copyright 2015 by Kai Uwe Broulik * - * * - * 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 QtQuick.Layouts 1.1 - -import org.kde.plasma.core 2.0 as PlasmaCore -import org.kde.plasma.components 2.0 as PlasmaComponents - -ListView { - id: menu - width: units.gridUnit * 14 - height: contentHeight - - currentIndex: -1 - focus: true - keyNavigationWraps: true - - onVisibleChanged: currentIndex = -1 - - // needs to be on released, otherwise Dashboard hides because it already gained focus - // because of the dialog closing right on the key *press* event - Keys.onReleased: { - if (event.key === Qt.Key_Escape) { - main.open = false - event.accepted = true - } - } - - Keys.onPressed: { - if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { - if (currentIndex >= 0) { - if (model[currentIndex].operation) { - performOperation(model[currentIndex].operation) - } else { - model[currentIndex].trigger() - } - } - main.open = false - event.accepted = true - } - } - - function performOperation(what) { - var service = dataEngine.serviceForSource("PowerDevil"); - var operation = service.operationDescription(what); - return service.startOperationCall(operation); - } - - highlightMoveDuration: 0 - highlightResizeDuration: 0 - highlight: PlasmaComponents.Highlight { } - - PlasmaCore.DataSource { - id: dataEngine - engine: "powermanagement" - connectedSources: ["PowerDevil", "Sleep States"] - } - - model: { - var model = [] - var actions = plasmoid.actions - for (var i = 0, j = actions.length; i < j; ++i) { - var action = actions[i] - if (action && action.visible && action.text !== "") { - model.push(action) - } - } - - if (dataEngine.data["Sleep States"].LockScreen) { - model.push({ - text: i18nd("plasma_toolbox_org.kde.desktoptoolbox", "Lock Screen"), - icon: "system-lock-screen", - visible: true, - enabled: true, - operation: "lockScreen" - }) - } - - if (dataEngine.data["Sleep States"].Logout) { - model.push({ - text: i18nd("plasma_toolbox_org.kde.desktoptoolbox", "Leave"), - icon: "system-log-out", - visible: true, - enabled: true, - operation: "requestShutDown" // cannot put function() into a model :( - }) - } - - return model - } - - delegate: MouseArea { - width: menu.width - height: labelRow.implicitHeight + units.smallSpacing * 2 - hoverEnabled: true - enabled: modelData.enabled - opacity: modelData.enabled ? 1 : 0.5 - - onEntered: menu.currentIndex = index - onExited: menu.currentIndex = -1 - - onClicked: { - main.open = false - if (modelData.operation) { - performOperation(modelData.operation) - } else { - modelData.trigger() - } - } - - Accessible.role: Accessible.MenuItem - Accessible.name: textLabel.text - - RowLayout { - id: labelRow - anchors { - left: parent.left - right: parent.right - margins: units.smallSpacing - verticalCenter: parent.verticalCenter - } - spacing: units.smallSpacing - - PlasmaCore.IconItem { - implicitWidth: units.iconSizes.smallMedium - implicitHeight: implicitWidth - source: modelData.icon - Accessible.ignored: true - } - - PlasmaComponents.Label { - id: textLabel - Layout.fillWidth: true - text: modelData.text.replace("&", "") // hack to get rid of keyboard accelerator hints - elide: Text.ElideRight - Accessible.ignored: true - } - } - } -} diff --git a/toolboxes/desktoptoolbox/contents/ui/ToolBoxRoot.qml b/toolboxes/desktoptoolbox/contents/ui/ToolBoxRoot.qml index c27229341..4478d6eda 100644 --- a/toolboxes/desktoptoolbox/contents/ui/ToolBoxRoot.qml +++ b/toolboxes/desktoptoolbox/contents/ui/ToolBoxRoot.qml @@ -1,202 +1,111 @@ /* * Copyright 2011 Sebastian Kügler * Copyright 2011 Marco Martin * Copyright 2015 Kai Uwe Broulik * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 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 Library General Public License for more details * * You should have received a copy of the GNU Library 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.2 import QtQuick.Window 2.2 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.kquickcontrolsaddons 2.0 as KQuickControlsAddons import org.kde.plasma.extras 2.0 as PlasmaExtras import org.kde.plasma.plasmoid 2.0 Item { id: main objectName: "org.kde.desktoptoolbox" z: 999 anchors.fill: parent Connections { target: plasmoid onAvailableScreenRegionChanged: placeToolBoxTimer.restart(); } - //FIXME: this timer shouldn't exist, but unfortunately when the focus passes - //from the desktop to the dialog or vice versa, the event is not atomic - //and ends up with neither of those having focus, hiding the dialog when - //it shouldn't - Timer { - id: hideDialogTimer - interval: 0 - //NOTE: it's checking activeFocusItem instead of active as active doesn't correctly signal its change - property bool desktopOrDialogFocus: main.Window.activeFocusItem !== null || (toolBoxLoader.item && toolBoxLoader.item.activeFocusItem !== null) - onDesktopOrDialogFocusChanged: { - if (!desktopOrDialogFocus) { - hideDialogTimer.restart(); - } - - } - onTriggered: { - if (!desktopOrDialogFocus) { - open = false; - } - } - } - - signal minimumWidthChanged - signal minimumHeightChanged - signal maximumWidthChanged - signal maximumHeightChanged - signal preferredWidthChanged - signal preferredHeightChanged - property int iconSize: units.iconSizes.small property int iconWidth: units.iconSizes.smallMedium property int iconHeight: iconWidth property bool dialogWasVisible: false property bool open: false - onOpenChanged: { - plasmoid.editMode = open - if (open) { - plasmoid.contextualActionsAboutToShow(); - - toolBoxLoader.active = true; - toolBoxLoader.item.visible = true; - } else { - toolBoxLoader.item.visible = false; - } - } onWidthChanged: placeToolBoxTimer.restart(); onHeightChanged: placeToolBoxTimer.restart(); LayoutMirroring.enabled: (Qt.application.layoutDirection === Qt.RightToLeft) LayoutMirroring.childrenInherit: true - onVisibleChanged: { - if (!visible && toolBoxLoader.item) { - toolBoxLoader.item.visible = false - } - } - ToolBoxButton { id: toolBoxButton visible: false Component.onCompleted: { placeToolBox(plasmoid.configuration.ToolBoxButtonState); toolBoxButton.visible = true } } Timer { id: placeToolBoxTimer interval: 100 repeat: false running: false onTriggered: { placeToolBox(plasmoid.configuration.ToolBoxButtonState); } } - Loader { - id: toolBoxLoader - active: false - sourceComponent: PlasmaCore.Dialog { - id: dialog - flags: Qt.WindowStaysOnTopHint - location: PlasmaCore.Types.Floating - visualParent: toolBoxButton - // hideOnWindowDeactivate: true - mainItem: ToolBoxItem { - LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft - LayoutMirroring.childrenInherit: true - - Timer { - id: visibleTimer - interval: 300 - onTriggered: main.dialogWasVisible = dialog.visible - } - } - onVisibleChanged: visibleTimer.restart(); - } - } - function placeToolBox(ts) { // if nothing has been setup yet, determine default position based on layout direction if (!ts) { - if (Qt.application.layoutDirection === Qt.RightToLeft) { - placeToolBox("topleft"); - } else { - placeToolBox("topright"); - } + placeToolBox("topcenter"); return; } var tx = Plasmoid.configuration.ToolBoxButtonX var ty = Plasmoid.configuration.ToolBoxButtonY var pos; switch (ts) { case "top": ty = main.y; pos = plasmoid.adjustToAvailableScreenRegion(tx, ty, toolBoxButton.width, toolBoxButton.height); break; - case "left": - tx = main.x; - pos = plasmoid.adjustToAvailableScreenRegion(tx, ty, toolBoxButton.width, toolBoxButton.height); - break; - case "right": - tx = main.width + main.x - toolBoxButton.width; - pos = plasmoid.adjustToAvailableScreenRegion(tx, ty, toolBoxButton.width, toolBoxButton.height); - break; case "bottom": ty = main.height + main.y - toolBoxButton.height; pos = plasmoid.adjustToAvailableScreenRegion(tx, ty, toolBoxButton.width, toolBoxButton.height); break; - case "topleft": - tx = main.x; - ty = main.y; - pos = plasmoid.adjustToAvailableScreenRegion(tx, ty, toolBoxButton.height, toolBoxButton.height); - break; - case "topright": - tx = main.width + main.x - toolBoxButton.height; - ty = main.y; - pos = plasmoid.adjustToAvailableScreenRegion(tx, ty, toolBoxButton.height, toolBoxButton.height); - break; - case "bottomleft": - tx = main.x; + case "bottomcenter": + tx = main.width / 2 - toolBoxButton.width / 2; ty = main.height + main.y - toolBoxButton.height; - pos = plasmoid.adjustToAvailableScreenRegion(tx, ty, toolBoxButton.height, toolBoxButton.height); + pos = plasmoid.adjustToAvailableScreenRegion(tx, ty, toolBoxButton.width, toolBoxButton.height); break; - case "bottomright": + case "topcenter": default: - tx = main.width + main.x - toolBoxButton.height; - ty = main.height + main.y - toolBoxButton.height; - pos = plasmoid.adjustToAvailableScreenRegion(tx, ty, toolBoxButton.height, toolBoxButton.height); + tx = main.width / 2 - toolBoxButton.width / 2; + ty = main.y; + pos = plasmoid.adjustToAvailableScreenRegion(tx, ty, toolBoxButton.width, toolBoxButton.height); break; } //print("XXXY Setting toolbox to: " + ts + " " + tx + "x" + ty + " screen: " + main.width+ "x" + main.height+""); toolBoxButton.x = pos.x; toolBoxButton.y = pos.y; } } diff --git a/toolboxes/paneltoolbox/contents/ui/main.qml b/toolboxes/paneltoolbox/contents/ui/main.qml index 7b99004fd..1751e3505 100644 --- a/toolboxes/paneltoolbox/contents/ui/main.qml +++ b/toolboxes/paneltoolbox/contents/ui/main.qml @@ -1,93 +1,116 @@ /* * Copyright 2011 Sebastian Kügler * Copyright 2013 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 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 Library General Public License for more details * * You should have received a copy of the GNU Library 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 QtQuick 2.8 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.plasmoid 2.0 Item { id: main width: isVertical ? units.iconSizes.medium : units.iconSizes.smallMedium + units.smallSpacing * 2 height: isVertical ? units.iconSizes.smallMedium + units.smallSpacing * 2 : units.iconSizes.medium property bool isVertical: plasmoid.formFactor === 3 - opacity: plasmoid.editMode || plasmoid.userConfiguring ? (mouseArea.containsMouse || plasmoid.userConfiguring ? 1 : 0.5) : 0 z: 999 + states: [ + State { + when: plasmoid.editMode + PropertyChanges { + target: main + visible: true + } + PropertyChanges { + target: main + opacity: mouseArea.containsMouse || plasmoid.userConfiguring ? 1 : 0.5 + } + }, + State { + when: !plasmoid.editMode + PropertyChanges { + target: main + visible: false + } + PropertyChanges { + target: main + opacity: 0 + } + } + ] Behavior on opacity { - NumberAnimation { + OpacityAnimator { duration: units.longDuration; - easing.type: Easing.InOutExpo; + easing.type: Easing.InOutQuad; } } LayoutMirroring.enabled: (Qt.application.layoutDirection === Qt.RightToLeft) anchors { left: undefined top: undefined right: isVertical || !parent ? undefined : parent.right bottom: isVertical && parent ? parent.bottom : undefined verticalCenter: isVertical || !parent ? undefined : parent.verticalCenter horizontalCenter: isVertical && parent ? parent.horizontalCenter : undefined } PlasmaCore.SvgItem { id: toolBoxIcon svg: PlasmaCore.Svg { id: iconSvg imagePath: "widgets/configuration-icons" } elementId: "configure" anchors.centerIn: mouseArea width: units.iconSizes.small height: width } Connections { target: plasmoid onUserConfiguringChanged: { - plasmoid.editMode = plasmoid.userConfiguring; if (plasmoid.userConfiguring) { + plasmoid.editMode = true; toolTipArea.hideToolTip(); } } } MouseArea { id: mouseArea anchors.fill: parent hoverEnabled: enabled enabled: plasmoid.editMode || plasmoid.userConfiguring onClicked: { main.Plasmoid.action("configure").trigger() } PlasmaCore.ToolTipArea { id: toolTipArea anchors.fill: parent mainText: i18nd("plasma_toolbox_org.kde.paneltoolbox", "Configure Panel...") icon: "configure" enabled: mouseArea.containsMouse } } }