mirror of https://github.com/Nheko-Reborn/nheko
parent
298822baea
commit
03d30a2abc
@ -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 <mtx/responses/groups.hpp> |
||||
#include <nlohmann/json.hpp> |
||||
|
||||
#include <QLabel> |
||||
|
||||
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<QString, RoomInfo> &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<std::string> &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<int>(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<CommunitiesListItem>(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<QString> &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<QString> 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<QString> |
||||
CommunitiesList::roomList(const QString &id) const |
||||
{ |
||||
if (communityExists(id)) |
||||
return communities_.at(id)->rooms(); |
||||
|
||||
return {}; |
||||
} |
||||
|
||||
std::vector<std::string> |
||||
CommunitiesList::currentTags() const |
||||
{ |
||||
std::vector<std::string> 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<QString> |
||||
CommunitiesList::hiddenTagsAndCommunities() const |
||||
{ |
||||
std::set<QString> hiddenTags; |
||||
for (auto &entry : communities_) { |
||||
if (entry.second->isDisabled()) |
||||
hiddenTags.insert(entry.first); |
||||
} |
||||
|
||||
return hiddenTags; |
||||
} |
||||
|
||||
void |
||||
CommunitiesList::sortEntries() |
||||
{ |
||||
std::vector<CommunitiesListItem *> header; |
||||
std::vector<CommunitiesListItem *> communities; |
||||
std::vector<CommunitiesListItem *> tags; |
||||
std::vector<CommunitiesListItem *> 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); |
||||
} |
@ -1,65 +0,0 @@ |
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once |
||||
|
||||
#include <QScrollArea> |
||||
#include <QSharedPointer> |
||||
#include <QVBoxLayout> |
||||
|
||||
#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<QString> roomList(const QString &id) const; |
||||
|
||||
void syncTags(const std::map<QString, RoomInfo> &info); |
||||
void setTagsForRoom(const QString &id, const std::vector<std::string> &tags); |
||||
std::vector<std::string> currentTags() const; |
||||
std::set<QString> 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<QString> &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<QString, QSharedPointer<CommunitiesListItem>> communities_; |
||||
}; |
@ -1,201 +0,0 @@ |
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "CommunitiesListItem.h" |
||||
|
||||
#include <QMenu> |
||||
#include <QMouseEvent> |
||||
|
||||
#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<qreal>(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<int>(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<int>(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)")); |
||||
} |
||||
} |
@ -1,107 +0,0 @@ |
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once |
||||
|
||||
#include <QSharedPointer> |
||||
#include <QWidget> |
||||
|
||||
#include <set> |
||||
|
||||
#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<QString> 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<QString> 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<QString> 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_; |
||||
}; |
@ -1,522 +0,0 @@ |
||||
// SPDX-FileCopyrightText: 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <QDateTime> |
||||
#include <QInputDialog> |
||||
#include <QMenu> |
||||
#include <QMouseEvent> |
||||
#include <QPainter> |
||||
#include <QtGlobal> |
||||
|
||||
#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<qreal>(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(); |
||||
} |
@ -1,210 +0,0 @@ |
||||
// SPDX-FileCopyrightText: 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once |
||||
|
||||
#include <QAction> |
||||
#include <QDateTime> |
||||
#include <QSharedPointer> |
||||
#include <QWidget> |
||||
|
||||
#include <mtx/responses/sync.hpp> |
||||
|
||||
#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; |
||||
}; |
@ -1,535 +0,0 @@ |
||||
// SPDX-FileCopyrightText: 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <limits> |
||||
#include <set> |
||||
|
||||
#include <QObject> |
||||
#include <QPainter> |
||||
#include <QScroller> |
||||
#include <QStyle> |
||||
#include <QStyleOption> |
||||
#include <QTimer> |
||||
|
||||
#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> 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<RoomInfoListItem> 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<QString, RoomInfo> &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<QString, RoomInfo> &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); |
||||
|
||||
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<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; |
||||
} |
||||
} |
||||
} |
||||
|
||||
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<RoomInfoListItem> &a, |
||||
const QSharedPointer<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->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<QString> &roomsToHide) |
||||
{ |
||||
setUpdatesEnabled(false); |
||||
for (int i = 0; i < contentsLayout_->count(); i++) { |
||||
auto widget = |
||||
qobject_cast<RoomInfoListItem *>(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<QString> &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<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); |
||||
} |
||||
|
||||
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<RoomInfoListItem> 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<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) { |
||||
auto topRoom = rooms_.find(item->roomId()); |
||||
if (topRoom != rooms_.end()) { |
||||
return std::pair<QString, QSharedPointer<RoomInfoListItem>>( |
||||
item->roomId(), topRoom->second); |
||||
} |
||||
} |
||||
} |
||||
|
||||
return {}; |
||||
} |
||||
|
||||
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); |
||||
} |
||||
} |
||||
} |
@ -1,101 +0,0 @@ |
||||
// SPDX-FileCopyrightText: 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once |
||||
|
||||
#include <QPushButton> |
||||
#include <QScrollArea> |
||||
#include <QSharedPointer> |
||||
#include <QVBoxLayout> |
||||
#include <QWidget> |
||||
|
||||
#include <set> |
||||
|
||||
#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> userSettings, QWidget *parent = nullptr); |
||||
|
||||
void initialize(const QMap<QString, RoomInfo> &info); |
||||
void sync(const std::map<QString, RoomInfo> &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<QString> &rooms); |
||||
//! Show all the available rooms.
|
||||
void removeFilter(const std::set<QString> &roomsToHide); |
||||
void updateRoom(const QString &room_id, const RoomInfo &info); |
||||
void cleanupInvites(const QHash<QString, RoomInfo> &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<QString, bool> &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<QString, QSharedPointer<RoomInfoListItem>> 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<QString, QSharedPointer<RoomInfoListItem>> rooms_; |
||||
std::vector<QSharedPointer<RoomInfoListItem>> rooms_sort_cache_; |
||||
QString selectedRoom_; |
||||
|
||||
bool isSortPending_ = false; |
||||
}; |
@ -1,89 +0,0 @@ |
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <QLabel> |
||||
#include <QPaintEvent> |
||||
#include <QPainter> |
||||
#include <QStyleOption> |
||||
|
||||
#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); |
||||
} |
@ -1,66 +0,0 @@ |
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once |
||||
|
||||
#include <QWidget> |
||||
|
||||
#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_; |
||||
}; |
@ -1,164 +0,0 @@ |
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <QPaintEvent> |
||||
#include <QPainter> |
||||
#include <QStyleOption> |
||||
|
||||
#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<RoomSearchResult> &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<RoomItem *>(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<RoomItem *>(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<PopupItem *>(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<PopupItem *>(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; |
||||
} |
||||
} |
@ -1,53 +0,0 @@ |
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once |
||||
|
||||
#include <QWidget> |
||||
|
||||
#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<RoomSearchResult> &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; |
||||
}; |
@ -1,178 +0,0 @@ |
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <QPaintEvent> |
||||
#include <QPainter> |
||||
#include <QScrollArea> |
||||
#include <QStyleOption> |
||||
#include <QTabWidget> |
||||
#include <QTimer> |
||||
#include <QVBoxLayout> |
||||
|
||||
#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<QString, mtx::responses::Notifications> ¬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<QWidget *>()) { |
||||
delete widget; |
||||
} |
||||
for (auto widget : local_scroll_layout_->findChildren<QWidget *>()) { |
||||
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); |
||||
} |
@ -1,49 +0,0 @@ |
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once |
||||
|
||||
#include <mtx/responses/notifications.hpp> |
||||
|
||||
#include <QMap> |
||||
#include <QString> |
||||
#include <QWidget> |
||||
|
||||
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<QString, mtx::responses::Notifications> ¬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_; |
||||
}; |
||||
} |
Loading…
Reference in new issue