diff --git a/src/app/roommanager.cpp b/src/app/roommanager.cpp index 4552d966e..cbd9bffa9 100644 --- a/src/app/roommanager.cpp +++ b/src/app/roommanager.cpp @@ -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) { diff --git a/src/app/roommanager.h b/src/app/roommanager.h index 84d8f63ca..f9354f7d3 100644 --- a/src/app/roommanager.h +++ b/src/app/roommanager.h @@ -11,7 +11,6 @@ #include #include -#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 m_chatDocumentHandler; RoomListModel *m_roomListModel; SortFilterRoomListModel *m_sortFilterRoomListModel; diff --git a/src/chatbar/ChatBar.qml b/src/chatbar/ChatBar.qml index 8da5d8668..8503ac324 100644 --- a/src/chatbar/ChatBar.qml +++ b/src/chatbar/ChatBar.qml @@ -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 { diff --git a/src/libneochat/CMakeLists.txt b/src/libneochat/CMakeLists.txt index a3a0229a0..b8f76a3f3 100644 --- a/src/libneochat/CMakeLists.txt +++ b/src/libneochat/CMakeLists.txt @@ -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 diff --git a/src/libneochat/chatdocumenthandler.cpp b/src/libneochat/chatdocumenthandler.cpp index 4cd354f11..5b0162d9c 100644 --- a/src/libneochat/chatdocumenthandler.cpp +++ b/src/libneochat/chatdocumenthandler.cpp @@ -14,6 +14,7 @@ #include #include +#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 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(([\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)); diff --git a/src/libneochat/chatdocumenthandler.h b/src/libneochat/chatdocumenthandler.h index 43c661787..62ccfc476 100644 --- a/src/libneochat/chatdocumenthandler.h +++ b/src/libneochat/chatdocumenthandler.h @@ -9,6 +9,7 @@ #include #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 m_document; QPointer m_room; - QPointer m_chatBarCache; QColor m_mentionColor; QColor m_errorColor; diff --git a/src/libneochat/enums/chatbartype.h b/src/libneochat/enums/chatbartype.h new file mode 100644 index 000000000..622cc0d49 --- /dev/null +++ b/src/libneochat/enums/chatbartype.h @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2025 James Graham +// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + +#pragma once + +#include +#include + +/** + * @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); +}; diff --git a/src/libneochat/neochatroom.cpp b/src/libneochat/neochatroom.cpp index a188a4c59..16bcec835 100644 --- a/src/libneochat/neochatroom.cpp +++ b/src/libneochat/neochatroom.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-only #include "neochatroom.h" +#include "chatbartype.h" #include #include @@ -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(); diff --git a/src/libneochat/neochatroom.h b/src/libneochat/neochatroom.h index d385e7ec1..f2d6f0149 100644 --- a/src/libneochat/neochatroom.h +++ b/src/libneochat/neochatroom.h @@ -12,6 +12,7 @@ #include +#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. * diff --git a/src/messagecontent/ChatBarComponent.qml b/src/messagecontent/ChatBarComponent.qml index 3529d82c4..bbaaf2cff 100644 --- a/src/messagecontent/ChatBarComponent.qml +++ b/src/messagecontent/ChatBarComponent.qml @@ -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 }