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 <noreply@anthropic.com>
This commit is contained in:
Andrés Eduardo García Márquez 2026-01-17 16:53:50 -05:00
parent 99eb2dbe17
commit 4d13665893
3 changed files with 167 additions and 37 deletions

View File

@ -12,9 +12,9 @@
<default>true</default> <default>true</default>
</entry> </entry>
<entry name="previewSize" type="Int"> <entry name="previewSize" type="Int">
<default>80</default> <default>130</default>
<min>40</min> <min>80</min>
<max>150</max> <max>200</max>
</entry> </entry>
</group> </group>
</kcfg> </kcfg>

View File

@ -26,10 +26,9 @@ Kirigami.FormLayout {
SpinBox { SpinBox {
id: previewSize id: previewSize
Kirigami.FormData.label: i18n("Preview Size:") Kirigami.FormData.label: i18n("Preview Size:")
from: 40 from: 80
to: 150 to: 200
stepSize: 10 stepSize: 10
enabled: showWindowPreviews.checked
textFromValue: function(value) { textFromValue: function(value) {
return value + " px" return value + " px"

View File

@ -10,11 +10,38 @@ Item {
id: root id: root
Plasmoid.preferredRepresentation: Plasmoid.compactRepresentation 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 // CONFIGURATION
// //
readonly property bool showPreviews: plasmoid.configuration.showWindowPreviews readonly property bool showPreviews: plasmoid.configuration.showWindowPreviews
readonly property bool showIcons: plasmoid.configuration.showWindowIcons readonly property bool showIcons: plasmoid.configuration.showWindowIcons
readonly property int previewSize: plasmoid.configuration.previewSize || 130
// //
// PAGER MODEL (native KDE plugin with real window geometry) // PAGER MODEL (native KDE plugin with real window geometry)
@ -33,13 +60,18 @@ Item {
if (pagerModel.count > 0 && pagerModel.currentPage >= 0) { if (pagerModel.count > 0 && pagerModel.currentPage >= 0) {
var idx = pagerModel.index(pagerModel.currentPage, 0) var idx = pagerModel.index(pagerModel.currentPage, 0)
var name = pagerModel.data(idx, Qt.DisplayRole) 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 addDesktop() { pagerModel.addDesktop() }
function removeDesktop() { pagerModel.removeDesktop() } function removeDesktop(index) {
if (pagerModel.count > 1) {
pagerModel.changePage(index)
pagerModel.removeDesktop()
}
}
// //
// HOVER TIMERS // HOVER TIMERS
@ -89,7 +121,7 @@ Item {
readonly property int cols: Math.max(1, Math.ceil(Math.sqrt(pagerModel.count))) 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 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 readonly property real deskH: pagerModel.pagerItemSize.height > 0
? deskW * pagerModel.pagerItemSize.height / pagerModel.pagerItemSize.width ? deskW * pagerModel.pagerItemSize.height / pagerModel.pagerItemSize.width
: deskW * 9 / 16 : deskW * 9 / 16
@ -97,7 +129,7 @@ Item {
readonly property real scaleY: deskH / Math.max(1, pagerModel.pagerItemSize.height) readonly property real scaleY: deskH / Math.max(1, pagerModel.pagerItemSize.height)
Layout.preferredWidth: cols * (deskW + 8) + 32 Layout.preferredWidth: cols * (deskW + 8) + 32
Layout.preferredHeight: rows * (deskH + 8) + 56 Layout.preferredHeight: rows * (deskH + 8) + 70
Layout.minimumWidth: 200 Layout.minimumWidth: 200
Layout.minimumHeight: 120 Layout.minimumHeight: 120
@ -133,25 +165,62 @@ Item {
width: popup.deskW width: popup.deskW
height: popup.deskH 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 readonly property bool isActive: index === pagerModel.currentPage
property bool isHovered: false
color: isActive ? Qt.darker(PlasmaCore.Theme.highlightColor, 1.3) : PlasmaCore.Theme.backgroundColor color: isActive ? Qt.darker(PlasmaCore.Theme.highlightColor, 1.3) : PlasmaCore.Theme.backgroundColor
border.width: isActive ? 2 : 1 border.width: isActive ? 2 : 1
border.color: isActive ? PlasmaCore.Theme.highlightColor : PlasmaCore.Theme.disabledTextColor border.color: isActive ? PlasmaCore.Theme.highlightColor : PlasmaCore.Theme.disabledTextColor
radius: 4 radius: 4
clip: true clip: true
opacity: isHovered ? 1 : 0.92
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
onEntered: { hoverPopup = true; closeTimer.stop(); parent.opacity = 1 } onEntered: { hoverPopup = true; closeTimer.stop(); desktop.isHovered = true }
onExited: parent.opacity = 0.92 onExited: { desktop.isHovered = false }
onClicked: mouse.button === Qt.RightButton ? ctxMenu.popup() : (pagerModel.changePage(index), plasmoid.expanded = 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 // WINDOWS with real geometry
@ -193,7 +262,7 @@ Item {
} }
// //
// DESKTOP LABEL (centered) // DESKTOP LABEL (centered, when no previews)
// //
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
@ -227,22 +296,6 @@ Item {
font.pixelSize: 11; font.bold: true; color: "white" 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,15 +314,94 @@ Item {
anchors.fill: parent anchors.fill: parent
spacing: 8 spacing: 8
PlasmaComponents.Button { PlasmaComponents.Button {
icon.name: "list-add"; text: "Add" icon.name: "list-add"; text: t.add
onClicked: pagerModel.addDesktop() onClicked: pagerModel.addDesktop()
} }
Item { Layout.fillWidth: true } Item { Layout.fillWidth: true }
PlasmaComponents.Label { PlasmaComponents.Label {
text: pagerModel.count + " desktops" text: pagerModel.count + " " + t.desktops
opacity: 0.6; font.pixelSize: 11 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()
} }
} }
@ -279,7 +411,7 @@ Item {
Dialog { Dialog {
id: renameDlg id: renameDlg
property int idx: 0 property int idx: 0
title: "Rename Desktop" title: t.renameTitle
standardButtons: Dialog.Ok | Dialog.Cancel standardButtons: Dialog.Ok | Dialog.Cancel
anchors.centerIn: parent anchors.centerIn: parent
contentItem: PlasmaComponents.TextField { contentItem: PlasmaComponents.TextField {
@ -289,7 +421,6 @@ Item {
} }
onAccepted: { onAccepted: {
if (renameField.text.trim()) { if (renameField.text.trim()) {
// Use KWin DBus to rename
var desktopItem = repeater.itemAt(idx) var desktopItem = repeater.itemAt(idx)
if (desktopItem) { if (desktopItem) {
execSource.connectSource("qdbus org.kde.KWin /VirtualDesktopManager setDesktopName '" + execSource.connectSource("qdbus org.kde.KWin /VirtualDesktopManager setDesktopName '" +