🏑 index : quine-global-labs/mirror-kde-plasma-desktop.git

/*
    SPDX-FileCopyrightText: 2017 Roman Gilg <subdiff@gmail.com>
    SPDX-FileCopyrightText: 2018 Furkan Tokac <furkantokac34@gmail.com>
    SPDX-FileCopyrightText: 2025 Jakob Petsovits <jpetso@petsovits.com>

    SPDX-License-Identifier: GPL-2.0-or-later
*/

pragma ComponentBehavior: Bound

import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts

import org.kde.config as KConfig // KAuthorized
import org.kde.kcmutils as KCMUtils
import org.kde.kirigami as Kirigami

import org.kde.plasma.private.kcm_touchpad as Touchpad

KCMUtils.SimpleKCM {
    id: root

    readonly property Touchpad.TouchpadBackend backend: KCMUtils.ConfigModule.backend

    spacing: Kirigami.Units.smallSpacing

    property Touchpad.InputDevice device: root.backend.inputDevices[KCMUtils.ConfigModule.currentDeviceIndex] ?? null

    LayoutMirroring.enabled: Application.layoutDirection === Qt.RightToLeft
    LayoutMirroring.childrenInherit: true

    headerPaddingEnabled: false // Let the InlineMessage touch the edges
    header: ColumnLayout {
        spacing: 0

        Message {
            Layout.fillWidth: true
            message: root.KCMUtils.ConfigModule.saveLoadMessage
        }

        Message {
            Layout.fillWidth: true
            message: root.KCMUtils.ConfigModule.hotplugMessage
            showCloseButton: true
        }
    }

    component Message : Kirigami.InlineMessage {
        required property Touchpad.message message

        position: Kirigami.InlineMessage.Position.Header
        visible: message.type !== Touchpad.MessageType.None
        text: message.text
        type: {
            switch (message.type) {
            case Touchpad.MessageType.Error:
                return Kirigami.MessageType.Error;
            case Touchpad.MessageType.Information:
            case Touchpad.MessageType.None:
            default:
                return Kirigami.MessageType.Information;
            }
        }
    }

    Kirigami.PlaceholderMessage {
        icon.name: "input-touchpad"
        text: i18ndc("kcm_touchpad", "@info:status placeholdermessage text", "No touchpad found")
        explanation: i18ndc("kcm_touchpad", "@info:usagetip placeholdermessage explanation", "Connect an external touchpad");
        anchors.centerIn: parent
        visible: !root.backend.inputDevices?.length
        width: parent.width - (Kirigami.Units.largeSpacing * 4)
    }

    Kirigami.Form {
        id: formLayout

        visible: root.backend.inputDevices?.length > 0
        enabled: visible

        Kirigami.FormGroup {
            // Device
            Kirigami.FormEntry {
                visible: deviceSelector.count > 1
                contentItem: QQC2.ComboBox {
                    Kirigami.FormData.label: i18ndc("kcm_touchpad", "@label:listbox device selection", "Device:")
                    id: deviceSelector

                    Layout.fillWidth: true
                    model: root.backend.inputDevices
                    textRole: "name"

                    Component.onCompleted: {
                        currentIndex = Qt.binding(() => root.KCMUtils.ConfigModule.currentDeviceIndex);
                    }

                    onActivated: {
                        root.KCMUtils.ConfigModule.currentDeviceIndex = currentIndex;
                    }
                }
            }

            Kirigami.FormSeparator {}

            // General settings
            Kirigami.FormEntry {
                contentItem: QQC2.CheckBox {
                    id: deviceEnabled
                    Kirigami.FormData.label: i18ndc("kcm_touchpad", "@title:group prefix for checkbox group", "General:")
                    text: i18ndc("kcm_touchpad", "@option:check", "Enable device")
                    enabled: root.device?.supportsDisableEvents ?? false
                    checked: root.device && (!root.device.supportsDisableEvents || root.device.enabled)

                    onToggled: {
                        if (root.device) {
                            root.device.enabled = checked
                        }
                    }
                }
            }

            Kirigami.FormEntry {
                contentItem: QQC2.CheckBox {
                    id: disableEventsOnExternalMouse
                    text: i18ndc("kcm_touchpad", "@option:check", "Disable while mouse is connected")
                    leftPadding: mirrored ? disableEventsOnExternalMouse.padding : (deviceEnabled.indicator.width + deviceEnabled.spacing)
                    rightPadding: mirrored ? (deviceEnabled.indicator.width + deviceEnabled.spacing) : disableEventsOnExternalMouse.padding
                    enabled: root.device?.supportsDisableEventsOnExternalMouse ?? false
                    checked: enabled && (root.device?.disableEventsOnExternalMouse ?? false)

                    onToggled: {
                        if (root.device) {
                            root.device.disableEventsOnExternalMouse = checked
                        }
                    }
                }
            }

            Kirigami.FormEntry {
                subtitle: dwt.Accessible.description
                contentItem: QQC2.CheckBox {
                    id: dwt
                    text: i18ndc("kcm_touchpad", "option:check", "Disable while typing")
                    Accessible.description: i18ndc("kcm_touchpad", "@label 'this' refers to the 'disable touchpad while typing' feature", "This can interfere with video games.")
                    leftPadding: mirrored ? disableEventsOnExternalMouse.padding : (deviceEnabled.indicator.width + deviceEnabled.spacing)
                    rightPadding: mirrored ? (deviceEnabled.indicator.width + deviceEnabled.spacing) : disableEventsOnExternalMouse.padding
                    enabled: root.device?.supportsDisableWhileTyping ?? false
                    checked: enabled && (root.device?.disableWhileTyping ?? false)

                    onToggled: {
                        if (root.device) {
                            root.device.disableWhileTyping = checked
                        }
                    }
                }
            }


            Kirigami.FormSeparator {}

            Kirigami.FormEntry {
                contentItem: QQC2.CheckBox {
                    id: leftHanded
                    Kirigami.FormData.label: i18ndc("kcm_touchpad", "@title:group", "Left and right buttons:")
                    text: i18ndc("kcm_touchpad", "@option:check", "Swap (left-handed mode)")
                    enabled: root.device?.supportsLeftHanded ?? false
                    checked: enabled && (root.device?.leftHanded ?? false)

                    onToggled: {
                        if (root.device) {
                            root.device.leftHanded = checked
                        }
                    }
                }
            }

            Kirigami.FormEntry {
                id: middleEmulationFormEntry
                visible: root.device?.supportsMiddleEmulation ?? false

                contentItem: QQC2.CheckBox {
                    id: middleEmulation
                    text: i18ndc("kcm_touchpad", "@option:check 'both' refers to the left and right buttons'", "Press both simultaneously to middle-click")
                    enabled: middleEmulationFormEntry.visible && (root.device?.clickMethodAreas ?? false)
                    checked: enabled && (root.device?.middleEmulation ?? false)

                    onToggled: {
                        if (root.device) {
                            root.device.middleEmulation = checked
                        }
                    }
                }

                trailingItems: Kirigami.ContextualHelpButton {
                    toolTipText: middleEmulation.enabled
                        ? i18ndc("kcm_touchpad", "@info:tooltip from ContextualHelpButton", "Activating this setting increases click latency by 50ms. The extra delay is needed to correctly detect simultaneous left and right button presses.")
                        : i18ndc("kcm_touchpad", "@info:tooltip from ContextualHelpButton", "This setting is not available while β€œRight-click by pressing anywhere with two fingers” is selected.")
                }
            }


            Kirigami.FormSeparator {}

            // Acceleration
            Kirigami.FormEntry {
                contentItem: RowLayout {
                    Kirigami.FormData.label: i18ndc("kcm_touchpad", "@label:slider and @label:spinbox", "Pointer speed:")
                    id: accelSpeed
                    Layout.fillWidth: true

                    function onAccelSpeedChanged(val) {
                        if ((val / 100) !== root.device.pointerAcceleration) {
                            root.device.pointerAcceleration = val / 100
                        }
                    }

                    QQC2.Slider {
                        id: accelSpeedSlider
                        Layout.fillWidth: true

                        from: 1
                        to: 11
                        stepSize: 1
                        Kirigami.StyleHints.tickMarkStepSize: 1
                        enabled: root.device?.supportsPointerAcceleration ?? false

                        // convert libinput pointer acceleration range [-1, 1] to slider range [1, 11]
                        value: enabled ? Math.round(6 + root.device.pointerAcceleration / 0.2) : 0

                        onMoved: {
                            if (root.device) {
                                // convert slider range [1, 11] to accelSpeedValue range [-100, 100]
                                const accelSpeedValue = Math.round(((value - 6) * 0.2) * 100)
                                accelSpeed.onAccelSpeedChanged(accelSpeedValue)
                            }
                        }
                    }

                    QQC2.SpinBox {
                        id: accelSpeedSpinbox

                        Layout.minimumWidth: Kirigami.Units.gridUnit * 4

                        from: -100
                        to: 100
                        stepSize: 1
                        editable: true
                        enabled: root.device?.supportsPointerAcceleration ?? false

                        // if existing configuration or another application set a value with more than 2 decimals
                        // we reduce the precision to 2
                        value: enabled ? Math.round(root.device.pointerAcceleration * 100) : 0

                        validator: DoubleValidator {
                            bottom: accelSpeedSpinbox.from
                            top: accelSpeedSpinbox.to
                        }

                        onValueModified: {
                            if (root.device) {
                                accelSpeed.onAccelSpeedChanged(value)
                                // Keyboard input breaks SpinBox value bindings with current Qt.
                                // Restore the binding so clicking "Reset" will update it correctly.
                                value = Qt.binding(() => accelSpeedSpinbox.enabled && root.device
                                    ? Math.round(root.device.pointerAcceleration * 100)
                                    : 0
                                );
                            }
                        }

                        textFromValue: function(val, locale) {
                            return Number(val / 100).toLocaleString(locale, "f", 2)
                        }

                        valueFromText: function(text, locale) {
                            return Number.fromLocaleString(locale, text) * 100
                        }
                    }
                }
            }

            Kirigami.FormEntry {
                enabled: root.device?.supportsPointerAccelerationProfileAdaptive ?? false
                visible: enabled
                contentItem: QQC2.CheckBox {
                    id: accelProfileEnabled
                    text: i18ndc("kcm_touchpad", "@option:check", "Enable pointer acceleration")
                    checked: enabled && !(root.device?.pointerAccelerationProfileFlat ?? false)

                    onToggled: {
                        if (root.device) {
                            root.device.pointerAccelerationProfileFlat = !checked
                            root.device.pointerAccelerationProfileAdaptive = checked
                        }
                    }
                }

                trailingItems: Kirigami.ContextualHelpButton {
                    toolTipText: i18ndc("kcm_touchpad", "@info:tooltip from ContextualHelpButton", "When enabled, pointer travel distance increases with faster movement speed.")
                }
            }
        }

        // Scrolling section
        Kirigami.FormGroup {
            // Scroll Speed aka scroll Factor
            Kirigami.FormEntry {
                contentItem: GridLayout {
                    id: scrollProfile
                    Kirigami.FormData.label: i18ndc("kcm_touchpad", "@label:slider", "Scrolling speed:")
                    Kirigami.FormData.buddyFor: scrollFactor
                    Layout.fillWidth: true

                    visible: root.device?.supportsScrollFactor ?? false
                    columns: 4

                    readonly property list<real> values: [
                        0.1,
                        0.3,
                        0.5,
                        0.75,
                        1, // default
                        1.5,
                        2,
                        3,
                        4,
                        5,
                        7,
                        9,
                        12,
                        15,
                        20
                    ]

                    QQC2.Slider {
                        id: scrollFactor
                        Layout.fillWidth: true
                        Layout.columnSpan: 3

                        from: 0
                        to: 14
                        stepSize: 1
                        Kirigami.StyleHints.tickMarkStepSize: 1
                        enabled: root.device

                        function indexOf(val) {
                            const index = scrollProfile.values.indexOf(val)
                            return index === -1 ? scrollProfile.values.indexOf(1) : index
                        }
                        value: indexOf(root.device?.scrollFactor ?? 1)

                        onMoved: {
                            if (root.device) {
                                root.device.scrollFactor = scrollProfile.values[value]
                            }
                        }
                    }

                    //row 2
                    QQC2.SpinBox {
                        id: scrollFactorSpinBox

                        Layout.minimumWidth: Kirigami.Units.gridUnit * 4

                        from: 0
                        to: 14
                        stepSize: 1

                        function indexOf(val: real): int {
                            const index = scrollProfile.values.indexOf(val)
                            return index === -1 ? scrollProfile.values.indexOf(1) : index
                        }
                        value: indexOf(root.device?.scrollFactor ?? 1)

                        onValueChanged: {
                            if (root.device) {
                                root.device.scrollFactor = scrollProfile.values[value]
                            }
                        }

                        textFromValue: function(val, locale) {
                            return Number(val + 1).toLocaleString(locale, "d", 0)
                        }

                        valueFromText: function(text, locale) {
                            return Number.fromLocaleString(locale, text) - 1
                        }
                    }

                    //row 3
                    QQC2.Label {
                        text: i18ndc("kcm_touchpad", "@item:inrange Slower Scroll", "Slower")
                        textFormat: Text.PlainText
                    }
                    Item {
                        Layout.fillWidth: true
                    }
                    QQC2.Label {
                        text: i18ndc("kcm_touchpad", "@item:inrange Faster Scroll Speed", "Faster")
                        textFormat: Text.PlainText
                    }
                }
            }

            Kirigami.FormEntry {
                title: i18ndc("kcm_touchpad", "@title group begins the sentence 'Scroll by moving two fingers anywhere/moving one finger on edge'", "Scroll by:")
                contentItem: QQC2.RadioButton {
                    id: scrollMethodTwoFingers
                    text: i18ndc("kcm_touchpad", "@option:radio completes the sentence 'Scroll by moving two fingers anywhere'", "Moving two fingers anywhere")
                    enabled: root.device?.supportsScrollTwoFinger ?? false
                    checked: root.device?.scrollTwoFinger ?? false
                }
                QQC2.ButtonGroup {
                    buttons: [scrollMethodTwoFingers, scrollMethodTouchpadEdges]
                    onClicked: {
                        if (root.device) {
                            root.device.scrollTwoFinger = scrollMethodTwoFingers.checked
                            root.device.scrollEdge = scrollMethodTouchpadEdges.checked
                        }
                    }
                }
            }

            Kirigami.FormEntry {
                contentItem: QQC2.RadioButton {
                    id: scrollMethodTouchpadEdges
                    text: i18ndc("kcm_touchpad", "@option:radio completes the sentence 'Scroll by moving one finger on edge'", "Moving one finger on edge")
                    enabled: root.device?.supportsScrollEdge ?? false
                    checked: root.device?.scrollEdge ?? false
                }
            }

            Kirigami.FormEntry {
                contentItem: QQC2.CheckBox {
                    id: naturalScroll
                    text: i18ndc("kcm_touchpad", "@option:check", "Invert scroll direction (Natural scrolling)")
                    enabled: root.device?.supportsNaturalScroll ?? false
                    checked: enabled && (root.device?.naturalScroll ?? false)

                    onToggled: {
                        if (root.device) {
                            root.device.naturalScroll = checked
                        }
                    }
                }
            }

            Kirigami.FormEntry {
                visible: root.device?.supportsHorizontalScrolling ?? false
                enabled: root.device?.supportsHorizontalScrolling ?? false
                contentItem: QQC2.CheckBox {
                    id: disableHorizontalScrolling
                    text: i18ndc("kcm_touchpad", "@option:check", "Disable horizontal scrolling")

                    checked: enabled && !(root.device?.horizontalScrolling ?? true)

                    onToggled: {
                        if (root.device) {
                            root.device.horizontalScrolling = !checked
                        }
                    }
                }
            }
        }


        // Right-click/middle-click section
        Kirigami.FormGroup {

            Kirigami.FormEntry {
                title: i18ndc("kcm_touchpad", "@title:group for radiobutton group, begins the sentence 'right-click by pressing bottom-right corner/pressing with two fingers'", "Right-click by:")
                subtitle: rightClickMethodAreas.Accessible.description
                contentItem: QQC2.RadioButton {
                    id: rightClickMethodAreas
                    text: (
                        root.device?.leftHanded ?? false
                            ? i18ndc("kcm_touchpad", "@option:radio Completes the sentence 'right-click by pressing bottom-left corner'", "Pressing bottom-left corner")
                            : i18ndc("kcm_touchpad", "@option:radio Completes the sentence 'right-click by pressing bottom-right corner'", "Pressing bottom-right corner")
                    )
                    enabled: root.device?.supportsClickMethodAreas ?? false
                    checked: enabled && (root.device?.clickMethodAreas ?? false)
                    Accessible.description: (
                        root.device?.middleEmulation ?? false
                            ? i18ndc("kcm_touchpad", "@info:usagetip shown below radio button", "Middle-click by pressing both bottom corners.")
                            : i18ndc("kcm_touchpad", "@info:usagetip shown below radio button", "Middle-click by pressing bottom center.")
                    )
                }

                enabled: (root.device?.supportsClickMethodAreas && root.device?.supportsClickMethodClickfinger) ?? false
                visible: (root.device?.supportsClickMethodAreas || root.device?.supportsClickMethodClickfinger) ?? false

                QQC2.ButtonGroup {
                    buttons: [rightClickMethodAreas, rightClickMethodClickfinger]
                    onClicked: {
                        if (root.device) {
                            root.device.clickMethodAreas = rightClickMethodAreas.checked && rightClickMethodAreas.visible
                            root.device.clickMethodClickfinger = rightClickMethodClickfinger.checked && rightClickMethodClickfinger.visible
                        }
                    }
                }
            }

            Kirigami.FormEntry {
                subtitle: rightClickMethodClickfinger.Accessible.description
                contentItem: QQC2.RadioButton {
                    id: rightClickMethodClickfinger
                    text: i18ndc("kcm_touchpad", "@option:radio Completes the sentence 'right-click by pressing with two fingers'", "Pressing anywhere with two fingers")
                    Accessible.description: i18ndc("kcm_touchpad", "@info shown below radio button", "Middle-click by pressing anywhere with three fingers.")
                    topPadding: Kirigami.Units.smallSpacing // in lieu of rightClickMethod.spacing
                    enabled: root.device?.supportsClickMethodClickfinger ?? false
                    checked: enabled && (root.device?.clickMethodClickfinger ?? false)
                }

                enabled: (root.device?.supportsClickMethodAreas && root.device?.supportsClickMethodClickfinger) ?? false
                visible: (root.device?.supportsClickMethodAreas || root.device?.supportsClickMethodClickfinger) ?? false
            }
        }


        // Tap-to-click section
        Kirigami.FormGroup {
            Kirigami.FormEntry {
                contentItem: QQC2.CheckBox {
                    id: tapToClick
                    Kirigami.FormData.label: i18ndc("kcm_touchpad", "@label for checkbox, tap-to-click", "Tapping:")
                    text: i18ndc("kcm_touchpad", "@option:check", "Tap to click")
                    enabled: root.device?.tapFingerCount > 0
                    checked: enabled && (root.device?.tapToClick ?? false)

                    onToggled: {
                        if (root.device) {
                            root.device.tapToClick = checked
                        }
                    }
                }
            }

            Kirigami.FormEntry {
                contentItem: QQC2.CheckBox {
                    id: tapAndDrag
                    text: i18ndc("kcm_touchpad", "@option:check", "Allow dragging after tapping")
                    enabled: root.device?.tapFingerCount > 0 && tapToClick.checked
                    checked: enabled && (root.device?.tapAndDrag ?? false)

                    onToggled: {
                        if (root.device) {
                            root.device.tapAndDrag = checked
                        }
                    }
                }

                trailingItems: Kirigami.ContextualHelpButton {
                    toolTipText: i18ndc("kcm_touchpad", "@info:tooltip from ContextualHelpButton", "Tap, then tap and immediately slide finger over the touchpad to drag. Lift finger to drop.")
                }
            }

            Kirigami.FormEntry {
                contentItem: QQC2.CheckBox {
                    id: tapAndDragLock
                    text: i18ndc("kcm_touchpad", "@option:check", "Allow briefly lifting finger during tap-and-drag")
                    enabled: root.device?.tapFingerCount > 0 && tapAndDrag.checked
                    checked: enabled && (root.device?.tapDragLock ?? false)

                    onToggled: {
                        if (root.device) {
                            root.device.tapDragLock = checked
                        }
                    }
                }
            }

            Kirigami.FormEntry {
                title: i18ndc("kcm_touchpad", "@title:group", "Two-finger tap:")
                visible: root.device?.supportsLmrTapButtonMap
                enabled: root.device?.supportsLmrTapButtonMap && tapToClick.checked
                contentItem: QQC2.RadioButton {
                    id: multiTapRightClick
                    text: i18ndc("kcm_touchpad", "@option:radio", "Right-click")
                    Accessible.description: (
                        root.device?.tapFingerCount > 2
                            ? i18ndc("kcm_touchpad", "@info:usagetip shown below radio button", "Middle-click by tapping anywhere with three fingers.")
                            : ""
                    )
                    checked: enabled && !(root.device?.lmrTapButtonMap ?? false)
                }
                subtitle: multiTapRightClick.Accessible.description

                QQC2.ButtonGroup {
                    buttons: [multiTapRightClick, multiTapMiddleClick]
                    onClicked: {
                        if (root.device) {
                            root.device.lmrTapButtonMap = multiTapMiddleClick.checked
                        }
                    }
                }
            }

            Kirigami.FormEntry {
                visible: root.device?.supportsLmrTapButtonMap
                enabled: root.device?.supportsLmrTapButtonMap && tapToClick.checked
                contentItem: QQC2.RadioButton {
                    id: multiTapMiddleClick
                    text: i18ndc("kcm_touchpad", "@option:radio", "Middle-click")
                    Accessible.description: (
                        root.device?.tapFingerCount > 2
                            ? i18ndc("kcm_touchpad", "@info:usagetip shown below radio button", "Right-click by tapping anywhere with three fingers.")
                            : ""
                    )
                    checked: enabled && (root.device?.lmrTapButtonMap ?? false)
                }
                subtitle: multiTapMiddleClick.Accessible.description
            }
        }
    }
}