Cache ChatLine layout data for visible chatlines
authorManuel Nickschas <sputnick@quassel-irc.org>
Tue, 11 May 2010 20:56:02 +0000 (22:56 +0200)
committerManuel Nickschas <sputnick@quassel-irc.org>
Tue, 11 May 2010 21:00:30 +0000 (23:00 +0200)
Up to now, we've computed e.g. the text layout for a chatline on the fly. This
happens surprisingly often, for example whenever the mouse is moved. As a result,
moving the mouse (among other things) caused high CPU load.

We now cache such data within the chatitems until the chatline goes out of view, in
which case the cache is cleared in order to not waste space needlessly.

Fixes #954.

src/qtui/chatitem.cpp
src/qtui/chatitem.h
src/qtui/chatline.cpp
src/qtui/chatline.h
src/qtui/chatview.cpp
src/qtui/chatview.h

index 05f0944..bec1eb7 100644 (file)
@@ -33,6 +33,7 @@
 #include "chatitem.h"
 #include "chatline.h"
 #include "chatlinemodel.h"
 #include "chatitem.h"
 #include "chatline.h"
 #include "chatlinemodel.h"
+#include "chatview.h"
 #include "contextmenuactionprovider.h"
 #include "iconloader.h"
 #include "mainwin.h"
 #include "contextmenuactionprovider.h"
 #include "iconloader.h"
 #include "mainwin.h"
@@ -43,11 +44,16 @@ ChatItem::ChatItem(const QRectF &boundingRect, ChatLine *parent)
 : _parent(parent),
   _boundingRect(boundingRect),
   _selectionMode(NoSelection),
 : _parent(parent),
   _boundingRect(boundingRect),
   _selectionMode(NoSelection),
-  _selectionStart(-1)
+  _selectionStart(-1),
+  _cachedLayout(0)
 {
 
 }
 
 {
 
 }
 
+ChatItem::~ChatItem() {
+  delete _cachedLayout;
+}
+
 ChatLine *ChatItem::chatLine() const {
   return _parent;
 }
 ChatLine *ChatItem::chatLine() const {
   return _parent;
 }
@@ -56,6 +62,10 @@ ChatScene *ChatItem::chatScene() const {
   return chatLine()->chatScene();
 }
 
   return chatLine()->chatScene();
 }
 
+ChatView *ChatItem::chatView() const {
+  return chatScene()->chatView();
+}
+
 const QAbstractItemModel *ChatItem::model() const {
   return chatLine()->model();
 }
 const QAbstractItemModel *ChatItem::model() const {
   return chatLine()->model();
 }
@@ -90,20 +100,24 @@ QVariant ChatItem::data(int role) const {
   return model()->data(index, role);
 }
 
   return model()->data(index, role);
 }
 
-qint16 ChatItem::posToCursor(const QPointF &posInLine) const {
-  QPointF pos = mapFromLine(posInLine);
-  if(pos.y() > height()) return data(MessageModel::DisplayRole).toString().length();
-  if(pos.y() < 0) return 0;
+QTextLayout *ChatItem::layout() const {
+  if(_cachedLayout)
+    return _cachedLayout;
 
 
-  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);
-    }
-  }
-  return 0;
+  _cachedLayout = new QTextLayout;
+  initLayout(_cachedLayout);
+  chatView()->setHasCache(chatLine());
+  return _cachedLayout;
+}
+
+void ChatItem::clearCache() {
+  delete _cachedLayout;
+  _cachedLayout = 0;
+}
+
+void ChatItem::initLayout(QTextLayout *layout, QTextOption::WrapMode mode, Qt::Alignment alignment) const {
+  initLayoutHelper(layout, mode, alignment);
+  doLayout(layout);
 }
 
 void ChatItem::initLayoutHelper(QTextLayout *layout, QTextOption::WrapMode wrapMode, Qt::Alignment alignment) const {
 }
 
 void ChatItem::initLayoutHelper(QTextLayout *layout, QTextOption::WrapMode wrapMode, Qt::Alignment alignment) const {
@@ -121,10 +135,6 @@ void ChatItem::initLayoutHelper(QTextLayout *layout, QTextOption::WrapMode wrapM
   layout->setAdditionalFormats(formatRanges);
 }
 
   layout->setAdditionalFormats(formatRanges);
 }
 
-UiStyle::FormatList ChatItem::formatList() const {
-  return data(MessageModel::FormatRole).value<UiStyle::FormatList>();
-}
-
 void ChatItem::doLayout(QTextLayout *layout) const {
   layout->beginLayout();
   QTextLine line = layout->createLine();
 void ChatItem::doLayout(QTextLayout *layout) const {
   layout->beginLayout();
   QTextLine line = layout->createLine();
@@ -135,6 +145,26 @@ void ChatItem::doLayout(QTextLayout *layout) const {
   layout->endLayout();
 }
 
   layout->endLayout();
 }
 
+UiStyle::FormatList ChatItem::formatList() const {
+  return data(MessageModel::FormatRole).value<UiStyle::FormatList>();
+}
+
+qint16 ChatItem::posToCursor(const QPointF &posInLine) const {
+  QPointF pos = mapFromLine(posInLine);
+  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);
+    if(pos.y() >= line.y()) {
+      return line.xToCursor(pos.x(), QTextLine::CursorOnCharacter);
+    }
+  }
+  return 0;
+}
+
 void ChatItem::paintBackground(QPainter *painter) {
   QVariant bgBrush;
   if(_selectionMode == FullSelection)
 void ChatItem::paintBackground(QPainter *painter) {
   QVariant bgBrush;
   if(_selectionMode == FullSelection)
@@ -153,9 +183,7 @@ void ChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
   painter->setClipRect(boundingRect());
   paintBackground(painter);
 
   painter->setClipRect(boundingRect());
   paintBackground(painter);
 
-  QTextLayout layout;
-  initLayout(&layout);
-  layout.draw(painter, pos(), additionalFormats(), boundingRect());
+  layout()->draw(painter, pos(), additionalFormats(), boundingRect());
 
   //  layout()->draw(painter, QPointF(0,0), formats, boundingRect());
 
 
   //  layout()->draw(painter, QPointF(0,0), formats, boundingRect());
 
@@ -311,10 +339,8 @@ QList<QRectF> ChatItem::findWords(const QString &searchWord, Qt::CaseSensitivity
     searchIdx = plainText.indexOf(searchWord, searchIdx + 1, caseSensitive);
   }
 
     searchIdx = plainText.indexOf(searchWord, searchIdx + 1, caseSensitive);
   }
 
-  QTextLayout layout;
-  initLayout(&layout);
   foreach(int idx, indexList) {
   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();
     qreal x = line.cursorToX(idx);
     qreal width = line.cursorToX(idx + searchWord.count()) - x;
     qreal height = line.height();
@@ -385,9 +411,7 @@ void SenderChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *op
   painter->setClipRect(boundingRect());
   paintBackground(painter);
 
   painter->setClipRect(boundingRect());
   paintBackground(painter);
 
-  QTextLayout layout;
-  initLayout(&layout);
-  qreal layoutWidth = layout.minimumWidth();
+  qreal layoutWidth = layout()->minimumWidth();
   qreal offset = 0;
   if(chatScene()->senderCutoffMode() == ChatScene::CutoffLeft)
     offset = qMin(width() - layoutWidth, (qreal)0);
   qreal offset = 0;
   if(chatScene()->senderCutoffMode() == ChatScene::CutoffLeft)
     offset = qMin(width() - layoutWidth, (qreal)0);
@@ -397,10 +421,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
   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);
     pixmap.fill(Qt::transparent);
     QPainter pixPainter(&pixmap);
-    layout.draw(&pixPainter, QPointF(qMax(offset, (qreal)0), 0), additionalFormats());
+    layout()->draw(&pixPainter, QPointF(qMax(offset, (qreal)0), 0), additionalFormats());
     pixPainter.end();
 
     // Create alpha channel mask
     pixPainter.end();
 
     // Create alpha channel mask
@@ -422,7 +446,7 @@ void SenderChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *op
     pixmap.setAlphaChannel(mask);
     painter->drawPixmap(pos(), pixmap);
   } else {
     pixmap.setAlphaChannel(mask);
     painter->drawPixmap(pos(), pixmap);
   } else {
-    layout.draw(painter, pos(), additionalFormats(), boundingRect());
+    layout()->draw(painter, pos(), additionalFormats(), boundingRect());
   }
   painter->restore();
 }
   }
   painter->restore();
 }
@@ -461,6 +485,12 @@ ContentsChatItem::~ContentsChatItem() {
   delete _data;
 }
 
   delete _data;
 }
 
+void ContentsChatItem::clearCache() {
+  delete _data;
+  _data = 0;
+  ChatItem::clearCache();
+}
+
 ContentsChatItemPrivate *ContentsChatItem::privateData() const {
   if(!_data) {
     ContentsChatItem *that = const_cast<ContentsChatItem *>(this);
 ContentsChatItemPrivate *ContentsChatItem::privateData() const {
   if(!_data) {
     ContentsChatItem *that = const_cast<ContentsChatItem *>(this);
@@ -489,7 +519,6 @@ qreal ContentsChatItem::setGeometryByWidth(qreal w) {
 }
 
 void ContentsChatItem::doLayout(QTextLayout *layout) const {
 }
 
 void ContentsChatItem::doLayout(QTextLayout *layout) const {
-  // QString t = data(Qt::DisplayRole).toString(); bool d = t.contains("protien");
   ChatLineModel::WrapList wrapList = data(ChatLineModel::WrapListRole).value<ChatLineModel::WrapList>();
   if(!wrapList.count()) return; // empty chatitem
 
   ChatLineModel::WrapList wrapList = data(ChatLineModel::WrapListRole).value<ChatLineModel::WrapList>();
   if(!wrapList.count()) return; // empty chatitem
 
@@ -677,9 +706,7 @@ void ContentsChatItem::showWebPreview(const Clickable &click) {
 #ifndef HAVE_WEBKIT
   Q_UNUSED(click);
 #else
 #ifndef HAVE_WEBKIT
   Q_UNUSED(click);
 #else
-  QTextLayout layout;
-  initLayout(&layout);
-  QTextLine line = layout.lineForTextPosition(click.start());
+  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();
   qreal x = line.cursorToX(click.start());
   qreal width = line.cursorToX(click.start() + click.length()) - x;
   qreal height = line.height();
index b6f4cf4..436f3d0 100644 (file)
@@ -33,6 +33,7 @@
 #include <QTextLayout>
 
 class ChatLine;
 #include <QTextLayout>
 
 class ChatLine;
+class ChatView;
 
 /* All external positions are relative to the parent ChatLine */
 /* Yes, that's also true for the boundingRect() and related things */
 
 /* All external positions are relative to the parent ChatLine */
 /* Yes, that's also true for the boundingRect() and related things */
@@ -41,12 +42,13 @@ class ChatItem {
 protected:
   // boundingRect is relative to the parent ChatLine
   ChatItem(const QRectF &boundingRect, ChatLine *parent);
 protected:
   // boundingRect is relative to the parent ChatLine
   ChatItem(const QRectF &boundingRect, ChatLine *parent);
-  virtual ~ChatItem() {}
+  virtual ~ChatItem();
 
 public:
   const QAbstractItemModel *model() const;
   ChatLine *chatLine() const;
   ChatScene *chatScene() const;
 
 public:
   const QAbstractItemModel *model() const;
   ChatLine *chatLine() const;
   ChatScene *chatScene() const;
+  ChatView *chatView() const;
   int row() const;
   virtual ChatLineModel::ColumnType column() const = 0;
 
   int row() const;
   virtual ChatLineModel::ColumnType column() const = 0;
 
@@ -63,14 +65,6 @@ public:
   QPointF mapToScene(const QPointF &) const;
   QPointF mapFromScene(const QPointF &) const;
 
   QPointF mapToScene(const QPointF &) const;
   QPointF mapFromScene(const QPointF &) const;
 
-  void initLayoutHelper(QTextLayout *layout, QTextOption::WrapMode, Qt::Alignment = Qt::AlignLeft) const;
-  virtual inline void initLayout(QTextLayout *layout) const {
-    initLayoutHelper(layout, QTextOption::NoWrap);
-    doLayout(layout);
-  }
-  virtual void doLayout(QTextLayout *) const;
-  virtual UiStyle::FormatList formatList() const;
-
   virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
   virtual inline int type() const { return ChatScene::ChatItemType; }
 
   virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
   virtual inline int type() const { return ChatScene::ChatItemType; }
 
@@ -89,6 +83,13 @@ public:
   virtual void addActionsToMenu(QMenu *menu, const QPointF &itemPos);
   virtual void handleClick(const QPointF &pos, ChatScene::ClickMode);
 
   virtual void addActionsToMenu(QMenu *menu, const QPointF &itemPos);
   virtual void handleClick(const QPointF &pos, ChatScene::ClickMode);
 
+  void initLayoutHelper(QTextLayout *layout, QTextOption::WrapMode, Qt::Alignment = Qt::AlignLeft) const;
+
+  //! Remove internally cached data
+  /** This removes e.g. the cached QTextLayout to avoid wasting space for nonvisible ChatLines
+   */
+  virtual void clearCache();
+
 protected:
   enum SelectionMode {
     NoSelection,
 protected:
   enum SelectionMode {
     NoSelection,
@@ -103,6 +104,12 @@ protected:
   virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *) {};
   virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *) {};
 
   virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *) {};
   virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *) {};
 
+  QTextLayout *layout() const;
+  virtual inline void initLayout(QTextLayout *layout) const { initLayout(layout, QTextOption::NoWrap); }
+  virtual void initLayout(QTextLayout *layout, QTextOption::WrapMode, Qt::Alignment alignment = Qt::AlignLeft) const;
+  virtual void doLayout(QTextLayout *) const;
+  virtual UiStyle::FormatList formatList() const;
+
   void paintBackground(QPainter *);
   QVector<QTextLayout::FormatRange> selectionFormats() const;
   virtual QVector<QTextLayout::FormatRange> additionalFormats() const;
   void paintBackground(QPainter *);
   QVector<QTextLayout::FormatRange> selectionFormats() const;
   virtual QVector<QTextLayout::FormatRange> additionalFormats() const;
@@ -130,6 +137,8 @@ private:
   SelectionMode _selectionMode;
   qint16 _selectionStart, _selectionEnd;
 
   SelectionMode _selectionMode;
   qint16 _selectionStart, _selectionEnd;
 
+  mutable QTextLayout *_cachedLayout;
+
   // internal selection stuff
   void setSelection(int start, int length);
 
   // internal selection stuff
   void setSelection(int start, int length);
 
@@ -161,10 +170,7 @@ public:
 protected:
   virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
   virtual inline int type() const { return ChatScene::SenderChatItemType; }
 protected:
   virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
   virtual inline int type() const { return ChatScene::SenderChatItemType; }
-  virtual inline void initLayout(QTextLayout *layout) const {
-    initLayoutHelper(layout, QTextOption::ManualWrap, Qt::AlignRight);
-    doLayout(layout);
-  }
+  virtual inline void initLayout(QTextLayout *layout) const { ChatItem::initLayout(layout, QTextOption::ManualWrap, Qt::AlignRight); }
 };
 
 // ************************************************************
 };
 
 // ************************************************************
@@ -185,6 +191,8 @@ public:
   inline ChatLineModel::ColumnType column() const { return ChatLineModel::ContentsColumn; }
   QFontMetricsF *fontMetrics() const;
 
   inline ChatLineModel::ColumnType column() const { return ChatLineModel::ContentsColumn; }
   QFontMetricsF *fontMetrics() const;
 
+  virtual void clearCache();
+
 protected:
   virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
   virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *event);
 protected:
   virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
   virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *event);
@@ -196,10 +204,7 @@ protected:
 
   virtual QVector<QTextLayout::FormatRange> additionalFormats() const;
 
 
   virtual QVector<QTextLayout::FormatRange> additionalFormats() const;
 
-  virtual inline void initLayout(QTextLayout *layout) const {
-    initLayoutHelper(layout, QTextOption::WrapAtWordBoundaryOrAnywhere);
-    doLayout(layout);
-  }
+  virtual inline void initLayout(QTextLayout *layout) const { ChatItem::initLayout(layout, QTextOption::WrapAtWordBoundaryOrAnywhere); }
   virtual void doLayout(QTextLayout *layout) const;
   virtual UiStyle::FormatList formatList() const;
 
   virtual void doLayout(QTextLayout *layout) const;
   virtual UiStyle::FormatList formatList() const;
 
@@ -207,7 +212,7 @@ private:
   class ActionProxy;
   class WrapColumnFinder;
 
   class ActionProxy;
   class WrapColumnFinder;
 
-  ContentsChatItemPrivate *_data;
+  mutable ContentsChatItemPrivate *_data;
   ContentsChatItemPrivate *privateData() const;
 
   Clickable clickableAt(const QPointF &pos) const;
   ContentsChatItemPrivate *privateData() const;
 
   Clickable clickableAt(const QPointF &pos) const;
@@ -217,13 +222,14 @@ private:
   void clearWebPreview();
 
   qreal setGeometryByWidth(qreal w);
   void clearWebPreview();
 
   qreal setGeometryByWidth(qreal w);
-  friend class ChatLine;
-  friend struct ContentsChatItemPrivate;
 
   QFontMetricsF *_fontMetrics;
 
   // we need a receiver for Action signals
   static ActionProxy _actionProxy;
 
   QFontMetricsF *_fontMetrics;
 
   // we need a receiver for Action signals
   static ActionProxy _actionProxy;
+
+  friend class ChatLine;
+  friend struct ContentsChatItemPrivate;
 };
 
 struct ContentsChatItemPrivate {
 };
 
 struct ContentsChatItemPrivate {
index 08d089e..99e0982 100644 (file)
@@ -59,6 +59,11 @@ ChatLine::ChatLine(int row, QAbstractItemModel *model,
   setHighlighted(index.data(MessageModel::FlagsRole).toInt() & Message::Highlight);
 }
 
   setHighlighted(index.data(MessageModel::FlagsRole).toInt() & Message::Highlight);
 }
 
+ChatLine::~ChatLine() {
+  if(chatView())
+    chatView()->setHasCache(this, false);
+}
+
 ChatItem *ChatLine::item(ChatLineModel::ColumnType column) {
   switch(column) {
     case ChatLineModel::TimestampColumn:
 ChatItem *ChatLine::item(ChatLineModel::ColumnType column) {
   switch(column) {
     case ChatLineModel::TimestampColumn:
@@ -82,6 +87,12 @@ ChatItem *ChatLine::itemAt(const QPointF &pos) {
   return 0;
 }
 
   return 0;
 }
 
+void ChatLine::clearCache() {
+  _timestampItem.clearCache();
+  _senderItem.clearCache();
+  _contentsItem.clearCache();
+}
+
 void ChatLine::setMouseGrabberItem(ChatItem *item) {
   _mouseGrabberItem = item;
 }
 void ChatLine::setMouseGrabberItem(ChatItem *item) {
   _mouseGrabberItem = item;
 }
index a7c68e8..4536520 100644 (file)
@@ -35,6 +35,8 @@ public:
            const QPointF &senderPos, const QPointF &contentsPos,
            QGraphicsItem *parent = 0);
 
            const QPointF &senderPos, const QPointF &contentsPos,
            QGraphicsItem *parent = 0);
 
+  virtual ~ChatLine();
+
   virtual inline QRectF boundingRect () const { return QRectF(0, 0, _width, _height); }
 
   inline QModelIndex index() const { return model()->index(row(), 0); }
   virtual inline QRectF boundingRect () const { return QRectF(0, 0, _width, _height); }
 
   inline QModelIndex index() const { return model()->index(row(), 0); }
@@ -44,7 +46,7 @@ public:
 
   inline const QAbstractItemModel *model() const { return _model; }
   inline ChatScene *chatScene() const { return qobject_cast<ChatScene *>(scene()); }
 
   inline const QAbstractItemModel *model() const { return _model; }
   inline ChatScene *chatScene() const { return qobject_cast<ChatScene *>(scene()); }
-  inline ChatView *chatView() const { return chatScene()->chatView(); }
+  inline ChatView *chatView() const { return chatScene() ? chatScene()->chatView() : 0; }
 
   inline qreal width() const { return _width; }
   inline qreal height() const { return _height; }
 
   inline qreal width() const { return _width; }
   inline qreal height() const { return _height; }
@@ -69,6 +71,8 @@ public:
   void setSelected(bool selected, ChatLineModel::ColumnType minColumn = ChatLineModel::ContentsColumn);
   void setHighlighted(bool highlighted);
 
   void setSelected(bool selected, ChatLineModel::ColumnType minColumn = ChatLineModel::ContentsColumn);
   void setHighlighted(bool highlighted);
 
+  void clearCache();
+
 protected:
   virtual bool sceneEvent(QEvent *event);
 
 protected:
   virtual bool sceneEvent(QEvent *event);
 
index ddec8fb..e61dc9e 100644 (file)
@@ -123,6 +123,8 @@ bool ChatView::event(QEvent *event) {
 void ChatView::resizeEvent(QResizeEvent *event) {
   QGraphicsView::resizeEvent(event);
 
 void ChatView::resizeEvent(QResizeEvent *event) {
   QGraphicsView::resizeEvent(event);
 
+  // FIXME: do we really need to scroll down on resize?
+
   // we can reduce viewport updates if we scroll to the bottom allready at the beginning
   verticalScrollBar()->setValue(verticalScrollBar()->maximum());
   scene()->updateForViewport(viewport()->width(), viewport()->height());
   // we can reduce viewport updates if we scroll to the bottom allready at the beginning
   verticalScrollBar()->setValue(verticalScrollBar()->maximum());
   scene()->updateForViewport(viewport()->width(), viewport()->height());
@@ -130,6 +132,8 @@ void ChatView::resizeEvent(QResizeEvent *event) {
 
   _lastScrollbarPos = verticalScrollBar()->maximum();
   verticalScrollBar()->setValue(verticalScrollBar()->maximum());
 
   _lastScrollbarPos = verticalScrollBar()->maximum();
   verticalScrollBar()->setValue(verticalScrollBar()->maximum());
+
+  checkChatLineCaches();
 }
 
 void ChatView::adjustSceneRect() {
 }
 
 void ChatView::adjustSceneRect() {
@@ -222,6 +226,7 @@ bool chatLinePtrLessThan(ChatLine *one, ChatLine *other) {
   return one->row() < other->row();
 }
 
   return one->row() < other->row();
 }
 
+// TODO: figure out if it's cheaper to use a cached list (that we'd need to keep updated)
 QSet<ChatLine *> ChatView::visibleChatLines(Qt::ItemSelectionMode mode) const {
   QSet<ChatLine *> result;
   foreach(QGraphicsItem *item, items(viewport()->rect().adjusted(-1, -1, 1, 1), mode)) {
 QSet<ChatLine *> ChatView::visibleChatLines(Qt::ItemSelectionMode mode) const {
   QSet<ChatLine *> result;
   foreach(QGraphicsItem *item, items(viewport()->rect().adjusted(-1, -1, 1, 1), mode)) {
@@ -338,3 +343,29 @@ void ChatView::invalidateFilter() {
     _invalidateFilter = true;
   }
 }
     _invalidateFilter = true;
   }
 }
+
+void ChatView::scrollContentsBy(int dx, int dy) {
+  QGraphicsView::scrollContentsBy(dx, dy);
+  checkChatLineCaches();
+}
+
+void ChatView::setHasCache(ChatLine *line, bool hasCache) {
+  if(hasCache)
+    _linesWithCache.insert(line);
+  else
+    _linesWithCache.remove(line);
+}
+
+void ChatView::checkChatLineCaches() {
+  qreal top = mapToScene(viewport()->rect().topLeft()).y() - 10; // some grace area to avoid premature cleaning
+  qreal bottom = mapToScene(viewport()->rect().bottomRight()).y() + 10;
+  QSet<ChatLine *>::iterator iter = _linesWithCache.begin();
+  while(iter != _linesWithCache.end()) {
+    ChatLine *line = *iter;
+    if(line->pos().y() + line->height() < top || line->pos().y() > bottom) {
+      line->clearCache();
+      iter = _linesWithCache.erase(iter);
+    } else
+      ++iter;
+  }
+}
index 3f9e29c..0265567 100644 (file)
@@ -69,11 +69,18 @@ public:
 
   virtual void addActionsToMenu(QMenu *, const QPointF &pos);
 
 
   virtual void addActionsToMenu(QMenu *, const QPointF &pos);
 
-  virtual bool event(QEvent *event);
-
   inline bool isMarkerLineVisible() const { return _markerLineVisible; }
   inline ChatLine *markedLine() const { return _markedLine; }
 
   inline bool isMarkerLineVisible() const { return _markerLineVisible; }
   inline ChatLine *markedLine() const { return _markedLine; }
 
+  //! Tell the view that this ChatLine has cached data
+  /** ChatLines cache some layout data that should be cleared as soon as it's no
+   *  longer visible. A ChatLine caching data registers itself with this method to
+   *  tell the view about it. The view will call ChatLine::clearCache() when
+   *  appropriate.
+   *  \param line The ChatLine having cached data
+   */
+  void setHasCache(ChatLine *line, bool hasCache = true);
+
 public slots:
   inline virtual void clear() {}
   void zoomIn();
 public slots:
   inline virtual void clear() {}
   void zoomIn();
@@ -84,7 +91,9 @@ public slots:
   void setMarkedLine(ChatLine *line);
 
 protected:
   void setMarkedLine(ChatLine *line);
 
 protected:
+  virtual bool event(QEvent *event);
   virtual void resizeEvent(QResizeEvent *event);
   virtual void resizeEvent(QResizeEvent *event);
+  virtual void scrollContentsBy(int dx, int dy);
 
 protected slots:
   virtual void verticalScrollbarChanged(int);
 
 protected slots:
   virtual void verticalScrollbarChanged(int);
@@ -92,6 +101,7 @@ protected slots:
 private slots:
   void lastLineChanged(QGraphicsItem *chatLine, qreal offset);
   void adjustSceneRect();
 private slots:
   void lastLineChanged(QGraphicsItem *chatLine, qreal offset);
   void adjustSceneRect();
+  void checkChatLineCaches();
   void mouseMoveWhileSelecting(const QPointF &scenePos);
   void scrollTimerTimeout();
   void invalidateFilter();
   void mouseMoveWhileSelecting(const QPointF &scenePos);
   void scrollTimerTimeout();
   void invalidateFilter();
@@ -109,6 +119,7 @@ private:
   bool _invalidateFilter;
   bool _markerLineVisible;
   ChatLine *_markedLine;
   bool _invalidateFilter;
   bool _markerLineVisible;
   ChatLine *_markedLine;
+  QSet<ChatLine *> _linesWithCache;
 };
 
 
 };