mirror of https://github.com/Nheko-Reborn/nheko
Merge pull request #655 from LorenDB/qml-all-the-things
QML all the things, part 2: Read receipts dialogpull/662/head
commit
5b5a89b522
@ -0,0 +1,131 @@ |
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors |
||||
// |
||||
// SPDX-License-Identifier: GPL-3.0-or-later |
||||
|
||||
import QtQuick 2.15 |
||||
import QtQuick.Controls 2.15 |
||||
import QtQuick.Layouts 1.15 |
||||
import im.nheko 1.0 |
||||
|
||||
ApplicationWindow { |
||||
id: readReceiptsRoot |
||||
|
||||
property ReadReceiptsProxy readReceipts |
||||
property Room room |
||||
|
||||
x: MainWindow.x + (MainWindow.width / 2) - (width / 2) |
||||
y: MainWindow.y + (MainWindow.height / 2) - (height / 2) |
||||
height: 380 |
||||
width: 340 |
||||
minimumHeight: 380 |
||||
minimumWidth: headerTitle.width + 2 * Nheko.paddingMedium |
||||
palette: Nheko.colors |
||||
color: Nheko.colors.window |
||||
flags: Qt.Dialog |
||||
|
||||
Shortcut { |
||||
sequence: StandardKey.Cancel |
||||
onActivated: readReceiptsRoot.close() |
||||
} |
||||
|
||||
ColumnLayout { |
||||
anchors.fill: parent |
||||
anchors.margins: Nheko.paddingMedium |
||||
spacing: Nheko.paddingMedium |
||||
|
||||
Label { |
||||
id: headerTitle |
||||
|
||||
color: Nheko.colors.text |
||||
Layout.alignment: Qt.AlignCenter |
||||
text: qsTr("Read receipts") |
||||
font.pointSize: fontMetrics.font.pointSize * 1.5 |
||||
} |
||||
|
||||
ScrollView { |
||||
palette: Nheko.colors |
||||
padding: Nheko.paddingMedium |
||||
ScrollBar.horizontal.visible: false |
||||
Layout.fillHeight: true |
||||
Layout.minimumHeight: 200 |
||||
Layout.fillWidth: true |
||||
|
||||
ListView { |
||||
id: readReceiptsList |
||||
|
||||
clip: true |
||||
spacing: Nheko.paddingMedium |
||||
boundsBehavior: Flickable.StopAtBounds |
||||
model: readReceipts |
||||
|
||||
delegate: RowLayout { |
||||
spacing: Nheko.paddingMedium |
||||
|
||||
Avatar { |
||||
width: Nheko.avatarSize |
||||
height: Nheko.avatarSize |
||||
userid: model.mxid |
||||
url: model.avatarUrl.replace("mxc://", "image://MxcImage/") |
||||
displayName: model.displayName |
||||
onClicked: room.openUserProfile(model.mxid) |
||||
ToolTip.visible: avatarHover.hovered |
||||
ToolTip.text: model.mxid |
||||
|
||||
HoverHandler { |
||||
id: avatarHover |
||||
} |
||||
|
||||
} |
||||
|
||||
ColumnLayout { |
||||
spacing: Nheko.paddingSmall |
||||
|
||||
Label { |
||||
text: model.displayName |
||||
color: TimelineManager.userColor(model ? model.mxid : "", Nheko.colors.window) |
||||
font.pointSize: fontMetrics.font.pointSize |
||||
ToolTip.visible: displayNameHover.hovered |
||||
ToolTip.text: model.mxid |
||||
|
||||
TapHandler { |
||||
onSingleTapped: room.openUserProfile(userId) |
||||
} |
||||
|
||||
CursorShape { |
||||
anchors.fill: parent |
||||
cursorShape: Qt.PointingHandCursor |
||||
} |
||||
|
||||
HoverHandler { |
||||
id: displayNameHover |
||||
} |
||||
|
||||
} |
||||
|
||||
Label { |
||||
text: model.timestamp |
||||
color: Nheko.colors.buttonText |
||||
font.pointSize: fontMetrics.font.pointSize * 0.9 |
||||
} |
||||
|
||||
Item { |
||||
Layout.fillHeight: true |
||||
Layout.fillWidth: true |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
footer: DialogButtonBox { |
||||
standardButtons: DialogButtonBox.Ok |
||||
onAccepted: readReceiptsRoot.close() |
||||
} |
||||
|
||||
} |
@ -0,0 +1,131 @@ |
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "ReadReceiptsModel.h" |
||||
|
||||
#include <QLocale> |
||||
|
||||
#include "Cache.h" |
||||
#include "Cache_p.h" |
||||
#include "Logging.h" |
||||
#include "Utils.h" |
||||
|
||||
ReadReceiptsModel::ReadReceiptsModel(QString event_id, QString room_id, QObject *parent) |
||||
: QAbstractListModel{parent} |
||||
, event_id_{event_id} |
||||
, room_id_{room_id} |
||||
{ |
||||
try { |
||||
addUsers(cache::readReceipts(event_id_, room_id_)); |
||||
} catch (const lmdb::error &) { |
||||
nhlog::db()->warn("failed to retrieve read receipts for {} {}", |
||||
event_id_.toStdString(), |
||||
room_id_.toStdString()); |
||||
|
||||
return; |
||||
} |
||||
|
||||
connect(cache::client(), &Cache::newReadReceipts, this, &ReadReceiptsModel::update); |
||||
} |
||||
|
||||
void |
||||
ReadReceiptsModel::update() |
||||
{ |
||||
try { |
||||
addUsers(cache::readReceipts(event_id_, room_id_)); |
||||
} catch (const lmdb::error &) { |
||||
nhlog::db()->warn("failed to retrieve read receipts for {} {}", |
||||
event_id_.toStdString(), |
||||
room_id_.toStdString()); |
||||
|
||||
return; |
||||
} |
||||
} |
||||
|
||||
QHash<int, QByteArray> |
||||
ReadReceiptsModel::roleNames() const |
||||
{ |
||||
// Note: RawTimestamp is purposely not included here
|
||||
return { |
||||
{Mxid, "mxid"}, |
||||
{DisplayName, "displayName"}, |
||||
{AvatarUrl, "avatarUrl"}, |
||||
{Timestamp, "timestamp"}, |
||||
}; |
||||
} |
||||
|
||||
QVariant |
||||
ReadReceiptsModel::data(const QModelIndex &index, int role) const |
||||
{ |
||||
if (!index.isValid() || index.row() >= (int)readReceipts_.size() || index.row() < 0) |
||||
return {}; |
||||
|
||||
switch (role) { |
||||
case Mxid: |
||||
return readReceipts_[index.row()].first; |
||||
case DisplayName: |
||||
return cache::displayName(room_id_, readReceipts_[index.row()].first); |
||||
case AvatarUrl: |
||||
return cache::avatarUrl(room_id_, readReceipts_[index.row()].first); |
||||
case Timestamp: |
||||
return dateFormat(readReceipts_[index.row()].second); |
||||
case RawTimestamp: |
||||
return readReceipts_[index.row()].second; |
||||
default: |
||||
return {}; |
||||
} |
||||
} |
||||
|
||||
void |
||||
ReadReceiptsModel::addUsers( |
||||
const std::multimap<uint64_t, std::string, std::greater<uint64_t>> &users) |
||||
{ |
||||
auto newReceipts = users.size() - readReceipts_.size(); |
||||
|
||||
if (newReceipts > 0) { |
||||
beginInsertRows( |
||||
QModelIndex{}, readReceipts_.size(), readReceipts_.size() + newReceipts - 1); |
||||
|
||||
for (const auto &user : users) { |
||||
QPair<QString, QDateTime> item = { |
||||
QString::fromStdString(user.second), |
||||
QDateTime::fromMSecsSinceEpoch(user.first)}; |
||||
if (!readReceipts_.contains(item)) |
||||
readReceipts_.push_back(item); |
||||
} |
||||
|
||||
endInsertRows(); |
||||
} |
||||
} |
||||
|
||||
QString |
||||
ReadReceiptsModel::dateFormat(const QDateTime &then) const |
||||
{ |
||||
auto now = QDateTime::currentDateTime(); |
||||
auto days = then.daysTo(now); |
||||
|
||||
if (days == 0) |
||||
return QLocale::system().toString(then.time(), QLocale::ShortFormat); |
||||
else if (days < 2) |
||||
return tr("Yesterday, %1") |
||||
.arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); |
||||
else if (days < 7) |
||||
//: %1 is the name of the current day, %2 is the time the read receipt was read. The
|
||||
//: result may look like this: Monday, 7:15
|
||||
return QString("%1, %2") |
||||
.arg(then.toString("dddd")) |
||||
.arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); |
||||
|
||||
return QLocale::system().toString(then.time(), QLocale::ShortFormat); |
||||
} |
||||
|
||||
ReadReceiptsProxy::ReadReceiptsProxy(QString event_id, QString room_id, QObject *parent) |
||||
: QSortFilterProxyModel{parent} |
||||
, model_{event_id, room_id, this} |
||||
{ |
||||
setSourceModel(&model_); |
||||
setSortRole(ReadReceiptsModel::RawTimestamp); |
||||
sort(0, Qt::DescendingOrder); |
||||
setDynamicSortFilter(true); |
||||
} |
@ -0,0 +1,73 @@ |
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef READRECEIPTSMODEL_H |
||||
#define READRECEIPTSMODEL_H |
||||
|
||||
#include <QAbstractListModel> |
||||
#include <QDateTime> |
||||
#include <QObject> |
||||
#include <QSortFilterProxyModel> |
||||
#include <QString> |
||||
|
||||
class ReadReceiptsModel : public QAbstractListModel |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
enum Roles |
||||
{ |
||||
Mxid, |
||||
DisplayName, |
||||
AvatarUrl, |
||||
Timestamp, |
||||
RawTimestamp, |
||||
}; |
||||
|
||||
explicit ReadReceiptsModel(QString event_id, QString room_id, QObject *parent = nullptr); |
||||
|
||||
QString eventId() const { return event_id_; } |
||||
QString roomId() const { return room_id_; } |
||||
|
||||
QHash<int, QByteArray> roleNames() const override; |
||||
int rowCount(const QModelIndex &parent) const override |
||||
{ |
||||
Q_UNUSED(parent) |
||||
return readReceipts_.size(); |
||||
} |
||||
QVariant data(const QModelIndex &index, int role) const override; |
||||
|
||||
public slots: |
||||
void addUsers(const std::multimap<uint64_t, std::string, std::greater<uint64_t>> &users); |
||||
void update(); |
||||
|
||||
private: |
||||
QString dateFormat(const QDateTime &then) const; |
||||
|
||||
QString event_id_; |
||||
QString room_id_; |
||||
QVector<QPair<QString, QDateTime>> readReceipts_; |
||||
}; |
||||
|
||||
class ReadReceiptsProxy : public QSortFilterProxyModel |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
Q_PROPERTY(QString eventId READ eventId CONSTANT) |
||||
Q_PROPERTY(QString roomId READ roomId CONSTANT) |
||||
|
||||
public: |
||||
explicit ReadReceiptsProxy(QString event_id, QString room_id, QObject *parent = nullptr); |
||||
|
||||
QString eventId() const { return event_id_; } |
||||
QString roomId() const { return room_id_; } |
||||
|
||||
private: |
||||
QString event_id_; |
||||
QString room_id_; |
||||
|
||||
ReadReceiptsModel model_; |
||||
}; |
||||
|
||||
#endif // READRECEIPTSMODEL_H
|
@ -1,179 +0,0 @@ |
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <QDebug> |
||||
#include <QIcon> |
||||
#include <QLabel> |
||||
#include <QListWidgetItem> |
||||
#include <QPainter> |
||||
#include <QPushButton> |
||||
#include <QShortcut> |
||||
#include <QStyleOption> |
||||
#include <QTimer> |
||||
#include <QVBoxLayout> |
||||
|
||||
#include "dialogs/ReadReceipts.h" |
||||
|
||||
#include "AvatarProvider.h" |
||||
#include "Cache.h" |
||||
#include "ChatPage.h" |
||||
#include "Config.h" |
||||
#include "Utils.h" |
||||
#include "ui/Avatar.h" |
||||
|
||||
using namespace dialogs; |
||||
|
||||
ReceiptItem::ReceiptItem(QWidget *parent, |
||||
const QString &user_id, |
||||
uint64_t timestamp, |
||||
const QString &room_id) |
||||
: QWidget(parent) |
||||
{ |
||||
topLayout_ = new QHBoxLayout(this); |
||||
topLayout_->setMargin(0); |
||||
|
||||
textLayout_ = new QVBoxLayout; |
||||
textLayout_->setMargin(0); |
||||
textLayout_->setSpacing(conf::modals::TEXT_SPACING); |
||||
|
||||
QFont nameFont; |
||||
nameFont.setPointSizeF(nameFont.pointSizeF() * 1.1); |
||||
|
||||
auto displayName = cache::displayName(room_id, user_id); |
||||
|
||||
avatar_ = new Avatar(this, 44); |
||||
avatar_->setLetter(utils::firstChar(displayName)); |
||||
|
||||
// If it's a matrix id we use the second letter.
|
||||
if (displayName.size() > 1 && displayName.at(0) == '@') |
||||
avatar_->setLetter(QChar(displayName.at(1))); |
||||
|
||||
userName_ = new QLabel(displayName, this); |
||||
userName_->setFont(nameFont); |
||||
|
||||
timestamp_ = new QLabel(dateFormat(QDateTime::fromMSecsSinceEpoch(timestamp)), this); |
||||
|
||||
textLayout_->addWidget(userName_); |
||||
textLayout_->addWidget(timestamp_); |
||||
|
||||
topLayout_->addWidget(avatar_); |
||||
topLayout_->addLayout(textLayout_, 1); |
||||
|
||||
avatar_->setImage(ChatPage::instance()->currentRoom(), user_id); |
||||
} |
||||
|
||||
void |
||||
ReceiptItem::paintEvent(QPaintEvent *) |
||||
{ |
||||
QStyleOption opt; |
||||
opt.init(this); |
||||
QPainter p(this); |
||||
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); |
||||
} |
||||
|
||||
QString |
||||
ReceiptItem::dateFormat(const QDateTime &then) const |
||||
{ |
||||
auto now = QDateTime::currentDateTime(); |
||||
auto days = then.daysTo(now); |
||||
|
||||
if (days == 0) |
||||
return tr("Today %1") |
||||
.arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); |
||||
else if (days < 2) |
||||
return tr("Yesterday %1") |
||||
.arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); |
||||
else if (days < 7) |
||||
return QString("%1 %2") |
||||
.arg(then.toString("dddd")) |
||||
.arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); |
||||
|
||||
return QLocale::system().toString(then.time(), QLocale::ShortFormat); |
||||
} |
||||
|
||||
ReadReceipts::ReadReceipts(QWidget *parent) |
||||
: QFrame(parent) |
||||
{ |
||||
setAutoFillBackground(true); |
||||
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); |
||||
setWindowModality(Qt::WindowModal); |
||||
setAttribute(Qt::WA_DeleteOnClose, true); |
||||
|
||||
auto layout = new QVBoxLayout(this); |
||||
layout->setSpacing(conf::modals::WIDGET_SPACING); |
||||
layout->setMargin(conf::modals::WIDGET_MARGIN); |
||||
|
||||
userList_ = new QListWidget; |
||||
userList_->setFrameStyle(QFrame::NoFrame); |
||||
userList_->setSelectionMode(QAbstractItemView::NoSelection); |
||||
userList_->setSpacing(conf::modals::TEXT_SPACING); |
||||
|
||||
QFont largeFont; |
||||
largeFont.setPointSizeF(largeFont.pointSizeF() * 1.5); |
||||
|
||||
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); |
||||
setMinimumHeight(userList_->sizeHint().height() * 2); |
||||
setMinimumWidth(std::max(userList_->sizeHint().width() + 4 * conf::modals::WIDGET_MARGIN, |
||||
QFontMetrics(largeFont).averageCharWidth() * 30 - |
||||
2 * conf::modals::WIDGET_MARGIN)); |
||||
|
||||
QFont font; |
||||
font.setPointSizeF(font.pointSizeF() * conf::modals::LABEL_MEDIUM_SIZE_RATIO); |
||||
|
||||
topLabel_ = new QLabel(tr("Read receipts"), this); |
||||
topLabel_->setAlignment(Qt::AlignCenter); |
||||
topLabel_->setFont(font); |
||||
|
||||
auto okBtn = new QPushButton(tr("Close"), this); |
||||
|
||||
auto buttonLayout = new QHBoxLayout(); |
||||
buttonLayout->setSpacing(15); |
||||
buttonLayout->addStretch(1); |
||||
buttonLayout->addWidget(okBtn); |
||||
|
||||
layout->addWidget(topLabel_); |
||||
layout->addWidget(userList_); |
||||
layout->addLayout(buttonLayout); |
||||
|
||||
auto closeShortcut = new QShortcut(QKeySequence(QKeySequence::Cancel), this); |
||||
connect(closeShortcut, &QShortcut::activated, this, &ReadReceipts::close); |
||||
connect(okBtn, &QPushButton::clicked, this, &ReadReceipts::close); |
||||
} |
||||
|
||||
void |
||||
ReadReceipts::addUsers(const std::multimap<uint64_t, std::string, std::greater<uint64_t>> &receipts) |
||||
{ |
||||
// We want to remove any previous items that have been set.
|
||||
userList_->clear(); |
||||
|
||||
for (const auto &receipt : receipts) { |
||||
auto user = new ReceiptItem(this, |
||||
QString::fromStdString(receipt.second), |
||||
receipt.first, |
||||
ChatPage::instance()->currentRoom()); |
||||
auto item = new QListWidgetItem(userList_); |
||||
|
||||
item->setSizeHint(user->minimumSizeHint()); |
||||
item->setFlags(Qt::NoItemFlags); |
||||
item->setTextAlignment(Qt::AlignCenter); |
||||
|
||||
userList_->setItemWidget(item, user); |
||||
} |
||||
} |
||||
|
||||
void |
||||
ReadReceipts::paintEvent(QPaintEvent *) |
||||
{ |
||||
QStyleOption opt; |
||||
opt.init(this); |
||||
QPainter p(this); |
||||
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); |
||||
} |
||||
|
||||
void |
||||
ReadReceipts::hideEvent(QHideEvent *event) |
||||
{ |
||||
userList_->clear(); |
||||
QFrame::hideEvent(event); |
||||
} |
@ -1,61 +0,0 @@ |
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once |
||||
|
||||
#include <QDateTime> |
||||
#include <QFrame> |
||||
|
||||
class Avatar; |
||||
class QLabel; |
||||
class QListWidget; |
||||
class QHBoxLayout; |
||||
class QVBoxLayout; |
||||
|
||||
namespace dialogs { |
||||
|
||||
class ReceiptItem : public QWidget |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
ReceiptItem(QWidget *parent, |
||||
const QString &user_id, |
||||
uint64_t timestamp, |
||||
const QString &room_id); |
||||
|
||||
protected: |
||||
void paintEvent(QPaintEvent *) override; |
||||
|
||||
private: |
||||
QString dateFormat(const QDateTime &then) const; |
||||
|
||||
QHBoxLayout *topLayout_; |
||||
QVBoxLayout *textLayout_; |
||||
|
||||
Avatar *avatar_; |
||||
|
||||
QLabel *userName_; |
||||
QLabel *timestamp_; |
||||
}; |
||||
|
||||
class ReadReceipts : public QFrame |
||||
{ |
||||
Q_OBJECT |
||||
public: |
||||
explicit ReadReceipts(QWidget *parent = nullptr); |
||||
|
||||
public slots: |
||||
void addUsers(const std::multimap<uint64_t, std::string, std::greater<uint64_t>> &users); |
||||
|
||||
protected: |
||||
void paintEvent(QPaintEvent *event) override; |
||||
void hideEvent(QHideEvent *event) override; |
||||
|
||||
private: |
||||
QLabel *topLabel_; |
||||
|
||||
QListWidget *userList_; |
||||
}; |
||||
} // dialogs
|
@ -1,168 +0,0 @@ |
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <QPainter> |
||||
#include <QPainterPath> |
||||
#include <QSettings> |
||||
|
||||
#include "AvatarProvider.h" |
||||
#include "Utils.h" |
||||
#include "ui/Avatar.h" |
||||
|
||||
Avatar::Avatar(QWidget *parent, int size) |
||||
: QWidget(parent) |
||||
, size_(size) |
||||
{ |
||||
type_ = ui::AvatarType::Letter; |
||||
letter_ = "A"; |
||||
|
||||
QFont _font(font()); |
||||
_font.setPointSizeF(ui::FontSize); |
||||
setFont(_font); |
||||
|
||||
QSizePolicy policy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); |
||||
setSizePolicy(policy); |
||||
} |
||||
|
||||
QColor |
||||
Avatar::textColor() const |
||||
{ |
||||
if (!text_color_.isValid()) |
||||
return QColor("black"); |
||||
|
||||
return text_color_; |
||||
} |
||||
|
||||
QColor |
||||
Avatar::backgroundColor() const |
||||
{ |
||||
if (!text_color_.isValid()) |
||||
return QColor("white"); |
||||
|
||||
return background_color_; |
||||
} |
||||
|
||||
QSize |
||||
Avatar::sizeHint() const |
||||
{ |
||||
return QSize(size_ + 2, size_ + 2); |
||||
} |
||||
|
||||
void |
||||
Avatar::setTextColor(const QColor &color) |
||||
{ |
||||
text_color_ = color; |
||||
} |
||||
|
||||
void |
||||
Avatar::setBackgroundColor(const QColor &color) |
||||
{ |
||||
background_color_ = color; |
||||
} |
||||
|
||||
void |
||||
Avatar::setLetter(const QString &letter) |
||||
{ |
||||
letter_ = letter; |
||||
type_ = ui::AvatarType::Letter; |
||||
update(); |
||||
} |
||||
|
||||
void |
||||
Avatar::setImage(const QString &avatar_url) |
||||
{ |
||||
avatar_url_ = avatar_url; |
||||
AvatarProvider::resolve(avatar_url, |
||||
static_cast<int>(size_ * pixmap_.devicePixelRatio()), |
||||
this, |
||||
[this, requestedRatio = pixmap_.devicePixelRatio()](QPixmap pm) { |
||||
if (pm.isNull()) |
||||
return; |
||||
type_ = ui::AvatarType::Image; |
||||
pixmap_ = pm; |
||||
pixmap_.setDevicePixelRatio(requestedRatio); |
||||
update(); |
||||
}); |
||||
} |
||||
|
||||
void |
||||
Avatar::setImage(const QString &room, const QString &user) |
||||
{ |
||||
room_ = room; |
||||
user_ = user; |
||||
AvatarProvider::resolve(room, |
||||
user, |
||||
static_cast<int>(size_ * pixmap_.devicePixelRatio()), |
||||
this, |
||||
[this, requestedRatio = pixmap_.devicePixelRatio()](QPixmap pm) { |
||||
if (pm.isNull()) |
||||
return; |
||||
type_ = ui::AvatarType::Image; |
||||
pixmap_ = pm; |
||||
pixmap_.setDevicePixelRatio(requestedRatio); |
||||
update(); |
||||
}); |
||||
} |
||||
|
||||
void |
||||
Avatar::setDevicePixelRatio(double ratio) |
||||
{ |
||||
if (type_ == ui::AvatarType::Image && abs(pixmap_.devicePixelRatio() - ratio) > 0.01) { |
||||
pixmap_ = pixmap_.scaled(QSize(size_, size_) * ratio); |
||||
pixmap_.setDevicePixelRatio(ratio); |
||||
|
||||
if (!avatar_url_.isEmpty()) |
||||
setImage(avatar_url_); |
||||
else |
||||
setImage(room_, user_); |
||||
} |
||||
} |
||||
|
||||
void |
||||
Avatar::paintEvent(QPaintEvent *) |
||||
{ |
||||
bool rounded = QSettings().value(QStringLiteral("user/avatar_circles"), true).toBool(); |
||||
|
||||
QPainter painter(this); |
||||
|
||||
painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform | |
||||
QPainter::TextAntialiasing); |
||||
|
||||
QRectF r = rect(); |
||||
const int hs = size_ / 2; |
||||
|
||||
if (type_ != ui::AvatarType::Image) { |
||||
QBrush brush; |
||||
brush.setStyle(Qt::SolidPattern); |
||||
brush.setColor(backgroundColor()); |
||||
|
||||
painter.setPen(Qt::NoPen); |
||||
painter.setBrush(brush); |
||||
rounded ? painter.drawEllipse(r) : painter.drawRoundedRect(r, 3, 3); |
||||
} else if (painter.isActive()) { |
||||
setDevicePixelRatio(painter.device()->devicePixelRatioF()); |
||||
} |
||||
|
||||
switch (type_) { |
||||
case ui::AvatarType::Image: { |
||||
QPainterPath ppath; |
||||
|
||||
rounded ? ppath.addEllipse(width() / 2 - hs, height() / 2 - hs, size_, size_) |
||||
: ppath.addRoundedRect(r, 3, 3); |
||||
|
||||
painter.setClipPath(ppath); |
||||
painter.drawPixmap(QRect(width() / 2 - hs, height() / 2 - hs, size_, size_), |
||||
pixmap_); |
||||
break; |
||||
} |
||||
case ui::AvatarType::Letter: { |
||||
painter.setPen(textColor()); |
||||
painter.setBrush(Qt::NoBrush); |
||||
painter.drawText(r.translated(0, -1), Qt::AlignCenter, letter_); |
||||
break; |
||||
} |
||||
default: |
||||
break; |
||||
} |
||||
} |
@ -1,48 +0,0 @@ |
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once |
||||
|
||||
#include <QImage> |
||||
#include <QPixmap> |
||||
#include <QWidget> |
||||
|
||||
#include "Theme.h" |
||||
|
||||
class Avatar : public QWidget |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
Q_PROPERTY(QColor textColor WRITE setTextColor READ textColor) |
||||
Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor) |
||||
|
||||
public: |
||||
explicit Avatar(QWidget *parent = nullptr, int size = ui::AvatarSize); |
||||
|
||||
void setBackgroundColor(const QColor &color); |
||||
void setImage(const QString &avatar_url); |
||||
void setImage(const QString &room, const QString &user); |
||||
void setLetter(const QString &letter); |
||||
void setTextColor(const QColor &color); |
||||
void setDevicePixelRatio(double ratio); |
||||
|
||||
QColor backgroundColor() const; |
||||
QColor textColor() const; |
||||
|
||||
QSize sizeHint() const override; |
||||
|
||||
protected: |
||||
void paintEvent(QPaintEvent *event) override; |
||||
|
||||
private: |
||||
void init(); |
||||
|
||||
ui::AvatarType type_; |
||||
QString letter_; |
||||
QString avatar_url_, room_, user_; |
||||
QColor background_color_; |
||||
QColor text_color_; |
||||
QPixmap pixmap_; |
||||
int size_; |
||||
}; |
Loading…
Reference in new issue