/*SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez <aleixpol@kde.org>SPDX-License-Identifier: GPL-2.0-or-later*/import QtQuickimport QtQuick.Layoutsimport QtQuick.Controls as QQC2import QtQuick.Shapesimport org.kde.kirigami as Kirigamiimport org.kde.plasma.tablet.kcm as KCMimport org.kde.kcmutilsimport org.kde.kquickcontrolsItem {id: rootrequired property var dbrequired property KCM.InputDevice devicerequired property KCM.TabletEvents tabletEventsreadonly property bool calibrationWindowOpen: calibrationWindow !== nullproperty Calibration calibrationWindowproperty string currentCalibrationSysName// TODO: don't allow calibration across multiple screens againreadonly property bool supportsCalibration: root.device.supportsCalibrationMatrixsignal settingsRestoredimplicitHeight: layout.implicitHeight + Kirigami.Units.largeSpacing * 2Repeater {id: buttonLineConnectionRepeatermodel: buttonRepeater.countdelegate: Shape {id: delegaterequired property int indexreadonly property Item item: buttonRepeater.itemAt(index)readonly property Item button: {switch (index) {default:case 0:return firstButton;case 1:return secondButton;case 2:return thirdButton;}}anchors.fill: parentpreferredRendererType: Shape.CurveRendererShapePath {id: pathfillColor: "transparent"strokeWidth: 1strokeColor: Qt.alpha(Kirigami.Theme.textColor, 0.5)readonly property real beginX: form.Kirigami.ScenePosition.x - root.Kirigami.ScenePosition.xreadonly property real endX: delegate.button.Kirigami.ScenePosition.x - root.Kirigami.ScenePosition.x + delegate.button.widthreadonly property real midX: Math.round((beginX + endX) / 2.0 + (Kirigami.Units.gridUnit * delegate.index)) + 0.5readonly property real beginY: Math.round(item.Kirigami.ScenePosition.y - root.Kirigami.ScenePosition.y + (item.height / 2)) + 0.5readonly property real endY: Math.round(delegate.button.Kirigami.ScenePosition.y - root.Kirigami.ScenePosition.y + (delegate.button.height / 2)) + 0.5function reloadPosition(): void {startX = Qt.binding(() => beginX);startY = Qt.binding(() => beginY);}PathLine {x: path.midXy: path.beginY}PathLine {x: path.midXy: path.endY}PathLine {x: path.endXy: path.endY}Component.onCompleted: reloadPosition()}Connections {target: rootfunction onWidthChanged(): void {path.reloadPosition();}function onHeightChanged(): void {path.reloadPosition();}}}}RowLayout {id: layoutanchors.fill: parentspacing: 0Item {id: svgContainerLayout.minimumWidth: svgOverlay.implicitWidth + Kirigami.Units.gridUnit * (2 + buttonRepeater.count)Layout.horizontalStretchFactor: 1Layout.fillWidth: trueLayout.fillHeight: trueShape {id: svgOverlaypreferredRendererType: Shape.CurveRendereranchors {verticalCenter: parent.verticalCenterright: parent.rightrightMargin: Kirigami.Units.gridUnit * (1 + buttonRepeater.count)}ShapePath {fillColor: "transparent"strokeColor: Kirigami.Theme.disabledTextColorstrokeWidth: 1// Copied from images/pen.svgPathSvg {path: "m15.938 299.77c-9.2112-0.315-10.544-2.052-11.134-5.418-4.5935-26.212-4.5966-254.2-4.5966-254.2l9.5589-27.513s1.0844-1.8566 6.1713-1.84c0.0244 1e-4 0.0407 0.0142 0.0651 0.0149 0.0245-1e-4 0.0408-0.0142 0.0652-0.0149 5.0869-0.0164 6.1713 1.84 6.1713 1.84l9.5589 27.513s-0.0033 227.98-4.5967 254.2c-0.5896 3.366-1.9224 5.103-11.134 5.418-0.0244 0-0.0407-1e-3 -0.0651 0-0.0245-1e-3 -0.0408 0-0.0652 0z"}PathSvg {path: "m13.169 10.8 0.7313-10.032 2.1791-0.5257 2.1632 0.5257 0.7313 10.032h-2.8948z"}}Rectangle {id: firstButtonanchors {horizontalCenter: parent.horizontalCentertop: parent.toptopMargin: 55}visible: buttonRepeater.count >= 1implicitWidth: 10implicitHeight: 25topLeftRadius: Kirigami.Units.cornerRadiustopRightRadius: Kirigami.Units.cornerRadiuscolor: "transparent"border {color: Kirigami.Theme.textColorwidth: 1}}Rectangle {id: secondButtonanchors {horizontalCenter: parent.horizontalCentertop: parent.toptopMargin: 79}visible: buttonRepeater.count >= 2implicitWidth: 10implicitHeight: 35bottomLeftRadius: Kirigami.Units.cornerRadiusbottomRightRadius: Kirigami.Units.cornerRadiuscolor: "transparent"border {color: Kirigami.Theme.textColorwidth: 1}}Rectangle {id: thirdButtonanchors {horizontalCenter: parent.horizontalCentertop: parent.toptopMargin: 125}visible: buttonRepeater.count >= 3implicitWidth: 10implicitHeight: 20radius: Kirigami.Units.cornerRadiuscolor: "transparent"border {color: Kirigami.Theme.textColorwidth: 1}}}}Kirigami.Form {id: formLayout.alignment: Qt.AlignTopLayout.fillWidth: trueLayout.maximumWidth: implicitWidthKirigami.FormGroup {Kirigami.FormEntry {title: i18ndc("kcm_tablet", "@label prefix for checkbox", "Left-handed mode:")contentItem: QQC2.CheckBox {id: leftHandedCheckboxtext: i18nc("@option:check Enable left-handed mode", "Enable")enabled: root.device && root.device.supportsLeftHandedchecked: root.device && root.device.leftHandedonCheckedChanged: root.device.leftHanded = checkedSettingHighlighter {// No device has a default of left-handed, so this is always an explicit user choicehighlight: leftHandedCheckbox.checked}}trailingItems: Kirigami.ContextualHelpButton {toolTipText: xi18nc("@info", "Tells the device to accommodate left-handed users. Effects will vary by device, but often it reverses the pad buttonsΚΌ functionality so the tablet can be used upside-down.")}}// Click behavior settingsKirigami.FormEntry {title: i18nc("'Mode' is the mode the stylus is in, pen (absolute) or mouse (relative)", "Mode:")subtitle: absoluteMode.Accessible.descriptioncontentItem: QQC2.RadioButton {id: absoluteModetext: i18nc("@option:radio Pen mode, where the point on the screen is always where you touch the stylus", "Pen")checked: root.device && !root.device.relativeonToggled: root.device.relative = falseQQC2.ButtonGroup.group: isRelativeGroupAccessible.description: i18nc("@info:whatsthis Accessible description", "The cursor follows where you touch the pen on the surface")QQC2.ButtonGroup { id: isRelativeGroup }}}Kirigami.FormEntry {subtitle: relativeMode.Accessible.descriptioncontentItem: QQC2.RadioButton {id: relativeModetext: i18nc("@option:radio Mouse mode, or like using the tablet like a giant touchpad", "Mouse")checked: root.device && root.device.relativeonToggled: root.device.relative = trueQQC2.ButtonGroup.group: isRelativeGroupAccessible.description: i18nc("@info:whatsthis Accessible description", "Moving the pen on the surface moves the cursor relative to where it was, like a mouse")}}Kirigami.FormEntry {contentItem: QQC2.CheckBox {text: i18n("Sync with mouse cursor")checked: kcm.syncWithMouseonCheckedChanged: kcm.syncWithMouse = checked}}}Kirigami.FormGroup {Repeater {id: buttonRepeatermodel: KCM.StylusButtonsModel {device: root.devicedb: root.db}delegate: Kirigami.FormEntry {id: bindingDelegaterequired property string namerequired property string labelrequired property int value // Gives us BTN_STYLUS, etc which KWin acceptstitle: (seq.buttonPressed ? "<b>" : "") + label + (seq.buttonPressed ? "</b>" : "")contentItem: ActionBinding {id: seqname: bindingDelegate.nameproperty bool buttonPressed: falseConnections {target: root.tabletEventsfunction onToolButtonReceived(hardware_serial_hi: int, hardware_serial_lo: int, button: int, pressed: bool): void {if (button !== bindingDelegate.value) {return;}seq.buttonPressed = pressed;}}Connections {target: rootfunction onSettingsRestored(): void {seq.refreshInputSequence();}}function refreshInputSequence(): void {seq.inputSequence = kcm.toolButtonMapping(root.device.name, bindingDelegate.value);}Component.onCompleted: seq.refreshInputSequence()onGotInputSequence: sequence => {kcm.assignToolButtonMapping(root.device.name, bindingDelegate.value, sequence);}SettingHighlighter {// Currently, application-defined is the defaulthighlight: seq.inputSequence.type !== KCM.InputSequence.ApplicationDefined}}}}}Kirigami.FormGroup {Kirigami.FormEntry {title: i18ndc("kcm_tablet", "@label prefix for pen pressure curve config", "Pen Pressure:")contentItem: RowLayout {Layout.fillWidth: falsespacing: Kirigami.Units.smallSpacingColumnLayout {spacing: Kirigami.Units.smallSpacingPressureCurve {id: pressureCurveproperty bool loadedSettings: falseonControlPoint1Changed: saveSettings()onControlPoint2Changed: saveSettings()isDefault: root.device.pressureCurveIsDefaultComponent.onCompleted: reloadSettings()function reloadSettings(): void {if (!root.device) {return;}const points = kcm.fromSerializedCurve(root.device.pressureCurve);if (points.length === 2) {pressureCurve.controlPoint1 = points[0];pressureCurve.controlPoint2 = points[1];pressureCurve.forceReloadControlPoints();} else {pressureCurve.controlPoint1 = Qt.point(0.0, 0.0);pressureCurve.controlPoint2 = Qt.point(1.0, 1.0);}loadedSettings = true;}function saveSettings(): void {// We need to make sure not to re-save the settings we are loading in reloadSettings()if (!root.device || !loadedSettings) {return;}root.device.pressureCurve = kcm.toSerializedCurve(pressureCurve.controlPoint1, pressureCurve.controlPoint2);}Connections {target: root.device// For reloading the curve when it's reset/set to defaultfunction onPressureCurveChanged(): void {pressureCurve.reloadSettings();}}}RowLayout {QQC2.Label {text: i18ndc("kcm_tablet", "% of minimum pen pressure", "%1%", Math.round(root.device.pressureRangeMin * 100.0))}Item {Layout.fillWidth: true}QQC2.Label {text: i18ndc("kcm_tablet", "% of maximum pen pressure", "%1%", Math.round(root.device.pressureRangeMax * 100.0))}}}ColumnLayout {Layout.maximumHeight: pressureCurve.implicitHeightLayout.alignment: Qt.AlignTopspacing: Kirigami.Units.smallSpacingQQC2.Label {text: i18ndc("kcm_tablet", "100% or maximum pen pressure", "100%")}Item {Layout.fillHeight: true}QQC2.Label {text: i18ndc("kcm_tablet", "0% or zero pen pressure", "0%")}}}trailingItems: Kirigami.ContextualHelpButton {toolTipText: i18ndc("kcm_tablet", "@info", "This curve controls the relationship between the pressure on the stylus and the pressure values received by applications.")}}Kirigami.FormEntry {title: i18ndc("kcm_tablet", "Pen pressure range", "Pressure Range:")contentItem: QQC2.RangeSlider {from: 0to: 1first {value: root.device.pressureRangeMinonMoved: root.device.pressureRangeMin = first.value}second {value: root.device.pressureRangeMaxonMoved: root.device.pressureRangeMax = second.value}implicitWidth: pressureCurve.width}trailingItems: Kirigami.ContextualHelpButton {toolTipText: i18ndc("kcm_tablet", "@info", "Pressure above or below the threshold will be clamped, and this becomes the new effective range.")}}Kirigami.FormEntry {subtitle: i18nc("@info", "You have manually calibrated this tablet. If it's no longer working correctly, try resetting this first.")contentItem: RowLayout {spacing: Kirigami.Units.smallSpacingLayout.fillWidth: parentQQC2.Button {text: {if (root.supportsCalibration) {if (root.calibrationWindowOpen) {return i18nc("@action:button Calibration in progress", "Calibration in Progress");} else {return i18nc("@action:button Calibrate the pen display", "Calibrate");}} else {return i18nc("@action:button Pen display doesn't support calibration", "Calibration Not Supported");}}icon.name: "crosshairs"enabled: root.supportsCalibration && !root.calibrationWindowOpenonClicked: {const component = Qt.createComponent("Calibration.qml");if (component.status === Component.Ready) {let screenIndex = 0;for (let i = 0; i < Qt.application.screens.length; i++) {if (Qt.application.screens[i].name === root.device.outputName) {screenIndex = i;break;}}const window = component.createObject(root, {device: root.device,tabletEvents: root.tabletEvents});// We need to show the window first, because Qt will override screen based on position.// Working around QTBUG-129989window.show();// Then override the screen, try showing it again and now it'll be on the correct screen:window.screen = Qt.application.screens[screenIndex];window.showFullScreen();window.closing.connect(close => {root.calibrationWindow = null;});root.currentCalibrationSysName = root.device.sysName;root.calibrationWindow = window;}}SettingHighlighter {highlight: !root.device.calibrationMatrixIsDefault}}QQC2.Button {text: i18nc("@action:button", "Reset Custom Calibration")icon.name: "edit-undo-symbolic"enabled: !root.device.calibrationMatrixIsDefaultvisible: root.supportsCalibrationdisplay: QQC2.AbstractButton.IconOnlyonClicked: root.device.resetCalibrationMatrix()QQC2.ToolTip.text: textQQC2.ToolTip.delay: Kirigami.Units.toolTipDelayQQC2.ToolTip.visible: hovered}Item {Layout.fillWidth: parent}}}}ActionDialog {id: actionDialogparent: root.QQC2.Overlay.overlay}}Item {Layout.horizontalStretchFactor: 1Layout.minimumWidth: Kirigami.Units.gridUnitLayout.fillWidth: true}}}