/* SPDX-FileCopyrightText: 2025 Oliver Beard SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL */ import QtQuick import QtQuick.Layouts import QtQuick.Controls as QQC2 import org.kde.kcmutils as KCM import org.kde.kirigami as Kirigami import org.kde.kquickcontrols as KQuickControls import org.kde.kwindowsystem Kirigami.Form { id: pageLayout component FullScreenZoomSettingsModel : ObjectModel { id: objectModel property bool visible: kcm.zoomMagnifierSettings.zoom Kirigami.FormEntry { visible: objectModel.visible title: i18nc("@label:spinbox", "Show pixel grid at zoom level:") contentItem: QQC2.SpinBox { id: zoomPixelGridZoomSpinBox from: toInt(0) to: toInt(100) stepSize: toInt(1) validator: IntValidator { bottom: Math.min(zoomPixelGridZoomSpinBox.from, zoomPixelGridZoomSpinBox.to) top: Math.max(zoomPixelGridZoomSpinBox.from, zoomPixelGridZoomSpinBox.to) } textFromValue: (value, locale) => fromInt(value).toLocaleString(locale, 'f', 2) valueFromText: (text, locale) => Math.round(toInt(Number.fromLocaleString(locale, text))) value: toInt(kcm.zoomMagnifierSettings.zoomPixelGridZoom) onValueModified: kcm.zoomMagnifierSettings.zoomPixelGridZoom = fromInt(value) function toInt(value: double) : int { return value * 100; } function fromInt(value: int) : double { return value / 100; } KCM.SettingStateBinding { configObject: kcm.zoomMagnifierSettings settingName: "ZoomPixelGridZoom" } } } Kirigami.FormEntry { visible: objectModel.visible title: i18nc("@label:listbox", "Pointer tracking:") contentItem: QQC2.ComboBox { delegate: QQC2.ItemDelegate { id: delegate required property string title required property string description width: parent?.width ?? 0 text: delegate.title contentItem: Kirigami.TitleSubtitle { title: delegate.title subtitle: delegate.description font: delegate.font selected: delegate.highlighted || delegate.down wrapMode: Text.Wrap } } model: [ { title: i18nc("@item:inlistbox", "Proportional"), description: i18nc("@item:inlistbox", "Zoom area moves in sync with pointer"), settingIndex: 0 }, { title: i18nc("@item:inlistbox", "Centered"), description: i18nc("@item:inlistbox", "Pointer stays centered on-screen, except near screen edges"), settingIndex: 1 }, { title: i18nc("@item:inlistbox", "Centered (Strict)"), description: i18nc("@item:inlistbox", "Pointer stays centered on-screen, even near screen edges"), settingIndex: 4 }, { title: i18nc("@item:inlistbox", "Push"), description: i18nc("@item:inlistbox", "Pointer pushes zoom area at screen edges"), settingIndex: 2 }, { title: i18nc("@item:inlistbox", "Disabled"), description: i18nc("@item:inlistbox", "Zoom area doesn't follow pointer"), settingIndex: 3 } ] textRole: "title" currentIndex: model.findIndex(m => m.settingIndex === kcm.zoomMagnifierSettings.zoomMouseTracking) onActivated: index => kcm.zoomMagnifierSettings.zoomMouseTracking = model[index].settingIndex Layout.preferredWidth: Kirigami.Units.gridUnit * 15 KCM.SettingStateBinding { configObject: kcm.zoomMagnifierSettings settingName: "ZoomMouseTracking" } } } Kirigami.FormEntry { visible: objectModel.visible contentItem: QQC2.CheckBox { text: i18nc("@option:check", "Sharpen screen content while zoomed in") checked: kcm.zoomMagnifierSettings.zoomUsePatternUpscaler onCheckedChanged: kcm.zoomMagnifierSettings.zoomUsePatternUpscaler = checked KCM.SettingStateBinding { configObject: kcm.zoomMagnifierSettings settingName: "ZoomUsePatternUpscaler" } } } Kirigami.FormEntry { visible: objectModel.visible contentItem: QQC2.CheckBox { text: i18nc("@option:check", "Enable text caret tracking") checked: kcm.zoomMagnifierSettings.zoomEnableTextCaretTracking onCheckedChanged: kcm.zoomMagnifierSettings.zoomEnableTextCaretTracking = checked KCM.SettingStateBinding { configObject: kcm.zoomMagnifierSettings settingName: "ZoomEnableTextCaretTracking" } } } } component MagnifierZoomSettingsModel : ObjectModel { id: objectModel property bool visible: kcm.zoomMagnifierSettings.magnifier Kirigami.FormEntry { visible: objectModel.visible title: i18nc("@label:spinbox", "Width:") contentItem: QQC2.SpinBox { id: magnifierWidthSpinBox from: 100 to: 2000 stepSize: 10 validator: IntValidator { bottom: Math.min(magnifierWidthSpinBox.from, magnifierWidthSpinBox.to) top: Math.max(magnifierWidthSpinBox.from, magnifierWidthSpinBox.to) } textFromValue: (value, locale) => { return i18ncp("short for pixel(s)", "%1 px", "%1 px", value); } valueFromText: (text, locale) => { return Number.fromLocaleString(locale, text.replace(i18nc("short for pixel(s)", "px"), "")); } value: kcm.zoomMagnifierSettings.magnifierWidth onValueModified: kcm.zoomMagnifierSettings.magnifierWidth = value KCM.SettingStateBinding { configObject: kcm.zoomMagnifierSettings settingName: "MagnifierWidth" } } } Kirigami.FormEntry { visible: objectModel.visible title: i18nc("@label:spinbox", "Height:") contentItem: QQC2.SpinBox { id: magnifierHeightSpinBox from: 100 to: 2000 stepSize: 10 validator: IntValidator { bottom: Math.min(magnifierHeightSpinBox.from, magnifierHeightSpinBox.to) top: Math.max(magnifierHeightSpinBox.from, magnifierHeightSpinBox.to) } textFromValue: (value, locale) => { return i18ncp("short for pixel(s)", "%1 px", "%1 px", value); } valueFromText: (text, locale) => { return Number.fromLocaleString(locale, text.replace(i18nc("short for pixel(s)", "px"), "")); } value: kcm.zoomMagnifierSettings.magnifierHeight onValueModified: kcm.zoomMagnifierSettings.magnifierHeight = value KCM.SettingStateBinding { configObject: kcm.zoomMagnifierSettings settingName: "MagnifierHeight" } } } } Kirigami.FormGroup { Kirigami.FormEntry { title: i18nc("@label, followed by 'full screen' or 'magnify region'", "Zoom:") contentItem: QQC2.RadioButton { id: zoomRadioButton QQC2.ButtonGroup.group: effectGroup text: i18nc("@option:check, enable zoom effect, following 'Zoom:'", "Full screen") checked: kcm.zoomMagnifierSettings.zoom onToggled: { kcm.zoomMagnifierSettings.zoom = true; kcm.zoomMagnifierSettings.magnifier = false; } QQC2.ButtonGroup { id: effectGroup } KCM.SettingHighlighter { highlight: !kcm.zoomMagnifierSettings.zoom } } } Kirigami.FormEntry { contentItem: QQC2.RadioButton { id: magnifierRadioButton QQC2.ButtonGroup.group: effectGroup text: i18nc("@option:check, enable magnify effect, following 'Zoom:'", "Magnify region") checked: kcm.zoomMagnifierSettings.magnifier onToggled: { kcm.zoomMagnifierSettings.zoom = false; kcm.zoomMagnifierSettings.magnifier = true; } KCM.SettingHighlighter { highlight: !kcm.zoomMagnifierSettings.zoom } } } Kirigami.FormEntry { contentItem: QQC2.RadioButton { id: noneRadioButton QQC2.ButtonGroup.group: effectGroup text: i18nc("@option:check, disable zoom/magnify effect", "Disabled") checked: !(kcm.zoomMagnifierSettings.zoom || kcm.zoomMagnifierSettings.magnifier) onToggled: { kcm.zoomMagnifierSettings.zoom = false; kcm.zoomMagnifierSettings.magnifier = false; } KCM.SettingHighlighter { highlight: !kcm.zoomMagnifierSettings.zoom } } } Kirigami.FormSeparator { visible: kcm.zoomMagnifierSettings.zoom || kcm.zoomMagnifierSettings.magnifier} Repeater { model: FullScreenZoomSettingsModel {} } Repeater { model: MagnifierZoomSettingsModel {} } Kirigami.FormSeparator {} Kirigami.FormEntry { title: i18nc("@label:spinbox", "Zoom factor:") contentItem: QQC2.SpinBox { id: sharedZoomFactorSpinBox from: toInt(1.05) to: toInt(4) stepSize: toInt(0.05) validator: IntValidator { bottom: Math.min(sharedZoomFactorSpinBox.from, sharedZoomFactorSpinBox.to) top: Math.max(sharedZoomFactorSpinBox.from, sharedZoomFactorSpinBox.to) } textFromValue: (value, locale) => fromInt(value).toLocaleString(locale, 'f', 2) valueFromText: (text, locale) => Math.round(toInt(Number.fromLocaleString(locale, text))) value: toInt(kcm.zoomMagnifierSettings.sharedZoomFactor) onValueModified: kcm.zoomMagnifierSettings.sharedZoomFactor = fromInt(value) function toInt(value: double) : int { return value * 20; } function fromInt(value: int) : double { return value / 20; } KCM.SettingStateBinding { configObject: kcm.zoomMagnifierSettings settingName: "SharedZoomFactor" extraEnabledConditions: kcm.zoomMagnifierSettings.zoom || kcm.zoomMagnifierSettings.magnifier } } } } Kirigami.FormGroup { Kirigami.FormEntry { title: i18nc("@label", "Scroll gesture modifier keys:") subtitle: i18nc("@label Hint for scroll gestures", "Scroll while modifier keys are pressed to zoom") contentItem: KQuickControls.KeySequenceItem { id: zoomPointerAxisGestureModifiersBox enabled: kcm.zoomMagnifierSettings.zoom || kcm.zoomMagnifierSettings.magnifier keySequence: kcm.zoomMagnifierSettings.zoomPointerAxisGestureModifiers onKeySequenceModified: kcm.zoomMagnifierSettings.zoomPointerAxisGestureModifiers = keySequence patterns: KQuickControls.ShortcutPattern.Modifier multiKeyShortcutsAllowed: false Connections { target: kcm.zoomMagnifierSettings // We have to do this because keySequence binding is broken function onZoomPointerAxisGestureModifiersChanged() { zoomPointerAxisGestureModifiersBox.keySequence = kcm.zoomMagnifierSettings.zoomPointerAxisGestureModifiers; } } KCM.SettingStateBinding { configObject: kcm.zoomMagnifierSettings settingName: "ZoomPointerAxisGestureModifiers" } } } Kirigami.FormSeparator {} Kirigami.FormAction { enabled: action.enabled action: QQC2.Action { enabled: (kcm.zoomMagnifierSettings.zoom || kcm.zoomMagnifierSettings.magnifier) text: i18nc("@action:button", "Configure Shortcuts…") icon.name: "preferences-desktop-keyboard-shortcut" onTriggered: kcm.configureZoomMagnifyShortcuts() } } } }