// SPDX-FileCopyrightText: 2024 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.syntaxhighlighting import org.kde.neochat QQC2.Control { id: root /** * @brief The index of the delegate in the model. */ required property int index /** * @brief The matrix ID of the message event. */ required property string eventId /** * @brief The message author. * * A Quotient::RoomMember object. * * @sa Quotient::RoomMember */ required property NeochatRoomMember author /** * @brief The timestamp of the event as a neoChatDateTime. */ required property neoChatDateTime dateTime /** * @brief The display text of the message. */ required property string display /** * @brief Whether the component should be editable. */ required property bool editable /** * @brief The attributes of the component. */ required property var componentAttributes readonly property ChatTextItemHelper chatTextItemHelper: componentAttributes?.chatTextItemHelper ?? null onChatTextItemHelperChanged: if (chatTextItemHelper) { chatTextItemHelper.textItem = codeText; } /** * @brief Whether the component is currently focussed. */ required property bool currentFocus onCurrentFocusChanged: if (currentFocus && !codeText.focus) { codeText.forceActiveFocus(); } /** * @brief The user selected text has changed. */ signal selectedTextChanged(string selectedText) Layout.fillWidth: true Layout.fillHeight: true Layout.maximumWidth: Message.maxContentWidth Layout.maximumHeight: Kirigami.Units.gridUnit * 20 width: ListView.view?.width ?? -1 topPadding: 0 bottomPadding: 0 leftPadding: 0 rightPadding: 0 contentItem: QQC2.ScrollView { id: codeScrollView contentWidth: root.Message.maxContentWidth // HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890) QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff QQC2.TextArea { id: codeText Keys.onUpPressed: (event) => { event.accepted = true; Message.contentModel.keyHelper.up(); } Keys.onDownPressed: (event) => { event.accepted = true; Message.contentModel.keyHelper.down(); } Keys.onDeletePressed: (event) => { event.accepted = true; root.Message.contentModel.keyHelper.deleteChar(); } Keys.onPressed: (event) => { if (event.key == Qt.Key_Backspace && cursorPosition == 0) { event.accepted = true; root.Message.contentModel.keyHelper.backspace(); return; } event.accepted = false; } onFocusChanged: if (focus && !root.currentFocus) { Message.contentModel.setFocusRow(root.index, true) } topPadding: Kirigami.Units.smallSpacing bottomPadding: Kirigami.Units.smallSpacing leftPadding: lineNumberColumn.width + lineNumberColumn.anchors.leftMargin + Kirigami.Units.smallSpacing * 2 text: root.editable ? "" : root.display readOnly: !root.editable textFormat: TextEdit.PlainText wrapMode: TextEdit.Wrap color: Kirigami.Theme.textColor font.family: "monospace" font.pointSize: Kirigami.Theme.defaultFont.pointSize * NeoChatConfig.fontScale Kirigami.SpellCheck.enabled: false onWidthChanged: lineModel.resetModel() onHeightChanged: lineModel.resetModel() onSelectedTextChanged: root.selectedTextChanged(selectedText) SyntaxHighlighter { property string definitionName: Repository.definitionForName(root.componentAttributes.class).name textEdit: definitionName == "None" ? null : codeText definition: definitionName } ColumnLayout { id: lineNumberColumn anchors { top: codeText.top topMargin: codeText.topPadding left: codeText.left leftMargin: Kirigami.Units.smallSpacing } spacing: 0 Repeater { id: repeater model: LineModel { id: lineModel Component.onCompleted: setDocument(codeText.textDocument) } delegate: QQC2.Label { id: label required property int index required property int docLineHeight Layout.fillWidth: true Layout.preferredHeight: docLineHeight horizontalAlignment: Text.AlignRight text: index + 1 color: Kirigami.Theme.disabledTextColor font.family: "monospace" } } } TapHandler { acceptedDevices: PointerDevice.TouchScreen onLongPressed: { const event = root.Message.room.findEvent(root.eventId); RoomManager.viewEventMenu(root.QQC2.Overlay.overlay, event, root.Message.room, root.Message.selectedText, root.Message.hoveredLink); } } background: null } } Kirigami.Separator { anchors { top: root.top bottom: root.bottom left: root.left leftMargin: lineNumberColumn.width + lineNumberColumn.anchors.leftMargin + Kirigami.Units.smallSpacing } } RowLayout { anchors { top: parent.top topMargin: Kirigami.Units.smallSpacing right: parent.right rightMargin: (codeScrollView.QQC2.ScrollBar.vertical.visible ? codeScrollView.QQC2.ScrollBar.vertical.width : 0) + Kirigami.Units.smallSpacing } visible: root.hovered && !root.editable spacing: Kirigami.Units.mediumSpacing QQC2.Button { icon.name: "edit-copy" text: i18n("Copy to clipboard") display: QQC2.AbstractButton.IconOnly onClicked: Clipboard.saveText(root.display); QQC2.ToolTip.text: text QQC2.ToolTip.visible: hovered QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay } QQC2.Button { visible: root.dateTime.isValid icon.name: "view-fullscreen" text: i18nc("@action:button", "Maximize") display: QQC2.AbstractButton.IconOnly onClicked: RoomManager.maximizeCode(root.author, root.dateTime, root.display, root.componentAttributes.class); QQC2.ToolTip.text: text QQC2.ToolTip.visible: hovered QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay } } background: Rectangle { color: Kirigami.Theme.backgroundColor Kirigami.Theme.colorSet: Kirigami.Theme.View Kirigami.Theme.inherit: false radius: Kirigami.Units.cornerRadius border { width: 1 color: Kirigami.Theme.highlightColor } } }