From cd67046f6011f0838b5ed4621fb3ee9b846e63a0 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 21 May 2021 21:19:03 +0200 Subject: Make roomlist look nice --- src/ChatPage.cpp | 5 ----- 1 file changed, 5 deletions(-) (limited to 'src/ChatPage.cpp') diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index c5199ff1..58b76174 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -233,11 +233,6 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) room_list_, &RoomList::updateRoomDescription); - connect(room_list_, - SIGNAL(totalUnreadMessageCountUpdated(int)), - this, - SIGNAL(unreadMessages(int))); - connect( this, &ChatPage::updateGroupsInfo, communitiesList_, &CommunitiesList::setCommunities); -- cgit 1.5.1 From c290b0747f34a6f683365f93d64ce93dc4428ca8 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 24 May 2021 14:04:07 +0200 Subject: Reenable invites --- resources/qml/RoomList.qml | 56 ++++++++++++ src/Cache.cpp | 50 +++++++++-- src/Cache.h | 2 +- src/Cache_p.h | 3 +- src/ChatPage.cpp | 4 +- src/ChatPage.h | 2 +- src/RoomList.cpp | 2 +- src/RoomList.h | 2 +- src/timeline/RoomlistModel.cpp | 169 ++++++++++++++++++++++++++++------- src/timeline/RoomlistModel.h | 8 +- src/timeline/TimelineViewManager.cpp | 4 +- src/timeline/TimelineViewManager.h | 2 +- src/ui/Theme.cpp | 3 + src/ui/Theme.h | 4 +- 14 files changed, 260 insertions(+), 51 deletions(-) (limited to 'src/ChatPage.cpp') diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index 40669eda..e9bb351f 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -141,6 +141,8 @@ Page { RowLayout { Layout.fillWidth: true spacing: 0 + visible: !model.isInvite + height: visible ? 0 : undefined ElidedLabel { color: roomItem.unimportantText @@ -182,6 +184,60 @@ Page { } + RowLayout { + Layout.fillWidth: true + spacing: Nheko.paddingMedium + visible: model.isInvite + enabled: visible + height: visible ? 0 : undefined + + ElidedLabel { + elideWidth: textContent.width / 2 - 2 * Nheko.paddingMedium + fullText: qsTr("Accept") + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + leftPadding: Nheko.paddingMedium + rightPadding: Nheko.paddingMedium + color: Nheko.colors.brightText + + TapHandler { + onSingleTapped: Rooms.acceptInvite(model.roomId) + } + + background: Rectangle { + color: Nheko.theme.alternateButton + radius: height / 2 + } + + } + + ElidedLabel { + Layout.alignment: Qt.AlignRight + elideWidth: textContent.width / 2 - 2 * Nheko.paddingMedium + fullText: qsTr("Decline") + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + leftPadding: Nheko.paddingMedium + rightPadding: Nheko.paddingMedium + color: Nheko.colors.brightText + + TapHandler { + onSingleTapped: Rooms.declineInvite(model.roomId) + } + + background: Rectangle { + color: Nheko.theme.alternateButton + radius: height / 2 + } + + } + + Item { + Layout.fillWidth: true + } + + } + } } diff --git a/src/Cache.cpp b/src/Cache.cpp index c41b66cc..4a99dd59 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -2045,21 +2045,57 @@ Cache::getLastMessageInfo(lmdb::txn &txn, const std::string &room_id) return fallbackDesc; } -std::map +QHash Cache::invites() { - std::map result; + QHash result; auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); auto cursor = lmdb::cursor::open(txn, invitesDb_); - std::string_view room_id, unused; + std::string_view room_id, room_data; - while (cursor.get(room_id, unused, MDB_NEXT)) - result.emplace(QString::fromStdString(std::string(room_id)), true); + while (cursor.get(room_id, room_data, MDB_NEXT)) { + try { + RoomInfo tmp = json::parse(room_data); + tmp.member_count = getInviteMembersDb(txn, std::string(room_id)).size(txn); + result.insert(QString::fromStdString(std::string(room_id)), std::move(tmp)); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse room info for invite: " + "room_id ({}), {}: {}", + room_id, + std::string(room_data), + e.what()); + } + } cursor.close(); - txn.commit(); + + return result; +} + +std::optional +Cache::invite(std::string_view roomid) +{ + std::optional result; + + auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); + + std::string_view room_data; + + if (invitesDb_.get(txn, roomid, room_data)) { + try { + RoomInfo tmp = json::parse(room_data); + tmp.member_count = getInviteMembersDb(txn, std::string(roomid)).size(txn); + result = std::move(tmp); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse room info for invite: " + "room_id ({}), {}: {}", + roomid, + std::string(room_data), + e.what()); + } + } return result; } @@ -4064,7 +4100,7 @@ roomInfo(bool withInvites) { return instance_->roomInfo(withInvites); } -std::map +QHash invites() { return instance_->invites(); diff --git a/src/Cache.h b/src/Cache.h index 427dbafc..74ec9695 100644 --- a/src/Cache.h +++ b/src/Cache.h @@ -62,7 +62,7 @@ joinedRooms(); QMap roomInfo(bool withInvites = true); -std::map +QHash invites(); //! Calculate & return the name of the room. diff --git a/src/Cache_p.h b/src/Cache_p.h index c55fa601..f2911622 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -70,7 +70,8 @@ public: QMap roomInfo(bool withInvites = true); std::optional getRoomAliases(const std::string &roomid); - std::map invites(); + QHash invites(); + std::optional invite(std::string_view roomid); //! Calculate & return the name of the room. QString getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb); diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 58b76174..166c03ec 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -313,7 +313,7 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) connect(this, &ChatPage::initializeEmptyViews, view_manager_, - &TimelineViewManager::initWithMessages); + &TimelineViewManager::initializeRoomlist); connect(this, &ChatPage::initializeMentions, user_mentions_popup_, @@ -554,7 +554,7 @@ ChatPage::loadStateFromCache() try { olm::client()->load(cache::restoreOlmAccount(), STORAGE_SECRET_KEY); - emit initializeEmptyViews(cache::client()->roomIds()); + emit initializeEmptyViews(); emit initializeRoomList(cache::roomInfo()); emit initializeMentions(cache::getTimelineMentions()); emit syncTags(cache::roomInfo().toStdMap()); diff --git a/src/ChatPage.h b/src/ChatPage.h index 84e7cdff..eb60047d 100644 --- a/src/ChatPage.h +++ b/src/ChatPage.h @@ -147,7 +147,7 @@ signals: void initializeRoomList(QMap); void initializeViews(const mtx::responses::Rooms &rooms); - void initializeEmptyViews(const std::vector &roomIds); + void initializeEmptyViews(); void initializeMentions(const QMap ¬ifs); void syncUI(const mtx::responses::Rooms &rooms); void syncRoomlist(const std::map &updates); diff --git a/src/RoomList.cpp b/src/RoomList.cpp index 5c41a7a1..5839c4a0 100644 --- a/src/RoomList.cpp +++ b/src/RoomList.cpp @@ -183,7 +183,7 @@ RoomList::initialize(const QMap &info) } void -RoomList::cleanupInvites(const std::map &invites) +RoomList::cleanupInvites(const QHash &invites) { if (invites.size() == 0) return; diff --git a/src/RoomList.h b/src/RoomList.h index 74152c55..af792fd7 100644 --- a/src/RoomList.h +++ b/src/RoomList.h @@ -48,7 +48,7 @@ public: //! Show all the available rooms. void removeFilter(const std::set &roomsToHide); void updateRoom(const QString &room_id, const RoomInfo &info); - void cleanupInvites(const std::map &invites); + void cleanupInvites(const QHash &invites); signals: void roomChanged(const QString &room_id); diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp index 28c3cf46..f3d4dad7 100644 --- a/src/timeline/RoomlistModel.cpp +++ b/src/timeline/RoomlistModel.cpp @@ -57,31 +57,64 @@ RoomlistModel::data(const QModelIndex &index, int role) const { if (index.row() >= 0 && static_cast(index.row()) < roomids.size()) { auto roomid = roomids.at(index.row()); - auto room = models.value(roomid); - switch (role) { - case Roles::AvatarUrl: - return room->roomAvatarUrl(); - case Roles::RoomName: - return room->plainRoomName(); - case Roles::RoomId: - return room->roomId(); - case Roles::LastMessage: - return room->lastMessage().body; - case Roles::Time: - return room->lastMessage().descriptiveTime; - case Roles::Timestamp: - return QVariant(static_cast(room->lastMessage().timestamp)); - case Roles::HasUnreadMessages: - return this->roomReadStatus.count(roomid) && - this->roomReadStatus.at(roomid); - case Roles::HasLoudNotification: - return room->hasMentions(); - case Roles::NotificationCount: - return room->notificationCount(); - case Roles::IsInvite: - case Roles::IsSpace: - return false; - default: + + if (models.contains(roomid)) { + auto room = models.value(roomid); + switch (role) { + case Roles::AvatarUrl: + return room->roomAvatarUrl(); + case Roles::RoomName: + return room->plainRoomName(); + case Roles::RoomId: + return room->roomId(); + case Roles::LastMessage: + return room->lastMessage().body; + case Roles::Time: + return room->lastMessage().descriptiveTime; + case Roles::Timestamp: + return QVariant( + static_cast(room->lastMessage().timestamp)); + case Roles::HasUnreadMessages: + return this->roomReadStatus.count(roomid) && + this->roomReadStatus.at(roomid); + case Roles::HasLoudNotification: + return room->hasMentions(); + case Roles::NotificationCount: + return room->notificationCount(); + case Roles::IsInvite: + case Roles::IsSpace: + return false; + default: + return {}; + } + } else if (invites.contains(roomid)) { + auto room = invites.value(roomid); + switch (role) { + case Roles::AvatarUrl: + return QString::fromStdString(room.avatar_url); + case Roles::RoomName: + return QString::fromStdString(room.name); + case Roles::RoomId: + return roomid; + case Roles::LastMessage: + return room.msgInfo.body; + case Roles::Time: + return room.msgInfo.descriptiveTime; + case Roles::Timestamp: + return QVariant(static_cast(room.msgInfo.timestamp)); + case Roles::HasUnreadMessages: + case Roles::HasLoudNotification: + return false; + case Roles::NotificationCount: + return 0; + case Roles::IsInvite: + return true; + case Roles::IsSpace: + return false; + default: + return {}; + } + } else { return {}; } } else { @@ -109,7 +142,7 @@ RoomlistModel::updateReadStatus(const std::map roomReadStatus_) Roles::HasUnreadMessages, }); } -}; +} void RoomlistModel::addRoom(const QString &room_id, bool suppressInsertNotification) { @@ -186,11 +219,21 @@ RoomlistModel::addRoom(const QString &room_id, bool suppressInsertNotification) newRoom->updateLastMessage(); - if (!suppressInsertNotification) + bool wasInvite = invites.contains(room_id); + if (!suppressInsertNotification && !wasInvite) beginInsertRows(QModelIndex(), (int)roomids.size(), (int)roomids.size()); + models.insert(room_id, std::move(newRoom)); - roomids.push_back(room_id); - if (!suppressInsertNotification) + + if (wasInvite) { + auto idx = roomidToIndex(room_id); + invites.remove(room_id); + emit dataChanged(index(idx), index(idx)); + } else { + roomids.push_back(room_id); + } + + if (!suppressInsertNotification && !wasInvite) endInsertRows(); } } @@ -234,20 +277,50 @@ RoomlistModel::sync(const mtx::responses::Rooms &rooms) if (idx != -1) { beginRemoveRows(QModelIndex(), idx, idx); roomids.erase(roomids.begin() + idx); - models.remove(QString::fromStdString(room_id)); + if (models.contains(QString::fromStdString(room_id))) + models.remove(QString::fromStdString(room_id)); + else if (invites.contains(QString::fromStdString(room_id))) + invites.remove(QString::fromStdString(room_id)); endRemoveRows(); } } + + for (const auto &[room_id, room] : rooms.invite) { + (void)room_id; + auto qroomid = QString::fromStdString(room_id); + + auto invite = cache::client()->invite(room_id); + if (!invite) + continue; + + if (invites.contains(qroomid)) { + invites[qroomid] = *invite; + auto idx = roomidToIndex(qroomid); + emit dataChanged(index(idx), index(idx)); + } else { + beginInsertRows(QModelIndex(), (int)roomids.size(), (int)roomids.size()); + invites.insert(qroomid, *invite); + roomids.push_back(std::move(qroomid)); + endInsertRows(); + } + } } void -RoomlistModel::initializeRooms(const std::vector &roomIds_) +RoomlistModel::initializeRooms() { beginResetModel(); models.clear(); roomids.clear(); - for (const auto &id : roomIds_) + invites.clear(); + + invites = cache::client()->invites(); + for (const auto &id : invites.keys()) + roomids.push_back(id); + + for (const auto &id : cache::client()->roomIds()) addRoom(id, true); + endResetModel(); } @@ -256,10 +329,42 @@ RoomlistModel::clear() { beginResetModel(); models.clear(); + invites.clear(); roomids.clear(); endResetModel(); } +void +RoomlistModel::acceptInvite(QString roomid) +{ + if (invites.contains(roomid)) { + auto idx = roomidToIndex(roomid); + + if (idx != -1) { + beginRemoveRows(QModelIndex(), idx, idx); + roomids.erase(roomids.begin() + idx); + invites.remove(roomid); + endRemoveRows(); + ChatPage::instance()->joinRoom(roomid); + } + } +} +void +RoomlistModel::declineInvite(QString roomid) +{ + if (invites.contains(roomid)) { + auto idx = roomidToIndex(roomid); + + if (idx != -1) { + beginRemoveRows(QModelIndex(), idx, idx); + roomids.erase(roomids.begin() + idx); + invites.remove(roomid); + endRemoveRows(); + ChatPage::instance()->leaveRoom(roomid); + } + } +} + namespace { enum NotificationImportance : short { diff --git a/src/timeline/RoomlistModel.h b/src/timeline/RoomlistModel.h index c3374bd2..ff85614c 100644 --- a/src/timeline/RoomlistModel.h +++ b/src/timeline/RoomlistModel.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include @@ -51,7 +52,7 @@ public: } public slots: - void initializeRooms(const std::vector &roomids); + void initializeRooms(); void sync(const mtx::responses::Rooms &rooms); void clear(); int roomidToIndex(QString roomid) @@ -63,6 +64,8 @@ public slots: return -1; } + void acceptInvite(QString roomid); + void declineInvite(QString roomid); private slots: void updateReadStatus(const std::map roomReadStatus_); @@ -75,6 +78,7 @@ private: TimelineViewManager *manager = nullptr; std::vector roomids; + QHash invites; QHash> models; std::map roomReadStatus; @@ -94,6 +98,8 @@ public slots: return mapFromSource(roomlistmodel->index(roomlistmodel->roomidToIndex(roomid))) .row(); } + void acceptInvite(QString roomid) { roomlistmodel->acceptInvite(roomid); } + void declineInvite(QString roomid) { roomlistmodel->declineInvite(roomid); } private: short int calculateImportance(const QModelIndex &idx) const; diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index c84e0df8..9fa7f8b6 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -499,9 +499,9 @@ TimelineViewManager::receivedSessionKey(const std::string &room_id, const std::s } void -TimelineViewManager::initWithMessages(const std::vector &roomIds) +TimelineViewManager::initializeRoomlist() { - rooms->initializeRooms(roomIds); + rooms->initializeRooms(); } void diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 609f5a4a..37e50804 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -100,7 +100,7 @@ signals: public slots: void updateReadReceipts(const QString &room_id, const std::vector &event_ids); void receivedSessionKey(const std::string &room_id, const std::string &session_id); - void initWithMessages(const std::vector &roomIds); + void initializeRoomlist(); void chatFocusChanged(bool focused) { isWindowFocused_ = focused; diff --git a/src/ui/Theme.cpp b/src/ui/Theme.cpp index b6c9579a..26119393 100644 --- a/src/ui/Theme.cpp +++ b/src/ui/Theme.cpp @@ -60,12 +60,15 @@ Theme::Theme(std::string_view theme) separator_ = p.mid().color(); if (theme == "light") { sidebarBackground_ = QColor("#233649"); + alternateButton_ = QColor("#ccc"); red_ = QColor("#a82353"); } else if (theme == "dark") { sidebarBackground_ = QColor("#2d3139"); + alternateButton_ = QColor("#414A59"); red_ = QColor("#a82353"); } else { sidebarBackground_ = p.window().color(); + alternateButton_ = p.dark().color(); red_ = QColor("red"); } } diff --git a/src/ui/Theme.h b/src/ui/Theme.h index 834571c0..b5bcd4dd 100644 --- a/src/ui/Theme.h +++ b/src/ui/Theme.h @@ -65,6 +65,7 @@ class Theme : public QPalette { Q_GADGET Q_PROPERTY(QColor sidebarBackground READ sidebarBackground CONSTANT) + Q_PROPERTY(QColor alternateButton READ alternateButton CONSTANT) Q_PROPERTY(QColor separator READ separator CONSTANT) Q_PROPERTY(QColor red READ red CONSTANT) public: @@ -73,9 +74,10 @@ public: static QPalette paletteFromTheme(std::string_view theme); QColor sidebarBackground() const { return sidebarBackground_; } + QColor alternateButton() const { return alternateButton_; } QColor separator() const { return separator_; } QColor red() const { return red_; } private: - QColor sidebarBackground_, separator_, red_; + QColor sidebarBackground_, separator_, red_, alternateButton_; }; -- cgit 1.5.1 From 298822baeaffdc83386e003099e34819bcd7d18c Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 28 May 2021 22:14:59 +0200 Subject: Move currentRoom/timeline handling to roomlist --- resources/qml/ChatPage.qml | 1 + resources/qml/Completer.qml | 6 +- resources/qml/ForwardCompleter.qml | 4 +- resources/qml/MessageInput.qml | 62 +++++----- resources/qml/MessageView.qml | 140 ++++++++++++++++++++- resources/qml/QuickSwitcher.qml | 3 +- resources/qml/Reactions.qml | 2 +- resources/qml/ReplyPopup.qml | 2 - resources/qml/RoomList.qml | 21 +++- resources/qml/Root.qml | 147 ----------------------- resources/qml/StatusIndicator.qml | 2 +- resources/qml/TimelineView.qml | 22 +++- resources/qml/TopBar.qml | 14 +-- resources/qml/TypingIndicator.qml | 2 - resources/qml/delegates/FileMessage.qml | 2 +- resources/qml/delegates/MessageDelegate.qml | 10 +- resources/qml/delegates/PlayableMediaMessage.qml | 4 +- resources/qml/emoji/EmojiButton.qml | 2 +- resources/qml/voip/ActiveCallBar.qml | 2 +- resources/qml/voip/CallInviteBar.qml | 2 +- resources/qml/voip/PlaceCall.qml | 12 +- resources/qml/voip/ScreenShare.qml | 4 +- src/ChatPage.cpp | 7 +- src/timeline/InputBar.cpp | 35 +++++- src/timeline/InputBar.h | 1 + src/timeline/RoomlistModel.cpp | 18 +++ src/timeline/RoomlistModel.h | 16 ++- src/timeline/TimelineViewManager.cpp | 125 ++++++------------- src/timeline/TimelineViewManager.h | 26 +--- src/ui/NhekoDropArea.cpp | 2 +- 30 files changed, 350 insertions(+), 346 deletions(-) (limited to 'src/ChatPage.cpp') diff --git a/resources/qml/ChatPage.qml b/resources/qml/ChatPage.qml index fc6137a6..966f169b 100644 --- a/resources/qml/ChatPage.qml +++ b/resources/qml/ChatPage.qml @@ -31,6 +31,7 @@ Rectangle { TimelineView { id: timeline + room: Rooms.currentRoom SplitView.fillWidth: true SplitView.minimumWidth: 400 diff --git a/resources/qml/Completer.qml b/resources/qml/Completer.qml index 2609371b..0cdd789d 100644 --- a/resources/qml/Completer.qml +++ b/resources/qml/Completer.qml @@ -70,7 +70,7 @@ Popup { onCompleterNameChanged: { if (completerName) { if (completerName == "user") - completer = TimelineManager.completerFor(completerName, TimelineManager.timeline.roomId()); + completer = TimelineManager.completerFor(completerName, room.roomId()); else completer = TimelineManager.completerFor(completerName); completer.setSearchString(""); @@ -83,8 +83,8 @@ Popup { height: listView.contentHeight + 2 // + 2 for the padding on top and bottom Connections { - onTimelineChanged: completer = null - target: TimelineManager + onRoomChanged: completer = null + target: timelineView } ListView { diff --git a/resources/qml/ForwardCompleter.qml b/resources/qml/ForwardCompleter.qml index 1ec18540..eee3879c 100644 --- a/resources/qml/ForwardCompleter.qml +++ b/resources/qml/ForwardCompleter.qml @@ -50,7 +50,7 @@ Popup { Reply { id: replyPreview - modelData: TimelineManager.timeline ? TimelineManager.timeline.getDump(mid, "") : { + modelData: room ? room.getDump(mid, "") : { } userColor: TimelineManager.userColor(modelData.userId, Nheko.colors.window) } @@ -95,7 +95,7 @@ Popup { Connections { onCompletionSelected: { - TimelineManager.timeline.forwardMessage(messageContextMenu.eventId, id); + room.forwardMessage(messageContextMenu.eventId, id); forwardMessagePopup.close(); } onCountChanged: { diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml index f4e253ad..24f9b0e8 100644 --- a/resources/qml/MessageInput.qml +++ b/resources/qml/MessageInput.qml @@ -28,7 +28,7 @@ Rectangle { RowLayout { id: row - visible: (TimelineManager.timeline ? TimelineManager.timeline.permissions.canSend(MtxEvent.TextMessage) : false) || messageContextMenu.isSender + visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false anchors.fill: parent ImageButton { @@ -43,7 +43,7 @@ Rectangle { ToolTip.text: CallManager.isOnCall ? qsTr("Hang up") : qsTr("Place a call") Layout.margins: 8 onClicked: { - if (TimelineManager.timeline) { + if (room) { if (CallManager.haveCallInvite) { return ; } else if (CallManager.isOnCall) { @@ -63,14 +63,14 @@ Rectangle { height: 22 image: ":/icons/icons/ui/paper-clip-outline.png" Layout.margins: 8 - onClicked: TimelineManager.timeline.input.openFileSelection() + onClicked: room.input.openFileSelection() ToolTip.visible: hovered ToolTip.text: qsTr("Send a file") Rectangle { anchors.fill: parent color: Nheko.colors.window - visible: TimelineManager.timeline && TimelineManager.timeline.input.uploading + visible: room && room.input.uploading NhekoBusyIndicator { anchors.fill: parent @@ -123,16 +123,16 @@ Rectangle { padding: 8 focus: true onTextChanged: { - if (TimelineManager.timeline) - TimelineManager.timeline.input.updateState(selectionStart, selectionEnd, cursorPosition, text); + if (room) + room.input.updateState(selectionStart, selectionEnd, cursorPosition, text); forceActiveFocus(); } onCursorPositionChanged: { - if (!TimelineManager.timeline) + if (!room) return ; - TimelineManager.timeline.input.updateState(selectionStart, selectionEnd, cursorPosition, text); + room.input.updateState(selectionStart, selectionEnd, cursorPosition, text); if (cursorPosition <= completerTriggeredAt) { completerTriggeredAt = -1; popup.close(); @@ -141,13 +141,13 @@ Rectangle { popup.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition)); } - onSelectionStartChanged: TimelineManager.timeline.input.updateState(selectionStart, selectionEnd, cursorPosition, text) - onSelectionEndChanged: TimelineManager.timeline.input.updateState(selectionStart, selectionEnd, cursorPosition, text) + onSelectionStartChanged: room.input.updateState(selectionStart, selectionEnd, cursorPosition, text) + onSelectionEndChanged: room.input.updateState(selectionStart, selectionEnd, cursorPosition, text) // Ensure that we get escape key press events first. Keys.onShortcutOverride: event.accepted = (completerTriggeredAt != -1 && (event.key === Qt.Key_Escape || event.key === Qt.Key_Tab || event.key === Qt.Key_Enter)) Keys.onPressed: { if (event.matches(StandardKey.Paste)) { - TimelineManager.timeline.input.paste(false); + room.input.paste(false); event.accepted = true; } else if (event.key == Qt.Key_Space) { // close popup if user enters space after colon @@ -160,9 +160,9 @@ Rectangle { } else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_U) { messageInput.clear(); } else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_P) { - messageInput.text = TimelineManager.timeline.input.previousText(); + messageInput.text = room.input.previousText(); } else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_N) { - messageInput.text = TimelineManager.timeline.input.nextText(); + messageInput.text = room.input.nextText(); } else if (event.key == Qt.Key_At) { messageInput.openCompleter(cursorPosition, "user"); popup.open(); @@ -188,7 +188,7 @@ Rectangle { return ; } } - TimelineManager.timeline.input.send(); + room.input.send(); event.accepted = true; } else if (event.key == Qt.Key_Tab) { event.accepted = true; @@ -223,11 +223,11 @@ Rectangle { } else if (event.key == Qt.Key_Up && event.modifiers == Qt.NoModifier) { if (cursorPosition == 0) { event.accepted = true; - var idx = TimelineManager.timeline.edit ? TimelineManager.timeline.idToIndex(TimelineManager.timeline.edit) + 1 : 0; + var idx = room.edit ? room.idToIndex(room.edit) + 1 : 0; while (true) { - var id = TimelineManager.timeline.indexToId(idx); - if (!id || TimelineManager.timeline.getDump(id, "").isEditable) { - TimelineManager.timeline.edit = id; + var id = room.indexToId(idx); + if (!id || room.getDump(id, "").isEditable) { + room.edit = id; cursorPosition = 0; Qt.callLater(positionCursorAtEnd); break; @@ -239,13 +239,13 @@ Rectangle { positionCursorAtStart(); } } else if (event.key == Qt.Key_Down && event.modifiers == Qt.NoModifier) { - if (cursorPosition == messageInput.length && TimelineManager.timeline.edit) { + if (cursorPosition == messageInput.length && room.edit) { event.accepted = true; - var idx = TimelineManager.timeline.idToIndex(TimelineManager.timeline.edit) - 1; + var idx = room.idToIndex(room.edit) - 1; while (true) { - var id = TimelineManager.timeline.indexToId(idx); - if (!id || TimelineManager.timeline.getDump(id, "").isEditable) { - TimelineManager.timeline.edit = id; + var id = room.indexToId(idx); + if (!id || room.getDump(id, "").isEditable) { + room.edit = id; Qt.callLater(positionCursorAtStart); break; } @@ -260,14 +260,14 @@ Rectangle { background: null Connections { - onActiveTimelineChanged: { + onRoomChanged: { messageInput.clear(); - messageInput.append(TimelineManager.timeline.input.text()); + messageInput.append(room.input.text()); messageInput.completerTriggeredAt = -1; popup.completerName = ""; messageInput.forceActiveFocus(); } - target: TimelineManager + target: timelineView } Connections { @@ -292,14 +292,14 @@ Rectangle { messageInput.text = newText; messageInput.cursorPosition = newText.length; } - target: TimelineManager.timeline ? TimelineManager.timeline.input : null + target: room ? room.input : null } Connections { ignoreUnknownSignals: true onReplyChanged: messageInput.forceActiveFocus() onEditChanged: messageInput.forceActiveFocus() - target: TimelineManager.timeline + target: room } Connections { @@ -312,7 +312,7 @@ Rectangle { anchors.fill: parent acceptedButtons: Qt.MiddleButton cursorShape: Qt.IBeamCursor - onClicked: TimelineManager.timeline.input.paste(true) + onClicked: room.input.paste(true) } } @@ -347,7 +347,7 @@ Rectangle { ToolTip.visible: hovered ToolTip.text: qsTr("Send") onClicked: { - TimelineManager.timeline.input.send(); + room.input.send(); } } @@ -355,7 +355,7 @@ Rectangle { Text { anchors.centerIn: parent - visible: TimelineManager.timeline ? (!TimelineManager.timeline.permissions.canSend(MtxEvent.TextMessage)) : false + visible: room ? (!room.permissions.canSend(MtxEvent.TextMessage)) : false text: qsTr("You don't have permission to send messages in this room") color: Nheko.colors.text } diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml index 5af4e4de..176905db 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml @@ -4,6 +4,7 @@ import "./delegates" import "./emoji" +import Qt.labs.platform 1.1 as Platform import QtGraphicalEffects 1.0 import QtQuick 2.12 import QtQuick.Controls 2.3 @@ -22,7 +23,7 @@ ScrollView { property int delegateMaxWidth: ((Settings.timelineMaxWidth > 100 && Settings.timelineMaxWidth < parent.availableWidth) ? Settings.timelineMaxWidth : parent.availableWidth) - parent.padding * 2 - model: TimelineManager.timeline + model: room boundsBehavior: Flickable.StopAtBounds pixelAligned: true spacing: 4 @@ -413,4 +414,141 @@ ScrollView { } + Platform.Menu { + id: messageContextMenu + + property string eventId + property string link + property string text + property int eventType + property bool isEncrypted + property bool isEditable + property bool isSender + + function show(eventId_, eventType_, isSender_, isEncrypted_, isEditable_, link_, text_, showAt_) { + eventId = eventId_; + eventType = eventType_; + isEncrypted = isEncrypted_; + isEditable = isEditable_; + isSender = isSender_; + if (text_) + text = text_; + else + text = ""; + if (link_) + link = link_; + else + link = ""; + if (showAt_) + open(showAt_); + else + open(); + } + + Platform.MenuItem { + visible: messageContextMenu.text + enabled: visible + text: qsTr("Copy") + onTriggered: Clipboard.text = messageContextMenu.text + } + + Platform.MenuItem { + visible: messageContextMenu.link + enabled: visible + text: qsTr("Copy link location") + onTriggered: Clipboard.text = messageContextMenu.link + } + + Platform.MenuItem { + id: reactionOption + + visible: room ? room.permissions.canSend(MtxEvent.Reaction) : false + text: qsTr("React") + onTriggered: emojiPopup.show(null, function(emoji) { + room.input.reaction(messageContextMenu.eventId, emoji); + }) + } + + Platform.MenuItem { + visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false + text: qsTr("Reply") + onTriggered: room.replyAction(messageContextMenu.eventId) + } + + Platform.MenuItem { + visible: messageContextMenu.isEditable && (room ? room.permissions.canSend(MtxEvent.TextMessage) : false) + enabled: visible + text: qsTr("Edit") + onTriggered: room.editAction(messageContextMenu.eventId) + } + + Platform.MenuItem { + text: qsTr("Read receipts") + onTriggered: room.readReceiptsAction(messageContextMenu.eventId) + } + + Platform.MenuItem { + visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker || messageContextMenu.eventType == MtxEvent.TextMessage || messageContextMenu.eventType == MtxEvent.LocationMessage || messageContextMenu.eventType == MtxEvent.EmoteMessage || messageContextMenu.eventType == MtxEvent.NoticeMessage + text: qsTr("Forward") + onTriggered: { + var forwardMess = forwardCompleterComponent.createObject(timelineRoot); + forwardMess.setMessageEventId(messageContextMenu.eventId); + forwardMess.open(); + } + } + + Platform.MenuItem { + text: qsTr("Mark as read") + } + + Platform.MenuItem { + text: qsTr("View raw message") + onTriggered: room.viewRawMessage(messageContextMenu.eventId) + } + + Platform.MenuItem { + // TODO(Nico): Fix this still being iterated over, when using keyboard to select options + visible: messageContextMenu.isEncrypted + enabled: visible + text: qsTr("View decrypted raw message") + onTriggered: room.viewDecryptedRawMessage(messageContextMenu.eventId) + } + + Platform.MenuItem { + visible: (room ? room.permissions.canRedact() : false) || messageContextMenu.isSender + text: qsTr("Remove message") + onTriggered: room.redactEvent(messageContextMenu.eventId) + } + + Platform.MenuItem { + visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker + enabled: visible + text: qsTr("Save as") + onTriggered: room.saveMedia(messageContextMenu.eventId) + } + + Platform.MenuItem { + visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker + enabled: visible + text: qsTr("Open in external program") + onTriggered: room.openMedia(messageContextMenu.eventId) + } + + Platform.MenuItem { + visible: messageContextMenu.eventId + enabled: visible + text: qsTr("Copy link to event") + onTriggered: room.copyLinkToEvent(messageContextMenu.eventId) + } + + } + + Component { + id: forwardCompleterComponent + + ForwardCompleter { + } + + } + } diff --git a/resources/qml/QuickSwitcher.qml b/resources/qml/QuickSwitcher.qml index a6373b1c..8c4f47ca 100644 --- a/resources/qml/QuickSwitcher.qml +++ b/resources/qml/QuickSwitcher.qml @@ -72,8 +72,7 @@ Popup { Connections { onCompletionSelected: { - TimelineManager.setHistoryView(id); - TimelineManager.highlightRoom(id); + Rooms.setCurrentRoom(id); quickSwitcher.close(); } onCountChanged: { diff --git a/resources/qml/Reactions.qml b/resources/qml/Reactions.qml index 064df543..def87f75 100644 --- a/resources/qml/Reactions.qml +++ b/resources/qml/Reactions.qml @@ -35,7 +35,7 @@ Flow { ToolTip.text: modelData.users onClicked: { console.debug("Picked " + modelData.key + "in response to " + reactionFlow.eventId + ". selfReactedEvent: " + modelData.selfReactedEvent); - TimelineManager.queueReactionMessage(reactionFlow.eventId, modelData.key); + room.input.reaction(reactionFlow.eventId, modelData.key); } contentItem: Row { diff --git a/resources/qml/ReplyPopup.qml b/resources/qml/ReplyPopup.qml index 1d85acb0..0de68fe8 100644 --- a/resources/qml/ReplyPopup.qml +++ b/resources/qml/ReplyPopup.qml @@ -11,8 +11,6 @@ import im.nheko 1.0 Rectangle { id: replyPopup - property var room: TimelineManager.timeline - Layout.fillWidth: true visible: room && (room.reply || room.edit) // Height of child, plus margins, plus border diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index b184aef0..c5e07032 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -149,7 +149,7 @@ Page { }, State { name: "selected" - when: TimelineManager.timeline && model.roomId == TimelineManager.timeline.roomId() + when: Rooms.currentRoom && model.roomId == Rooms.currentRoom.roomId() PropertyChanges { target: roomItem @@ -165,16 +165,25 @@ Page { TapHandler { acceptedButtons: Qt.RightButton - onSingleTapped: roomContextMenu.show(model.roomId, model.tags) + onSingleTapped: { + if (!TimelineManager.isInvite) { + roomContextMenu.show(model.roomId, model.tags); + } + } gesturePolicy: TapHandler.ReleaseWithinBounds } - HoverHandler { - id: hovered + TapHandler { + onSingleTapped: Rooms.setCurrentRoom(model.roomId) + onLongPressed: { + if (!TimelineManager.isInvite) { + roomContextMenu.show(model.roomId, model.tags); + } + } } - TapHandler { - onSingleTapped: TimelineManager.setHistoryView(model.roomId) + HoverHandler { + id: hovered } RowLayout { diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index 35b81a1f..a8b6fa52 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -63,14 +63,6 @@ Page { } - Component { - id: forwardCompleterComponent - - ForwardCompleter { - } - - } - Shortcut { sequence: "Ctrl+K" onActivated: { @@ -80,135 +72,6 @@ Page { } } - Platform.Menu { - id: messageContextMenu - - property string eventId - property string link - property string text - property int eventType - property bool isEncrypted - property bool isEditable - property bool isSender - - function show(eventId_, eventType_, isSender_, isEncrypted_, isEditable_, link_, text_, showAt_) { - eventId = eventId_; - eventType = eventType_; - isEncrypted = isEncrypted_; - isEditable = isEditable_; - isSender = isSender_; - if (text_) - text = text_; - else - text = ""; - if (link_) - link = link_; - else - link = ""; - if (showAt_) - open(showAt_); - else - open(); - } - - Platform.MenuItem { - visible: messageContextMenu.text - enabled: visible - text: qsTr("Copy") - onTriggered: Clipboard.text = messageContextMenu.text - } - - Platform.MenuItem { - visible: messageContextMenu.link - enabled: visible - text: qsTr("Copy link location") - onTriggered: Clipboard.text = messageContextMenu.link - } - - Platform.MenuItem { - id: reactionOption - - visible: TimelineManager.timeline ? TimelineManager.timeline.permissions.canSend(MtxEvent.Reaction) : false - text: qsTr("React") - onTriggered: emojiPopup.show(null, function(emoji) { - TimelineManager.queueReactionMessage(messageContextMenu.eventId, emoji); - }) - } - - Platform.MenuItem { - visible: TimelineManager.timeline ? TimelineManager.timeline.permissions.canSend(MtxEvent.TextMessage) : false - text: qsTr("Reply") - onTriggered: TimelineManager.timeline.replyAction(messageContextMenu.eventId) - } - - Platform.MenuItem { - visible: messageContextMenu.isEditable && (TimelineManager.timeline ? TimelineManager.timeline.permissions.canSend(MtxEvent.TextMessage) : false) - enabled: visible - text: qsTr("Edit") - onTriggered: TimelineManager.timeline.editAction(messageContextMenu.eventId) - } - - Platform.MenuItem { - text: qsTr("Read receipts") - onTriggered: TimelineManager.timeline.readReceiptsAction(messageContextMenu.eventId) - } - - Platform.MenuItem { - visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker || messageContextMenu.eventType == MtxEvent.TextMessage || messageContextMenu.eventType == MtxEvent.LocationMessage || messageContextMenu.eventType == MtxEvent.EmoteMessage || messageContextMenu.eventType == MtxEvent.NoticeMessage - text: qsTr("Forward") - onTriggered: { - var forwardMess = forwardCompleterComponent.createObject(timelineRoot); - forwardMess.setMessageEventId(messageContextMenu.eventId); - forwardMess.open(); - } - } - - Platform.MenuItem { - text: qsTr("Mark as read") - } - - Platform.MenuItem { - text: qsTr("View raw message") - onTriggered: TimelineManager.timeline.viewRawMessage(messageContextMenu.eventId) - } - - Platform.MenuItem { - // TODO(Nico): Fix this still being iterated over, when using keyboard to select options - visible: messageContextMenu.isEncrypted - enabled: visible - text: qsTr("View decrypted raw message") - onTriggered: TimelineManager.timeline.viewDecryptedRawMessage(messageContextMenu.eventId) - } - - Platform.MenuItem { - visible: (TimelineManager.timeline ? TimelineManager.timeline.permissions.canRedact() : false) || messageContextMenu.isSender - text: qsTr("Remove message") - onTriggered: TimelineManager.timeline.redactEvent(messageContextMenu.eventId) - } - - Platform.MenuItem { - visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker - enabled: visible - text: qsTr("Save as") - onTriggered: TimelineManager.timeline.saveMedia(messageContextMenu.eventId) - } - - Platform.MenuItem { - visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker - enabled: visible - text: qsTr("Open in external program") - onTriggered: TimelineManager.timeline.openMedia(messageContextMenu.eventId) - } - - Platform.MenuItem { - visible: messageContextMenu.eventId - enabled: visible - text: qsTr("Copy link to event") - onTriggered: TimelineManager.timeline.copyLinkToEvent(messageContextMenu.eventId) - } - - } - Component { id: deviceVerificationDialog @@ -233,16 +96,6 @@ Page { } } - Connections { - target: TimelineManager.timeline - onOpenRoomSettingsDialog: { - var roomSettings = roomSettingsComponent.createObject(timelineRoot, { - "roomSettings": settings - }); - roomSettings.show(); - } - } - Connections { target: CallManager onNewInviteState: { diff --git a/resources/qml/StatusIndicator.qml b/resources/qml/StatusIndicator.qml index 3d2d8278..739cc007 100644 --- a/resources/qml/StatusIndicator.qml +++ b/resources/qml/StatusIndicator.qml @@ -31,7 +31,7 @@ ImageButton { } onClicked: { if (model.state == MtxEvent.Read) - TimelineManager.timeline.readReceiptsAction(model.id); + room.readReceiptsAction(model.id); } image: { diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 257d670d..747be61e 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -18,8 +18,10 @@ import im.nheko.EmojiModel 1.0 Item { id: timelineView + property var room: null + Label { - visible: !TimelineManager.timeline && !TimelineManager.isInitialSync + visible: !room && !TimelineManager.isInitialSync anchors.centerIn: parent text: qsTr("No room open") font.pointSize: 24 @@ -38,7 +40,7 @@ Item { ColumnLayout { id: timelineLayout - visible: TimelineManager.timeline != null + visible: room != null anchors.fill: parent spacing: 0 @@ -69,11 +71,11 @@ Item { currentIndex: 0 Connections { - function onActiveTimelineChanged() { + function onRoomChanged() { stackLayout.currentIndex = 0; } - target: TimelineManager + target: timelineView } MessageView { @@ -125,7 +127,17 @@ Item { NhekoDropArea { anchors.fill: parent - roomid: TimelineManager.timeline ? TimelineManager.timeline.roomId() : "" + roomid: room ? room.roomId() : "" + } + + Connections { + target: room + onOpenRoomSettingsDialog: { + var roomSettings = roomSettingsComponent.createObject(timelineRoot, { + "roomSettings": settings + }); + roomSettings.show(); + } } } diff --git a/resources/qml/TopBar.qml b/resources/qml/TopBar.qml index bda5ce14..65e27939 100644 --- a/resources/qml/TopBar.qml +++ b/resources/qml/TopBar.qml @@ -11,8 +11,6 @@ import im.nheko 1.0 Rectangle { id: topBar - property var room: TimelineManager.timeline - Layout.fillWidth: true implicitHeight: topLayout.height + Nheko.paddingMedium * 2 z: 3 @@ -20,7 +18,7 @@ Rectangle { TapHandler { onSingleTapped: { - TimelineManager.timeline.openRoomSettings(); + room.openRoomSettings(); eventPoint.accepted = true; } gesturePolicy: TapHandler.ReleaseWithinBounds @@ -61,7 +59,7 @@ Rectangle { height: Nheko.avatarSize url: room ? room.roomAvatarUrl.replace("mxc://", "image://MxcImage/") : "" displayName: room ? room.roomName : qsTr("No room selected") - onClicked: TimelineManager.timeline.openRoomSettings() + onClicked: room.openRoomSettings() } Label { @@ -101,24 +99,24 @@ Rectangle { id: roomOptionsMenu Platform.MenuItem { - visible: TimelineManager.timeline ? TimelineManager.timeline.permissions.canInvite() : false + visible: room ? room.permissions.canInvite() : false text: qsTr("Invite users") onTriggered: TimelineManager.openInviteUsersDialog() } Platform.MenuItem { text: qsTr("Members") - onTriggered: TimelineManager.openMemberListDialog() + onTriggered: TimelineManager.openMemberListDialog(room.roomId()) } Platform.MenuItem { text: qsTr("Leave room") - onTriggered: TimelineManager.openLeaveRoomDialog() + onTriggered: TimelineManager.openLeaveRoomDialog(room.roomId()) } Platform.MenuItem { text: qsTr("Settings") - onTriggered: TimelineManager.timeline.openRoomSettings() + onTriggered: room.openRoomSettings() } } diff --git a/resources/qml/TypingIndicator.qml b/resources/qml/TypingIndicator.qml index 783a9ebc..974d1840 100644 --- a/resources/qml/TypingIndicator.qml +++ b/resources/qml/TypingIndicator.qml @@ -8,8 +8,6 @@ import QtQuick.Layouts 1.2 import im.nheko 1.0 Item { - property var room: TimelineManager.timeline - implicitHeight: Math.max(fontMetrics.height * 1.2, typingDisplay.height) Layout.fillWidth: true diff --git a/resources/qml/delegates/FileMessage.qml b/resources/qml/delegates/FileMessage.qml index 2e5f33c2..0392c73a 100644 --- a/resources/qml/delegates/FileMessage.qml +++ b/resources/qml/delegates/FileMessage.qml @@ -34,7 +34,7 @@ Item { } TapHandler { - onSingleTapped: TimelineManager.timeline.saveMedia(model.data.id) + onSingleTapped: room.saveMedia(model.data.id) gesturePolicy: TapHandler.ReleaseWithinBounds } diff --git a/resources/qml/delegates/MessageDelegate.qml b/resources/qml/delegates/MessageDelegate.qml index 4e6a73fe..9e076a7a 100644 --- a/resources/qml/delegates/MessageDelegate.qml +++ b/resources/qml/delegates/MessageDelegate.qml @@ -207,7 +207,7 @@ Item { roleValue: MtxEvent.PowerLevels NoticeMessage { - text: TimelineManager.timeline.formatPowerLevelEvent(model.data.id) + text: room.formatPowerLevelEvent(model.data.id) } } @@ -216,7 +216,7 @@ Item { roleValue: MtxEvent.RoomJoinRules NoticeMessage { - text: TimelineManager.timeline.formatJoinRuleEvent(model.data.id) + text: room.formatJoinRuleEvent(model.data.id) } } @@ -225,7 +225,7 @@ Item { roleValue: MtxEvent.RoomHistoryVisibility NoticeMessage { - text: TimelineManager.timeline.formatHistoryVisibilityEvent(model.data.id) + text: room.formatHistoryVisibilityEvent(model.data.id) } } @@ -234,7 +234,7 @@ Item { roleValue: MtxEvent.RoomGuestAccess NoticeMessage { - text: TimelineManager.timeline.formatGuestAccessEvent(model.data.id) + text: room.formatGuestAccessEvent(model.data.id) } } @@ -243,7 +243,7 @@ Item { roleValue: MtxEvent.Member NoticeMessage { - text: TimelineManager.timeline.formatMemberEvent(model.data.id) + text: room.formatMemberEvent(model.data.id) } } diff --git a/resources/qml/delegates/PlayableMediaMessage.qml b/resources/qml/delegates/PlayableMediaMessage.qml index 0234495d..83864db9 100644 --- a/resources/qml/delegates/PlayableMediaMessage.qml +++ b/resources/qml/delegates/PlayableMediaMessage.qml @@ -121,7 +121,7 @@ Rectangle { onClicked: { switch (button.state) { case "": - TimelineManager.timeline.cacheMedia(model.data.id); + room.cacheMedia(model.data.id); break; case "stopped": media.play(); @@ -174,7 +174,7 @@ Rectangle { } Connections { - target: TimelineManager.timeline + target: room onMediaCached: { if (mxcUrl == model.data.url) { media.source = cacheUrl; diff --git a/resources/qml/emoji/EmojiButton.qml b/resources/qml/emoji/EmojiButton.qml index cec51d75..5f4d23d3 100644 --- a/resources/qml/emoji/EmojiButton.qml +++ b/resources/qml/emoji/EmojiButton.qml @@ -17,7 +17,7 @@ ImageButton { image: ":/icons/icons/ui/smile.png" onClicked: emojiPicker.visible ? emojiPicker.close() : emojiPicker.show(emojiButton, function(emoji) { - TimelineManager.queueReactionMessage(event_id, emoji); + room.input.reaction(event_id, emoji); TimelineManager.focusMessageInput(); }) } diff --git a/resources/qml/voip/ActiveCallBar.qml b/resources/qml/voip/ActiveCallBar.qml index 5798433a..3106c382 100644 --- a/resources/qml/voip/ActiveCallBar.qml +++ b/resources/qml/voip/ActiveCallBar.qml @@ -35,7 +35,7 @@ Rectangle { height: Nheko.avatarSize url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/") displayName: CallManager.callParty - onClicked: TimelineManager.openImageOverlay(TimelineManager.timeline.avatarUrl(userid), TimelineManager.timeline.data.id) + onClicked: TimelineManager.openImageOverlay(room.avatarUrl(userid), room.data.id) } Label { diff --git a/resources/qml/voip/CallInviteBar.qml b/resources/qml/voip/CallInviteBar.qml index a169aca9..2d8e3040 100644 --- a/resources/qml/voip/CallInviteBar.qml +++ b/resources/qml/voip/CallInviteBar.qml @@ -42,7 +42,7 @@ Rectangle { height: Nheko.avatarSize url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/") displayName: CallManager.callParty - onClicked: TimelineManager.openImageOverlay(TimelineManager.timeline.avatarUrl(userid), TimelineManager.timeline.data.id) + onClicked: TimelineManager.openImageOverlay(room.avatarUrl(userid), room.data.id) } Label { diff --git a/resources/qml/voip/PlaceCall.qml b/resources/qml/voip/PlaceCall.qml index 7e2146cb..97e39e02 100644 --- a/resources/qml/voip/PlaceCall.qml +++ b/resources/qml/voip/PlaceCall.qml @@ -45,7 +45,7 @@ Popup { Layout.leftMargin: 8 Label { - text: qsTr("Place a call to %1?").arg(TimelineManager.timeline.roomName) + text: qsTr("Place a call to %1?").arg(room.roomName) color: Nheko.colors.windowText } @@ -77,9 +77,9 @@ Popup { Layout.rightMargin: cameraCombo.visible ? 16 : 64 width: Nheko.avatarSize height: Nheko.avatarSize - url: TimelineManager.timeline.roomAvatarUrl.replace("mxc://", "image://MxcImage/") - displayName: TimelineManager.timeline.roomName - onClicked: TimelineManager.openImageOverlay(TimelineManager.timeline.avatarUrl(userid), TimelineManager.timeline.data.id) + url: room.roomAvatarUrl.replace("mxc://", "image://MxcImage/") + displayName: room.roomName + onClicked: TimelineManager.openImageOverlay(room.avatarUrl(userid), room.data.id) } Button { @@ -88,7 +88,7 @@ Popup { onClicked: { if (buttonLayout.validateMic()) { Settings.microphone = micCombo.currentText; - CallManager.sendInvite(TimelineManager.timeline.roomId(), CallType.VOICE); + CallManager.sendInvite(room.roomId(), CallType.VOICE); close(); } } @@ -102,7 +102,7 @@ Popup { if (buttonLayout.validateMic()) { Settings.microphone = micCombo.currentText; Settings.camera = cameraCombo.currentText; - CallManager.sendInvite(TimelineManager.timeline.roomId(), CallType.VIDEO); + CallManager.sendInvite(room.roomId(), CallType.VIDEO); close(); } } diff --git a/resources/qml/voip/ScreenShare.qml b/resources/qml/voip/ScreenShare.qml index 258ac9b0..a10057b2 100644 --- a/resources/qml/voip/ScreenShare.qml +++ b/resources/qml/voip/ScreenShare.qml @@ -27,7 +27,7 @@ Popup { Layout.leftMargin: 8 Layout.rightMargin: 8 Layout.alignment: Qt.AlignLeft - text: qsTr("Share desktop with %1?").arg(TimelineManager.timeline.roomName) + text: qsTr("Share desktop with %1?").arg(room.roomName) color: Nheko.colors.windowText } @@ -136,7 +136,7 @@ Popup { Settings.screenSharePiP = pipCheckBox.checked; Settings.screenShareRemoteVideo = remoteVideoCheckBox.checked; Settings.screenShareHideCursor = hideCursorCheckBox.checked; - CallManager.sendInvite(TimelineManager.timeline.roomId(), CallType.SCREEN, windowCombo.currentIndex); + CallManager.sendInvite(room.roomId(), CallType.SCREEN, windowCombo.currentIndex); close(); } } diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 166c03ec..bee20d60 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -215,8 +215,6 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) this->current_room_ = room_id; }); connect(room_list_, &RoomList::roomChanged, splitter, &Splitter::showChatView); - connect( - room_list_, &RoomList::roomChanged, view_manager_, &TimelineViewManager::setHistoryView); connect(room_list_, &RoomList::acceptInvite, this, [this](const QString &room_id) { joinRoom(room_id); @@ -982,7 +980,7 @@ ChatPage::leaveRoom(const QString &room_id) void ChatPage::changeRoom(const QString &room_id) { - view_manager_->setHistoryView(room_id); + view_manager_->rooms()->setCurrentRoom(room_id); room_list_->highlightSelectedRoom(room_id); } @@ -1397,7 +1395,8 @@ ChatPage::handleMatrixUri(const QByteArray &uri) if (sigil1 == "u") { if (action.isEmpty()) { - view_manager_->activeTimeline()->openUserProfile(mxid1); + if (auto t = view_manager_->rooms()->currentRoom()) + t->openUserProfile(mxid1); } else if (action == "chat") { this->startChat(mxid1); } diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp index cda38b75..a283d24e 100644 --- a/src/timeline/InputBar.cpp +++ b/src/timeline/InputBar.cpp @@ -508,8 +508,7 @@ InputBar::command(QString command, QString args) } else if (command == "react") { auto eventId = room->reply(); if (!eventId.isEmpty()) - ChatPage::instance()->timelineManager()->queueReactionMessage( - eventId, args.trimmed()); + reaction(eventId, args.trimmed()); } else if (command == "join") { ChatPage::instance()->joinRoom(args); } else if (command == "part" || command == "leave") { @@ -715,3 +714,35 @@ InputBar::stopTyping() } }); } + +void +InputBar::reaction(const QString &reactedEvent, const QString &reactionKey) +{ + auto reactions = room->reactions(reactedEvent.toStdString()); + + QString selfReactedEvent; + for (const auto &reaction : reactions) { + if (reactionKey == reaction.key_) { + selfReactedEvent = reaction.selfReactedEvent_; + break; + } + } + + if (selfReactedEvent.startsWith("m")) + return; + + // If selfReactedEvent is empty, that means we haven't previously reacted + if (selfReactedEvent.isEmpty()) { + mtx::events::msg::Reaction reaction; + mtx::common::Relation rel; + rel.rel_type = mtx::common::RelationType::Annotation; + rel.event_id = reactedEvent.toStdString(); + rel.key = reactionKey.toStdString(); + reaction.relations.relations.push_back(rel); + + room->sendMessageEvent(reaction, mtx::events::EventType::Reaction); + // Otherwise, we have previously reacted and the reaction should be redacted + } else { + room->redactEvent(selfReactedEvent); + } +} diff --git a/src/timeline/InputBar.h b/src/timeline/InputBar.h index 9db16bae..c9728379 100644 --- a/src/timeline/InputBar.h +++ b/src/timeline/InputBar.h @@ -56,6 +56,7 @@ public slots: void message(QString body, MarkdownOverride useMarkdown = MarkdownOverride::NOT_SPECIFIED, bool rainbowify = false); + void reaction(const QString &reactedEvent, const QString &reactionKey); private slots: void startTyping(); diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp index 63054aa9..ad4177a4 100644 --- a/src/timeline/RoomlistModel.cpp +++ b/src/timeline/RoomlistModel.cpp @@ -341,6 +341,8 @@ RoomlistModel::clear() models.clear(); invites.clear(); roomids.clear(); + currentRoom_ = nullptr; + emit currentRoomChanged(); endResetModel(); } @@ -390,6 +392,17 @@ RoomlistModel::leave(QString roomid) } } +void +RoomlistModel::setCurrentRoom(QString roomid) +{ + nhlog::ui()->debug("Trying to switch to: {}", roomid.toStdString()); + if (models.contains(roomid)) { + currentRoom_ = models.value(roomid); + emit currentRoomChanged(); + nhlog::ui()->debug("Switched to: {}", roomid.toStdString()); + } +} + namespace { enum NotificationImportance : short { @@ -463,6 +476,11 @@ FilteredRoomlistModel::FilteredRoomlistModel(RoomlistModel *model, QObject *pare invalidate(); }); + connect(roomlistmodel, + &RoomlistModel::currentRoomChanged, + this, + &FilteredRoomlistModel::currentRoomChanged); + sort(0); } diff --git a/src/timeline/RoomlistModel.h b/src/timeline/RoomlistModel.h index 2d1e5264..1c6fa833 100644 --- a/src/timeline/RoomlistModel.h +++ b/src/timeline/RoomlistModel.h @@ -14,12 +14,14 @@ #include -class TimelineModel; +#include "TimelineModel.h" + class TimelineViewManager; class RoomlistModel : public QAbstractListModel { Q_OBJECT + Q_PROPERTY(TimelineModel *currentRoom READ currentRoom NOTIFY currentRoomChanged) public: enum Roles { @@ -69,12 +71,15 @@ public slots: void acceptInvite(QString roomid); void declineInvite(QString roomid); void leave(QString roomid); + TimelineModel *currentRoom() const { return currentRoom_.get(); } + void setCurrentRoom(QString roomid); private slots: void updateReadStatus(const std::map roomReadStatus_); signals: void totalUnreadMessageCountUpdated(int unreadMessages); + void currentRoomChanged(); private: void addRoom(const QString &room_id, bool suppressInsertNotification = false); @@ -85,12 +90,15 @@ private: QHash> models; std::map roomReadStatus; + QSharedPointer currentRoom_; + friend class FilteredRoomlistModel; }; class FilteredRoomlistModel : public QSortFilterProxyModel { Q_OBJECT + Q_PROPERTY(TimelineModel *currentRoom READ currentRoom NOTIFY currentRoomChanged) public: FilteredRoomlistModel(RoomlistModel *model, QObject *parent = nullptr); bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; @@ -107,6 +115,12 @@ public slots: QStringList tags(); void toggleTag(QString roomid, QString tag, bool on); + TimelineModel *currentRoom() const { return roomlistmodel->currentRoom(); } + void setCurrentRoom(QString roomid) { roomlistmodel->setCurrentRoom(std::move(roomid)); } + +signals: + void currentRoomChanged(); + private: short int calculateImportance(const QModelIndex &idx) const; RoomlistModel *roomlistmodel; diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 9fa7f8b6..3b3ea423 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -133,7 +133,7 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par , colorImgProvider(new ColorImageProvider()) , blurhashProvider(new BlurhashProvider()) , callManager_(callManager) - , rooms(new RoomlistModel(this)) + , rooms_(new RoomlistModel(this)) { qRegisterMetaType(); qRegisterMetaType(); @@ -193,7 +193,7 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par }); qmlRegisterSingletonType( "im.nheko", 1, 0, "Rooms", [](QQmlEngine *, QJSEngine *) -> QObject * { - return new FilteredRoomlistModel(self->rooms); + return new FilteredRoomlistModel(self->rooms_); }); qmlRegisterSingletonType( "im.nheko", 1, 0, "Settings", [](QQmlEngine *, QJSEngine *) -> QObject * { @@ -320,9 +320,9 @@ TimelineViewManager::setVideoCallItem() } void -TimelineViewManager::sync(const mtx::responses::Rooms &rooms_) +TimelineViewManager::sync(const mtx::responses::Rooms &rooms_res) { - this->rooms->sync(rooms_); + this->rooms_->sync(rooms_res); if (isInitialSync_) { this->isInitialSync_ = false; @@ -330,37 +330,17 @@ TimelineViewManager::sync(const mtx::responses::Rooms &rooms_) } } -void -TimelineViewManager::setHistoryView(const QString &room_id) -{ - nhlog::ui()->info("Trying to activate room {}", room_id.toStdString()); - - if (auto room = rooms->getRoomById(room_id)) { - timeline_ = room.get(); - emit activeTimelineChanged(timeline_); - container->setFocus(); - nhlog::ui()->info("Activated room {}", room_id.toStdString()); - } -} - -void -TimelineViewManager::highlightRoom(const QString &room_id) -{ - ChatPage::instance()->highlightRoom(room_id); -} - void TimelineViewManager::showEvent(const QString &room_id, const QString &event_id) { - if (auto room = rooms->getRoomById(room_id)) { - if (timeline_ != room) { - timeline_ = room.get(); - emit activeTimelineChanged(timeline_); + if (auto room = rooms_->getRoomById(room_id)) { + if (rooms_->currentRoom() != room) { + rooms_->setCurrentRoom(room_id); container->setFocus(); nhlog::ui()->info("Activated room {}", room_id.toStdString()); } - timeline_->showEvent(event_id); + room->showEvent(event_id); } } @@ -395,17 +375,20 @@ TimelineViewManager::openImageOverlayInternal(QString eventId, QImage img) auto imgDialog = new dialogs::ImageOverlay(pixmap); imgDialog->showFullScreen(); - connect(imgDialog, &dialogs::ImageOverlay::saving, timeline_, [this, eventId, imgDialog]() { - // hide the overlay while presenting the save dialog for better - // cross platform support. - imgDialog->hide(); - - if (!timeline_->saveMedia(eventId)) { - imgDialog->show(); - } else { - imgDialog->close(); - } - }); + + auto room = rooms_->currentRoom(); + connect( + imgDialog, &dialogs::ImageOverlay::saving, room, [this, eventId, imgDialog, room]() { + // hide the overlay while presenting the save dialog for better + // cross platform support. + imgDialog->hide(); + + if (!room->saveMedia(eventId)) { + imgDialog->show(); + } else { + imgDialog->close(); + } + }); } void @@ -415,14 +398,14 @@ TimelineViewManager::openInviteUsersDialog() [this](const QStringList &invitees) { emit inviteUsers(invitees); }); } void -TimelineViewManager::openMemberListDialog() const +TimelineViewManager::openMemberListDialog(QString roomid) const { - MainWindow::instance()->openMemberListDialog(timeline_->roomId()); + MainWindow::instance()->openMemberListDialog(roomid); } void -TimelineViewManager::openLeaveRoomDialog() const +TimelineViewManager::openLeaveRoomDialog(QString roomid) const { - MainWindow::instance()->openLeaveRoomDialog(timeline_->roomId()); + MainWindow::instance()->openLeaveRoomDialog(roomid); } void @@ -439,7 +422,7 @@ TimelineViewManager::verifyUser(QString userid) room_members.end(), (userid).toStdString()) != room_members.end()) { if (auto model = - rooms->getRoomById(QString::fromStdString(room_id))) { + rooms_->getRoomById(QString::fromStdString(room_id))) { auto flow = DeviceVerificationFlow::InitiateUserVerification( this, model.data(), userid); @@ -485,7 +468,7 @@ void TimelineViewManager::updateReadReceipts(const QString &room_id, const std::vector &event_ids) { - if (auto room = rooms->getRoomById(room_id)) { + if (auto room = rooms_->getRoomById(room_id)) { room->markEventsAsRead(event_ids); } } @@ -493,7 +476,7 @@ TimelineViewManager::updateReadReceipts(const QString &room_id, void TimelineViewManager::receivedSessionKey(const std::string &room_id, const std::string &session_id) { - if (auto room = rooms->getRoomById(QString::fromStdString(room_id))) { + if (auto room = rooms_->getRoomById(QString::fromStdString(room_id))) { room->receivedSessionKey(session_id); } } @@ -501,7 +484,7 @@ TimelineViewManager::receivedSessionKey(const std::string &room_id, const std::s void TimelineViewManager::initializeRoomlist() { - rooms->initializeRooms(); + rooms_->initializeRooms(); } void @@ -509,51 +492,17 @@ TimelineViewManager::queueReply(const QString &roomid, const QString &repliedToEvent, const QString &replyBody) { - if (auto room = rooms->getRoomById(roomid)) { + if (auto room = rooms_->getRoomById(roomid)) { room->setReply(repliedToEvent); room->input()->message(replyBody); } } -void -TimelineViewManager::queueReactionMessage(const QString &reactedEvent, const QString &reactionKey) -{ - if (!timeline_) - return; - - auto reactions = timeline_->reactions(reactedEvent.toStdString()); - - QString selfReactedEvent; - for (const auto &reaction : reactions) { - if (reactionKey == reaction.key_) { - selfReactedEvent = reaction.selfReactedEvent_; - break; - } - } - - if (selfReactedEvent.startsWith("m")) - return; - - // If selfReactedEvent is empty, that means we haven't previously reacted - if (selfReactedEvent.isEmpty()) { - mtx::events::msg::Reaction reaction; - mtx::common::Relation rel; - rel.rel_type = mtx::common::RelationType::Annotation; - rel.event_id = reactedEvent.toStdString(); - rel.key = reactionKey.toStdString(); - reaction.relations.relations.push_back(rel); - - timeline_->sendMessageEvent(reaction, mtx::events::EventType::Reaction); - // Otherwise, we have previously reacted and the reaction should be redacted - } else { - timeline_->redactEvent(selfReactedEvent); - } -} void TimelineViewManager::queueCallMessage(const QString &roomid, const mtx::events::msg::CallInvite &callInvite) { - if (auto room = rooms->getRoomById(roomid)) + if (auto room = rooms_->getRoomById(roomid)) room->sendMessageEvent(callInvite, mtx::events::EventType::CallInvite); } @@ -561,7 +510,7 @@ void TimelineViewManager::queueCallMessage(const QString &roomid, const mtx::events::msg::CallCandidates &callCandidates) { - if (auto room = rooms->getRoomById(roomid)) + if (auto room = rooms_->getRoomById(roomid)) room->sendMessageEvent(callCandidates, mtx::events::EventType::CallCandidates); } @@ -569,7 +518,7 @@ void TimelineViewManager::queueCallMessage(const QString &roomid, const mtx::events::msg::CallAnswer &callAnswer) { - if (auto room = rooms->getRoomById(roomid)) + if (auto room = rooms_->getRoomById(roomid)) room->sendMessageEvent(callAnswer, mtx::events::EventType::CallAnswer); } @@ -577,7 +526,7 @@ void TimelineViewManager::queueCallMessage(const QString &roomid, const mtx::events::msg::CallHangUp &callHangUp) { - if (auto room = rooms->getRoomById(roomid)) + if (auto room = rooms_->getRoomById(roomid)) room->sendMessageEvent(callHangUp, mtx::events::EventType::CallHangUp); } @@ -629,7 +578,7 @@ void TimelineViewManager::forwardMessageToRoom(mtx::events::collections::TimelineEvents *e, QString roomId) { - auto room = rooms->getRoomById(roomId); + auto room = rooms_->getRoomById(roomId); auto content = mtx::accessors::url(*e); std::optional encryptionInfo = mtx::accessors::file(*e); @@ -672,7 +621,7 @@ TimelineViewManager::forwardMessageToRoom(mtx::events::collections::TimelineEven ev.content.url = url; } - if (auto room = rooms->getRoomById(roomId)) { + if (auto room = rooms_->getRoomById(roomId)) { removeReplyFallback(ev); ev.content.relations.relations .clear(); diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 37e50804..c4707208 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -35,8 +35,6 @@ class TimelineViewManager : public QObject { Q_OBJECT - Q_PROPERTY( - TimelineModel *timeline MEMBER timeline_ READ activeTimeline NOTIFY activeTimelineChanged) Q_PROPERTY( bool isInitialSync MEMBER isInitialSync_ READ isInitialSync NOTIFY initialSyncChanged) Q_PROPERTY( @@ -53,14 +51,8 @@ public: MxcImageProvider *imageProvider() { return imgProvider; } CallManager *callManager() { return callManager_; } - void clearAll() - { - timeline_ = nullptr; - emit activeTimelineChanged(nullptr); - rooms->clear(); - } + void clearAll() { rooms_->clear(); } - Q_INVOKABLE TimelineModel *activeTimeline() const { return timeline_; } Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; } bool isNarrowView() const { return isNarrowView_; } bool isWindowFocused() const { return isWindowFocused_; } @@ -74,8 +66,8 @@ public: Q_INVOKABLE void focusMessageInput(); Q_INVOKABLE void openInviteUsersDialog(); - Q_INVOKABLE void openMemberListDialog() const; - Q_INVOKABLE void openLeaveRoomDialog() const; + Q_INVOKABLE void openMemberListDialog(QString roomid) const; + Q_INVOKABLE void openLeaveRoomDialog(QString roomid) const; Q_INVOKABLE void removeVerificationFlow(DeviceVerificationFlow *flow); void verifyUser(QString userid); @@ -107,20 +99,13 @@ public slots: emit focusChanged(); } - void setHistoryView(const QString &room_id); - void highlightRoom(const QString &room_id); void showEvent(const QString &room_id, const QString &event_id); void focusTimeline(); - TimelineModel *getHistoryView(const QString &room_id) - { - return rooms->getRoomById(room_id).get(); - } void updateColorPalette(); void queueReply(const QString &roomid, const QString &repliedToEvent, const QString &replyBody); - void queueReactionMessage(const QString &reactedEvent, const QString &reactionKey); void queueCallMessage(const QString &roomid, const mtx::events::msg::CallInvite &); void queueCallMessage(const QString &roomid, const mtx::events::msg::CallCandidates &); void queueCallMessage(const QString &roomid, const mtx::events::msg::CallAnswer &); @@ -147,6 +132,8 @@ public slots: QObject *completerFor(QString completerName, QString roomId = ""); void forwardMessageToRoom(mtx::events::collections::TimelineEvents *e, QString roomId); + RoomlistModel *rooms() { return rooms_; } + private slots: void openImageOverlayInternal(QString eventId, QImage img); @@ -162,14 +149,13 @@ private: ColorImageProvider *colorImgProvider; BlurhashProvider *blurhashProvider; - TimelineModel *timeline_ = nullptr; CallManager *callManager_ = nullptr; bool isInitialSync_ = true; bool isNarrowView_ = false; bool isWindowFocused_ = false; - RoomlistModel *rooms = nullptr; + RoomlistModel *rooms_ = nullptr; QHash userColors; diff --git a/src/ui/NhekoDropArea.cpp b/src/ui/NhekoDropArea.cpp index 54f48d3c..bbcedd7e 100644 --- a/src/ui/NhekoDropArea.cpp +++ b/src/ui/NhekoDropArea.cpp @@ -35,7 +35,7 @@ void NhekoDropArea::dropEvent(QDropEvent *event) { if (event) { - auto model = ChatPage::instance()->timelineManager()->getHistoryView(roomid_); + auto model = ChatPage::instance()->timelineManager()->rooms()->getRoomById(roomid_); if (model) { model->input()->insertMimeData(event->mimeData()); } -- cgit 1.5.1 From 03d30a2abc6a2c9c9e1eaecc5a611b70e3041066 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 28 May 2021 23:25:57 +0200 Subject: Delete old room list --- CMakeLists.txt | 20 -- resources/qml/Root.qml | 9 + src/ChatPage.cpp | 309 +++-------------------- src/ChatPage.h | 63 +---- src/CommunitiesList.cpp | 345 -------------------------- src/CommunitiesList.h | 65 ----- src/CommunitiesListItem.cpp | 201 --------------- src/CommunitiesListItem.h | 107 -------- src/MainWindow.cpp | 34 --- src/MainWindow.h | 4 - src/RoomInfoListItem.cpp | 522 --------------------------------------- src/RoomInfoListItem.h | 210 ---------------- src/RoomList.cpp | 535 ---------------------------------------- src/RoomList.h | 101 -------- src/popups/PopupItem.cpp | 89 ------- src/popups/PopupItem.h | 66 ----- src/popups/SuggestionsPopup.cpp | 164 ------------ src/popups/SuggestionsPopup.h | 53 ---- src/popups/UserMentions.cpp | 178 ------------- src/popups/UserMentions.h | 49 ---- src/timeline/InputBar.cpp | 1 + src/timeline/RoomlistModel.cpp | 30 +++ src/timeline/RoomlistModel.h | 3 + 23 files changed, 76 insertions(+), 3082 deletions(-) delete mode 100644 src/CommunitiesList.cpp delete mode 100644 src/CommunitiesList.h delete mode 100644 src/CommunitiesListItem.cpp delete mode 100644 src/CommunitiesListItem.h delete mode 100644 src/RoomInfoListItem.cpp delete mode 100644 src/RoomInfoListItem.h delete mode 100644 src/RoomList.cpp delete mode 100644 src/RoomList.h delete mode 100644 src/popups/PopupItem.cpp delete mode 100644 src/popups/PopupItem.h delete mode 100644 src/popups/SuggestionsPopup.cpp delete mode 100644 src/popups/SuggestionsPopup.h delete mode 100644 src/popups/UserMentions.cpp delete mode 100644 src/popups/UserMentions.h (limited to 'src/ChatPage.cpp') 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) +{ + return isActiveWindow() && currentRoom() == room_id; +} + +QString +ChatPage::currentRoom() const { - room_list_->highlightSelectedRoom(room_id); + 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(); -- cgit 1.5.1 From 18ff58edb3bc186e2114efad34de7ffca803be02 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 30 May 2021 00:23:57 +0200 Subject: Fix use after free from Qml widget --- src/ChatPage.cpp | 8 ++++++++ src/timeline/RoomlistModel.cpp | 3 ++- src/timeline/TimelineViewManager.cpp | 5 +++-- 3 files changed, 13 insertions(+), 3 deletions(-) (limited to 'src/ChatPage.cpp') diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 4ad7bd14..0f16f205 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -171,6 +171,14 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) activateWindow(); }); + connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [this]() { + // ensure the qml context is shutdown before we destroy all other singletons + // Otherwise Qml will try to access the room list or settings, after they have been + // destroyed + topLayout_->removeWidget(view_manager_->getWidget()); + delete view_manager_->getWidget(); + }); + connect( this, &ChatPage::initializeViews, diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp index d2ba0dc3..283224f1 100644 --- a/src/timeline/RoomlistModel.cpp +++ b/src/timeline/RoomlistModel.cpp @@ -13,7 +13,8 @@ #include "UserSettingsPage.h" RoomlistModel::RoomlistModel(TimelineViewManager *parent) - : manager(parent) + : QAbstractListModel(parent) + , manager(parent) { connect(ChatPage::instance(), &ChatPage::decryptSidebarChanged, this, [this]() { auto decrypt = ChatPage::instance()->userSettings()->decryptSidebar(); diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 3b3ea423..dd623f2f 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -129,7 +129,8 @@ TimelineViewManager::userStatus(QString id) const } TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *parent) - : imgProvider(new MxcImageProvider()) + : QObject(parent) + , imgProvider(new MxcImageProvider()) , colorImgProvider(new ColorImageProvider()) , blurhashProvider(new BlurhashProvider()) , callManager_(callManager) @@ -230,7 +231,7 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par "Error: Only enums"); #ifdef USE_QUICK_VIEW - view = new QQuickView(); + view = new QQuickView(parent); container = QWidget::createWindowContainer(view, parent); #else view = new QQuickWidget(parent); -- cgit 1.5.1