summary refs log tree commit diff
path: root/src/timeline/CommunitiesModel.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/timeline/CommunitiesModel.cpp')
-rw-r--r--src/timeline/CommunitiesModel.cpp234
1 files changed, 221 insertions, 13 deletions
diff --git a/src/timeline/CommunitiesModel.cpp b/src/timeline/CommunitiesModel.cpp

index 90f1532b..7b323bb9 100644 --- a/src/timeline/CommunitiesModel.cpp +++ b/src/timeline/CommunitiesModel.cpp
@@ -7,6 +7,8 @@ #include <set> #include "Cache.h" +#include "Cache_p.h" +#include "Logging.h" #include "UserSettingsPage.h" CommunitiesModel::CommunitiesModel(QObject *parent) @@ -20,12 +22,29 @@ CommunitiesModel::roleNames() const {AvatarUrl, "avatarUrl"}, {DisplayName, "displayName"}, {Tooltip, "tooltip"}, - {ChildrenHidden, "childrenHidden"}, + {Collapsed, "collapsed"}, + {Collapsible, "collapsible"}, {Hidden, "hidden"}, + {Depth, "depth"}, {Id, "id"}, }; } +bool +CommunitiesModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (role != CommunitiesModel::Collapsed) + return false; + else if (index.row() >= 2 || index.row() - 2 < spaceOrder_.size()) { + spaceOrder_.tree.at(index.row() - 2).collapsed = value.toBool(); + + const auto cindex = spaceOrder_.lastChild(index.row() - 2); + emit dataChanged(index, this->index(cindex + 2), {Collapsed, Qt::DisplayRole}); + return true; + } else + return false; +} + QVariant CommunitiesModel::data(const QModelIndex &index, int role) const { @@ -37,10 +56,16 @@ CommunitiesModel::data(const QModelIndex &index, int role) const return tr("All rooms"); case CommunitiesModel::Roles::Tooltip: return tr("Shows all rooms without filtering."); - case CommunitiesModel::Roles::ChildrenHidden: + case CommunitiesModel::Roles::Collapsed: + return false; + case CommunitiesModel::Roles::Collapsible: return false; case CommunitiesModel::Roles::Hidden: return false; + case CommunitiesModel::Roles::Parent: + return ""; + case CommunitiesModel::Roles::Depth: + return 0; case CommunitiesModel::Roles::Id: return ""; } @@ -52,25 +77,43 @@ CommunitiesModel::data(const QModelIndex &index, int role) const return tr("Direct Chats"); case CommunitiesModel::Roles::Tooltip: return tr("Show direct chats."); - case CommunitiesModel::Roles::ChildrenHidden: + case CommunitiesModel::Roles::Collapsed: + return false; + case CommunitiesModel::Roles::Collapsible: return false; case CommunitiesModel::Roles::Hidden: return hiddentTagIds_.contains("dm"); + case CommunitiesModel::Roles::Parent: + return ""; + case CommunitiesModel::Roles::Depth: + return 0; case CommunitiesModel::Roles::Id: return "dm"; } } else if (index.row() - 2 < spaceOrder_.size()) { - auto id = spaceOrder_.at(index.row() - 2); + auto id = spaceOrder_.tree.at(index.row() - 2).name; switch (role) { case CommunitiesModel::Roles::AvatarUrl: return QString::fromStdString(spaces_.at(id).avatar_url); case CommunitiesModel::Roles::DisplayName: case CommunitiesModel::Roles::Tooltip: return QString::fromStdString(spaces_.at(id).name); - case CommunitiesModel::Roles::ChildrenHidden: - return true; + case CommunitiesModel::Roles::Collapsed: + return spaceOrder_.tree.at(index.row() - 2).collapsed; + case CommunitiesModel::Roles::Collapsible: { + auto idx = index.row() - 2; + return idx != spaceOrder_.lastChild(idx); + } case CommunitiesModel::Roles::Hidden: return hiddentTagIds_.contains("space:" + id); + case CommunitiesModel::Roles::Parent: { + if (auto p = spaceOrder_.parent(index.row() - 2); p >= 0) + return spaceOrder_.tree[p].name; + + return ""; + } + case CommunitiesModel::Roles::Depth: + return spaceOrder_.tree.at(index.row() - 2).depth; case CommunitiesModel::Roles::Id: return "space:" + id; } @@ -116,8 +159,14 @@ CommunitiesModel::data(const QModelIndex &index, int role) const switch (role) { case CommunitiesModel::Roles::Hidden: return hiddentTagIds_.contains("tag:" + tag); - case CommunitiesModel::Roles::ChildrenHidden: + case CommunitiesModel::Roles::Collapsed: return true; + case CommunitiesModel::Roles::Collapsible: + return false; + case CommunitiesModel::Roles::Parent: + return ""; + case CommunitiesModel::Roles::Depth: + return 0; case CommunitiesModel::Roles::Id: return "tag:" + tag; } @@ -125,21 +174,67 @@ CommunitiesModel::data(const QModelIndex &index, int role) const return QVariant(); } +namespace { +struct temptree +{ + std::map<std::string, temptree> children; + + void insert(const std::vector<std::string> &parents, const std::string &child) + { + temptree *t = this; + for (const auto &e : parents) + t = &t->children[e]; + t->children[child]; + } + + void flatten(CommunitiesModel::FlatTree &to, int i = 0) const + { + for (const auto &[child, subtree] : children) { + to.tree.push_back({QString::fromStdString(child), i, false}); + subtree.flatten(to, i + 1); + } + } +}; + +void +addChildren(temptree &t, + std::vector<std::string> path, + std::string child, + const std::map<std::string, std::set<std::string>> &children) +{ + if (std::find(path.begin(), path.end(), child) != path.end()) + return; + + path.push_back(child); + + if (children.count(child)) { + for (const auto &c : children.at(child)) { + t.insert(path, c); + addChildren(t, path, c, children); + } + } +} +} + void CommunitiesModel::initializeSidebar() { beginResetModel(); tags_.clear(); - spaceOrder_.clear(); + spaceOrder_.tree.clear(); spaces_.clear(); std::set<std::string> ts; - std::vector<RoomInfo> tempSpaces; + + std::set<std::string> isSpace; + std::map<std::string, std::set<std::string>> spaceChilds; + std::map<std::string, std::set<std::string>> spaceParents; + auto infos = cache::roomInfo(); for (auto it = infos.begin(); it != infos.end(); it++) { if (it.value().is_space) { - spaceOrder_.push_back(it.key()); spaces_[it.key()] = it.value(); + isSpace.insert(it.key().toStdString()); } else { for (const auto &t : it.value().tags) { if (t.find("u.") == 0 || t.find("m." == 0)) { @@ -149,6 +244,34 @@ CommunitiesModel::initializeSidebar() } } + // NOTE(Nico): We build a forrest from the Directed Cyclic(!) Graph of spaces. To do that we + // start with orphan spaces at the top. This leaves out some space circles, but there is no good + // way to break that cycle imo anyway. Then we carefully walk a tree down from each root in our + // forrest, carefully checking not to run in a circle and get lost forever. + // TODO(Nico): Optimize this. We can do this with a lot fewer allocations and checks. + for (const auto &space : isSpace) { + spaceParents[space]; + for (const auto &p : cache::client()->getParentRoomIds(space)) { + spaceParents[space].insert(p); + spaceChilds[p].insert(space); + } + } + + temptree spacetree; + std::vector<std::string> path; + for (const auto &space : isSpace) { + if (!spaceParents[space].empty()) + continue; + + spacetree.children[space] = {}; + } + for (const auto &space : spacetree.children) { + addChildren(spacetree, path, space.first, spaceChilds); + } + + // NOTE(Nico): This flattens the tree into a list, preserving the depth at each element. + spacetree.flatten(spaceOrder_); + for (const auto &t : ts) tags_.push_back(QString::fromStdString(t)); @@ -199,7 +322,7 @@ CommunitiesModel::sync(const mtx::responses::Sync &sync_) } for (const auto &[roomid, room] : sync_.rooms.leave) { (void)room; - if (spaceOrder_.contains(QString::fromStdString(roomid))) + if (spaces_.count(QString::fromStdString(roomid))) tagsUpdated = true; } for (const auto &e : sync_.account_data.events) { @@ -228,8 +351,8 @@ CommunitiesModel::setCurrentTagId(QString tagId) } } else if (tagId.startsWith("space:")) { auto tag = tagId.mid(6); - for (const auto &t : spaceOrder_) { - if (t == tag) { + for (const auto &t : spaceOrder_.tree) { + if (t.name == tag) { this->currentTagId_ = tagId; emit currentTagIdChanged(currentTagId_); return; @@ -271,3 +394,88 @@ CommunitiesModel::toggleTagId(QString tagId) emit hiddenTagsChanged(); } + +FilteredCommunitiesModel::FilteredCommunitiesModel(CommunitiesModel *model, QObject *parent) + : QSortFilterProxyModel(parent) +{ + setSourceModel(model); + setDynamicSortFilter(true); + sort(0); +} + +namespace { +enum Categories +{ + World, + Direct, + Favourites, + Server, + LowPrio, + Space, + UserTag, +}; + +Categories +tagIdToCat(QString tagId) +{ + if (tagId.isEmpty()) + return World; + else if (tagId == "dm") + return Direct; + else if (tagId == "tag:m.favourite") + return Favourites; + else if (tagId == "tag:m.server_notice") + return Server; + else if (tagId == "tag:m.lowpriority") + return LowPrio; + else if (tagId.startsWith("space:")) + return Space; + else + return UserTag; +} +} + +bool +FilteredCommunitiesModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + nhlog::ui()->debug("lessThan"); + QModelIndex const left_idx = sourceModel()->index(left.row(), 0, QModelIndex()); + QModelIndex const right_idx = sourceModel()->index(right.row(), 0, QModelIndex()); + + Categories leftCat = tagIdToCat(sourceModel()->data(left_idx, CommunitiesModel::Id).toString()); + Categories rightCat = + tagIdToCat(sourceModel()->data(right_idx, CommunitiesModel::Id).toString()); + + if (leftCat != rightCat) + return leftCat < rightCat; + + if (leftCat == Space) { + return left.row() < right.row(); + } + + QString leftName = sourceModel()->data(left_idx, CommunitiesModel::DisplayName).toString(); + QString rightName = sourceModel()->data(right_idx, CommunitiesModel::DisplayName).toString(); + + return leftName.compare(rightName, Qt::CaseInsensitive) < 0; +} +bool +FilteredCommunitiesModel::filterAcceptsRow(int sourceRow, const QModelIndex &) const +{ + CommunitiesModel *m = qobject_cast<CommunitiesModel *>(this->sourceModel()); + if (!m) + return true; + + if (sourceRow < 2 || sourceRow - 2 >= m->spaceOrder_.size()) + return true; + + auto idx = sourceRow - 2; + + while (idx >= 0 && m->spaceOrder_.tree[idx].depth > 0) { + idx = m->spaceOrder_.parent(idx); + + if (idx >= 0 && m->spaceOrder_.tree.at(idx).collapsed) + return false; + } + + return true; +}