|
|
|
@ -65,6 +65,33 @@ MessageDelegate::Layout MessageDelegate::calcLayout( |
|
|
|
|
|
|
|
|
|
|
|
bool hasQuote = index.data(MsgHasQuoteRole).toBool(); |
|
|
|
bool hasQuote = index.data(MsgHasQuoteRole).toBool(); |
|
|
|
QString text = index.data(MsgTextRole).toString(); |
|
|
|
QString text = index.data(MsgTextRole).toString(); |
|
|
|
|
|
|
|
L.isOutgoing = index.data(MsgIsOutgoingRole).toBool(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
L.emojiCount = detectEmojiOnly(text); |
|
|
|
|
|
|
|
L.emojiOnly = (L.emojiCount >= 1); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (L.emojiOnly) { |
|
|
|
|
|
|
|
QFontMetrics fm(option.font); |
|
|
|
|
|
|
|
int lineH = fm.lineSpacing(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int availW = L.viewWidth - kMarginH - kAvatar - kGap - kMarginH; |
|
|
|
|
|
|
|
bool narrow = (availW < 400); |
|
|
|
|
|
|
|
int contentEdge = !L.isOutgoing |
|
|
|
|
|
|
|
? (narrow ? kMarginH : (kMarginH + kAvatar + kGap)) |
|
|
|
|
|
|
|
: L.viewWidth - (narrow ? kMarginH : (kMarginH + kAvatar + kGap)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int eSize = (L.emojiCount == 1) ? lineH * 4 : lineH * 2; |
|
|
|
|
|
|
|
int emojiGap = lineH / 2; |
|
|
|
|
|
|
|
int totalW = L.emojiCount * eSize + (L.emojiCount - 1) * emojiGap; |
|
|
|
|
|
|
|
int x = !L.isOutgoing |
|
|
|
|
|
|
|
? (contentEdge + kPadH) |
|
|
|
|
|
|
|
: (contentEdge - kPadH - totalW); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
L.emojiArea = QRect(x, kGap * 2, totalW, eSize); |
|
|
|
|
|
|
|
L.totalHeight = eSize + kGap * 2 + kItemGap; |
|
|
|
|
|
|
|
L.bubbleRect = QRect(); |
|
|
|
|
|
|
|
return L; |
|
|
|
|
|
|
|
} |
|
|
|
QString qText = hasQuote ? index.data(MsgQuoteTextRole).toString() : QString(); |
|
|
|
QString qText = hasQuote ? index.data(MsgQuoteTextRole).toString() : QString(); |
|
|
|
|
|
|
|
|
|
|
|
int availW = L.viewWidth - kMarginH - kAvatar - kGap - kMarginH; |
|
|
|
int availW = L.viewWidth - kMarginH - kAvatar - kGap - kMarginH; |
|
|
|
@ -77,14 +104,18 @@ MessageDelegate::Layout MessageDelegate::calcLayout( |
|
|
|
QFontMetrics fm(option.font); |
|
|
|
QFontMetrics fm(option.font); |
|
|
|
int contentW = calcMaxLineWidth(option.font, text, limitW); |
|
|
|
int contentW = calcMaxLineWidth(option.font, text, limitW); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!L.isOutgoing) { |
|
|
|
QString author = index.data(MsgAuthorRole).toString(); |
|
|
|
QString author = index.data(MsgAuthorRole).toString(); |
|
|
|
QFont boldFm = option.font; |
|
|
|
QFont boldFm = option.font; |
|
|
|
boldFm.setBold(true); |
|
|
|
boldFm.setBold(true); |
|
|
|
int authorW = QFontMetrics(boldFm).horizontalAdvance(author); |
|
|
|
int authorW = QFontMetrics(boldFm).horizontalAdvance(author); |
|
|
|
contentW = qMax(contentW, authorW); |
|
|
|
contentW = qMax(contentW, authorW); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (hasQuote) { |
|
|
|
if (hasQuote) { |
|
|
|
QString qAuthor = index.data(MsgQuoteAuthorRole).toString(); |
|
|
|
QString qAuthor = index.data(MsgQuoteAuthorRole).toString(); |
|
|
|
|
|
|
|
QFont boldFm = option.font; |
|
|
|
|
|
|
|
boldFm.setBold(true); |
|
|
|
int qAuthorW = QFontMetrics(boldFm).horizontalAdvance(qAuthor); |
|
|
|
int qAuthorW = QFontMetrics(boldFm).horizontalAdvance(qAuthor); |
|
|
|
contentW = qMax(contentW, qAuthorW + kQuoteBarW + kQuotePadH + 4); |
|
|
|
contentW = qMax(contentW, qAuthorW + kQuoteBarW + kQuotePadH + 4); |
|
|
|
int qTextW = qMin(contentW + 8, limitW); |
|
|
|
int qTextW = qMin(contentW + 8, limitW); |
|
|
|
@ -107,23 +138,39 @@ MessageDelegate::Layout MessageDelegate::calcLayout( |
|
|
|
|
|
|
|
|
|
|
|
int textH = measureTextHeight(option.font, text, textW); |
|
|
|
int textH = measureTextHeight(option.font, text, textW); |
|
|
|
int authorH = fm.height(); |
|
|
|
int authorH = fm.height(); |
|
|
|
int bubbleH = kPadTop + authorH + 2 + (hasQuote ? qH + kQuoteGap : 0) + textH + kStatusH + kPadBot; |
|
|
|
int bubbleH = L.isOutgoing |
|
|
|
|
|
|
|
? kPadTop + (hasQuote ? qH + kQuoteGap : 0) + textH + kStatusH + kPadBot |
|
|
|
|
|
|
|
: kPadTop + authorH + 2 + (hasQuote ? qH + kQuoteGap : 0) + textH + kStatusH + kPadBot; |
|
|
|
L.totalHeight = qMax(bubbleH, kAvatar) + kItemGap; |
|
|
|
L.totalHeight = qMax(bubbleH, kAvatar) + kItemGap; |
|
|
|
|
|
|
|
|
|
|
|
int contentLeft = L.narrow ? kMarginH : (kMarginH + kAvatar + kGap); |
|
|
|
int contentEdge = L.isOutgoing |
|
|
|
|
|
|
|
? L.viewWidth - (L.narrow ? kMarginH : (kMarginH + kAvatar + kGap)) |
|
|
|
|
|
|
|
: (L.narrow ? kMarginH : (kMarginH + kAvatar + kGap)); |
|
|
|
|
|
|
|
|
|
|
|
L.bubbleRect = QRect(contentLeft, 0, bubbleW, bubbleH); |
|
|
|
if (!L.isOutgoing) |
|
|
|
|
|
|
|
L.bubbleRect = QRect(contentEdge, 0, bubbleW, bubbleH); |
|
|
|
|
|
|
|
else |
|
|
|
|
|
|
|
L.bubbleRect = QRect(contentEdge - bubbleW, 0, bubbleW, bubbleH); |
|
|
|
|
|
|
|
|
|
|
|
int contentTop = kPadTop; |
|
|
|
int contentTop = kPadTop; |
|
|
|
|
|
|
|
|
|
|
|
L.authorRect = QRect(contentLeft + kPadH, contentTop, textW, authorH); |
|
|
|
if (!L.isOutgoing) { |
|
|
|
|
|
|
|
L.authorRect = QRect(contentEdge + kPadH, contentTop, textW, authorH); |
|
|
|
contentTop += authorH + 2; |
|
|
|
contentTop += authorH + 2; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (hasQuote) { |
|
|
|
if (hasQuote) { |
|
|
|
L.quoteRect = QRect(contentLeft + kPadH, contentTop, bubbleW - 2 * kPadH, qH); |
|
|
|
int qX = !L.isOutgoing |
|
|
|
L.quoteBarRect = QRect(L.quoteRect.left(), L.quoteRect.top(), |
|
|
|
? (contentEdge + kPadH) |
|
|
|
kQuoteBarW, qH); |
|
|
|
: (contentEdge - kPadH - (bubbleW - 2 * kPadH)); |
|
|
|
int tx = L.quoteBarRect.right() + kQuotePadH; |
|
|
|
L.quoteRect = QRect(qX, contentTop, bubbleW - 2 * kPadH, qH); |
|
|
|
|
|
|
|
if (!L.isOutgoing) |
|
|
|
|
|
|
|
L.quoteBarRect = QRect(L.quoteRect.left(), L.quoteRect.top(), kQuoteBarW, qH); |
|
|
|
|
|
|
|
else |
|
|
|
|
|
|
|
L.quoteBarRect = QRect(L.quoteRect.right() - kQuoteBarW, L.quoteRect.top(), kQuoteBarW, qH); |
|
|
|
|
|
|
|
int tx = !L.isOutgoing |
|
|
|
|
|
|
|
? (L.quoteBarRect.right() + kQuotePadH) |
|
|
|
|
|
|
|
: (L.quoteRect.left() + kQuotePadH); |
|
|
|
L.quoteAuthorRect = QRect(tx, L.quoteRect.top() + kQuotePadV, |
|
|
|
L.quoteAuthorRect = QRect(tx, L.quoteRect.top() + kQuotePadV, |
|
|
|
L.quoteRect.width() - kQuoteBarW - kQuotePadH, |
|
|
|
L.quoteRect.width() - kQuoteBarW - kQuotePadH, |
|
|
|
QFontMetrics(option.font).height()); |
|
|
|
QFontMetrics(option.font).height()); |
|
|
|
@ -134,15 +181,18 @@ MessageDelegate::Layout MessageDelegate::calcLayout( |
|
|
|
contentTop += qH + kQuoteGap; |
|
|
|
contentTop += qH + kQuoteGap; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
L.textRect = QRect(contentLeft + kPadH, contentTop, |
|
|
|
int txRect = !L.isOutgoing |
|
|
|
textW, textH); |
|
|
|
? (contentEdge + kPadH) |
|
|
|
L.statusRect = QRect(contentLeft + kPadH, |
|
|
|
: (contentEdge - kPadH - textW); |
|
|
|
contentTop + textH + 2, |
|
|
|
L.textRect = QRect(txRect, contentTop, textW, textH); |
|
|
|
textW, kStatusH); |
|
|
|
L.statusRect = QRect(txRect, contentTop + textH + 2, textW, kStatusH); |
|
|
|
|
|
|
|
|
|
|
|
if (!L.narrow) { |
|
|
|
if (!L.narrow) { |
|
|
|
int avatarY = L.bubbleRect.bottom() - kAvatar; |
|
|
|
int avatarY = L.bubbleRect.bottom() - kAvatar; |
|
|
|
|
|
|
|
if (!L.isOutgoing) |
|
|
|
L.avatarRect = QRect(kMarginH, avatarY, kAvatar, kAvatar); |
|
|
|
L.avatarRect = QRect(kMarginH, avatarY, kAvatar, kAvatar); |
|
|
|
|
|
|
|
else |
|
|
|
|
|
|
|
L.avatarRect = QRect(L.viewWidth - kMarginH - kAvatar, avatarY, kAvatar, kAvatar); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return L; |
|
|
|
return L; |
|
|
|
@ -349,6 +399,56 @@ void MessageDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio |
|
|
|
|
|
|
|
|
|
|
|
Layout L = calcLayout(option, index); |
|
|
|
Layout L = calcLayout(option, index); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (L.emojiOnly) { |
|
|
|
|
|
|
|
QString msgText = index.data(MsgTextRole).toString(); |
|
|
|
|
|
|
|
QString trimmed = msgText.trimmed(); |
|
|
|
|
|
|
|
QFontMetrics fm(option.font); |
|
|
|
|
|
|
|
int lineH = fm.lineSpacing(); |
|
|
|
|
|
|
|
int eSize = (L.emojiCount == 1) ? lineH * 4 : lineH * 2; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int x = L.emojiArea.left(); |
|
|
|
|
|
|
|
int y = L.emojiArea.top(); |
|
|
|
|
|
|
|
int pos = 0; |
|
|
|
|
|
|
|
while (pos < trimmed.length()) { |
|
|
|
|
|
|
|
if (trimmed.at(pos).isSpace()) { ++pos; continue; } |
|
|
|
|
|
|
|
QString emojiStr; |
|
|
|
|
|
|
|
for (const auto &cat : builtinEmojiSet()) |
|
|
|
|
|
|
|
for (const auto &e : cat.items) { |
|
|
|
|
|
|
|
if (e.unicode.length() > trimmed.length() - pos) continue; |
|
|
|
|
|
|
|
if (QStringView(trimmed).mid(pos, e.unicode.length()) == e.unicode) { |
|
|
|
|
|
|
|
emojiStr = e.unicode; |
|
|
|
|
|
|
|
pos += e.unicode.length(); |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (emojiStr.isEmpty()) { ++pos; continue; } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool animated = g_animatedEmojiMap.contains(emojiStr); |
|
|
|
|
|
|
|
if (animated) { |
|
|
|
|
|
|
|
LottieIcon *icon = AnimTimer::instance()->icon(g_animatedEmojiMap[emojiStr]); |
|
|
|
|
|
|
|
if (icon && icon->isValid()) { |
|
|
|
|
|
|
|
QImage frame = icon->renderFrame(icon->currentFrame(), QSize(eSize, eSize)); |
|
|
|
|
|
|
|
painter->drawImage(x, y, frame); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
QFont big = option.font; |
|
|
|
|
|
|
|
big.setPointSize((int)(big.pointSize() * (L.emojiCount == 1 ? 4.0 : 2.0))); |
|
|
|
|
|
|
|
painter->setFont(big); |
|
|
|
|
|
|
|
painter->setPen(option.palette.text().color()); |
|
|
|
|
|
|
|
painter->drawText(QRect(x, y, eSize, eSize), Qt::AlignCenter, emojiStr); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
QFont big = option.font; |
|
|
|
|
|
|
|
big.setPointSize((int)(big.pointSize() * (L.emojiCount == 1 ? 4.0 : 2.0))); |
|
|
|
|
|
|
|
painter->setFont(big); |
|
|
|
|
|
|
|
painter->setPen(option.palette.text().color()); |
|
|
|
|
|
|
|
painter->drawText(QRect(x, y, eSize, eSize), Qt::AlignCenter, emojiStr); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
x += eSize + lineH / 2; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
painter->restore(); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
auto *cv = qobject_cast<const ChatView *>(option.widget); |
|
|
|
auto *cv = qobject_cast<const ChatView *>(option.widget); |
|
|
|
bool singleTextSel = false; |
|
|
|
bool singleTextSel = false; |
|
|
|
bool multiRowSel = false; |
|
|
|
bool multiRowSel = false; |
|
|
|
@ -379,12 +479,14 @@ void MessageDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio |
|
|
|
if (!L.narrow) |
|
|
|
if (!L.narrow) |
|
|
|
drawAvatar(painter, L, index); |
|
|
|
drawAvatar(painter, L, index); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!L.isOutgoing) { |
|
|
|
QFont authorFont = option.font; |
|
|
|
QFont authorFont = option.font; |
|
|
|
authorFont.setBold(true); |
|
|
|
authorFont.setBold(true); |
|
|
|
painter->setFont(authorFont); |
|
|
|
painter->setFont(authorFont); |
|
|
|
painter->setPen(QColor(0x33, 0x90, 0xEC)); |
|
|
|
painter->setPen(QColor(0x33, 0x90, 0xEC)); |
|
|
|
painter->drawText(L.authorRect, Qt::AlignLeft | Qt::AlignVCenter, |
|
|
|
painter->drawText(L.authorRect, Qt::AlignLeft | Qt::AlignVCenter, |
|
|
|
index.data(MsgAuthorRole).toString()); |
|
|
|
index.data(MsgAuthorRole).toString()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
painter->setPen(option.palette.text().color()); |
|
|
|
painter->setPen(option.palette.text().color()); |
|
|
|
QFont textFont = option.font; |
|
|
|
QFont textFont = option.font; |
|
|
|
@ -458,17 +560,30 @@ void MessageDelegate::drawBubble(QPainter *p, const Layout &L, const QStyleOptio |
|
|
|
body.addRoundedRect(b, r, r); |
|
|
|
body.addRoundedRect(b, r, r); |
|
|
|
|
|
|
|
|
|
|
|
QPainterPath tail; |
|
|
|
QPainterPath tail; |
|
|
|
|
|
|
|
if (!L.isOutgoing) { |
|
|
|
qreal tx = b.left(); |
|
|
|
qreal tx = b.left(); |
|
|
|
qreal ty = b.bottom(); |
|
|
|
qreal ty = b.bottom(); |
|
|
|
tail.moveTo(tx, ty - r); |
|
|
|
tail.moveTo(tx, ty - r); |
|
|
|
tail.lineTo(tx - t, ty - t / 2.0); |
|
|
|
tail.lineTo(tx - t, ty - t / 2.0); |
|
|
|
tail.lineTo(tx + r, ty); |
|
|
|
tail.lineTo(tx + r, ty); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
qreal tx = b.right(); |
|
|
|
|
|
|
|
qreal ty = b.bottom(); |
|
|
|
|
|
|
|
tail.moveTo(tx, ty - r); |
|
|
|
|
|
|
|
tail.lineTo(tx + t, ty - t / 2.0); |
|
|
|
|
|
|
|
tail.lineTo(tx - r, ty); |
|
|
|
|
|
|
|
} |
|
|
|
tail.closeSubpath(); |
|
|
|
tail.closeSubpath(); |
|
|
|
|
|
|
|
|
|
|
|
QPainterPath combined = body.united(tail); |
|
|
|
QPainterPath combined = body.united(tail); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
QColor bubbleFill; |
|
|
|
|
|
|
|
if (!L.isOutgoing) { |
|
|
|
QColor base = opt.palette.window().color(); |
|
|
|
QColor base = opt.palette.window().color(); |
|
|
|
QColor bubbleFill = base.lighter(135); |
|
|
|
bubbleFill = base.lighter(135); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
bubbleFill = QColor(0xE1, 0xF5, 0xE1); |
|
|
|
|
|
|
|
} |
|
|
|
p->setPen(Qt::NoPen); |
|
|
|
p->setPen(Qt::NoPen); |
|
|
|
p->setBrush(bubbleFill); |
|
|
|
p->setBrush(bubbleFill); |
|
|
|
p->drawPath(combined); |
|
|
|
p->drawPath(combined); |
|
|
|
|