Update ChatDocumentHandler so RoomManger is no longer required for saving text

Update `ChatDocumentHandler` so `RoomManger` is no longer required for saving the text in the chatbar between room switches. This is achieved by allowing `ChatDocumentHandler` to get the correct `ChatBarChache` itself rather than having to have it passed from `ChatBar.qml`. This avoids any race conditions.
This commit is contained in:
James Graham
2025-08-02 10:56:20 +01:00
parent 401cf29ca8
commit dc32f2f947
10 changed files with 117 additions and 84 deletions

View File

@@ -369,19 +369,6 @@ void RoomManager::visitRoom(Room *r, const QString &eventId)
if (m_currentRoom && !m_currentRoom->editCache()->editId().isEmpty()) {
m_currentRoom->editCache()->setEditId({});
}
if (m_currentRoom && !m_currentRoom->isSpace() && m_chatDocumentHandler) {
// We're doing these things here because it is critical that they are switched at the same time
if (m_chatDocumentHandler->document()) {
m_currentRoom->mainCache()->setSavedText(m_chatDocumentHandler->document()->textDocument()->toPlainText());
m_chatDocumentHandler->setRoom(room);
if (room) {
m_chatDocumentHandler->document()->textDocument()->setPlainText(room->mainCache()->savedText());
room->mainCache()->setText(room->mainCache()->savedText());
}
} else {
m_chatDocumentHandler->setRoom(room);
}
}
if (!room) {
setCurrentRoom({});
@@ -481,18 +468,6 @@ bool RoomManager::visitNonMatrix(const QUrl &url)
return true;
}
ChatDocumentHandler *RoomManager::chatDocumentHandler() const
{
return m_chatDocumentHandler;
}
void RoomManager::setChatDocumentHandler(ChatDocumentHandler *handler)
{
m_chatDocumentHandler = handler;
m_chatDocumentHandler->setRoom(m_currentRoom);
Q_EMIT chatDocumentHandlerChanged();
}
void RoomManager::setConnection(NeoChatConnection *connection)
{
if (m_connection == connection) {

View File

@@ -11,7 +11,6 @@
#include <Quotient/roommember.h>
#include <Quotient/uriresolver.h>
#include "chatdocumenthandler.h"
#include "enums/messagecomponenttype.h"
#include "enums/messagetype.h"
#include "models/mediamessagefiltermodel.h"
@@ -137,13 +136,6 @@ class RoomManager : public QObject, public UriResolverBase
*/
Q_PROPERTY(bool hasOpenRoom READ hasOpenRoom NOTIFY currentRoomChanged)
/**
* @brief The ChatDocumentHandler for the open room.
*
* @sa ChatDocumentHandler
*/
Q_PROPERTY(ChatDocumentHandler *chatDocumentHandler READ chatDocumentHandler WRITE setChatDocumentHandler NOTIFY chatDocumentHandlerChanged)
public:
virtual ~RoomManager();
static RoomManager &instance();
@@ -233,9 +225,6 @@ public:
Q_INVOKABLE void
viewEventMenu(const QString &eventId, NeoChatRoom *room, NeochatRoomMember *sender, const QString &selectedText = {}, const QString &hoveredLink = {});
ChatDocumentHandler *chatDocumentHandler() const;
void setChatDocumentHandler(ChatDocumentHandler *handler);
/**
* @brief Set a URL to be loaded as the initial room.
*/
@@ -338,8 +327,6 @@ Q_SIGNALS:
*/
void showMessage(MessageType::Type messageType, const QString &message);
void chatDocumentHandlerChanged();
void connectionChanged();
void directChatsActiveChanged();
@@ -368,7 +355,6 @@ private:
KConfigGroup m_lastRoomConfig;
KConfigGroup m_lastSpaceConfig;
KConfigGroup m_directChatsConfig;
QPointer<ChatDocumentHandler> m_chatDocumentHandler;
RoomListModel *m_roomListModel;
SortFilterRoomListModel *m_sortFilterRoomListModel;

View File

@@ -422,7 +422,6 @@ QQC2.Control {
QtObject {
id: _private
property ChatBarCache chatBarCache
onChatBarCacheChanged: documentHandler.chatBarCache = chatBarCache
function postMessage() {
_private.chatBarCache.postMessage();
@@ -484,15 +483,14 @@ QQC2.Control {
ChatDocumentHandler {
id: documentHandler
type: ChatBarType.Room
room: root.currentRoom
document: textField.textDocument
cursorPosition: textField.cursorPosition
selectionStart: textField.selectionStart
selectionEnd: textField.selectionEnd
mentionColor: Kirigami.Theme.linkColor
errorColor: Kirigami.Theme.negativeTextColor
Component.onCompleted: {
RoomManager.chatDocumentHandler = documentHandler;
}
}
Component {

View File

@@ -22,6 +22,7 @@ target_sources(LibNeoChat PRIVATE
texthandler.cpp
urlhelper.cpp
utils.cpp
enums/chatbartype.h
enums/messagecomponenttype.h
enums/messagetype.h
enums/powerlevel.cpp

View File

@@ -14,6 +14,7 @@
#include <Sonnet/BackgroundChecker>
#include <Sonnet/Settings>
#include "chatbartype.h"
#include "chatdocumenthandler_logging.h"
#include "eventhandler.h"
@@ -67,7 +68,11 @@ public:
if (!room) {
return;
}
auto mentions = handler->chatBarCache()->mentions();
const auto chatchache = handler->chatBarCache();
if (!chatchache) {
return;
}
auto mentions = chatchache->mentions();
mentions->erase(std::remove_if(mentions->begin(),
mentions->end(),
[this](auto &mention) {
@@ -105,18 +110,6 @@ ChatDocumentHandler::ChatDocumentHandler(QObject *parent)
, m_highlighter(new SyntaxHighlighter(this))
, m_completionModel(new CompletionModel(this))
{
connect(this, &ChatDocumentHandler::roomChanged, this, [this]() {
m_completionModel->setRoom(m_room);
static QPointer<NeoChatRoom> previousRoom = nullptr;
if (previousRoom) {
disconnect(m_chatBarCache, &ChatBarCache::textChanged, this, nullptr);
}
previousRoom = m_room;
connect(m_chatBarCache, &ChatBarCache::textChanged, this, [this]() {
int start = completionStartIndex();
m_completionModel->setText(getText().mid(start, cursorPosition() - start), getText().mid(start));
});
});
connect(this, &ChatDocumentHandler::documentChanged, this, [this]() {
if (!m_document) {
m_highlighter->setDocument(nullptr);
@@ -153,6 +146,20 @@ int ChatDocumentHandler::completionStartIndex() const
return start;
}
ChatBarType::Type ChatDocumentHandler::type() const
{
return m_type;
}
void ChatDocumentHandler::setType(ChatBarType::Type type)
{
if (type == m_type) {
return;
}
m_type = type;
Q_EMIT typeChanged();
}
QQuickTextDocument *ChatDocumentHandler::document() const
{
return m_document;
@@ -198,22 +205,36 @@ void ChatDocumentHandler::setRoom(NeoChatRoom *room)
return;
}
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());
}
}
m_room = room;
m_completionModel->setRoom(m_room);
if (m_room && m_type != ChatBarType::None) {
connect(m_room->cacheForType(m_type), &ChatBarCache::textChanged, this, [this]() {
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());
m_room->mainCache()->setText(room->mainCache()->savedText());
}
}
Q_EMIT roomChanged();
}
ChatBarCache *ChatDocumentHandler::chatBarCache() const
{
return m_chatBarCache;
}
void ChatDocumentHandler::setChatBarCache(ChatBarCache *chatBarCache)
{
if (m_chatBarCache == chatBarCache) {
return;
if (!m_room || m_type == ChatBarType::None) {
return nullptr;
}
m_chatBarCache = chatBarCache;
Q_EMIT chatBarCacheChanged();
return m_room->cacheForType(m_type);
}
void ChatDocumentHandler::complete(int index)
@@ -313,20 +334,20 @@ void ChatDocumentHandler::setSelectionEnd(int position)
QString ChatDocumentHandler::getText() const
{
if (!m_chatBarCache) {
qCWarning(ChatDocumentHandling) << "getText called with m_chatBarCache set to nullptr.";
if (!m_room || m_type == ChatBarType::None) {
qCWarning(ChatDocumentHandling) << "getText called with no ChatBarCache available. ChatBarType: " << m_type << " Room: " << m_room;
return {};
}
return m_chatBarCache->text();
return m_room->cacheForType(m_type)->text();
}
void ChatDocumentHandler::pushMention(const Mention mention) const
{
if (!m_chatBarCache) {
qCWarning(ChatDocumentHandling) << "pushMention called with m_chatBarCache set to nullptr.";
if (!m_room || m_type == ChatBarType::None) {
qCWarning(ChatDocumentHandling) << "pushMention called with no ChatBarCache available. ChatBarType: " << m_type << " Room: " << m_room;
return;
}
m_chatBarCache->mentions()->push_back(mention);
m_room->cacheForType(m_type)->mentions()->push_back(mention);
}
QColor ChatDocumentHandler::mentionColor() const
@@ -365,7 +386,7 @@ void ChatDocumentHandler::updateMentions(QQuickTextDocument *document, const QSt
{
setDocument(document);
if (editId.isEmpty() || !m_chatBarCache || !m_room) {
if (editId.isEmpty() || m_type == ChatBarType::None || !m_room) {
return;
}
@@ -374,7 +395,7 @@ void ChatDocumentHandler::updateMentions(QQuickTextDocument *document, const QSt
// Replaces the mentions that are baked into the HTML but plaintext in the original markdown
const QRegularExpression re(uR"lit(<a\shref="https:\/\/matrix.to\/#\/([\S]*)"\s?>([\S]*)<\/a>)lit"_s);
m_chatBarCache->mentions()->clear();
m_room->cacheForType(m_type)->mentions()->clear();
int linkSize = 0;
auto matches = re.globalMatch(EventHandler::rawMessageBody(*roomMessageEvent));

View File

@@ -9,6 +9,7 @@
#include <QTextCursor>
#include "chatbarcache.h"
#include "enums/chatbartype.h"
#include "models/completionmodel.h"
#include "neochatroom.h"
@@ -62,6 +63,11 @@ class ChatDocumentHandler : public QObject
Q_OBJECT
QML_ELEMENT
/**
* @brief The QQuickTextDocument that is being handled.
*/
Q_PROPERTY(ChatBarType::Type type READ type WRITE setType NOTIFY typeChanged)
/**
* @brief The QQuickTextDocument that is being handled.
*/
@@ -95,11 +101,6 @@ class ChatDocumentHandler : public QObject
*/
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
/**
* @brief The cache for the chat bar the text document is being handled for.
*/
Q_PROPERTY(ChatBarCache *chatBarCache READ chatBarCache WRITE setChatBarCache NOTIFY chatBarCacheChanged)
/**
* @brief The color to highlight user mentions.
*/
@@ -113,6 +114,9 @@ class ChatDocumentHandler : public QObject
public:
explicit ChatDocumentHandler(QObject *parent = nullptr);
ChatBarType::Type type() const;
void setType(ChatBarType::Type type);
[[nodiscard]] QQuickTextDocument *document() const;
void setDocument(QQuickTextDocument *document);
@@ -128,8 +132,7 @@ public:
[[nodiscard]] NeoChatRoom *room() const;
void setRoom(NeoChatRoom *room);
[[nodiscard]] ChatBarCache *chatBarCache() const;
void setChatBarCache(ChatBarCache *chatBarCache);
ChatBarCache *chatBarCache() const;
Q_INVOKABLE void complete(int index);
@@ -147,10 +150,10 @@ public:
Q_INVOKABLE void updateMentions(QQuickTextDocument *document, const QString &editId);
Q_SIGNALS:
void typeChanged();
void documentChanged();
void cursorPositionChanged();
void roomChanged();
void chatBarCacheChanged();
void selectionStartChanged();
void selectionEndChanged();
void errorColorChanged();
@@ -159,10 +162,10 @@ Q_SIGNALS:
private:
int completionStartIndex() const;
ChatBarType::Type m_type = ChatBarType::None;
QPointer<QQuickTextDocument> m_document;
QPointer<NeoChatRoom> m_room;
QPointer<ChatBarCache> m_chatBarCache;
QColor m_mentionColor;
QColor m_errorColor;

View File

@@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: 2025 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QObject>
#include <QQmlEngine>
/**
* @class ChatBarType
*
* This class is designed to define the ChatBarType enumeration.
*/
class ChatBarType : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
public:
/**
* @brief The type of chatbar.
*/
enum Type {
Room = 0, /**< A standard room chatbar for creating new messages. */
Edit, /**< A chatbar for editing an existing message. */
Thread, /**< A chatbar for creating a new threaded message. */
None, /**< Undefined. */
};
Q_ENUM(Type);
};

View File

@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only
#include "neochatroom.h"
#include "chatbartype.h"
#include <QFileInfo>
#include <QMediaMetaData>
@@ -1390,6 +1391,20 @@ ChatBarCache *NeoChatRoom::threadCache() const
return m_threadCache;
}
ChatBarCache *NeoChatRoom::cacheForType(ChatBarType::Type type) const
{
switch (type) {
case ChatBarType::Room:
return m_mainCache;
case ChatBarType::Edit:
return m_editCache;
case ChatBarType::Thread:
return m_threadCache;
default:
return nullptr;
}
}
void NeoChatRoom::replyLastMessage()
{
const auto &timelineBottom = messageEvents().rbegin();

View File

@@ -12,6 +12,7 @@
#include <QCoroTask>
#include "enums/chatbartype.h"
#include "enums/messagetype.h"
#include "enums/pushrule.h"
#include "events/pollevent.h"
@@ -484,6 +485,8 @@ public:
ChatBarCache *threadCache() const;
ChatBarCache *cacheForType(ChatBarType::Type type) const;
/**
* @brief Reply to the last message sent in the timeline.
*

View File

@@ -21,7 +21,6 @@ QQC2.Control {
* @brief The ChatBarCache to use.
*/
required property ChatBarCache chatBarCache
onChatBarCacheChanged: documentHandler.chatBarCache = chatBarCache
readonly property bool isBusy: root.Message.room && root.Message.room.hasFileUploading
@@ -125,11 +124,12 @@ 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
room: root.Message.room // We don't care about saving for edits so this is OK.
room: root.Message.room
mentionColor: Kirigami.Theme.linkColor
errorColor: Kirigami.Theme.negativeTextColor
}