From 86d792a90e2868635170a1092ac7a9278a84ef71 Mon Sep 17 00:00:00 2001 From: Marcus Eggenberger Date: Thu, 15 Jan 2009 16:16:15 +0100 Subject: [PATCH] No longer saving QTextLayouts for chatitems --- src/qtui/chatitem.cpp | 110 ++++++++++++++++++++++-------------------- src/qtui/chatitem.h | 65 +++++++++---------------- src/qtui/chatline.cpp | 7 --- 3 files changed, 81 insertions(+), 101 deletions(-) diff --git a/src/qtui/chatitem.cpp b/src/qtui/chatitem.cpp index 3f429de2..fc45b77c 100644 --- a/src/qtui/chatitem.cpp +++ b/src/qtui/chatitem.cpp @@ -39,7 +39,6 @@ ChatItem::ChatItem(const qreal &width, const qreal &height, const QPointF &pos, QGraphicsItem *parent) : QGraphicsItem(parent), - _data(0), _boundingRect(0, 0, width, height), _selectionMode(NoSelection), _selectionStart(-1) @@ -49,10 +48,6 @@ ChatItem::ChatItem(const qreal &width, const qreal &height, const QPointF &pos, setPos(pos); } -ChatItem::~ChatItem() { - delete _data; -} - QVariant ChatItem::data(int role) const { QModelIndex index = model()->index(row(), column()); if(!index.isValid()) { @@ -62,8 +57,10 @@ QVariant ChatItem::data(int role) const { return model()->data(index, role); } -QTextLayout *ChatItem::createLayout(QTextOption::WrapMode wrapMode, Qt::Alignment alignment) const { - QTextLayout *layout = new QTextLayout(data(MessageModel::DisplayRole).toString()); +void ChatItem::initLayoutHelper(QTextLayout *layout, QTextOption::WrapMode wrapMode, Qt::Alignment alignment) const { + Q_ASSERT(layout); + + layout->setText(data(MessageModel::DisplayRole).toString()); QTextOption option; option.setWrapMode(wrapMode); @@ -73,33 +70,18 @@ QTextLayout *ChatItem::createLayout(QTextOption::WrapMode wrapMode, Qt::Alignmen QList formatRanges = QtUi::style()->toTextLayoutList(data(MessageModel::FormatRole).value(), layout->text().length()); layout->setAdditionalFormats(formatRanges); - return layout; } -void ChatItem::doLayout() { - QTextLayout *layout_ = layout(); - layout_->beginLayout(); - QTextLine line = layout_->createLine(); +void ChatItem::doLayout(QTextLayout *layout) const { + layout->beginLayout(); + QTextLine line = layout->createLine(); if(line.isValid()) { line.setLineWidth(width()); line.setPosition(QPointF(0,0)); } - layout_->endLayout(); + layout->endLayout(); } -void ChatItem::clearLayout() { - delete _data; - _data = 0; -} - -ChatItemPrivate *ChatItem::privateData() const { - if(!_data) { - ChatItem *that = const_cast(this); - that->_data = that->newPrivateData(); - that->doLayout(); - } - return _data; -} // NOTE: This is not the most time-efficient implementation, but it saves space by not caching unnecessary data // This is a deliberate trade-off. (-> selectFmt creation, data() call) @@ -109,7 +91,11 @@ void ChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QVector formats = additionalFormats(); QTextLayout::FormatRange selectFmt = selectionFormat(); if(selectFmt.format.isValid()) formats.append(selectFmt); - layout()->draw(painter, QPointF(0,0), formats, boundingRect()); + QTextLayout layout; + initLayout(&layout); + layout.draw(painter, QPointF(0,0), formats, boundingRect()); + + // layout()->draw(painter, QPointF(0,0), formats, boundingRect()); // Debuging Stuff // uncomment partially or all of the following stuff: @@ -142,8 +128,11 @@ void ChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, qint16 ChatItem::posToCursor(const QPointF &pos) const { if(pos.y() > height()) return data(MessageModel::DisplayRole).toString().length(); if(pos.y() < 0) return 0; - for(int l = layout()->lineCount() - 1; l >= 0; l--) { - QTextLine line = layout()->lineAt(l); + + QTextLayout layout; + initLayout(&layout); + for(int l = layout.lineCount() - 1; l >= 0; l--) { + QTextLine line = layout.lineAt(l); if(pos.y() >= line.y()) { return line.xToCursor(pos.x(), QTextLine::CursorOnCharacter); } @@ -238,10 +227,10 @@ QList ChatItem::findWords(const QString &searchWord, Qt::CaseSensitivity searchIdx = plainText.indexOf(searchWord, searchIdx + 1, caseSensitive); } - bool hadPrivateData = hasPrivateData(); - + QTextLayout layout; + initLayout(&layout); foreach(int idx, indexList) { - QTextLine line = layout()->lineForTextPosition(idx); + QTextLine line = layout.lineForTextPosition(idx); qreal x = line.cursorToX(idx); qreal width = line.cursorToX(idx + searchWord.count()) - x; qreal height = line.height(); @@ -249,8 +238,6 @@ QList ChatItem::findWords(const QString &searchWord, Qt::CaseSensitivity resultList << QRectF(x, y, width, height); } - if(!hadPrivateData) - clearLayout(); return resultList; } @@ -312,7 +299,9 @@ void SenderChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *op Q_UNUSED(option); Q_UNUSED(widget); painter->setClipRect(boundingRect()); // no idea why QGraphicsItem clipping won't work - qreal layoutWidth = layout()->minimumWidth(); + QTextLayout layout; + initLayout(&layout); + qreal layoutWidth = layout.minimumWidth(); qreal offset = 0; if(chatScene()->senderCutoffMode() == ChatScene::CutoffLeft) offset = qMin(width() - layoutWidth, (qreal)0); @@ -324,10 +313,10 @@ void SenderChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *op if(layoutWidth > width()) { // Draw a nice gradient for longer items // Qt's text drawing with a gradient brush sucks, so we use an alpha-channeled pixmap instead - QPixmap pixmap(layout()->boundingRect().toRect().size()); + QPixmap pixmap(layout.boundingRect().toRect().size()); pixmap.fill(Qt::transparent); QPainter pixPainter(&pixmap); - layout()->draw(&pixPainter, QPointF(qMax(offset, (qreal)0), 0), QVector() << selectFmt); + layout.draw(&pixPainter, QPointF(qMax(offset, (qreal)0), 0), QVector() << selectFmt); pixPainter.end(); // Create alpha channel mask @@ -349,7 +338,7 @@ void SenderChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *op pixmap.setAlphaChannel(mask); painter->drawPixmap(0, 0, pixmap); } else { - layout()->draw(painter, QPointF(0,0), QVector() << selectFmt, boundingRect()); + layout.draw(painter, QPointF(0,0), QVector() << selectFmt, boundingRect()); } } @@ -360,7 +349,8 @@ void SenderChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *op ContentsChatItem::ActionProxy ContentsChatItem::_actionProxy; ContentsChatItem::ContentsChatItem(const qreal &width, const QPointF &pos, QGraphicsItem *parent) - : ChatItem(0, 0, pos, parent) + : ChatItem(0, 0, pos, parent), + _data(0) { const QAbstractItemModel *model_ = model(); QModelIndex index = model_->index(row(), column()); @@ -369,6 +359,18 @@ ContentsChatItem::ContentsChatItem(const qreal &width, const QPointF &pos, QGrap setGeometryByWidth(width); } +ContentsChatItem::~ContentsChatItem() { + delete _data; +} + +ContentsChatItemPrivate *ContentsChatItem::privateData() const { + if(!_data) { + ContentsChatItem *that = const_cast(this); + that->_data = new ContentsChatItemPrivate(findClickables(), that); + } + return _data; +} + qreal ContentsChatItem::setGeometryByWidth(qreal w) { if(w != width()) { prepareGeometryChange(); @@ -379,28 +381,30 @@ qreal ContentsChatItem::setGeometryByWidth(qreal w) { while(finder.nextWrapColumn() > 0) lines++; setHeight(lines * fontMetrics()->lineSpacing()); + delete _data; + _data = 0; } return height(); } -void ContentsChatItem::doLayout() { +void ContentsChatItem::doLayout(QTextLayout *layout) const { ChatLineModel::WrapList wrapList = data(ChatLineModel::WrapListRole).value(); if(!wrapList.count()) return; // empty chatitem qreal h = 0; WrapColumnFinder finder(this); - layout()->beginLayout(); + layout->beginLayout(); forever { - QTextLine line = layout()->createLine(); + QTextLine line = layout->createLine(); if(!line.isValid()) break; int col = finder.nextWrapColumn(); - line.setNumColumns(col >= 0 ? col - line.textStart() : layout()->text().length()); + line.setNumColumns(col >= 0 ? col - line.textStart() : layout->text().length()); line.setPosition(QPointF(0, h)); h += fontMetrics()->lineSpacing(); } - layout()->endLayout(); + layout->endLayout(); } // NOTE: This method is not threadsafe and not reentrant! @@ -500,7 +504,7 @@ QVector ContentsChatItem::additionalFormats() const { } void ContentsChatItem::endHoverMode() { - if(hasPrivateData()) { + if(privateData()) { if(privateData()->currentClickable.isValid()) { setCursor(Qt::ArrowCursor); privateData()->currentClickable = Clickable(); @@ -642,7 +646,9 @@ void ContentsChatItem::showWebPreview(const Clickable &click) { #ifndef HAVE_WEBKIT Q_UNUSED(click); #else - QTextLine line = layout()->lineForTextPosition(click.start); + QTextLayout layout; + initLayout(&layout); + QTextLine line = layout.lineForTextPosition(click.start); qreal x = line.cursorToX(click.start); qreal width = line.cursorToX(click.start + click.length) - x; qreal height = line.height(); @@ -666,9 +672,8 @@ void ContentsChatItem::clearWebPreview() { /*************************************************************************************************/ -ContentsChatItem::WrapColumnFinder::WrapColumnFinder(ChatItem *_item) +ContentsChatItem::WrapColumnFinder::WrapColumnFinder(const ChatItem *_item) : item(_item), - layout(0), wrapList(item->data(ChatLineModel::WrapListRole).value()), wordidx(0), lineCount(0), @@ -677,7 +682,6 @@ ContentsChatItem::WrapColumnFinder::WrapColumnFinder(ChatItem *_item) } ContentsChatItem::WrapColumnFinder::~WrapColumnFinder() { - delete layout; } qint16 ContentsChatItem::WrapColumnFinder::nextWrapColumn() { @@ -697,10 +701,10 @@ qint16 ContentsChatItem::WrapColumnFinder::nextWrapColumn() { // check if we have a very long word that needs inter word wrap if(wrapList.at(start).endX > targetWidth) { if(!line.isValid()) { - layout = item->createLayout(QTextOption::NoWrap); - layout->beginLayout(); - line = layout->createLine(); - layout->endLayout(); + item->initLayoutHelper(&layout, QTextOption::NoWrap); + layout.beginLayout(); + line = layout.createLine(); + layout.endLayout(); } return line.xToCursor(targetWidth, QTextLine::CursorOnCharacter); } diff --git a/src/qtui/chatitem.h b/src/qtui/chatitem.h index d6938886..c73d7c14 100644 --- a/src/qtui/chatitem.h +++ b/src/qtui/chatitem.h @@ -30,13 +30,11 @@ #include "uistyle.h" #include "qtui.h" -class QTextLayout; -struct ChatItemPrivate; +#include class ChatItem : public QGraphicsItem { protected: ChatItem(const qreal &width, const qreal &height, const QPointF &pos, QGraphicsItem *parent); - virtual ~ChatItem(); public: inline const QAbstractItemModel *model() const; @@ -48,9 +46,12 @@ public: inline qreal width() const { return _boundingRect.width(); } inline qreal height() const { return _boundingRect.height(); } - QTextLayout *createLayout(QTextOption::WrapMode, Qt::Alignment = Qt::AlignLeft) const; - virtual void doLayout(); - void clearLayout(); + void initLayoutHelper(QTextLayout *layout, QTextOption::WrapMode, Qt::Alignment = Qt::AlignLeft) const; + virtual inline void initLayout(QTextLayout *layout) const { + initLayoutHelper(layout, QTextOption::WrapAnywhere); + doLayout(layout); + } + virtual void doLayout(QTextLayout *) const; virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); enum { Type = ChatScene::ChatItemType }; @@ -82,8 +83,6 @@ protected: virtual void mousePressEvent(QGraphicsSceneMouseEvent *event); virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); - inline QTextLayout *layout() const; - virtual QTextLayout::FormatRange selectionFormat() const; virtual inline QVector additionalFormats() const { return QVector(); } @@ -97,10 +96,6 @@ protected: qint16 posToCursor(const QPointF &pos) const; - inline bool hasPrivateData() const { return (bool)_data; } - ChatItemPrivate *privateData() const; - virtual inline ChatItemPrivate *newPrivateData(); - // WARNING: setGeometry and setHeight should not be used without either: // a) calling prepareGeometryChange() immediately before setColumns() // b) calling Chatline::setPos() immediately afterwards @@ -119,7 +114,6 @@ private: // internal selection stuff void setSelection(int start, int length); - ChatItemPrivate *_data; QRectF _boundingRect; SelectionMode _selectionMode; @@ -128,18 +122,6 @@ private: friend class ChatLine; }; -struct ChatItemPrivate { - QTextLayout *layout; - ChatItemPrivate(QTextLayout *l) : layout(l) {} - virtual ~ChatItemPrivate() { - delete layout; - } -}; - -// inlines of ChatItem -QTextLayout *ChatItem::layout() const { return privateData()->layout; } -ChatItemPrivate *ChatItem::newPrivateData() { return new ChatItemPrivate(createLayout(QTextOption::WrapAnywhere)); } - // ************************************************************ // TimestampChatItem // ************************************************************ @@ -166,7 +148,10 @@ protected: virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); enum { Type = ChatScene::SenderChatItemType }; virtual inline int type() const { return Type; } - virtual inline ChatItemPrivate *newPrivateData() { return new ChatItemPrivate(createLayout(QTextOption::ManualWrap, Qt::AlignRight)); } + virtual inline void initLayout(QTextLayout *layout) const { + initLayoutHelper(layout, QTextOption::ManualWrap, Qt::AlignRight); + doLayout(layout); + } }; // ************************************************************ @@ -180,6 +165,7 @@ class ContentsChatItem : public ChatItem { public: ContentsChatItem(const qreal &width, const QPointF &pos, QGraphicsItem *parent); + ~ContentsChatItem(); enum { Type = ChatScene::ContentsChatItemType }; virtual inline int type() const { return Type; } @@ -198,15 +184,19 @@ protected: virtual QVector additionalFormats() const; - virtual void doLayout(); - virtual inline ChatItemPrivate *newPrivateData(); + virtual inline void initLayout(QTextLayout *layout) const { + initLayoutHelper(layout, QTextOption::WrapAnywhere); + doLayout(layout); + } + virtual void doLayout(QTextLayout *layout) const; private: struct Clickable; class ActionProxy; class WrapColumnFinder; - inline ContentsChatItemPrivate *privateData() const; + ContentsChatItemPrivate *_data; + ContentsChatItemPrivate *privateData() const; QList findClickables() const; Clickable clickableAt(const QPointF &pos) const; @@ -243,32 +233,25 @@ struct ContentsChatItem::Clickable { inline bool isValid() const { return type != Invalid; } }; -struct ContentsChatItemPrivate : ChatItemPrivate { +struct ContentsChatItemPrivate { ContentsChatItem *contentsItem; QList clickables; ContentsChatItem::Clickable currentClickable; ContentsChatItem::Clickable activeClickable; - ContentsChatItemPrivate(QTextLayout *l, const QList &c, ContentsChatItem *parent) - : ChatItemPrivate(l), contentsItem(parent), clickables(c) {} + ContentsChatItemPrivate(const QList &c, ContentsChatItem *parent) : contentsItem(parent), clickables(c) {} }; -//inlines regarding ContentsChatItemPrivate -ChatItemPrivate *ContentsChatItem::newPrivateData() { - return new ContentsChatItemPrivate(createLayout(QTextOption::WrapAnywhere), findClickables(), this); -} -ContentsChatItemPrivate *ContentsChatItem::privateData() const { return (ContentsChatItemPrivate *)ChatItem::privateData(); } - class ContentsChatItem::WrapColumnFinder { public: - WrapColumnFinder(ChatItem *parent); + WrapColumnFinder(const ChatItem *parent); ~WrapColumnFinder(); qint16 nextWrapColumn(); private: - ChatItem *item; - QTextLayout *layout; + const ChatItem *item; + QTextLayout layout; QTextLine line; ChatLineModel::WrapList wrapList; qint16 wordidx; diff --git a/src/qtui/chatline.cpp b/src/qtui/chatline.cpp index 276c73ed..278c1de4 100644 --- a/src/qtui/chatline.cpp +++ b/src/qtui/chatline.cpp @@ -75,9 +75,6 @@ void ChatLine::setFirstColumn(const qreal ×tampWidth, const qreal &senderWi // senderItem doesn't need a geom change as it's Pos is changed (ensured by void ChatScene::firstHandlePositionChanged(qreal xpos)) _senderItem.setGeometry(senderWidth, _height); _senderItem.setPos(senderPos); - - _timestampItem.clearLayout(); - _senderItem.clearLayout(); } // NOTE: contentsPos is in ChatLines coordinate system! @@ -97,9 +94,6 @@ void ChatLine::setSecondColumn(const qreal &senderWidth, const qreal &contentsWi _contentsItem.setPos(contentsPos); - _senderItem.clearLayout(); - _contentsItem.clearLayout(); - if(needGeometryChange) prepareGeometryChange(); @@ -119,7 +113,6 @@ void ChatLine::setGeometryByWidth(const qreal &width, const qreal &contentsWidth } _timestampItem.setHeight(height); _senderItem.setHeight(height); - _contentsItem.clearLayout(); if(needGeometryChange) prepareGeometryChange(); -- 2.20.1