You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

251 lines
8.5 KiB

#include "messagedelegate.h"
#include <QPainter>
#include <QPainterPath>
#include <QApplication>
#include <QAbstractItemView>
#include <QFontMetrics>
MessageDelegate::MessageDelegate(QObject *parent)
: QStyledItemDelegate(parent) {}
QSize MessageDelegate::sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const {
Layout L = calcLayout(option, index);
return QSize(L.viewWidth, L.totalHeight);
}
static int measureTextHeight(const QFont &font, const QString &text, int width) {
if (text.isEmpty() || width <= 0) return 0;
QRect r = QFontMetrics(font).boundingRect(QRect(0, 0, width, 100000),
Qt::AlignLeft | Qt::TextWordWrap, text);
return r.height();
}
MessageDelegate::Layout MessageDelegate::calcLayout(
const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Layout L{};
L.viewWidth = 300;
if (option.widget) {
auto *v = qobject_cast<const QAbstractItemView *>(option.widget);
if (v) L.viewWidth = v->viewport()->width();
}
if (L.viewWidth <= 0) L.viewWidth = 300;
bool hasQuote = index.data(MsgHasQuoteRole).toBool();
QString text = index.data(MsgTextRole).toString();
QString qText = hasQuote ? index.data(MsgQuoteTextRole).toString() : QString();
int bubbleW = L.viewWidth - kMarginH - kAvatar - kGap - kMarginH;
if (bubbleW < 100) bubbleW = 100;
L.bubbleWidth = bubbleW;
int textW = bubbleW - 2 * kPadH;
int qH = 0;
if (hasQuote) {
int qTextW = bubbleW - 2 * kPadH - kQuoteBarW - kQuotePadH;
int qTextH = measureTextHeight(option.font, qText, qTextW);
int qOneLineH = QFontMetrics(option.font).height();
qH = kQuotePadV + qOneLineH + (qText.isEmpty() ? 0 : 2 + qTextH) + kQuotePadV;
}
int textH = measureTextHeight(option.font, text, textW);
int bubbleH = kPadTop + (hasQuote ? qH + kQuoteGap : 0) + textH + kStatusH + kPadBot;
L.totalHeight = qMax(bubbleH, kAvatar) + kItemGap;
int contentLeft = kMarginH + kAvatar + kGap;
L.bubbleRect = QRect(contentLeft, 0, bubbleW, bubbleH);
int contentTop = kPadTop;
if (hasQuote) {
L.quoteRect = QRect(contentLeft + kPadH, contentTop, bubbleW - 2 * kPadH, qH);
L.quoteBarRect = QRect(L.quoteRect.left(), L.quoteRect.top(),
kQuoteBarW, qH);
int tx = L.quoteBarRect.right() + kQuotePadH;
L.quoteAuthorRect = QRect(tx, L.quoteRect.top() + kQuotePadV,
L.quoteRect.width() - kQuoteBarW - kQuotePadH,
QFontMetrics(option.font).height());
L.quoteTextRect = QRect(tx,
L.quoteAuthorRect.bottom() + 2,
L.quoteAuthorRect.width(),
qH - kQuotePadV - QFontMetrics(option.font).height() - 2 - kQuotePadV);
contentTop += qH + kQuoteGap;
}
L.textRect = QRect(contentLeft + kPadH, contentTop,
textW, textH);
L.statusRect = QRect(contentLeft + kPadH,
contentTop + textH + 2,
textW, kStatusH);
int avatarY = L.bubbleRect.bottom() - kAvatar;
L.avatarRect = QRect(kMarginH, avatarY, kAvatar, kAvatar);
return L;
}
void MessageDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const {
painter->save();
painter->translate(option.rect.topLeft());
painter->setClipRect(QRect(QPoint(0, 0), option.rect.size()));
Layout L = calcLayout(option, index);
drawBubble(painter, L);
if (index.data(MsgHasQuoteRole).toBool()) {
drawQuote(painter, L, index, option);
}
drawAvatar(painter, L, index);
painter->setPen(option.palette.text().color());
QFont textFont = option.font;
textFont.setPointSize(textFont.pointSize());
painter->setFont(textFont);
painter->drawText(L.textRect, Qt::AlignLeft | Qt::TextWordWrap,
index.data(MsgTextRole).toString());
drawStatusBar(painter, L, index, option);
painter->restore();
}
void MessageDelegate::drawQuote(QPainter *p, const Layout &L,
const QModelIndex &index,
const QStyleOptionViewItem &opt) const {
p->save();
p->setPen(Qt::NoPen);
p->setBrush(QColor(0xFF, 0xEE, 0xEE));
p->drawRoundedRect(L.quoteRect, 5, 5);
p->setPen(Qt::NoPen);
p->setBrush(QColor(0xE0, 0x40, 0x40));
p->drawRect(L.quoteBarRect);
QFont boldFont = opt.font;
boldFont.setBold(true);
p->setFont(boldFont);
p->setPen(QColor(0xE0, 0x40, 0x40));
p->drawText(L.quoteAuthorRect, Qt::AlignLeft | Qt::AlignVCenter,
index.data(MsgQuoteAuthorRole).toString());
p->setFont(opt.font);
p->setPen(QColor(0x99, 0x55, 0x55));
p->drawText(L.quoteTextRect, Qt::AlignLeft | Qt::TextWordWrap,
index.data(MsgQuoteTextRole).toString());
p->restore();
}
void MessageDelegate::drawBubble(QPainter *p, const Layout &L) const {
p->save();
p->setRenderHint(QPainter::Antialiasing);
QRectF b = L.bubbleRect;
qreal r = kRadius;
qreal t = kTail;
QPainterPath body;
body.addRoundedRect(b, r, r);
QPainterPath tail;
qreal tx = b.left();
qreal ty = b.bottom();
tail.moveTo(tx, ty - r);
tail.lineTo(tx - t, ty - t / 2.0);
tail.lineTo(tx + r, ty);
tail.closeSubpath();
QPainterPath combined = body.united(tail);
p->setPen(QPen(QColor(0xE0, 0xE0, 0xE0), 0.5));
p->setBrush(QColor(255, 255, 255));
p->drawPath(combined);
p->restore();
}
void MessageDelegate::drawStatusBar(QPainter *p, const Layout &L,
const QModelIndex &index,
const QStyleOptionViewItem &opt) const {
p->save();
QRect r = L.statusRect;
QVariant reactionsVar = index.data(MsgReactionsRole);
QVariantList reactions = reactionsVar.toList();
int rx = r.left();
if (!reactions.isEmpty()) {
for (const QVariant &rv : reactions) {
QVariantMap rm = rv.toMap();
QString emoji = rm["emoji"].toString();
int count = rm["count"].toInt();
QVariant colorsVar = rm["colors"];
QList<QColor> colors = colorsVar.value<QList<QColor>>();
if (count < 4 && !colors.isEmpty()) {
int iconSize = 16;
for (int i = colors.size() - 1; i >= 0; --i) {
int cx = rx + i * 11;
p->setPen(Qt::NoPen);
p->setBrush(colors[i]);
p->drawEllipse(cx, r.top() + 1, iconSize, iconSize);
}
rx += colors.size() * 11 + 8;
} else {
QFont emojiFont = opt.font;
emojiFont.setPointSize(emojiFont.pointSize() - 1);
p->setFont(emojiFont);
p->setPen(opt.palette.text().color());
QRect emojiRect(rx, r.top(), 20, r.height());
p->drawText(emojiRect, Qt::AlignLeft | Qt::AlignVCenter, emoji);
rx += 20;
QFont countFont = opt.font;
countFont.setPointSize(countFont.pointSize() - 2);
p->setFont(countFont);
p->setPen(QColor(0x80, 0x80, 0x80));
QRect cntRect(rx, r.top(), 24, r.height());
p->drawText(cntRect, Qt::AlignLeft | Qt::AlignVCenter,
QString::number(count));
rx = cntRect.right() + 8;
}
}
}
QFont smallFont = opt.font;
smallFont.setPointSize(smallFont.pointSize() - 2);
p->setFont(smallFont);
p->setPen(QColor(0x80, 0x80, 0x80));
QString statusText;
if (index.data(MsgEditedRole).toBool()) {
statusText = QString::fromUtf8("изменено ");
}
statusText += index.data(MsgTimeRole).toString();
p->drawText(QRect(r.left(), r.top(), r.width(), r.height()),
Qt::AlignRight | Qt::AlignVCenter, statusText);
p->restore();
}
void MessageDelegate::drawAvatar(QPainter *p, const Layout &L,
const QModelIndex &index) const {
QPixmap av = index.data(MsgAvatarRole).value<QPixmap>();
if (av.isNull()) return;
p->save();
p->setRenderHint(QPainter::Antialiasing);
QPainterPath clip;
clip.addEllipse(L.avatarRect);
p->setClipPath(clip);
p->drawPixmap(L.avatarRect, av);
p->restore();
}