Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display room's parent in room switcher #1732

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions resources/qml/Completer.qml
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,14 @@ Control {
text: model.roomName
textFormat: Text.RichText
}
Loader {
active: Settings.displayParentInSwitcher && model.roomParent !== ""
sourceComponent: Label {
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
text: "[" + model.roomParent + "]"
font.pixelSize: popup.avatarHeight * 0.5
}
}
Comment on lines +252 to +259
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you make this font somehow special, so that it doesn't just look like a suffix to the roomname? I.e. make it smaller, bold, different color or whatever?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea

}
}
DelegateChoice {
Expand Down
29 changes: 29 additions & 0 deletions src/Cache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

#include <nlohmann/json.hpp>

#include <mtx/events/spaces.hpp>
#include <mtx/responses/common.hpp>
#include <mtx/responses/messages.hpp>

Expand Down Expand Up @@ -2945,6 +2946,26 @@ Cache::roomNamesAndAliases()
{
auto txn = ro_txn(env_);

auto getParentRoomIdsWithTxn = [&](const std::string &id) -> std::optional<std::string> {
auto cursor = lmdb::cursor::open(txn, spacesParentsDb_);
std::string_view sp = id, space_parent;
if (cursor.get(sp, space_parent, MDB_SET)) {
while (cursor.get(sp, space_parent, MDB_FIRST_DUP)) {
if (!space_parent.empty())
return std::make_optional(static_cast<std::string>(space_parent));
}
}
cursor.close();

return std::nullopt;
};
Comment on lines +2949 to +2961
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This actually gets a random room parent. I would suggest you actually query the canonical room parent. Otherwise it might appear quite random, which room is actually listed here. I would suggest you check the parentSpace function in the TimelineModel.

Actually since you don't need the parentSpace for anything but displaying the name of the filtered entries, I would suggest you might want to do (in RoomsModel::roleNames()):

auto parent = roomList->getRoomById();
if (parent) {
  auto desc = parent->parentSpace();
  if (desc) return desc->roomName();
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why, a room's parentSpace is empty. I tried to read off mtx::events::state::space::Parent events manually from a room and I did not find a parent. That's why I think I gave up and used spacesParentsDb.

This is the diff I used to test your approach

case Roles::RoomParent: {
            const auto roomPtr = roomListModel_.getRoomById(QString::fromStdString(rooms[index.row()].id));
            if (auto &room = *roomPtr; roomPtr) {
                if (const auto &parent = room.parentSpace(); parent) {
                    qInfo() << "Parent has name" << parent->roomName();
                    return parent->roomName();
                } else {
                    qWarning() << "No parent for room" << "expected" << rooms[index.row()].parent;
                }
            }
            else {
                qWarning() << "No room with ID";
            }
            return QString{};
        }

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did that room possibly not have a canonical parent? Can you maybe log the space parent event for that room?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think these rooms have any parents.

cache::client()->getStateEventsWithType<mtx::events::state::space::Parent>(this->room_id_.toStdString()).size() is 0.

Copy link
Author

@nishanthkarthik nishanthkarthik Jun 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think spacesParentsDb_ builds this relation using mtx::events::state::space::Child. MSC2946 describes walking the spaces tree using m.space.child.

The matrix spec also says

Spaces form a hierarchy of rooms which clients can use to structure their room list into a tree-like view. The parent/child relationship can be defined in two ways: with m.space.child state events in the space-room, or with m.space.parent state events in the child room.

In most cases, both the child and parent relationship should be defined to aid discovery of the space and its rooms. When only a m.space.child is used, the space is effectively a curated list of rooms which the rooms themselves might not be aware of. When only a m.space.parent is used, the rooms are “secretly” added to spaces with the effect of not being advertised directly by the space.

...

To avoid situations where a room falsely claims it is part of a given space, m.space.parent events should be ignored unless one of the following is true: A corresponding m.space.child event can be found in the supposed parent space. ...

I think m.space.child should be the primary sources of truth

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All 3 options are valid:

  • only space.child
  • only space.parent
  • both

We generally only accept canonicalParents as something added to the room name in Nheko, because this means the room admin accepted that relationship. Otherwise a room can have multiple parents or someone could maliciously craft space.child events to make a room belong to a community it doesn't want to. So I think we really should only accept canonical parent events.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case the canonical parent events are not available for a room, can this be an opt-in config? This is the structure that mautrix - matrix bridges use, so I'd really like to use the parent-child relationship from spacesParentsDb_. I think I can manually add the parent event to my ~50 matrix rooms but I'd prefer not to.

Alternatively, I can make an issue on mautrix about this but I'm not aware of the technical decisions behind not doing this by default.

Copy link
Member

@deepbluev7 deepbluev7 Nov 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we really should stick to only showing the canonical parent as the "official" parent. There are other features that rely on that like custom stickers and emojis. Maybe mautrix could change their spaces to use canonical parents?

In any case, if there is a canonical parent, we should really pick that one!


auto getRoomName = [&](const std::string &roomId) {
auto spaceDb = getStatesDb(txn, roomId);
auto membersDb = getMembersDb(txn, roomId);
return Cache::getRoomName(txn, spaceDb, membersDb).toStdString();
};

std::vector<RoomNameAlias> result;
result.reserve(roomsDb_.size(txn));

Expand All @@ -2962,13 +2983,21 @@ Cache::roomNamesAndAliases()
alias = aliases->content.alias;
}

auto parentId = getParentRoomIdsWithTxn(room_id_str);
auto parentName = std::string{};

if (parentId) {
parentName = getRoomName(*parentId);
}

result.push_back(RoomNameAlias{
.id = std::move(room_id_str),
.name = std::move(info.name),
.alias = std::move(alias),
.recent_activity = info.approximate_last_modification_ts,
.is_tombstoned = info.is_tombstoned,
.is_space = info.is_space,
.parent = std::move(parentName),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we load this information lazily when it is accessed first in the RoomsModel instead? Looking up this data seems to add quite a lot of overhead to constructing the fuzzy search structure, but we only need it when displaying specific results,so we can really look it up only when showing a certain entry.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a few deadlines coming up. I'll make the fixups in a few weeks :)

});
} catch (std::exception &e) {
nhlog::db()->warn("Failed to add room {} to result: {}", room_id, e.what());
Expand Down
1 change: 1 addition & 0 deletions src/CacheStructs.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ struct RoomNameAlias
std::uint64_t recent_activity;
bool is_tombstoned;
bool is_space;
std::string parent;
};

//! Basic information per member.
Expand Down
21 changes: 20 additions & 1 deletion src/RoomsModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@

#include "Cache_p.h"
#include "CompletionModelRoles.h"
#include "RoomlistModel.h"
#include "TimelineModel.h"
#include "UserSettingsPage.h"
#include "Utils.h"

RoomsModel::RoomsModel(bool showOnlyRoomWithAliases, QObject *parent)
RoomsModel::RoomsModel(RoomlistModel &roomlistModel, bool showOnlyRoomWithAliases, QObject *parent)
: QAbstractListModel(parent)
, roomListModel_(roomlistModel)
, showOnlyRoomWithAliases_(showOnlyRoomWithAliases)
{
rooms = cache::client()->roomNamesAndAliases();
Expand All @@ -35,6 +38,7 @@ RoomsModel::roleNames() const
{Roles::AvatarUrl, "avatarUrl"},
{Roles::RoomID, "roomid"},
{Roles::RoomName, "roomName"},
{Roles::RoomParent, "roomParent"},
{Roles::IsTombstoned, "isTombstoned"},
{Roles::IsSpace, "isSpace"},
};
Expand Down Expand Up @@ -72,6 +76,21 @@ RoomsModel::data(const QModelIndex &index, int role) const
return rooms[index.row()].is_tombstoned;
case Roles::IsSpace:
return rooms[index.row()].is_space;
case Roles::RoomParent: {
const auto roomPtr = roomListModel_.getRoomById(QString::fromStdString(rooms[index.row()].id));
if (auto &room = *roomPtr; roomPtr) {
if (const auto &parent = room.parentSpace(); parent) {
qInfo() << "Parent has name" << parent->roomName();
return parent->roomName();
} else {
qWarning() << "No parent for room" << "expected" << rooms[index.row()].parent;
}
}
else {
qWarning() << "No room with ID";
}
return QString{};
}
}
}
return {};
Expand Down
6 changes: 5 additions & 1 deletion src/RoomsModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#include <QAbstractListModel>
#include <QString>

class RoomlistModel;

class RoomsModel final : public QAbstractListModel
{
public:
Expand All @@ -20,9 +22,10 @@ class RoomsModel final : public QAbstractListModel
RoomName,
IsTombstoned,
IsSpace,
RoomParent,
};

RoomsModel(bool showOnlyRoomWithAliases = false, QObject *parent = nullptr);
RoomsModel(RoomlistModel &roomListModel, bool showOnlyRoomWithAliases = false, QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
Expand All @@ -32,6 +35,7 @@ class RoomsModel final : public QAbstractListModel
QVariant data(const QModelIndex &index, int role) const override;

private:
RoomlistModel &roomListModel_;
std::vector<RoomNameAlias> rooms;
bool showOnlyRoomWithAliases_;
};
51 changes: 41 additions & 10 deletions src/UserSettingsPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,17 @@ UserSettings::load(std::optional<QString> profile)
settings.value("user/timeline/message_hover_highlight", false).toBool();
enlargeEmojiOnlyMessages_ =
settings.value("user/timeline/enlarge_emoji_only_msg", false).toBool();
markdown_ = settings.value("user/markdown_enabled", true).toBool();
invertEnterKey_ = settings.value("user/invert_enter_key", false).toBool();
bubbles_ = settings.value("user/bubbles_enabled", false).toBool();
smallAvatars_ = settings.value("user/small_avatars_enabled", false).toBool();
animateImagesOnHover_ = settings.value("user/animate_images_on_hover", false).toBool();
typingNotifications_ = settings.value("user/typing_notifications", true).toBool();
sortByImportance_ = settings.value("user/sort_by_unread", true).toBool();
sortByAlphabet_ = settings.value("user/sort_by_alphabet", false).toBool();
readReceipts_ = settings.value("user/read_receipts", true).toBool();
theme_ = settings.value("user/theme", defaultTheme_).toString();
markdown_ = settings.value("user/markdown_enabled", true).toBool();
invertEnterKey_ = settings.value("user/invert_enter_key", false).toBool();
bubbles_ = settings.value("user/bubbles_enabled", false).toBool();
smallAvatars_ = settings.value("user/small_avatars_enabled", false).toBool();
animateImagesOnHover_ = settings.value("user/animate_images_on_hover", false).toBool();
typingNotifications_ = settings.value("user/typing_notifications", true).toBool();
sortByImportance_ = settings.value("user/sort_by_unread", true).toBool();
sortByAlphabet_ = settings.value("user/sort_by_alphabet", false).toBool();
readReceipts_ = settings.value("user/read_receipts", true).toBool();
theme_ = settings.value("user/theme", defaultTheme_).toString();
displayParentInSwitcher_ = settings.value("user/display_parent_on_room_switch", true).toBool();

font_ = settings.value("user/font_family", "").toString();

Expand Down Expand Up @@ -857,6 +858,16 @@ UserSettings::setOpenVideoExternal(bool state)
save();
}

void
UserSettings::setDisplayParentInSwitcher(bool state)
{
if (state == displayParentInSwitcher_)
return;
displayParentInSwitcher_ = state;
emit displayParentInSwitcherChanged(displayParentInSwitcher_);
save();
}

void
UserSettings::applyTheme()
{
Expand Down Expand Up @@ -931,6 +942,7 @@ UserSettings::save()
settings.setValue("expose_dbus_api", exposeDBusApi_);
settings.setValue("space_background_maintenance", updateSpaceVias_);
settings.setValue("expired_events_background_maintenance", expireEvents_);
settings.setValue("display_parent_on_room_switch", displayParentInSwitcher_);

settings.endGroup(); // user

Expand Down Expand Up @@ -1014,6 +1026,8 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
return tr("Communities sidebar");
case ScrollbarsInRoomlist:
return tr("Scrollbars in room list");
case DisplayParentInSwitcher:
return tr("Display room's parent in room switcher");
case Markdown:
return tr("Send messages as Markdown");
case InvertEnterKey:
Expand Down Expand Up @@ -1172,6 +1186,8 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
return i->groupView();
case ScrollbarsInRoomlist:
return i->scrollbarsInRoomlist();
case DisplayParentInSwitcher:
return i->displayParentInSwitcher();
case Markdown:
return i->markdown();
case InvertEnterKey:
Expand Down Expand Up @@ -1334,6 +1350,10 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
return tr("Show a column containing communities and tags next to the room list.");
case ScrollbarsInRoomlist:
return tr("Shows scrollbars in the room list and communities list.");
case DisplayParentInSwitcher:
return tr("Display a room's parent in the room switcher. "
"Enabling this option allows distinguishing multiple rooms "
"with the same name");
case Markdown:
return tr(
"Allow using markdown in messages.\nWhen disabled, all messages are sent as a plain "
Expand Down Expand Up @@ -1516,6 +1536,7 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
case StartInTray:
case GroupView:
case ScrollbarsInRoomlist:
case DisplayParentInSwitcher:
case Markdown:
case InvertEnterKey:
case Bubbles:
Expand Down Expand Up @@ -1758,6 +1779,13 @@ UserSettingsModel::setData(const QModelIndex &index, const QVariant &value, int
} else
return false;
}
case DisplayParentInSwitcher: {
if (value.userType() == QMetaType::Bool) {
i->setDisplayParentInSwitcher(value.toBool());
return true;
} else
return false;
}
case Markdown: {
if (value.userType() == QMetaType::Bool) {
i->setMarkdown(value.toBool());
Expand Down Expand Up @@ -2253,6 +2281,9 @@ UserSettingsModel::UserSettingsModel(QObject *p)
connect(s.get(), &UserSettings::scrollbarsInRoomlistChanged, this, [this]() {
emit dataChanged(index(ScrollbarsInRoomlist), index(ScrollbarsInRoomlist), {Value});
});
connect(s.get(), &UserSettings::displayParentInSwitcherChanged, this, [this]() {
emit dataChanged(index(DisplayParentInSwitcher), index(DisplayParentInSwitcher), {Value});
});
connect(s.get(), &UserSettings::roomSortingChangedImportance, this, [this]() {
emit dataChanged(index(SortByImportance), index(SortByImportance), {Value});
});
Expand Down
7 changes: 7 additions & 0 deletions src/UserSettingsPage.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ class UserSettings final : public QObject
Q_PROPERTY(bool updateSpaceVias READ updateSpaceVias WRITE setUpdateSpaceVias NOTIFY
updateSpaceViasChanged)
Q_PROPERTY(bool expireEvents READ expireEvents WRITE setExpireEvents NOTIFY expireEventsChanged)
Q_PROPERTY(bool displayParentInSwitcher READ displayParentInSwitcher WRITE
setDisplayParentInSwitcher NOTIFY displayParentInSwitcherChanged)

UserSettings();

Expand Down Expand Up @@ -228,6 +230,7 @@ class UserSettings final : public QObject
void setExposeDBusApi(bool state);
void setUpdateSpaceVias(bool state);
void setExpireEvents(bool state);
void setDisplayParentInSwitcher(bool state);

QString theme() const { return !theme_.isEmpty() ? theme_ : defaultTheme_; }
bool messageHoverHighlight() const { return messageHoverHighlight_; }
Expand Down Expand Up @@ -305,6 +308,7 @@ class UserSettings final : public QObject
bool exposeDBusApi() const { return exposeDBusApi_; }
bool updateSpaceVias() const { return updateSpaceVias_; }
bool expireEvents() const { return expireEvents_; }
bool displayParentInSwitcher() const { return displayParentInSwitcher_; }

signals:
void groupViewStateChanged(bool state);
Expand Down Expand Up @@ -371,6 +375,7 @@ class UserSettings final : public QObject
void exposeDBusApiChanged(bool state);
void updateSpaceViasChanged(bool state);
void expireEventsChanged(bool state);
void displayParentInSwitcherChanged(bool state);

private:
// Default to system theme if QT_QPA_PLATFORMTHEME var is set.
Expand Down Expand Up @@ -447,6 +452,7 @@ class UserSettings final : public QObject
bool exposeDBusApi_;
bool updateSpaceVias_;
bool expireEvents_;
bool displayParentInSwitcher_;

QSettings settings;

Expand Down Expand Up @@ -476,6 +482,7 @@ class UserSettingsModel : public QAbstractListModel
PrivacyScreen,
PrivacyScreenTimeout,
ScrollbarsInRoomlist,
DisplayParentInSwitcher,
#ifdef NHEKO_DBUS_SYS
ExposeDBusApi,
#endif
Expand Down
13 changes: 11 additions & 2 deletions src/timeline/TimelineViewManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -517,12 +517,12 @@ TimelineViewManager::completerFor(const QString &completerName, const QString &r
emojiModel->setParent(proxy);
return proxy;
} else if (completerName == QLatin1String("room")) {
auto roomModel = new RoomsModel(false);
auto roomModel = new RoomsModel(*rooms_, false);
auto proxy = new CompletionProxyModel(roomModel, 4);
roomModel->setParent(proxy);
return proxy;
} else if (completerName == QLatin1String("roomAliases")) {
auto roomModel = new RoomsModel(true);
auto roomModel = new RoomsModel(*rooms_, true);
auto proxy = new CompletionProxyModel(roomModel);
roomModel->setParent(proxy);
return proxy;
Expand Down Expand Up @@ -620,6 +620,9 @@ TimelineViewManager::forwardMessageToRoom(mtx::events::collections::TimelineEven
}

//! WORKAROUND(Nico): for https://bugreports.qt.io/browse/QTBUG-93281
// QTBUG-93281 Fixed in 6.7.0
// https://github.com/qt/qtdeclarative/commit/7fb39a7accba014063e32ac41a58b77905bbd95b
#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
void
TimelineViewManager::fixImageRendering([[maybe_unused]] QQuickTextDocument *t,
[[maybe_unused]] QQuickItem *i)
Expand All @@ -630,6 +633,12 @@ TimelineViewManager::fixImageRendering([[maybe_unused]] QQuickTextDocument *t,
}
#endif
}
#else
void
TimelineViewManager::fixImageRendering(QQuickTextDocument *, QQuickItem *)
{
}
#endif

using IgnoredUsers = mtx::events::EphemeralEvent<mtx::events::account_data::IgnoredUsers>;

Expand Down