From 4d136658935ef50bd776932e6076c58ca522bd27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Eduardo=20Garc=C3=ADa=20M=C3=A1rquez?= Date: Sat, 17 Jan 2026 16:53:50 -0500 Subject: [PATCH] Add i18n, delete button, resize handle and improved context menu Features added: - Internationalization (i18n) with 6 languages: English, Spanish, Chinese, French, German, Portuguese - Auto-detects system language via Qt.locale() - Delete button appears on hover (top-right, 25% size, max 44px) - Resize handle in bottom-right corner to adjust preview size - Enhanced context menu with: Switch to, Rename, Delete, New Desktop - Updated config schema: previewSize range 80-200px, default 130px Co-Authored-By: Claude Opus 4.5 --- contents/config/main.xml | 6 +- contents/ui/configGeneral.qml | 5 +- contents/ui/main.qml | 193 ++++++++++++++++++++++++++++------ 3 files changed, 167 insertions(+), 37 deletions(-) diff --git a/contents/config/main.xml b/contents/config/main.xml index 2a4bda5..b393e7e 100644 --- a/contents/config/main.xml +++ b/contents/config/main.xml @@ -12,9 +12,9 @@ true - 80 - 40 - 150 + 130 + 80 + 200 diff --git a/contents/ui/configGeneral.qml b/contents/ui/configGeneral.qml index 4e43224..3164469 100644 --- a/contents/ui/configGeneral.qml +++ b/contents/ui/configGeneral.qml @@ -26,10 +26,9 @@ Kirigami.FormLayout { SpinBox { id: previewSize Kirigami.FormData.label: i18n("Preview Size:") - from: 40 - to: 150 + from: 80 + to: 200 stepSize: 10 - enabled: showWindowPreviews.checked textFromValue: function(value) { return value + " px" diff --git a/contents/ui/main.qml b/contents/ui/main.qml index f1756ce..0b4fcd3 100644 --- a/contents/ui/main.qml +++ b/contents/ui/main.qml @@ -10,11 +10,38 @@ 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) @@ -33,13 +60,18 @@ Item { if (pagerModel.count > 0 && pagerModel.currentPage >= 0) { var idx = pagerModel.index(pagerModel.currentPage, 0) var name = pagerModel.data(idx, Qt.DisplayRole) - return name || ("Desktop " + (pagerModel.currentPage + 1)) + return name || (t.desktop + " " + (pagerModel.currentPage + 1)) } - return "Desktop" + return t.desktop } function addDesktop() { pagerModel.addDesktop() } - function removeDesktop() { pagerModel.removeDesktop() } + function removeDesktop(index) { + if (pagerModel.count > 1) { + pagerModel.changePage(index) + pagerModel.removeDesktop() + } + } // ───────────────────────────────────────────────────────────────────────── // HOVER TIMERS @@ -89,7 +121,7 @@ Item { 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: 130 + readonly property real deskW: previewSize readonly property real deskH: pagerModel.pagerItemSize.height > 0 ? deskW * pagerModel.pagerItemSize.height / pagerModel.pagerItemSize.width : deskW * 9 / 16 @@ -97,7 +129,7 @@ Item { readonly property real scaleY: deskH / Math.max(1, pagerModel.pagerItemSize.height) Layout.preferredWidth: cols * (deskW + 8) + 32 - Layout.preferredHeight: rows * (deskH + 8) + 56 + Layout.preferredHeight: rows * (deskH + 8) + 70 Layout.minimumWidth: 200 Layout.minimumHeight: 120 @@ -133,25 +165,62 @@ Item { width: popup.deskW height: popup.deskH - readonly property string desktopName: model.display || ("Desktop " + (index + 1)) + 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(); parent.opacity = 1 } - onExited: parent.opacity = 0.92 - onClicked: mouse.button === Qt.RightButton ? ctxMenu.popup() : (pagerModel.changePage(index), plasmoid.expanded = false) + 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 + } + } } - opacity: 0.92 + // ───────────────────────────────────────────────── + // 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 @@ -193,7 +262,7 @@ Item { } // ───────────────────────────────────────────────── - // DESKTOP LABEL (centered) + // DESKTOP LABEL (centered, when no previews) // ───────────────────────────────────────────────── Column { anchors.centerIn: parent @@ -227,22 +296,6 @@ Item { font.pixelSize: 11; font.bold: true; color: "white" } } - - // Context menu - Menu { - id: ctxMenu - MenuItem { - text: "Rename..." - icon.name: "edit-rename" - onTriggered: { renameDlg.idx = index; renameDlg.open(); renameField.text = desktop.desktopName; renameField.selectAll() } - } - MenuItem { - text: "Delete" - icon.name: "edit-delete" - enabled: pagerModel.count > 1 - onTriggered: pagerModel.removeDesktop() - } - } } } } @@ -261,25 +314,104 @@ Item { anchors.fill: parent spacing: 8 PlasmaComponents.Button { - icon.name: "list-add"; text: "Add" + icon.name: "list-add"; text: t.add onClicked: pagerModel.addDesktop() } Item { Layout.fillWidth: true } PlasmaComponents.Label { - text: pagerModel.count + " desktops" + 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: "Rename Desktop" + title: t.renameTitle standardButtons: Dialog.Ok | Dialog.Cancel anchors.centerIn: parent contentItem: PlasmaComponents.TextField { @@ -289,7 +421,6 @@ Item { } onAccepted: { if (renameField.text.trim()) { - // Use KWin DBus to rename var desktopItem = repeater.itemAt(idx) if (desktopItem) { execSource.connectSource("qdbus org.kde.KWin /VirtualDesktopManager setDesktopName '" +