Browse Source

chatgui: MsgIsOutgoingRole support for outgoing message alignment

topo_upd
Evgeny 2 months ago
parent
commit
e8920fe00e
  1. 39
      tools/chatgui/src/emoji.cpp
  2. 2
      tools/chatgui/src/emoji.h
  3. 181
      tools/chatgui/src/messagedelegate.cpp
  4. 5
      tools/chatgui/src/messagedelegate.h
  5. 4
      tools/chatgui/src/messagelist.cpp

39
tools/chatgui/src/emoji.cpp

@ -139,3 +139,42 @@ QVector<EmojiCategory> builtinEmojiSet() {
{"Animations", animations()}, {"Animations", animations()},
}; };
} }
#include <QSet>
static QSet<QString> buildAllEmojiSet() {
QSet<QString> s;
for (const auto &cat : builtinEmojiSet())
for (const auto &e : cat.items)
s.insert(e.unicode);
return s;
}
int detectEmojiOnly(const QString &text) {
static const QSet<QString> emojiSet = buildAllEmojiSet();
QString t = text.trimmed();
if (t.isEmpty()) return 0;
int pos = 0;
int count = 0;
while (pos < t.length()) {
const QChar *ch = t.constData() + pos;
if (ch->isSpace()) { ++pos; continue; }
bool matched = false;
for (const QString &e : emojiSet) {
if (e.length() <= t.length() - pos
&& QStringView(t).mid(pos, e.length()) == e)
{
pos += e.length();
++count;
matched = true;
break;
}
}
if (!matched) return 0;
if (count > 3) return 0;
}
return (count >= 1 && count <= 3) ? count : 0;
}

2
tools/chatgui/src/emoji.h

@ -25,3 +25,5 @@ struct EmojiCategory {
QVector<EmojiCategory> builtinEmojiSet(); QVector<EmojiCategory> builtinEmojiSet();
extern QHash<QString, QString> g_animatedEmojiMap; extern QHash<QString, QString> g_animatedEmojiMap;
int detectEmojiOnly(const QString &text);

181
tools/chatgui/src/messagedelegate.cpp

@ -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);
QString author = index.data(MsgAuthorRole).toString(); if (!L.isOutgoing) {
QFont boldFm = option.font; QString author = index.data(MsgAuthorRole).toString();
boldFm.setBold(true); QFont boldFm = option.font;
int authorW = QFontMetrics(boldFm).horizontalAdvance(author); boldFm.setBold(true);
contentW = qMax(contentW, authorW); int authorW = QFontMetrics(boldFm).horizontalAdvance(author);
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) {
contentTop += authorH + 2; L.authorRect = QRect(contentEdge + kPadH, contentTop, textW, authorH);
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;
L.avatarRect = QRect(kMarginH, avatarY, kAvatar, kAvatar); if (!L.isOutgoing)
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);
QFont authorFont = option.font; if (!L.isOutgoing) {
authorFont.setBold(true); QFont authorFont = option.font;
painter->setFont(authorFont); authorFont.setBold(true);
painter->setPen(QColor(0x33, 0x90, 0xEC)); painter->setFont(authorFont);
painter->drawText(L.authorRect, Qt::AlignLeft | Qt::AlignVCenter, painter->setPen(QColor(0x33, 0x90, 0xEC));
index.data(MsgAuthorRole).toString()); painter->drawText(L.authorRect, Qt::AlignLeft | Qt::AlignVCenter,
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;
qreal tx = b.left(); if (!L.isOutgoing) {
qreal ty = b.bottom(); qreal tx = b.left();
tail.moveTo(tx, ty - r); qreal ty = b.bottom();
tail.lineTo(tx - t, ty - t / 2.0); tail.moveTo(tx, ty - r);
tail.lineTo(tx + r, ty); tail.lineTo(tx - t, ty - t / 2.0);
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 base = opt.palette.window().color(); QColor bubbleFill;
QColor bubbleFill = base.lighter(135); if (!L.isOutgoing) {
QColor base = opt.palette.window().color();
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);

5
tools/chatgui/src/messagedelegate.h

@ -13,6 +13,7 @@ enum MessageDataRole {
MsgQuoteTextRole = Qt::UserRole + 8, MsgQuoteTextRole = Qt::UserRole + 8,
MsgReactionsRole = Qt::UserRole + 9, MsgReactionsRole = Qt::UserRole + 9,
MsgIdRole = Qt::UserRole + 10, MsgIdRole = Qt::UserRole + 10,
MsgIsOutgoingRole = Qt::UserRole + 11,
}; };
class MessageDelegate : public QStyledItemDelegate { class MessageDelegate : public QStyledItemDelegate {
@ -40,6 +41,10 @@ private:
QRect statusRect; QRect statusRect;
int totalHeight; int totalHeight;
bool narrow = false; bool narrow = false;
bool emojiOnly = false;
int emojiCount = 0;
QRect emojiArea;
bool isOutgoing = false;
}; };
Layout calcLayout(const QStyleOptionViewItem &option, Layout calcLayout(const QStyleOptionViewItem &option,

4
tools/chatgui/src/messagelist.cpp

@ -219,6 +219,7 @@ void MessageList::loadChannel(const QString& channelId) {
setMsg(item, author, QString::fromUtf8(m.data), time, avatar, false, setMsg(item, author, QString::fromUtf8(m.data), time, avatar, false,
false, QString(), QString(), reacts); false, QString(), QString(), reacts);
item->setData(m.id, MsgIdRole); item->setData(m.id, MsgIdRole);
item->setData(m.isOutgoing ? 1 : 0, MsgIsOutgoingRole);
m_model->appendRow(item); m_model->appendRow(item);
} }
@ -278,6 +279,7 @@ void MessageList::refresh() {
setMsg(item, author, QString::fromUtf8(m.data), time, avatar, false, setMsg(item, author, QString::fromUtf8(m.data), time, avatar, false,
false, QString(), QString(), QVariantList{}); false, QString(), QString(), QVariantList{});
item->setData(m.id, MsgIdRole); item->setData(m.id, MsgIdRole);
item->setData(m.isOutgoing ? 1 : 0, MsgIsOutgoingRole);
m_model->appendRow(item); m_model->appendRow(item);
while (m_model->rowCount() > 100) while (m_model->rowCount() > 100)
m_model->removeRow(0); m_model->removeRow(0);
@ -308,6 +310,8 @@ void MessageList::addMessage(const QString& channelId, quint64 authorNodeId,
auto *item = new QStandardItem(); auto *item = new QStandardItem();
setMsg(item, author, QString::fromUtf8(content), time, avatar, false, setMsg(item, author, QString::fromUtf8(content), time, avatar, false,
false, QString(), QString(), QVariantList{}); false, QString(), QString(), QVariantList{});
bool isSelf = m_db && (authorNodeId == m_db->myNodeId());
item->setData(isSelf ? 1 : 0, MsgIsOutgoingRole);
m_model->appendRow(item); m_model->appendRow(item);
if (m_model->rowCount() > 50) if (m_model->rowCount() > 50)

Loading…
Cancel
Save