// SPDX-FileCopyrightText: 2023 James Graham // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL #include "threadmodel.h" #include #include #include #include #include "chatbarcache.h" #include "eventhandler.h" #include "messagecomponenttype.h" #include "neochatroom.h" ThreadModel::ThreadModel(const QString &threadRootId, NeoChatRoom *room) : QConcatenateTablesProxyModel(room) , m_threadRootId(threadRootId) , m_threadChatBarModel(new ThreadChatBarModel(this, room)) { Q_ASSERT(!m_threadRootId.isEmpty()); Q_ASSERT(room); m_threadRootContentModel = std::unique_ptr(new MessageContentModel(room, threadRootId)); connect(room, &Quotient::Room::pendingEventAboutToAdd, this, [this](Quotient::RoomEvent *event) { if (auto roomEvent = eventCast(event)) { if (roomEvent->isThreaded() && roomEvent->threadRootEventId() == m_threadRootId) { addNewEvent(event); addModels(); } } }); connect(room, &Quotient::Room::aboutToAddNewMessages, this, [this](Quotient::RoomEventsRange events) { for (const auto &event : events) { if (auto roomEvent = eventCast(event)) { if (roomEvent->isThreaded() && roomEvent->threadRootEventId() == m_threadRootId) { addNewEvent(roomEvent); } } } addModels(); }); fetchMore({}); addModels(); } QString ThreadModel::threadRootId() const { return m_threadRootId; } MessageContentModel *ThreadModel::threadRootContentModel() const { return m_threadRootContentModel.get(); } QHash ThreadModel::roleNames() const { return m_threadRootContentModel->roleNames(); } bool ThreadModel::canFetchMore(const QModelIndex &parent) const { Q_UNUSED(parent); return !m_currentJob && m_nextBatch.has_value(); } void ThreadModel::fetchMore(const QModelIndex &parent) { Q_UNUSED(parent); if (!m_currentJob && m_nextBatch.has_value()) { const auto room = dynamic_cast(QObject::parent()); const auto connection = room->connection(); m_currentJob = connection->callApi(room->id(), m_threadRootId, QLatin1String("m.thread"), *m_nextBatch, QString(), 5); connect(m_currentJob, &Quotient::BaseJob::success, this, [this]() { const auto room = dynamic_cast(QObject::parent()); auto newEvents = m_currentJob->chunk(); for (auto &event : newEvents) { m_contentModels.push_back(new MessageContentModel(room, event->id())); } addModels(); const auto newNextBatch = m_currentJob->nextBatch(); if (!newNextBatch.isEmpty() && *m_nextBatch != newNextBatch) { *m_nextBatch = newNextBatch; } else { // Insert the thread root at the end. beginInsertRows({}, rowCount(), rowCount()); endInsertRows(); m_nextBatch.reset(); } m_currentJob.clear(); }); } } void ThreadModel::addNewEvent(const Quotient::RoomEvent *event) { const auto room = dynamic_cast(QObject::parent()); m_contentModels.push_front(new MessageContentModel(room, event->id())); } void ThreadModel::addModels() { if (!sourceModels().isEmpty()) { clearModels(); } addSourceModel(m_threadRootContentModel.get()); for (auto it = m_contentModels.crbegin(); it != m_contentModels.crend(); ++it) { addSourceModel(*it); } addSourceModel(m_threadChatBarModel); beginResetModel(); endResetModel(); } void ThreadModel::clearModels() { removeSourceModel(m_threadRootContentModel.get()); for (const auto &model : m_contentModels) { if (sourceModels().contains(model)) { removeSourceModel(model); } } removeSourceModel(m_threadChatBarModel); } ThreadChatBarModel::ThreadChatBarModel(QObject *parent, NeoChatRoom *room) : QAbstractListModel(parent) , m_room(room) { if (m_room != nullptr) { connect(m_room->threadCache(), &ChatBarCache::threadIdChanged, this, [this](const QString &oldThreadId, const QString &newThreadId) { const auto threadModel = dynamic_cast(this->parent()); if (threadModel != nullptr && (oldThreadId == threadModel->threadRootId() || newThreadId == threadModel->threadRootId())) { beginResetModel(); endResetModel(); } }); } } QVariant ThreadChatBarModel::data(const QModelIndex &idx, int role) const { if (idx.row() > 1) { return {}; } if (role == ComponentTypeRole) { return MessageComponentType::ChatBar; } if (role == ChatBarCacheRole) { if (m_room == nullptr) { return {}; } return QVariant::fromValue(m_room->threadCache()); } return {}; } int ThreadChatBarModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent) if (m_room == nullptr) { return 0; } const auto threadModel = dynamic_cast(this->parent()); if (threadModel != nullptr) { return m_room->threadCache()->threadId() == threadModel->threadRootId() ? 1 : 0; } return 0; } QHash ThreadChatBarModel::roleNames() const { return { {ComponentTypeRole, "componentType"}, {ChatBarCacheRole, "chatBarCache"}, }; } #include "moc_threadmodel.cpp"