plasma-virtual-desktop-swit.../contents/ui/main.qml

442 lines
24 KiB
QML

import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
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.pager 2.0
Item {
id: root
Plasmoid.preferredRepresentation: Plasmoid.compactRepresentation
// ─────────────────────────────────────────────────────────────────────────
// INTERNATIONALIZATION (i18n)
// ─────────────────────────────────────────────────────────────────────────
readonly property string systemLang: Qt.locale().name.substring(0, 2)
readonly property var translations: ({
"en": { desktop: "Desktop", add: "Add", desktops: "desktops", rename: "Rename...", delete_: "Delete",
renameTitle: "Rename Desktop", switchTo: "Switch to", moveWindowHere: "Move window here",
newDesktop: "New Desktop", confirmDelete: "Delete this desktop?" },
"es": { desktop: "Escritorio", add: "Agregar", desktops: "escritorios", rename: "Renombrar...", delete_: "Eliminar",
renameTitle: "Renombrar Escritorio", switchTo: "Cambiar a", moveWindowHere: "Mover ventana aquí",
newDesktop: "Nuevo Escritorio", confirmDelete: "¿Eliminar este escritorio?" },
"zh": { desktop: "桌面", add: "添加", desktops: "个桌面", rename: "重命名...", delete_: "删除",
renameTitle: "重命名桌面", switchTo: "切换到", moveWindowHere: "移动窗口到此处",
newDesktop: "新建桌面", confirmDelete: "删除此桌面?" },
"fr": { desktop: "Bureau", add: "Ajouter", desktops: "bureaux", rename: "Renommer...", delete_: "Supprimer",
renameTitle: "Renommer le bureau", switchTo: "Basculer vers", moveWindowHere: "Déplacer la fenêtre ici",
newDesktop: "Nouveau bureau", confirmDelete: "Supprimer ce bureau ?" },
"de": { desktop: "Desktop", add: "Hinzufügen", desktops: "Desktops", rename: "Umbenennen...", delete_: "Löschen",
renameTitle: "Desktop umbenennen", switchTo: "Wechseln zu", moveWindowHere: "Fenster hierher verschieben",
newDesktop: "Neuer Desktop", confirmDelete: "Diesen Desktop löschen?" },
"pt": { desktop: "Área de trabalho", add: "Adicionar", desktops: "áreas de trabalho", rename: "Renomear...", delete_: "Excluir",
renameTitle: "Renomear área de trabalho", switchTo: "Mudar para", moveWindowHere: "Mover janela para cá",
newDesktop: "Nova área de trabalho", confirmDelete: "Excluir esta área de trabalho?" }
})
readonly property var t: translations[systemLang] || translations["en"]
// ─────────────────────────────────────────────────────────────────────────
// CONFIGURATION
// ─────────────────────────────────────────────────────────────────────────
readonly property bool showPreviews: plasmoid.configuration.showWindowPreviews
readonly property bool showIcons: plasmoid.configuration.showWindowIcons
readonly property int previewSize: plasmoid.configuration.previewSize || 130
// ─────────────────────────────────────────────────────────────────────────
// PAGER MODEL (native KDE plugin with real window geometry)
// ─────────────────────────────────────────────────────────────────────────
PagerModel {
id: pagerModel
enabled: root.visible
showDesktop: false
pagerType: PagerModel.VirtualDesktops
}
// ─────────────────────────────────────────────────────────────────────────
// HELPER FUNCTIONS
// ─────────────────────────────────────────────────────────────────────────
function currentDesktopName() {
if (pagerModel.count > 0 && pagerModel.currentPage >= 0) {
var idx = pagerModel.index(pagerModel.currentPage, 0)
var name = pagerModel.data(idx, Qt.DisplayRole)
return name || (t.desktop + " " + (pagerModel.currentPage + 1))
}
return t.desktop
}
function addDesktop() { pagerModel.addDesktop() }
function removeDesktop(index) {
if (pagerModel.count > 1) {
pagerModel.changePage(index)
pagerModel.removeDesktop()
}
}
// ─────────────────────────────────────────────────────────────────────────
// HOVER TIMERS
// ─────────────────────────────────────────────────────────────────────────
property bool hoverCompact: false
property bool hoverPopup: false
Timer { id: openTimer; interval: 80; onTriggered: plasmoid.expanded = true }
Timer { id: closeTimer; interval: 300; onTriggered: if (!hoverCompact && !hoverPopup) plasmoid.expanded = false }
// ─────────────────────────────────────────────────────────────────────────
// COMPACT REPRESENTATION
// ─────────────────────────────────────────────────────────────────────────
Plasmoid.compactRepresentation: MouseArea {
id: compactArea
Layout.minimumWidth: compactLabel.implicitWidth + 16
hoverEnabled: true
Rectangle {
anchors.fill: parent
color: parent.containsMouse ? PlasmaCore.Theme.highlightColor : "transparent"
opacity: 0.2; radius: 3
}
PlasmaComponents.Label {
id: compactLabel
anchors.centerIn: parent
text: currentDesktopName()
font.bold: true
}
onEntered: { hoverCompact = true; closeTimer.stop(); openTimer.start() }
onExited: { hoverCompact = false; openTimer.stop(); if (!hoverPopup) closeTimer.start() }
onClicked: { openTimer.stop(); closeTimer.stop(); plasmoid.expanded = !plasmoid.expanded }
onWheel: {
var next = wheel.angleDelta.y > 0
? (pagerModel.currentPage - 1 + pagerModel.count) % pagerModel.count
: (pagerModel.currentPage + 1) % pagerModel.count
pagerModel.changePage(next)
}
}
// ─────────────────────────────────────────────────────────────────────────
// FULL REPRESENTATION
// ─────────────────────────────────────────────────────────────────────────
Plasmoid.fullRepresentation: Item {
id: popup
readonly property int cols: Math.max(1, Math.ceil(Math.sqrt(pagerModel.count)))
readonly property int rows: Math.max(1, Math.ceil(pagerModel.count / cols))
readonly property real deskW: previewSize
readonly property real deskH: pagerModel.pagerItemSize.height > 0
? deskW * pagerModel.pagerItemSize.height / pagerModel.pagerItemSize.width
: deskW * 9 / 16
readonly property real scaleX: deskW / Math.max(1, pagerModel.pagerItemSize.width)
readonly property real scaleY: deskH / Math.max(1, pagerModel.pagerItemSize.height)
Layout.preferredWidth: cols * (deskW + 8) + 32
Layout.preferredHeight: rows * (deskH + 8) + 70
Layout.minimumWidth: 200
Layout.minimumHeight: 120
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: { hoverPopup = true; closeTimer.stop() }
onExited: { hoverPopup = false; if (!hoverCompact) closeTimer.start() }
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 8
// ─────────────────────────────────────────────────────────────
// DESKTOP GRID
// ─────────────────────────────────────────────────────────────
Grid {
id: desktopGrid
Layout.fillWidth: true
Layout.fillHeight: true
Layout.alignment: Qt.AlignHCenter
columns: popup.cols
spacing: 6
Repeater {
id: repeater
model: pagerModel
Rectangle {
id: desktop
width: popup.deskW
height: popup.deskH
readonly property string desktopName: model.display || (t.desktop + " " + (index + 1))
readonly property bool isActive: index === pagerModel.currentPage
property bool isHovered: false
color: isActive ? Qt.darker(PlasmaCore.Theme.highlightColor, 1.3) : PlasmaCore.Theme.backgroundColor
border.width: isActive ? 2 : 1
border.color: isActive ? PlasmaCore.Theme.highlightColor : PlasmaCore.Theme.disabledTextColor
radius: 4
clip: true
opacity: isHovered ? 1 : 0.92
MouseArea {
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
onEntered: { hoverPopup = true; closeTimer.stop(); desktop.isHovered = true }
onExited: { desktop.isHovered = false }
onClicked: {
if (mouse.button === Qt.RightButton) {
ctxMenu.desktopIndex = index
ctxMenu.desktopName = desktop.desktopName
ctxMenu.popup()
} else {
pagerModel.changePage(index)
plasmoid.expanded = false
}
}
}
// ─────────────────────────────────────────────────
// DELETE BUTTON (top-right, on hover)
// ─────────────────────────────────────────────────
Rectangle {
id: deleteBtn
visible: desktop.isHovered && pagerModel.count > 1
anchors { top: parent.top; right: parent.right; margins: 2 }
width: Math.min(parent.width * 0.25, 44)
height: width
radius: width / 2
color: deleteMouseArea.containsMouse ? "#e74c3c" : Qt.rgba(0,0,0,0.6)
PlasmaCore.IconItem {
anchors.centerIn: parent
width: parent.width * 0.6
height: width
source: "edit-delete"
usesPlasmaTheme: false
}
MouseArea {
id: deleteMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: removeDesktop(index)
}
}
// ─────────────────────────────────────────────────
// WINDOWS with real geometry
// ─────────────────────────────────────────────────
Item {
anchors.fill: parent
anchors.margins: 2
clip: true
visible: showPreviews
Repeater {
model: TasksModel
Rectangle {
readonly property rect geo: model.Geometry
readonly property bool minimized: model.IsMinimized === true
x: Math.round(geo.x * popup.scaleX)
y: Math.round(geo.y * popup.scaleY)
width: Math.max(8, Math.round(geo.width * popup.scaleX))
height: Math.max(6, Math.round(geo.height * popup.scaleY))
visible: !minimized
color: model.IsActive ? Qt.rgba(1,1,1,0.4) : Qt.rgba(1,1,1,0.2)
border.width: 1
border.color: model.IsActive ? PlasmaCore.Theme.highlightColor : PlasmaCore.Theme.textColor
radius: 2
PlasmaCore.IconItem {
visible: showIcons && parent.width > 16 && parent.height > 12
anchors.centerIn: parent
width: Math.min(parent.width - 4, parent.height - 4, 20)
height: width
source: model.decoration || "application-x-executable"
usesPlasmaTheme: false
}
}
}
}
// ─────────────────────────────────────────────────
// DESKTOP LABEL (centered, when no previews)
// ─────────────────────────────────────────────────
Column {
anchors.centerIn: parent
visible: !showPreviews
PlasmaComponents.Label {
anchors.horizontalCenter: parent.horizontalCenter
text: index + 1
font.bold: true; font.pixelSize: 18
color: desktop.isActive ? PlasmaCore.Theme.highlightedTextColor : PlasmaCore.Theme.textColor
}
PlasmaComponents.Label {
anchors.horizontalCenter: parent.horizontalCenter
text: desktop.desktopName
font.pixelSize: 10
color: desktop.isActive ? PlasmaCore.Theme.highlightedTextColor : PlasmaCore.Theme.textColor
width: popup.deskW - 10
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
}
}
// Badge when previews enabled
Rectangle {
visible: showPreviews
anchors { bottom: parent.bottom; horizontalCenter: parent.horizontalCenter; bottomMargin: 3 }
width: badgeLbl.implicitWidth + 12; height: badgeLbl.implicitHeight + 4
color: Qt.rgba(0,0,0,0.7); radius: 3
PlasmaComponents.Label {
id: badgeLbl; anchors.centerIn: parent
text: (index + 1) + " " + desktop.desktopName
font.pixelSize: 11; font.bold: true; color: "white"
}
}
}
}
}
// ─────────────────────────────────────────────────────────────
// BOTTOM BAR
// ─────────────────────────────────────────────────────────────
MouseArea {
Layout.fillWidth: true
Layout.preferredHeight: 36
hoverEnabled: true
onEntered: { hoverPopup = true; closeTimer.stop() }
onExited: { hoverPopup = false; if (!hoverCompact) closeTimer.start() }
RowLayout {
anchors.fill: parent
spacing: 8
PlasmaComponents.Button {
icon.name: "list-add"; text: t.add
onClicked: pagerModel.addDesktop()
}
Item { Layout.fillWidth: true }
PlasmaComponents.Label {
text: pagerModel.count + " " + t.desktops
opacity: 0.6; font.pixelSize: 11
}
// ─────────────────────────────────────────────────
// RESIZE HANDLE
// ─────────────────────────────────────────────────
Rectangle {
width: 20; height: 20
color: "transparent"
PlasmaCore.IconItem {
anchors.fill: parent
source: "transform-scale"
opacity: resizeMouseArea.containsMouse ? 1 : 0.5
}
MouseArea {
id: resizeMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.SizeFDiagCursor
property real startX: 0
property real startY: 0
property int startSize: 0
onPressed: {
startX = mouse.x
startY = mouse.y
startSize = previewSize
}
onPositionChanged: {
if (pressed) {
var delta = (mouse.x - startX + mouse.y - startY) / 2
var newSize = Math.max(80, Math.min(200, startSize + delta))
plasmoid.configuration.previewSize = Math.round(newSize)
}
}
}
}
}
}
}
// ─────────────────────────────────────────────────────────────────
// CONTEXT MENU
// ─────────────────────────────────────────────────────────────────
Menu {
id: ctxMenu
property int desktopIndex: 0
property string desktopName: ""
MenuItem {
text: t.switchTo + " \"" + ctxMenu.desktopName + "\""
icon.name: "go-jump"
onTriggered: {
pagerModel.changePage(ctxMenu.desktopIndex)
plasmoid.expanded = false
}
}
MenuSeparator {}
MenuItem {
text: t.rename
icon.name: "edit-rename"
onTriggered: {
renameDlg.idx = ctxMenu.desktopIndex
renameDlg.open()
renameField.text = ctxMenu.desktopName
renameField.selectAll()
}
}
MenuItem {
text: t.delete_
icon.name: "edit-delete"
enabled: pagerModel.count > 1
onTriggered: removeDesktop(ctxMenu.desktopIndex)
}
MenuSeparator {}
MenuItem {
text: t.newDesktop
icon.name: "list-add"
onTriggered: pagerModel.addDesktop()
}
}
// ─────────────────────────────────────────────────────────────────
// RENAME DIALOG
// ─────────────────────────────────────────────────────────────────
Dialog {
id: renameDlg
property int idx: 0
title: t.renameTitle
standardButtons: Dialog.Ok | Dialog.Cancel
anchors.centerIn: parent
contentItem: PlasmaComponents.TextField {
id: renameField
Layout.preferredWidth: 180
onAccepted: renameDlg.accept()
}
onAccepted: {
if (renameField.text.trim()) {
var desktopItem = repeater.itemAt(idx)
if (desktopItem) {
execSource.connectSource("qdbus org.kde.KWin /VirtualDesktopManager setDesktopName '" +
pagerModel.data(pagerModel.index(idx, 0), 0x0100 + 1) + "' '" +
renameField.text.trim().replace(/'/g, "'\\''") + "'")
}
pagerModel.refresh()
}
}
}
PlasmaCore.DataSource {
id: execSource
engine: "executable"
onNewData: disconnectSource(sourceName)
}
}
}