/*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 org.kde.kirigami as Kirigamiimport org.kde.plasma.tablet.kcmimport org.kde.kcmutilsimport org.kde.kquickcontrolsSimpleKCM {id: rootproperty bool testerWindowOpen: falsereadonly property bool calibrationWindowOpen: calibrationWindow !== nullproperty Calibration calibrationWindowproperty string currentCalibrationSysNameConfigModule.buttons: ConfigModule.Default | ConfigModule.ApplyimplicitWidth: Kirigami.Units.gridUnit * 38implicitHeight: Kirigami.Units.gridUnit * 35// So it doesn't scroll while draggingflickable.interactive: Kirigami.Settings.hasTransientTouchInputKirigami.PlaceholderMessage {icon.name: "preferences-desktop-tablet"text: i18nd("kcm_tablet", "No drawing tablets found")explanation: i18n("Connect a drawing tablet")anchors.centerIn: parentvisible: combo.count === 0width: parent.width - (Kirigami.Units.largeSpacing * 4)}actions: [Kirigami.Action {text: i18ndc("kcm_tablet", "Tests tablet functionality like the pen", "Test Tabletβ¦")icon.name: "tool_pen-symbolic"enabled: !root.testerWindowOpen && combo.count !== 0onTriggered: {const component = Qt.createComponent("Tester.qml");if (component.status === Component.Ready) {const window = component.createObject(root, {tabletEvents});window.showNormal();window.closing.connect((close) => {root.testerWindowOpen = false;});root.testerWindowOpen = true;} else {console.error(component.errorString());}}}]function cancelCalibration(): void {if (root.calibrationWindowOpen) {calibrationWindow.close();calibrationWindow = null;}}Connections {target: combo.modelfunction onDeviceRemoved(sysname: string): void {if (root.currentCalibrationSysName === sysname) {cancelCalibration();}}}Kirigami.FormLayout {id: formvisible: combo.count > 0enabled: combo.count > 0QQC2.ComboBox {id: comboKirigami.FormData.label: i18ndc("kcm_tablet", "@label:listbox The device we are configuring", "Device:")model: kcm.toolsModelonCountChanged: if (count > 0 && currentIndex < 0) {currentIndex = 0;}function reloadOutputView() {const initialOutputArea = form.device.outputArea;if (initialOutputArea === Qt.rect(0, 0, 1, 1)) {outputAreaCombo.currentIndex = 0;} else if (initialOutputArea.x === 0 && initialOutputArea.y === 0 && initialOutputArea.width === 1) {outputAreaCombo.currentIndex = 1;} else {outputAreaCombo.currentIndex = 2;}keepAspectRatio.checked = tabletItem.aspectRatio === (form.device.size.width / form.device.size.height)outputAreaView.resetOutputArea(outputAreaCombo.currentIndex, initialOutputArea)}onCurrentIndexChanged: {parent.device = kcm.toolsModel.deviceAt(combo.currentIndex)reloadOutputView()}Connections {target: kcmfunction onSettingsRestored() {outputAreaView.changed = falsecombo.reloadOutputView()}}}property QtObject device: nullQQC2.ComboBox {id: outputsComboKirigami.FormData.label: i18nd("kcm_tablet", "Map to screen:")model: OutputsModel {id: outputsModel}enabled: count > 3 //It's only interesting when there's more than 1 screencurrentIndex: {if (count === 0) {return -1}outputsModel.rowForDevice(parent.device)}textRole: "display"onActivated: {parent.device.outputName = outputsModel.outputNameAt(currentIndex)parent.device.mapToWorkspace = outputsModel.isMapToWorkspaceAt(currentIndex)}}QQC2.ComboBox {Kirigami.FormData.label: i18nd("kcm_tablet", "Orientation:")model: OrientationsModel {id: orientationsModel}enabled: parent.device && parent.device.supportsOrientationcurrentIndex: orientationsModel.rowForOrientation(parent.device.orientation)displayText: parent.device.supportsOrientation ? currentText : i18nd("kcm_tablet", "Not Supported")textRole: "display"onActivated: {parent.device.orientation = orientationsModel.orientationAt(currentIndex)}}RowLayout {Kirigami.FormData.label: i18nd("kcm_tablet", "Left-handed mode:")Kirigami.FormData.buddyFor: leftHandedCheckboxspacing: 0QQC2.CheckBox {id: leftHandedCheckboxenabled: form.device && form.device.supportsLeftHandedchecked: form.device && form.device.leftHandedonCheckedChanged: form.device.leftHanded = checked}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.")}}QQC2.ComboBox {id: outputAreaComboLayout.fillWidth: trueKirigami.FormData.label: i18nd("kcm_tablet", "Mapped Area:")model: OutputsFittingModel {}onActivated: index => {outputAreaView.changed = truekeepAspectRatio.checked = trueoutputAreaView.resetOutputArea(index, index === 0 ? Qt.rect(0,0, 1,1) : Qt.rect(0, 0, 1, outputItem.aspectRatio/tabletItem.aspectRatio))}}// Display fit demoItem {id: outputAreaViewfunction resetOutputArea(mode, outputArea) {if (mode === 0) {tabletItem.x = 0tabletItem.y = 0tabletItem.width = Qt.binding(() => outputItem.width);tabletItem.height = Qt.binding(() => outputItem.height);} else {tabletItem.x = Qt.binding(() => outputArea.x * outputItem.width)tabletItem.y = Qt.binding(() => outputArea.y * outputItem.height)tabletItem.width = Qt.binding(() => tabletSizeHandle.x);tabletItem.height = Qt.binding(() => tabletSizeHandle.y);tabletSizeHandle.x = Qt.binding(() => outputArea.width * outputItem.width)if (keepAspectRatio.checked) {tabletSizeHandle.y = Qt.binding(() => tabletSizeHandle.x / tabletItem.aspectRatio);} else {tabletSizeHandle.y = Qt.binding(() => outputArea.height * outputItem.height)}}}readonly property rect outputAreaSetting: Qt.rect(tabletItem.x/outputItem.width, tabletItem.y/outputItem.height,tabletItem.width/outputItem.width, tabletItem.height/outputItem.height)property bool changed: falseonOutputAreaSettingChanged: {if (form.device && changed) {form.device.outputArea = outputAreaSetting}}Layout.fillWidth: trueLayout.preferredHeight: Math.max(outputItem.height, tabletItem.height)enabled: parent.deviceOutput {id: outputItemreadonly property size outputSize: outputsModel.data(outputsModel.index(outputsCombo.currentIndex, 0), Qt.UserRole + 2)readonly property real aspectRatio: outputSize.width / outputSize.heightwidth: parent.width * 0.7height: width / aspectRatio}Rectangle {id: tabletItemcolor: Kirigami.Theme.activeBackgroundColoropacity: 0.8readonly property real aspectRatio: outputAreaCombo.currentIndex === 0 ? outputItem.aspectRatio : form.device.size.width / form.device.size.heightwidth: tabletSizeHandle.xheight: tabletSizeHandle.yDragHandler {cursorShape: Qt.ClosedHandCursortarget: parentenabled: outputAreaCombo.currentIndex >= 2onActiveChanged: { outputAreaView.changed = true }xAxis.minimum: 0xAxis.maximum: outputItem.width - tabletItem.widthyAxis.minimum: 0yAxis.maximum: outputItem.height - tabletItem.height}TapHandler {gesturePolicy: TapHandler.WithinBounds}QQC2.Button {id: tabletSizeHandlex: outputItem.widthy: outputItem.width / parent.aspectRatiovisible: outputAreaCombo.currentIndex >= 2icon.name: "transform-move"display: QQC2.AbstractButton.IconOnlytext: i18nd("kcm_tablet", "Resize the tablet area")QQC2.ToolTip {text: tabletSizeHandle.textvisible: parent.hovereddelay: Kirigami.Units.toolTipDelay}DragHandler {cursorShape: Qt.SizeFDiagCursortarget: parentonActiveChanged: { outputAreaView.changed = true }xAxis.minimum: 10xAxis.maximum: outputItem.width - tabletItem.xyAxis.minimum: keepAspectRatio.checked ? (tabletItem.width / tabletItem.aspectRatio) : 10yAxis.maximum: keepAspectRatio.checked ? (tabletItem.width / tabletItem.aspectRatio) : outputItem.height - tabletItem.y}}}}QQC2.CheckBox {id: keepAspectRatiotext: i18ndc("kcm_tablet", "@option:check", "Lock aspect ratio")visible: outputAreaCombo.currentIndex >= 2checked: trueonToggled: {outputAreaView.resetOutputArea(outputAreaCombo.currentIndex, form.device.outputArea)}}QQC2.Label {text: i18ndc("kcm_tablet", "tablet area position - size", "%1,%2 - %3Γ%4", String(Math.floor(outputAreaView.outputAreaSetting.x * outputItem.outputSize.width)), String(Math.floor(outputAreaView.outputAreaSetting.y * outputItem.outputSize.height)), String(Math.floor(outputAreaView.outputAreaSetting.width * outputItem.outputSize.width)), String(Math.floor(outputAreaView.outputAreaSetting.height * outputItem.outputSize.height)))textFormat: Text.PlainText}Repeater {model: [{value: 0x14b, // BTN_STYLUStext: i18nd("kcm_tablet", "Pen button 1:"),name: i18ndc("kcm_tablet", "@info Meant to be inserted into an existing sentence like 'configuring pen button 1'", "pen button 1")},{value: 0x14c, // BTN_STYLUS2text: i18nd("kcm_tablet", "Pen button 2:"),name: i18ndc("kcm_tablet", "@info Meant to be inserted into an existing sentence like 'configuring pen button 2'", "pen button 2")},{value: 0x149, // BTN_STYLUS3text: i18nd("kcm_tablet", "Pen button 3:"),name: i18ndc("kcm_tablet", "@info Meant to be inserted into an existing sentence like 'configuring pen button 3'", "pen button 3")}]delegate: ActionBinding {id: seqrequired property var modelDataKirigami.FormData.label: (buttonPressed ? "<b>" : "") + modelData.text + (buttonPressed ? "</b>" : "")property bool buttonPressed: falsename: modelData.nameConnections {target: tabletEventsfunction onToolButtonReceived(hardware_serial_hi, hardware_serial_lo, button, pressed) {if (button !== modelData.value) {return;}seq.buttonPressed = pressed}}Connections {target: kcmfunction onSettingsRestored() {refreshInputSequence();}}Connections {target: formfunction onDeviceChanged() {refreshInputSequence();}}function refreshInputSequence(): void {seq.inputSequence = kcm.toolButtonMapping(form.device.name, modelData.value)}onGotInputSequence: sequence => {kcm.assignToolButtonMapping(form.device.name, modelData.value, sequence)}}}QQC2.Button {// The device must support calibration, and we don't support calibration across multiple screensreadonly property bool supportsCalibration: form.device.supportsCalibrationMatrix && !outputsModel.isMapToWorkspaceAt(outputsCombo.currentIndex)text: {if (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: 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 === form.device.outputName) {screenIndex = i;break;}}const window = component.createObject(root, {device: form.device, 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 = form.device.sysName;root.calibrationWindow = window;}}QQC2.ToolTip.delay: Kirigami.Units.toolTipDelayQQC2.ToolTip.text: i18nc("@info:tooltip", "Calibration across multiple screens is not supported.")QQC2.ToolTip.visible: hovered && outputsModel.isMapToWorkspaceAt(outputsCombo.currentIndex)}Kirigami.Separator {Layout.fillWidth: true}property QtObject padDevice: nullQQC2.ComboBox {Kirigami.FormData.label: i18ndc("kcm_tablet", "@label:listbox The pad we are configuring", "Pad:")model: kcm.padsModelonCurrentIndexChanged: {parent.padDevice = kcm.padsModel.deviceAt(currentIndex)}onCountChanged: if (count > 0 && currentIndex < 0) {currentIndex = 0;}enabled: count > 0displayText: enabled ? currentText : i18n("None")}TabletEvents {id: tabletEventsanchors.fill: parentonPadButtonsChanged: {if (!path.endsWith(form.padDevice.sysName)) {return;}buttonsRepeater.model = buttonCount}}Repeater {id: buttonsRepeatermodel: tabletEvents.padButtonsdelegate: ActionBinding {id: seqrequired property var modelDataKirigami.FormData.label: (buttonPressed ? "<b>" : "") + i18nd("kcm_tablet", "Pad button %1:", modelData + 1) + (buttonPressed ? "</b>" : "")property bool buttonPressed: falsename: i18ndc("kcm_tablet", "@info Meant to be inserted into an existing sentence like 'configuring pad button 0'", "pad button %1", modelData + 1)Connections {target: tabletEventsfunction onPadButtonReceived(path, button, pressed) {if (button !== modelData || !path.endsWith(form.padDevice.sysName)) {return;}seq.buttonPressed = pressed}}function refreshInputSequence(): void {seq.inputSequence = kcm.padButtonMapping(form.padDevice.name, modelData)}inputSequence: kcm.padButtonMapping(form.padDevice.name, modelData)Connections {target: kcmfunction onSettingsRestored() {refreshInputSequence();}}onGotInputSequence: sequence => {kcm.assignPadButtonMapping(form.padDevice.name, modelData, sequence)}}}}ActionDialog {id: actionDialogparent: root.QQC2.Overlay.overlay}}