// SPDX-FileCopyrightText: 2023 James Graham // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL import QtQuick import QtQuick.Controls as QQC2 import QtQuick.Layouts import org.kde.kirigami as Kirigami import org.kde.neochat import org.kde.neochat.chatbar /** * @brief A component to show an edit text field for a text message being edited. */ QQC2.TextArea { id: root /** * @brief The NeoChatRoom the delegate is being displayed in. */ required property NeoChatRoom room onRoomChanged: { _private.chatBarCache = room.editCache; _private.chatBarCache.relationIdChanged.connect(_private.updateEditText()); } /** * @brief The ActionsHandler object to use. * * This is expected to have the correct room set otherwise messages will be sent * to the wrong room. */ required property ActionsHandler actionsHandler property var minimumHeight: editButtons.height + topPadding + bottomPadding property var preferredWidth: editTextMetrics.advanceWidth + rightPadding + Kirigami.Units.smallSpacing + Kirigami.Units.gridUnit /** * @brief The maximum width that the bubble's content can be. */ property real maxContentWidth: -1 Layout.fillWidth: true Layout.maximumWidth: root.maxContentWidth Component.onCompleted: _private.updateEditText() rightPadding: editButtons.width + editButtons.anchors.rightMargin * 2 color: Kirigami.Theme.textColor verticalAlignment: TextEdit.AlignVCenter wrapMode: TextEdit.Wrap onTextChanged: { _private.chatBarCache.text = text; } Keys.onEnterPressed: { if (completionMenu.visible) { completionMenu.complete(); } else if (event.modifiers & Qt.ShiftModifier) { root.insert(cursorPosition, "\n"); } else { root.postEdit(); } } Keys.onReturnPressed: { if (completionMenu.visible) { completionMenu.complete(); } else if (event.modifiers & Qt.ShiftModifier) { root.insert(cursorPosition, "\n"); } else { root.postEdit(); } } Keys.onTabPressed: { if (completionMenu.visible) { completionMenu.complete(); } } Keys.onPressed: event => { if (event.key === Qt.Key_Up && completionMenu.visible) { completionMenu.decrementIndex(); } else if (event.key === Qt.Key_Down && completionMenu.visible) { completionMenu.incrementIndex(); } } /** * This is anchored like this so that control expands properly as the edited * text grows in length. */ RowLayout { id: editButtons anchors.verticalCenter: root.verticalCenter anchors.right: root.right anchors.rightMargin: Kirigami.Units.smallSpacing spacing: 0 QQC2.ToolButton { display: QQC2.AbstractButton.IconOnly action: Kirigami.Action { text: i18nc("@action:button", "Confirm edit") icon.name: "checkmark" onTriggered: { root.postEdit(); } } QQC2.ToolTip.text: text QQC2.ToolTip.visible: hovered } QQC2.ToolButton { display: QQC2.AbstractButton.IconOnly action: Kirigami.Action { text: i18nc("@action:button", "Cancel edit") icon.name: "dialog-close" onTriggered: { _private.chatBarCache.editId = ""; } shortcut: "Escape" } QQC2.ToolTip.text: text QQC2.ToolTip.visible: hovered } } CompletionMenu { id: completionMenu height: implicitHeight y: -height - 5 z: 10 connection: root.room.connection chatDocumentHandler: documentHandler Behavior on height { NumberAnimation { property: "height" duration: Kirigami.Units.shortDuration easing.type: Easing.OutCubic } } } // opt-out of whatever spell checker a styled TextArea might come with Kirigami.SpellCheck.enabled: false ChatDocumentHandler { id: documentHandler document: root.textDocument cursorPosition: root.cursorPosition selectionStart: root.selectionStart selectionEnd: root.selectionEnd room: root.room // We don't care about saving for edits so this is OK. mentionColor: Kirigami.Theme.linkColor errorColor: Kirigami.Theme.negativeTextColor } TextMetrics { id: editTextMetrics text: root.text } function postEdit() { root.actionsHandler.handleMessageEvent(_private.chatBarCache); root.clear(); _private.chatBarCache.editId = ""; } QtObject { id: _private property ChatBarCache chatBarCache onChatBarCacheChanged: documentHandler.chatBarCache = chatBarCache function updateEditText() { // This could possibly be undefined due to some esoteric QtQuick issue. Referencing it somewhere in JS is enough. documentHandler.document; if (chatBarCache?.isEditing && chatBarCache.relationMessage.length > 0) { root.text = chatBarCache.relationMessage; chatBarCache.updateMentions(root.textDocument, documentHandler); root.forceActiveFocus(); root.cursorPosition = root.length; } } } }