diff options
Diffstat (limited to 'src/RoomList.cpp')
-rw-r--r-- | src/RoomList.cpp | 440 |
1 files changed, 440 insertions, 0 deletions
diff --git a/src/RoomList.cpp b/src/RoomList.cpp new file mode 100644 index 00000000..a9328984 --- /dev/null +++ b/src/RoomList.cpp @@ -0,0 +1,440 @@ +/* + * nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <QApplication> +#include <QBuffer> +#include <QObject> +#include <QTimer> + +#include "Cache.h" +#include "Logging.h" +#include "MainWindow.h" +#include "MatrixClient.h" +#include "RoomInfoListItem.h" +#include "RoomList.h" +#include "UserSettingsPage.h" +#include "Utils.h" +#include "ui/OverlayModal.h" + +RoomList::RoomList(QSharedPointer<UserSettings> userSettings, QWidget *parent) + : QWidget(parent) + , userSettings_{userSettings} +{ + setStyleSheet("border: none;"); + topLayout_ = new QVBoxLayout(this); + topLayout_->setSpacing(0); + topLayout_->setMargin(0); + + 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); + + scrollAreaContents_ = new QWidget(this); + + contentsLayout_ = new QVBoxLayout(scrollAreaContents_); + contentsLayout_->setSpacing(0); + contentsLayout_->setMargin(0); + contentsLayout_->addStretch(1); + + scrollArea_->setWidget(scrollAreaContents_); + topLayout_->addWidget(scrollArea_); + + connect(this, &RoomList::updateRoomAvatarCb, this, &RoomList::updateRoomAvatar); +} + +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); + }); + + rooms_.emplace(room_id, QSharedPointer<RoomInfoListItem>(room_item)); + + 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) +{ + if (url.isEmpty()) + return; + + QByteArray savedImgData; + + if (cache::client()) + savedImgData = cache::client()->image(url); + + if (savedImgData.isEmpty()) { + mtx::http::ThumbOpts opts; + opts.mxc_url = url.toStdString(); + http::client()->get_thumbnail( + opts, [room_id, opts, this](const std::string &res, mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn( + "failed to download room avatar: {} {} {}", + opts.mxc_url, + mtx::errors::to_string(err->matrix_error.errcode), + err->matrix_error.error); + return; + } + + if (cache::client()) + cache::client()->saveImage(opts.mxc_url, res); + + auto data = QByteArray(res.data(), res.size()); + QPixmap pixmap; + pixmap.loadFromData(data); + + emit updateRoomAvatarCb(room_id, pixmap); + }); + } else { + QPixmap img; + img.loadFromData(savedImgData); + + updateRoomAvatar(room_id, img); + } +} + +void +RoomList::removeRoom(const QString &room_id, bool reset) +{ + 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) +{ + if (!roomExists(roomid)) { + nhlog::ui()->warn("updateUnreadMessageCount: unknown room_id {}", + roomid.toStdString()); + return; + } + + rooms_[roomid]->updateUnreadMessageCount(count); + + calculateUnreadMessageCount(); +} + +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<QString, RoomInfo> &info) +{ + nhlog::ui()->info("initialize room list"); + + rooms_.clear(); + + 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; + + auto room = firstRoom(); + if (room.second.isNull()) + return; + + room.second->setPressedState(true); + emit roomChanged(room.first); +} + +void +RoomList::cleanupInvites(const std::map<QString, bool> &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<QString, RoomInfo> &info) + +{ + for (const auto &room : info) + updateRoom(room.first, room.second); +} + +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::updateRoomAvatar(const QString &roomid, const QPixmap &img) +{ + if (!roomExists(roomid)) { + nhlog::ui()->warn("avatar update on non-existent room_id: {}", + roomid.toStdString()); + return; + } + + rooms_[roomid]->setAvatar(img.toImage()); + + // 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)) { + nhlog::ui()->warn("description update on non-existent room_id: {}, {}", + roomid.toStdString(), + info.body.toStdString()); + 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(); +} + +void +RoomList::sortRoomsByLastMessage() +{ + if (!userSettings_->isOrderingEnabled()) + return; + + isSortPending_ = false; + + std::multimap<uint64_t, RoomInfoListItem *, std::greater<uint64_t>> times; + + for (int ii = 0; ii < contentsLayout_->count(); ++ii) { + auto room = qobject_cast<RoomInfoListItem *>(contentsLayout_->itemAt(ii)->widget()); + + if (!room) + continue; + + // Not a room message. + if (room->lastMessageInfo().userid.isEmpty()) + times.emplace(0, room); + else + times.emplace(room->lastMessageInfo().datetime.toMSecsSinceEpoch(), room); + } + + for (auto it = times.cbegin(); it != times.cend(); ++it) { + const auto roomWidget = it->second; + const auto currentIndex = contentsLayout_->indexOf(roomWidget); + const auto newIndex = std::distance(times.cbegin(), it); + + if (currentIndex == newIndex) + continue; + + contentsLayout_->removeWidget(roomWidget); + contentsLayout_->insertWidget(newIndex, roomWidget); + } +} + +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::setFilterRooms(bool isFilteringEnabled) +{ + for (int i = 0; i < contentsLayout_->count(); i++) { + // If roomFilter_ contains the room for the current RoomInfoListItem, + // show the list item, otherwise hide it + auto listitem = + qobject_cast<RoomInfoListItem *>(contentsLayout_->itemAt(i)->widget()); + + if (!listitem) + continue; + + if (!isFilteringEnabled || filterItemExists(listitem->roomId())) + listitem->show(); + else + listitem->hide(); + } + + if (isFilteringEnabled && !filterItemExists(selectedRoom_)) { + RoomInfoListItem *firstVisibleRoom = nullptr; + + for (int i = 0; i < contentsLayout_->count(); i++) { + QWidget *item = contentsLayout_->itemAt(i)->widget(); + + if (item != nullptr && item->isVisible()) { + firstVisibleRoom = qobject_cast<RoomInfoListItem *>(item); + break; + } + } + + if (firstVisibleRoom != nullptr) + highlightSelectedRoom(firstVisibleRoom->roomId()); + } else { + scrollArea_->ensureWidgetVisible(rooms_[selectedRoom_].data()); + } +} + +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::setRoomFilter(std::vector<QString> room_ids) +{ + roomFilter_ = room_ids; + setFilterRooms(true); +} + +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); + + rooms_.emplace(room_id, QSharedPointer<RoomInfoListItem>(room_item)); + + updateAvatar(room_id, QString::fromStdString(info.avatar_url)); + + int pos = contentsLayout_->count() - 1; + contentsLayout_->insertWidget(pos, room_item); +} + +std::pair<QString, QSharedPointer<RoomInfoListItem>> +RoomList::firstRoom() const +{ + auto firstRoom = rooms_.begin(); + + while (firstRoom->second.isNull() && firstRoom != rooms_.end()) + firstRoom++; + + return std::pair<QString, QSharedPointer<RoomInfoListItem>>(firstRoom->first, + firstRoom->second); +} |