#include "messagedelegate.h" #include #include #include #include #include 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(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 colors = colorsVar.value>(); 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(); 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(); }