// SPDX-FileCopyright: 2014 David Edmundson <kde@davidedmundson.co.uk
// SPDX-FileCopyright: 2020 David Redondo <kde@david-redondo.de
// SPDX-FileCopyright: 2022 Aleix Pol <aleixpol@kde.org>
// SPDX-FileCopyright: 2024 ivan tkachenko <me@ratijas.tk>
// SPDX-License-Identifier: LGPL-2.1-or-later
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.private.kquickcontrols as KQuickControlsPrivate
RowLayout {
id: root
property bool showClearButton: true
property bool showCancelButton: false /// TODO KF6 default to true
property alias modifierOnlyAllowed: helper.modifierOnlyAllowed
property alias modifierlessAllowed: helper.modifierlessAllowed
property alias multiKeyShortcutsAllowed: helper.multiKeyShortcutsAllowed
property alias keySequence: helper.currentKeySequence
/**
* This property controls which types of shortcuts are checked for conflicts when the keySequence
* is set. If a conflict is detected, a messagebox will be shown asking the user to confirm their
* input. Valid values are combinations of the following flags:
* - @p ShortcutType.None Do not check for conflicts.
* - @p ShortcutType.StandardShortcuts Check against standard shortcuts. @see KStandardshortcut
* - @p ShortcutType.GlobalShortcuts Check against global shortcuts. @see KGlobalAccel
*
* The default is `ShortcutType.GlobalShortcuts | ShortcutType.StandardShortcut`
* @since 5.74
*/
property alias checkForConflictsAgainst: helper.checkAgainstShortcutTypes
/**
* This signal is emitted after the user introduces a new key sequence
*
* @since 5.68
* @deprecated Use keySequenceModified()
*/
signal captureFinished()
/***
* Emitted whenever the key sequence is modified by the user, interacting with the component
*
* Either by interacting capturing a key sequence or pressing the clear button.
*
* @since 5.99
*/
signal keySequenceModified()
/**
* Start capturing a key sequence. This equivalent to the user clicking on the main button of the item
* @since 5.70
*/
function startCapturing() {
mainButton.checked = true;
}
// A layout like RowLayout would automatically and implicitly fillHeight
// if placed inside a ColumnLayout, so an explicit binding should prevent
// that behavior. On the contrary, filling width wouldn't hurt, although
// it doesn't make much sense, as this component is not really adaptive.
Layout.fillHeight: false
KQuickControlsPrivate.KeySequenceHelper {
id: helper
onGotKeySequence: keySequence => {
validator.validateSequence(keySequence)
}
onQuestionDialogAccepted: validator.accept()
onQuestionDialogRejected: validator.reject()
}
KQuickControlsPrivate.KeySequenceValidator {
id: validator
validateTypes: helper.checkAgainstShortcutTypes
onError: (title, message) => {
helper.showErrorDialog(title, message)
}
onQuestion: (title, message) => {
helper.showQuestionDialog(title, message)
}
onFinished: keySequence => {
helper.updateKeySequence(keySequence)
mainButton.checked = false
root.captureFinished()
root.keySequenceModified()
}
}
KQuickControlsPrivate.TranslationContext {
id: _tr
domain: "kdeclarative6"
}
QQC2.Button {
id: mainButton
Layout.fillHeight: true
icon.name: "configure"
checkable: true
focus: checked
hoverEnabled: true
text: {
const keySequence = helper.currentKeySequence;
const text = helper.keySequenceIsEmpty(keySequence)
? (helper.isRecording
? _tr.i18nc("What the user inputs now will be taken as the new shortcut", "Input")
: _tr.i18nc("No shortcut defined", "None"))
// Single ampersand gets interpreted by the button as a mnemonic
// and removed; replace it with a double ampersand so that it
// will be displayed by the button as a single ampersand, or
// else shortcuts with the actual ampersand character will
// appear to be partially empty.
: helper.keySequenceNativeText(keySequence).replace('&', '&&');
// These spaces are intentional
return " " + text + (helper.isRecording ? " ... " : " ");
}
Accessible.description: _tr.i18n("Click on the button, then enter the shortcut like you would in the program.\nExample for Ctrl+A: hold the Ctrl key and press A.")
Accessible.role: Accessible.Button
QQC2.ToolTip {
visible: mainButton.hovered
text: mainButton.Accessible.description
}
onCheckedChanged: {
if (checked) {
validator.currentKeySequence = root.keySequence
helper.window = helper.renderWindow(parent.Window.window)
mainButton.forceActiveFocus()
helper.startRecording()
} else if (helper.isRecording) {
helper.cancelRecording()
}
}
onFocusChanged: {
if (!focus) {
mainButton.checked = false
}
}
}
QQC2.Button {
id: clearButton
Layout.fillHeight: true
Layout.preferredWidth: height
visible: root.showClearButton && !helper.isRecording
onClicked: {
root.keySequence = "";
root.keySequenceModified();
root.captureFinished(); // Not really capturing, but otherwise we cannot track this state, hence apps should use keySequenceModified
}
enabled: !helper.keySequenceIsEmpty(helper.currentKeySequence)
hoverEnabled: true
// icon name determines the direction of the arrow, NOT the direction of the app layout
icon.name: Qt.application.layoutDirection === Qt.LeftToRight ? "edit-clear-locationbar-rtl" : "edit-clear-locationbar-ltr"
Accessible.name: _tr.i18nc("@info:tooltip", "Clear Key Sequence")
QQC2.ToolTip {
visible: clearButton.hovered
text: clearButton.Accessible.name
}
}
QQC2.Button {
Layout.fillHeight: true
Layout.preferredWidth: height
onClicked: helper.cancelRecording()
visible: root.showCancelButton && helper.isRecording
icon.name: "dialog-cancel"
Accessible.name: _tr.i18nc("@info:tooltip", "Cancel Key Sequence Recording")
QQC2.ToolTip {
visible: parent.hovered
text: parent.Accessible.name
}
}
}