From 3482e4a327199a471b04ab45f165bda6cfcd00d5 Mon Sep 17 00:00:00 2001 From: evgeny Date: Sun, 4 Oct 2026 19:04:11 +0200 Subject: [PATCH] Share Qt text layout for web links and invitations --- tools/chatgui/src/messagedelegate.cpp | 36 +++++++++++++-------------- tools/chatgui/src/messagedelegate.h | 2 +- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/tools/chatgui/src/messagedelegate.cpp b/tools/chatgui/src/messagedelegate.cpp index 6fc17689..9cd49be5 100644 --- a/tools/chatgui/src/messagedelegate.cpp +++ b/tools/chatgui/src/messagedelegate.cpp @@ -1,4 +1,5 @@ #include "messagedelegate.h" +#include "message_links.h" #include "chatview.h" #include "debug_ui.h" #include "emoji.h" @@ -24,10 +25,9 @@ #include #include #include -#include -/* Один layout для размеров, отрисовки и hit-test длинных invite-ссылок. */ -static qreal layoutInviteText(QTextLayout& layout, int width, const QString& compactLink = {}) { +/* Один layout для размеров, отрисовки, выделения и hit-test ссылок. */ +static qreal layoutMessageText(QTextLayout& layout, int width, const QString& compactLink = {}) { int linkStart = -1; if (!compactLink.isEmpty() && layout.text().endsWith(compactLink)) { linkStart = layout.text().length() - compactLink.length(); @@ -36,16 +36,14 @@ static qreal layoutInviteText(QTextLayout& layout, int width, const QString& com } QTextOption option; option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); layout.setTextOption(option); - static const QRegularExpression pattern(QStringLiteral("utun://[A-Za-z0-9+/]+={0,2}")); QVector formats; - auto matches = pattern.globalMatch(layout.text()); - while (linkStart >= 0 || matches.hasNext()) { + const auto links = linkStart >= 0 ? QVector{{linkStart, (int)layout.text().length() - linkStart, compactLink}} + : messageLinks(layout.text()); + for (const auto& link : links) { QTextLayout::FormatRange range; - if (linkStart >= 0) { range.start = linkStart; range.length = layout.text().length() - linkStart; } - else { auto match = matches.next(); range.start = match.capturedStart(); range.length = match.capturedLength(); } + range.start = link.start; range.length = link.length; range.format.setForeground(QColor(0x33, 0x90, 0xEC)); range.format.setFontUnderline(true); formats.append(range); - if (linkStart >= 0) break; } layout.setFormats(formats); layout.beginLayout(); @@ -59,9 +57,9 @@ static qreal layoutInviteText(QTextLayout& layout, int width, const QString& com return y; } -QString MessageDelegate::inviteLinkAt(const QStyleOptionViewItem& option, const QModelIndex& index, const QPoint& point) const { +QString MessageDelegate::linkAt(const QStyleOptionViewItem& option, const QModelIndex& index, const QPoint& point) const { const QString text = index.data(MsgTextRole).toString(); - if (!text.contains(QStringLiteral("utun://"))) return {}; + if (messageLinks(text).isEmpty()) return {}; const Layout geometry = calcLayout(option, index); if (geometry.isVoice || geometry.isFile || geometry.isVideo || geometry.isImage || geometry.emojiOnly) return {}; const QPoint local = point - option.rect.topLeft() - geometry.textRect.topLeft(); @@ -69,7 +67,7 @@ QString MessageDelegate::inviteLinkAt(const QStyleOptionViewItem& option, const QString display = text; display.replace('\n', QChar(0x2028)); QTextLayout layout(display, option.font); const QString compactLink = index.data(MsgInviteLinkRole).toString(); - layoutInviteText(layout, geometry.textRect.width(), compactLink); + layoutMessageText(layout, geometry.textRect.width(), compactLink); for (int i = 0; i < layout.lineCount(); ++i) { const auto line = layout.lineAt(i); if (local.y() < line.y() || local.y() >= line.y() + line.height()) continue; @@ -325,10 +323,10 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem &option, static int measureTextHeight(const QFont &font, const QString &text, int width, const QString& compactLink = {}) { if (text.isEmpty() || width <= 0) return 0; - if (text.contains(QStringLiteral("utun://"))) { + if (!messageLinks(text).isEmpty()) { QString display = text; display.replace('\n', QChar(0x2028)); QTextLayout layout(display, font); - return (int)std::ceil(layoutInviteText(layout, width, compactLink)); + return (int)std::ceil(layoutMessageText(layout, width, compactLink)); } QRect r = QFontMetrics(font).boundingRect(QRect(0, 0, width, 100000), Qt::AlignLeft | Qt::TextWordWrap, text); @@ -832,7 +830,7 @@ static void drawAnimatedEmojiOverlay(QPainter *p, const QFont &font, if (!para.isEmpty()) { QTextLayout layout(para, font); - if (text.contains(QStringLiteral("utun://"))) { + if (!messageLinks(text).isEmpty()) { QTextOption wrap; wrap.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); layout.setTextOption(wrap); } layout.beginLayout(); @@ -1381,12 +1379,12 @@ void MessageDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio QString msgText = index.data(MsgTextRole).toString(); QString overlayText = msgText; - const bool hasInvite = msgText.contains(QStringLiteral("utun://")); - if (hasInvite) { + const bool hasLinks = !messageLinks(msgText).isEmpty(); + if (hasLinks) { QString display = msgText; display.replace('\n', QChar(0x2028)); QTextLayout layout(display, textFont); const QString compactLink = index.data(MsgInviteLinkRole).toString(); - layoutInviteText(layout, L.textRect.width(), compactLink); + layoutMessageText(layout, L.textRect.width(), compactLink); overlayText = layout.text(); overlayText.replace(QChar(0x2028), '\n'); QVector selections; if (singleTextSel && cv) { @@ -1418,7 +1416,7 @@ void MessageDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio layout.draw(painter, L.textRect.topLeft(), selections); } else painter->drawText(L.textRect, Qt::AlignLeft | Qt::TextWordWrap, msgText); - if (singleTextSel && cv && !hasInvite) { + if (singleTextSel && cv && !hasLinks) { const auto &ts = cv->textSelection(); QPoint localP1(ts.clickPos.x() - L.textRect.left(), ts.clickPos.y() - L.textRect.top()); diff --git a/tools/chatgui/src/messagedelegate.h b/tools/chatgui/src/messagedelegate.h index 94ab1bc6..0e75eb5d 100644 --- a/tools/chatgui/src/messagedelegate.h +++ b/tools/chatgui/src/messagedelegate.h @@ -58,7 +58,7 @@ public: bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override; - QString inviteLinkAt(const QStyleOptionViewItem& option, const QModelIndex& index, const QPoint& point) const; + QString linkAt(const QStyleOptionViewItem& option, const QModelIndex& index, const QPoint& point) const; static void startAutoPlay(const QString& filePath, float actualDurationSec, uint64_t playbackId); static void stopAllAutoPlay();