Warning, /plasma/plasma-desktop/kcms/keys/ui/main.qml is written in an unsupported language. File is not indexed.
0001 /*
0002 SPDX-FileCopyrightText: 2020 David Redondo <david@david-redondo.de>
0003
0004 SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006
0007 import QtCore
0008 import QtQuick
0009 import QtQuick.Dialogs
0010 import QtQuick.Layouts
0011 import QtQuick.Controls as QQC2
0012 import QtQml
0013 import QtQml.Models
0014
0015 import org.kde.kirigami as Kirigami
0016 import org.kde.kcmutils as KCM
0017 import org.kde.private.kcms.keys as Private
0018
0019 KCM.AbstractKCM {
0020 id: root
0021 implicitWidth: Kirigami.Units.gridUnit * 44
0022 implicitHeight: Kirigami.Units.gridUnit * 33
0023
0024 framedView: false
0025
0026 // order must be in sync with ComponentType enum in basemodel.h
0027 readonly property var sectionNames: [i18n("Applications"), i18n("Commands"), i18n("System Services"), i18n("Common Actions")]
0028
0029 property alias exportActive: exportInfo.visible
0030 readonly property bool errorOccured: kcm.lastError !== ""
0031
0032 Connections {
0033 target: kcm
0034 function onShowComponent(row) {
0035 components.currentIndex = row
0036 }
0037 }
0038
0039 actions: [
0040 Kirigami.Action {
0041 enabled: !exportActive
0042 icon.name: "document-import-symbolic"
0043 text: i18nc("@action: button Import shortcut scheme", "Import…")
0044 onTriggered: importSheet.open()
0045 }, Kirigami.Action {
0046 icon.name: exportActive ? "dialog-cancel-symbolic" : "document-export-symbolic"
0047 text: exportActive
0048 ? i18nc("@action:button", "Cancel Export")
0049 : i18nc("@action:button Export shortcut scheme", "Export…")
0050 onTriggered: {
0051 if (exportActive) {
0052 exportActive = false
0053 } else if (kcm.needsSave) {
0054 exportWarning.visible = true
0055 } else {
0056 search.text = ""
0057 exportActive = true
0058 }
0059 }
0060 }
0061 ]
0062
0063 header: ColumnLayout {
0064 spacing: Kirigami.Units.smallSpacing
0065
0066 Kirigami.InlineMessage {
0067 Layout.fillWidth: true
0068 visible: errorOccured
0069 text: kcm.lastError
0070 type: Kirigami.MessageType.Error
0071 }
0072 Kirigami.InlineMessage {
0073 id: exportWarning
0074 Layout.fillWidth: true
0075 text: i18n("Cannot export scheme while there are unsaved changes")
0076 type: Kirigami.MessageType.Warning
0077 showCloseButton: true
0078 Binding on visible {
0079 when: exportWarning.visible
0080 value: kcm.needsSave
0081 restoreMode: Binding.RestoreNone
0082 }
0083 }
0084 Kirigami.InlineMessage {
0085 id: exportInfo
0086 Layout.fillWidth: true
0087 text: i18n("Select the components below that should be included in the exported scheme")
0088 type: Kirigami.MessageType.Information
0089 showCloseButton: true
0090 actions: [
0091 Kirigami.Action {
0092 icon.name: "document-save"
0093 text: i18nc("@action:button Save shortcut scheme", "Save Scheme")
0094 onTriggered: {
0095 shortcutSchemeFileDialogLoader.save = true
0096 shortcutSchemeFileDialogLoader.active = true
0097 exportActive = false
0098 }
0099 }
0100 ]
0101 }
0102 Kirigami.SearchField {
0103 id: search
0104 enabled: !errorOccured && !exportActive
0105 Layout.fillWidth: true
0106 Binding {
0107 target: kcm.filteredModel
0108 property: "filter"
0109 value: search.text
0110 restoreMode: Binding.RestoreBinding
0111 }
0112 }
0113 }
0114
0115 // Since we disabled the scroll views' frame and background, we're responsible
0116 // for setting the background color ourselves, because the background color
0117 // of the page it sits on top of doesn't have the right color for these views.
0118 Rectangle {
0119 anchors.fill: parent
0120 Kirigami.Theme.inherit: false
0121 Kirigami.Theme.colorSet: Kirigami.Theme.View
0122 color: Kirigami.Theme.backgroundColor
0123
0124 RowLayout {
0125 anchors.fill: parent
0126 enabled: !errorOccured
0127 spacing: 0
0128
0129 QQC2.ScrollView {
0130 id: categoryList
0131
0132 Layout.preferredWidth: Kirigami.Units.gridUnit * 14
0133 Layout.fillHeight: true
0134 clip: true
0135
0136 ListView {
0137 id: components
0138 clip: true
0139 model: kcm.filteredModel
0140 activeFocusOnTab: true
0141 add: Transition {
0142 id: transition
0143 PropertyAction {
0144 target: components
0145 property: "currentIndex"
0146 value: transition.ViewTransition.index
0147 }
0148 }
0149
0150 headerPositioning: ListView.OverlayHeader
0151 header: Kirigami.InlineViewHeader {
0152 width: ListView.view.width
0153 Kirigami.ActionToolBar {
0154 alignment: Qt.AlignRight
0155 enabled: !exportActive
0156
0157 actions: Kirigami.Action {
0158 text: i18nc("@action:button Add new shortcut", "Add New")
0159 icon.name: "list-add-symbolic"
0160 displayHint: Kirigami.DisplayHint.KeepVisible
0161
0162 Kirigami.Action {
0163 icon.name: "applications-all-symbolic"
0164 text: i18nc("@action:menu End of the sentence 'Add New Application…'", "Application…")
0165 onTriggered: kcm.addApplication(root)
0166 }
0167
0168 Kirigami.Action {
0169 icon.name: "scriptnew-symbolic"
0170 text: i18nc("@action:menu End of the sentence 'Add New Command or Script…'", "Command or Script…")
0171 onTriggered: addCommandDialog.open()
0172 }
0173 }
0174 }
0175 }
0176
0177 delegate: QQC2.ItemDelegate {
0178 id: componentDelegate
0179 width: ListView.view.width
0180
0181 KeyNavigation.right: shortcutsList
0182
0183 onClicked: ListView.view.currentIndex = index
0184 highlighted: ListView.isCurrentItem
0185
0186 contentItem: RowLayout {
0187 spacing: Kirigami.Units.smallSpacing
0188 Kirigami.IconTitleSubtitle {
0189 id: label
0190
0191 icon.name: model.decoration
0192 title: model.display
0193 Layout.fillWidth: true
0194 opacity: model.pendingDeletion ? 0.5 : 1.0
0195 selected: componentDelegate.highlighted || componentDelegate.down
0196 }
0197
0198 QQC2.CheckBox {
0199 checked: model.checked
0200 visible: exportActive
0201 onToggled: model.checked = checked
0202 }
0203 QQC2.Button {
0204 id: editButton
0205
0206 implicitHeight: label.implicitHeight
0207 implicitWidth: implicitHeight
0208
0209 visible: model.section === Private.ComponentType.Command
0210 && !exportActive
0211 && !model.pendingDeletion
0212 && (componentDelegate.hovered || componentDelegate.ListView.isCurrentItem)
0213 icon.name: "edit-rename"
0214 onClicked: {
0215 addCommandDialog.editing = true;
0216 addCommandDialog.componentName = model.component;
0217 // for commands, Name == Exec
0218 addCommandDialog.oldExec = model.display;
0219 addCommandDialog.commandListItemDelegate = componentDelegate;
0220 addCommandDialog.open();
0221 }
0222 QQC2.ToolTip {
0223 text: i18nc("@tooltip:button %1 is the text of a custom command", "Edit command for %1", model.display)
0224 }
0225 }
0226 QQC2.Button {
0227 id: deleteButton
0228
0229 implicitHeight: label.implicitHeight
0230 implicitWidth: implicitHeight
0231
0232 visible: model.section !== Private.ComponentType.CommonAction
0233 && !exportActive
0234 && !model.pendingDeletion
0235 && (componentDelegate.hovered || componentDelegate.ListView.isCurrentItem)
0236 icon.name: "edit-delete"
0237 onClicked: model.pendingDeletion = true
0238 QQC2.ToolTip {
0239 text: i18n("Remove all shortcuts for %1", model.display)
0240 }
0241 }
0242 QQC2.Button {
0243 implicitHeight: label.implicitHeight
0244 implicitWidth: implicitHeight
0245
0246 visible: !exportActive && model.pendingDeletion
0247 icon.name: "edit-undo"
0248 onClicked: model.pendingDeletion = false
0249 QQC2.ToolTip {
0250 text: i18n("Undo deletion")
0251 }
0252 }
0253 Rectangle {
0254 id: defaultIndicator
0255 radius: width * 0.5
0256 implicitWidth: Kirigami.Units.largeSpacing
0257 implicitHeight: Kirigami.Units.largeSpacing
0258 visible: kcm.defaultsIndicatorsVisible
0259 opacity: !model.isDefault
0260 color: Kirigami.Theme.neutralTextColor
0261 }
0262 }
0263 }
0264
0265 section.property: "section"
0266 section.delegate: Kirigami.ListSectionHeader {
0267 label: root.sectionNames[section]
0268 width: components.width
0269 QQC2.CheckBox {
0270 id: sectionCheckbox
0271 Layout.alignment: Qt.AlignRight
0272 // width of indicator + layout spacing
0273 Layout.rightMargin: kcm.defaultsIndicatorsVisible ? Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing : 0
0274 visible: exportActive
0275 onToggled: {
0276 const checked = sectionCheckbox.checked
0277 const startIndex = kcm.shortcutsModel.index(0, 0)
0278 const indices = kcm.shortcutsModel.match(startIndex, Private.BaseModel.SectionRole, section, -1, Qt.MatchExactly)
0279 for (const index of indices) {
0280 kcm.shortcutsModel.setData(index, checked, Private.BaseModel.CheckedRole)
0281 }
0282 }
0283 Connections {
0284 enabled: exportActive
0285 target: kcm.shortcutsModel
0286 function onDataChanged (topLeft, bottomRight, roles) {
0287 const startIndex = kcm.shortcutsModel.index(0, 0)
0288 const indices = kcm.shortcutsModel.match(startIndex, Private.BaseModel.SectionRole, section, -1, Qt.MatchExactly)
0289 sectionCheckbox.checked = indices.reduce((acc, index) => acc && kcm.shortcutsModel.data(index, Private.BaseModel.CheckedRole), true)
0290 }
0291 }
0292 }
0293 }
0294
0295 onCurrentItemChanged: dm.rootIndex = kcm.filteredModel.index(currentIndex, 0)
0296 onCurrentIndexChanged: {
0297 shortcutsList.selectedIndex = -1;
0298 }
0299
0300 Kirigami.PlaceholderMessage {
0301 anchors.centerIn: parent
0302 width: parent.width - (Kirigami.Units.largeSpacing * 4)
0303 visible: components.count === 0 && search.text.length > 0
0304 text: i18n("No items matched the search terms")
0305 }
0306 }
0307 }
0308
0309 Kirigami.Separator {
0310 Layout.fillHeight: true
0311 }
0312
0313 QQC2.ScrollView {
0314 enabled: !exportActive
0315 id: shortcutsScroll
0316 Layout.fillHeight: true
0317 Layout.fillWidth: true
0318 clip: true
0319
0320 ListView {
0321 clip:true
0322 id: shortcutsList
0323 property int selectedIndex: -1
0324 model: DelegateModel {
0325 id: dm
0326 model: rootIndex.valid ? kcm.filteredModel : undefined
0327 delegate: ShortcutActionDelegate {
0328 showExpandButton: shortcutsList.count > 1
0329 }
0330 KeyNavigation.left: components
0331 }
0332
0333 Kirigami.PlaceholderMessage {
0334 anchors.centerIn: parent
0335 width: parent.width - (Kirigami.Units.largeSpacing * 4)
0336 visible: components.currentIndex == -1
0337 text: i18n("Select an item from the list to view its shortcuts here")
0338 }
0339 }
0340 }
0341 }
0342 }
0343
0344 Loader {
0345 id: shortcutSchemeFileDialogLoader
0346 active: false
0347 property bool save
0348 sourceComponent: FileDialog {
0349 title: save ? i18n("Export Shortcut Scheme") : i18n("Import Shortcut Scheme")
0350 currentFolder: StandardPaths.standardLocations(StandardPaths.HomeLocation)[0]
0351 nameFilters: [ i18nc("Template for file dialog","Shortcut Scheme (*.kksrc)") ]
0352 defaultSuffix: ".kksrc"
0353 fileMode: shortcutSchemeFileDialogLoader.save ? FileDialog.SaveFile : FileDialog.OpenFile
0354 Component.onCompleted: open()
0355 onAccepted: {
0356 if (save) {
0357 kcm.writeScheme(selectedFile)
0358 } else {
0359 var schemes = schemeBox.model
0360 schemes.splice(schemes.length - 1, 0, {name: kcm.urlFilename(selectedFile), url: selectedFile})
0361 schemeBox.model = schemes
0362 schemeBox.currentIndex = schemes.length - 2
0363 }
0364 shortcutSchemeFileDialogLoader.active = false
0365 }
0366 onRejected: shortcutSchemeFileDialogLoader.active = false
0367 }
0368 }
0369
0370 Kirigami.PromptDialog {
0371 id: addCommandDialog
0372 property bool editing: false
0373 property string componentName: ""
0374 property string oldExec: ""
0375 property Item commandListItemDelegate: null
0376
0377 width: Math.max(root.width / 2, Kirigami.Units.gridUnit * 24)
0378
0379 title: editing ? i18n("Edit Command") : i18n("Add Command")
0380
0381 onVisibleChanged: {
0382 if (visible) {
0383 cmdField.clear();
0384 cmdField.forceActiveFocus();
0385 if (editing) {
0386 cmdField.text = oldExec;
0387 }
0388 }
0389 }
0390 onRejected: {
0391 if (addCommandDialog.editing) {
0392 addCommandDialog.editing = false;
0393 }
0394 }
0395
0396 property Kirigami.Action addCommandAction: Kirigami.Action {
0397 text: addCommandDialog.editing ? i18n("Save") : i18n("Add")
0398 icon.name: addCommandDialog.editing ? "dialog-ok" : "list-add"
0399 enabled: cmdField.length > 0
0400 onTriggered: {
0401 if (addCommandDialog.editing) {
0402 const newLabel = kcm.editCommand(addCommandDialog.componentName, cmdField.text);
0403 if (addCommandDialog.commandListItemDelegate) {
0404 addCommandDialog.commandListItemDelegate.label = newLabel;
0405 }
0406 } else {
0407 kcm.addCommand(cmdField.text);
0408 }
0409 addCommandDialog.editing = false;
0410 addCommandDialog.close();
0411 }
0412 }
0413
0414 standardButtons: Kirigami.Dialog.NoButton
0415
0416 customFooterActions: [addCommandAction]
0417
0418 ColumnLayout {
0419 anchors.centerIn: parent
0420 spacing: Kirigami.Units.smallSpacing
0421
0422 QQC2.Label {
0423 text: i18n("Enter a command or choose a script file:")
0424 textFormat: Text.PlainText
0425 }
0426 RowLayout {
0427 Layout.fillWidth: true
0428 spacing: Kirigami.Units.smallSpacing
0429
0430 QQC2.TextField {
0431 id: cmdField
0432 Layout.fillWidth: true
0433 font.family: "monospace"
0434 onAccepted: addCommandDialog.addCommandAction.triggered()
0435 }
0436 QQC2.Button {
0437 icon.name: "document-open"
0438 text: i18nc("@action:button", "Choose…")
0439 onClicked: {
0440 openScriptFileDialogLoader.active = true
0441 }
0442 }
0443 }
0444 }
0445 }
0446
0447 Loader {
0448 id: openScriptFileDialogLoader
0449 active: false
0450 sourceComponent: FileDialog {
0451 title: i18nc("@title:window", "Choose Script File")
0452 currentFolder: StandardPaths.standardLocations(StandardPaths.HomeLocation)[0]
0453 nameFilters: [ i18nc("Template for file dialog","Script file (*.*sh)") ]
0454 Component.onCompleted: open()
0455 onAccepted: {
0456 cmdField.text = selectedFile
0457 cmdField.text = kcm.quoteUrl(selectedFile)
0458 openScriptFileDialogLoader.active = false
0459 }
0460 onRejected: openScriptFileDialogLoader.active = false
0461 }
0462 }
0463
0464 Kirigami.OverlaySheet {
0465 id: importSheet
0466
0467 title: i18n("Import Shortcut Scheme")
0468
0469 ColumnLayout {
0470 anchors.centerIn: parent
0471 spacing: Kirigami.Units.smallSpacing
0472
0473 QQC2.Label {
0474 text: i18n("Select the scheme to import:")
0475 textFormat: Text.PlainText
0476 Layout.margins: Kirigami.Units.largeSpacing
0477 }
0478
0479 RowLayout {
0480 spacing: Kirigami.Units.smallSpacing
0481 Layout.margins: Kirigami.Units.largeSpacing
0482
0483 QQC2.ComboBox {
0484 id: schemeBox
0485 readonly property bool customSchemeSelected: currentIndex == count - 1
0486 property string url: ""
0487 currentIndex: count - 1
0488 textRole: "name"
0489 onActivated: url = model[index]["url"]
0490 Component.onCompleted: {
0491 var defaultSchemes = kcm.defaultSchemes()
0492 defaultSchemes.push({name: i18n("Custom Scheme"), url: "unused"})
0493 model = defaultSchemes
0494 }
0495 }
0496 QQC2.Button {
0497 text: schemeBox.customSchemeSelected ? i18n("Select File…") : i18n("Import")
0498 onClicked: {
0499 if (schemeBox.customSchemeSelected) {
0500 shortcutSchemeFileDialogLoader.save = false;
0501 shortcutSchemeFileDialogLoader.active = true;
0502 } else {
0503 kcm.loadScheme(schemeBox.model[schemeBox.currentIndex]["url"])
0504 importSheet.close()
0505 }
0506 }
0507 }
0508 }
0509 }
0510 }
0511 }
0512