diff --git a/src/widgets/room/delegate/messagedelegatehelpertext.h b/src/widgets/room/delegate/messagedelegatehelpertext.h --- a/src/widgets/room/delegate/messagedelegatehelpertext.h +++ b/src/widgets/room/delegate/messagedelegatehelpertext.h @@ -25,7 +25,12 @@ #include #include #include +#include #include +#include + +#include + class QPainter; class QRect; class QModelIndex; @@ -49,12 +54,14 @@ QString makeMessageText(const QModelIndex &index, const QWidget *widget) const; void setClipboardSelection(); void updateView(const QWidget *widget, const QModelIndex &index); + QTextDocument *documentForIndex(const QModelIndex &index, int width, const QWidget *widget) const; bool mShowThreadContext = true; QPersistentModelIndex mCurrentIndex; // during selection - QTextDocument mCurrentDocument; // during selection + QPointer mCurrentDocument = nullptr; // during selection QTextCursor mCurrentTextCursor; // during selection mutable MessageCache mMessageCache; + mutable LRUCache, 32> mDocumentCache; }; #endif // MESSAGEDELEGATEHELPERTEXT_H diff --git a/src/widgets/room/delegate/messagedelegatehelpertext.cpp b/src/widgets/room/delegate/messagedelegatehelpertext.cpp --- a/src/widgets/room/delegate/messagedelegatehelpertext.cpp +++ b/src/widgets/room/delegate/messagedelegatehelpertext.cpp @@ -126,42 +126,22 @@ return isSystemMessage || messageType == Message::Video || messageType == Message::Audio; } -// QTextDocument lacks a move constructor -static void fillTextDocument(const QModelIndex &index, QTextDocument &doc, const QString &text, int width) -{ - doc.setHtml(text); - doc.setTextWidth(width); - QFont font = doc.defaultFont(); - font.setItalic(useItalicsForMessage(index)); - doc.setDefaultFont(font); - QTextFrame *frame = doc.frameAt(0); - QTextFrameFormat frameFormat = frame->frameFormat(); - frameFormat.setMargin(0); - frame->setFrameFormat(frameFormat); -} - void MessageDelegateHelperText::draw(QPainter *painter, const QRect &rect, const QModelIndex &index, const QStyleOptionViewItem &option) { - const QString text = makeMessageText(index, option.widget); - - if (text.isEmpty()) { + auto *doc = documentForIndex(index, rect.width(), option.widget); + if (!doc) { return; } - // Possible optimisation: store the QTextDocument into the Message itself? - QTextDocument doc; - QTextDocument *pDoc = &doc; + QVector selections; if (index == mCurrentIndex) { - pDoc = &mCurrentDocument; // optimization, not stricly necessary QTextCharFormat selectionFormat; selectionFormat.setBackground(option.palette.brush(QPalette::Highlight)); selectionFormat.setForeground(option.palette.brush(QPalette::HighlightedText)); selections.append({mCurrentTextCursor, selectionFormat}); - } else { - fillTextDocument(index, doc, text, rect.width()); } if (useItalicsForMessage(index)) { - QTextCursor cursor(pDoc); + QTextCursor cursor(doc); cursor.select(QTextCursor::Document); QTextCharFormat format; format.setForeground(Qt::gray); //TODO use color from theme. @@ -179,22 +159,20 @@ painter->setClipRect(clip); ctx.clip = clip; } - pDoc->documentLayout()->draw(painter, ctx); + doc->documentLayout()->draw(painter, ctx); painter->restore(); } QSize MessageDelegateHelperText::sizeHint(const QModelIndex &index, int maxWidth, const QStyleOptionViewItem &option, qreal *pBaseLine) const { Q_UNUSED(option) - const QString text = makeMessageText(index, option.widget); - if (text.isEmpty()) { + auto *doc = documentForIndex(index, maxWidth, option.widget); + if (!doc) { return QSize(); } - QTextDocument doc; - fillTextDocument(index, doc, text, maxWidth); - const QSize size(doc.idealWidth(), doc.size().height()); // do the layouting, required by lineAt(0) below + const QSize size(doc->idealWidth(), doc->size().height()); // do the layouting, required by lineAt(0) below - const QTextLine &line = doc.firstBlock().layout()->lineAt(0); + const QTextLine &line = doc->firstBlock().layout()->lineAt(0); *pBaseLine = line.y() + line.ascent(); // relative return size; @@ -213,21 +191,23 @@ updateView(option.widget, mCurrentIndex); } mCurrentIndex = index; - const QString text = makeMessageText(index, option.widget); - mCurrentDocument.clear(); - fillTextDocument(index, mCurrentDocument, text, messageRect.width()); - const int charPos = mCurrentDocument.documentLayout()->hitTest(pos, Qt::FuzzyHit); - // QWidgetTextControl also has code to support selectBlockOnTripleClick, shift to extend selection - if (charPos != -1) { - mCurrentTextCursor = QTextCursor(&mCurrentDocument); - mCurrentTextCursor.setPosition(charPos); - return true; + mCurrentDocument = documentForIndex(index, messageRect.width(), option.widget); + if (mCurrentDocument) { + const int charPos = mCurrentDocument->documentLayout()->hitTest(pos, Qt::FuzzyHit); + // QWidgetTextControl also has code to support selectBlockOnTripleClick, shift to extend selection + if (charPos != -1) { + mCurrentTextCursor = QTextCursor(mCurrentDocument); + mCurrentTextCursor.setPosition(charPos); + return true; + } + } else { + mCurrentIndex = QModelIndex(); } break; } case QEvent::MouseMove: - if (index == mCurrentIndex) { - const int charPos = mCurrentDocument.documentLayout()->hitTest(pos, Qt::FuzzyHit); + if (index == mCurrentIndex && mCurrentDocument) { + const int charPos = mCurrentDocument->documentLayout()->hitTest(pos, Qt::FuzzyHit); if (charPos != -1) { // QWidgetTextControl also has code to support dragging, isPreediting()/commitPreedit(), selectBlockOnTripleClick mCurrentTextCursor.setPosition(charPos, QTextCursor::KeepAnchor); @@ -256,12 +236,11 @@ } // Clicks on links if (eventType == QEvent::MouseButtonRelease) { - // ## we should really cache that QTextDocument... - const QString text = makeMessageText(index, option.widget); - QTextDocument doc; - fillTextDocument(index, doc, text, messageRect.width()); - - const QString link = doc.documentLayout()->anchorAt(pos); + const auto *doc = documentForIndex(index, messageRect.width(), option.widget); + if (!doc) { + return false; + } + const QString link = doc->documentLayout()->anchorAt(pos); if (!link.isEmpty()) { auto *rcAccount = Ruqola::self()->rocketChatAccount(); Q_EMIT rcAccount->openLinkRequested(link); @@ -277,13 +256,13 @@ return false; } - // ## we should really cache that QTextDocument... - const auto text = makeMessageText(index, view); - QTextDocument doc; - fillTextDocument(index, doc, text, messageRect.width()); + const auto *doc = documentForIndex(index, messageRect.width(), view); + if (!doc) { + return false; + } const QPoint pos = helpEvent->pos() - messageRect.topLeft(); - const auto format = doc.documentLayout()->formatAt(pos); + const auto format = doc->documentLayout()->formatAt(pos); const auto tooltip = format.property(QTextFormat::TextToolTip).toString(); const auto href = format.property(QTextFormat::AnchorHref).toString(); if (tooltip.isEmpty() && (href.isEmpty() || href.startsWith(QLatin1String("ruqola:/")))) { @@ -311,3 +290,43 @@ { mShowThreadContext = b; } + +// QTextDocument lacks a move constructor +static void fillTextDocument(const QModelIndex &index, QTextDocument &doc, const QString &text, int width) +{ + doc.setHtml(text); + doc.setTextWidth(width); + QFont font = doc.defaultFont(); + font.setItalic(useItalicsForMessage(index)); + doc.setDefaultFont(font); + QTextFrame *frame = doc.frameAt(0); + QTextFrameFormat frameFormat = frame->frameFormat(); + frameFormat.setMargin(0); + frame->setFrameFormat(frameFormat); +} + +QTextDocument * MessageDelegateHelperText::documentForIndex(const QModelIndex& index, int width, const QWidget *widget) const +{ + const Message *message = index.data(MessageModel::MessagePointer).value(); + Q_ASSERT(message); + const auto messageId = message->messageId(); + Q_ASSERT(!messageId.isEmpty()); + + auto it = mDocumentCache.find(messageId); + if (it != mDocumentCache.end()) { + auto ret = it->value.get(); + ret->setTextWidth(width); + return ret; + } + + const QString text = makeMessageText(index, widget); + if (text.isEmpty()) { + return nullptr; + } + + auto doc = std::unique_ptr(new QTextDocument); + fillTextDocument(index, *doc, text, width); + auto ret = doc.get(); + mDocumentCache.insert(messageId, std::move(doc)); + return ret; +} diff --git a/src/widgets/room/delegate/messagelistdelegate.cpp b/src/widgets/room/delegate/messagelistdelegate.cpp --- a/src/widgets/room/delegate/messagelistdelegate.cpp +++ b/src/widgets/room/delegate/messagelistdelegate.cpp @@ -150,7 +150,7 @@ const int widthAfterMessage = iconSize + margin + timeSize.width() + margin / 2; const int maxWidth = qMax(30, option.rect.width() - textLeft - widthAfterMessage); layout.baseLine = 0; - const QSize textSize = mHelperText->sizeHint(index, maxWidth, option, &layout.baseLine); // TODO share the QTextDocument + const QSize textSize = mHelperText->sizeHint(index, maxWidth, option, &layout.baseLine); int attachmentsY; const int textVMargin = 3; // adjust this for "compactness" if (textSize.isValid()) {