diff --git a/resources/qml/Completer.qml b/resources/qml/Completer.qml index c6fea98e..c580cbc2 100644 --- a/resources/qml/Completer.qml +++ b/resources/qml/Completer.qml @@ -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 + } + } } } DelegateChoice { diff --git a/src/Cache.cpp b/src/Cache.cpp index 5e8ba13f..3521b308 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -32,6 +32,7 @@ #include +#include #include #include @@ -3095,6 +3096,26 @@ Cache::roomNamesAndAliases() { auto txn = ro_txn(db->env_); + auto getParentRoomIdsWithTxn = [&](const std::string &id) -> std::optional { + 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(space_parent)); + } + } + cursor.close(); + + return std::nullopt; + }; + + 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 result; result.reserve(db->rooms.size(txn)); @@ -3112,6 +3133,13 @@ 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), @@ -3119,6 +3147,7 @@ Cache::roomNamesAndAliases() .recent_activity = info.approximate_last_modification_ts, .is_tombstoned = info.is_tombstoned, .is_space = info.is_space, + .parent = std::move(parentName), }); } catch (std::exception &e) { nhlog::db()->warn("Failed to add room {} to result: {}", room_id, e.what()); diff --git a/src/CacheStructs.h b/src/CacheStructs.h index f1aafb96..18dadc10 100644 --- a/src/CacheStructs.h +++ b/src/CacheStructs.h @@ -111,6 +111,7 @@ struct RoomNameAlias std::uint64_t recent_activity; bool is_tombstoned; bool is_space; + std::string parent; }; //! Basic information per member. diff --git a/src/RoomsModel.cpp b/src/RoomsModel.cpp index fff9cbc6..d07277f2 100644 --- a/src/RoomsModel.cpp +++ b/src/RoomsModel.cpp @@ -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(); @@ -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"}, }; @@ -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 {}; diff --git a/src/RoomsModel.h b/src/RoomsModel.h index 83a21ae0..845f22ba 100644 --- a/src/RoomsModel.h +++ b/src/RoomsModel.h @@ -9,6 +9,8 @@ #include #include +class RoomlistModel; + class RoomsModel final : public QAbstractListModel { public: @@ -20,9 +22,10 @@ public: RoomName, IsTombstoned, IsSpace, + RoomParent, }; - RoomsModel(bool showOnlyRoomWithAliases = false, QObject *parent = nullptr); + RoomsModel(RoomlistModel &roomListModel, bool showOnlyRoomWithAliases = false, QObject *parent = nullptr); QHash roleNames() const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override { @@ -32,6 +35,7 @@ public: QVariant data(const QModelIndex &index, int role) const override; private: + RoomlistModel &roomListModel_; std::vector rooms; bool showOnlyRoomWithAliases_; }; diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index a56aaa6d..0ed8f701 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -65,16 +65,17 @@ UserSettings::load(std::optional 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(); @@ -873,6 +874,16 @@ UserSettings::setOpenVideoExternal(bool state) save(); } +void +UserSettings::setDisplayParentInSwitcher(bool state) +{ + if (state == displayParentInSwitcher_) + return; + displayParentInSwitcher_ = state; + emit displayParentInSwitcherChanged(displayParentInSwitcher_); + save(); +} + void UserSettings::applyTheme() { @@ -947,6 +958,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 @@ -1033,6 +1045,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: @@ -1193,6 +1207,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: @@ -1357,6 +1373,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 " @@ -1545,6 +1565,7 @@ UserSettingsModel::data(const QModelIndex &index, int role) const case StartInTray: case GroupView: case ScrollbarsInRoomlist: + case DisplayParentInSwitcher: case Markdown: case InvertEnterKey: case Bubbles: @@ -1803,6 +1824,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()); @@ -2315,6 +2343,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}); }); diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h index 63a4d616..0e1da621 100644 --- a/src/UserSettingsPage.h +++ b/src/UserSettingsPage.h @@ -122,6 +122,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(); @@ -238,6 +240,7 @@ public: 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_; } @@ -316,6 +319,7 @@ public: 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); @@ -383,6 +387,7 @@ signals: 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. @@ -460,6 +465,7 @@ private: bool exposeDBusApi_; bool updateSpaceVias_; bool expireEvents_; + bool displayParentInSwitcher_; QSettings settings; @@ -489,6 +495,7 @@ class UserSettingsModel : public QAbstractListModel PrivacyScreen, PrivacyScreenTimeout, ScrollbarsInRoomlist, + DisplayParentInSwitcher, #ifdef NHEKO_DBUS_SYS ExposeDBusApi, #endif diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 1b2635b0..fd7a340f 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -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; @@ -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) @@ -630,6 +633,12 @@ TimelineViewManager::fixImageRendering([[maybe_unused]] QQuickTextDocument *t, } #endif } +#else +void +TimelineViewManager::fixImageRendering(QQuickTextDocument *, QQuickItem *) +{ +} +#endif using IgnoredUsers = mtx::events::EphemeralEvent;