From 27662f9a4a8762c0ceca4e57611275d313e207ec Mon Sep 17 00:00:00 2001 From: James Graham Date: Fri, 26 Jan 2024 15:58:12 +0000 Subject: [PATCH] MessageSource Line Numbers Create a model for getting line numbers from a QQuickTextDocument and then add them to the MessageSource page --- src/CMakeLists.txt | 2 + src/models/linemodel.cpp | 64 +++++++++++++++++++++++++++ src/models/linemodel.h | 80 ++++++++++++++++++++++++++++++++++ src/qml/MessageSourceSheet.qml | 62 +++++++++++++++++++++++--- 4 files changed, 202 insertions(+), 6 deletions(-) create mode 100644 src/models/linemodel.cpp create mode 100644 src/models/linemodel.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 86e3a86f8..d6c72e62e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -148,6 +148,8 @@ add_library(neochat STATIC models/itinerarymodel.h proxycontroller.cpp proxycontroller.h + models/linemodel.cpp + models/linemodel.h ) qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN diff --git a/src/models/linemodel.cpp b/src/models/linemodel.cpp new file mode 100644 index 000000000..6dc12e246 --- /dev/null +++ b/src/models/linemodel.cpp @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: 2024 James Graham +// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + +#include "linemodel.h" + +LineModel::LineModel(QObject *parent) + : QAbstractListModel(parent) +{ +} + +QQuickTextDocument *LineModel::document() const +{ + return m_document; +} + +void LineModel::setDocument(QQuickTextDocument *document) +{ + if (document == m_document) { + return; + } + + m_document = document; + Q_EMIT documentChanged(); + + resetModel(); +} + +QVariant LineModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return {}; + } + + const auto &row = index.row(); + if (row < 0 || row > rowCount()) { + return {}; + } + + if (role == LineHeightRole) { + auto textDoc = m_document->textDocument(); + return int(textDoc->documentLayout()->blockBoundingRect(textDoc->findBlockByNumber(row)).height()); + } + return {}; +} + +int LineModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + if (m_document == nullptr) { + return 0; + } + return m_document->textDocument()->blockCount(); +} + +QHash LineModel::roleNames() const +{ + return {{LineHeightRole, "docLineHeight"}}; +} + +void LineModel::resetModel() +{ + beginResetModel(); + endResetModel(); +} diff --git a/src/models/linemodel.h b/src/models/linemodel.h new file mode 100644 index 000000000..a8d5997e1 --- /dev/null +++ b/src/models/linemodel.h @@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: 2024 James Graham +// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + +#pragma once + +#include +#include +#include +#include +#include +#include + +/** + * @class LineModel + * + * A model to provide line info for a QQuickTextDocument. + * + * @sa QQuickTextDocument + */ +class LineModel : public QAbstractListModel +{ + Q_OBJECT + QML_ELEMENT + + /** + * @brief The QQuickTextDocument that is being handled. + */ + Q_PROPERTY(QQuickTextDocument *document READ document WRITE setDocument NOTIFY documentChanged) + +public: + /** + * @brief Defines the model roles. + */ + enum Roles { + LineHeightRole = Qt::UserRole + 1, /**< The delegate type of the message. */ + }; + Q_ENUM(Roles) + + explicit LineModel(QObject *parent = nullptr); + + [[nodiscard]] QQuickTextDocument *document() const; + void setDocument(QQuickTextDocument *document); + + /** + * @brief Get the given role value at the given index. + * + * @sa QAbstractItemModel::data + */ + [[nodiscard]] QVariant data(const QModelIndex &index, int role) const override; + + /** + * @brief Number of rows in the model. + * + * @sa QAbstractItemModel::rowCount + */ + [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + /** + * @brief Returns a mapping from Role enum values to role names. + * + * @sa Roles, QAbstractItemModel::roleNames() + */ + [[nodiscard]] QHash roleNames() const override; + + /** + * @brief Reset the model. + * + * This needs to be called when the QQuickTextDocument container changes width + * or height as this may change line heights due to wrapping. + * + * @sa QQuickTextDocument + */ + Q_INVOKABLE void resetModel(); + +Q_SIGNALS: + void documentChanged(); + +private: + QPointer m_document = nullptr; +}; diff --git a/src/qml/MessageSourceSheet.qml b/src/qml/MessageSourceSheet.qml index de2ed777c..4fbbc3fc2 100644 --- a/src/qml/MessageSourceSheet.qml +++ b/src/qml/MessageSourceSheet.qml @@ -4,6 +4,7 @@ import QtQuick import QtQuick.Controls as QQC2 +import QtQuick.Layouts import org.kde.kirigami as Kirigami import org.kde.syntaxhighlighting @@ -11,6 +12,7 @@ import org.kde.syntaxhighlighting import org.kde.neochat Kirigami.Page { + id: root property string sourceText topPadding: 0 @@ -21,6 +23,7 @@ Kirigami.Page { title: i18n("Event Source") QQC2.ScrollView { + id: scrollView anchors.fill: parent contentWidth: availableWidth @@ -29,24 +32,71 @@ Kirigami.Page { QQC2.TextArea { id: sourceTextArea - text: sourceText + Layout.fillWidth: true + + leftPadding: lineNumberColumn.width + lineNumberColumn.anchors.leftMargin + Kirigami.Units.smallSpacing * 2 + + text: root.sourceText readOnly: true textFormat: TextEdit.PlainText wrapMode: TextEdit.Wrap - background: Rectangle { - Kirigami.Theme.colorSet: Kirigami.Theme.View - Kirigami.Theme.inherit: false - color: Kirigami.Theme.backgroundColor - } // opt-out of whatever spell checker a styled TextArea might come with Kirigami.SpellCheck.enabled: false + onWidthChanged: lineModel.resetModel() + onHeightChanged: lineModel.resetModel() + SyntaxHighlighter { textEdit: sourceTextArea definition: "JSON" repository: Repository } + + ColumnLayout { + id: lineNumberColumn + + anchors { + top: sourceTextArea.top + topMargin: sourceTextArea.topPadding + left: sourceTextArea.left + leftMargin: Kirigami.Units.smallSpacing + } + spacing: 0 + Repeater { + id: repeater + model: LineModel { + id: lineModel + document: sourceTextArea.textDocument + } + delegate: QQC2.Label { + id: label + required property int index + required property int docLineHeight + Layout.fillWidth: true + Layout.preferredHeight: docLineHeight + topPadding: 1 + horizontalAlignment: Text.AlignRight + text: index + 1 + color: Kirigami.Theme.disabledTextColor + } + } + } + + background: Rectangle { + Kirigami.Theme.colorSet: Kirigami.Theme.View + Kirigami.Theme.inherit: false + color: Kirigami.Theme.backgroundColor + } + } + } + + Kirigami.Separator { + anchors { + top: parent.top + bottom: parent.bottom + left: parent.left + leftMargin: lineNumberColumn.width + lineNumberColumn.anchors.leftMargin + Kirigami.Units.smallSpacing } } }