Browse Source

Render and hit-test wrapped invitation links in desktop messages

master
evgeny 5 days ago
parent
commit
56fe1cab95
  1. 61
      tools/chatgui/src/messagedelegate.cpp
  2. 1
      tools/chatgui/src/messagedelegate.h

61
tools/chatgui/src/messagedelegate.cpp

@ -24,6 +24,55 @@
#include <QDebug>
#include <QImageReader>
#include <QPersistentModelIndex>
#include <QRegularExpression>
/* Один layout для размеров, отрисовки и hit-test длинных invite-ссылок. */
static qreal layoutInviteText(QTextLayout& layout, int width) {
QTextOption option; option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
layout.setTextOption(option);
static const QRegularExpression pattern(QStringLiteral("utun://[A-Za-z0-9+/]+={0,2}"));
QVector<QTextLayout::FormatRange> formats;
auto matches = pattern.globalMatch(layout.text());
while (matches.hasNext()) {
auto match = matches.next();
QTextLayout::FormatRange range;
range.start = match.capturedStart(); range.length = match.capturedLength();
range.format.setForeground(QColor(0x33, 0x90, 0xEC)); range.format.setFontUnderline(true);
formats.append(range);
}
layout.setFormats(formats);
layout.beginLayout();
qreal y = 0;
while (true) {
auto line = layout.createLine();
if (!line.isValid()) break;
line.setLineWidth(qMax(1, width)); line.setPosition(QPointF(0, y)); y += line.height();
}
layout.endLayout();
return y;
}
QString MessageDelegate::inviteLinkAt(const QStyleOptionViewItem& option, const QModelIndex& index, const QPoint& point) const {
const QString text = index.data(MsgTextRole).toString();
if (!text.contains(QStringLiteral("utun://"))) 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();
if (!QRect(QPoint(0, 0), geometry.textRect.size()).contains(local)) return {};
QString display = text; display.replace('\n', QChar(0x2028));
QTextLayout layout(display, option.font);
layoutInviteText(layout, geometry.textRect.width());
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;
if (local.x() < line.x() || local.x() >= line.x() + line.naturalTextWidth()) return {};
const int position = line.xToCursor(local.x(), QTextLine::CursorOnCharacter);
for (const auto& range : layout.formats()) {
if (position >= range.start && position < range.start + range.length) return text.mid(range.start, range.length);
}
}
return {};
}
MessageDelegate::MessageDelegate(QObject *parent)
: QStyledItemDelegate(parent) {}
@ -267,6 +316,11 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem &option,
static int measureTextHeight(const QFont &font, const QString &text, int width) {
if (text.isEmpty() || width <= 0) return 0;
if (text.contains(QStringLiteral("utun://"))) {
QString display = text; display.replace('\n', QChar(0x2028));
QTextLayout layout(display, font);
return (int)std::ceil(layoutInviteText(layout, width));
}
QRect r = QFontMetrics(font).boundingRect(QRect(0, 0, width, 100000),
Qt::AlignLeft | Qt::TextWordWrap, text);
return r.height();
@ -1314,7 +1368,12 @@ void MessageDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio
painter->setFont(textFont);
QString msgText = index.data(MsgTextRole).toString();
painter->drawText(L.textRect, Qt::AlignLeft | Qt::TextWordWrap, msgText);
if (msgText.contains(QStringLiteral("utun://"))) {
QString display = msgText; display.replace('\n', QChar(0x2028));
QTextLayout layout(display, textFont);
layoutInviteText(layout, L.textRect.width());
layout.draw(painter, L.textRect.topLeft());
} else painter->drawText(L.textRect, Qt::AlignLeft | Qt::TextWordWrap, msgText);
if (singleTextSel && cv) {
const auto &ts = cv->textSelection();

1
tools/chatgui/src/messagedelegate.h

@ -57,6 +57,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;
static void startAutoPlay(const QString& filePath, float actualDurationSec, uint64_t playbackId);
static void stopAllAutoPlay();

Loading…
Cancel
Save