File indexing completed on 2024-05-05 05:01:26

0001 // SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
0002 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0003 
0004 #include "spacechildrenmodel.h"
0005 
0006 #include <Quotient/connection.h>
0007 #include <Quotient/jobs/basejob.h>
0008 #include <Quotient/room.h>
0009 
0010 #include "neochatconnection.h"
0011 
0012 SpaceChildrenModel::SpaceChildrenModel(QObject *parent)
0013     : QAbstractItemModel(parent)
0014 {
0015     m_rootItem = new SpaceTreeItem(nullptr);
0016 }
0017 
0018 SpaceChildrenModel::~SpaceChildrenModel()
0019 {
0020     delete m_rootItem;
0021 }
0022 
0023 NeoChatRoom *SpaceChildrenModel::space() const
0024 {
0025     return m_space;
0026 }
0027 
0028 void SpaceChildrenModel::setSpace(NeoChatRoom *space)
0029 {
0030     if (space == m_space) {
0031         return;
0032     }
0033     // disconnect the new room signal from the old connection in case it is different.
0034     if (m_space != nullptr) {
0035         disconnect(m_space->connection(), &Quotient::Connection::loadedRoomState, this, nullptr);
0036     }
0037 
0038     m_space = space;
0039     Q_EMIT spaceChanged();
0040 
0041     for (auto job : m_currentJobs) {
0042         if (job) {
0043             job->abandon();
0044         }
0045     }
0046     m_currentJobs.clear();
0047 
0048     auto connection = m_space->connection();
0049     connect(connection, &Quotient::Connection::loadedRoomState, this, [this](Quotient::Room *room) {
0050         if (m_pendingChildren.contains(room->name())) {
0051             m_pendingChildren.removeAll(room->name());
0052             refreshModel();
0053         }
0054     });
0055     connect(m_space, &Quotient::Room::changed, this, [this]() {
0056         refreshModel();
0057     });
0058 
0059     refreshModel();
0060 }
0061 
0062 bool SpaceChildrenModel::loading() const
0063 {
0064     return m_loading;
0065 }
0066 
0067 void SpaceChildrenModel::refreshModel()
0068 {
0069     beginResetModel();
0070     m_replacedRooms.clear();
0071     delete m_rootItem;
0072     m_loading = true;
0073     Q_EMIT loadingChanged();
0074     m_rootItem =
0075         new SpaceTreeItem(dynamic_cast<NeoChatConnection *>(m_space->connection()), nullptr, m_space->id(), m_space->displayName(), m_space->canonicalAlias());
0076     endResetModel();
0077     auto job = m_space->connection()->callApi<Quotient::GetSpaceHierarchyJob>(m_space->id(), Quotient::none, Quotient::none, 1);
0078     m_currentJobs.append(job);
0079     connect(job, &Quotient::BaseJob::success, this, [this, job]() {
0080         insertChildren(job->rooms());
0081     });
0082 }
0083 
0084 void SpaceChildrenModel::insertChildren(std::vector<Quotient::GetSpaceHierarchyJob::ChildRoomsChunk> children, const QModelIndex &parent)
0085 {
0086     SpaceTreeItem *parentItem = getItem(parent);
0087 
0088     if (children[0].roomId == m_space->id() || children[0].roomId == parentItem->id()) {
0089         parentItem->setChildStates(std::move(children[0].childrenState));
0090         children.erase(children.begin());
0091     }
0092 
0093     // If this is the first set of children added to the root item then we need to
0094     // set it so that we are no longer loading.
0095     if (rowCount(QModelIndex()) == 0 && !children.empty()) {
0096         m_loading = false;
0097         Q_EMIT loadingChanged();
0098     }
0099 
0100     beginInsertRows(parent, parentItem->childCount(), parentItem->childCount() + children.size() - 1);
0101     for (unsigned long i = 0; i < children.size(); ++i) {
0102         if (children[i].roomId == m_space->id() || children[i].roomId == parentItem->id()) {
0103             continue;
0104         } else {
0105             int insertRow = parentItem->childCount();
0106             if (const auto room = m_space->connection()->room(children[i].roomId)) {
0107                 const auto predecessorId = room->predecessorId();
0108                 if (!predecessorId.isEmpty()) {
0109                     m_replacedRooms += predecessorId;
0110                 }
0111                 const auto successorId = room->successorId();
0112                 if (!successorId.isEmpty()) {
0113                     m_replacedRooms += successorId;
0114                 }
0115             }
0116             if (children[i].childrenState.size() > 0) {
0117                 auto job = m_space->connection()->callApi<Quotient::GetSpaceHierarchyJob>(children[i].roomId, Quotient::none, Quotient::none, 1);
0118                 m_currentJobs.append(job);
0119                 connect(job, &Quotient::BaseJob::success, this, [this, parent, insertRow, job]() {
0120                     insertChildren(job->rooms(), index(insertRow, 0, parent));
0121                 });
0122             }
0123             parentItem->insertChild(insertRow,
0124                                     new SpaceTreeItem(dynamic_cast<NeoChatConnection *>(m_space->connection()),
0125                                                       parentItem,
0126                                                       children[i].roomId,
0127                                                       children[i].name,
0128                                                       children[i].canonicalAlias,
0129                                                       children[i].topic,
0130                                                       children[i].numJoinedMembers,
0131                                                       children[i].avatarUrl,
0132                                                       children[i].guestCanJoin,
0133                                                       children[i].worldReadable,
0134                                                       children[i].roomType == QLatin1String("m.space"),
0135                                                       std::move(children[i].childrenState)));
0136         }
0137     }
0138     endInsertRows();
0139 }
0140 
0141 SpaceTreeItem *SpaceChildrenModel::getItem(const QModelIndex &index) const
0142 {
0143     if (index.isValid()) {
0144         SpaceTreeItem *item = static_cast<SpaceTreeItem *>(index.internalPointer());
0145         if (item) {
0146             return item;
0147         }
0148     }
0149     return m_rootItem;
0150 }
0151 
0152 QVariant SpaceChildrenModel::data(const QModelIndex &index, int role) const
0153 {
0154     if (!index.isValid()) {
0155         return QVariant();
0156     }
0157 
0158     SpaceTreeItem *child = getItem(index);
0159     if (role == DisplayNameRole) {
0160         auto displayName = child->name();
0161         if (!displayName.isEmpty()) {
0162             return displayName;
0163         }
0164 
0165         displayName = child->canonicalAlias();
0166         if (!displayName.isEmpty()) {
0167             return displayName;
0168         }
0169 
0170         return child->id();
0171     }
0172     if (role == AvatarUrlRole) {
0173         return child->avatarUrl();
0174     }
0175     if (role == TopicRole) {
0176         return child->topic();
0177     }
0178     if (role == RoomIDRole) {
0179         return child->id();
0180     }
0181     if (role == AliasRole) {
0182         return child->canonicalAlias();
0183     }
0184     if (role == MemberCountRole) {
0185         return child->memberCount();
0186     }
0187     if (role == AllowGuestsRole) {
0188         return child->allowGuests();
0189     }
0190     if (role == WorldReadableRole) {
0191         return child->worldReadable();
0192     }
0193     if (role == IsJoinedRole) {
0194         return child->isJoined();
0195     }
0196     if (role == IsSpaceRole) {
0197         return child->isSpace();
0198     }
0199     if (role == IsSuggestedRole) {
0200         return child->isSuggested();
0201     }
0202     if (role == CanAddChildrenRole) {
0203         if (const auto room = static_cast<NeoChatRoom *>(m_space->connection()->room(child->id()))) {
0204             return room->canSendState(QLatin1String("m.space.child"));
0205         }
0206         return false;
0207     }
0208     if (role == ParentDisplayNameRole) {
0209         const auto parent = child->parentItem();
0210         auto displayName = parent->name();
0211         if (!displayName.isEmpty()) {
0212             return displayName;
0213         }
0214 
0215         displayName = parent->canonicalAlias();
0216         if (!displayName.isEmpty()) {
0217             return displayName;
0218         }
0219 
0220         return parent->id();
0221     }
0222     if (role == CanSetParentRole) {
0223         if (const auto room = static_cast<NeoChatRoom *>(m_space->connection()->room(child->id()))) {
0224             return room->canSendState(QLatin1String("m.space.parent"));
0225         }
0226         return false;
0227     }
0228     if (role == IsDeclaredParentRole) {
0229         if (const auto room = static_cast<NeoChatRoom *>(m_space->connection()->room(child->id()))) {
0230             return room->currentState().contains(QLatin1String("m.space.parent"), child->parentItem()->id());
0231         }
0232         return false;
0233     }
0234     if (role == CanRemove) {
0235         const auto parent = child->parentItem();
0236         if (const auto room = static_cast<NeoChatRoom *>(m_space->connection()->room(parent->id()))) {
0237             return room->canSendState(QLatin1String("m.space.child"));
0238         }
0239         return false;
0240     }
0241     if (role == ParentRoomRole) {
0242         if (const auto parentRoom = static_cast<NeoChatRoom *>(m_space->connection()->room(child->parentItem()->id()))) {
0243             return QVariant::fromValue(parentRoom);
0244         }
0245         return QVariant::fromValue(nullptr);
0246     }
0247     if (role == OrderRole) {
0248         if (child->parentItem() == nullptr) {
0249             return QString();
0250         }
0251         const auto childState = child->parentItem()->childStateContent(child);
0252         return childState[QLatin1String("order")].toString();
0253     }
0254     if (role == ChildTimestampRole) {
0255         if (child->parentItem() == nullptr) {
0256             return QString();
0257         }
0258         const auto childState = child->parentItem()->childState(child);
0259         return childState[QLatin1String("origin_server_ts")].toString();
0260     }
0261 
0262     return {};
0263 }
0264 
0265 QModelIndex SpaceChildrenModel::index(int row, int column, const QModelIndex &parent) const
0266 {
0267     if (!hasIndex(row, column, parent)) {
0268         return QModelIndex();
0269     }
0270 
0271     SpaceTreeItem *parentItem = getItem(parent);
0272     if (!parentItem) {
0273         return QModelIndex();
0274     }
0275 
0276     SpaceTreeItem *childItem = parentItem->child(row);
0277     if (childItem) {
0278         return createIndex(row, column, childItem);
0279     }
0280     return QModelIndex();
0281 }
0282 
0283 QModelIndex SpaceChildrenModel::parent(const QModelIndex &index) const
0284 {
0285     if (!index.isValid()) {
0286         return QModelIndex();
0287     }
0288 
0289     SpaceTreeItem *childItem = static_cast<SpaceTreeItem *>(index.internalPointer());
0290     SpaceTreeItem *parentItem = childItem->parentItem();
0291 
0292     if (parentItem == m_rootItem) {
0293         return QModelIndex();
0294     }
0295 
0296     return createIndex(parentItem->row(), 0, parentItem);
0297 }
0298 
0299 int SpaceChildrenModel::rowCount(const QModelIndex &parent) const
0300 {
0301     SpaceTreeItem *parentItem;
0302     if (parent.column() > 0) {
0303         return 0;
0304     }
0305 
0306     if (!parent.isValid()) {
0307         parentItem = m_rootItem;
0308     } else {
0309         parentItem = static_cast<SpaceTreeItem *>(parent.internalPointer());
0310     }
0311 
0312     return parentItem->childCount();
0313 }
0314 
0315 int SpaceChildrenModel::columnCount(const QModelIndex &parent) const
0316 {
0317     Q_UNUSED(parent);
0318     return 1;
0319 }
0320 
0321 QHash<int, QByteArray> SpaceChildrenModel::roleNames() const
0322 {
0323     QHash<int, QByteArray> roles;
0324 
0325     roles[DisplayNameRole] = "displayName";
0326     roles[AvatarUrlRole] = "avatarUrl";
0327     roles[TopicRole] = "topic";
0328     roles[RoomIDRole] = "roomId";
0329     roles[MemberCountRole] = "memberCount";
0330     roles[AllowGuestsRole] = "allowGuests";
0331     roles[WorldReadableRole] = "worldReadable";
0332     roles[IsJoinedRole] = "isJoined";
0333     roles[AliasRole] = "alias";
0334     roles[IsSpaceRole] = "isSpace";
0335     roles[IsSuggestedRole] = "isSuggested";
0336     roles[CanAddChildrenRole] = "canAddChildren";
0337     roles[ParentDisplayNameRole] = "parentDisplayName";
0338     roles[CanSetParentRole] = "canSetParent";
0339     roles[IsDeclaredParentRole] = "isDeclaredParent";
0340     roles[CanRemove] = "canRemove";
0341     roles[ParentRoomRole] = "parentRoom";
0342     roles[OrderRole] = "order";
0343     roles[ChildTimestampRole] = "childTimestamp";
0344 
0345     return roles;
0346 }
0347 
0348 bool SpaceChildrenModel::isRoomReplaced(const QString &roomId) const
0349 {
0350     return m_replacedRooms.contains(roomId);
0351 }
0352 
0353 void SpaceChildrenModel::addPendingChild(const QString &childName)
0354 {
0355     m_pendingChildren += childName;
0356 }
0357 
0358 #include "moc_spacechildrenmodel.cpp"