diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b43559f..5a5e3ba1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -311,8 +311,6 @@ set(SRC_FILES src/ChatPage.cpp src/Clipboard.cpp src/ColorImageProvider.cpp - src/CommunitiesList.cpp - src/CommunitiesListItem.cpp src/CompletionProxyModel.cpp src/DeviceVerificationFlow.cpp src/EventAccessors.cpp @@ -324,22 +322,14 @@ set(SRC_FILES src/MxcImageProvider.cpp src/Olm.cpp src/RegisterPage.cpp - src/RoomInfoListItem.cpp - src/RoomList.cpp src/SSOHandler.cpp - src/SideBarActions.cpp - src/Splitter.cpp src/TrayIcon.cpp - src/UserInfoWidget.cpp src/UserSettingsPage.cpp src/UsersModel.cpp src/RoomsModel.cpp src/Utils.cpp src/WebRTCSession.cpp src/WelcomePage.cpp - src/popups/PopupItem.cpp - src/popups/SuggestionsPopup.cpp - src/popups/UserMentions.cpp src/main.cpp third_party/blurhash/blurhash.cpp @@ -535,8 +525,6 @@ qt5_wrap_cpp(MOC_HEADERS src/CallManager.h src/ChatPage.h src/Clipboard.h - src/CommunitiesList.h - src/CommunitiesListItem.h src/CompletionProxyModel.h src/DeviceVerificationFlow.h src/InviteeItem.h @@ -544,21 +532,13 @@ qt5_wrap_cpp(MOC_HEADERS src/MainWindow.h src/MxcImageProvider.h src/RegisterPage.h - src/RoomInfoListItem.h - src/RoomList.h src/SSOHandler.h - src/SideBarActions.h - src/Splitter.h src/TrayIcon.h - src/UserInfoWidget.h src/UserSettingsPage.h src/UsersModel.h src/RoomsModel.h src/WebRTCSession.h src/WelcomePage.h - src/popups/PopupItem.h - src/popups/SuggestionsPopup.h - src/popups/UserMentions.h ) # diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index a8b6fa52..c23ab97d 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -72,6 +72,15 @@ Page { } } + Shortcut { + sequence: "Ctrl+Down" + onActivated: Rooms.nextRoom(); + } + Shortcut { + sequence: "Ctrl+Up" + onActivated: Rooms.previousRoom(); + } + Component { id: deviceVerificationDialog diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index bee20d60..4ad7bd14 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -23,10 +23,6 @@ #include "MainWindow.h" #include "MatrixClient.h" #include "Olm.h" -#include "RoomList.h" -#include "SideBarActions.h" -#include "Splitter.h" -#include "UserInfoWidget.h" #include "UserSettingsPage.h" #include "Utils.h" #include "ui/OverlayModal.h" @@ -36,7 +32,6 @@ #include "notifications/Manager.h" #include "dialogs/ReadReceipts.h" -#include "popups/UserMentions.h" #include "timeline/TimelineViewManager.h" #include "blurhash.hpp" @@ -76,62 +71,9 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) topLayout_->setSpacing(0); topLayout_->setMargin(0); - communitiesList_ = new CommunitiesList(this); - topLayout_->addWidget(communitiesList_); - - splitter = new Splitter(this); - splitter->setHandleWidth(0); - - topLayout_->addWidget(splitter); - - // SideBar - sideBar_ = new QFrame(this); - sideBar_->setObjectName("sideBar"); - sideBar_->setMinimumWidth(::splitter::calculateSidebarSizes(QFont{}).normal); - sideBarLayout_ = new QVBoxLayout(sideBar_); - sideBarLayout_->setSpacing(0); - sideBarLayout_->setMargin(0); - - sideBarTopWidget_ = new QWidget(sideBar_); - sidebarActions_ = new SideBarActions(this); - connect( - sidebarActions_, &SideBarActions::showSettings, this, &ChatPage::showUserSettingsPage); - connect(sidebarActions_, &SideBarActions::joinRoom, this, &ChatPage::joinRoom); - connect(sidebarActions_, &SideBarActions::createRoom, this, &ChatPage::createRoom); - - user_info_widget_ = new UserInfoWidget(sideBar_); - connect(user_info_widget_, &UserInfoWidget::openGlobalUserProfile, this, [this]() { - UserProfile *userProfile = new UserProfile("", utils::localUser(), view_manager_); - emit view_manager_->openProfile(userProfile); - }); - - user_mentions_popup_ = new popups::UserMentions(); - room_list_ = new RoomList(userSettings, sideBar_); - connect(room_list_, &RoomList::joinRoom, this, &ChatPage::joinRoom); - - sideBarLayout_->addWidget(user_info_widget_); - sideBarLayout_->addWidget(room_list_); - sideBarLayout_->addWidget(sidebarActions_); - - sideBarTopWidgetLayout_ = new QVBoxLayout(sideBarTopWidget_); - sideBarTopWidgetLayout_->setSpacing(0); - sideBarTopWidgetLayout_->setMargin(0); - - // Content - content_ = new QFrame(this); - content_->setObjectName("mainContent"); - contentLayout_ = new QVBoxLayout(content_); - contentLayout_->setSpacing(0); - contentLayout_->setMargin(0); - view_manager_ = new TimelineViewManager(callManager_, this); - contentLayout_->addWidget(view_manager_->getWidget()); - - // Splitter - splitter->addWidget(sideBar_); - splitter->addWidget(content_); - splitter->restoreSizes(parent->width()); + topLayout_->addWidget(view_manager_->getWidget()); connect(this, &ChatPage::downloadedSecrets, @@ -153,17 +95,6 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) trySync(); }); - connect( - new QShortcut(QKeySequence("Ctrl+Down"), this), &QShortcut::activated, this, [this]() { - if (isVisible()) - room_list_->nextRoom(); - }); - connect( - new QShortcut(QKeySequence("Ctrl+Up"), this), &QShortcut::activated, this, [this]() { - if (isVisible()) - room_list_->previousRoom(); - }); - connectivityTimer_.setInterval(CHECK_CONNECTIVITY_INTERVAL); connect(&connectivityTimer_, &QTimer::timeout, this, [=]() { if (http::client()->access_token().empty()) { @@ -185,10 +116,8 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) connect(this, &ChatPage::loggedOut, this, &ChatPage::logout); - connect( - view_manager_, &TimelineViewManager::showRoomList, splitter, &Splitter::showFullRoomList); connect(view_manager_, &TimelineViewManager::inviteUsers, this, [this](QStringList users) { - const auto room_id = current_room_.toStdString(); + const auto room_id = currentRoom().toStdString(); for (int ii = 0; ii < users.size(); ++ii) { QTimer::singleShot(ii * 500, this, [this, room_id, ii, users]() { @@ -211,29 +140,6 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) } }); - connect(room_list_, &RoomList::roomChanged, this, [this](QString room_id) { - this->current_room_ = room_id; - }); - connect(room_list_, &RoomList::roomChanged, splitter, &Splitter::showChatView); - - connect(room_list_, &RoomList::acceptInvite, this, [this](const QString &room_id) { - joinRoom(room_id); - room_list_->removeRoom(room_id, currentRoom() == room_id); - }); - - connect(room_list_, &RoomList::declineInvite, this, [this](const QString &room_id) { - leaveRoom(room_id); - room_list_->removeRoom(room_id, currentRoom() == room_id); - }); - - connect(view_manager_, - &TimelineViewManager::updateRoomsLastMessage, - room_list_, - &RoomList::updateRoomDescription); - - connect( - this, &ChatPage::updateGroupsInfo, communitiesList_, &CommunitiesList::setCommunities); - connect(this, &ChatPage::leftRoom, this, &ChatPage::removeRoom); connect(this, &ChatPage::newRoom, this, &ChatPage::changeRoom, Qt::QueuedConnection); connect(this, &ChatPage::notificationsRetrieved, this, &ChatPage::sendNotifications); @@ -248,60 +154,23 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) } }); - connect(communitiesList_, - &CommunitiesList::communityChanged, - this, - [this](const QString &groupId) { - current_community_ = groupId; - - if (groupId == "world") { - auto hidden = communitiesList_->hiddenTagsAndCommunities(); - std::set roomsToHide = communitiesList_->roomList(groupId); - for (const auto &hiddenTag : hidden) { - auto temp = communitiesList_->roomList(hiddenTag); - roomsToHide.insert(temp.begin(), temp.end()); - } - - room_list_->removeFilter(roomsToHide); - } else { - auto hidden = communitiesList_->hiddenTagsAndCommunities(); - hidden.erase(current_community_); - - auto roomsToShow = communitiesList_->roomList(groupId); - for (const auto &hiddenTag : hidden) { - for (const auto &r : communitiesList_->roomList(hiddenTag)) - roomsToShow.erase(r); - } - - room_list_->applyFilter(roomsToShow); - } - }); - connect(¬ificationsManager, &NotificationsManager::notificationClicked, this, [this](const QString &roomid, const QString &eventid) { Q_UNUSED(eventid) - room_list_->highlightSelectedRoom(roomid); + view_manager_->rooms()->setCurrentRoom(roomid); activateWindow(); }); connect(¬ificationsManager, &NotificationsManager::sendNotificationReply, this, [this](const QString &roomid, const QString &eventid, const QString &body) { + view_manager_->rooms()->setCurrentRoom(roomid); view_manager_->queueReply(roomid, eventid, body); - room_list_->highlightSelectedRoom(roomid); activateWindow(); }); - setGroupViewState(userSettings_->groupView()); - - connect(userSettings_.data(), - &UserSettings::groupViewStateChanged, - this, - &ChatPage::setGroupViewState); - - connect(this, &ChatPage::initializeRoomList, room_list_, &RoomList::initialize); connect( this, &ChatPage::initializeViews, @@ -312,30 +181,13 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) &ChatPage::initializeEmptyViews, view_manager_, &TimelineViewManager::initializeRoomlist); - connect(this, - &ChatPage::initializeMentions, - user_mentions_popup_, - &popups::UserMentions::initializeMentions); connect( this, &ChatPage::chatFocusChanged, view_manager_, &TimelineViewManager::chatFocusChanged); connect(this, &ChatPage::syncUI, this, [this](const mtx::responses::Rooms &rooms) { - try { - room_list_->cleanupInvites(cache::invites()); - } catch (const lmdb::error &e) { - nhlog::db()->error("failed to retrieve invites: {}", e.what()); - } - view_manager_->sync(rooms); - removeLeftRooms(rooms.leave); bool hasNotifications = false; for (const auto &room : rooms.join) { - auto room_id = QString::fromStdString(room.first); - updateRoomNotificationCount( - room_id, - room.second.unread_notifications.notification_count, - room.second.unread_notifications.highlight_count); - if (room.second.unread_notifications.notification_count > 0) hasNotifications = true; } @@ -358,16 +210,6 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) emit notificationsRetrieved(std::move(res)); }); }); - connect(this, &ChatPage::syncRoomlist, room_list_, &RoomList::sync); - connect(this, &ChatPage::syncTags, communitiesList_, &CommunitiesList::syncTags); - - // Callbacks to update the user info (top left corner of the page). - connect(this, &ChatPage::setUserAvatar, user_info_widget_, &UserInfoWidget::setAvatar); - connect(this, &ChatPage::setUserDisplayName, this, [this](const QString &name) { - auto userid = utils::localUser(); - user_info_widget_->setUserId(userid); - user_info_widget_->setDisplayName(name); - }); connect( this, &ChatPage::tryInitialSyncCb, this, &ChatPage::tryInitialSync, Qt::QueuedConnection); @@ -420,8 +262,6 @@ ChatPage::dropToLoginPage(const QString &msg) void ChatPage::resetUI() { - room_list_->clear(); - user_info_widget_->reset(); view_manager_->clearAll(); emit unreadMessages(0); @@ -474,9 +314,6 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token) view_manager_, &TimelineViewManager::updateReadReceipts); - connect( - cache::client(), &Cache::roomReadStatus, room_list_, &RoomList::updateReadStatus); - connect(cache::client(), &Cache::removeNotification, ¬ificationsManager, @@ -553,9 +390,7 @@ ChatPage::loadStateFromCache() olm::client()->load(cache::restoreOlmAccount(), STORAGE_SECRET_KEY); emit initializeEmptyViews(); - emit initializeRoomList(cache::roomInfo()); emit initializeMentions(cache::getTimelineMentions()); - emit syncTags(cache::roomInfo().toStdMap()); cache::calculateRoomReadStatus(); @@ -593,38 +428,6 @@ ChatPage::removeRoom(const QString &room_id) nhlog::db()->critical("failure while removing room: {}", e.what()); // TODO: Notify the user. } - - room_list_->removeRoom(room_id, room_id == current_room_); -} - -void -ChatPage::removeLeftRooms(const std::map &rooms) -{ - for (auto it = rooms.cbegin(); it != rooms.cend(); ++it) { - const auto room_id = QString::fromStdString(it->first); - room_list_->removeRoom(room_id, room_id == current_room_); - } -} - -void -ChatPage::setGroupViewState(bool isEnabled) -{ - if (!isEnabled) { - communitiesList_->communityChanged("world"); - communitiesList_->hide(); - - return; - } - - communitiesList_->show(); -} - -void -ChatPage::updateRoomNotificationCount(const QString &room_id, - uint16_t notification_count, - uint16_t highlight_count) -{ - room_list_->updateUnreadMessageCount(room_id, notification_count, highlight_count); } void @@ -672,18 +475,6 @@ ChatPage::sendNotifications(const mtx::responses::Notifications &res) } } -void -ChatPage::showNotificationsDialog(const QPoint &widgetPos) -{ - auto notifDialog = user_mentions_popup_; - - notifDialog->setGeometry( - widgetPos.x() - (width() / 10), widgetPos.y() + 25, width() / 5, height() / 2); - - notifDialog->raise(); - notifDialog->showPopup(); -} - void ChatPage::tryInitialSync() { @@ -782,11 +573,9 @@ ChatPage::startInitialSync() olm::handle_to_device_messages(res.to_device.events); emit initializeViews(std::move(res.rooms)); - emit initializeRoomList(cache::roomInfo()); emit initializeMentions(cache::getTimelineMentions()); cache::calculateRoomReadStatus(); - emit syncTags(cache::roomInfo().toStdMap()); } catch (const lmdb::error &e) { nhlog::db()->error("failed to save state after initial sync: {}", e.what()); @@ -823,12 +612,8 @@ ChatPage::handleSyncResponse(const mtx::responses::Sync &res, const std::string auto updates = cache::getRoomInfo(cache::client()->roomsWithStateUpdates(res)); - emit syncRoomlist(updates); - emit syncUI(res.rooms); - emit syncTags(cache::getRoomInfo(cache::client()->roomsWithTagUpdates(res))); - // if we process a lot of syncs (1 every 200ms), this means we clean the // db every 100s static int syncCounter = 0; @@ -932,7 +717,7 @@ ChatPage::joinRoomVia(const std::string &room_id, emit showNotification(tr("Failed to remove invite: %1").arg(e.what())); } - room_list_->highlightSelectedRoom(QString::fromStdString(room_id)); + view_manager_->rooms()->setCurrentRoom(QString::fromStdString(room_id)); }); } @@ -981,18 +766,17 @@ void ChatPage::changeRoom(const QString &room_id) { view_manager_->rooms()->setCurrentRoom(room_id); - room_list_->highlightSelectedRoom(room_id); } void ChatPage::inviteUser(QString userid, QString reason) { - auto room = current_room_; + auto room = currentRoom(); if (QMessageBox::question(this, tr("Confirm invite"), tr("Do you really want to invite %1 (%2)?") - .arg(cache::displayName(current_room_, userid)) + .arg(cache::displayName(room, userid)) .arg(userid)) != QMessageBox::Yes) return; @@ -1014,12 +798,12 @@ ChatPage::inviteUser(QString userid, QString reason) void ChatPage::kickUser(QString userid, QString reason) { - auto room = current_room_; + auto room = currentRoom(); if (QMessageBox::question(this, tr("Confirm kick"), tr("Do you really want to kick %1 (%2)?") - .arg(cache::displayName(current_room_, userid)) + .arg(cache::displayName(room, userid)) .arg(userid)) != QMessageBox::Yes) return; @@ -1041,12 +825,12 @@ ChatPage::kickUser(QString userid, QString reason) void ChatPage::banUser(QString userid, QString reason) { - auto room = current_room_; + auto room = currentRoom(); if (QMessageBox::question(this, tr("Confirm ban"), tr("Do you really want to ban %1 (%2)?") - .arg(cache::displayName(current_room_, userid)) + .arg(cache::displayName(room, userid)) .arg(userid)) != QMessageBox::Yes) return; @@ -1068,12 +852,12 @@ ChatPage::banUser(QString userid, QString reason) void ChatPage::unbanUser(QString userid, QString reason) { - auto room = current_room_; + auto room = currentRoom(); if (QMessageBox::question(this, tr("Confirm unban"), tr("Do you really want to unban %1 (%2)?") - .arg(cache::displayName(current_room_, userid)) + .arg(cache::displayName(room, userid)) .arg(userid)) != QMessageBox::Yes) return; @@ -1175,51 +959,6 @@ ChatPage::getProfileInfo() emit setUserAvatar(QString::fromStdString(res.avatar_url)); }); - - http::client()->joined_groups( - [this](const mtx::responses::JoinedGroups &res, mtx::http::RequestErr err) { - if (err) { - nhlog::net()->critical("failed to retrieve joined groups: {} {}", - static_cast(err->status_code), - err->matrix_error.error); - emit updateGroupsInfo({}); - return; - } - - emit updateGroupsInfo(res); - }); -} - -void -ChatPage::hideSideBars() -{ - // Don't hide side bar, if we are currently only showing the side bar! - if (view_manager_->getWidget()->isVisible()) { - communitiesList_->hide(); - sideBar_->hide(); - } - view_manager_->enableBackButton(); -} - -void -ChatPage::showSideBars() -{ - if (userSettings_->groupView()) - communitiesList_->show(); - - sideBar_->show(); - view_manager_->disableBackButton(); - content_->show(); -} - -uint64_t -ChatPage::timelineWidth() -{ - int sidebarWidth = sideBar_->minimumSize().width(); - sidebarWidth += communitiesList_->minimumSize().width(); - nhlog::ui()->info("timelineWidth: {}", size().width() - sidebarWidth); - - return size().width() - sidebarWidth; } void @@ -1305,7 +1044,8 @@ ChatPage::startChat(QString userid) if (std::find(room_members.begin(), room_members.end(), (userid).toStdString()) != room_members.end()) { - room_list_->highlightSelectedRoom(QString::fromStdString(room_id)); + view_manager_->rooms()->setCurrentRoom( + QString::fromStdString(room_id)); return; } } @@ -1406,7 +1146,7 @@ ChatPage::handleMatrixUri(const QByteArray &uri) for (auto roomid : joined_rooms) { if (roomid == targetRoomId) { - room_list_->highlightSelectedRoom(mxid1); + view_manager_->rooms()->setCurrentRoom(mxid1); if (!mxid2.isEmpty()) view_manager_->showEvent(mxid1, mxid2); return; @@ -1424,7 +1164,7 @@ ChatPage::handleMatrixUri(const QByteArray &uri) auto aliases = cache::client()->getRoomAliases(roomid); if (aliases) { if (aliases->alias == targetRoomAlias) { - room_list_->highlightSelectedRoom( + view_manager_->rooms()->setCurrentRoom( QString::fromStdString(roomid)); if (!mxid2.isEmpty()) view_manager_->showEvent( @@ -1446,8 +1186,17 @@ ChatPage::handleMatrixUri(const QUrl &uri) handleMatrixUri(uri.toString(QUrl::ComponentFormattingOption::FullyEncoded).toUtf8()); } -void -ChatPage::highlightRoom(const QString &room_id) +bool +ChatPage::isRoomActive(const QString &room_id) { - room_list_->highlightSelectedRoom(room_id); + return isActiveWindow() && currentRoom() == room_id; +} + +QString +ChatPage::currentRoom() const +{ + if (view_manager_->rooms()->currentRoom()) + return view_manager_->rooms()->currentRoom()->roomId(); + else + return ""; } diff --git a/src/ChatPage.h b/src/ChatPage.h index eb60047d..751e7074 100644 --- a/src/ChatPage.h +++ b/src/ChatPage.h @@ -27,15 +27,10 @@ #include "CacheCryptoStructs.h" #include "CacheStructs.h" -#include "CommunitiesList.h" #include "notifications/Manager.h" class OverlayModal; -class RoomList; -class SideBarActions; -class Splitter; class TimelineViewManager; -class UserInfoWidget; class UserSettings; class NotificationsManager; class TimelineModel; @@ -53,11 +48,6 @@ struct Notifications; struct Sync; struct Timeline; struct Rooms; -struct LeftRoom; -} - -namespace popups { -class UserMentions; } using SecretsToDecrypt = std::map; @@ -71,7 +61,6 @@ public: // Initialize all the components of the UI. void bootstrap(QString userid, QString homeserver, QString token); - QString currentRoom() const { return current_room_; } static ChatPage *instance() { return instance_; } @@ -80,14 +69,6 @@ public: TimelineViewManager *timelineManager() { return view_manager_; } void deleteConfigs(); - CommunitiesList *communitiesList() { return communitiesList_; } - - //! Calculate the width of the message timeline. - uint64_t timelineWidth(); - //! Hide the room & group list (if it was visible). - void hideSideBars(); - //! Show the room/group list (if it was visible). - void showSideBars(); void initiateLogout(); QString status() const; @@ -95,6 +76,9 @@ public: mtx::presence::PresenceState currentPresence() const; + // TODO(Nico): Get rid of this! + QString currentRoom() const; + public slots: void handleMatrixUri(const QByteArray &uri); void handleMatrixUri(const QUrl &uri); @@ -102,7 +86,6 @@ public slots: void startChat(QString userid); void leaveRoom(const QString &room_id); void createRoom(const mtx::requests::CreateRoom &req); - void highlightRoom(const QString &room_id); void joinRoom(const QString &room); void joinRoomVia(const std::string &room_id, const std::vector &via, @@ -145,13 +128,10 @@ signals: void leftRoom(const QString &room_id); void newRoom(const QString &room_id); - void initializeRoomList(QMap); void initializeViews(const mtx::responses::Rooms &rooms); void initializeEmptyViews(); void initializeMentions(const QMap ¬ifs); void syncUI(const mtx::responses::Rooms &rooms); - void syncRoomlist(const std::map &updates); - void syncTags(const std::map &updates); void dropToLoginPageCb(const QString &msg); void notifyMessage(const QString &roomid, @@ -161,7 +141,6 @@ signals: const QString &message, const QImage &icon); - void updateGroupsInfo(const mtx::responses::JoinedGroups &groups); void retrievedPresence(const QString &statusMsg, mtx::presence::PresenceState state); void themeChanged(); void decryptSidebarChanged(); @@ -207,65 +186,31 @@ private: void getProfileInfo(); //! Check if the given room is currently open. - bool isRoomActive(const QString &room_id) - { - return isActiveWindow() && currentRoom() == room_id; - } + bool isRoomActive(const QString &room_id); using UserID = QString; using Membership = mtx::events::StateEvent; using Memberships = std::map; - using LeftRooms = std::map; - void removeLeftRooms(const LeftRooms &rooms); - void loadStateFromCache(); void resetUI(); - //! Decides whether or not to hide the group's sidebar. - void setGroupViewState(bool isEnabled); template Memberships getMemberships(const std::vector &events) const; - //! Update the room with the new notification count. - void updateRoomNotificationCount(const QString &room_id, - uint16_t notification_count, - uint16_t highlight_count); //! Send desktop notification for the received messages. void sendNotifications(const mtx::responses::Notifications &); - void showNotificationsDialog(const QPoint &point); - template void connectCallMessage(); QHBoxLayout *topLayout_; - Splitter *splitter; - - QWidget *sideBar_; - QVBoxLayout *sideBarLayout_; - QWidget *sideBarTopWidget_; - QVBoxLayout *sideBarTopWidgetLayout_; - - QFrame *content_; - QVBoxLayout *contentLayout_; - - CommunitiesList *communitiesList_; - RoomList *room_list_; TimelineViewManager *view_manager_; - SideBarActions *sidebarActions_; QTimer connectivityTimer_; std::atomic_bool isConnected_; - QString current_room_; - QString current_community_; - - UserInfoWidget *user_info_widget_; - - popups::UserMentions *user_mentions_popup_; - // Global user settings. QSharedPointer userSettings_; diff --git a/src/CommunitiesList.cpp b/src/CommunitiesList.cpp deleted file mode 100644 index 7cc5d10e..00000000 --- a/src/CommunitiesList.cpp +++ /dev/null @@ -1,345 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "CommunitiesList.h" -#include "Cache.h" -#include "Logging.h" -#include "MatrixClient.h" -#include "MxcImageProvider.h" -#include "Splitter.h" -#include "UserSettingsPage.h" - -#include -#include - -#include - -CommunitiesList::CommunitiesList(QWidget *parent) - : QWidget(parent) -{ - QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); - sizePolicy.setHorizontalStretch(0); - sizePolicy.setVerticalStretch(1); - setSizePolicy(sizePolicy); - - topLayout_ = new QVBoxLayout(this); - topLayout_->setSpacing(0); - topLayout_->setMargin(0); - - const auto sideBarSizes = splitter::calculateSidebarSizes(QFont{}); - setFixedWidth(sideBarSizes.groups); - - scrollArea_ = new QScrollArea(this); - scrollArea_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - scrollArea_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - scrollArea_->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); - scrollArea_->setWidgetResizable(true); - scrollArea_->setAlignment(Qt::AlignLeading | Qt::AlignTop | Qt::AlignVCenter); - - contentsLayout_ = new QVBoxLayout(); - contentsLayout_->setSpacing(0); - contentsLayout_->setMargin(0); - - addGlobalItem(); - contentsLayout_->addStretch(1); - - scrollArea_->setLayout(contentsLayout_); - topLayout_->addWidget(scrollArea_); - - connect( - this, &CommunitiesList::avatarRetrieved, this, &CommunitiesList::updateCommunityAvatar); -} - -void -CommunitiesList::setCommunities(const mtx::responses::JoinedGroups &response) -{ - // remove all non-tag communities - auto it = communities_.begin(); - while (it != communities_.end()) { - if (it->second->is_tag()) { - ++it; - } else { - it = communities_.erase(it); - } - } - - addGlobalItem(); - - for (const auto &group : response.groups) - addCommunity(group); - - communities_["world"]->setPressedState(true); - selectedCommunity_ = "world"; - emit communityChanged("world"); - sortEntries(); -} - -void -CommunitiesList::syncTags(const std::map &info) -{ - for (const auto &room : info) - setTagsForRoom(room.first, room.second.tags); - emit communityChanged(selectedCommunity_); - sortEntries(); -} - -void -CommunitiesList::setTagsForRoom(const QString &room_id, const std::vector &tags) -{ - // create missing tag if any - for (const auto &tag : tags) { - // filter out tags we should ignore according to the spec - // https://matrix.org/docs/spec/client_server/r0.4.0.html#id154 - // nheko currently does not make use of internal tags - // so we ignore any tag containig a `.` (which would indicate a tag - // in the form `tld.domain.*`) except for `m.*` and `u.*`. - if (tag.find(".") != ::std::string::npos && tag.compare(0, 2, "m.") && - tag.compare(0, 2, "u.")) - continue; - QString name = QString("tag:") + QString::fromStdString(tag); - if (!communityExists(name)) { - addCommunity(std::string("tag:") + tag); - } - } - // update membership of the room for all tags - auto it = communities_.begin(); - while (it != communities_.end()) { - // Skip if the community is not a tag - if (!it->second->is_tag()) { - ++it; - continue; - } - // insert or remove the room from the tag as appropriate - std::string current_tag = - it->first.right(static_cast(it->first.size() - strlen("tag:"))) - .toStdString(); - if (std::find(tags.begin(), tags.end(), current_tag) != tags.end()) { - // the room has this tag - it->second->addRoom(room_id); - } else { - // the room does not have this tag - it->second->delRoom(room_id); - } - // Check if the tag is now empty, if yes delete it - if (it->second->rooms().empty()) { - it = communities_.erase(it); - } else { - ++it; - } - } -} - -void -CommunitiesList::addCommunity(const std::string &group_id) -{ - auto hiddenTags = UserSettings::instance()->hiddenTags(); - - const auto id = QString::fromStdString(group_id); - - CommunitiesListItem *list_item = new CommunitiesListItem(id, scrollArea_); - - if (hiddenTags.contains(id)) - list_item->setDisabled(true); - - communities_.emplace(id, QSharedPointer(list_item)); - contentsLayout_->insertWidget(contentsLayout_->count() - 1, list_item); - - connect(list_item, - &CommunitiesListItem::clicked, - this, - &CommunitiesList::highlightSelectedCommunity); - connect(list_item, &CommunitiesListItem::isDisabledChanged, this, [this]() { - for (const auto &community : communities_) { - if (community.second->isPressed()) { - emit highlightSelectedCommunity(community.first); - break; - } - } - - auto hiddenTags = hiddenTagsAndCommunities(); - // Qt < 5.14 compat - QStringList hiddenTags_; - for (auto &&t : hiddenTags) - hiddenTags_.push_back(t); - UserSettings::instance()->setHiddenTags(hiddenTags_); - }); - - if (group_id.empty() || group_id.front() != '+') - return; - - nhlog::ui()->debug("Add community: {}", group_id); - - connect(this, - &CommunitiesList::groupProfileRetrieved, - this, - [this](const QString &id, const mtx::responses::GroupProfile &profile) { - if (communities_.find(id) == communities_.end()) - return; - - communities_.at(id)->setName(QString::fromStdString(profile.name)); - - if (!profile.avatar_url.empty()) - fetchCommunityAvatar(id, - QString::fromStdString(profile.avatar_url)); - }); - connect(this, - &CommunitiesList::groupRoomsRetrieved, - this, - [this](const QString &id, const std::set &rooms) { - nhlog::ui()->info( - "Fetched rooms for {}: {}", id.toStdString(), rooms.size()); - if (communities_.find(id) == communities_.end()) - return; - - communities_.at(id)->setRooms(rooms); - }); - - http::client()->group_profile( - group_id, [id, this](const mtx::responses::GroupProfile &res, mtx::http::RequestErr err) { - if (err) { - return; - } - - emit groupProfileRetrieved(id, res); - }); - - http::client()->group_rooms( - group_id, [id, this](const nlohmann::json &res, mtx::http::RequestErr err) { - if (err) { - return; - } - - std::set room_ids; - for (const auto &room : res.at("chunk")) - room_ids.emplace(QString::fromStdString(room.at("room_id"))); - - emit groupRoomsRetrieved(id, room_ids); - }); -} - -void -CommunitiesList::updateCommunityAvatar(const QString &community_id, const QPixmap &img) -{ - if (!communityExists(community_id)) { - nhlog::ui()->warn("Avatar update on nonexistent community {}", - community_id.toStdString()); - return; - } - - communities_.at(community_id)->setAvatar(img.toImage()); -} - -void -CommunitiesList::highlightSelectedCommunity(const QString &community_id) -{ - if (!communityExists(community_id)) { - nhlog::ui()->debug("CommunitiesList: clicked unknown community"); - return; - } - - selectedCommunity_ = community_id; - emit communityChanged(community_id); - - for (const auto &community : communities_) { - if (community.first != community_id) { - community.second->setPressedState(false); - } else { - community.second->setPressedState(true); - scrollArea_->ensureWidgetVisible(community.second.data()); - } - } -} - -void -CommunitiesList::fetchCommunityAvatar(const QString &id, const QString &avatarUrl) -{ - MxcImageProvider::download( - QString(avatarUrl).remove(QStringLiteral("mxc://")), - QSize(96, 96), - [this, id](QString, QSize, QImage img, QString) { - if (img.isNull()) { - nhlog::net()->warn("failed to download avatar: {})", id.toStdString()); - return; - } - - emit avatarRetrieved(id, QPixmap::fromImage(img)); - }); -} - -std::set -CommunitiesList::roomList(const QString &id) const -{ - if (communityExists(id)) - return communities_.at(id)->rooms(); - - return {}; -} - -std::vector -CommunitiesList::currentTags() const -{ - std::vector tags; - for (auto &entry : communities_) { - CommunitiesListItem *item = entry.second.data(); - if (item->is_tag()) - tags.push_back(entry.first.mid(4).toStdString()); - } - return tags; -} - -std::set -CommunitiesList::hiddenTagsAndCommunities() const -{ - std::set hiddenTags; - for (auto &entry : communities_) { - if (entry.second->isDisabled()) - hiddenTags.insert(entry.first); - } - - return hiddenTags; -} - -void -CommunitiesList::sortEntries() -{ - std::vector header; - std::vector communities; - std::vector tags; - std::vector footer; - // remove all the contents and sort them in the 4 vectors - for (auto &entry : communities_) { - CommunitiesListItem *item = entry.second.data(); - contentsLayout_->removeWidget(item); - // world is handled separately - if (entry.first == "world") - continue; - // sort the rest - if (item->is_tag()) - if (entry.first == "tag:m.favourite") - header.push_back(item); - else if (entry.first == "tag:m.lowpriority") - footer.push_back(item); - else - tags.push_back(item); - else - communities.push_back(item); - } - - // now there remains only the stretch in the layout, remove it - QLayoutItem *stretch = contentsLayout_->itemAt(0); - contentsLayout_->removeItem(stretch); - - contentsLayout_->addWidget(communities_["world"].data()); - - auto insert_widgets = [this](auto &vec) { - for (auto item : vec) - contentsLayout_->addWidget(item); - }; - insert_widgets(header); - insert_widgets(communities); - insert_widgets(tags); - insert_widgets(footer); - - contentsLayout_->addItem(stretch); -} diff --git a/src/CommunitiesList.h b/src/CommunitiesList.h deleted file mode 100644 index 12b275b0..00000000 --- a/src/CommunitiesList.h +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include -#include - -#include "CacheStructs.h" -#include "CommunitiesListItem.h" - -namespace mtx::responses { -struct GroupProfile; -struct JoinedGroups; -} - -class CommunitiesList : public QWidget -{ - Q_OBJECT - -public: - CommunitiesList(QWidget *parent = nullptr); - - void clear() { communities_.clear(); } - - void addCommunity(const std::string &id); - void removeCommunity(const QString &id) { communities_.erase(id); }; - std::set roomList(const QString &id) const; - - void syncTags(const std::map &info); - void setTagsForRoom(const QString &id, const std::vector &tags); - std::vector currentTags() const; - std::set hiddenTagsAndCommunities() const; - -signals: - void communityChanged(const QString &id); - void avatarRetrieved(const QString &id, const QPixmap &img); - void groupProfileRetrieved(const QString &group_id, const mtx::responses::GroupProfile &); - void groupRoomsRetrieved(const QString &group_id, const std::set &res); - -public slots: - void updateCommunityAvatar(const QString &id, const QPixmap &img); - void highlightSelectedCommunity(const QString &id); - void setCommunities(const mtx::responses::JoinedGroups &groups); - -private: - void fetchCommunityAvatar(const QString &id, const QString &avatarUrl); - void addGlobalItem() { addCommunity("world"); } - void sortEntries(); - - //! Check whether or not a community id is currently managed. - bool communityExists(const QString &id) const - { - return communities_.find(id) != communities_.end(); - } - - QString selectedCommunity_; - QVBoxLayout *topLayout_; - QVBoxLayout *contentsLayout_; - QScrollArea *scrollArea_; - - std::map> communities_; -}; diff --git a/src/CommunitiesListItem.cpp b/src/CommunitiesListItem.cpp deleted file mode 100644 index a2f2777d..00000000 --- a/src/CommunitiesListItem.cpp +++ /dev/null @@ -1,201 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "CommunitiesListItem.h" - -#include -#include - -#include "Utils.h" -#include "ui/Painter.h" -#include "ui/Ripple.h" -#include "ui/RippleOverlay.h" - -CommunitiesListItem::CommunitiesListItem(QString group_id, QWidget *parent) - : QWidget(parent) - , groupId_(group_id) -{ - setMouseTracking(true); - setAttribute(Qt::WA_Hover); - - QPainterPath path; - path.addRect(0, 0, parent->width(), height()); - rippleOverlay_ = new RippleOverlay(this); - rippleOverlay_->setClipPath(path); - rippleOverlay_->setClipping(true); - - menu_ = new QMenu(this); - hideRoomsWithTagAction_ = - new QAction(tr("Hide rooms with this tag or from this community"), this); - hideRoomsWithTagAction_->setCheckable(true); - menu_->addAction(hideRoomsWithTagAction_); - connect(menu_, &QMenu::aboutToShow, this, [this]() { - hideRoomsWithTagAction_->setChecked(isDisabled_); - }); - - connect(hideRoomsWithTagAction_, &QAction::triggered, this, [this](bool checked) { - this->setDisabled(checked); - }); - - updateTooltip(); -} - -void -CommunitiesListItem::contextMenuEvent(QContextMenuEvent *event) -{ - menu_->popup(event->globalPos()); -} - -void -CommunitiesListItem::setName(QString name) -{ - name_ = name; - updateTooltip(); -} - -void -CommunitiesListItem::setPressedState(bool state) -{ - if (isPressed_ != state) { - isPressed_ = state; - update(); - } -} - -void -CommunitiesListItem::setDisabled(bool state) -{ - if (isDisabled_ != state) { - isDisabled_ = state; - update(); - emit isDisabledChanged(); - } -} - -void -CommunitiesListItem::mousePressEvent(QMouseEvent *event) -{ - if (event->buttons() == Qt::RightButton) { - QWidget::mousePressEvent(event); - return; - } - - emit clicked(groupId_); - - setPressedState(true); - - QPoint pos = event->pos(); - qreal radiusEndValue = static_cast(width()) / 3; - - auto ripple = new Ripple(pos); - ripple->setRadiusEndValue(radiusEndValue); - ripple->setOpacityStartValue(0.15); - ripple->setColor("white"); - ripple->radiusAnimation()->setDuration(200); - ripple->opacityAnimation()->setDuration(400); - rippleOverlay_->addRipple(ripple); -} - -void -CommunitiesListItem::paintEvent(QPaintEvent *) -{ - Painter p(this); - PainterHighQualityEnabler hq(p); - - if (isPressed_) - p.fillRect(rect(), highlightedBackgroundColor_); - else if (isDisabled_) - p.fillRect(rect(), disabledBackgroundColor_); - else if (underMouse()) - p.fillRect(rect(), hoverBackgroundColor_); - else - p.fillRect(rect(), backgroundColor_); - - if (avatar_.isNull()) { - QPixmap source; - if (groupId_ == "world") - source = QPixmap(":/icons/icons/ui/world.png"); - else if (groupId_ == "tag:m.favourite") - source = QPixmap(":/icons/icons/ui/star.png"); - else if (groupId_ == "tag:m.lowpriority") - source = QPixmap(":/icons/icons/ui/lowprio.png"); - else if (groupId_.startsWith("tag:")) - source = QPixmap(":/icons/icons/ui/tag.png"); - - if (source.isNull()) { - QFont font; - font.setPointSizeF(font.pointSizeF() * 1.3); - p.setFont(font); - - p.drawLetterAvatar(utils::firstChar(resolveName()), - avatarFgColor_, - avatarBgColor_, - width(), - height(), - IconSize); - } else { - QPainter painter(&source); - painter.setCompositionMode(QPainter::CompositionMode_SourceIn); - painter.fillRect(source.rect(), avatarFgColor_); - painter.end(); - - const int imageSz = 32; - p.drawPixmap( - QRect( - (width() - imageSz) / 2, (height() - imageSz) / 2, imageSz, imageSz), - source); - } - } else { - p.save(); - - p.drawAvatar(avatar_, width(), height(), IconSize); - p.restore(); - } -} - -void -CommunitiesListItem::setAvatar(const QImage &img) -{ - avatar_ = utils::scaleImageToPixmap(img, IconSize); - update(); -} - -QString -CommunitiesListItem::resolveName() const -{ - if (!name_.isEmpty()) - return name_; - if (groupId_.startsWith("tag:")) - return groupId_.right(static_cast(groupId_.size() - strlen("tag:"))); - if (!groupId_.startsWith("+")) - return QString("Group"); // Group with no name or id. - - // Extract the localpart of the group. - auto firstPart = groupId_.split(':').at(0); - return firstPart.right(firstPart.size() - 1); -} - -void -CommunitiesListItem::updateTooltip() -{ - if (groupId_ == "world") - setToolTip(tr("All rooms")); - else if (is_tag()) { - QStringRef tag = - groupId_.rightRef(static_cast(groupId_.size() - strlen("tag:"))); - if (tag == "m.favourite") - setToolTip(tr("Favourite rooms")); - else if (tag == "m.lowpriority") - setToolTip(tr("Low priority rooms")); - else if (tag == "m.server_notice") - setToolTip(tr("Server Notices", "Tag translation for m.server_notice")); - else if (tag.startsWith("u.")) - setToolTip(tag.right(tag.size() - 2) + tr(" (tag)")); - else - setToolTip(tag + tr(" (tag)")); - } else { - QString name = resolveName(); - setToolTip(name + tr(" (community)")); - } -} diff --git a/src/CommunitiesListItem.h b/src/CommunitiesListItem.h deleted file mode 100644 index e7468611..00000000 --- a/src/CommunitiesListItem.h +++ /dev/null @@ -1,107 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include - -#include - -#include "Config.h" - -class RippleOverlay; -class QMouseEvent; -class QMenu; - -class CommunitiesListItem : public QWidget -{ - Q_OBJECT - Q_PROPERTY(QColor highlightedBackgroundColor READ highlightedBackgroundColor WRITE - setHighlightedBackgroundColor) - Q_PROPERTY(QColor disabledBackgroundColor READ disabledBackgroundColor WRITE - setDisabledBackgroundColor) - Q_PROPERTY( - QColor hoverBackgroundColor READ hoverBackgroundColor WRITE setHoverBackgroundColor) - Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor) - - Q_PROPERTY(QColor avatarFgColor READ avatarFgColor WRITE setAvatarFgColor) - Q_PROPERTY(QColor avatarBgColor READ avatarBgColor WRITE setAvatarBgColor) - -public: - CommunitiesListItem(QString group_id, QWidget *parent = nullptr); - - void setName(QString name); - bool isPressed() const { return isPressed_; } - bool isDisabled() const { return isDisabled_; } - void setAvatar(const QImage &img); - - void setRooms(std::set room_ids) { room_ids_ = std::move(room_ids); } - void addRoom(const QString &id) { room_ids_.insert(id); } - void delRoom(const QString &id) { room_ids_.erase(id); } - std::set rooms() const { return room_ids_; } - - bool is_tag() const { return groupId_.startsWith("tag:"); } - - QColor highlightedBackgroundColor() const { return highlightedBackgroundColor_; } - QColor disabledBackgroundColor() const { return disabledBackgroundColor_; } - QColor hoverBackgroundColor() const { return hoverBackgroundColor_; } - QColor backgroundColor() const { return backgroundColor_; } - - QColor avatarFgColor() const { return avatarFgColor_; } - QColor avatarBgColor() const { return avatarBgColor_; } - - void setHighlightedBackgroundColor(QColor &color) { highlightedBackgroundColor_ = color; } - void setDisabledBackgroundColor(QColor &color) { disabledBackgroundColor_ = color; } - void setHoverBackgroundColor(QColor &color) { hoverBackgroundColor_ = color; } - void setBackgroundColor(QColor &color) { backgroundColor_ = color; } - - void setAvatarFgColor(QColor &color) { avatarFgColor_ = color; } - void setAvatarBgColor(QColor &color) { avatarBgColor_ = color; } - - QSize sizeHint() const override - { - return QSize(IconSize + IconSize / 3, IconSize + IconSize / 3); - } - -signals: - void clicked(const QString &group_id); - void isDisabledChanged(); - -public slots: - void setPressedState(bool state); - void setDisabled(bool state); - -protected: - void mousePressEvent(QMouseEvent *event) override; - void paintEvent(QPaintEvent *event) override; - void contextMenuEvent(QContextMenuEvent *event) override; - -private: - const int IconSize = 36; - - QString resolveName() const; - void updateTooltip(); - - std::set room_ids_; - - QString name_; - QString groupId_; - QPixmap avatar_; - - QColor highlightedBackgroundColor_; - QColor disabledBackgroundColor_; - QColor hoverBackgroundColor_; - QColor backgroundColor_; - - QColor avatarFgColor_; - QColor avatarBgColor_; - - bool isPressed_ = false; - bool isDisabled_ = false; - - RippleOverlay *rippleOverlay_; - QMenu *menu_; - QAction *hideRoomsWithTagAction_; -}; diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index e2b625b0..057ee4af 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -109,10 +109,6 @@ MainWindow::MainWindow(QWidget *parent) userSettingsPage_, SIGNAL(trayOptionChanged(bool)), trayIcon_, SLOT(setVisible(bool))); connect( userSettingsPage_, &UserSettingsPage::themeChanged, chat_page_, &ChatPage::themeChanged); - connect(userSettingsPage_, - &UserSettingsPage::decryptSidebarChanged, - chat_page_, - &ChatPage::decryptSidebarChanged); connect(trayIcon_, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, @@ -176,20 +172,6 @@ MainWindow::setWindowTitle(int notificationCount) QMainWindow::setWindowTitle(name); } -void -MainWindow::showEvent(QShowEvent *event) -{ - adjustSideBars(); - QMainWindow::showEvent(event); -} - -void -MainWindow::resizeEvent(QResizeEvent *event) -{ - adjustSideBars(); - QMainWindow::resizeEvent(event); -} - bool MainWindow::event(QEvent *event) { @@ -203,22 +185,6 @@ MainWindow::event(QEvent *event) return QMainWindow::event(event); } -void -MainWindow::adjustSideBars() -{ - const auto sz = splitter::calculateSidebarSizes(QFont{}); - - const uint64_t timelineWidth = chat_page_->timelineWidth(); - const uint64_t minAvailableWidth = sz.collapsePoint + sz.groups; - - nhlog::ui()->info("timelineWidth: {}, min {}", timelineWidth, minAvailableWidth); - if (timelineWidth < minAvailableWidth) { - chat_page_->hideSideBars(); - } else { - chat_page_->showSideBars(); - } -} - void MainWindow::restoreWindowSize() { diff --git a/src/MainWindow.h b/src/MainWindow.h index 69d07e62..3571f079 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -77,13 +77,9 @@ public: protected: void closeEvent(QCloseEvent *event) override; - void resizeEvent(QResizeEvent *event) override; - void showEvent(QShowEvent *event) override; bool event(QEvent *event) override; private slots: - //! Show or hide the sidebars based on window's size. - void adjustSideBars(); //! Handle interaction with the tray icon. void iconActivated(QSystemTrayIcon::ActivationReason reason); diff --git a/src/RoomInfoListItem.cpp b/src/RoomInfoListItem.cpp deleted file mode 100644 index ea5de674..00000000 --- a/src/RoomInfoListItem.cpp +++ /dev/null @@ -1,522 +0,0 @@ -// SPDX-FileCopyrightText: 2017 Konstantinos Sideris -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include -#include -#include -#include -#include -#include - -#include "AvatarProvider.h" -#include "Cache.h" -#include "ChatPage.h" -#include "Config.h" -#include "Logging.h" -#include "MatrixClient.h" -#include "RoomInfoListItem.h" -#include "Splitter.h" -#include "UserSettingsPage.h" -#include "Utils.h" -#include "ui/Ripple.h" -#include "ui/RippleOverlay.h" - -constexpr int MaxUnreadCountDisplayed = 99; - -struct WidgetMetrics -{ - int maxHeight; - int iconSize; - int padding; - int unit; - - int unreadLineWidth; - int unreadLineOffset; - - int inviteBtnX; - int inviteBtnY; -}; - -WidgetMetrics -getMetrics(const QFont &font) -{ - WidgetMetrics m; - - const int height = QFontMetrics(font).lineSpacing(); - - m.unit = height; - m.maxHeight = std::ceil((double)height * 3.8); - m.iconSize = std::ceil((double)height * 2.8); - m.padding = std::ceil((double)height / 2.0); - m.unreadLineWidth = m.padding - m.padding / 3; - m.unreadLineOffset = m.padding - m.padding / 4; - - m.inviteBtnX = m.iconSize + 2 * m.padding; - m.inviteBtnY = m.iconSize / 2.0 + m.padding + m.padding / 3.0; - - return m; -} - -void -RoomInfoListItem::init(QWidget *parent) -{ - setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - setMouseTracking(true); - setAttribute(Qt::WA_Hover); - - auto wm = getMetrics(QFont{}); - setFixedHeight(wm.maxHeight); - - QPainterPath path; - path.addRect(0, 0, parent->width(), height()); - - ripple_overlay_ = new RippleOverlay(this); - ripple_overlay_->setClipPath(path); - ripple_overlay_->setClipping(true); - - avatar_ = new Avatar(nullptr, wm.iconSize); - avatar_->setLetter(utils::firstChar(roomName_)); - avatar_->resize(wm.iconSize, wm.iconSize); - - unreadCountFont_.setPointSizeF(unreadCountFont_.pointSizeF() * 0.8); - unreadCountFont_.setBold(true); - - bubbleDiameter_ = QFontMetrics(unreadCountFont_).averageCharWidth() * 3; - - menu_ = new QMenu(this); - leaveRoom_ = new QAction(tr("Leave room"), this); - connect(leaveRoom_, &QAction::triggered, this, [this]() { emit leaveRoom(roomId_); }); - - connect(menu_, &QMenu::aboutToShow, this, [this]() { - menu_->clear(); - menu_->addAction(leaveRoom_); - - menu_->addSection(QIcon(":/icons/icons/ui/tag.png"), tr("Tag room as:")); - - auto roomInfo = cache::singleRoomInfo(roomId_.toStdString()); - - auto tags = ChatPage::instance()->communitiesList()->currentTags(); - - // add default tag, remove server notice tag - if (std::find(tags.begin(), tags.end(), "m.favourite") == tags.end()) - tags.push_back("m.favourite"); - if (std::find(tags.begin(), tags.end(), "m.lowpriority") == tags.end()) - tags.push_back("m.lowpriority"); - if (auto it = std::find(tags.begin(), tags.end(), "m.server_notice"); - it != tags.end()) - tags.erase(it); - - for (const auto &tag : tags) { - QString tagName; - if (tag == "m.favourite") - tagName = tr("Favourite", "Standard matrix tag for favourites"); - else if (tag == "m.lowpriority") - tagName = - tr("Low Priority", "Standard matrix tag for low priority rooms"); - else if (tag == "m.server_notice") - tagName = - tr("Server Notice", "Standard matrix tag for server notices"); - else if ((tag.size() > 2 && tag.substr(0, 2) == "u.") || - tag.find(".") != - std::string::npos) // tag manager creates tags without u., which - // is wrong, but we still want to display them - tagName = QString::fromStdString(tag.substr(2)); - - if (tagName.isEmpty()) - continue; - - auto tagAction = menu_->addAction(tagName); - tagAction->setCheckable(true); - tagAction->setWhatsThis(tr("Adds or removes the specified tag.", - "WhatsThis hint for tag menu actions")); - - for (const auto &riTag : roomInfo.tags) { - if (riTag == tag) { - tagAction->setChecked(true); - break; - } - } - - connect(tagAction, &QAction::triggered, this, [this, tag](bool checked) { - if (checked) - http::client()->put_tag( - roomId_.toStdString(), - tag, - {}, - [tag](mtx::http::RequestErr err) { - if (err) { - nhlog::ui()->error( - "Failed to add tag: {}, {}", - tag, - err->matrix_error.error); - } - }); - else - http::client()->delete_tag( - roomId_.toStdString(), - tag, - [tag](mtx::http::RequestErr err) { - if (err) { - nhlog::ui()->error( - "Failed to delete tag: {}, {}", - tag, - err->matrix_error.error); - } - }); - }); - } - - auto newTagAction = menu_->addAction(tr("New tag...", "Add a new tag to the room")); - connect(newTagAction, &QAction::triggered, this, [this]() { - QString tagName = - QInputDialog::getText(this, - tr("New Tag", "Tag name prompt title"), - tr("Tag:", "Tag name prompt")); - if (tagName.isEmpty()) - return; - - std::string tag = "u." + tagName.toStdString(); - - http::client()->put_tag( - roomId_.toStdString(), tag, {}, [tag](mtx::http::RequestErr err) { - if (err) { - nhlog::ui()->error("Failed to add tag: {}, {}", - tag, - err->matrix_error.error); - } - }); - }); - }); -} - -RoomInfoListItem::RoomInfoListItem(QString room_id, const RoomInfo &info, QWidget *parent) - : QWidget(parent) - , roomType_{info.is_invite ? RoomType::Invited : RoomType::Joined} - , roomId_(std::move(room_id)) - , roomName_{QString::fromStdString(std::move(info.name))} - , isPressed_(false) - , unreadMsgCount_(0) - , unreadHighlightedMsgCount_(0) -{ - init(parent); -} - -void -RoomInfoListItem::resizeEvent(QResizeEvent *) -{ - // Update ripple's clipping path. - QPainterPath path; - path.addRect(0, 0, width(), height()); - - const auto sidebarSizes = splitter::calculateSidebarSizes(QFont{}); - - if (width() > sidebarSizes.small) - setToolTip(""); - else - setToolTip(roomName_); - - ripple_overlay_->setClipPath(path); - ripple_overlay_->setClipping(true); -} - -void -RoomInfoListItem::paintEvent(QPaintEvent *event) -{ - Q_UNUSED(event); - - QPainter p(this); - p.setRenderHint(QPainter::TextAntialiasing); - p.setRenderHint(QPainter::SmoothPixmapTransform); - p.setRenderHint(QPainter::Antialiasing); - - QFontMetrics metrics(QFont{}); - - QPen titlePen(titleColor_); - QPen subtitlePen(subtitleColor_); - - auto wm = getMetrics(QFont{}); - - QPixmap pixmap(avatar_->size() * p.device()->devicePixelRatioF()); - pixmap.setDevicePixelRatio(p.device()->devicePixelRatioF()); - if (isPressed_) { - p.fillRect(rect(), highlightedBackgroundColor_); - titlePen.setColor(highlightedTitleColor_); - subtitlePen.setColor(highlightedSubtitleColor_); - pixmap.fill(highlightedBackgroundColor_); - } else if (underMouse()) { - p.fillRect(rect(), hoverBackgroundColor_); - titlePen.setColor(hoverTitleColor_); - subtitlePen.setColor(hoverSubtitleColor_); - pixmap.fill(hoverBackgroundColor_); - } else { - p.fillRect(rect(), backgroundColor_); - titlePen.setColor(titleColor_); - subtitlePen.setColor(subtitleColor_); - pixmap.fill(backgroundColor_); - } - - avatar_->render(&pixmap, QPoint(), QRegion(), RenderFlags(DrawChildren)); - p.drawPixmap(QPoint(wm.padding, wm.padding), pixmap); - - // Description line with the default font. - int bottom_y = wm.maxHeight - wm.padding - metrics.ascent() / 2; - - const auto sidebarSizes = splitter::calculateSidebarSizes(QFont{}); - - if (width() > sidebarSizes.small) { - QFont headingFont; - headingFont.setWeight(QFont::Medium); - p.setFont(headingFont); - p.setPen(titlePen); - - QFont tsFont; - tsFont.setPointSizeF(tsFont.pointSizeF() * 0.9); -#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0) - const int msgStampWidth = - QFontMetrics(tsFont).width(lastMsgInfo_.descriptiveTime) + 4; -#else - const int msgStampWidth = - QFontMetrics(tsFont).horizontalAdvance(lastMsgInfo_.descriptiveTime) + 4; -#endif - // We use the full width of the widget if there is no unread msg bubble. - const int bottomLineWidthLimit = (unreadMsgCount_ > 0) ? msgStampWidth : 0; - - // Name line. - QFontMetrics fontNameMetrics(headingFont); - int top_y = 2 * wm.padding + fontNameMetrics.ascent() / 2; - - const auto name = metrics.elidedText( - roomName(), - Qt::ElideRight, - (width() - wm.iconSize - 2 * wm.padding - msgStampWidth) * 0.8); - p.drawText(QPoint(2 * wm.padding + wm.iconSize, top_y), name); - - if (roomType_ == RoomType::Joined) { - p.setFont(QFont{}); - p.setPen(subtitlePen); - - int descriptionLimit = std::max( - 0, width() - 3 * wm.padding - bottomLineWidthLimit - wm.iconSize); - auto description = - metrics.elidedText(lastMsgInfo_.body, Qt::ElideRight, descriptionLimit); - p.drawText(QPoint(2 * wm.padding + wm.iconSize, bottom_y), description); - - // We show the last message timestamp. - p.save(); - if (isPressed_) { - p.setPen(QPen(highlightedTimestampColor_)); - } else if (underMouse()) { - p.setPen(QPen(hoverTimestampColor_)); - } else { - p.setPen(QPen(timestampColor_)); - } - - p.setFont(tsFont); - p.drawText(QPoint(width() - wm.padding - msgStampWidth, top_y), - lastMsgInfo_.descriptiveTime); - p.restore(); - } else { - int btnWidth = (width() - wm.iconSize - 6 * wm.padding) / 2; - - acceptBtnRegion_ = QRectF(wm.inviteBtnX, wm.inviteBtnY, btnWidth, 20); - declineBtnRegion_ = QRectF( - wm.inviteBtnX + btnWidth + 2 * wm.padding, wm.inviteBtnY, btnWidth, 20); - - QPainterPath acceptPath; - acceptPath.addRoundedRect(acceptBtnRegion_, 10, 10); - - p.setPen(Qt::NoPen); - p.fillPath(acceptPath, btnColor_); - p.drawPath(acceptPath); - - QPainterPath declinePath; - declinePath.addRoundedRect(declineBtnRegion_, 10, 10); - - p.setPen(Qt::NoPen); - p.fillPath(declinePath, btnColor_); - p.drawPath(declinePath); - - p.setPen(QPen(btnTextColor_)); - p.setFont(QFont{}); - p.drawText(acceptBtnRegion_, - Qt::AlignCenter, - metrics.elidedText(tr("Accept"), Qt::ElideRight, btnWidth)); - p.drawText(declineBtnRegion_, - Qt::AlignCenter, - metrics.elidedText(tr("Decline"), Qt::ElideRight, btnWidth)); - } - } - - p.setPen(Qt::NoPen); - - if (unreadMsgCount_ > 0) { - QBrush brush; - brush.setStyle(Qt::SolidPattern); - if (unreadHighlightedMsgCount_ > 0) { - brush.setColor(mentionedColor()); - } else { - brush.setColor(bubbleBgColor()); - } - - if (isPressed_) - brush.setColor(bubbleFgColor()); - - p.setBrush(brush); - p.setPen(Qt::NoPen); - p.setFont(unreadCountFont_); - - // Extra space on the x-axis to accomodate the extra character space - // inside the bubble. - const int x_width = unreadMsgCount_ > MaxUnreadCountDisplayed - ? QFontMetrics(p.font()).averageCharWidth() - : 0; - - QRectF r(width() - bubbleDiameter_ - wm.padding - x_width, - bottom_y - bubbleDiameter_ / 2 - 5, - bubbleDiameter_ + x_width, - bubbleDiameter_); - - if (width() == sidebarSizes.small) - r = QRectF(width() - bubbleDiameter_ - 5, - height() - bubbleDiameter_ - 5, - bubbleDiameter_ + x_width, - bubbleDiameter_); - - p.setPen(Qt::NoPen); - p.drawEllipse(r); - - p.setPen(QPen(bubbleFgColor())); - - if (isPressed_) - p.setPen(QPen(bubbleBgColor())); - - auto countTxt = unreadMsgCount_ > MaxUnreadCountDisplayed - ? QString("99+") - : QString::number(unreadMsgCount_); - - p.setBrush(Qt::NoBrush); - p.drawText(r.translated(0, -0.5), Qt::AlignCenter, countTxt); - } - - if (!isPressed_ && hasUnreadMessages_) { - QPen pen; - pen.setWidth(wm.unreadLineWidth); - pen.setColor(highlightedBackgroundColor_); - - p.setPen(pen); - p.drawLine(0, wm.unreadLineOffset, 0, height() - wm.unreadLineOffset); - } -} - -void -RoomInfoListItem::updateUnreadMessageCount(int count, int highlightedCount) -{ - unreadMsgCount_ = count; - unreadHighlightedMsgCount_ = highlightedCount; - update(); -} - -enum NotificationImportance : short -{ - ImportanceDisabled = -1, - AllEventsRead = 0, - NewMessage = 1, - NewMentions = 2, - Invite = 3 -}; - -short int -RoomInfoListItem::calculateImportance() const -{ - // Returns the degree of importance of the unread messages in the room. - // If sorting by importance is disabled in settings, this only ever - // returns ImportanceDisabled or Invite - if (isInvite()) { - return Invite; - } else if (!ChatPage::instance()->userSettings()->sortByImportance()) { - return ImportanceDisabled; - } else if (unreadHighlightedMsgCount_) { - return NewMentions; - } else if (unreadMsgCount_) { - return NewMessage; - } else { - return AllEventsRead; - } -} - -void -RoomInfoListItem::setPressedState(bool state) -{ - if (isPressed_ != state) { - isPressed_ = state; - update(); - } -} - -void -RoomInfoListItem::contextMenuEvent(QContextMenuEvent *event) -{ - Q_UNUSED(event); - - if (roomType_ == RoomType::Invited) - return; - - menu_->popup(event->globalPos()); -} - -void -RoomInfoListItem::mousePressEvent(QMouseEvent *event) -{ - if (event->buttons() == Qt::RightButton) { - QWidget::mousePressEvent(event); - return; - } else if (event->buttons() == Qt::LeftButton) { - if (roomType_ == RoomType::Invited) { - const auto point = event->pos(); - - if (acceptBtnRegion_.contains(point)) - emit acceptInvite(roomId_); - - if (declineBtnRegion_.contains(point)) - emit declineInvite(roomId_); - - return; - } - - emit clicked(roomId_); - - setPressedState(true); - - // Ripple on mouse position by default. - QPoint pos = event->pos(); - qreal radiusEndValue = static_cast(width()) / 3; - - Ripple *ripple = new Ripple(pos); - - ripple->setRadiusEndValue(radiusEndValue); - ripple->setOpacityStartValue(0.15); - ripple->setColor(QColor("white")); - ripple->radiusAnimation()->setDuration(200); - ripple->opacityAnimation()->setDuration(400); - - ripple_overlay_->addRipple(ripple); - } -} - -void -RoomInfoListItem::setAvatar(const QString &avatar_url) -{ - if (avatar_url.isEmpty()) - avatar_->setLetter(utils::firstChar(roomName_)); - else - avatar_->setImage(avatar_url); -} - -void -RoomInfoListItem::setDescriptionMessage(const DescInfo &info) -{ - lastMsgInfo_ = info; - update(); -} diff --git a/src/RoomInfoListItem.h b/src/RoomInfoListItem.h deleted file mode 100644 index a5e0009e..00000000 --- a/src/RoomInfoListItem.h +++ /dev/null @@ -1,210 +0,0 @@ -// SPDX-FileCopyrightText: 2017 Konstantinos Sideris -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include -#include -#include - -#include - -#include "CacheStructs.h" -#include "UserSettingsPage.h" -#include "ui/Avatar.h" - -class QMenu; -class RippleOverlay; - -class RoomInfoListItem : public QWidget -{ - Q_OBJECT - Q_PROPERTY(QColor highlightedBackgroundColor READ highlightedBackgroundColor WRITE - setHighlightedBackgroundColor) - Q_PROPERTY( - QColor hoverBackgroundColor READ hoverBackgroundColor WRITE setHoverBackgroundColor) - Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor) - - Q_PROPERTY(QColor bubbleBgColor READ bubbleBgColor WRITE setBubbleBgColor) - Q_PROPERTY(QColor bubbleFgColor READ bubbleFgColor WRITE setBubbleFgColor) - - Q_PROPERTY(QColor titleColor READ titleColor WRITE setTitleColor) - Q_PROPERTY(QColor subtitleColor READ subtitleColor WRITE setSubtitleColor) - - Q_PROPERTY(QColor timestampColor READ timestampColor WRITE setTimestampColor) - Q_PROPERTY(QColor highlightedTimestampColor READ highlightedTimestampColor WRITE - setHighlightedTimestampColor) - Q_PROPERTY(QColor hoverTimestampColor READ hoverTimestampColor WRITE setHoverTimestampColor) - - Q_PROPERTY( - QColor highlightedTitleColor READ highlightedTitleColor WRITE setHighlightedTitleColor) - Q_PROPERTY(QColor highlightedSubtitleColor READ highlightedSubtitleColor WRITE - setHighlightedSubtitleColor) - - Q_PROPERTY(QColor hoverTitleColor READ hoverTitleColor WRITE setHoverTitleColor) - Q_PROPERTY(QColor hoverSubtitleColor READ hoverSubtitleColor WRITE setHoverSubtitleColor) - - Q_PROPERTY(QColor mentionedColor READ mentionedColor WRITE setMentionedColor) - Q_PROPERTY(QColor btnColor READ btnColor WRITE setBtnColor) - Q_PROPERTY(QColor btnTextColor READ btnTextColor WRITE setBtnTextColor) - -public: - RoomInfoListItem(QString room_id, const RoomInfo &info, QWidget *parent = nullptr); - - void updateUnreadMessageCount(int count, int highlightedCount); - void clearUnreadMessageCount() { updateUnreadMessageCount(0, 0); }; - - short int calculateImportance() const; - - QString roomId() { return roomId_; } - bool isPressed() const { return isPressed_; } - int unreadMessageCount() const { return unreadMsgCount_; } - - void setAvatar(const QString &avatar_url); - void setDescriptionMessage(const DescInfo &info); - DescInfo lastMessageInfo() const { return lastMsgInfo_; } - - QColor highlightedBackgroundColor() const { return highlightedBackgroundColor_; } - QColor hoverBackgroundColor() const { return hoverBackgroundColor_; } - QColor hoverTitleColor() const { return hoverTitleColor_; } - QColor hoverSubtitleColor() const { return hoverSubtitleColor_; } - QColor hoverTimestampColor() const { return hoverTimestampColor_; } - QColor backgroundColor() const { return backgroundColor_; } - - QColor highlightedTitleColor() const { return highlightedTitleColor_; } - QColor highlightedSubtitleColor() const { return highlightedSubtitleColor_; } - QColor highlightedTimestampColor() const { return highlightedTimestampColor_; } - - QColor titleColor() const { return titleColor_; } - QColor subtitleColor() const { return subtitleColor_; } - QColor timestampColor() const { return timestampColor_; } - QColor btnColor() const { return btnColor_; } - QColor btnTextColor() const { return btnTextColor_; } - - QColor bubbleFgColor() const { return bubbleFgColor_; } - QColor bubbleBgColor() const { return bubbleBgColor_; } - QColor mentionedColor() const { return mentionedFontColor_; } - - void setHighlightedBackgroundColor(QColor &color) { highlightedBackgroundColor_ = color; } - void setHoverBackgroundColor(QColor &color) { hoverBackgroundColor_ = color; } - void setHoverSubtitleColor(QColor &color) { hoverSubtitleColor_ = color; } - void setHoverTitleColor(QColor &color) { hoverTitleColor_ = color; } - void setHoverTimestampColor(QColor &color) { hoverTimestampColor_ = color; } - void setBackgroundColor(QColor &color) { backgroundColor_ = color; } - void setTimestampColor(QColor &color) { timestampColor_ = color; } - - void setHighlightedTitleColor(QColor &color) { highlightedTitleColor_ = color; } - void setHighlightedSubtitleColor(QColor &color) { highlightedSubtitleColor_ = color; } - void setHighlightedTimestampColor(QColor &color) { highlightedTimestampColor_ = color; } - - void setTitleColor(QColor &color) { titleColor_ = color; } - void setSubtitleColor(QColor &color) { subtitleColor_ = color; } - - void setBtnColor(QColor &color) { btnColor_ = color; } - void setBtnTextColor(QColor &color) { btnTextColor_ = color; } - - void setBubbleFgColor(QColor &color) { bubbleFgColor_ = color; } - void setBubbleBgColor(QColor &color) { bubbleBgColor_ = color; } - void setMentionedColor(QColor &color) { mentionedFontColor_ = color; } - - void setRoomName(const QString &name) { roomName_ = name; } - void setRoomType(bool isInvite) - { - if (isInvite) - roomType_ = RoomType::Invited; - else - roomType_ = RoomType::Joined; - } - - bool isInvite() const { return roomType_ == RoomType::Invited; } - void setReadState(bool hasUnreadMessages) - { - if (hasUnreadMessages_ != hasUnreadMessages) { - hasUnreadMessages_ = hasUnreadMessages; - update(); - } - } - -signals: - void clicked(const QString &room_id); - void leaveRoom(const QString &room_id); - void acceptInvite(const QString &room_id); - void declineInvite(const QString &room_id); - -public slots: - void setPressedState(bool state); - -protected: - void mousePressEvent(QMouseEvent *event) override; - void paintEvent(QPaintEvent *event) override; - void resizeEvent(QResizeEvent *event) override; - void contextMenuEvent(QContextMenuEvent *event) override; - -private: - void init(QWidget *parent); - QString roomName() { return roomName_; } - - RippleOverlay *ripple_overlay_; - Avatar *avatar_; - - enum class RoomType - { - Joined, - Invited, - }; - - RoomType roomType_ = RoomType::Joined; - - // State information for the invited rooms. - mtx::responses::InvitedRoom invitedRoom_; - - QString roomId_; - QString roomName_; - - DescInfo lastMsgInfo_; - - QMenu *menu_; - QAction *leaveRoom_; - - bool isPressed_ = false; - bool hasUnreadMessages_ = true; - - int unreadMsgCount_ = 0; - int unreadHighlightedMsgCount_ = 0; - - QColor highlightedBackgroundColor_; - QColor hoverBackgroundColor_; - QColor backgroundColor_; - - QColor highlightedTitleColor_; - QColor highlightedSubtitleColor_; - - QColor titleColor_; - QColor subtitleColor_; - - QColor hoverTitleColor_; - QColor hoverSubtitleColor_; - - QColor btnColor_; - QColor btnTextColor_; - - QRectF acceptBtnRegion_; - QRectF declineBtnRegion_; - - // Fonts - QColor mentionedFontColor_; - QFont unreadCountFont_; - int bubbleDiameter_; - - QColor timestampColor_; - QColor highlightedTimestampColor_; - QColor hoverTimestampColor_; - - QColor bubbleBgColor_; - QColor bubbleFgColor_; - - friend struct room_sort; -}; diff --git a/src/RoomList.cpp b/src/RoomList.cpp deleted file mode 100644 index 5839c4a0..00000000 --- a/src/RoomList.cpp +++ /dev/null @@ -1,535 +0,0 @@ -// SPDX-FileCopyrightText: 2017 Konstantinos Sideris -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "Logging.h" -#include "MainWindow.h" -#include "RoomInfoListItem.h" -#include "RoomList.h" -#include "UserSettingsPage.h" -#include "Utils.h" -#include "ui/OverlayModal.h" - -RoomList::RoomList(QSharedPointer userSettings, QWidget *parent) - : QWidget(parent) -{ - topLayout_ = new QVBoxLayout(this); - topLayout_->setSpacing(0); - topLayout_->setMargin(0); - - scrollArea_ = new QScrollArea(this); - scrollArea_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - scrollArea_->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); - scrollArea_->setWidgetResizable(true); - scrollArea_->setAlignment(Qt::AlignLeading | Qt::AlignTop | Qt::AlignVCenter); - scrollArea_->setAttribute(Qt::WA_AcceptTouchEvents); - - QScroller::grabGesture(scrollArea_, QScroller::TouchGesture); - QScroller::grabGesture(scrollArea_, QScroller::LeftMouseButtonGesture); - -// The scrollbar on macOS will hide itself when not active so it won't interfere -// with the content. -#if not defined(Q_OS_MAC) - scrollArea_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); -#endif - - scrollAreaContents_ = new QWidget(this); - scrollAreaContents_->setObjectName("roomlist_area"); - - contentsLayout_ = new QVBoxLayout(scrollAreaContents_); - contentsLayout_->setAlignment(Qt::AlignTop); - contentsLayout_->setSpacing(0); - contentsLayout_->setMargin(0); - - scrollArea_->setWidget(scrollAreaContents_); - topLayout_->addWidget(scrollArea_); - - connect(this, &RoomList::updateRoomAvatarCb, this, &RoomList::updateRoomAvatar); - connect(userSettings.data(), - &UserSettings::roomSortingChanged, - this, - &RoomList::sortRoomsByLastMessage); -} - -void -RoomList::addRoom(const QString &room_id, const RoomInfo &info) -{ - auto room_item = new RoomInfoListItem(room_id, info, scrollArea_); - room_item->setRoomName(QString::fromStdString(std::move(info.name))); - - connect(room_item, &RoomInfoListItem::clicked, this, &RoomList::highlightSelectedRoom); - connect(room_item, &RoomInfoListItem::leaveRoom, this, [](const QString &room_id) { - MainWindow::instance()->openLeaveRoomDialog(room_id); - }); - - QSharedPointer roomWidget(room_item, &QObject::deleteLater); - rooms_.emplace(room_id, roomWidget); - rooms_sort_cache_.push_back(roomWidget); - - if (!info.avatar_url.empty()) - updateAvatar(room_id, QString::fromStdString(info.avatar_url)); - - int pos = contentsLayout_->count() - 1; - contentsLayout_->insertWidget(pos, room_item); -} - -void -RoomList::updateAvatar(const QString &room_id, const QString &url) -{ - emit updateRoomAvatarCb(room_id, url); -} - -void -RoomList::removeRoom(const QString &room_id, bool reset) -{ - auto roomIt = rooms_.find(room_id); - if (roomIt == rooms_.end()) { - return; - } - - for (auto roomSortIt = rooms_sort_cache_.begin(); roomSortIt != rooms_sort_cache_.end(); - ++roomSortIt) { - if (roomIt->second == *roomSortIt) { - rooms_sort_cache_.erase(roomSortIt); - break; - } - } - rooms_.erase(room_id); - - if (rooms_.empty() || !reset) - return; - - auto room = firstRoom(); - - if (room.second.isNull()) - return; - - room.second->setPressedState(true); - emit roomChanged(room.first); -} - -void -RoomList::updateUnreadMessageCount(const QString &roomid, int count, int highlightedCount) -{ - if (!roomExists(roomid)) { - nhlog::ui()->warn("updateUnreadMessageCount: unknown room_id {}", - roomid.toStdString()); - return; - } - - rooms_[roomid]->updateUnreadMessageCount(count, highlightedCount); - - calculateUnreadMessageCount(); - - sortRoomsByLastMessage(); -} - -void -RoomList::calculateUnreadMessageCount() -{ - int total_unread_msgs = 0; - - for (const auto &room : rooms_) { - if (!room.second.isNull()) - total_unread_msgs += room.second->unreadMessageCount(); - } - - emit totalUnreadMessageCountUpdated(total_unread_msgs); -} - -void -RoomList::initialize(const QMap &info) -{ - nhlog::ui()->info("initialize room list"); - - rooms_.clear(); - - // prevent flickering and save time sorting over and over again - setUpdatesEnabled(false); - for (auto it = info.begin(); it != info.end(); it++) { - if (it.value().is_invite) - addInvitedRoom(it.key(), it.value()); - else - addRoom(it.key(), it.value()); - } - - for (auto it = info.begin(); it != info.end(); it++) - updateRoomDescription(it.key(), it.value().msgInfo); - - setUpdatesEnabled(true); - - if (rooms_.empty()) - return; - - sortRoomsByLastMessage(); - - auto room = firstRoom(); - if (room.second.isNull()) - return; - - room.second->setPressedState(true); - emit roomChanged(room.first); -} - -void -RoomList::cleanupInvites(const QHash &invites) -{ - if (invites.size() == 0) - return; - - utils::erase_if(rooms_, [invites](auto &room) { - auto room_id = room.first; - auto item = room.second; - - if (!item) - return false; - - return item->isInvite() && (invites.find(room_id) == invites.end()); - }); -} - -void -RoomList::sync(const std::map &info) - -{ - for (const auto &room : info) - updateRoom(room.first, room.second); - - if (!info.empty()) - sortRoomsByLastMessage(); -} - -void -RoomList::highlightSelectedRoom(const QString &room_id) -{ - emit roomChanged(room_id); - - if (!roomExists(room_id)) { - nhlog::ui()->warn("roomlist: clicked unknown room_id"); - return; - } - - for (auto const &room : rooms_) { - if (room.second.isNull()) - continue; - - if (room.first != room_id) { - room.second->setPressedState(false); - } else { - room.second->setPressedState(true); - scrollArea_->ensureWidgetVisible(room.second.data()); - } - } - - selectedRoom_ = room_id; -} - -void -RoomList::nextRoom() -{ - for (int ii = 0; ii < contentsLayout_->count() - 1; ++ii) { - auto room = qobject_cast(contentsLayout_->itemAt(ii)->widget()); - - if (!room) - continue; - - if (room->roomId() == selectedRoom_) { - auto nextRoom = qobject_cast( - contentsLayout_->itemAt(ii + 1)->widget()); - - // Not a room message. - if (!nextRoom || nextRoom->isInvite()) - return; - - emit roomChanged(nextRoom->roomId()); - if (!roomExists(nextRoom->roomId())) { - nhlog::ui()->warn("roomlist: clicked unknown room_id"); - return; - } - - room->setPressedState(false); - nextRoom->setPressedState(true); - - scrollArea_->ensureWidgetVisible(nextRoom); - selectedRoom_ = nextRoom->roomId(); - return; - } - } -} - -void -RoomList::previousRoom() -{ - for (int ii = 1; ii < contentsLayout_->count(); ++ii) { - auto room = qobject_cast(contentsLayout_->itemAt(ii)->widget()); - - if (!room) - continue; - - if (room->roomId() == selectedRoom_) { - auto nextRoom = qobject_cast( - contentsLayout_->itemAt(ii - 1)->widget()); - - // Not a room message. - if (!nextRoom || nextRoom->isInvite()) - return; - - emit roomChanged(nextRoom->roomId()); - if (!roomExists(nextRoom->roomId())) { - nhlog::ui()->warn("roomlist: clicked unknown room_id"); - return; - } - - room->setPressedState(false); - nextRoom->setPressedState(true); - - scrollArea_->ensureWidgetVisible(nextRoom); - selectedRoom_ = nextRoom->roomId(); - return; - } - } -} - -void -RoomList::updateRoomAvatar(const QString &roomid, const QString &img) -{ - if (!roomExists(roomid)) { - return; - } - - rooms_[roomid]->setAvatar(img); - - // Used to inform other widgets for the new image data. - emit roomAvatarChanged(roomid, img); -} - -void -RoomList::updateRoomDescription(const QString &roomid, const DescInfo &info) -{ - if (!roomExists(roomid)) { - return; - } - - rooms_[roomid]->setDescriptionMessage(info); - - if (underMouse()) { - // When the user hover out of the roomlist a sort will be triggered. - isSortPending_ = true; - return; - } - - isSortPending_ = false; - - emit sortRoomsByLastMessage(); -} - -struct room_sort -{ - bool operator()(const QSharedPointer &a, - const QSharedPointer &b) const - { - // Sort by "importance" (i.e. invites before mentions before - // notifs before new events before old events), then secondly - // by recency. - - // Checking importance first - const auto a_importance = a->calculateImportance(); - const auto b_importance = b->calculateImportance(); - if (a_importance != b_importance) { - return a_importance > b_importance; - } - - // Now sort by recency - // Zero if empty, otherwise the time that the event occured - const uint64_t a_recency = - a->lastMsgInfo_.userid.isEmpty() ? 0 : a->lastMsgInfo_.timestamp; - const uint64_t b_recency = - b->lastMsgInfo_.userid.isEmpty() ? 0 : b->lastMsgInfo_.timestamp; - return a_recency > b_recency; - } -}; - -void -RoomList::sortRoomsByLastMessage() -{ - isSortPending_ = false; - - std::stable_sort(begin(rooms_sort_cache_), end(rooms_sort_cache_), room_sort{}); - - int newIndex = 0; - for (const auto &roomWidget : rooms_sort_cache_) { - const auto currentIndex = contentsLayout_->indexOf(roomWidget.data()); - - if (currentIndex != newIndex) { - contentsLayout_->removeWidget(roomWidget.data()); - contentsLayout_->insertWidget(newIndex, roomWidget.data()); - } - newIndex++; - } -} - -void -RoomList::leaveEvent(QEvent *event) -{ - if (isSortPending_) - QTimer::singleShot(700, this, &RoomList::sortRoomsByLastMessage); - - QWidget::leaveEvent(event); -} - -void -RoomList::closeJoinRoomDialog(bool isJoining, QString roomAlias) -{ - joinRoomModal_->hide(); - - if (isJoining) - emit joinRoom(roomAlias); -} - -void -RoomList::removeFilter(const std::set &roomsToHide) -{ - setUpdatesEnabled(false); - for (int i = 0; i < contentsLayout_->count(); i++) { - auto widget = - qobject_cast(contentsLayout_->itemAt(i)->widget()); - if (widget) { - if (roomsToHide.find(widget->roomId()) == roomsToHide.end()) - widget->show(); - else - widget->hide(); - } - } - setUpdatesEnabled(true); -} - -void -RoomList::applyFilter(const std::set &filter) -{ - // Disabling paint updates will resolve issues with screen flickering on big room lists. - setUpdatesEnabled(false); - - for (int i = 0; i < contentsLayout_->count(); i++) { - // If filter contains the room for the current RoomInfoListItem, - // show the list item, otherwise hide it - auto listitem = - qobject_cast(contentsLayout_->itemAt(i)->widget()); - - if (!listitem) - continue; - - if (filter.find(listitem->roomId()) != filter.end()) - listitem->show(); - else - listitem->hide(); - } - - setUpdatesEnabled(true); - - // If the already selected room is part of the group, make sure it's visible. - if (!selectedRoom_.isEmpty() && (filter.find(selectedRoom_) != filter.end())) - return; - - selectFirstVisibleRoom(); -} - -void -RoomList::selectFirstVisibleRoom() -{ - for (int i = 0; i < contentsLayout_->count(); i++) { - auto item = qobject_cast(contentsLayout_->itemAt(i)->widget()); - - if (item && item->isVisible()) { - highlightSelectedRoom(item->roomId()); - break; - } - } -} - -void -RoomList::paintEvent(QPaintEvent *) -{ - QStyleOption opt; - opt.init(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); -} - -void -RoomList::updateRoom(const QString &room_id, const RoomInfo &info) -{ - if (!roomExists(room_id)) { - if (info.is_invite) - addInvitedRoom(room_id, info); - else - addRoom(room_id, info); - - return; - } - - auto room = rooms_[room_id]; - updateAvatar(room_id, QString::fromStdString(info.avatar_url)); - room->setRoomName(QString::fromStdString(info.name)); - room->setRoomType(info.is_invite); - room->update(); -} - -void -RoomList::addInvitedRoom(const QString &room_id, const RoomInfo &info) -{ - auto room_item = new RoomInfoListItem(room_id, info, scrollArea_); - - connect(room_item, &RoomInfoListItem::acceptInvite, this, &RoomList::acceptInvite); - connect(room_item, &RoomInfoListItem::declineInvite, this, &RoomList::declineInvite); - - QSharedPointer roomWidget(room_item); - rooms_.emplace(room_id, roomWidget); - rooms_sort_cache_.push_back(roomWidget); - - updateAvatar(room_id, QString::fromStdString(info.avatar_url)); - - int pos = contentsLayout_->count() - 1; - contentsLayout_->insertWidget(pos, room_item); -} - -std::pair> -RoomList::firstRoom() const -{ - for (int i = 0; i < contentsLayout_->count(); i++) { - auto item = qobject_cast(contentsLayout_->itemAt(i)->widget()); - - if (item) { - auto topRoom = rooms_.find(item->roomId()); - if (topRoom != rooms_.end()) { - return std::pair>( - item->roomId(), topRoom->second); - } - } - } - - return {}; -} - -void -RoomList::updateReadStatus(const std::map &status) -{ - for (const auto &room : status) { - if (roomExists(room.first)) { - auto item = rooms_.at(room.first); - - if (item) - item->setReadState(room.second); - } - } -} diff --git a/src/RoomList.h b/src/RoomList.h deleted file mode 100644 index af792fd7..00000000 --- a/src/RoomList.h +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-FileCopyrightText: 2017 Konstantinos Sideris -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include -#include -#include -#include - -#include - -#include "CacheStructs.h" -#include "UserSettingsPage.h" - -class LeaveRoomDialog; -class OverlayModal; -class RoomInfoListItem; -class Sync; -struct DescInfo; -struct RoomInfo; - -class RoomList : public QWidget -{ - Q_OBJECT - -public: - explicit RoomList(QSharedPointer userSettings, QWidget *parent = nullptr); - - void initialize(const QMap &info); - void sync(const std::map &info); - - void clear() - { - rooms_.clear(); - rooms_sort_cache_.clear(); - }; - void updateAvatar(const QString &room_id, const QString &url); - - void addRoom(const QString &room_id, const RoomInfo &info); - void addInvitedRoom(const QString &room_id, const RoomInfo &info); - void removeRoom(const QString &room_id, bool reset); - //! Hide rooms that are not present in the given filter. - void applyFilter(const std::set &rooms); - //! Show all the available rooms. - void removeFilter(const std::set &roomsToHide); - void updateRoom(const QString &room_id, const RoomInfo &info); - void cleanupInvites(const QHash &invites); - -signals: - void roomChanged(const QString &room_id); - void totalUnreadMessageCountUpdated(int count); - void acceptInvite(const QString &room_id); - void declineInvite(const QString &room_id); - void roomAvatarChanged(const QString &room_id, const QString &img); - void joinRoom(const QString &room_id); - void updateRoomAvatarCb(const QString &room_id, const QString &img); - -public slots: - void updateRoomAvatar(const QString &roomid, const QString &img); - void highlightSelectedRoom(const QString &room_id); - void updateUnreadMessageCount(const QString &roomid, int count, int highlightedCount); - void updateRoomDescription(const QString &roomid, const DescInfo &info); - void closeJoinRoomDialog(bool isJoining, QString roomAlias); - void updateReadStatus(const std::map &status); - void nextRoom(); - void previousRoom(); - -protected: - void paintEvent(QPaintEvent *event) override; - void leaveEvent(QEvent *event) override; - -private slots: - void sortRoomsByLastMessage(); - -private: - //! Return the first non-null room. - std::pair> firstRoom() const; - void calculateUnreadMessageCount(); - bool roomExists(const QString &room_id) { return rooms_.find(room_id) != rooms_.end(); } - //! Select the first visible room in the room list. - void selectFirstVisibleRoom(); - - QVBoxLayout *topLayout_; - QVBoxLayout *contentsLayout_; - QScrollArea *scrollArea_; - QWidget *scrollAreaContents_; - - QPushButton *joinRoomButton_; - - OverlayModal *joinRoomModal_; - - std::map> rooms_; - std::vector> rooms_sort_cache_; - QString selectedRoom_; - - bool isSortPending_ = false; -}; diff --git a/src/popups/PopupItem.cpp b/src/popups/PopupItem.cpp deleted file mode 100644 index 2daa6143..00000000 --- a/src/popups/PopupItem.cpp +++ /dev/null @@ -1,89 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include -#include -#include -#include - -#include "../Utils.h" -#include "../ui/Avatar.h" -#include "PopupItem.h" - -constexpr int PopupHMargin = 4; -constexpr int PopupItemMargin = 3; - -PopupItem::PopupItem(QWidget *parent) - : QWidget(parent) - , avatar_{new Avatar(this, conf::popup::avatar)} - , hovering_{false} -{ - setMouseTracking(true); - setAttribute(Qt::WA_Hover); - - topLayout_ = new QHBoxLayout(this); - topLayout_->setContentsMargins( - PopupHMargin, PopupItemMargin, PopupHMargin, PopupItemMargin); - - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); -} - -void -PopupItem::paintEvent(QPaintEvent *) -{ - QStyleOption opt; - opt.init(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); - - if (underMouse() || hovering_) - p.fillRect(rect(), hoverColor_); -} - -RoomItem::RoomItem(QWidget *parent, const RoomSearchResult &res) - : PopupItem(parent) - , roomId_{QString::fromStdString(res.room_id)} -{ - auto name = QFontMetrics(QFont()).elidedText( - QString::fromStdString(res.info.name), Qt::ElideRight, parentWidget()->width() - 10); - - avatar_->setLetter(utils::firstChar(name)); - - roomName_ = new QLabel(name, this); - roomName_->setMargin(0); - - topLayout_->addWidget(avatar_); - topLayout_->addWidget(roomName_, 1); - - if (!res.info.avatar_url.empty()) - avatar_->setImage(QString::fromStdString(res.info.avatar_url)); -} - -void -RoomItem::updateItem(const RoomSearchResult &result) -{ - roomId_ = QString::fromStdString(std::move(result.room_id)); - - auto name = - QFontMetrics(QFont()).elidedText(QString::fromStdString(std::move(result.info.name)), - Qt::ElideRight, - parentWidget()->width() - 10); - - roomName_->setText(name); - - // if there is not an avatar set for the room, we want to at least show the letter - // correctly! - avatar_->setLetter(utils::firstChar(name)); - if (!result.info.avatar_url.empty()) - avatar_->setImage(QString::fromStdString(result.info.avatar_url)); -} - -void -RoomItem::mousePressEvent(QMouseEvent *event) -{ - if (event->buttons() != Qt::RightButton) - emit clicked(selectedText()); - - QWidget::mousePressEvent(event); -} diff --git a/src/popups/PopupItem.h b/src/popups/PopupItem.h deleted file mode 100644 index fc24915e..00000000 --- a/src/popups/PopupItem.h +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -#include "../AvatarProvider.h" -#include "../ChatPage.h" - -class Avatar; -struct SearchResult; -class QLabel; -class QHBoxLayout; - -class PopupItem : public QWidget -{ - Q_OBJECT - - Q_PROPERTY(QColor hoverColor READ hoverColor WRITE setHoverColor) - Q_PROPERTY(bool hovering READ hovering WRITE setHovering) - -public: - PopupItem(QWidget *parent); - - QString selectedText() const { return QString(); } - QColor hoverColor() const { return hoverColor_; } - void setHoverColor(QColor &color) { hoverColor_ = color; } - - bool hovering() const { return hovering_; } - void setHovering(const bool hover) { hovering_ = hover; }; - -protected: - void paintEvent(QPaintEvent *event) override; - -signals: - void clicked(const QString &text); - -protected: - QHBoxLayout *topLayout_; - Avatar *avatar_; - QColor hoverColor_; - - //! Set if the item is currently being - //! hovered during tab completion (cycling). - bool hovering_; -}; - -class RoomItem : public PopupItem -{ - Q_OBJECT - -public: - RoomItem(QWidget *parent, const RoomSearchResult &res); - QString selectedText() const { return roomId_; } - void updateItem(const RoomSearchResult &res); - -protected: - void mousePressEvent(QMouseEvent *event) override; - -private: - QLabel *roomName_; - QString roomId_; - RoomSearchResult info_; -}; diff --git a/src/popups/SuggestionsPopup.cpp b/src/popups/SuggestionsPopup.cpp deleted file mode 100644 index 7b545d61..00000000 --- a/src/popups/SuggestionsPopup.cpp +++ /dev/null @@ -1,164 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include -#include -#include - -#include "../Config.h" -#include "../Utils.h" -#include "../ui/Avatar.h" -#include "../ui/DropShadow.h" -#include "ChatPage.h" -#include "PopupItem.h" -#include "SuggestionsPopup.h" - -SuggestionsPopup::SuggestionsPopup(QWidget *parent) - : QWidget(parent) -{ - setAttribute(Qt::WA_ShowWithoutActivating, true); - setWindowFlags(Qt::ToolTip | Qt::NoDropShadowWindowHint); - - layout_ = new QVBoxLayout(this); - layout_->setMargin(0); - layout_->setSpacing(0); -} - -QString -SuggestionsPopup::displayName(QString room, QString user) -{ - return cache::displayName(room, user); -} - -void -SuggestionsPopup::addRooms(const std::vector &rooms) -{ - if (rooms.empty()) { - hide(); - return; - } - - const int layoutCount = (int)layout_->count(); - const int roomCount = (int)rooms.size(); - - // Remove the extra widgets from the layout. - if (roomCount < layoutCount) - removeLayoutItemsAfter(roomCount - 1); - - for (int i = 0; i < roomCount; ++i) { - auto item = layout_->itemAt(i); - - // Create a new widget if there isn't already one in that - // layout position. - if (!item) { - auto room = new RoomItem(this, rooms.at(i)); - connect(room, &RoomItem::clicked, this, &SuggestionsPopup::itemSelected); - layout_->addWidget(room); - } else { - // Update the current widget with the new data. - auto room = qobject_cast(item->widget()); - if (room) - room->updateItem(rooms.at(i)); - } - } - - resetSelection(); - adjustSize(); - - resize(geometry().width(), 40 * (int)rooms.size()); - - selectNextSuggestion(); -} - -void -SuggestionsPopup::hoverSelection() -{ - resetHovering(); - setHovering(selectedItem_); - update(); -} - -void -SuggestionsPopup::selectHoveredSuggestion() -{ - const auto item = layout_->itemAt(selectedItem_); - if (!item) - return; - - const auto &widget = qobject_cast(item->widget()); - emit itemSelected(displayName(ChatPage::instance()->currentRoom(), widget->selectedText())); - - resetSelection(); -} - -void -SuggestionsPopup::selectNextSuggestion() -{ - selectedItem_++; - if (selectedItem_ >= layout_->count()) - selectFirstItem(); - - hoverSelection(); -} - -void -SuggestionsPopup::selectPreviousSuggestion() -{ - selectedItem_--; - if (selectedItem_ < 0) - selectLastItem(); - - hoverSelection(); -} - -void -SuggestionsPopup::resetHovering() -{ - for (int i = 0; i < layout_->count(); ++i) { - const auto item = qobject_cast(layout_->itemAt(i)->widget()); - - if (item) - item->setHovering(false); - } -} - -void -SuggestionsPopup::setHovering(int pos) -{ - const auto &item = layout_->itemAt(pos); - const auto &widget = qobject_cast(item->widget()); - - if (widget) - widget->setHovering(true); -} - -void -SuggestionsPopup::paintEvent(QPaintEvent *) -{ - QStyleOption opt; - opt.init(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); -} - -void -SuggestionsPopup::selectLastItem() -{ - selectedItem_ = layout_->count() - 1; -} - -void -SuggestionsPopup::removeLayoutItemsAfter(size_t startingPos) -{ - size_t posToRemove = layout_->count() - 1; - - QLayoutItem *item; - while (startingPos <= posToRemove && - (item = layout_->takeAt((int)posToRemove)) != nullptr) { - delete item->widget(); - delete item; - - posToRemove = layout_->count() - 1; - } -} diff --git a/src/popups/SuggestionsPopup.h b/src/popups/SuggestionsPopup.h deleted file mode 100644 index 281edddb..00000000 --- a/src/popups/SuggestionsPopup.h +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -#include "CacheStructs.h" - -class QVBoxLayout; -class QLayoutItem; - -class SuggestionsPopup : public QWidget -{ - Q_OBJECT - -public: - explicit SuggestionsPopup(QWidget *parent = nullptr); - - void selectHoveredSuggestion(); - -public slots: - void addRooms(const std::vector &rooms); - - //! Move to the next available suggestion item. - void selectNextSuggestion(); - //! Move to the previous available suggestion item. - void selectPreviousSuggestion(); - //! Remove hovering from all items. - void resetHovering(); - //! Set hovering to the item in the given layout position. - void setHovering(int pos); - -protected: - void paintEvent(QPaintEvent *event) override; - -signals: - void itemSelected(const QString &user); - -private: - QString displayName(QString roomid, QString userid); - void hoverSelection(); - void resetSelection() { selectedItem_ = -1; } - void selectFirstItem() { selectedItem_ = 0; } - void selectLastItem(); - void removeLayoutItemsAfter(size_t startingPos); - - QVBoxLayout *layout_; - - //! Counter for tab completion (cycling). - int selectedItem_ = -1; -}; diff --git a/src/popups/UserMentions.cpp b/src/popups/UserMentions.cpp deleted file mode 100644 index 56b57503..00000000 --- a/src/popups/UserMentions.cpp +++ /dev/null @@ -1,178 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include -#include -#include -#include -#include -#include -#include - -#include "Cache.h" -#include "ChatPage.h" -#include "EventAccessors.h" -#include "Logging.h" -#include "UserMentions.h" - -using namespace popups; - -UserMentions::UserMentions(QWidget *parent) - : QWidget{parent} -{ - setAttribute(Qt::WA_ShowWithoutActivating, true); - setWindowFlags(Qt::FramelessWindowHint | Qt::Popup); - - tab_layout_ = new QTabWidget(this); - - top_layout_ = new QVBoxLayout(this); - top_layout_->setSpacing(0); - top_layout_->setMargin(0); - - local_scroll_area_ = new QScrollArea(this); - local_scroll_area_->setWidgetResizable(true); - local_scroll_area_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - - local_scroll_widget_ = new QWidget(this); - local_scroll_widget_->setObjectName("local_scroll_widget"); - - all_scroll_area_ = new QScrollArea(this); - all_scroll_area_->setWidgetResizable(true); - all_scroll_area_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - - all_scroll_widget_ = new QWidget(this); - all_scroll_widget_->setObjectName("all_scroll_widget"); - - // Height of the typing display. - QFont f; - f.setPointSizeF(f.pointSizeF() * 0.9); - const int bottomMargin = QFontMetrics(f).height() + 6; - - local_scroll_layout_ = new QVBoxLayout(local_scroll_widget_); - local_scroll_layout_->setContentsMargins(4, 0, 15, bottomMargin); - local_scroll_layout_->setSpacing(0); - local_scroll_layout_->setObjectName("localscrollarea"); - - all_scroll_layout_ = new QVBoxLayout(all_scroll_widget_); - all_scroll_layout_->setContentsMargins(4, 0, 15, bottomMargin); - all_scroll_layout_->setSpacing(0); - all_scroll_layout_->setObjectName("allscrollarea"); - - local_scroll_area_->setWidget(local_scroll_widget_); - local_scroll_area_->setAlignment(Qt::AlignBottom); - - all_scroll_area_->setWidget(all_scroll_widget_); - all_scroll_area_->setAlignment(Qt::AlignBottom); - - tab_layout_->addTab(local_scroll_area_, tr("This Room")); - tab_layout_->addTab(all_scroll_area_, tr("All Rooms")); - top_layout_->addWidget(tab_layout_); - - setLayout(top_layout_); -} - -void -UserMentions::initializeMentions(const QMap ¬ifs) -{ - nhlog::ui()->debug("Initializing " + std::to_string(notifs.size()) + " notifications."); - - for (const auto &item : notifs) { - for (const auto ¬if : item.notifications) { - const auto event_id = - QString::fromStdString(mtx::accessors::event_id(notif.event)); - - try { - const auto room_id = QString::fromStdString(notif.room_id); - const auto user_id = - QString::fromStdString(mtx::accessors::sender(notif.event)); - const auto body = - QString::fromStdString(mtx::accessors::body(notif.event)); - - pushItem(event_id, - user_id, - body, - room_id, - ChatPage::instance()->currentRoom()); - - } catch (const lmdb::error &e) { - nhlog::db()->warn("error while sending desktop notification: {}", - e.what()); - } - } - } -} - -void -UserMentions::showPopup() -{ - for (auto widget : all_scroll_layout_->findChildren()) { - delete widget; - } - for (auto widget : local_scroll_layout_->findChildren()) { - delete widget; - } - - auto notifs = cache::getTimelineMentions(); - - initializeMentions(notifs); - show(); -} - -void -UserMentions::pushItem(const QString &event_id, - const QString &user_id, - const QString &body, - const QString &room_id, - const QString ¤t_room_id) -{ - (void)event_id; - (void)user_id; - (void)body; - (void)room_id; - (void)current_room_id; - // setUpdatesEnabled(false); - // - // // Add to the 'all' section - // TimelineItem *view_item = new TimelineItem( - // mtx::events::MessageType::Text, user_id, body, true, room_id, - // all_scroll_widget_); - // view_item->setEventId(event_id); - // view_item->hide(); - // - // all_scroll_layout_->addWidget(view_item); - // QTimer::singleShot(0, this, [view_item, this]() { - // view_item->show(); - // view_item->adjustSize(); - // setUpdatesEnabled(true); - // }); - // - // // if it matches the current room... add it to the current room as well. - // if (QString::compare(room_id, current_room_id, Qt::CaseInsensitive) == 0) { - // // Add to the 'local' section - // TimelineItem *local_view_item = new - // TimelineItem(mtx::events::MessageType::Text, - // user_id, - // body, - // true, - // room_id, - // local_scroll_widget_); - // local_view_item->setEventId(event_id); - // local_view_item->hide(); - // local_scroll_layout_->addWidget(local_view_item); - // - // QTimer::singleShot(0, this, [local_view_item]() { - // local_view_item->show(); - // local_view_item->adjustSize(); - // }); - // } -} - -void -UserMentions::paintEvent(QPaintEvent *) -{ - QStyleOption opt; - opt.init(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); -} diff --git a/src/popups/UserMentions.h b/src/popups/UserMentions.h deleted file mode 100644 index f0b662d8..00000000 --- a/src/popups/UserMentions.h +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -#include -#include -#include - -class QPaintEvent; -class QTabWidget; -class QScrollArea; -class QVBoxLayout; - -namespace popups { - -class UserMentions : public QWidget -{ - Q_OBJECT -public: - UserMentions(QWidget *parent = nullptr); - - void initializeMentions(const QMap ¬ifs); - void showPopup(); - -protected: - void paintEvent(QPaintEvent *) override; - -private: - void pushItem(const QString &event_id, - const QString &user_id, - const QString &body, - const QString &room_id, - const QString ¤t_room_id); - QTabWidget *tab_layout_; - QVBoxLayout *top_layout_; - QVBoxLayout *local_scroll_layout_; - QVBoxLayout *all_scroll_layout_; - - QScrollArea *local_scroll_area_; - QWidget *local_scroll_widget_; - - QScrollArea *all_scroll_area_; - QWidget *all_scroll_widget_; -}; -} diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp index a283d24e..c309daab 100644 --- a/src/timeline/InputBar.cpp +++ b/src/timeline/InputBar.cpp @@ -20,6 +20,7 @@ #include "Cache.h" #include "ChatPage.h" #include "CompletionProxyModel.h" +#include "Config.h" #include "Logging.h" #include "MainWindow.h" #include "MatrixClient.h" diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp index ad4177a4..9f926d2b 100644 --- a/src/timeline/RoomlistModel.cpp +++ b/src/timeline/RoomlistModel.cpp @@ -530,3 +530,33 @@ FilteredRoomlistModel::toggleTag(QString roomid, QString tag, bool on) }); } } + +void +FilteredRoomlistModel::nextRoom() +{ + auto r = currentRoom(); + + if (r) { + int idx = roomidToIndex(r->roomId()); + idx++; + if (idx < rowCount()) { + setCurrentRoom( + data(index(idx, 0), RoomlistModel::Roles::RoomId).toString()); + } + } +} + +void +FilteredRoomlistModel::previousRoom() +{ + auto r = currentRoom(); + + if (r) { + int idx = roomidToIndex(r->roomId()); + idx--; + if (idx > 0) { + setCurrentRoom( + data(index(idx, 0), RoomlistModel::Roles::RoomId).toString()); + } + } +} diff --git a/src/timeline/RoomlistModel.h b/src/timeline/RoomlistModel.h index 1c6fa833..d3e1e1f9 100644 --- a/src/timeline/RoomlistModel.h +++ b/src/timeline/RoomlistModel.h @@ -118,6 +118,9 @@ public slots: TimelineModel *currentRoom() const { return roomlistmodel->currentRoom(); } void setCurrentRoom(QString roomid) { roomlistmodel->setCurrentRoom(std::move(roomid)); } + void nextRoom(); + void previousRoom(); + signals: void currentRoomChanged();