Hide me underneath the space tree
This commit is contained in:
parent
b505fa42d5
commit
fe49beb68e
8 changed files with 344 additions and 31 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue