Another Speed boost for the new ChatView.
authorMarcus Eggenberger <egs@quassel-irc.org>
Sun, 21 Sep 2008 11:13:45 +0000 (13:13 +0200)
committerMarcus Eggenberger <egs@quassel-irc.org>
Mon, 22 Sep 2008 09:52:46 +0000 (11:52 +0200)
This commit is more then due but is still WIP.
Heavy changes in ChatScene, ChatLine, ChatItems.
Finally making use of the disposable layouts.

src/qtui/chatitem.cpp
src/qtui/chatitem.h
src/qtui/chatline.cpp
src/qtui/chatline.h
src/qtui/chatlinemodel.h
src/qtui/chatlinemodelitem.cpp
src/qtui/chatscene.cpp
src/qtui/chatscene.h
src/qtui/chatview.cpp
src/qtui/chatview.h

index 28b496e..495a97e 100644 (file)
 #include "qtui.h"
 #include "qtuistyle.h"
 
-ChatItem::ChatItem(ChatLineModel::ColumnType col, QAbstractItemModel *model, QGraphicsItem *parent)
+ChatItem::ChatItem(const qreal &width, const qreal &height, const QPointF &pos, ChatLineModel::ColumnType col, QGraphicsItem *parent)
   : QGraphicsItem(parent),
+    _data(0),
+    _boundingRect(0, 0, width, height),
     _fontMetrics(0),
     _selectionMode(NoSelection),
-    _selectionStart(-1),
-    _layout(0)
+    _selectionStart(-1)
 {
-  Q_ASSERT(model);
-  QModelIndex index = model->index(row(), col);
-  _fontMetrics = QtUi::style()->fontMetrics(model->data(index, ChatLineModel::FormatRole).value<UiStyle::FormatList>().at(0).second);
+  const QAbstractItemModel *model_ = model();
+  QModelIndex index = model_->index(row(), col);
+  _fontMetrics = QtUi::style()->fontMetrics(model_->data(index, ChatLineModel::FormatRole).value<UiStyle::FormatList>().at(0).second);
   setAcceptHoverEvents(true);
   setZValue(20);
+  setPos(pos);
 }
 
 ChatItem::~ChatItem() {
-  delete _layout;
+  delete _data;
 }
 
 QVariant ChatItem::data(int role) const {
@@ -59,20 +61,6 @@ QVariant ChatItem::data(int role) const {
   return model()->data(index, role);
 }
 
-qreal ChatItem::setGeometry(qreal w, qreal h) {
-  if(w == _boundingRect.width()) return _boundingRect.height();
-  prepareGeometryChange();
-  _boundingRect.setWidth(w);
-  if(h < 0) h = computeHeight();
-  _boundingRect.setHeight(h);
-  if(haveLayout()) updateLayout();
-  return h;
-}
-
-qreal ChatItem::computeHeight() {
-  return fontMetrics()->lineSpacing(); // only contents can be multi-line
-}
-
 QTextLayout *ChatItem::createLayout(QTextOption::WrapMode wrapMode, Qt::Alignment alignment) {
   QTextLayout *layout = new QTextLayout(data(MessageModel::DisplayRole).toString());
 
@@ -88,28 +76,30 @@ QTextLayout *ChatItem::createLayout(QTextOption::WrapMode wrapMode, Qt::Alignmen
 }
 
 void ChatItem::updateLayout() {
-  if(!haveLayout())
-    setLayout(createLayout(QTextOption::WrapAnywhere, Qt::AlignLeft));
-
-  layout()->beginLayout();
-  QTextLine line = layout()->createLine();
+  if(!privateData()) {
+    setPrivateData(new ChatItemPrivate(createLayout()));
+  }
+  QTextLayout *layout_ = layout();
+  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 _layout;
-  _layout = 0;
+  delete _data;
+  _data = 0;
 }
 
 // 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)
 void ChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
   Q_UNUSED(option); Q_UNUSED(widget);
-  if(!haveLayout()) updateLayout();
+  if(!hasLayout())
+    updateLayout();
   painter->setClipRect(boundingRect()); // no idea why QGraphicsItem clipping won't work
   //if(_selectionMode == FullSelection) {
     //painter->save();
@@ -131,12 +121,29 @@ void ChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
     formats.append(selectFmt);
   }
   layout()->draw(painter, QPointF(0,0), formats, boundingRect());
+
+  // Debuging Stuff
+  // uncomment the following lines to draw the bounding rect and the row number in alternating colors
+//   if(row() % 2)
+//     painter->setPen(Qt::red);
+//   else
+//     painter->setPen(Qt::blue);
+//   QString rowString = QString::number(row());
+//   QRect rowRect = painter->fontMetrics().boundingRect(rowString);
+//   QPointF topPoint = _boundingRect.topLeft();
+//   topPoint.ry() += rowRect.height();
+//   painter->drawText(topPoint, rowString);
+//   QPointF bottomPoint = _boundingRect.bottomRight();
+//   bottomPoint.rx() -= rowRect.width();
+//   painter->drawText(bottomPoint, rowString);
+//   painter->drawRect(_boundingRect.adjusted(0, 0, -1, -1));
 }
 
 qint16 ChatItem::posToCursor(const QPointF &pos) {
   if(pos.y() > height()) return data(MessageModel::DisplayRole).toString().length();
   if(pos.y() < 0) return 0;
-  if(!haveLayout()) updateLayout();
+  if(!hasLayout())
+    updateLayout();
   for(int l = layout()->lineCount() - 1; l >= 0; l--) {
     QTextLine line = layout()->lineAt(l);
     if(pos.y() >= line.y()) {
@@ -178,7 +185,7 @@ QList<QRectF> ChatItem::findWords(const QString &searchWord, Qt::CaseSensitivity
     searchIdx = plainText.indexOf(searchWord, searchIdx + 1, caseSensitive);
   }
 
-  if(!haveLayout())
+  if(!hasLayout())
     updateLayout();
 
   foreach(int idx, indexList) {
@@ -235,51 +242,39 @@ void ChatItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
   }
 }
 
-/*************************************************************************************************/
-
-/*************************************************************************************************/
-
-void SenderChatItem::updateLayout() {
-  if(!haveLayout()) setLayout(createLayout(QTextOption::WrapAnywhere, Qt::AlignRight));
-  ChatItem::updateLayout();
-}
-
-/*************************************************************************************************/
+// ************************************************************
+// SenderChatItem
+// ************************************************************
 
-ContentsChatItem::ContentsChatItem(QAbstractItemModel *model, QGraphicsItem *parent) : ChatItem(column(), model, parent),
-  _layoutData(0)
+// ************************************************************
+// ContentsChatItem
+// ************************************************************
+ContentsChatItem::ContentsChatItem(const qreal &width, const QPointF &pos, QGraphicsItem *parent)
+  : ChatItem(0, 0, pos, column(), parent)
 {
-
-}
-
-ContentsChatItem::~ContentsChatItem() {
-  delete _layoutData;
-}
-
-qreal ContentsChatItem::computeHeight() {
-  int lines = 1;
-  WrapColumnFinder finder(this);
-  while(finder.nextWrapColumn() > 0) lines++;
-  return lines * fontMetrics()->lineSpacing();
-}
-
-void ContentsChatItem::setLayout(QTextLayout *layout) {
-  if(!_layoutData) {
-    _layoutData = new LayoutData;
-    _layoutData->clickables = findClickables();
-  } else {
-    delete _layoutData->layout;
+  setGeometryByWidth(width);
+}
+
+qreal ContentsChatItem::setGeometryByWidth(qreal w) {
+  if(w != width()) {
+    setWidth(w);
+    // compute height
+    int lines = 1;
+    WrapColumnFinder finder(this);
+    while(finder.nextWrapColumn() > 0)
+      lines++;
+    setHeight(lines * fontMetrics()->lineSpacing());
   }
-  _layoutData->layout = layout;
-}
-
-void ContentsChatItem::clearLayout() {
-  delete _layoutData;
-  _layoutData = 0;
+  return height();
 }
 
 void ContentsChatItem::updateLayout() {
-  if(!haveLayout()) setLayout(createLayout(QTextOption::WrapAnywhere));
+  if(!privateData()) {
+    ContentsChatItemPrivate *data = new ContentsChatItemPrivate(createLayout(QTextOption::WrapAnywhere),
+                                                               findClickables());
+    // data->clickables = findClickables();
+    setPrivateData(data);
+  }
 
   // Now layout
   ChatLineModel::WrapList wrapList = data(ChatLineModel::WrapListRole).value<ChatLineModel::WrapList>();
@@ -367,8 +362,8 @@ QList<ContentsChatItem::Clickable> ContentsChatItem::findClickables() {
 QVector<QTextLayout::FormatRange> ContentsChatItem::additionalFormats() const {
   // mark a clickable if hovered upon
   QVector<QTextLayout::FormatRange> fmt;
-  if(layoutData()->currentClickable.isValid()) {
-    Clickable click = layoutData()->currentClickable;
+  if(privateData()->currentClickable.isValid()) {
+    Clickable click = privateData()->currentClickable;
     QTextLayout::FormatRange f;
     f.start = click.start;
     f.length = click.length;
@@ -379,22 +374,22 @@ QVector<QTextLayout::FormatRange> ContentsChatItem::additionalFormats() const {
 }
 
 void ContentsChatItem::endHoverMode() {
-  if(layoutData()->currentClickable.isValid()) {
+  if(privateData()->currentClickable.isValid()) {
     setCursor(Qt::ArrowCursor);
-    layoutData()->currentClickable = Clickable();
+    privateData()->currentClickable = Clickable();
     update();
   }
 }
 
 void ContentsChatItem::mousePressEvent(QGraphicsSceneMouseEvent *event) {
-  layoutData()->hasDragged = false;
+  privateData()->hasDragged = false;
   ChatItem::mousePressEvent(event);
 }
 
 void ContentsChatItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
-  if(!event->buttons() && !layoutData()->hasDragged) {
+  if(!event->buttons() && !privateData()->hasDragged) {
     // got a click
-    Clickable click = layoutData()->currentClickable;
+    Clickable click = privateData()->currentClickable;
     if(click.isValid()) {
       QString str = data(ChatLineModel::DisplayRole).toString().mid(click.start, click.length);
       switch(click.type) {
@@ -416,9 +411,9 @@ void ContentsChatItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
   // mouse move events always mean we're not hovering anymore...
   endHoverMode();
   // also, check if we have dragged the mouse
-  if(!layoutData()->hasDragged && event->buttons() & Qt::LeftButton
+  if(!privateData()->hasDragged && event->buttons() & Qt::LeftButton
     && (event->buttonDownScreenPos(Qt::LeftButton) - event->screenPos()).manhattanLength() >= QApplication::startDragDistance())
-    layoutData()->hasDragged = true;
+    privateData()->hasDragged = true;
   ChatItem::mouseMoveEvent(event);
 }
 
@@ -430,8 +425,8 @@ void ContentsChatItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) {
 void ContentsChatItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) {
   bool onClickable = false;
   qint16 idx = posToCursor(event->pos());
-  for(int i = 0; i < layoutData()->clickables.count(); i++) {
-    Clickable click = layoutData()->clickables.at(i);
+  for(int i = 0; i < privateData()->clickables.count(); i++) {
+    Clickable click = privateData()->clickables.at(i);
     if(idx >= click.start && idx < click.start + click.length) {
       if(click.type == Clickable::Url)
         onClickable = true;
@@ -441,7 +436,7 @@ void ContentsChatItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) {
       }
       if(onClickable) {
         setCursor(Qt::PointingHandCursor);
-        layoutData()->currentClickable = click;
+        privateData()->currentClickable = click;
         update();
         break;
       }
@@ -458,9 +453,11 @@ ContentsChatItem::WrapColumnFinder::WrapColumnFinder(ChatItem *_item)
     layout(0),
     wrapList(item->data(ChatLineModel::WrapListRole).value<ChatLineModel::WrapList>()),
     wordidx(0),
+    lineCount(0),
+    choppedTrailing(0),
     lastwrapcol(0),
     lastwrappos(0),
-    w(0)
+    width(0)
 {
 }
 
@@ -469,33 +466,35 @@ ContentsChatItem::WrapColumnFinder::~WrapColumnFinder() {
 }
 
 qint16 ContentsChatItem::WrapColumnFinder::nextWrapColumn() {
-  while(wordidx < wrapList.count()) {
-    w += wrapList.at(wordidx).width;
-    if(w >= item->width()) {
-      if(lastwrapcol >= wrapList.at(wordidx).start) {
-        // first word, and it doesn't fit
-        if(!line.isValid()) {
-          layout = item->createLayout(QTextOption::NoWrap);
-          layout->beginLayout();
-          line = layout->createLine();
-          line.setLineWidth(item->width());
-          layout->endLayout();
-        }
-        int idx = line.xToCursor(lastwrappos + item->width(), QTextLine::CursorOnCharacter);
-        qreal x = line.cursorToX(idx, QTextLine::Trailing);
-        w = w - wrapList.at(wordidx).width - (x - lastwrappos);
-        lastwrappos = x;
-        lastwrapcol = idx;
-        return idx;
+  if(wordidx >= wrapList.count())
+    return -1;
+
+  lineCount++;
+  qreal targetWidth = lineCount * item->width() + choppedTrailing;
+
+  qint16 start = wordidx;
+  qint16 end = wrapList.count() - 1;
+
+  // check if the whole line fits
+  if(wrapList.at(end).endX <= targetWidth || start == end)
+    return -1;
+
+  while(true) {
+    if(start == end) {
+      wordidx = start;
+      if(wordidx > 0) {
+       const ChatLineModel::Word &prevWord = wrapList.at(wordidx - 1);
+       choppedTrailing += prevWord.trailing - (targetWidth - prevWord.endX);
       }
-      // not the first word, so just wrap before this
-      lastwrapcol = wrapList.at(wordidx).start;
-      lastwrappos = lastwrappos + w - wrapList.at(wordidx).width;
-      w = 0;
-      return lastwrapcol;
+      return wrapList.at(wordidx).start;
+    }
+    qint16 pivot = (end + start) / 2;
+    if(wrapList.at(pivot).endX > targetWidth && wordidx != pivot) {
+      end = pivot;
+    } else {
+      start = pivot + 1;
     }
-    w += wrapList.at(wordidx).trailing;
-    wordidx++;
   }
   return -1;
 }
+
index a6eb531..587cc60 100644 (file)
 #include "qtui.h"
 
 class QTextLayout;
+struct ChatItemPrivate;
 
 class ChatItem : public QGraphicsItem {
-
 protected:
-  ChatItem(ChatLineModel::ColumnType column, QAbstractItemModel *, QGraphicsItem *parent);
+  ChatItem(const qreal &width, const qreal &height, const QPointF &pos, ChatLineModel::ColumnType column, QGraphicsItem *parent);
   virtual ~ChatItem();
 
 public:
-  inline const QAbstractItemModel *model() const { return chatScene() ? chatScene()->model() : 0; }
+  inline const QAbstractItemModel *model() const;
   inline int row() const;
   virtual ChatLineModel::ColumnType column() const = 0;
   inline ChatScene *chatScene() const { return qobject_cast<ChatScene *>(scene()); }
@@ -48,16 +48,15 @@ public:
   inline qreal width() const { return _boundingRect.width(); }
   inline qreal height() const { return _boundingRect.height(); }
 
-  virtual inline bool haveLayout() const { return layout() != 0; }
-  virtual void clearLayout();
-  virtual QTextLayout *createLayout(QTextOption::WrapMode, Qt::Alignment = Qt::AlignLeft);
+  inline bool hasLayout() const { return (bool)_data; }
+  QTextLayout *createLayout(QTextOption::WrapMode, Qt::Alignment = Qt::AlignLeft);
+  virtual inline QTextLayout *createLayout() { return createLayout(QTextOption::WrapAnywhere); }
+  void clearLayout();
+
   virtual void updateLayout();
   virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
 
-  virtual QVariant data(int role) const;
-
-  // returns height
-  qreal setGeometry(qreal width, qreal height = -1);
+  QVariant data(int role) const;
 
   // selection stuff, to be called by the scene
   void clearSelection();
@@ -71,66 +70,85 @@ protected:
   virtual void mousePressEvent(QGraphicsSceneMouseEvent *event);
   virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
 
-  virtual inline QTextLayout *layout() const { return _layout; }
-  virtual inline void setLayout(QTextLayout *l) { _layout = l; }
+  inline QTextLayout *layout() const;
+
   virtual inline QVector<QTextLayout::FormatRange> additionalFormats() const { return QVector<QTextLayout::FormatRange>(); }
   qint16 posToCursor(const QPointF &pos);
 
-  virtual qreal computeHeight();
+  inline void ChatItem::setPrivateData(ChatItemPrivate *data) { Q_ASSERT(!_data); _data = data; }
+  inline ChatItemPrivate *ChatItem::privateData() const;
 
-  QRectF _boundingRect;
+  // WARNING: setGeometry and setHeight should not be used without either:
+  //  a) calling prepareGeometryChange() immediately before setColumns()
+  //  b) calling Chatline::setPos() immediately afterwards
+  inline void setGeometry(qreal width, qreal height) {
+    _boundingRect.setWidth(width);
+    _boundingRect.setHeight(height);
+  }
+  inline void setHeight(const qreal &height) { _boundingRect.setHeight(height); }
+  inline void setWidth(const qreal &width) { _boundingRect.setWidth(width); }
 
 private:
   // internal selection stuff
   void setSelection(int start, int length);
 
+  ChatItemPrivate *_data;
+  QRectF _boundingRect;
   QFontMetricsF *_fontMetrics;
 
   enum SelectionMode { NoSelection, PartialSelection, FullSelection };
   SelectionMode _selectionMode;
   qint16 _selectionStart, _selectionEnd;
 
-  QTextLayout *_layout;
+  friend class ChatLine;
 };
 
-/*************************************************************************************************/
+struct ChatItemPrivate {
+  QTextLayout *layout;
+  ChatItemPrivate(QTextLayout *l) : layout(l) {}
+  ~ChatItemPrivate() {
+    delete layout;
+  }
+};
+
+// inlines of ChatItem
+QTextLayout *ChatItem::layout() const { return privateData()->layout; }
+ChatItemPrivate *ChatItem::privateData() const { return _data; }
+
+// ************************************************************
+// TimestampChatItem
+// ************************************************************
 
 //! A ChatItem for the timestamp column
 class TimestampChatItem : public ChatItem {
-
 public:
-  TimestampChatItem(QAbstractItemModel *model, QGraphicsItem *parent) : ChatItem(column(), model, parent) {}
-  inline ChatLineModel::ColumnType column() const { return ChatLineModel::TimestampColumn; }
-
+  TimestampChatItem(const qreal &width, const qreal &height, QGraphicsItem *parent) : ChatItem(width, height, QPointF(0, 0), column(), parent) {}
+  virtual inline ChatLineModel::ColumnType column() const { return ChatLineModel::TimestampColumn; }
 };
 
-/*************************************************************************************************/
-
+// ************************************************************
+// SenderChatItem
+// ************************************************************
 //! A ChatItem for the sender column
 class SenderChatItem : public ChatItem {
-
 public:
-  SenderChatItem(QAbstractItemModel *model, QGraphicsItem *parent) : ChatItem(column(), model, parent) {}
-  inline ChatLineModel::ColumnType column() const { return ChatLineModel::SenderColumn; }
-
-  virtual void updateLayout();
+  SenderChatItem(const qreal &width, const qreal &height, const QPointF &pos, QGraphicsItem *parent) : ChatItem(width, height, pos, column(), parent) {}
+  virtual inline ChatLineModel::ColumnType column() const { return ChatLineModel::SenderColumn; }
+  virtual inline QTextLayout *createLayout() { return ChatItem::createLayout(QTextOption::WrapAnywhere, Qt::AlignRight); }
 };
 
-/*************************************************************************************************/
+// ************************************************************
+// ContentsChatItem
+// ************************************************************
+struct ContentsChatItemPrivate;
 
 //! A ChatItem for the contents column
 class ContentsChatItem : public ChatItem {
-
 public:
-  ContentsChatItem(QAbstractItemModel *model, QGraphicsItem *parent);
-  virtual ~ContentsChatItem();
+  ContentsChatItem(const qreal &width, const QPointF &pos, QGraphicsItem *parent);
 
   inline ChatLineModel::ColumnType column() const { return ChatLineModel::ContentsColumn; }
 
-  virtual void clearLayout();
-  virtual void updateLayout();
-  virtual inline bool haveLayout() const { return _layoutData != 0 && layout() != 0; }
-
 protected:
   virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
   virtual void mousePressEvent(QGraphicsSceneMouseEvent *event);
@@ -138,22 +156,25 @@ protected:
   virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *event);
   virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *event);
 
-  virtual inline QTextLayout *layout() const;
-  virtual void setLayout(QTextLayout *l);
   virtual QVector<QTextLayout::FormatRange> additionalFormats() const;
 
+  virtual void updateLayout();
+
 private:
-  struct LayoutData;
   struct Clickable;
   class WrapColumnFinder;
 
-  inline LayoutData *layoutData() const;
+  inline ContentsChatItemPrivate *privateData() const;
 
-  qreal computeHeight();
   QList<Clickable> findClickables();
   void endHoverMode();
 
-  LayoutData *_layoutData;
+  // WARNING: setGeometry and setHeight should not be used without either:
+  //  a) calling prepareGeometryChange() immediately before setColumns()
+  //  b) calling Chatline::setPos() immediately afterwards
+  qreal setGeometryByWidth(qreal w);
+  friend class ChatLine;
+  friend struct ContentsChatItemPrivate;
 };
 
 struct ContentsChatItem::Clickable {
@@ -174,14 +195,12 @@ struct ContentsChatItem::Clickable {
   inline bool isValid() const { return type != Invalid; }
 };
 
-struct ContentsChatItem::LayoutData {
-  QTextLayout *layout;
-  QList<Clickable> clickables;
-  Clickable currentClickable;
+struct ContentsChatItemPrivate : ChatItemPrivate {
+  QList<ContentsChatItem::Clickable> clickables;
+  ContentsChatItem::Clickable currentClickable;
   bool hasDragged;
 
-  LayoutData() { layout = 0; hasDragged = false; }
-  ~LayoutData() { delete layout; }
+  ContentsChatItemPrivate(QTextLayout *l, const QList<ContentsChatItem::Clickable> &c) : ChatItemPrivate(l), clickables(c), hasDragged(false) {}
 };
 
 class ContentsChatItem::WrapColumnFinder {
@@ -197,18 +216,20 @@ private:
   QTextLine line;
   ChatLineModel::WrapList wrapList;
   qint16 wordidx;
+  qint16 lineCount;
+  qreal choppedTrailing;
   qint16 lastwrapcol;
   qreal lastwrappos;
-  qreal w;
+  qreal width;
 };
 
 /*************************************************************************************************/
 
 // Avoid circular include deps
 #include "chatline.h"
+const QAbstractItemModel *ChatItem::model() const { return static_cast<ChatLine *>(parentItem())->model(); }
 int ChatItem::row() const { return static_cast<ChatLine *>(parentItem())->row(); }
 
-QTextLayout *ContentsChatItem::layout() const { return _layoutData->layout; }
-ContentsChatItem::LayoutData *ContentsChatItem::layoutData() const { Q_ASSERT(_layoutData); return _layoutData; }
+ContentsChatItemPrivate *ContentsChatItem::privateData() const { return (ContentsChatItemPrivate *)privateData(); }
 
 #endif
index 1a8501c..df218a5 100644 (file)
 #include "qtuisettings.h"
 #include "qtuistyle.h"
 
-ChatLine::ChatLine(int row, QAbstractItemModel *model, QGraphicsItem *parent)
+// ChatLine::ChatLine(int row, QAbstractItemModel *model, QGraphicsItem *parent)
+//   : QGraphicsItem(parent),
+//     _row(row), // needs to be set before the items
+//     _model(model),
+//     _contentsItem(this),
+//     _senderItem(this),
+//     _timestampItem(this),
+//     _width(0),
+//     _height(0),
+//     _selection(0)
+// {
+//   Q_ASSERT(model);
+//   QModelIndex index = model->index(row, ChatLineModel::ContentsColumn);
+//   setHighlighted(model->data(index, MessageModel::FlagsRole).toInt() & Message::Highlight);
+// }
+
+ChatLine::ChatLine(int row, QAbstractItemModel *model,
+                  const qreal &width,
+                  const qreal &timestampWidth, const qreal &senderWidth, const qreal &contentsWidth,
+                  const QPointF &senderPos, const QPointF &contentsPos,
+                  QGraphicsItem *parent)
   : QGraphicsItem(parent),
     _row(row), // needs to be set before the items
-    _timestampItem(model, this),
-    _senderItem(model, this),
-    _contentsItem(model, this),
-    _width(0),
-    _height(0),
+    _model(model),
+    _contentsItem(contentsWidth, contentsPos, this),
+    _senderItem(senderWidth, _contentsItem.height(), senderPos, this),
+    _timestampItem(timestampWidth, _contentsItem.height(), this),
+    _width(width),
+    _height(_contentsItem.height()),
     _selection(0)
 {
   Q_ASSERT(model);
@@ -67,13 +88,47 @@ ChatItem &ChatLine::item(ChatLineModel::ColumnType column) {
   }
 }
 
-qreal ChatLine::setGeometry(qreal width) {
+// WARNING: setColumns should not be used without either:
+//  a) calling prepareGeometryChange() immediately before setColumns()
+//  b) calling Chatline::setPos() immediately afterwards
+//
+// NOTE: senderPos and contentsPos are in ChatLines coordinate system!
+qreal ChatLine::setColumns(const qreal &timestampWidth, const qreal &senderWidth, const qreal &contentsWidth,
+                          const QPointF &senderPos, const QPointF &contentsPos) {
+  _height = _contentsItem.setGeometryByWidth(contentsWidth);
+  _senderItem.setGeometry(senderWidth, _height);
+  _timestampItem.setGeometry(timestampWidth, _height);
+
+  _senderItem.setPos(senderPos);
+  _contentsItem.setPos(contentsPos);
+
+  _contentsItem.clearLayout();
+  _senderItem.clearLayout();
+  _timestampItem.clearLayout();
+
+  return _height;
+}
+
+// WARNING: setGeometryByWidth should not be used without either:
+//  a) calling prepareGeometryChange() immediately before setColumns()
+//  b) calling Chatline::setPos() immediately afterwards
+qreal ChatLine::setGeometryByWidth(const qreal &width, const qreal &contentsWidth) {
+  _width = width;
+  _height = _contentsItem.setGeometryByWidth(contentsWidth);
+  _timestampItem.setHeight(_height);
+  _senderItem.setHeight(_height);
+  _contentsItem.clearLayout();
+  return _height;
+}
+
+qreal ChatLine::setGeometryByWidth(qreal width) {
   if(width != _width)
     prepareGeometryChange();
 
   ColumnHandleItem *firstColumnHandle = chatScene()->firstColumnHandle();
   ColumnHandleItem *secondColumnHandle = chatScene()->secondColumnHandle();
-  _height = _contentsItem.setGeometry(width - secondColumnHandle->sceneRight());
+
+  _height = _contentsItem.setGeometryByWidth(width - secondColumnHandle->sceneRight());
   _timestampItem.setGeometry(firstColumnHandle->sceneLeft(), _height);
   _senderItem.setGeometry(secondColumnHandle->sceneLeft() - firstColumnHandle->sceneRight(), _height);
 
index d69caa5..13f45ce 100644 (file)
 #include "chatitem.h"
 
 class ChatLine : public QGraphicsItem {
-
 public:
-  ChatLine(int row, QAbstractItemModel *model, QGraphicsItem *parent = 0);
+//   ChatLine(int row, QAbstractItemModel *model, QGraphicsItem *parent = 0);
+  ChatLine(int row, QAbstractItemModel *model,
+          const qreal &width,
+          const qreal &timestampWidth, const qreal &senderWidth, const qreal &contentsWidth,
+          const QPointF &senderPos, const QPointF &contentsPos,
+          QGraphicsItem *parent = 0);
 
   virtual QRectF boundingRect () const;
 
   inline int row() { return _row; }
   inline void setRow(int row) { _row = row; }
-  inline const QAbstractItemModel *model() const { return chatScene() ? chatScene()->model() : 0; }
+  inline const QAbstractItemModel *model() const { return _model; }
   inline ChatScene *chatScene() const { return qobject_cast<ChatScene *>(scene()); }
   inline qreal width() const { return _width; }
   inline qreal height() const { return _height; }
   ChatItem &item(ChatLineModel::ColumnType);
+  inline ChatItem &timestampItem() { return _timestampItem; }
+  inline ChatItem &senderItem() { return _senderItem; }
+  inline ContentsChatItem &contentsItem() { return _contentsItem; }
 
   virtual void paint (QPainter * painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
 
   // returns height
-  qreal setGeometry(qreal width);
+  qreal setGeometryByWidth(qreal width);
   void setSelected(bool selected, ChatLineModel::ColumnType minColumn = ChatLineModel::ContentsColumn);
   void setHighlighted(bool highlighted);
 
-protected:
-
 private:
   int _row;
-  TimestampChatItem _timestampItem;
-  SenderChatItem _senderItem;
+  QAbstractItemModel *_model;
   ContentsChatItem _contentsItem;
+  SenderChatItem _senderItem;
+  TimestampChatItem _timestampItem;
   qreal _width, _height;
 
   enum { Selected = 0x40, Highlighted = 0x80 };
   quint8 _selection;  // save space, so we put both the col and the flags into one byte
+
+  // setColumns and setGeometryByWidth both return height
+  qreal setColumns(const qreal &timestampWidth, const qreal &senderWidth, const qreal &contentsWidth,
+                  const QPointF &senderPos, const QPointF &contentsPos);
+  qreal setGeometryByWidth(const qreal &width, const qreal &contentsWidth);
+  friend class ChatScene;
 };
 
 #endif
index 4604a9b..4f64efe 100644 (file)
@@ -36,6 +36,7 @@ public:
   /// Used to store information about words to be used for wrapping
   struct Word {
     quint16 start;
+    qreal endX;
     qreal width;
     qreal trailing;
   };
index 24e2273..1e12cd1 100644 (file)
@@ -156,6 +156,7 @@ private:
       }
       qreal wordendx = line.cursorToX(oldidx);
       qreal trailingendx = line.cursorToX(idx);
+      word.endX = wordendx;
       word.width = wordendx - wordstartx;
       word.trailing = trailingendx - wordendx;
       wordstartx = trailingendx;
index c3a07b1..ed8e68d 100644 (file)
@@ -43,6 +43,7 @@ ChatScene::ChatScene(QAbstractItemModel *model, const QString &idString, qreal w
     _model(model),
     _singleBufferScene(false),
     _sceneRect(0, 0, width, 0),
+    _viewportHeight(0),
     _selectingItem(0),
     _selectionStart(-1),
     _isSelecting(false),
@@ -95,7 +96,6 @@ void ChatScene::rowsInserted(const QModelIndex &index, int start, int end) {
   bool atTop = true;
   bool atBottom = false;
   bool moveTop = false;
-  bool hasWidth = (width != 0);
 
   if(start > 0) {
     y = _lines.value(start - 1)->y() + _lines.value(start - 1)->height();
@@ -104,19 +104,27 @@ void ChatScene::rowsInserted(const QModelIndex &index, int start, int end) {
   if(start == _lines.count())
     atBottom = true;
 
+  qreal contentsWidth = width - secondColumnHandle()->sceneRight();
+  qreal senderWidth = secondColumnHandle()->sceneLeft() - firstColumnHandle()->sceneRight();
+  qreal timestampWidth = firstColumnHandle()->sceneLeft();
+  QPointF contentsPos(secondColumnHandle()->sceneRight(), 0);
+  QPointF senderPos(firstColumnHandle()->sceneRight(), 0);
+
+
   for(int i = end; i >= start; i--) {
-    ChatLine *line = new ChatLine(i, model());
+    ChatLine *line = new ChatLine(i, model(),
+                                 width,
+                                 timestampWidth, senderWidth, contentsWidth,
+                                 senderPos, contentsPos);
+    if(atTop) {
+      h -= line->height();
+      line->setPos(0, y+h);
+    } else {
+      line->setPos(0, y+h);
+      h += line->height();
+    }
     _lines.insert(start, line);
     addItem(line);
-    if(hasWidth) {
-      if(atTop) {
-       h -= line->setGeometry(width);
-       line->setPos(0, y+h);
-      } else {
-       line->setPos(0, y+h);
-       h += line->setGeometry(width);
-      }
-    }
   }
 
   // update existing items
@@ -159,7 +167,7 @@ void ChatScene::rowsInserted(const QModelIndex &index, int start, int end) {
     updateSceneRect(_sceneRect.adjusted(0, h, 0, 0));
   } else {
     updateSceneRect(_sceneRect.adjusted(0, 0, 0, h));
-    emit sceneHeightChanged(h);
+    emit lastLineChanged(_lines.last());
   }
 
 }
@@ -230,31 +238,57 @@ void ChatScene::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int e
   }
 }
 
+void ChatScene::updateForViewport(qreal width, qreal height) {
+  _viewportHeight = height;
+  setWidth(width);
+}
+
+// setWidth is used for 2 things:
+//  a) updating the scene to fit the width of the corresponding view
+//  b) to update the positions of the items if a columhandle has changed it's position
+// forceReposition is true in the second case
+// this method features some codeduplication for the sake of performance
 void ChatScene::setWidth(qreal width, bool forceReposition) {
   if(width == _sceneRect.width() && !forceReposition)
     return;
 
-  // clock_t startT = clock();
-  qreal oldHeight = _sceneRect.height();
-  qreal y = _sceneRect.y();
-  qreal linePos = y;
-
-  foreach(ChatLine *line, _lines) {
-    line->setPos(0, linePos);
-    linePos += line->setGeometry(width);
+//   clock_t startT = clock();
+
+  qreal linePos = _sceneRect.y() + _sceneRect.height();
+  qreal yBottom = linePos;
+  QList<ChatLine *>::iterator lineIter = _lines.end();
+  QList<ChatLine *>::iterator lineIterBegin = _lines.begin();
+  ChatLine *line = 0;
+  qreal lineHeight = 0;
+  qreal contentsWidth = width - secondColumnHandle()->sceneRight();
+
+  if(forceReposition) {
+    qreal timestampWidth = firstColumnHandle()->sceneLeft();
+    qreal senderWidth = secondColumnHandle()->sceneLeft() - firstColumnHandle()->sceneRight();
+    QPointF senderPos(firstColumnHandle()->sceneRight(), 0);
+    QPointF contentsPos(secondColumnHandle()->sceneRight(), 0);
+    while(lineIter != lineIterBegin) {
+      lineIter--;
+      line = *lineIter;
+      lineHeight = line->setColumns(timestampWidth, senderWidth, contentsWidth, senderPos, contentsPos);
+      linePos -= lineHeight;
+      line->setPos(0, linePos);
+    }
+  } else {
+    while(lineIter != lineIterBegin) {
+      lineIter--;
+      line = *lineIter;
+      lineHeight = line->setGeometryByWidth(width, contentsWidth);
+      linePos -= lineHeight;
+      line->setPos(0, linePos);
+    }
   }
 
-  qreal height = linePos - y;
-
-  updateSceneRect(QRectF(0, y, width, height));
+  updateSceneRect(QRectF(0, linePos, width, yBottom - linePos));
   setHandleXLimits();
 
-  qreal dh = height - oldHeight;
-  if(dh > 0)
-    emit sceneHeightChanged(dh);
-
-  // clock_t endT = clock();
-  // qDebug() << "resized" << _lines.count() << "in" << (float)(endT - startT) / CLOCKS_PER_SEC << "sec";
+//   clock_t endT = clock();
+//   qDebug() << "resized" << _lines.count() << "in" << (float)(endT - startT) / CLOCKS_PER_SEC << "sec";
 }
 
 void ChatScene::handlePositionChanged(qreal xpos) {
@@ -416,7 +450,7 @@ QString ChatScene::selectionToString() const {
 }
 
 void ChatScene::requestBacklog() {
-  static const int REQUEST_COUNT = 100;
+  static const int REQUEST_COUNT = 500;
   int backlogSize = model()->rowCount();
   if(isSingleBufferScene() && backlogSize != 0 && _lastBacklogSize + REQUEST_COUNT <= backlogSize) {
     QModelIndex msgIdx = model()->index(0, 0);
index fa44102..e0aaa50 100644 (file)
@@ -54,6 +54,7 @@ public:
   inline ColumnHandleItem *secondColumnHandle() const { return secondColHandle; }
 
 public slots:
+  void updateForViewport(qreal width, qreal height);
   void setWidth(qreal, bool forceReposition = false);
 
   // these are used by the chatitems to notify the scene and manage selections
@@ -65,7 +66,7 @@ public slots:
   void requestBacklog();
 
 signals:
-  void sceneHeightChanged(qreal dh);
+  void lastLineChanged(QGraphicsItem *);
 
 protected:
   virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent);
@@ -93,6 +94,7 @@ private:
   // we store the size in a member variable.
   QRectF _sceneRect;
   void updateSceneRect(const QRectF &rect);
+  qreal _viewportHeight;
 
   ColumnHandleItem *firstColHandle, *secondColHandle;
   qreal firstColHandlePos, secondColHandlePos;
@@ -108,7 +110,11 @@ private:
 };
 
 bool ChatScene::containsBuffer(const BufferId &id) const {
-  return qobject_cast<MessageFilter*>(model()) ? qobject_cast<MessageFilter*>(model())->containsBuffer(id) : false;
+  MessageFilter *filter = qobject_cast<MessageFilter*>(model());
+  if(filter)
+    return filter->containsBuffer(id);
+  else
+    return false;
 }
 
 #endif
index 6344c9c..6b2d1f5 100644 (file)
@@ -47,29 +47,33 @@ ChatView::ChatView(MessageFilter *filter, QWidget *parent)
 
 void ChatView::init(MessageFilter *filter) {
   setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
-  setAlignment(Qt::AlignBottom);
+  // setAlignment(Qt::AlignBottom);
   setInteractive(true);
+  setOptimizationFlags(QGraphicsView::DontClipPainter | QGraphicsView::DontAdjustForAntialiasing);
+  setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);
+  setTransformationAnchor(QGraphicsView::NoAnchor);
 
   _scene = new ChatScene(filter, filter->idString(), viewport()->width() - 2, this); // see below: resizeEvent()
-  connect(_scene, SIGNAL(sceneHeightChanged(qreal)), this, SLOT(sceneHeightChanged(qreal)));
   connect(_scene, SIGNAL(sceneRectChanged(const QRectF &)), this, SLOT(sceneRectChanged(const QRectF &)));
+  //connect(_scene, SIGNAL(lastLineChanged(QGraphicsItem *)), this, SLOT(lastLineChanged(QGraphicsItem *)));
   setScene(_scene);
 
-  _lastScrollbarPos = verticalScrollBar()->maximum();
   connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(verticalScrollbarChanged(int)));
 }
 
 void ChatView::resizeEvent(QResizeEvent *event) {
   QGraphicsView::resizeEvent(event);
-  scene()->setWidth(viewport()->width() - 2);  // FIXME figure out why we have to hardcode the -2 here -> Qt-Bug most probably
+
+  // FIXME: without the hardcoded -2 Qt reserves space for a horizontal scrollbar even though it's disabled permanently.
+  // this does only occur on QtX11 (at least not on Qt for Mac OS). Seems like a Qt Bug.
+  scene()->updateForViewport(viewport()->width() - 2, viewport()->height());
+  _lastScrollbarPos = verticalScrollBar()->maximum();
   verticalScrollBar()->setValue(verticalScrollBar()->maximum());
 }
 
-void ChatView::sceneHeightChanged(qreal dh) {
-  QAbstractSlider *vbar = verticalScrollBar();
-  Q_ASSERT(vbar);
-  if(vbar->maximum() - vbar->value() <= dh + 5) // in case we had scrolled only about half a line to the bottom we allow a grace of 5
-    vbar->setValue(vbar->maximum());
+void ChatView::lastLineChanged(QGraphicsItem *chatLine) {
+  // FIXME: if vbar offset < chatline height -> centerOn(chatLine);
+  centerOn(chatLine);
 }
 
 void ChatView::verticalScrollbarChanged(int newPos) {
index 5341109..eab096b 100644 (file)
@@ -48,7 +48,7 @@ protected:
   virtual void resizeEvent(QResizeEvent *event);
 
 protected slots:
-  virtual void sceneHeightChanged(qreal dh);
+  void lastLineChanged(QGraphicsItem *chatLine);
   virtual inline void sceneRectChanged(const QRectF &rect) { setSceneRect(rect); }
   virtual void verticalScrollbarChanged(int);