aerothemeplasma/plasma/plasmoids/org.kde.desktopcontainment/contents/ui/main.qml
2024-08-09 03:20:25 +02:00

404 lines
15 KiB
QML

/*
SPDX-FileCopyrightText: 2011-2013 Sebastian Kügler <sebas@kde.org>
SPDX-FileCopyrightText: 2011-2019 Marco Martin <mart@kde.org>
SPDX-FileCopyrightText: 2014-2015 Eike Hein <hein@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.15
import QtQuick.Layouts 1.15
import org.kde.plasma.plasmoid 2.0
import org.kde.plasma.core as PlasmaCore
import org.kde.ksvg 1.0 as KSvg
import org.kde.kquickcontrolsaddons 2.0 as KQuickControlsAddons
import org.kde.kirigami 2.20 as Kirigami
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
ContainmentItem {
id: root
switchWidth: { switchSize(); }
switchHeight: { switchSize(); }
// Only exists because the default CompactRepresentation doesn't:
// - open on drag
// - allow defining a custom drop handler
// TODO remove once it gains that feature (perhaps optionally?)
compactRepresentation: (isFolder && !isContainment) ? compactRepresentation : null
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.
function switchSize() {
// Support expanding into the full representation on very thick vertical panels.
if (isPopup && Plasmoid.formFactor === PlasmaCore.Types.Vertical) {
return Kirigami.Units.iconSizes.small * 8;
}
return 0;
}
LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft
LayoutMirroring.childrenInherit: true
property bool isFolder: (Plasmoid.pluginName === "org.kde.plasma.folder")
property bool isContainment: Plasmoid.isContainment
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: Kirigami.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.
readonly property Loader folderViewLayer: fullRepresentationItem.folderViewLayer
readonly property ContainmentLayoutManager.AppletsLayout appletsLayout: fullRepresentationItem.appletsLayout
// Plasmoid.title is set by a Binding {} in FolderViewLayer
toolTipSubText: ""
Plasmoid.icon: (!Plasmoid.configuration.useCustomIcon && folderViewLayer.ready) ? symbolicizeIconName(folderViewLayer.view.model.iconName) : Plasmoid.configuration.icon
onIconHeightChanged: updateGridSize()
// We want to do this here rather than in the model because we don't always want
// symbolic icons everywhere, but we do know that we always want them in this
// specific representation right here
function symbolicizeIconName(iconName) {
const symbolicSuffix = "-symbolic";
if (iconName.endsWith(symbolicSuffix)) {
return iconName;
}
return iconName + symbolicSuffix;
}
function updateGridSize() {
// onIconHeightChanged can be triggered before this component is complete and all the children are created
if (!toolBoxSvg) {
return;
}
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(forMinimumSize: bool): real {
if (isContainment || !folderViewLayer.ready) {
return -1;
} else if (useListViewMode) {
return (forMinimumSize ? folderViewLayer.view.cellHeight * 4 : Kirigami.Units.iconSizes.small * 16);
}
return (folderViewLayer.view.cellWidth * (forMinimumSize ? 1 : 3)) + (Kirigami.Units.iconSizes.small * 2);
}
function preferredHeight(forMinimumSize: bool): real {
let height;
if (isContainment || !folderViewLayer.ready) {
return -1;
} else if (useListViewMode) {
height = (folderViewLayer.view.cellHeight * (forMinimumSize ? 1 : 15)) + Kirigami.Units.smallSpacing;
} else {
height = (folderViewLayer.view.cellHeight * (forMinimumSize ? 1 : 2)) + Kirigami.Units.iconSizes.small;
}
if (Plasmoid.configuration.labelMode !== 0) {
height += folderViewLayer.item.labelHeight;
}
return height;
}
function isDrag(fromX, fromY, toX, toY) {
const length = Math.abs(fromX - toX) + Math.abs(fromY - toY);
return length >= Qt.styleHints.startDragDistance;
}
onFocusChanged: {
if (focus && isFolder) {
folderViewLayer.item.forceActiveFocus();
}
}
onExternalData: (mimetype, data) => {
Plasmoid.configuration.url = data
}
component ShortDropBehavior : Behavior {
NumberAnimation {
duration: Kirigami.Units.shortDuration
easing.type: Easing.InOutQuad
}
}
component LongDropBehavior : Behavior {
NumberAnimation {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
KSvg.FrameSvgItem {
id: highlightItemSvg
visible: false
imagePath: isPopup ? "widgets/viewitem" : ""
prefix: "hover"
}
KSvg.FrameSvgItem {
id: listItemSvg
visible: false
imagePath: isPopup ? "widgets/viewitem" : ""
prefix: "normal"
}
KSvg.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
}
// FIXME: the use and existence of this property is a workaround
preloadFullRepresentation: true
fullRepresentation: FolderViewDropArea {
id: dropArea
anchors {
fill: parent
leftMargin: (isContainment && root.availableScreenRect) ? root.availableScreenRect.x : 0
topMargin: (isContainment && root.availableScreenRect) ? root.availableScreenRect.y : 0
rightMargin: (isContainment && root.availableScreenRect && parent)
? (parent.width - root.availableScreenRect.x - root.availableScreenRect.width) : 0
bottomMargin: (isContainment && root.availableScreenRect && parent)
? (parent.height - root.availableScreenRect.y - root.availableScreenRect.height) : 0
}
LongDropBehavior on anchors.topMargin { }
LongDropBehavior on anchors.leftMargin { }
LongDropBehavior on anchors.rightMargin { }
LongDropBehavior on anchors.bottomMargin { }
property alias folderViewLayer: folderViewLayer
property alias appletsLayout: appletsLayout
Layout.minimumWidth: preferredWidth(!isPopup)
Layout.minimumHeight: preferredHeight(!isPopup)
Layout.preferredWidth: preferredWidth(false)
Layout.preferredHeight: preferredHeight(false)
// Maximum size is intentionally unbounded
preventStealing: true
onDragEnter: event => {
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: event => {
// 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: event => {
// Cancel autoscroll.
if (isFolder) {
handleDragEnd(folderViewLayer.view);
}
if (isContainment) {
appletsLayout.hidePlaceHolder();
}
}
onDrop: event => {
if (isFolder && FolderTools.isFileDrag(event)) {
handleDragEnd(folderViewLayer.view);
folderViewLayer.view.drop(root, event, mapToItem(folderViewLayer.view, event.x, event.y));
} else if (isContainment) {
root.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.containment.corona
ignoreUnknownSignals: true
function onEditModeChanged() {
appletsLayout.editMode = Plasmoid.containment.corona.editMode;
}
}
ContainmentLayoutManager.AppletsLayout {
id: appletsLayout
anchors.fill: parent
relayoutLock: width !== root.availableScreenRect.width || height !== root.availableScreenRect.height
// NOTE: use root.availableScreenRect and not own width and height as they are updated not atomically
configKey: "ItemGeometries-" + Math.round(root.screenGeometry.width) + "x" + Math.round(root.screenGeometry.height)
fallbackConfigKey: root.availableScreenRect.width > root.availableScreenRect.height ? "ItemGeometriesHorizontal" : "ItemGeometriesVertical"
containment: Plasmoid
containmentItem: root
editModeCondition: Plasmoid.immutable
? ContainmentLayoutManager.AppletsLayout.Locked
: ContainmentLayoutManager.AppletsLayout.AfterPressAndHold
// Sets the containment in edit mode when we go in edit mode as well
onEditModeChanged: Plasmoid.containment.corona.editMode = editMode;
minimumItemWidth: Kirigami.Units.iconSizes.small * 3
minimumItemHeight: minimumItemWidth
cellWidth: Kirigami.Units.iconSizes.small
cellHeight: cellWidth
eventManagerToFilter: folderViewLayer.item?.view.view ?? null
appletContainerComponent: ContainmentLayoutManager.BasicAppletContainer {
id: appletContainer
editModeCondition: Plasmoid.immutable
? ContainmentLayoutManager.ItemContainer.Locked
: ContainmentLayoutManager.ItemContainer.AfterPressAndHold
configOverlaySource: "ConfigOverlay.qml"
onUserDrag: (newPosition, dragCenter) => {
const pos = mapToItem(root.parent, dragCenter.x, dragCenter.y);
const newCont = root.containmentItemAt(pos.x, pos.y);
if (newCont && newCont.plasmoid !== Plasmoid) {
const newPos = newCont.mapFromApplet(Plasmoid, pos.x, pos.y);
// First go out of applet edit mode, get rid of the config overlay, release mouse grabs in preparation of applet reparenting
cancelEdit();
newCont.Plasmoid.addApplet(appletContainer.applet.plasmoid, Qt.rect(newPos.x, newPos.y, appletContainer.applet.width, appletContainer.applet.height));
appletsLayout.hidePlaceHolder();
}
}
ShortDropBehavior on x { }
ShortDropBehavior on y { }
}
placeHolder: ContainmentLayoutManager.PlaceHolder {}
Loader {
id: folderViewLayer
anchors.fill: parent
property bool ready: status === Loader.Ready
property Item view: item?.view ?? null
property QtObject model: 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.
function onPressed() {
folderViewLayer.forceActiveFocus();
}
}
}
}
PlasmaCore.Action {
id: configAction
text: i18n("Desktop and Wallpaper")
icon.name: "preferences-desktop-wallpaper"
shortcut: "alt+d,alt+s"
onTriggered: Plasmoid.containment.configureRequested(Plasmoid)
}
Component.onCompleted: {
if (!Plasmoid.isContainment) {
return;
}
Plasmoid.setInternalAction("configure", configAction)
updateGridSize();
}
}
}