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