message bubbles, avatar rounding, roster adjusments

This commit is contained in:
Blue 2022-01-09 01:28:29 +03:00
parent 9ac0ca10f3
commit 8a2658e4fc
Signed by: blue
GPG Key ID: 9B203B252A63EE38
7 changed files with 158 additions and 49 deletions

View File

@ -39,7 +39,11 @@ Squawk::Squawk(QWidget *parent) :
m_ui->setupUi(this); m_ui->setupUi(this);
m_ui->roster->setModel(&rosterModel); m_ui->roster->setModel(&rosterModel);
m_ui->roster->setContextMenuPolicy(Qt::CustomContextMenu); m_ui->roster->setContextMenuPolicy(Qt::CustomContextMenu);
m_ui->roster->setColumnWidth(1, 30); if (QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1) {
m_ui->roster->setColumnWidth(1, 52);
} else {
m_ui->roster->setColumnWidth(1, 26);
}
m_ui->roster->setIconSize(QSize(20, 20)); m_ui->roster->setIconSize(QSize(20, 20));
m_ui->roster->header()->setStretchLastSection(false); m_ui->roster->header()->setStretchLastSection(false);
m_ui->roster->header()->setSectionResizeMode(0, QHeaderView::Stretch); m_ui->roster->header()->setSectionResizeMode(0, QHeaderView::Stretch);

View File

@ -73,6 +73,9 @@
<property name="bottomMargin"> <property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </property>
<property name="spacing">
<number>0</number>
</property>
<item row="2" column="1"> <item row="2" column="1">
<widget class="QTreeView" name="roster"> <widget class="QTreeView" name="roster">
<property name="frameShape"> <property name="frameShape">
@ -96,16 +99,31 @@
<property name="allColumnsShowFocus"> <property name="allColumnsShowFocus">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="headerHidden">
<bool>true</bool>
</property>
<property name="expandsOnDoubleClick"> <property name="expandsOnDoubleClick">
<bool>false</bool> <bool>false</bool>
</property> </property>
<attribute name="headerVisible"> <attribute name="headerVisible">
<bool>false</bool> <bool>false</bool>
</attribute> </attribute>
<attribute name="headerMinimumSectionSize">
<number>10</number>
</attribute>
<attribute name="headerStretchLastSection">
<bool>false</bool>
</attribute>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="1" column="1">
<widget class="QComboBox" name="comboBox"> <widget class="QComboBox" name="comboBox">
<property name="minimumSize">
<size>
<width>0</width>
<height>30</height>
</size>
</property>
<property name="editable"> <property name="editable">
<bool>false</bool> <bool>false</bool>
</property> </property>
@ -115,6 +133,9 @@
<property name="currentIndex"> <property name="currentIndex">
<number>-1</number> <number>-1</number>
</property> </property>
<property name="frame">
<bool>true</bool>
</property>
</widget> </widget>
</item> </item>
</layout> </layout>
@ -162,7 +183,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>718</width> <width>718</width>
<height>27</height> <height>32</height>
</rect> </rect>
</property> </property>
<widget class="QMenu" name="menuSettings"> <widget class="QMenu" name="menuSettings">

View File

@ -25,12 +25,16 @@
#include <QTimer> #include <QTimer>
#include <QFileDialog> #include <QFileDialog>
#include <QMimeDatabase> #include <QMimeDatabase>
#include <unistd.h>
#include <QAbstractTextDocumentLayout> #include <QAbstractTextDocumentLayout>
#include <QCoreApplication> #include <QCoreApplication>
#include <QTemporaryFile> #include <QTemporaryFile>
#include <QDir> #include <QDir>
#include <QMenu> #include <QMenu>
#include <QBitmap>
#include <unistd.h>
constexpr QSize avatarSize(50, 50);
Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, const QString pJid, const QString pRes, QWidget* parent): Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, const QString pJid, const QString pRes, QWidget* parent):
QWidget(parent), QWidget(parent),
@ -348,11 +352,25 @@ void Conversation::onClearButton()
void Conversation::setAvatar(const QString& path) void Conversation::setAvatar(const QString& path)
{ {
QPixmap pixmap;
if (path.size() == 0) { if (path.size() == 0) {
m_ui->avatar->setPixmap(Shared::icon("user", true).pixmap(QSize(50, 50))); pixmap = Shared::icon("user", true).pixmap(avatarSize);
} else { } else {
m_ui->avatar->setPixmap(path); pixmap = QPixmap(path).scaled(avatarSize);
} }
QPixmap result(avatarSize);
result.fill(Qt::transparent);
QPainter painter(&result);
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::SmoothPixmapTransform);
QPainterPath maskPath;
maskPath.addEllipse(0, 0, avatarSize.width(), avatarSize.height());
painter.setClipPath(maskPath);
painter.drawPixmap(0, 0, pixmap);
m_ui->avatar->setPixmap(result);
} }
void Conversation::onTextEditDocSizeChanged(const QSizeF& size) void Conversation::onTextEditDocSizeChanged(const QSizeF& size)

View File

@ -141,6 +141,14 @@ protected:
ShadowOverlay shadow; ShadowOverlay shadow;
QMenu* contextMenu; QMenu* contextMenu;
private:
static bool painterInitialized;
static QPainterPath* avatarMask;
static QPixmap* avatarPixmap;
static QPainter* avatarPainter;
}; };
#endif // CONVERSATION_H #endif // CONVERSATION_H

View File

@ -31,10 +31,6 @@ constexpr int approximateSingleMessageHeight = 20;
constexpr int progressSize = 70; constexpr int progressSize = 70;
constexpr int dateDeviderMargin = 10; constexpr int dateDeviderMargin = 10;
constexpr int avatarHeight = 50;
constexpr int margin = 6;
constexpr int halfMargin = 3;
const std::set<int> FeedView::geometryChangingRoles = { const std::set<int> FeedView::geometryChangingRoles = {
Models::MessageFeed::Attach, Models::MessageFeed::Attach,
Models::MessageFeed::Text, Models::MessageFeed::Text,

View File

@ -18,6 +18,7 @@
#include <QDebug> #include <QDebug>
#include <QPainter> #include <QPainter>
#include <QPainterPath>
#include <QApplication> #include <QApplication>
#include <QMouseEvent> #include <QMouseEvent>
#include <QAbstractItemView> #include <QAbstractItemView>
@ -29,6 +30,11 @@ constexpr int avatarHeight = 50;
constexpr int margin = 6; constexpr int margin = 6;
constexpr int textMargin = 2; constexpr int textMargin = 2;
constexpr int statusIconSize = 16; constexpr int statusIconSize = 16;
constexpr float nickFontMultiplier = 1.1;
constexpr float dateFontMultiplier = 0.8;
constexpr int bubbleMargin = 6;
constexpr int bubbleBorderRadius = 3;
MessageDelegate::MessageDelegate(QObject* parent): MessageDelegate::MessageDelegate(QObject* parent):
QStyledItemDelegate(parent), QStyledItemDelegate(parent),
@ -101,9 +107,10 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
painter->save(); painter->save();
painter->setRenderHint(QPainter::Antialiasing, true); painter->setRenderHint(QPainter::Antialiasing, true);
if (option.state & QStyle::State_MouseOver) { // if (option.state & QStyle::State_MouseOver) {
painter->fillRect(option.rect, option.palette.brush(QPalette::Inactive, QPalette::Highlight)); // painter->fillRect(option.rect, option.palette.brush(QPalette::Inactive, QPalette::Highlight));
} // }
bool ntds = needToDrawSender(index, data); bool ntds = needToDrawSender(index, data);
if (ntds || option.rect.y() < 1) { if (ntds || option.rect.y() < 1) {
@ -118,6 +125,9 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
} else { } else {
opt.displayAlignment = Qt::AlignRight | Qt::AlignTop; opt.displayAlignment = Qt::AlignRight | Qt::AlignTop;
} }
QPoint bubbleBegin = messageRect.topLeft();
messageRect.adjust(bubbleMargin, bubbleMargin, -bubbleMargin, -bubbleMargin / 2);
opt.rect = messageRect; opt.rect = messageRect;
QSize messageSize(0, 0); QSize messageSize(0, 0);
@ -127,9 +137,6 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
bodySize = messageSize; bodySize = messageSize;
} }
if (ntds) {
messageSize.rheight() += nickMetrics.lineSpacing();
}
messageSize.rheight() += dateMetrics.height(); messageSize.rheight() += dateMetrics.height();
QString dateString = data.date.toLocalTime().toString("hh:mm"); QString dateString = data.date.toLocalTime().toString("hh:mm");
@ -156,45 +163,63 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
messageSize.setWidth(opt.rect.width()); messageSize.setWidth(opt.rect.width());
} }
QRect rect;
if (ntds) {
painter->setFont(nickFont);
painter->drawText(opt.rect, opt.displayAlignment, data.sender, &rect);
opt.rect.adjust(0, rect.height() + textMargin, 0, 0);
}
painter->save(); painter->save();
int storedY = opt.rect.y();
if (ntds) {
opt.rect.adjust(0, nickMetrics.lineSpacing() + textMargin, 0, 0);
}
int attWidth(0);
switch (data.attach.state) { switch (data.attach.state) {
case Models::none: case Models::none:
clearHelperWidget(data); //i can't imagine the situation where it's gonna be needed clearHelperWidget(data); //i can't imagine the situation where it's gonna be needed
break; //but it's a possible performance problem break; //but it's a possible performance problem
case Models::uploading: case Models::uploading:
paintPreview(data, painter, opt); attWidth = std::max(paintPreview(data, painter, opt), attWidth);
[[fallthrough]]; [[fallthrough]];
case Models::downloading: case Models::downloading:
paintBar(getBar(data), painter, data.sentByMe, opt); messageSize.setWidth(opt.rect.width());
messageSize.rheight() += barHeight + textMargin + opt.rect.y() - storedY;
paintBubble(data, painter, messageSize, opt, bubbleBegin);
attWidth = std::max(paintBar(getBar(data), painter, data.sentByMe, opt), attWidth);
break; break;
case Models::remote: case Models::remote:
paintButton(getButton(data), painter, data.sentByMe, opt); attWidth = std::max(paintButton(getButton(data), painter, data.sentByMe, opt), attWidth);
break; break;
case Models::ready: case Models::ready:
case Models::local: case Models::local:
clearHelperWidget(data); clearHelperWidget(data);
paintPreview(data, painter, opt); attWidth = std::max(paintPreview(data, painter, opt), attWidth);
break; break;
case Models::errorDownload: { case Models::errorDownload: {
paintButton(getButton(data), painter, data.sentByMe, opt); attWidth = std::max(paintButton(getButton(data), painter, data.sentByMe, opt), attWidth);
paintComment(data, painter, opt); attWidth = std::max(paintComment(data, painter, opt), attWidth);
} }
break; break;
case Models::errorUpload:{ case Models::errorUpload:{
clearHelperWidget(data); clearHelperWidget(data);
paintPreview(data, painter, opt); attWidth = std::max(paintPreview(data, painter, opt), attWidth);
paintComment(data, painter, opt); attWidth = std::max(paintComment(data, painter, opt), attWidth);
} }
break; break;
} }
painter->restore(); painter->restore();
if (data.attach.state != Models::uploading && data.attach.state != Models::downloading) {
messageSize.rheight() += opt.rect.y() - storedY;
messageSize.setWidth(std::max(attWidth, messageSize.width()));
paintBubble(data, painter, messageSize, opt, bubbleBegin);
}
QRect rect;
if (ntds) {
painter->setFont(nickFont);
int storedY2 = opt.rect.y();
opt.rect.setY(storedY);
painter->drawText(opt.rect, opt.displayAlignment, data.sender, &rect);
opt.rect.setY(storedY2);
}
int messageLeft = INT16_MAX; int messageLeft = INT16_MAX;
int messageRight = opt.rect.x() + messageSize.width(); int messageRight = opt.rect.x() + messageSize.width();
@ -256,6 +281,23 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
} }
} }
void MessageDelegate::paintBubble(const Models::FeedItem& data, QPainter* painter, const QSize& messageSize, QStyleOptionViewItem& option, QPoint bubbleBegin) const
{
painter->save();
if (data.sentByMe) {
bubbleBegin.setX(option.rect.topRight().x() - messageSize.width() - bubbleMargin);
painter->setBrush(option.palette.brush(QPalette::Inactive, QPalette::Highlight));
} else {
painter->setBrush(option.palette.brush(QPalette::Window));
}
QSize bubbleAddition(2 * bubbleMargin, 1.5 * bubbleMargin);
QRect bubble(bubbleBegin, messageSize + bubbleAddition);
painter->setPen(Qt::NoPen);
painter->drawRoundedRect(bubble, bubbleBorderRadius, bubbleBorderRadius);
painter->restore();
}
void MessageDelegate::paintAvatar(const Models::FeedItem& data, const QModelIndex& index, const QStyleOptionViewItem& option, QPainter* painter) const void MessageDelegate::paintAvatar(const Models::FeedItem& data, const QModelIndex& index, const QStyleOptionViewItem& option, QPainter* painter) const
{ {
int currentRow = index.row(); int currentRow = index.row();
@ -282,11 +324,22 @@ void MessageDelegate::paintAvatar(const Models::FeedItem& data, const QModelInde
y = std::min(0, rect.bottom() - margin - avatarHeight); y = std::min(0, rect.bottom() - margin - avatarHeight);
--currentRow; --currentRow;
} }
QPixmap pixmap = icon.pixmap(avatarHeight, avatarHeight);
QPainterPath path;
int ax;
if (data.sentByMe) { if (data.sentByMe) {
painter->drawPixmap(option.rect.width() - avatarHeight - margin, y + margin / 2, icon.pixmap(avatarHeight, avatarHeight)); ax = option.rect.width() - avatarHeight - margin;
} else { } else {
painter->drawPixmap(margin, y + margin / 2, icon.pixmap(avatarHeight, avatarHeight)); ax = margin;
} }
path.addEllipse(ax, y + margin / 2, avatarHeight, avatarHeight);
painter->save();
painter->setClipPath(path);
painter->drawPixmap(ax, y + margin / 2, pixmap);
painter->restore();
} }
bool MessageDelegate::needToDrawAvatar(const QModelIndex& index, const Models::FeedItem& data, const QStyleOptionViewItem& option) const bool MessageDelegate::needToDrawAvatar(const QModelIndex& index, const Models::FeedItem& data, const QStyleOptionViewItem& option) const
@ -309,7 +362,7 @@ bool MessageDelegate::needToDrawSender(const QModelIndex& index, const Models::F
QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
{ {
QRect messageRect = option.rect.adjusted(0, margin / 2, -(avatarHeight + 3 * margin), -margin / 2); QRect messageRect = option.rect.adjusted(bubbleMargin, margin / 2 + bubbleMargin, -(avatarHeight + 3 * margin + bubbleMargin), -(margin + bubbleMargin) / 2);
QStyleOptionViewItem opt = option; QStyleOptionViewItem opt = option;
opt.rect = messageRect; opt.rect = messageRect;
QVariant vi = index.data(Models::MessageFeed::Bulk); QVariant vi = index.data(Models::MessageFeed::Bulk);
@ -347,10 +400,10 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
} }
if (needToDrawSender(index, data)) { if (needToDrawSender(index, data)) {
messageSize.rheight() += nickMetrics.lineSpacing(); messageSize.rheight() += nickMetrics.lineSpacing() + textMargin;
messageSize.rheight() += textMargin;
} }
messageSize.rheight() += bubbleMargin + bubbleMargin / 2;
messageSize.rheight() += dateMetrics.height() > statusIconSize ? dateMetrics.height() : statusIconSize; messageSize.rheight() += dateMetrics.height() > statusIconSize ? dateMetrics.height() : statusIconSize;
// if (messageSize.height() < avatarHeight) { // if (messageSize.height() < avatarHeight) {
@ -372,17 +425,17 @@ void MessageDelegate::initializeFonts(const QFont& font)
float ndps = nickFont.pointSizeF(); float ndps = nickFont.pointSizeF();
if (ndps != -1) { if (ndps != -1) {
nickFont.setPointSizeF(ndps * 1.2); nickFont.setPointSizeF(ndps * nickFontMultiplier);
} else { } else {
nickFont.setPointSize(nickFont.pointSize() + 2); nickFont.setPointSize(nickFont.pointSize() * nickFontMultiplier);
} }
dateFont.setItalic(true); dateFont.setItalic(true);
float dps = dateFont.pointSizeF(); float dps = dateFont.pointSizeF();
if (dps != -1) { if (dps != -1) {
dateFont.setPointSizeF(dps * 0.8); dateFont.setPointSizeF(dps * dateFontMultiplier);
} else { } else {
dateFont.setPointSize(dateFont.pointSize() - 2); dateFont.setPointSize(dateFont.pointSize() * dateFontMultiplier);
} }
bodyMetrics = QFontMetrics(bodyFont); bodyMetrics = QFontMetrics(bodyFont);
@ -400,11 +453,11 @@ bool MessageDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, cons
return QStyledItemDelegate::editorEvent(event, model, option, index); return QStyledItemDelegate::editorEvent(event, model, option, index);
} }
void MessageDelegate::paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const int MessageDelegate::paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const
{ {
QPoint start; QPoint start;
if (sentByMe) { if (sentByMe) {
start = {option.rect.width() - btn->width(), option.rect.top()}; start = {option.rect.x() + option.rect.width() - btn->width(), option.rect.top()};
} else { } else {
start = option.rect.topLeft(); start = option.rect.topLeft();
} }
@ -415,9 +468,10 @@ void MessageDelegate::paintButton(QPushButton* btn, QPainter* painter, bool sent
btn->show(); btn->show();
option.rect.adjust(0, buttonHeight + textMargin, 0, 0); option.rect.adjust(0, buttonHeight + textMargin, 0, 0);
return btn->width();
} }
void MessageDelegate::paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const int MessageDelegate::paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const
{ {
painter->setFont(dateFont); painter->setFont(dateFont);
QColor q = painter->pen().color(); QColor q = painter->pen().color();
@ -426,9 +480,11 @@ void MessageDelegate::paintComment(const Models::FeedItem& data, QPainter* paint
QRect rect; QRect rect;
painter->drawText(option.rect, option.displayAlignment, data.attach.error, &rect); painter->drawText(option.rect, option.displayAlignment, data.attach.error, &rect);
option.rect.adjust(0, rect.height() + textMargin, 0, 0); option.rect.adjust(0, rect.height() + textMargin, 0, 0);
return rect.width();
} }
void MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const int MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const
{ {
QPoint start = option.rect.topLeft(); QPoint start = option.rect.topLeft();
bar->resize(option.rect.width(), barHeight); bar->resize(option.rect.width(), barHeight);
@ -437,9 +493,11 @@ void MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentBy
bar->render(painter, QPoint(), QRegion(), QWidget::DrawChildren); bar->render(painter, QPoint(), QRegion(), QWidget::DrawChildren);
option.rect.adjust(0, barHeight + textMargin, 0, 0); option.rect.adjust(0, barHeight + textMargin, 0, 0);
return option.rect.width();
} }
void MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const int MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const
{ {
Preview* preview = 0; Preview* preview = 0;
std::map<QString, Preview*>::iterator itr = previews->find(data.id); std::map<QString, Preview*>::iterator itr = previews->find(data.id);
@ -458,7 +516,10 @@ void MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* paint
emit invalidPath(data.id); //or deleted. This signal notifies the model, and the model notifies the core, preview can emit invalidPath(data.id); //or deleted. This signal notifies the model, and the model notifies the core, preview can
} //handle being invalid for as long as I need and can be even become valid again with a new path } //handle being invalid for as long as I need and can be even become valid again with a new path
option.rect.adjust(0, preview->size().height() + textMargin, 0, 0); QSize pSize(preview->size());
option.rect.adjust(0, pSize.height() + textMargin, 0, 0);
return pSize.width();
} }
QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const

View File

@ -61,11 +61,12 @@ signals:
void invalidPath(const QString& messageId) const; void invalidPath(const QString& messageId) const;
protected: protected:
void paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const; int paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const;
void paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const; int paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const;
void paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const; int paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const;
void paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const; int paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const;
void paintAvatar(const Models::FeedItem& data, const QModelIndex& index, const QStyleOptionViewItem& option, QPainter* painter) const; void paintAvatar(const Models::FeedItem& data, const QModelIndex& index, const QStyleOptionViewItem& option, QPainter* painter) const;
void paintBubble(const Models::FeedItem& data, QPainter* painter, const QSize& messageSize, QStyleOptionViewItem& option, QPoint bubbleBegin) const;
QPushButton* getButton(const Models::FeedItem& data) const; QPushButton* getButton(const Models::FeedItem& data) const;
QProgressBar* getBar(const Models::FeedItem& data) const; QProgressBar* getBar(const Models::FeedItem& data) const;
QLabel* getStatusIcon(const Models::FeedItem& data) const; QLabel* getStatusIcon(const Models::FeedItem& data) const;