ChatCache

Move the functionality to cache the contents of a chat bar from the room directly and to a new ChatCache object. This works pretty much the same with a few extra check and balances, this also made it easy to put a test suite around the functionality so I did. The current functionality should be identical to what exists.

This is in prep for threads which will require managing even more caches if we create one per thread.
This commit is contained in:
James Graham
2023-10-15 12:55:56 +00:00
parent f5417a6227
commit a57744891a
22 changed files with 775 additions and 410 deletions

View File

@@ -41,3 +41,7 @@ License: CC0-1.0
Files: .flatpak-manifest.json
Copyright: 2020-2022 Tobias Fella <tobias.fella@kde.org>
License: BSD-2-Clause
Files: autotests/data/*
Copyright: none
License: CC0-1.0

View File

@@ -3,6 +3,8 @@
enable_testing()
add_definitions(-DDATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data" )
ecm_add_test(
neochatroomtest.cpp
LINK_LIBRARIES neochat Qt::Test
@@ -32,3 +34,9 @@ ecm_add_test(
LINK_LIBRARIES neochat Qt::Test
TEST_NAME eventhandlertest
)
ecm_add_test(
chatbarcachetest.cpp
LINK_LIBRARIES neochat Qt::Test
TEST_NAME chatbarcachetest
)

View File

@@ -0,0 +1,157 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include <QJsonDocument>
#include <QJsonObject>
#include <QObject>
#include <QTest>
#include <Quotient/syncdata.h>
#include <qtestcase.h>
#include "chatbarcache.h"
#include "neochatroom.h"
using namespace Quotient;
class TestRoom : public NeoChatRoom
{
public:
using NeoChatRoom::NeoChatRoom;
void update(SyncRoomData &&data, bool fromCache = false)
{
Room::updateData(std::move(data), fromCache);
}
};
class ChatBarCacheTest : public QObject
{
Q_OBJECT
private:
Connection *connection = nullptr;
TestRoom *room = nullptr;
private Q_SLOTS:
void initTestCase();
void empty();
void noRoom();
void badParent();
void reply();
void edit();
void attachment();
};
void ChatBarCacheTest::initTestCase()
{
connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
room = new TestRoom(connection, QStringLiteral("#myroom:kde.org"), JoinState::Join);
QFile testMinSyncFile;
testMinSyncFile.setFileName(QLatin1String(DATA_DIR) + u'/' + QLatin1String("test-min-sync.json"));
testMinSyncFile.open(QIODevice::ReadOnly);
const auto testMinSyncJson = QJsonDocument::fromJson(testMinSyncFile.readAll());
SyncRoomData roomData(QStringLiteral("@bob:kde.org"), JoinState::Join, testMinSyncJson.object());
room->update(std::move(roomData));
}
void ChatBarCacheTest::empty()
{
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room));
QCOMPARE(chatBarCache->text(), QString());
QCOMPARE(chatBarCache->isReplying(), false);
QCOMPARE(chatBarCache->replyId(), QString());
QCOMPARE(chatBarCache->isEditing(), false);
QCOMPARE(chatBarCache->editId(), QString());
QCOMPARE(chatBarCache->relationUser(), room->getUser(nullptr));
QCOMPARE(chatBarCache->relationMessage(), QString());
QCOMPARE(chatBarCache->attachmentPath(), QString());
}
void ChatBarCacheTest::noRoom()
{
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache());
chatBarCache->setReplyId(QLatin1String("$153456789:example.org"));
// These should return empty even though a reply ID has been set because the
// ChatBarCache has no parent.
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.");
QCOMPARE(chatBarCache->relationUser(), QVariantMap());
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.");
QCOMPARE(chatBarCache->relationMessage(), QString());
}
void ChatBarCacheTest::badParent()
{
QScopedPointer<QObject> badParent(new QObject());
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(badParent.get()));
chatBarCache->setReplyId(QLatin1String("$153456789:example.org"));
// These should return empty even though a reply ID has been set because the
// ChatBarCache has no parent.
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.");
QCOMPARE(chatBarCache->relationUser(), QVariantMap());
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.");
QCOMPARE(chatBarCache->relationMessage(), QString());
}
void ChatBarCacheTest::reply()
{
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room));
chatBarCache->setText(QLatin1String("some text"));
chatBarCache->setAttachmentPath(QLatin1String("some/path"));
chatBarCache->setReplyId(QLatin1String("$153456789:example.org"));
QCOMPARE(chatBarCache->text(), QLatin1String("some text"));
QCOMPARE(chatBarCache->isReplying(), true);
QCOMPARE(chatBarCache->replyId(), QLatin1String("$153456789:example.org"));
QCOMPARE(chatBarCache->isEditing(), false);
QCOMPARE(chatBarCache->editId(), QString());
QCOMPARE(chatBarCache->relationUser(), room->getUser(room->user(QLatin1String("@example:example.org"))));
QCOMPARE(chatBarCache->relationMessage(), QLatin1String("This is an example\ntext message"));
QCOMPARE(chatBarCache->attachmentPath(), QString());
}
void ChatBarCacheTest::edit()
{
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room));
chatBarCache->setText(QLatin1String("some text"));
chatBarCache->setAttachmentPath(QLatin1String("some/path"));
chatBarCache->setEditId(QLatin1String("$153456789:example.org"));
QCOMPARE(chatBarCache->text(), QLatin1String("some text"));
QCOMPARE(chatBarCache->isReplying(), false);
QCOMPARE(chatBarCache->replyId(), QString());
QCOMPARE(chatBarCache->isEditing(), true);
QCOMPARE(chatBarCache->editId(), QLatin1String("$153456789:example.org"));
QCOMPARE(chatBarCache->relationUser(), room->getUser(room->user(QLatin1String("@example:example.org"))));
QCOMPARE(chatBarCache->relationMessage(), QLatin1String("This is an example\ntext message"));
QCOMPARE(chatBarCache->attachmentPath(), QString());
}
void ChatBarCacheTest::attachment()
{
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room));
chatBarCache->setText(QLatin1String("some text"));
chatBarCache->setEditId(QLatin1String("$153456789:example.org"));
chatBarCache->setAttachmentPath(QLatin1String("some/path"));
QCOMPARE(chatBarCache->text(), QLatin1String("some text"));
QCOMPARE(chatBarCache->isReplying(), false);
QCOMPARE(chatBarCache->replyId(), QString());
QCOMPARE(chatBarCache->isEditing(), false);
QCOMPARE(chatBarCache->editId(), QString());
QCOMPARE(chatBarCache->relationUser(), room->getUser(nullptr));
QCOMPARE(chatBarCache->relationMessage(), QString());
QCOMPARE(chatBarCache->attachmentPath(), QLatin1String("some/path"));
}
QTEST_MAIN(ChatBarCacheTest)
#include "chatbarcachetest.moc"

View File

@@ -0,0 +1,87 @@
{
"account_data": {
"events": [
{
"content": {
"tags": {
"u.work": {
"order": 0.9
}
}
},
"type": "m.tag"
},
{
"content": {
"custom_config_key": "custom_config_value"
},
"type": "org.example.custom.room.config"
}
]
},
"ephemeral": {
"events": [
{
"content": {
"user_ids": [
"@alice:matrix.org",
"@bob:example.com"
]
},
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"type": "m.typing"
}
]
},
"state": {
"events": [
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Alice Margatroid",
"membership": "join",
"reason": "Looking for support"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"state_key": "@alice:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
}
]
},
"summary": {
"m.heroes": [
"@alice:example.com",
"@bob:example.com"
],
"m.invited_member_count": 0,
"m.joined_member_count": 2
},
"timeline": {
"events": [
{
"content": {
"body": "This is an example\ntext message",
"format": "org.matrix.custom.html",
"formatted_body": "<b>This is an example<br>text message</b>",
"msgtype": "m.text"
},
"event_id": "$153456789:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1232
}
}
],
"limited": true,
"prev_batch": "t34-23535_0_0"
}
}

View File

@@ -135,6 +135,8 @@ add_library(neochat STATIC
enums/delegatetype.h
roomlastmessageprovider.cpp
roomlastmessageprovider.h
chatbarcache.cpp
chatbarcache.h
)
qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN

View File

@@ -38,45 +38,33 @@ void ActionsHandler::setRoom(NeoChatRoom *room)
Q_EMIT roomChanged();
}
void ActionsHandler::handleNewMessage()
void ActionsHandler::handleMessageEvent(ChatBarCache *chatBarCache)
{
checkEffects(m_room->chatBoxText());
if (!m_room->chatBoxAttachmentPath().isEmpty()) {
QUrl url(m_room->chatBoxAttachmentPath());
auto path = url.isLocalFile() ? url.toLocalFile() : url.toString();
m_room->uploadFile(QUrl(path), m_room->chatBoxText().isEmpty() ? path.mid(path.lastIndexOf(u'/') + 1) : m_room->chatBoxText());
m_room->setChatBoxAttachmentPath({});
m_room->setChatBoxText({});
if (!chatBarCache) {
return;
}
QString handledText = m_room->chatBoxText();
handledText = handleMentions(handledText);
handleMessage(m_room->chatBoxText(), handledText);
checkEffects(chatBarCache->text());
if (!chatBarCache->attachmentPath().isEmpty()) {
QUrl url(chatBarCache->attachmentPath());
auto path = url.isLocalFile() ? url.toLocalFile() : url.toString();
m_room->uploadFile(QUrl(path), chatBarCache->text().isEmpty() ? path.mid(path.lastIndexOf(u'/') + 1) : chatBarCache->text());
chatBarCache->setAttachmentPath({});
chatBarCache->setText({});
return;
}
QString handledText = chatBarCache->text();
handledText = handleMentions(handledText, chatBarCache->mentions());
handleMessage(m_room->mainCache()->text(), handledText, chatBarCache);
}
void ActionsHandler::handleEdit()
{
checkEffects(m_room->editText());
QString handledText = m_room->editText();
handledText = handleMentions(handledText, true);
handleMessage(m_room->editText(), handledText, true);
}
QString ActionsHandler::handleMentions(QString handledText, const bool &isEdit)
QString ActionsHandler::handleMentions(QString handledText, QVector<Mention> *mentions)
{
if (!m_room) {
return QString();
}
QVector<Mention> *mentions;
if (isEdit) {
mentions = m_room->editMentions();
} else {
mentions = m_room->mentions();
}
std::sort(mentions->begin(), mentions->end(), [](const auto &a, const auto &b) -> bool {
return a.cursor.anchor() > b.cursor.anchor();
});
@@ -94,7 +82,7 @@ QString ActionsHandler::handleMentions(QString handledText, const bool &isEdit)
return handledText;
}
void ActionsHandler::handleMessage(const QString &text, QString handledText, const bool &isEdit)
void ActionsHandler::handleMessage(const QString &text, QString handledText, ChatBarCache *chatBarCache)
{
if (NeoChatConfig::allowQuickEdit()) {
QRegularExpression sed(QStringLiteral("^s/([^/]*)/([^/]*)(/g)?$"));
@@ -134,7 +122,7 @@ void ActionsHandler::handleMessage(const QString &text, QString handledText, con
for (const auto &action : ActionsModel::instance().allActions()) {
if (handledText.indexOf(action.prefix) == 1
&& (handledText.indexOf(" "_ls) == action.prefix.length() + 1 || handledText.length() == action.prefix.length() + 1)) {
handledText = action.handle(handledText.mid(action.prefix.length() + 1).trimmed(), m_room);
handledText = action.handle(handledText.mid(action.prefix.length() + 1).trimmed(), m_room, chatBarCache);
if (action.messageType.has_value()) {
messageType = *action.messageType;
}
@@ -161,7 +149,7 @@ void ActionsHandler::handleMessage(const QString &text, QString handledText, con
return;
}
m_room->postMessage(text, handledText, messageType, m_room->chatBoxReplyId(), isEdit ? m_room->chatBoxEditId() : QString());
m_room->postMessage(text, handledText, messageType, chatBarCache->replyId(), chatBarCache->editId());
}
void ActionsHandler::checkEffects(const QString &text)

View File

@@ -8,6 +8,7 @@
#include <Quotient/events/roommessageevent.h>
#include "chatbarcache.h"
#include "neochatroom.h"
class NeoChatRoom;
@@ -51,21 +52,15 @@ Q_SIGNALS:
void showEffect(const QString &effect);
public Q_SLOTS:
/**
* @brief Pre-process text and send message.
* @brief Pre-process text and send message event.
*/
void handleNewMessage();
/**
* @brief Pre-process text and send edit.
*/
void handleEdit();
void handleMessageEvent(ChatBarCache *chatBarCache);
private:
NeoChatRoom *m_room = nullptr;
void checkEffects(const QString &text);
QString handleMentions(QString handledText, const bool &isEdit = false);
void handleMessage(const QString &text, QString handledText, const bool &isEdit = false);
QString handleMentions(QString handledText, QVector<Mention> *mentions);
void handleMessage(const QString &text, QString handledText, ChatBarCache *chatBarCache);
};

153
src/chatbarcache.cpp Normal file
View File

@@ -0,0 +1,153 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "chatbarcache.h"
#include "eventhandler.h"
#include "neochatroom.h"
ChatBarCache::ChatBarCache(QObject *parent)
: QObject(parent)
{
}
QString ChatBarCache::text() const
{
return m_text;
}
void ChatBarCache::setText(const QString &text)
{
if (text == m_text) {
return;
}
m_text = text;
Q_EMIT textChanged();
}
bool ChatBarCache::isReplying() const
{
return m_relationType == Reply && !m_relationId.isEmpty();
}
QString ChatBarCache::replyId() const
{
if (m_relationType != Reply) {
return {};
}
return m_relationId;
}
void ChatBarCache::setReplyId(const QString &replyId)
{
if (m_relationType == Reply && m_relationId == replyId) {
return;
}
m_relationId = replyId;
if (m_relationId.isEmpty()) {
m_relationType = None;
} else {
m_relationType = Reply;
}
m_attachmentPath = QString();
Q_EMIT relationIdChanged();
}
bool ChatBarCache::isEditing() const
{
return m_relationType == Edit && !m_relationId.isEmpty();
}
QString ChatBarCache::editId() const
{
if (m_relationType != Edit) {
return {};
}
return m_relationId;
}
void ChatBarCache::setEditId(const QString &editId)
{
if (m_relationType == Edit && m_relationId == editId) {
return;
}
m_relationId = editId;
if (m_relationId.isEmpty()) {
m_relationType = None;
} else {
m_relationType = Edit;
}
m_attachmentPath = QString();
Q_EMIT relationIdChanged();
}
QVariantMap ChatBarCache::relationUser() const
{
if (parent() == nullptr) {
qWarning() << "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.";
return {};
}
auto room = dynamic_cast<NeoChatRoom *>(parent());
if (room == nullptr) {
qWarning() << "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.";
return {};
}
if (m_relationId.isEmpty()) {
return room->getUser(nullptr);
}
return room->getUser(room->user((*room->findInTimeline(m_relationId))->senderId()));
}
QString ChatBarCache::relationMessage() const
{
if (parent() == nullptr) {
qWarning() << "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.";
return {};
}
if (m_relationId.isEmpty()) {
return {};
}
auto room = dynamic_cast<NeoChatRoom *>(parent());
if (room == nullptr) {
qWarning() << "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.";
return {};
}
EventHandler eventhandler;
eventhandler.setRoom(room);
if (auto event = room->findInTimeline(m_relationId); event != room->historyEdge()) {
eventhandler.setEvent(&**event);
return eventhandler.getPlainBody();
}
return {};
}
QString ChatBarCache::attachmentPath() const
{
return m_attachmentPath;
}
void ChatBarCache::setAttachmentPath(const QString &attachmentPath)
{
if (attachmentPath == m_attachmentPath) {
return;
}
m_attachmentPath = attachmentPath;
m_relationType = None;
m_relationId = QString();
Q_EMIT attachmentPathChanged();
}
QVector<Mention> *ChatBarCache::mentions()
{
return &m_mentions;
}
QString ChatBarCache::savedText() const
{
return m_savedText;
}
void ChatBarCache::setSavedText(const QString &savedText)
{
m_savedText = savedText;
}

185
src/chatbarcache.h Normal file
View File

@@ -0,0 +1,185 @@
// SPDX-FileCopyrightText: 2023 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>
#include <QTextCursor>
/**
* @brief Defines a user mention in the current chat or edit text.
*/
struct Mention {
QTextCursor cursor; /**< Contains the mention's text and position in the text. */
QString text; /**< The inserted text of the mention. */
int start = 0; /**< Start position of the mention. */
int position = 0; /**< End position of the mention. */
QString id; /**< The id the mention (used to create link when sending the message). */
};
/**
* @class ChatBarCache
*
* A class to cache data from a chat bar.
*
* A chat bar can be anything that allows users to compose or edit message, it doesn't
* necessarily have to use the ChatBar component, e.g. MessageEditComponent.
*
* This object is intended to allow the current contents of a chat bar to be cached
* between different rooms, i.e. there is an expectation that each NeoChatRoom could
* have a separate cache for each chat bar.
*
* @note The NeoChatRoom which this component is created in is expected to be set
* as it's parent. This is necessary for certain functions which need to get
* relevant room information.
*
* @sa ChatBar, MessageEditComponent, NeoChatRoom
*/
class ChatBarCache : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
/**
* @brief The text in the chat bar.
*
* Due to problems with QTextDocument, unlike the other properties here,
* text is *not* used to store the text when switching rooms.
*/
Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
/**
* @brief Whether the chat bar is currently replying to a message.
*/
Q_PROPERTY(bool isReplying READ isReplying NOTIFY relationIdChanged)
/**
* @brief The Matrix message ID of an event being replied to, if any.
*
* Will return empty if the RelationType is currently set to None or Edit.
*
* @note Replying, editing and attachments are exclusive so setting this will
* clear an edit or attachment.
*
* @sa RelationType
*/
Q_PROPERTY(QString replyId READ replyId WRITE setReplyId NOTIFY relationIdChanged)
/**
* @brief Whether the chat bar is currently editing a message.
*/
Q_PROPERTY(bool isEditing READ isEditing NOTIFY relationIdChanged)
/**
* @brief The Matrix message ID of an event being edited, if any.
*
* Will return empty if the RelationType is currently set to None or Reply.
*
* @note Replying, editing and attachments are exclusive so setting this will
* clear an reply or attachment.
*
* @sa RelationType
*/
Q_PROPERTY(QString editId READ editId WRITE setEditId NOTIFY relationIdChanged)
/**
* @brief Get the user for the message being replied to.
*
* This is different to getting a Quotient::User object
* as neither of those can provide details like the displayName or avatarMediaId
* without the room context as these can vary from room to room.
*
* Returns an empty user if not replying to a message.
*
* The user QVariantMap has the following properties:
* - isLocalUser - Whether the user is the local user.
* - id - The matrix ID of the user.
* - displayName - Display name in the context of this room.
* - avatarSource - The mxc URL for the user's avatar in the current room.
* - avatarMediaId - Avatar id in the context of this room.
* - color - Color for the user.
* - object - The Quotient::User object for the user.
*
* @sa getUser, Quotient::User
*/
Q_PROPERTY(QVariantMap relationUser READ relationUser NOTIFY relationIdChanged)
/**
* @brief The content of the related message.
*
* Will be QString() if no related message.
*/
Q_PROPERTY(QString relationMessage READ relationMessage NOTIFY relationIdChanged)
/**
* @brief The local path for a file to send, if any.
*
* @note Replying, editing and attachments are exclusive so setting this will
* clear an edit or reply.
*/
Q_PROPERTY(QString attachmentPath READ attachmentPath WRITE setAttachmentPath NOTIFY attachmentPathChanged)
public:
/**
* @brief Describes the type of relation which relationId can refer to.
*
* A chat bar can only be relating to a single message at a time making these
* exclusive.
*/
enum RelationType {
Reply, /**< The current relation is a message being replied to. */
Edit, /**< The current relation is a message being edited. */
None, /**< There is currently no relation event */
};
Q_ENUM(RelationType)
explicit ChatBarCache(QObject *parent = nullptr);
QString text() const;
void setText(const QString &text);
bool isReplying() const;
QString replyId() const;
void setReplyId(const QString &replyId);
bool isEditing() const;
QString editId() const;
void setEditId(const QString &editId);
QVariantMap relationUser() const;
QString relationMessage() const;
QString attachmentPath() const;
void setAttachmentPath(const QString &attachmentPath);
/**
* @brief Retrieve the mentions for the current chat bar text.
*/
QVector<Mention> *mentions();
/**
* @brief Get the saved chat bar text.
*/
QString savedText() const;
/**
* @brief Save the chat bar text.
*/
void setSavedText(const QString &savedText);
Q_SIGNALS:
void textChanged();
void relationIdChanged();
void attachmentPathChanged();
private:
QString m_text = QString();
QString m_relationId = QString();
RelationType m_relationType = RelationType::None;
QString m_attachmentPath = QString();
QVector<Mention> m_mentions;
QString m_savedText;
};

View File

@@ -57,11 +57,12 @@ public:
setFormat(error.first, error.second.size(), errorFormat);
}
}
auto room = dynamic_cast<ChatDocumentHandler *>(parent())->room();
auto handler = dynamic_cast<ChatDocumentHandler *>(parent());
auto room = handler->room();
if (!room) {
return;
}
auto mentions = room->mentions();
auto mentions = handler->chatBarCache()->mentions();
mentions->erase(std::remove_if(mentions->begin(),
mentions->end(),
[this](auto &mention) {
@@ -103,15 +104,10 @@ ChatDocumentHandler::ChatDocumentHandler(QObject *parent)
m_completionModel->setRoom(m_room);
static QPointer<NeoChatRoom> previousRoom = nullptr;
if (previousRoom) {
disconnect(previousRoom, &NeoChatRoom::chatBoxTextChanged, this, nullptr);
disconnect(previousRoom, &NeoChatRoom::editTextChanged, this, nullptr);
disconnect(m_chatBarCache, &ChatBarCache::textChanged, this, nullptr);
}
previousRoom = m_room;
connect(m_room, &NeoChatRoom::chatBoxTextChanged, this, [this]() {
int start = completionStartIndex();
m_completionModel->setText(getText().mid(start, cursorPosition() - start), getText().mid(start));
});
connect(m_room, &NeoChatRoom::editTextChanged, this, [this]() {
connect(m_chatBarCache, &ChatBarCache::textChanged, this, [this]() {
int start = completionStartIndex();
m_completionModel->setText(getText().mid(start, cursorPosition() - start), getText().mid(start));
});
@@ -215,6 +211,20 @@ void ChatDocumentHandler::setRoom(NeoChatRoom *room)
Q_EMIT roomChanged();
}
ChatBarCache *ChatDocumentHandler::chatBarCache() const
{
return m_chatBarCache;
}
void ChatDocumentHandler::setChatBarCache(ChatBarCache *chatBarCache)
{
if (m_chatBarCache == chatBarCache) {
return;
}
m_chatBarCache = chatBarCache;
Q_EMIT chatBarCacheChanged();
}
void ChatDocumentHandler::complete(int index)
{
if (m_completionModel->autoCompletionType() == CompletionModel::User) {
@@ -303,11 +313,7 @@ QString ChatDocumentHandler::getText() const
if (!m_room) {
return QString();
}
if (m_isEdit) {
return m_room->editText();
} else {
return m_room->chatBoxText();
}
return m_chatBarCache->text();
}
void ChatDocumentHandler::pushMention(const Mention mention) const
@@ -315,11 +321,7 @@ void ChatDocumentHandler::pushMention(const Mention mention) const
if (!m_room) {
return;
}
if (m_isEdit) {
m_room->editMentions()->push_back(mention);
} else {
m_room->mentions()->push_back(mention);
}
m_chatBarCache->mentions()->push_back(mention);
}
QColor ChatDocumentHandler::mentionColor() const

View File

@@ -8,6 +8,7 @@
#include <QQuickTextDocument>
#include <QTextCursor>
#include "chatbarcache.h"
#include "models/completionmodel.h"
#include "neochatroom.h"
@@ -102,6 +103,11 @@ 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.
*/
@@ -133,6 +139,9 @@ public:
[[nodiscard]] NeoChatRoom *room() const;
void setRoom(NeoChatRoom *room);
[[nodiscard]] ChatBarCache *chatBarCache() const;
void setChatBarCache(ChatBarCache *chatBarCache);
Q_INVOKABLE void complete(int index);
void updateCompletions();
@@ -149,6 +158,7 @@ Q_SIGNALS:
void documentChanged();
void cursorPositionChanged();
void roomChanged();
void chatBarCacheChanged();
void completionModelChanged();
void selectionStartChanged();
void selectionEndChanged();
@@ -163,6 +173,7 @@ private:
QPointer<QQuickTextDocument> m_document;
QPointer<NeoChatRoom> m_room;
QPointer<ChatBarCache> m_chatBarCache;
bool completionVisible = false;
QColor m_mentionColor;

View File

@@ -3,6 +3,7 @@
#include "actionsmodel.h"
#include "chatbarcache.h"
#include "controller.h"
#include "neochatroom.h"
#include "roommanager.h"
@@ -19,7 +20,7 @@ QStringList rainbowColors{"#ff2b00"_ls, "#ff5500"_ls, "#ff8000"_ls, "#ffaa00"_ls
"#00d4ff"_ls, "#00aaff"_ls, "#007fff"_ls, "#0055ff"_ls, "#002bff"_ls, "#0000ff"_ls, "#2a00ff"_ls, "#5500ff"_ls, "#7f00ff"_ls,
"#aa00ff"_ls, "#d400ff"_ls, "#ff00ff"_ls, "#ff00d4"_ls, "#ff00aa"_ls, "#ff0080"_ls, "#ff0055"_ls, "#ff002b"_ls, "#ff0000"_ls};
auto leaveRoomLambda = [](const QString &text, NeoChatRoom *room) {
auto leaveRoomLambda = [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
if (text.isEmpty()) {
Q_EMIT room->showMessage(NeoChatRoom::Info, i18n("Leaving this room."));
room->connection()->leaveRoom(room);
@@ -45,7 +46,7 @@ auto leaveRoomLambda = [](const QString &text, NeoChatRoom *room) {
return QString();
};
auto roomNickLambda = [](const QString &text, NeoChatRoom *room) {
auto roomNickLambda = [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
if (text.isEmpty()) {
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("No new nickname provided, no changes will happen."));
} else {
@@ -57,7 +58,7 @@ auto roomNickLambda = [](const QString &text, NeoChatRoom *room) {
QVector<ActionsModel::Action> actions{
Action{
QStringLiteral("shrug"),
[](const QString &message, NeoChatRoom *) {
[](const QString &message, NeoChatRoom *, ChatBarCache *) {
return QStringLiteral("¯\\\\_(ツ)_/¯ %1").arg(message);
},
true,
@@ -67,7 +68,7 @@ QVector<ActionsModel::Action> actions{
},
Action{
QStringLiteral("lenny"),
[](const QString &message, NeoChatRoom *) {
[](const QString &message, NeoChatRoom *, ChatBarCache *) {
return QStringLiteral("( ͡° ͜ʖ ͡°) %1").arg(message);
},
true,
@@ -77,7 +78,7 @@ QVector<ActionsModel::Action> actions{
},
Action{
QStringLiteral("tableflip"),
[](const QString &message, NeoChatRoom *) {
[](const QString &message, NeoChatRoom *, ChatBarCache *) {
return QStringLiteral("(╯°□°)╯︵ ┻━┻ %1").arg(message);
},
true,
@@ -87,7 +88,7 @@ QVector<ActionsModel::Action> actions{
},
Action{
QStringLiteral("unflip"),
[](const QString &message, NeoChatRoom *) {
[](const QString &message, NeoChatRoom *, ChatBarCache *) {
return QStringLiteral("┬──┬ ( ゜-゜ノ) %1").arg(message);
},
true,
@@ -97,7 +98,7 @@ QVector<ActionsModel::Action> actions{
},
Action{
QStringLiteral("rainbow"),
[](const QString &text, NeoChatRoom *room) {
[](const QString &text, NeoChatRoom *room, ChatBarCache *chatBarCache) {
QString rainbowText;
for (int i = 0; i < text.length(); i++) {
rainbowText += QStringLiteral("<font color='%2'>%3</font>").arg(rainbowColors[i % rainbowColors.length()], text.at(i));
@@ -106,8 +107,8 @@ QVector<ActionsModel::Action> actions{
room->postMessage(QStringLiteral("/rainbow %1").arg(text),
rainbowText,
RoomMessageEvent::MsgType::Text,
room->chatBoxReplyId(),
room->chatBoxEditId());
chatBarCache->replyId(),
chatBarCache->editId());
return QString();
},
false,
@@ -117,7 +118,7 @@ QVector<ActionsModel::Action> actions{
},
Action{
QStringLiteral("rainbowme"),
[](const QString &text, NeoChatRoom *room) {
[](const QString &text, NeoChatRoom *room, ChatBarCache *chatBarCache) {
QString rainbowText;
for (int i = 0; i < text.length(); i++) {
rainbowText += QStringLiteral("<font color='%2'>%3</font>").arg(rainbowColors[i % rainbowColors.length()], text.at(i));
@@ -126,8 +127,8 @@ QVector<ActionsModel::Action> actions{
room->postMessage(QStringLiteral("/rainbow %1").arg(text),
rainbowText,
RoomMessageEvent::MsgType::Emote,
room->chatBoxReplyId(),
room->chatBoxEditId());
chatBarCache->replyId(),
chatBarCache->editId());
return QString();
},
false,
@@ -137,7 +138,7 @@ QVector<ActionsModel::Action> actions{
},
Action{
QStringLiteral("plain"),
[](const QString &text, NeoChatRoom *room) {
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
room->postMessage(text, text.toHtmlEscaped(), RoomMessageEvent::MsgType::Text, {}, {});
return QString();
},
@@ -148,13 +149,13 @@ QVector<ActionsModel::Action> actions{
},
Action{
QStringLiteral("spoiler"),
[](const QString &text, NeoChatRoom *room) {
[](const QString &text, NeoChatRoom *room, ChatBarCache *chatBarCache) {
// Ideally, we would just return rainbowText and let that do the rest, but the colors don't survive markdownToHTML.
room->postMessage(QStringLiteral("/spoiler %1").arg(text),
QStringLiteral("<span data-mx-spoiler>%1</span>").arg(text),
RoomMessageEvent::MsgType::Text,
room->chatBoxReplyId(),
room->chatBoxEditId());
chatBarCache->replyId(),
chatBarCache->editId());
return QString();
},
false,
@@ -164,7 +165,7 @@ QVector<ActionsModel::Action> actions{
},
Action{
QStringLiteral("me"),
[](const QString &text, NeoChatRoom *) {
[](const QString &text, NeoChatRoom *, ChatBarCache *) {
return text;
},
true,
@@ -174,7 +175,7 @@ QVector<ActionsModel::Action> actions{
},
Action{
QStringLiteral("notice"),
[](const QString &text, NeoChatRoom *) {
[](const QString &text, NeoChatRoom *, ChatBarCache *) {
return text;
},
true,
@@ -184,7 +185,7 @@ QVector<ActionsModel::Action> actions{
},
Action{
QStringLiteral("invite"),
[](const QString &text, NeoChatRoom *room) {
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
static const QRegularExpression mxidRegex(
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
auto regexMatch = mxidRegex.match(text);
@@ -220,7 +221,7 @@ QVector<ActionsModel::Action> actions{
},
Action{
QStringLiteral("join"),
[](const QString &text, NeoChatRoom *room) {
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
auto regexMatch = roomRegex.match(text);
if (!regexMatch.hasMatch()) {
@@ -244,7 +245,7 @@ QVector<ActionsModel::Action> actions{
},
Action{
QStringLiteral("knock"),
[](const QString &text, NeoChatRoom *room) {
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
auto parts = text.split(QLatin1String(" "));
QString roomName = parts[0];
QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
@@ -276,7 +277,7 @@ QVector<ActionsModel::Action> actions{
},
Action{
QStringLiteral("j"),
[](const QString &text, NeoChatRoom *room) {
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
auto regexMatch = roomRegex.match(text);
if (!regexMatch.hasMatch()) {
@@ -315,7 +316,7 @@ QVector<ActionsModel::Action> actions{
},
Action{
QStringLiteral("nick"),
[](const QString &text, NeoChatRoom *room) {
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
if (text.isEmpty()) {
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("No new nickname provided, no changes will happen."));
} else {
@@ -346,7 +347,7 @@ QVector<ActionsModel::Action> actions{
},
Action{
QStringLiteral("ignore"),
[](const QString &text, NeoChatRoom *room) {
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
static const QRegularExpression mxidRegex(
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
auto regexMatch = mxidRegex.match(text);
@@ -374,7 +375,7 @@ QVector<ActionsModel::Action> actions{
},
Action{
QStringLiteral("unignore"),
[](const QString &text, NeoChatRoom *room) {
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
static const QRegularExpression mxidRegex(
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
auto regexMatch = mxidRegex.match(text);
@@ -402,9 +403,8 @@ QVector<ActionsModel::Action> actions{
},
Action{
QStringLiteral("react"),
[](const QString &text, NeoChatRoom *room) {
QString replyEventId = room->chatBoxReplyId();
if (replyEventId.isEmpty()) {
[](const QString &text, NeoChatRoom *room, ChatBarCache *chatBarCache) {
if (chatBarCache->replyId().isEmpty()) {
for (auto it = room->messageEvents().crbegin(); it != room->messageEvents().crend(); it++) {
const auto &evt = **it;
if (const auto event = eventCast<const RoomMessageEvent>(&evt)) {
@@ -413,7 +413,7 @@ QVector<ActionsModel::Action> actions{
}
}
}
room->toggleReaction(replyEventId, text);
room->toggleReaction(chatBarCache->replyId(), text);
return QString();
},
false,
@@ -423,7 +423,7 @@ QVector<ActionsModel::Action> actions{
},
Action{
QStringLiteral("ban"),
[](const QString &text, NeoChatRoom *room) {
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
auto parts = text.split(QLatin1String(" "));
static const QRegularExpression mxidRegex(
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
@@ -462,7 +462,7 @@ QVector<ActionsModel::Action> actions{
},
Action{
QStringLiteral("unban"),
[](const QString &text, NeoChatRoom *room) {
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
static const QRegularExpression mxidRegex(
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
auto regexMatch = mxidRegex.match(text);
@@ -495,7 +495,7 @@ QVector<ActionsModel::Action> actions{
},
Action{
QStringLiteral("kick"),
[](const QString &text, NeoChatRoom *room) {
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
auto parts = text.split(QLatin1String(" "));
static const QRegularExpression mxidRegex(
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));

View File

@@ -7,6 +7,7 @@
#include <QAbstractListModel>
#include <Quotient/events/roommessageevent.h>
class ChatBarCache;
class NeoChatRoom;
/**
@@ -28,7 +29,7 @@ public:
/**
* @brief The function to execute when the action is triggered.
*/
std::function<QString(const QString &, NeoChatRoom *)> handle;
std::function<QString(const QString &, NeoChatRoom *, ChatBarCache *)> handle;
/**
* @brief Whether the action is a message type action.
*

View File

@@ -39,6 +39,7 @@
#include <Quotient/jobs/downloadfilejob.h>
#include <Quotient/qt_connection_util.h>
#include "chatbarcache.h"
#include "clipboard.h"
#include "controller.h"
#include "eventhandler.h"
@@ -65,6 +66,9 @@ using namespace Quotient;
NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinState)
: Room(connection, std::move(roomId), joinState)
{
m_mainCache = new ChatBarCache(this);
m_editCache = new ChatBarCache(this);
connect(connection, &Connection::accountDataChanged, this, &NeoChatRoom::updatePushNotificationState);
connect(this, &Room::fileTransferCompleted, this, [this] {
setFileUploadingProgress(0);
@@ -1571,128 +1575,14 @@ void NeoChatRoom::copyEventMedia(const QString &eventId)
}
}
QString NeoChatRoom::chatBoxText() const
ChatBarCache *NeoChatRoom::mainCache() const
{
return m_chatBoxText;
return m_mainCache;
}
void NeoChatRoom::setChatBoxText(const QString &text)
ChatBarCache *NeoChatRoom::editCache() const
{
m_chatBoxText = text;
Q_EMIT chatBoxTextChanged();
}
QString NeoChatRoom::editText() const
{
return m_editText;
}
void NeoChatRoom::setEditText(const QString &text)
{
m_editText = text;
Q_EMIT editTextChanged();
}
QString NeoChatRoom::chatBoxReplyId() const
{
return m_chatBoxReplyId;
}
void NeoChatRoom::setChatBoxReplyId(const QString &replyId)
{
if (replyId == m_chatBoxReplyId) {
return;
}
m_chatBoxReplyId = replyId;
Q_EMIT chatBoxReplyIdChanged();
}
QString NeoChatRoom::chatBoxEditId() const
{
return m_chatBoxEditId;
}
void NeoChatRoom::setChatBoxEditId(const QString &editId)
{
if (editId == m_chatBoxEditId) {
return;
}
m_chatBoxEditId = editId;
Q_EMIT chatBoxEditIdChanged();
}
QVariantMap NeoChatRoom::chatBoxReplyUser() const
{
if (m_chatBoxReplyId.isEmpty()) {
return emptyUser;
}
return getUser(user((*findInTimeline(m_chatBoxReplyId))->senderId()));
}
QString NeoChatRoom::chatBoxReplyMessage() const
{
if (m_chatBoxReplyId.isEmpty()) {
return {};
}
EventHandler eventhandler;
eventhandler.setRoom(this);
eventhandler.setEvent(&**findInTimeline(m_chatBoxReplyId));
return eventhandler.getPlainBody();
}
QVariantMap NeoChatRoom::chatBoxEditUser() const
{
if (m_chatBoxEditId.isEmpty()) {
return emptyUser;
}
return getUser(user((*findInTimeline(m_chatBoxEditId))->senderId()));
}
QString NeoChatRoom::chatBoxEditMessage() const
{
if (m_chatBoxEditId.isEmpty()) {
return {};
}
EventHandler eventhandler;
eventhandler.setRoom(this);
if (auto event = findInTimeline(m_chatBoxEditId); event != historyEdge()) {
eventhandler.setEvent(&**event);
return eventhandler.getPlainBody();
}
return {};
}
QString NeoChatRoom::chatBoxAttachmentPath() const
{
return m_chatBoxAttachmentPath;
}
void NeoChatRoom::setChatBoxAttachmentPath(const QString &attachmentPath)
{
m_chatBoxAttachmentPath = attachmentPath;
Q_EMIT chatBoxAttachmentPathChanged();
}
QVector<Mention> *NeoChatRoom::mentions()
{
return &m_mentions;
}
QVector<Mention> *NeoChatRoom::editMentions()
{
return &m_editMentions;
}
QString NeoChatRoom::savedText() const
{
return m_savedText;
}
void NeoChatRoom::setSavedText(const QString &savedText)
{
m_savedText = savedText;
return m_editCache;
}
void NeoChatRoom::replyLastMessage()
@@ -1721,7 +1611,7 @@ void NeoChatRoom::replyLastMessage()
// For any message that isn't an edit return the id of the current message
eventId = (*it)->id();
}
setChatBoxReplyId(eventId);
mainCache()->setReplyId(eventId);
return;
}
}
@@ -1755,7 +1645,7 @@ void NeoChatRoom::editLastMessage()
// For any message that isn't an edit return the id of the current message
eventId = (*it)->id();
}
setChatBoxEditId(eventId);
editCache()->setEditId(eventId);
return;
}
}

View File

@@ -20,6 +20,8 @@ namespace Quotient
class User;
}
class ChatBarCache;
class PushNotificationState : public QObject
{
Q_OBJECT
@@ -40,17 +42,6 @@ public:
Q_ENUM(State)
};
/**
* @brief Defines a user mention in the current chat or edit text.
*/
struct Mention {
QTextCursor cursor; /**< Contains the mention's text and position in the text. */
QString text; /**< The inserted text of the mention. */
int start = 0; /**< Start position of the mention. */
int position = 0; /**< End position of the mention. */
QString id; /**< The id the mention (used to create link when sending the message). */
};
/**
* @class NeoChatRoom
*
@@ -311,94 +302,14 @@ class NeoChatRoom : public Quotient::Room
Q_PROPERTY(int spaceParentPowerLevel READ spaceParentPowerLevel WRITE setSpaceParentPowerLevel NOTIFY spaceParentPowerLevelChanged)
/**
* @brief The current text in the chatbox for the room.
*
* Due to problems with QTextDocument, unlike the other properties here,
* chatBoxText is *not* used to store the text when switching rooms.
* @brief The cache for the main chat bar in the room.
*/
Q_PROPERTY(QString chatBoxText READ chatBoxText WRITE setChatBoxText NOTIFY chatBoxTextChanged)
Q_PROPERTY(ChatBarCache *mainCache READ mainCache CONSTANT)
/**
* @brief The text for any message currently being edited in the room.
* @brief The cache for the edit chat bar in the room.
*/
Q_PROPERTY(QString editText READ editText WRITE setEditText NOTIFY editTextChanged)
/**
* @brief The event id of a message being replied to.
*
* Will be QString() if not replying to a message.
*/
Q_PROPERTY(QString chatBoxReplyId READ chatBoxReplyId WRITE setChatBoxReplyId NOTIFY chatBoxReplyIdChanged)
/**
* @brief The event id of a message being edited.
*
* Will be QString() if not editing to a message.
*/
Q_PROPERTY(QString chatBoxEditId READ chatBoxEditId WRITE setChatBoxEditId NOTIFY chatBoxEditIdChanged)
/**
* @brief Get the user for the message being replied to.
*
* This is different to getting a Quotient::User object
* as neither of those can provide details like the displayName or avatarMediaId
* without the room context as these can vary from room to room.
*
* Returns an empty user if not replying to a message.
*
* The user QVariantMap has the following properties:
* - isLocalUser - Whether the user is the local user.
* - id - The matrix ID of the user.
* - displayName - Display name in the context of this room.
* - avatarSource - The mxc URL for the user's avatar in the current room.
* - avatarMediaId - Avatar id in the context of this room.
* - color - Color for the user.
* - object - The Quotient::User object for the user.
*
* @sa getUser, Quotient::User
*/
Q_PROPERTY(QVariantMap chatBoxReplyUser READ chatBoxReplyUser NOTIFY chatBoxReplyIdChanged)
/**
* @brief The content of the message being replied to.
*
* Will be QString() if not replying to a message.
*/
Q_PROPERTY(QString chatBoxReplyMessage READ chatBoxReplyMessage NOTIFY chatBoxReplyIdChanged)
/**
* @brief Get the user for the message being edited.
*
* This is different to getting a Quotient::User object
* as neither of those can provide details like the displayName or avatarMediaId
* without the room context as these can vary from room to room.
*
* Returns an empty user if not replying to a message.
*
* The user QVariantMap has the following properties:
* - isLocalUser - Whether the user is the local user.
* - id - The matrix ID of the user.
* - displayName - Display name in the context of this room.
* - avatarSource - The mxc URL for the user's avatar in the current room.
* - avatarMediaId - Avatar id in the context of this room.
* - color - Color for the user.
* - object - The Quotient::User object for the user.
*
* @sa getUser, Quotient::User
*/
Q_PROPERTY(QVariantMap chatBoxEditUser READ chatBoxEditUser NOTIFY chatBoxEditIdChanged)
/**
* @brief The content of the message being edited.
*
* Will be QString() if not editing a message.
*/
Q_PROPERTY(QString chatBoxEditMessage READ chatBoxEditMessage NOTIFY chatBoxEditIdChanged)
/**
* @brief The file path of the attachment to be sent.
*/
Q_PROPERTY(QString chatBoxAttachmentPath READ chatBoxAttachmentPath WRITE setChatBoxAttachmentPath NOTIFY chatBoxAttachmentPathChanged)
Q_PROPERTY(ChatBarCache *editCache READ editCache CONSTANT)
public:
/**
@@ -805,46 +716,9 @@ public:
[[nodiscard]] int spaceParentPowerLevel() const;
void setSpaceParentPowerLevel(const int &newPowerLevel);
QString chatBoxText() const;
void setChatBoxText(const QString &text);
ChatBarCache *mainCache() const;
QString editText() const;
void setEditText(const QString &text);
QString chatBoxReplyId() const;
void setChatBoxReplyId(const QString &replyId);
QVariantMap chatBoxReplyUser() const;
QString chatBoxReplyMessage() const;
QString chatBoxEditId() const;
void setChatBoxEditId(const QString &editId);
QVariantMap chatBoxEditUser() const;
QString chatBoxEditMessage() const;
QString chatBoxAttachmentPath() const;
void setChatBoxAttachmentPath(const QString &attachmentPath);
/**
* @brief Retrieve the mentions for the current chatbox text.
*/
QVector<Mention> *mentions();
/**
* @brief Retrieve the mentions for the current edit text.
*/
QVector<Mention> *editMentions();
/**
* @brief Get the saved chatbox text for the room.
*/
QString savedText() const;
/**
* @brief Save the chatbox text for the room.
*/
void setSavedText(const QString &savedText);
ChatBarCache *editCache() const;
/**
* @brief Reply to the last message sent in the timeline.
@@ -916,14 +790,9 @@ private:
std::unique_ptr<Quotient::RoomEvent> m_cachedEvent;
QString m_chatBoxText;
QString m_editText;
QString m_chatBoxReplyId;
QString m_chatBoxEditId;
QString m_chatBoxAttachmentPath;
QVector<Mention> m_mentions;
QVector<Mention> m_editMentions;
QString m_savedText;
ChatBarCache *m_mainCache;
ChatBarCache *m_editCache;
QCache<QString, PollHandler> m_polls;
std::vector<Quotient::event_ptr_tt<Quotient::RoomEvent>> m_extraEvents;
@@ -946,11 +815,6 @@ Q_SIGNALS:
void displayNameChanged();
void pushNotificationStateChanged(PushNotificationState::State state);
void showMessage(MessageType messageType, const QString &message);
void chatBoxTextChanged();
void editTextChanged();
void chatBoxReplyIdChanged();
void chatBoxEditIdChanged();
void chatBoxAttachmentPathChanged();
void canEncryptRoomChanged();
void joinRuleChanged();
void historyVisibilityChanged();

View File

@@ -39,6 +39,7 @@ QQC2.Control {
* @brief The current room that user is viewing.
*/
required property NeoChatRoom currentRoom
onCurrentRoomChanged: _private.chatBarCache = currentRoom.mainCache
/**
* @brief The QQC2.TextArea object.
@@ -62,7 +63,7 @@ QQC2.Control {
property bool isBusy: root.currentRoom && root.currentRoom.hasFileUploading
// Matrix does not allow sending attachments in replies
visible: root.currentRoom.chatBoxReplyId.length === 0 && root.currentRoom.chatBoxAttachmentPath.length === 0
visible: _private.chatBarCache.isReplying && _private.chatBarCache.attachmentPath.length === 0
icon.name: "mail-attachment"
text: i18n("Attach an image or file")
displayHint: Kirigami.DisplayHint.IconOnly
@@ -76,7 +77,7 @@ QQC2.Control {
if (!path) {
return;
}
root.currentRoom.chatBoxAttachmentPath = path;
_private.chatBarCache.attachmentPath = path;
})
fileDialog.open()
}
@@ -169,7 +170,7 @@ QQC2.Control {
leftPadding: LayoutMirroring.enabled ? actionsRow.width : Kirigami.Units.largeSpacing
rightPadding: LayoutMirroring.enabled ? Kirigami.Units.largeSpacing : actionsRow.width + x * 2 + Kirigami.Units.largeSpacing * 2
placeholderText: root.currentRoom.usesEncryption ? i18n("Send an encrypted message…") : root.currentRoom.chatBoxAttachmentPath.length > 0 ? i18n("Set an attachment caption...") : i18n("Send a message…")
placeholderText: root.currentRoom.usesEncryption ? i18n("Send an encrypted message…") : _private.chatBarCache.attachmentPath.length > 0 ? i18n("Set an attachment caption...") : i18n("Send a message…")
verticalAlignment: TextEdit.AlignVCenter
wrapMode: Text.Wrap
@@ -189,7 +190,7 @@ QQC2.Control {
root.currentRoom.sendTypingNotification(textExists)
textExists ? repeatTimer.start() : repeatTimer.stop()
}
root.currentRoom.chatBoxText = text
_private.chatBarCache.text = text
}
onCursorRectangleChanged: chatBarScrollView.ensureVisible(cursorRectangle)
onSelectedTextChanged: {
@@ -285,25 +286,25 @@ QQC2.Control {
anchors.rightMargin: root.width > chatBarSizeHelper.currentWidth ? 0 : (chatBarScrollView.QQC2.ScrollBar.vertical.visible ? Kirigami.Units.largeSpacing * 3.5 : Kirigami.Units.largeSpacing)
active: visible
visible: root.currentRoom.chatBoxReplyId.length > 0 || root.currentRoom.chatBoxAttachmentPath.length > 0
sourceComponent: root.currentRoom.chatBoxReplyId.length > 0 ? replyPane : attachmentPane
visible: _private.chatBarCache.isReplying || _private.chatBarCache.attachmentPath.length > 0
sourceComponent: _private.chatBarCache.isReplying ? replyPane : attachmentPane
}
Component {
id: replyPane
ReplyPane {
userName: root.currentRoom.chatBoxReplyUser.displayName
userColor: root.currentRoom.chatBoxReplyUser.color
userAvatar: root.currentRoom.chatBoxReplyUser.avatarSource
text: root.currentRoom.chatBoxReplyMessage
userName: _private.chatBarCache.relationUser.displayName
userColor: _private.chatBarCache.relationUser.color
userAvatar: _private.chatBarCache.relationUser.avatarSource
text: _private.chatBarCache.relationMessage
}
}
Component {
id: attachmentPane
AttachmentPane {
attachmentPath: root.currentRoom.chatBoxAttachmentPath
attachmentPath: _private.chatBarCache.attachmentPath
onAttachmentCancelled: {
root.currentRoom.chatBoxAttachmentPath = "";
_private.chatBarCache.attachmentPath = "";
root.forceActiveFocus()
}
}
@@ -349,14 +350,14 @@ QQC2.Control {
anchors.right: parent.right
anchors.rightMargin: (root.width - chatBarSizeHelper.currentWidth) / 2 + Kirigami.Units.largeSpacing + (chatBarScrollView.QQC2.ScrollBar.vertical.visible && !(root.width > chatBarSizeHelper.currentWidth) ? Kirigami.Units.largeSpacing * 2.5 : 0)
visible: root.currentRoom.chatBoxReplyId.length > 0
visible: _private.chatBarCache.isReplying
display: QQC2.AbstractButton.IconOnly
action: Kirigami.Action {
text: i18nc("@action:button", "Cancel reply")
icon.name: "dialog-close"
onTriggered: {
root.currentRoom.chatBoxReplyId = "";
root.currentRoom.chatBoxAttachmentPath = "";
_private.chatBarCache.replyId = "";
_private.chatBarCache.attachmentPath = "";
root.forceActiveFocus()
}
}
@@ -472,16 +473,16 @@ QQC2.Control {
if (localPath.length === 0) {
return false;
}
root.currentRoom.chatBoxAttachmentPath = localPath;
_private.chatBarCache.attachmentPath = localPath;
return true;
}
function postMessage() {
actionsHandler.handleNewMessage();
actionsHandler.handleMessageEvent(_private.chatBarCache);
repeatTimer.stop()
root.currentRoom.markAllMessagesAsRead();
textField.clear();
root.currentRoom.chatBoxReplyId = "";
_private.chatBarCache.replyId = "";
messageSent()
}
@@ -558,7 +559,7 @@ QQC2.Control {
if (!path) {
return;
}
root.currentRoom.chatBoxAttachmentPath = path;
_private.chatBarCache.attachmentPath = path;
})
fileDialog.open()
@@ -581,7 +582,7 @@ QQC2.Control {
if (!Clipboard.saveImage(localPath)) {
return;
}
root.currentRoom.chatBoxAttachmentPath = localPath;
_private.chatBarCache.attachmentPath = localPath;
attachDialog.close();
}
}
@@ -595,4 +596,10 @@ QQC2.Control {
parentWindow: Window.window
}
}
QtObject {
id: _private
property ChatBarCache chatBarCache
onChatBarCacheChanged: documentHandler.chatBarCache = chatBarCache
}
}

View File

@@ -59,8 +59,8 @@ MessageDelegateContextMenu {
text: i18n("Reply")
icon.name: "mail-replied-symbolic"
onTriggered: {
currentRoom.chatBoxReplyId = eventId
currentRoom.chatBoxEditId = ""
currentRoom.mainCache.replyId = eventId;
currentRoom.editCache.editId = "";
RoomManager.requestFullScreenClose()
}
},

View File

@@ -93,8 +93,8 @@ Loader {
text: i18n("Edit")
icon.name: "document-edit"
onTriggered: {
currentRoom.chatBoxEditId = eventId;
currentRoom.chatBoxReplyId = "";
currentRoom.editCache.editId = eventId;
currentRoom.mainCache.replyId = "";
}
visible: author.isLocalUser && (root.delegateType === DelegateType.Emote || root.delegateType === DelegateType.Message)
},
@@ -102,8 +102,8 @@ Loader {
text: i18n("Reply")
icon.name: "mail-replied-symbolic"
onTriggered: {
currentRoom.chatBoxReplyId = eventId;
currentRoom.chatBoxEditId = "";
currentRoom.mainCache.replyId = eventId;
currentRoom.editCache.editId = "";
}
},
Kirigami.Action {

View File

@@ -13,7 +13,10 @@ QQC2.TextArea {
id: root
required property NeoChatRoom room
onRoomChanged: room.chatBoxEditIdChanged.connect(updateEditText)
onRoomChanged: {
_private.chatBarCache = room.editCache
_private.chatBarCache.relationIdChanged.connect(_private.updateEditText)
}
property string messageId
@@ -26,7 +29,7 @@ QQC2.TextArea {
wrapMode: Text.Wrap
onTextChanged: {
room.editText = text
_private.chatBarCache.text = text
}
Keys.onEnterPressed: {
@@ -88,7 +91,7 @@ QQC2.TextArea {
text: i18nc("@action:button", "Cancel edit")
icon.name: "dialog-close"
onTriggered: {
room.chatBoxEditId = "";
_private.chatBarCache.editId = "";
}
shortcut: "Escape"
}
@@ -134,16 +137,22 @@ QQC2.TextArea {
}
function postEdit() {
actionsHandler.handleEdit();
actionsHandler.handleMessageEvent(_private.chatBarCache);
root.clear();
room.chatBoxEditId = "";
_private.chatBarCache.editId = "";
}
function updateEditText() {
if (room.chatBoxEditId == messageId && room.chatBoxEditMessage.length > 0) {
root.text = room.chatBoxEditMessage
forceActiveFocus();
root.cursorPosition = root.length;
QtObject {
id: _private
property ChatBarCache chatBarCache
onChatBarCacheChanged: documentHandler.chatBarCache = chatBarCache
function updateEditText() {
if (chatBarCache.isEditing && chatBarCache.relationMessage.length > 0) {
root.text = chatBarCache.relationMessage
root.forceActiveFocus();
root.cursorPosition = root.length;
}
}
}
}

View File

@@ -43,7 +43,7 @@ MessageDelegate {
RichLabel {
id: label
Layout.fillWidth: true
visible: currentRoom.chatBoxEditId !== root.eventId
visible: currentRoom.editCache.editId !== root.eventId
isReply: root.isReply
@@ -59,7 +59,7 @@ MessageDelegate {
Layout.fillWidth: true
Layout.minimumHeight: item ? item.minimumHeight : -1
Layout.preferredWidth: item ? item.preferredWidth : -1
visible: currentRoom.chatBoxEditId === root.eventId
visible: currentRoom.editCache.editId === root.eventId
active: visible
sourceComponent: MessageEditComponent {
room: currentRoom

View File

@@ -182,7 +182,7 @@ QQC2.ScrollView {
DropArea {
id: dropAreaFile
anchors.fill: parent
onDropped: root.currentRoom.chatBoxAttachmentPath = drop.urls[0]
onDropped: root.currentRoom.mainCache.attachmentPath = drop.urls[0]
;
enabled: !Controller.isFlatpak
}
@@ -250,12 +250,13 @@ QQC2.ScrollView {
}
}
onEditClicked: {
root.currentRoom.chatBoxEditId = delegate.eventId;
root.currentRoom.chatBoxReplyId = "";
root.currentRoom.editCache.editId = delegate.eventId;
root.currentRoom.mainCache.replyId = "";
}
onReplyClicked: {
root.currentRoom.chatBoxReplyId = delegate.eventId;
root.currentRoom.chatBoxEditId = "";
root.currentRoom.mainCache.replyId = delegate.eventId;
root.currentRoom.editCache.editId = "";
root.focusChatBox();
}
}

View File

@@ -4,6 +4,7 @@
#include "roommanager.h"
#include "chatbarcache.h"
#include "controller.h"
#include "enums/delegatetype.h"
#include "models/messageeventmodel.h"
@@ -190,15 +191,15 @@ void RoomManager::openRoomForActiveConnection()
void RoomManager::enterRoom(NeoChatRoom *room)
{
if (m_currentRoom && !m_currentRoom->chatBoxEditId().isEmpty()) {
m_currentRoom->setChatBoxEditId({});
if (m_currentRoom && !m_currentRoom->editCache()->editId().isEmpty()) {
m_currentRoom->editCache()->setEditId({});
}
if (m_currentRoom && 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->setSavedText(m_chatDocumentHandler->document()->textDocument()->toPlainText());
m_currentRoom->mainCache()->setSavedText(m_chatDocumentHandler->document()->textDocument()->toPlainText());
m_chatDocumentHandler->setRoom(room);
m_chatDocumentHandler->document()->textDocument()->setPlainText(room->savedText());
m_chatDocumentHandler->document()->textDocument()->setPlainText(room->mainCache()->savedText());
} else {
m_chatDocumentHandler->setRoom(room);
}
@@ -232,13 +233,13 @@ void RoomManager::enterSpaceHome(NeoChatRoom *spaceRoom)
return;
}
// If replacing a normal room message timeline make sure any edit is cancelled.
if (m_currentRoom && !m_currentRoom->chatBoxEditId().isEmpty()) {
m_currentRoom->setChatBoxEditId({});
if (m_currentRoom && !m_currentRoom->editCache()->editId().isEmpty()) {
m_currentRoom->editCache()->setEditId({});
}
// Save the chatbar text for the current room if any before switching
if (m_currentRoom && m_chatDocumentHandler) {
if (m_chatDocumentHandler->document()) {
m_currentRoom->setSavedText(m_chatDocumentHandler->document()->textDocument()->toPlainText());
m_currentRoom->mainCache()->setSavedText(m_chatDocumentHandler->document()->textDocument()->toPlainText());
}
}
m_lastCurrentRoom = std::exchange(m_currentRoom, spaceRoom);