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