From bc82ceeb5f4f7b5e0e89ee889e8e51e4174abec0 Mon Sep 17 00:00:00 2001 From: James Graham Date: Mon, 11 Aug 2025 19:23:40 +0100 Subject: [PATCH] Simpify the API for ChatDocumentHandler Simpify the API for ChatDocumentHandler by taking the text item and grabbing everything else needed from there --- src/chatbar/ChatBar.qml | 7 +- src/libneochat/chatdocumenthandler.cpp | 175 +++++++++--------------- src/libneochat/chatdocumenthandler.h | 76 ++-------- src/messagecontent/ChatBarComponent.qml | 9 +- 4 files changed, 80 insertions(+), 187 deletions(-) diff --git a/src/chatbar/ChatBar.qml b/src/chatbar/ChatBar.qml index 2340c72dc..f876f9a0f 100644 --- a/src/chatbar/ChatBar.qml +++ b/src/chatbar/ChatBar.qml @@ -500,13 +500,8 @@ QQC2.Control { ChatDocumentHandler { id: documentHandler type: ChatBarType.Room + textItem: textField room: root.currentRoom - document: textField.textDocument - cursorPosition: textField.cursorPosition - selectionStart: textField.selectionStart - selectionEnd: textField.selectionEnd - mentionColor: Kirigami.Theme.linkColor - errorColor: Kirigami.Theme.negativeTextColor } Component { diff --git a/src/libneochat/chatdocumenthandler.cpp b/src/libneochat/chatdocumenthandler.cpp index 5b0162d9c..8f7317e53 100644 --- a/src/libneochat/chatdocumenthandler.cpp +++ b/src/libneochat/chatdocumenthandler.cpp @@ -1,16 +1,19 @@ // SPDX-FileCopyrightText: 2020 Carl Schwan +// SPDX-FileCopyrightText: 2025 James Graham // SPDX-License-Identifier: GPL-3.0-or-later #include "chatdocumenthandler.h" #include #include +#include #include #include #include #include #include +#include #include #include @@ -33,10 +36,16 @@ public: SyntaxHighlighter(QObject *parent) : QSyntaxHighlighter(parent) { - mentionFormat.setFontWeight(QFont::Bold); - mentionFormat.setForeground(Qt::blue); + m_theme = static_cast(qmlAttachedPropertiesObject(this, true)); + connect(m_theme, &Kirigami::Platform::PlatformTheme::colorsChanged, this, [this]() { + mentionFormat.setForeground(m_theme->linkColor()); + errorFormat.setForeground(m_theme->negativeTextColor()); + }); - errorFormat.setForeground(Qt::red); + mentionFormat.setFontWeight(QFont::Bold); + mentionFormat.setForeground(m_theme->linkColor()); + + errorFormat.setForeground(m_theme->negativeTextColor()); errorFormat.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline); connect(checker, &Sonnet::BackgroundChecker::misspelling, this, [this](const QString &word, int start) { @@ -101,29 +110,22 @@ public: }), mentions->end()); } + +private: + Kirigami::Platform::PlatformTheme *m_theme = nullptr; }; ChatDocumentHandler::ChatDocumentHandler(QObject *parent) : QObject(parent) - , m_document(nullptr) - , m_cursorPosition(-1) , m_highlighter(new SyntaxHighlighter(this)) , m_completionModel(new CompletionModel(this)) { - connect(this, &ChatDocumentHandler::documentChanged, this, [this]() { - if (!m_document) { - m_highlighter->setDocument(nullptr); - return; - } - m_highlighter->setDocument(m_document->textDocument()); - }); - connect(this, &ChatDocumentHandler::cursorPositionChanged, this, [this]() { - if (!m_room) { - return; - } - int start = completionStartIndex(); - m_completionModel->setText(getText().mid(start, cursorPosition() - start), getText().mid(start)); - }); +} + +void ChatDocumentHandler::updateCompletion() const +{ + int start = completionStartIndex(); + m_completionModel->setText(getText().mid(start, cursorPosition() - start), getText().mid(start)); } int ChatDocumentHandler::completionStartIndex() const @@ -160,38 +162,49 @@ void ChatDocumentHandler::setType(ChatBarType::Type type) Q_EMIT typeChanged(); } -QQuickTextDocument *ChatDocumentHandler::document() const +QQuickItem *ChatDocumentHandler::textItem() const { - return m_document; + return m_textItem; } -void ChatDocumentHandler::setDocument(QQuickTextDocument *document) +void ChatDocumentHandler::setTextItem(QQuickItem *textItem) { - if (document == m_document) { + if (textItem == m_textItem) { return; } - if (m_document) { - m_document->textDocument()->disconnect(this); + if (m_textItem) { + m_textItem->disconnect(this); + if (const auto textDoc = document()) { + textDoc->disconnect(this); + } } - m_document = document; - Q_EMIT documentChanged(); + + m_textItem = textItem; + + m_highlighter->setDocument(document()); + if (m_textItem) { + connect(m_textItem, SIGNAL(cursorPositionChanged()), this, SLOT(updateCompletion())); + } + + Q_EMIT textItemChanged(); +} + +QTextDocument *ChatDocumentHandler::document() const +{ + if (!m_textItem) { + return nullptr; + } + const auto quickDocument = qvariant_cast(m_textItem->property("textDocument")); + return quickDocument ? quickDocument->textDocument() : nullptr; } int ChatDocumentHandler::cursorPosition() const { - return m_cursorPosition; -} - -void ChatDocumentHandler::setCursorPosition(int position) -{ - if (position == m_cursorPosition) { - return; + if (!m_textItem) { + return -1; } - if (m_room) { - m_cursorPosition = position; - } - Q_EMIT cursorPositionChanged(); + return m_textItem->property("cursorPosition").toInt(); } NeoChatRoom *ChatDocumentHandler::room() const @@ -207,8 +220,8 @@ void ChatDocumentHandler::setRoom(NeoChatRoom *room) if (m_room && m_type != ChatBarType::None) { m_room->cacheForType(m_type)->disconnect(this); - if (!m_room->isSpace() && m_document && m_type == ChatBarType::Room) { - m_room->mainCache()->setSavedText(document()->textDocument()->toPlainText()); + if (!m_room->isSpace() && document() && m_type == ChatBarType::Room) { + m_room->mainCache()->setSavedText(document()->toPlainText()); } } @@ -220,8 +233,8 @@ void ChatDocumentHandler::setRoom(NeoChatRoom *room) int start = completionStartIndex(); m_completionModel->setText(getText().mid(start, cursorPosition() - start), getText().mid(start)); }); - if (!m_room->isSpace() && m_document && m_type == ChatBarType::Room) { - document()->textDocument()->setPlainText(room->mainCache()->savedText()); + if (!m_room->isSpace() && document() && m_type == ChatBarType::Room) { + document()->setPlainText(room->mainCache()->savedText()); m_room->mainCache()->setText(room->mainCache()->savedText()); } } @@ -239,7 +252,7 @@ ChatBarCache *ChatDocumentHandler::chatBarCache() const void ChatDocumentHandler::complete(int index) { - if (m_document == nullptr) { + if (document() == nullptr) { qCWarning(ChatDocumentHandling) << "complete called with m_document set to nullptr."; return; } @@ -256,7 +269,7 @@ void ChatDocumentHandler::complete(int index) auto id = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::SubtitleRole).toString(); auto text = getText(); auto at = text.indexOf(QLatin1Char('@'), fromIndex); - QTextCursor cursor(document()->textDocument()); + QTextCursor cursor(document()); cursor.setPosition(at); cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor); cursor.insertText(name + u" "_s); @@ -269,7 +282,7 @@ void ChatDocumentHandler::complete(int index) auto command = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::ReplacedTextRole).toString(); auto text = getText(); auto at = text.indexOf(QLatin1Char('/'), fromIndex); - QTextCursor cursor(document()->textDocument()); + QTextCursor cursor(document()); cursor.setPosition(at); cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor); cursor.insertText(u"/%1 "_s.arg(command)); @@ -277,7 +290,7 @@ void ChatDocumentHandler::complete(int index) auto alias = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::SubtitleRole).toString(); auto text = getText(); auto at = text.indexOf(QLatin1Char('#'), fromIndex); - QTextCursor cursor(document()->textDocument()); + QTextCursor cursor(document()); cursor.setPosition(at); cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor); cursor.insertText(alias + u" "_s); @@ -290,7 +303,7 @@ void ChatDocumentHandler::complete(int index) auto shortcode = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::ReplacedTextRole).toString(); auto text = getText(); auto at = text.indexOf(QLatin1Char(':'), fromIndex); - QTextCursor cursor(document()->textDocument()); + QTextCursor cursor(document()); cursor.setPosition(at); cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor); cursor.insertText(shortcode); @@ -302,36 +315,6 @@ CompletionModel *ChatDocumentHandler::completionModel() const return m_completionModel; } -int ChatDocumentHandler::selectionStart() const -{ - return m_selectionStart; -} - -void ChatDocumentHandler::setSelectionStart(int position) -{ - if (position == m_selectionStart) { - return; - } - - m_selectionStart = position; - Q_EMIT selectionStartChanged(); -} - -int ChatDocumentHandler::selectionEnd() const -{ - return m_selectionEnd; -} - -void ChatDocumentHandler::setSelectionEnd(int position) -{ - if (position == m_selectionEnd) { - return; - } - - m_selectionEnd = position; - Q_EMIT selectionEndChanged(); -} - QString ChatDocumentHandler::getText() const { if (!m_room || m_type == ChatBarType::None) { @@ -350,42 +333,8 @@ void ChatDocumentHandler::pushMention(const Mention mention) const m_room->cacheForType(m_type)->mentions()->push_back(mention); } -QColor ChatDocumentHandler::mentionColor() const +void ChatDocumentHandler::updateMentions(const QString &editId) { - return m_mentionColor; -} - -void ChatDocumentHandler::setMentionColor(const QColor &color) -{ - if (m_mentionColor == color) { - return; - } - m_mentionColor = color; - m_highlighter->mentionFormat.setForeground(m_mentionColor); - m_highlighter->rehighlight(); - Q_EMIT mentionColorChanged(); -} - -QColor ChatDocumentHandler::errorColor() const -{ - return m_errorColor; -} - -void ChatDocumentHandler::setErrorColor(const QColor &color) -{ - if (m_errorColor == color) { - return; - } - m_errorColor = color; - m_highlighter->errorFormat.setForeground(m_errorColor); - m_highlighter->rehighlight(); - Q_EMIT errorColorChanged(); -} - -void ChatDocumentHandler::updateMentions(QQuickTextDocument *document, const QString &editId) -{ - setDocument(document); - if (editId.isEmpty() || m_type == ChatBarType::None || !m_room) { return; } @@ -409,7 +358,7 @@ void ChatDocumentHandler::updateMentions(QQuickTextDocument *document, const QSt const int end = position + name.length(); linkSize += match.capturedLength(0) - name.length(); - QTextCursor cursor(this->document()->textDocument()); + QTextCursor cursor(document()); cursor.setPosition(position); cursor.setPosition(end, QTextCursor::KeepAnchor); cursor.setKeepPositionOnInsert(true); diff --git a/src/libneochat/chatdocumenthandler.h b/src/libneochat/chatdocumenthandler.h index 62ccfc476..a836f1ab7 100644 --- a/src/libneochat/chatdocumenthandler.h +++ b/src/libneochat/chatdocumenthandler.h @@ -1,11 +1,11 @@ // SPDX-FileCopyrightText: 2020 Carl Schwan +// SPDX-FileCopyrightText: 2025 James Graham // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include #include -#include #include #include "chatbarcache.h" @@ -13,6 +13,8 @@ #include "models/completionmodel.h" #include "neochatroom.h" +class QTextDocument; + class NeoChatRoom; class SyntaxHighlighter; @@ -69,24 +71,9 @@ class ChatDocumentHandler : public QObject Q_PROPERTY(ChatBarType::Type type READ type WRITE setType NOTIFY typeChanged) /** - * @brief The QQuickTextDocument that is being handled. + * @brief The QML text Item the ChatDocumentHandler is handling. */ - Q_PROPERTY(QQuickTextDocument *document READ document WRITE setDocument NOTIFY documentChanged) - - /** - * @brief The current saved cursor position. - */ - Q_PROPERTY(int cursorPosition READ cursorPosition WRITE setCursorPosition NOTIFY cursorPositionChanged) - - /** - * @brief The start position of any currently selected text. - */ - Q_PROPERTY(int selectionStart READ selectionStart WRITE setSelectionStart NOTIFY selectionStartChanged) - - /** - * @brief The end position of any currently selected text. - */ - Q_PROPERTY(int selectionEnd READ selectionEnd WRITE setSelectionEnd NOTIFY selectionEndChanged) + Q_PROPERTY(QQuickItem *textItem READ textItem WRITE setTextItem NOTIFY textItemChanged) /** * @brief The current CompletionModel. @@ -101,33 +88,14 @@ class ChatDocumentHandler : public QObject */ Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged) - /** - * @brief The color to highlight user mentions. - */ - Q_PROPERTY(QColor mentionColor READ mentionColor WRITE setMentionColor NOTIFY mentionColorChanged) - - /** - * @brief The color to highlight spelling errors. - */ - Q_PROPERTY(QColor errorColor READ errorColor WRITE setErrorColor NOTIFY errorColorChanged) - public: explicit ChatDocumentHandler(QObject *parent = nullptr); ChatBarType::Type type() const; void setType(ChatBarType::Type type); - [[nodiscard]] QQuickTextDocument *document() const; - void setDocument(QQuickTextDocument *document); - - [[nodiscard]] int cursorPosition() const; - void setCursorPosition(int position); - - [[nodiscard]] int selectionStart() const; - void setSelectionStart(int position); - - [[nodiscard]] int selectionEnd() const; - void setSelectionEnd(int position); + QQuickItem *textItem() const; + void setTextItem(QQuickItem *textItem); [[nodiscard]] NeoChatRoom *room() const; void setRoom(NeoChatRoom *room); @@ -138,41 +106,27 @@ public: CompletionModel *completionModel() const; - [[nodiscard]] QColor mentionColor() const; - void setMentionColor(const QColor &color); - - [[nodiscard]] QColor errorColor() const; - void setErrorColor(const QColor &color); - /** * @brief Update the mentions in @p document when editing a message. */ - Q_INVOKABLE void updateMentions(QQuickTextDocument *document, const QString &editId); + Q_INVOKABLE void updateMentions(const QString &editId); Q_SIGNALS: void typeChanged(); - void documentChanged(); - void cursorPositionChanged(); + void textItemChanged(); void roomChanged(); - void selectionStartChanged(); - void selectionEndChanged(); - void errorColorChanged(); - void mentionColorChanged(); private: - int completionStartIndex() const; - ChatBarType::Type m_type = ChatBarType::None; - QPointer m_document; + QPointer m_textItem; + QTextDocument *document() const; + + void updateCompletion() const; + int completionStartIndex() const; QPointer m_room; - QColor m_mentionColor; - QColor m_errorColor; - - int m_cursorPosition; - int m_selectionStart; - int m_selectionEnd; + int cursorPosition() const; QString getText() const; void pushMention(const Mention mention) const; diff --git a/src/messagecontent/ChatBarComponent.qml b/src/messagecontent/ChatBarComponent.qml index bbaaf2cff..df12fe9f4 100644 --- a/src/messagecontent/ChatBarComponent.qml +++ b/src/messagecontent/ChatBarComponent.qml @@ -125,13 +125,8 @@ QQC2.Control { ChatDocumentHandler { id: documentHandler type: root.chatBarCache.isEditing ? ChatBarType.Edit : ChatBarType.Thread - document: textArea.textDocument - cursorPosition: textArea.cursorPosition - selectionStart: textArea.selectionStart - selectionEnd: textArea.selectionEnd + textItem: textArea room: root.Message.room - mentionColor: Kirigami.Theme.linkColor - errorColor: Kirigami.Theme.negativeTextColor } TextMetrics { @@ -264,7 +259,7 @@ QQC2.Control { documentHandler.document; if (chatBarCache?.isEditing && chatBarCache.relationMessage.length > 0) { textArea.text = chatBarCache.relationMessage; - documentHandler.updateMentions(textArea.textDocument, chatBarCache.editId); + documentHandler.updateMentions(chatBarCache.editId); textArea.forceActiveFocus(); textArea.cursorPosition = textArea.text.length; }