tools/chatgui: Qt5 GUI chat app stub with tray, channel list, message bubbles
This commit is contained in:
1 parent
6717c863e7
commit
b667d6f352
20 files changed
+1089
No files matched your search
@@ -0,0 +1,251 @@
|
||||
#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();
|
||||
}
|
||||
Reference in new issue
Block a user