Hide me underneath the space tree
This commit is contained in:
parent
b505fa42d5
commit
fe49beb68e
1
resources/icons/ui/collapsed.svg
Normal file
1
resources/icons/ui/collapsed.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M8.47 4.22a.75.75 0 0 0 0 1.06L15.19 12l-6.72 6.72a.75.75 0 1 0 1.06 1.06l7.25-7.25a.75.75 0 0 0 0-1.06L9.53 4.22a.75.75 0 0 0-1.06 0Z" fill="#212121"/></svg>
|
After Width: | Height: | Size: 262 B |
1
resources/icons/ui/expanded.svg
Normal file
1
resources/icons/ui/expanded.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M4.22 8.47a.75.75 0 0 1 1.06 0L12 15.19l6.72-6.72a.75.75 0 1 1 1.06 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L4.22 9.53a.75.75 0 0 1 0-1.06Z" fill="#212121"/></svg>
|
After Width: | Height: | Size: 262 B |
@ -11,6 +11,7 @@ import QtQuick.Layouts 1.3
|
|||||||
import im.nheko 1.0
|
import im.nheko 1.0
|
||||||
|
|
||||||
Page {
|
Page {
|
||||||
|
id: communitySidebar
|
||||||
//leftPadding: Nheko.paddingSmall
|
//leftPadding: Nheko.paddingSmall
|
||||||
//rightPadding: Nheko.paddingSmall
|
//rightPadding: Nheko.paddingSmall
|
||||||
property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 1.6)
|
property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 1.6)
|
||||||
@ -22,7 +23,7 @@ Page {
|
|||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
height: parent.height
|
height: parent.height
|
||||||
model: Communities
|
model: Communities.filtered()
|
||||||
|
|
||||||
ScrollHelper {
|
ScrollHelper {
|
||||||
flickable: parent
|
flickable: parent
|
||||||
@ -107,9 +108,31 @@ Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
id: r
|
||||||
spacing: Nheko.paddingMedium
|
spacing: Nheko.paddingMedium
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Nheko.paddingMedium
|
anchors.margins: Nheko.paddingMedium
|
||||||
|
anchors.leftMargin: communitySidebar.collapsed ? Nheko.paddingMedium : (Nheko.paddingMedium * (model.depth + 1))
|
||||||
|
|
||||||
|
ImageButton {
|
||||||
|
visible: !communitySidebar.collapsed && model.collapsible
|
||||||
|
Layout.preferredHeight: fontMetrics.lineSpacing
|
||||||
|
Layout.preferredWidth: fontMetrics.lineSpacing
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
height: fontMetrics.lineSpacing
|
||||||
|
width: fontMetrics.lineSpacing
|
||||||
|
image: model.collapsed ? ":/icons/icons/ui/collapsed.svg" : ":/icons/icons/ui/expanded.svg"
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.text: model.collapsed ? qsTr("Expand") : qsTr("Collapse")
|
||||||
|
hoverEnabled: true
|
||||||
|
|
||||||
|
onClicked: model.collapsed = !model.collapsed
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.preferredWidth: fontMetrics.lineSpacing
|
||||||
|
visible: !communitySidebar.collapsed && !model.collapsible
|
||||||
|
}
|
||||||
|
|
||||||
Avatar {
|
Avatar {
|
||||||
id: avatar
|
id: avatar
|
||||||
@ -130,10 +153,10 @@ Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ElidedLabel {
|
ElidedLabel {
|
||||||
visible: !collapsed
|
visible: !communitySidebar.collapsed
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
color: communityItem.importantText
|
color: communityItem.importantText
|
||||||
elideWidth: parent.width - avatar.width - Nheko.paddingMedium
|
elideWidth: parent.width - avatar.width - r.anchors.leftMargin - Nheko.paddingMedium - fontMetrics.lineSpacing
|
||||||
fullText: model.displayName
|
fullText: model.displayName
|
||||||
textFormat: Text.PlainText
|
textFormat: Text.PlainText
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,29 @@
|
|||||||
<RCC>
|
<RCC>
|
||||||
<qresource prefix="/icons">
|
<qresource prefix="/icons">
|
||||||
<file>icons/ui/sticky-note-solid.svg</file>
|
|
||||||
<file>icons/ui/add-square-button.svg</file>
|
<file>icons/ui/add-square-button.svg</file>
|
||||||
<file>icons/ui/send.svg</file>
|
|
||||||
<file>icons/ui/smile.svg</file>
|
|
||||||
<file>icons/ui/user-friends-solid.svg</file>
|
|
||||||
<file>icons/ui/place-call.svg</file>
|
|
||||||
<file>icons/ui/attach.svg</file>
|
|
||||||
<file>icons/ui/angle-arrow-left.svg</file>
|
<file>icons/ui/angle-arrow-left.svg</file>
|
||||||
|
<file>icons/ui/attach.svg</file>
|
||||||
|
<file>icons/ui/ban.svg</file>
|
||||||
<file>icons/ui/chat.svg</file>
|
<file>icons/ui/chat.svg</file>
|
||||||
<file>icons/ui/checkmark.svg</file>
|
<file>icons/ui/checkmark.svg</file>
|
||||||
<file>icons/ui/clock.svg</file>
|
<file>icons/ui/clock.svg</file>
|
||||||
|
<file>icons/ui/collapsed.svg</file>
|
||||||
<file>icons/ui/delete.svg</file>
|
<file>icons/ui/delete.svg</file>
|
||||||
|
<file>icons/ui/dismiss.svg</file>
|
||||||
|
<file>icons/ui/double-checkmark.svg</file>
|
||||||
|
<file>icons/ui/download.svg</file>
|
||||||
<file>icons/ui/edit.svg</file>
|
<file>icons/ui/edit.svg</file>
|
||||||
<file>icons/ui/end-call.svg</file>
|
<file>icons/ui/end-call.svg</file>
|
||||||
|
<file>icons/ui/expanded.svg</file>
|
||||||
|
<file>icons/ui/image-failed.svg</file>
|
||||||
<file>icons/ui/lowprio.svg</file>
|
<file>icons/ui/lowprio.svg</file>
|
||||||
<file>icons/ui/microphone-mute.svg</file>
|
<file>icons/ui/microphone-mute.svg</file>
|
||||||
<file>icons/ui/microphone-unmute.svg</file>
|
<file>icons/ui/microphone-unmute.svg</file>
|
||||||
|
<file>icons/ui/options.svg</file>
|
||||||
<file>icons/ui/pause-symbol.svg</file>
|
<file>icons/ui/pause-symbol.svg</file>
|
||||||
<file>icons/ui/people.svg</file>
|
<file>icons/ui/people.svg</file>
|
||||||
|
<file>icons/ui/picture-in-picture.svg</file>
|
||||||
|
<file>icons/ui/place-call.svg</file>
|
||||||
<file>icons/ui/play-sign.svg</file>
|
<file>icons/ui/play-sign.svg</file>
|
||||||
<file>icons/ui/power-off.svg</file>
|
<file>icons/ui/power-off.svg</file>
|
||||||
<file>icons/ui/refresh.svg</file>
|
<file>icons/ui/refresh.svg</file>
|
||||||
@ -26,21 +31,18 @@
|
|||||||
<file>icons/ui/round-remove-button.svg</file>
|
<file>icons/ui/round-remove-button.svg</file>
|
||||||
<file>icons/ui/screen-share.svg</file>
|
<file>icons/ui/screen-share.svg</file>
|
||||||
<file>icons/ui/search.svg</file>
|
<file>icons/ui/search.svg</file>
|
||||||
|
<file>icons/ui/send.svg</file>
|
||||||
<file>icons/ui/settings.svg</file>
|
<file>icons/ui/settings.svg</file>
|
||||||
|
<file>icons/ui/smile.svg</file>
|
||||||
<file>icons/ui/speech-bubbles.svg</file>
|
<file>icons/ui/speech-bubbles.svg</file>
|
||||||
<file>icons/ui/star.svg</file>
|
<file>icons/ui/star.svg</file>
|
||||||
|
<file>icons/ui/sticky-note-solid.svg</file>
|
||||||
<file>icons/ui/tag.svg</file>
|
<file>icons/ui/tag.svg</file>
|
||||||
|
<file>icons/ui/user-friends-solid.svg</file>
|
||||||
<file>icons/ui/video.svg</file>
|
<file>icons/ui/video.svg</file>
|
||||||
<file>icons/ui/volume-off-indicator.svg</file>
|
<file>icons/ui/volume-off-indicator.svg</file>
|
||||||
<file>icons/ui/volume-up.svg</file>
|
<file>icons/ui/volume-up.svg</file>
|
||||||
<file>icons/ui/world.svg</file>
|
<file>icons/ui/world.svg</file>
|
||||||
<file>icons/ui/picture-in-picture.svg</file>
|
|
||||||
<file>icons/ui/options.svg</file>
|
|
||||||
<file>icons/ui/double-checkmark.svg</file>
|
|
||||||
<file>icons/ui/ban.svg</file>
|
|
||||||
<file>icons/ui/image-failed.svg</file>
|
|
||||||
<file>icons/ui/dismiss.svg</file>
|
|
||||||
<file>icons/ui/download.svg</file>
|
|
||||||
<file>icons/emoji-categories/activity.svg</file>
|
<file>icons/emoji-categories/activity.svg</file>
|
||||||
<file>icons/emoji-categories/flags.svg</file>
|
<file>icons/emoji-categories/flags.svg</file>
|
||||||
<file>icons/emoji-categories/foods.svg</file>
|
<file>icons/emoji-categories/foods.svg</file>
|
||||||
|
@ -95,6 +95,12 @@ public:
|
|||||||
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
|
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
|
||||||
return getStateEvent<T>(txn, room_id, state_key);
|
return getStateEvent<T>(txn, room_id, state_key);
|
||||||
}
|
}
|
||||||
|
template<typename T>
|
||||||
|
std::vector<mtx::events::StateEvent<T>> getStateEventsWithType(const std::string &room_id)
|
||||||
|
{
|
||||||
|
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
|
||||||
|
return getStateEventsWithType<T>(txn, room_id);
|
||||||
|
}
|
||||||
|
|
||||||
//! retrieve a specific event from account data
|
//! retrieve a specific event from account data
|
||||||
//! pass empty room_id for global account data
|
//! pass empty room_id for global account data
|
||||||
|
@ -7,6 +7,8 @@
|
|||||||
#include <set>
|
#include <set>
|
||||||
|
|
||||||
#include "Cache.h"
|
#include "Cache.h"
|
||||||
|
#include "Cache_p.h"
|
||||||
|
#include "Logging.h"
|
||||||
#include "UserSettingsPage.h"
|
#include "UserSettingsPage.h"
|
||||||
|
|
||||||
CommunitiesModel::CommunitiesModel(QObject *parent)
|
CommunitiesModel::CommunitiesModel(QObject *parent)
|
||||||
@ -20,12 +22,29 @@ CommunitiesModel::roleNames() const
|
|||||||
{AvatarUrl, "avatarUrl"},
|
{AvatarUrl, "avatarUrl"},
|
||||||
{DisplayName, "displayName"},
|
{DisplayName, "displayName"},
|
||||||
{Tooltip, "tooltip"},
|
{Tooltip, "tooltip"},
|
||||||
{ChildrenHidden, "childrenHidden"},
|
{Collapsed, "collapsed"},
|
||||||
|
{Collapsible, "collapsible"},
|
||||||
{Hidden, "hidden"},
|
{Hidden, "hidden"},
|
||||||
|
{Depth, "depth"},
|
||||||
{Id, "id"},
|
{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
|
QVariant
|
||||||
CommunitiesModel::data(const QModelIndex &index, int role) const
|
CommunitiesModel::data(const QModelIndex &index, int role) const
|
||||||
{
|
{
|
||||||
@ -37,10 +56,16 @@ CommunitiesModel::data(const QModelIndex &index, int role) const
|
|||||||
return tr("All rooms");
|
return tr("All rooms");
|
||||||
case CommunitiesModel::Roles::Tooltip:
|
case CommunitiesModel::Roles::Tooltip:
|
||||||
return tr("Shows all rooms without filtering.");
|
return tr("Shows all rooms without filtering.");
|
||||||
case CommunitiesModel::Roles::ChildrenHidden:
|
case CommunitiesModel::Roles::Collapsed:
|
||||||
|
return false;
|
||||||
|
case CommunitiesModel::Roles::Collapsible:
|
||||||
return false;
|
return false;
|
||||||
case CommunitiesModel::Roles::Hidden:
|
case CommunitiesModel::Roles::Hidden:
|
||||||
return false;
|
return false;
|
||||||
|
case CommunitiesModel::Roles::Parent:
|
||||||
|
return "";
|
||||||
|
case CommunitiesModel::Roles::Depth:
|
||||||
|
return 0;
|
||||||
case CommunitiesModel::Roles::Id:
|
case CommunitiesModel::Roles::Id:
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@ -52,25 +77,43 @@ CommunitiesModel::data(const QModelIndex &index, int role) const
|
|||||||
return tr("Direct Chats");
|
return tr("Direct Chats");
|
||||||
case CommunitiesModel::Roles::Tooltip:
|
case CommunitiesModel::Roles::Tooltip:
|
||||||
return tr("Show direct chats.");
|
return tr("Show direct chats.");
|
||||||
case CommunitiesModel::Roles::ChildrenHidden:
|
case CommunitiesModel::Roles::Collapsed:
|
||||||
|
return false;
|
||||||
|
case CommunitiesModel::Roles::Collapsible:
|
||||||
return false;
|
return false;
|
||||||
case CommunitiesModel::Roles::Hidden:
|
case CommunitiesModel::Roles::Hidden:
|
||||||
return hiddentTagIds_.contains("dm");
|
return hiddentTagIds_.contains("dm");
|
||||||
|
case CommunitiesModel::Roles::Parent:
|
||||||
|
return "";
|
||||||
|
case CommunitiesModel::Roles::Depth:
|
||||||
|
return 0;
|
||||||
case CommunitiesModel::Roles::Id:
|
case CommunitiesModel::Roles::Id:
|
||||||
return "dm";
|
return "dm";
|
||||||
}
|
}
|
||||||
} else if (index.row() - 2 < spaceOrder_.size()) {
|
} 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) {
|
switch (role) {
|
||||||
case CommunitiesModel::Roles::AvatarUrl:
|
case CommunitiesModel::Roles::AvatarUrl:
|
||||||
return QString::fromStdString(spaces_.at(id).avatar_url);
|
return QString::fromStdString(spaces_.at(id).avatar_url);
|
||||||
case CommunitiesModel::Roles::DisplayName:
|
case CommunitiesModel::Roles::DisplayName:
|
||||||
case CommunitiesModel::Roles::Tooltip:
|
case CommunitiesModel::Roles::Tooltip:
|
||||||
return QString::fromStdString(spaces_.at(id).name);
|
return QString::fromStdString(spaces_.at(id).name);
|
||||||
case CommunitiesModel::Roles::ChildrenHidden:
|
case CommunitiesModel::Roles::Collapsed:
|
||||||
return true;
|
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:
|
case CommunitiesModel::Roles::Hidden:
|
||||||
return hiddentTagIds_.contains("space:" + id);
|
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:
|
case CommunitiesModel::Roles::Id:
|
||||||
return "space:" + id;
|
return "space:" + id;
|
||||||
}
|
}
|
||||||
@ -116,8 +159,14 @@ CommunitiesModel::data(const QModelIndex &index, int role) const
|
|||||||
switch (role) {
|
switch (role) {
|
||||||
case CommunitiesModel::Roles::Hidden:
|
case CommunitiesModel::Roles::Hidden:
|
||||||
return hiddentTagIds_.contains("tag:" + tag);
|
return hiddentTagIds_.contains("tag:" + tag);
|
||||||
case CommunitiesModel::Roles::ChildrenHidden:
|
case CommunitiesModel::Roles::Collapsed:
|
||||||
return true;
|
return true;
|
||||||
|
case CommunitiesModel::Roles::Collapsible:
|
||||||
|
return false;
|
||||||
|
case CommunitiesModel::Roles::Parent:
|
||||||
|
return "";
|
||||||
|
case CommunitiesModel::Roles::Depth:
|
||||||
|
return 0;
|
||||||
case CommunitiesModel::Roles::Id:
|
case CommunitiesModel::Roles::Id:
|
||||||
return "tag:" + tag;
|
return "tag:" + tag;
|
||||||
}
|
}
|
||||||
@ -125,21 +174,67 @@ CommunitiesModel::data(const QModelIndex &index, int role) const
|
|||||||
return QVariant();
|
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
|
void
|
||||||
CommunitiesModel::initializeSidebar()
|
CommunitiesModel::initializeSidebar()
|
||||||
{
|
{
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
tags_.clear();
|
tags_.clear();
|
||||||
spaceOrder_.clear();
|
spaceOrder_.tree.clear();
|
||||||
spaces_.clear();
|
spaces_.clear();
|
||||||
|
|
||||||
std::set<std::string> ts;
|
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();
|
auto infos = cache::roomInfo();
|
||||||
for (auto it = infos.begin(); it != infos.end(); it++) {
|
for (auto it = infos.begin(); it != infos.end(); it++) {
|
||||||
if (it.value().is_space) {
|
if (it.value().is_space) {
|
||||||
spaceOrder_.push_back(it.key());
|
|
||||||
spaces_[it.key()] = it.value();
|
spaces_[it.key()] = it.value();
|
||||||
|
isSpace.insert(it.key().toStdString());
|
||||||
} else {
|
} else {
|
||||||
for (const auto &t : it.value().tags) {
|
for (const auto &t : it.value().tags) {
|
||||||
if (t.find("u.") == 0 || t.find("m." == 0)) {
|
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)
|
for (const auto &t : ts)
|
||||||
tags_.push_back(QString::fromStdString(t));
|
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) {
|
for (const auto &[roomid, room] : sync_.rooms.leave) {
|
||||||
(void)room;
|
(void)room;
|
||||||
if (spaceOrder_.contains(QString::fromStdString(roomid)))
|
if (spaces_.count(QString::fromStdString(roomid)))
|
||||||
tagsUpdated = true;
|
tagsUpdated = true;
|
||||||
}
|
}
|
||||||
for (const auto &e : sync_.account_data.events) {
|
for (const auto &e : sync_.account_data.events) {
|
||||||
@ -228,8 +351,8 @@ CommunitiesModel::setCurrentTagId(QString tagId)
|
|||||||
}
|
}
|
||||||
} else if (tagId.startsWith("space:")) {
|
} else if (tagId.startsWith("space:")) {
|
||||||
auto tag = tagId.mid(6);
|
auto tag = tagId.mid(6);
|
||||||
for (const auto &t : spaceOrder_) {
|
for (const auto &t : spaceOrder_.tree) {
|
||||||
if (t == tag) {
|
if (t.name == tag) {
|
||||||
this->currentTagId_ = tagId;
|
this->currentTagId_ = tagId;
|
||||||
emit currentTagIdChanged(currentTagId_);
|
emit currentTagIdChanged(currentTagId_);
|
||||||
return;
|
return;
|
||||||
@ -271,3 +394,88 @@ CommunitiesModel::toggleTagId(QString tagId)
|
|||||||
|
|
||||||
emit hiddenTagsChanged();
|
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;
|
||||||
|
}
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
|
#include <QSortFilterProxyModel>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
|
||||||
@ -13,6 +14,18 @@
|
|||||||
|
|
||||||
#include "CacheStructs.h"
|
#include "CacheStructs.h"
|
||||||
|
|
||||||
|
class CommunitiesModel;
|
||||||
|
|
||||||
|
class FilteredCommunitiesModel : public QSortFilterProxyModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
FilteredCommunitiesModel(CommunitiesModel *model, QObject *parent = nullptr);
|
||||||
|
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
|
||||||
|
bool filterAcceptsRow(int sourceRow, const QModelIndex &) const override;
|
||||||
|
};
|
||||||
|
|
||||||
class CommunitiesModel : public QAbstractListModel
|
class CommunitiesModel : public QAbstractListModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@ -27,11 +40,59 @@ public:
|
|||||||
AvatarUrl = Qt::UserRole,
|
AvatarUrl = Qt::UserRole,
|
||||||
DisplayName,
|
DisplayName,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
ChildrenHidden,
|
Collapsed,
|
||||||
|
Collapsible,
|
||||||
Hidden,
|
Hidden,
|
||||||
|
Parent,
|
||||||
|
Depth,
|
||||||
Id,
|
Id,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct FlatTree
|
||||||
|
{
|
||||||
|
struct Elem
|
||||||
|
{
|
||||||
|
QString name;
|
||||||
|
int depth = 0;
|
||||||
|
bool collapsed = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Elem> tree;
|
||||||
|
|
||||||
|
int size() const { return static_cast<int>(tree.size()); }
|
||||||
|
int indexOf(const QString &s) const
|
||||||
|
{
|
||||||
|
for (int i = 0; i < size(); i++)
|
||||||
|
if (tree[i].name == s)
|
||||||
|
return i;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int lastChild(int index) const
|
||||||
|
{
|
||||||
|
if (index >= size() || index < 0)
|
||||||
|
return index;
|
||||||
|
const auto depth = tree[index].depth;
|
||||||
|
int i = index + 1;
|
||||||
|
for (; i < size(); i++)
|
||||||
|
if (tree[i].depth == depth)
|
||||||
|
break;
|
||||||
|
return i - 1;
|
||||||
|
}
|
||||||
|
int parent(int index) const
|
||||||
|
{
|
||||||
|
if (index >= size() || index < 0)
|
||||||
|
return -1;
|
||||||
|
const auto depth = tree[index].depth;
|
||||||
|
if (depth == 0)
|
||||||
|
return -1;
|
||||||
|
int i = index - 1;
|
||||||
|
for (; i >= 0; i--)
|
||||||
|
if (tree[i].depth < depth)
|
||||||
|
break;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
CommunitiesModel(QObject *parent = nullptr);
|
CommunitiesModel(QObject *parent = nullptr);
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override
|
||||||
@ -40,6 +101,7 @@ public:
|
|||||||
return 2 + tags_.size() + spaceOrder_.size();
|
return 2 + tags_.size() + spaceOrder_.size();
|
||||||
}
|
}
|
||||||
QVariant data(const QModelIndex &index, int role) const override;
|
QVariant data(const QModelIndex &index, int role) const override;
|
||||||
|
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void initializeSidebar();
|
void initializeSidebar();
|
||||||
@ -63,6 +125,7 @@ public slots:
|
|||||||
return tagsWD;
|
return tagsWD;
|
||||||
}
|
}
|
||||||
void toggleTagId(QString tagId);
|
void toggleTagId(QString tagId);
|
||||||
|
FilteredCommunitiesModel *filtered() { return new FilteredCommunitiesModel(this, this); }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void currentTagIdChanged(QString tagId);
|
void currentTagIdChanged(QString tagId);
|
||||||
@ -73,6 +136,8 @@ private:
|
|||||||
QStringList tags_;
|
QStringList tags_;
|
||||||
QString currentTagId_;
|
QString currentTagId_;
|
||||||
QStringList hiddentTagIds_;
|
QStringList hiddentTagIds_;
|
||||||
QStringList spaceOrder_;
|
FlatTree spaceOrder_;
|
||||||
std::map<QString, RoomInfo> spaces_;
|
std::map<QString, RoomInfo> spaces_;
|
||||||
|
|
||||||
|
friend class FilteredCommunitiesModel;
|
||||||
};
|
};
|
||||||
|
@ -260,6 +260,13 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
|
|||||||
qRegisterMetaType<mtx::events::collections::TimelineEvents>();
|
qRegisterMetaType<mtx::events::collections::TimelineEvents>();
|
||||||
qRegisterMetaType<std::vector<DeviceInfo>>();
|
qRegisterMetaType<std::vector<DeviceInfo>>();
|
||||||
|
|
||||||
|
qmlRegisterUncreatableType<FilteredCommunitiesModel>(
|
||||||
|
"im.nheko",
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
"FilteredCommunitiesModel",
|
||||||
|
"Use Communities.filtered() to create a FilteredCommunitiesModel");
|
||||||
|
|
||||||
qmlRegisterType<emoji::EmojiModel>("im.nheko.EmojiModel", 1, 0, "EmojiModel");
|
qmlRegisterType<emoji::EmojiModel>("im.nheko.EmojiModel", 1, 0, "EmojiModel");
|
||||||
qmlRegisterUncreatableType<emoji::Emoji>(
|
qmlRegisterUncreatableType<emoji::Emoji>(
|
||||||
"im.nheko.EmojiModel", 1, 0, "Emoji", "Used by emoji models");
|
"im.nheko.EmojiModel", 1, 0, "Emoji", "Used by emoji models");
|
||||||
|
Loading…
Reference in New Issue
Block a user