Warning, /graphics/spectacle/src/Gui/Annotations/TextTool.qml is written in an unsupported language. File is not indexed.

0001 /* SPDX-FileCopyrightText: 2022 Marco Martin <mart@kde.org>
0002  * SPDX-FileCopyrightText: 2022 Noah Davis <noahadvs@gmail.com>
0003  * SPDX-License-Identifier: LGPL-2.0-or-later
0004  */
0005 
0006 import QtQuick
0007 import QtQuick.Templates as T
0008 import org.kde.kirigami as Kirigami
0009 import org.kde.spectacle.private
0010 import ".."
0011 
0012 AnimatedLoader {
0013     id: root
0014     required property AnnotationViewport viewport
0015     readonly property AnnotationDocument document: viewport.document
0016     readonly property bool shouldShow: enabled
0017         && document.selectedItem.options & AnnotationTool.TextOption
0018         && (document.tool.options & AnnotationTool.TextOption
0019             || document.tool.type === AnnotationTool.SelectTool)
0020 
0021     state: shouldShow ? "active" : "inactive"
0022 
0023     sourceComponent: T.TextArea {
0024         id: textField
0025         readonly property bool mirrored: effectiveHorizontalAlignment === TextInput.AlignRight
0026 
0027         LayoutMirroring.enabled: false
0028         LayoutMirroring.childrenInherit: true
0029 
0030         Binding on implicitWidth {
0031             value: root.document.selectedItem.mousePath.boundingRect.width
0032             restoreMode: Binding.RestoreNone
0033             when: root.shouldShow
0034         }
0035         Binding on implicitHeight {
0036             value: root.document.selectedItem.mousePath.boundingRect.height
0037             restoreMode: Binding.RestoreNone
0038             when: root.shouldShow
0039         }
0040         Binding {
0041             target: root
0042             property: "x"
0043             when: root.shouldShow
0044             value: root.document.selectedItem.mousePath.boundingRect.x
0045             restoreMode: Binding.RestoreNone
0046         }
0047         Binding {
0048             target: root
0049             property: "y"
0050             when: root.shouldShow
0051             value: root.document.selectedItem.mousePath.boundingRect.y
0052             restoreMode: Binding.RestoreNone
0053         }
0054         property color textColor
0055         Binding on textColor {
0056             value: root.document.selectedItem.options & AnnotationTool.TextOption ?
0057                 root.document.selectedItem.fontColor : root.document.tool.fontColor
0058             restoreMode: Binding.RestoreNone
0059             when: root.shouldShow
0060         }
0061         color: Qt.rgba(textColor.r, textColor.g, textColor.b, 0)
0062         Binding on font {
0063             value: root.document.selectedItem.options & AnnotationTool.TextOption ?
0064                 root.document.selectedItem.font : root.document.tool.font
0065             restoreMode: Binding.RestoreNone
0066             when: root.shouldShow
0067         }
0068 
0069         focus: true
0070         selectByMouse: true
0071         selectionColor: Qt.rgba(1-textColor.r, 1-textColor.g, 1-textColor.b, 1)
0072         selectedTextColor: Qt.rgba(textColor.r, textColor.g, textColor.b, 1)
0073         cursorPosition: {
0074             const mapped = mapFromItem(root.viewport, root.viewport.pressPosition)
0075             return positionAt(mapped.x, mapped.y)
0076         }
0077         cursorDelegate: Item {
0078             id: cursor
0079             visible: textField.cursorVisible
0080             Rectangle {
0081                 // prevent the cursor from overlapping with the background
0082                 x: textField.cursorPosition === textField.length && textField.length > 0 ?
0083                     -width : 0
0084                 width: Math.max(1 / root.viewport.zoom,
0085                                 contextWindow.dprRound(fontMetrics.xHeight / 12))
0086                 height: parent.height
0087                 color: Qt.rgba(textField.textColor.r, textField.textColor.g, textField.textColor.b, 1)
0088             }
0089             Connections {
0090                 target: textField
0091                 function onCursorPositionChanged() {
0092                     if (textField.cursorVisible) {
0093                         Qt.callLater(blinkAnimation.restart)
0094                     }
0095                 }
0096             }
0097             SequentialAnimation {
0098                 id: blinkAnimation
0099                 running: textField.cursorVisible
0100                 loops: Animation.Infinite
0101                 PropertyAction {
0102                     target: cursor
0103                     property: "visible"
0104                     value: true
0105                 }
0106                 PauseAnimation {
0107                     duration: Qt.styleHints.cursorFlashTime / 2
0108                 }
0109                 PropertyAction {
0110                     target: cursor
0111                     property: "visible"
0112                     value: false
0113                 }
0114                 PauseAnimation {
0115                     duration: Qt.styleHints.cursorFlashTime / 2
0116                 }
0117             }
0118         }
0119 
0120         // Keep this in sync with the value used in Traits::createTextPath
0121         tabStopDistance: Math.round(fontMetrics.advanceWidth("x") * 8)
0122         // QPainter::drawText doesn't support rich text.
0123         // We could consider using QStaticText to add rich text support.
0124         // We probably shouldn't use QTextDocument because that's unnecessarily heavy.
0125         textFormat: TextEdit.PlainText
0126         // Keep in sync with Traits::Text::textFlags
0127         horizontalAlignment: TextEdit.AlignLeft
0128         verticalAlignment: TextEdit.AlignTop
0129         wrapMode: TextEdit.NoWrap
0130 
0131         // QPainter uses native antialiasing
0132         renderType: TextEdit.NativeRendering
0133 
0134         Binding on text {
0135             value: root.document.selectedItem.text
0136             restoreMode: Binding.RestoreNone
0137             when: root.shouldShow
0138         }
0139         onTextChanged: {
0140             if (root.document.selectedItem.text === text) {
0141                 return
0142             }
0143             let wasEmpty = root.document.selectedItem.text.length === 0
0144             root.document.selectedItem.text = text
0145             if (wasEmpty) {
0146                 root.document.selectedItem.commitChanges()
0147             } else {
0148                 commitChangesTimer.restart()
0149             }
0150         }
0151 
0152         Keys.onDeletePressed: (event) => {
0153             event.accepted = text.length === 0
0154             if (event.accepted) {
0155                 root.document.deleteSelectedItem()
0156             }
0157         }
0158 
0159         Timer {
0160             id: commitChangesTimer
0161             interval: 250
0162             onTriggered: root.document.selectedItem.commitChanges()
0163         }
0164 
0165         Connections {
0166             target: root.document
0167             function onSelectedItemWrapperChanged() {
0168                 commitChangesTimer.stop()
0169             }
0170         }
0171 
0172         leftInset: -background.effectiveStrokeWidth
0173         rightInset: -background.effectiveStrokeWidth
0174         topInset: -background.effectiveStrokeWidth
0175         bottomInset: -background.effectiveStrokeWidth
0176         background: SelectionBackground {
0177             zoom: root.viewport.effectiveZoom
0178         }
0179 
0180         FontMetrics {
0181             id: fontMetrics
0182             font: textField.font
0183         }
0184 
0185         Rectangle {
0186             id: handle
0187             implicitHeight: fontMetrics.height % 2 ? fontMetrics.height + 1 : fontMetrics.height
0188             implicitWidth: implicitHeight
0189             anchors.left: textField.effectiveHorizontalAlignment === TextInput.AlignRight ?
0190                 parent.right : undefined
0191             anchors.right: textField.effectiveHorizontalAlignment === TextInput.AlignLeft ?
0192                 parent.left : undefined
0193             anchors.margins: Kirigami.Units.mediumSpacing
0194             radius: height / 2
0195             color: Kirigami.Theme.backgroundColor
0196             Kirigami.Icon {
0197                 height: Kirigami.Units.iconSizes.roundedIconSize(parent.height)
0198                 width: height
0199                 anchors.centerIn: parent
0200                 source: "transform-move"
0201             }
0202             DragHandler {
0203                 id: dragHandler
0204                 target: null
0205                 cursorShape: Qt.SizeAllCursor
0206                 dragThreshold: 0
0207                 onActiveTranslationChanged: if (active) {
0208                     let dx = activeTranslation.x / viewport.effectiveZoom
0209                     let dy = activeTranslation.y / viewport.effectiveZoom
0210                     root.document.selectedItem.transform(dx, dy)
0211                 }
0212                 onActiveChanged: if (!active) {
0213                     root.document.selectedItem.commitChanges()
0214                 }
0215             }
0216             TapHandler {
0217                 cursorShape: Qt.SizeAllCursor
0218             }
0219             HoverHandler {
0220                 cursorShape: Qt.SizeAllCursor
0221             }
0222         }
0223         Component.onCompleted: forceActiveFocus()
0224     }
0225 }
0226