Newer
Older
/*
* 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 <QScroller>
Max Sandholm
committed
#include "MainWindow.h"
#include "RoomInfoListItem.h"
#include "RoomList.h"
RoomList::RoomList(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);
QScroller::grabGesture(scrollArea_, QScroller::TouchGesture);
// 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);
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)));
Max Sandholm
committed
connect(room_item, &RoomInfoListItem::clicked, this, &RoomList::highlightSelectedRoom);
connect(room_item, &RoomInfoListItem::leaveRoom, this, [](const QString &room_id) {
MainWindow::instance()->openLeaveRoomDialog(room_id);
});
Max Sandholm
committed
rooms_.emplace(room_id, QSharedPointer<RoomInfoListItem>(room_item));
Max Sandholm
committed
if (!info.avatar_url.empty())
updateAvatar(room_id, QString::fromStdString(info.avatar_url));
Max Sandholm
committed
int pos = contentsLayout_->count() - 1;
contentsLayout_->insertWidget(pos, room_item);
Max Sandholm
committed
}
void
RoomList::updateAvatar(const QString &room_id, const QString &url)
{
if (url.isEmpty())
return;
emit updateRoomAvatarCb(room_id, url);
Max Sandholm
committed
void
RoomList::removeRoom(const QString &room_id, bool reset)
{
Max Sandholm
committed
if (rooms_.empty() || !reset)
Max Sandholm
committed
return;
auto room = firstRoom();
Max Sandholm
committed
if (room.second.isNull())
return;
room.second->setPressedState(true);
emit roomChanged(room.first);
Max Sandholm
committed
}
RoomList::updateUnreadMessageCount(const QString &roomid, int count, int highlightedCount)
if (!roomExists(roomid)) {
Konstantinos Sideris
committed
nhlog::ui()->warn("updateUnreadMessageCount: unknown room_id {}",
rooms_[roomid]->updateUnreadMessageCount(count, highlightedCount);
sortRoomsByLastMessage();
void
RoomList::calculateUnreadMessageCount()
for (const auto &room : rooms_) {
if (!room.second.isNull())
total_unread_msgs += room.second->unreadMessageCount();
}
emit totalUnreadMessageCountUpdated(total_unread_msgs);
RoomList::initialize(const QMap<QString, RoomInfo> &info)
Konstantinos Sideris
committed
nhlog::ui()->info("initialize room list");
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);
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)
{
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());
RoomList::sync(const std::map<QString, RoomInfo> &info)
for (const auto &room : info)
updateRoom(room.first, room.second);
if (!info.empty())
sortRoomsByLastMessage();
void
RoomList::highlightSelectedRoom(const QString &room_id)
if (!roomExists(room_id)) {
Konstantinos Sideris
committed
nhlog::ui()->warn("roomlist: clicked unknown room_id");
for (auto const &room : rooms_) {
if (room.second.isNull())
continue;
if (room.first != room_id) {
room.second->setPressedState(false);
room.second->setPressedState(true);
scrollArea_->ensureWidgetVisible(room.second.data());
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
void
RoomList::nextRoom()
{
for (int ii = 0; ii < contentsLayout_->count() - 1; ++ii) {
auto room = qobject_cast<RoomInfoListItem *>(contentsLayout_->itemAt(ii)->widget());
if (!room)
continue;
if (room->roomId() == selectedRoom_) {
auto nextRoom = qobject_cast<RoomInfoListItem *>(
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<RoomInfoListItem *>(contentsLayout_->itemAt(ii)->widget());
if (!room)
continue;
if (room->roomId() == selectedRoom_) {
auto nextRoom = qobject_cast<RoomInfoListItem *>(
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;
}
}
}
RoomList::updateRoomAvatar(const QString &roomid, const QString &img)
if (!roomExists(roomid)) {
Konstantinos Sideris
committed
nhlog::ui()->warn("avatar update on non-existent room_id: {}",
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)) {
Konstantinos Sideris
committed
nhlog::ui()->warn("description update on non-existent room_id: {}, {}",
roomid.toStdString(),
info.body.toStdString());
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 RoomInfoListItem * a, const RoomInfoListItem * 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->lastMessageInfo().userid.isEmpty() ? 0 :
a->lastMessageInfo().datetime.toMSecsSinceEpoch();
const uint64_t b_recency = b->lastMessageInfo().userid.isEmpty() ? 0 :
b->lastMessageInfo().datetime.toMSecsSinceEpoch();
return a_recency > b_recency;
}
};
void
RoomList::sortRoomsByLastMessage()
{
isSortPending_ = false;
std::multiset<RoomInfoListItem *, room_sort> times;
for (int ii = 0; ii < contentsLayout_->count(); ++ii) {
auto room = qobject_cast<RoomInfoListItem *>(contentsLayout_->itemAt(ii)->widget());
if (!room)
continue;
else
}
for (auto it = times.cbegin(); it != times.cend(); ++it) {
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);
Max Sandholm
committed
void
RoomList::closeJoinRoomDialog(bool isJoining, QString roomAlias)
{
Konstantinos Sideris
committed
joinRoomModal_->hide();
Max Sandholm
committed
emit joinRoom(roomAlias);
Max Sandholm
committed
}
RoomList::removeFilter()
{
setUpdatesEnabled(false);
for (int i = 0; i < contentsLayout_->count(); i++) {
auto widget =
qobject_cast<RoomInfoListItem *>(contentsLayout_->itemAt(i)->widget());
if (widget)
widget->show();
}
setUpdatesEnabled(true);
}
void
RoomList::applyFilter(const std::map<QString, bool> &filter)
// Disabling paint updates will resolve issues with screen flickering on big room lists.
setUpdatesEnabled(false);
// If filter contains the room for the current RoomInfoListItem,
auto listitem =
qobject_cast<RoomInfoListItem *>(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<RoomInfoListItem *>(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);
}
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();
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
{
for (int i = 0; i < contentsLayout_->count(); i++) {
auto item = qobject_cast<RoomInfoListItem *>(contentsLayout_->itemAt(i)->widget());
if (item) {
return std::pair<QString, QSharedPointer<RoomInfoListItem>>(
item->roomId(), rooms_.at(item->roomId()));
}
}
void
RoomList::updateReadStatus(const std::map<QString, bool> &status)
{
for (const auto &room : status) {
if (roomExists(room.first)) {
auto item = rooms_.at(room.first);
if (item)
item->setReadState(room.second);
}
}
}