summary refs log tree commit diff
path: root/src/RoomList.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/RoomList.cpp')
-rw-r--r--src/RoomList.cpp440
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);
+}