faster ChatLine insertion into the ChatScene. And some minor improvements
authorMarcus Eggenberger <egs@quassel-irc.org>
Sat, 30 Aug 2008 16:22:13 +0000 (18:22 +0200)
committerMarcus Eggenberger <egs@quassel-irc.org>
Sat, 30 Aug 2008 16:22:13 +0000 (18:22 +0200)
src/qtui/chatscene.cpp
src/qtui/chatscene.h
src/qtui/chatview.cpp
src/qtui/chatview.h
src/qtui/columnhandleitem.cpp
src/qtui/columnhandleitem.h

index d16e19c..3a87e87 100644 (file)
 
 const qreal minContentsWidth = 200;
 
-ChatScene::ChatScene(QAbstractItemModel *model, const QString &idString, QObject *parent)
-  : QGraphicsScene(parent),
+ChatScene::ChatScene(QAbstractItemModel *model, const QString &idString, qreal width, QObject *parent)
+  : QGraphicsScene(0, 0, width, 0, parent),
     _idString(idString),
-    _width(0),
-    _height(0),
     _model(model),
     _singleBufferScene(false),
     _selectingItem(0),
@@ -53,19 +51,6 @@ ChatScene::ChatScene(QAbstractItemModel *model, const QString &idString, QObject
     _singleBufferScene = filter->isSingleBufferFilter();
   }
 
-  connect(this, SIGNAL(sceneRectChanged(const QRectF &)), this, SLOT(rectChanged(const QRectF &)));
-
-  connect(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
-         this, SLOT(rowsInserted(const QModelIndex &, int, int)));
-  connect(model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)),
-         this, SLOT(rowsAboutToBeRemoved(const QModelIndex &, int, int)));
-
-  for(int i = 0; i < model->rowCount(); i++) {
-    ChatLine *line = new ChatLine(i, model);
-    _lines.append(line);
-    addItem(line);
-  }
-
   QtUiSettings s;
   int defaultFirstColHandlePos = s.value("ChatView/DefaultFirstColumnHandlePos", 80).toInt();
   int defaultSecondColHandlePos = s.value("ChatView/DefaultSecondColumnHandlePos", 200).toInt();
@@ -77,19 +62,25 @@ ChatScene::ChatScene(QAbstractItemModel *model, const QString &idString, QObject
 
   firstColHandle = new ColumnHandleItem(QtUi::style()->firstColumnSeparator());
   addItem(firstColHandle);
+  firstColHandle->setXPos(firstColHandlePos);
+  connect(firstColHandle, SIGNAL(positionChanged(qreal)), this, SLOT(handlePositionChanged(qreal)));
+  connect(this, SIGNAL(sceneRectChanged(const QRectF &)), firstColHandle, SLOT(sceneRectChanged(const QRectF &)));
 
   secondColHandle = new ColumnHandleItem(QtUi::style()->secondColumnSeparator());
   addItem(secondColHandle);
-
-  connect(firstColHandle, SIGNAL(positionChanged(qreal)), this, SLOT(handlePositionChanged(qreal)));
+  secondColHandle->setXPos(secondColHandlePos);
   connect(secondColHandle, SIGNAL(positionChanged(qreal)), this, SLOT(handlePositionChanged(qreal)));
+  connect(this, SIGNAL(sceneRectChanged(const QRectF &)), secondColHandle, SLOT(sceneRectChanged(const QRectF &)));
 
-  firstColHandle->setXPos(firstColHandlePos);
-  secondColHandle->setXPos(secondColHandlePos);
   setHandleXLimits();
 
-  emit heightChanged(height());
-  emit heightChangedAt(0, height());
+  connect(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
+         this, SLOT(rowsInserted(const QModelIndex &, int, int)));
+  connect(model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)),
+         this, SLOT(rowsAboutToBeRemoved(const QModelIndex &, int, int)));
+
+  if(model->rowCount() > 0)
+    rowsInserted(QModelIndex(), 0, model->rowCount() - 1);
 }
 
 ChatScene::~ChatScene() {
@@ -97,45 +88,49 @@ ChatScene::~ChatScene() {
 
 void ChatScene::rowsInserted(const QModelIndex &index, int start, int end) {
   Q_UNUSED(index);
-  // maybe make this more efficient by prepending stuff with negative yval
-  // dunno if that's worth not guranteeing that 0 is on the top...
-  // TODO bulk inserts, iterators
   qreal h = 0;
-  qreal y = 0;
-  if(_width && start > 0)
+  qreal y = sceneRect().y();
+  qreal width = sceneRect().width();
+  bool atTop = true;
+  bool atBottom = false;
+  bool hasWidth = (width != 0);
+
+  if(start > 0) {
     y = _lines.value(start - 1)->y() + _lines.value(start - 1)->height();
+    atTop = false;
+  }
+  if(start == _lines.count())
+    atBottom = true;
+
+  // right now this method doesn't properly handle messages that are inserted at an arbitrary point
+  Q_ASSERT(atBottom || atTop);
 
-  for(int i = start; i <= end; i++) {
+  for(int i = end; i >= start; i--) {
     ChatLine *line = new ChatLine(i, model());
-    _lines.insert(i, line);
+    _lines.insert(start, line);
     addItem(line);
-    if(_width > 0) {
-      line->setPos(0, y+h);
-      h += line->setGeometry(_width);
+    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
   for(int i = end+1; i < _lines.count(); i++) {
     _lines[i]->setRow(i);
   }
 
-  // update selection
-  if(_selectionStart >= 0) {
-    int offset = end - start + 1;
-    if(_selectionStart >= start) _selectionStart += offset;
-    if(_selectionEnd >= start) _selectionEnd += offset;
-    if(_firstSelectionRow >= start) _firstSelectionRow += offset;
-    if(_lastSelectionRow >= start) _lastSelectionRow += offset;
-  }
-
-  if(h > 0) {
-    _height += h;
-    for(int i = end+1; i < _lines.count(); i++) {
-      _lines.at(i)->moveBy(0, h);
-    }
-    setSceneRect(QRectF(0, 0, _width, _height));
-    emit heightChanged(_height);
-    emit heightChangedAt(_lines.at(start)->y(), h);
+  // update sceneRect
+  if(atBottom) {
+    setSceneRect(sceneRect().adjusted(0, 0, 0, h));
+    emit sceneHeightChanged(h);
+  } else {
+    setSceneRect(sceneRect().adjusted(0, h, 0, 0));
   }
 }
 
@@ -144,6 +139,12 @@ void ChatScene::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int e
 
   qreal h = 0; // total height of removed items;
 
+  bool atTop = (start == 0);
+  bool atBottom = (end == _lines.count() - 1);
+
+  // right now this method doesn't properly handle messages that are removed at an arbitrary point
+  Q_ASSERT(atBottom || atTop);
+
   // remove items from scene
   QList<ChatLine *>::iterator lineIter = _lines.begin() + start;
   int lineCount = start;
@@ -159,52 +160,35 @@ void ChatScene::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int e
     _lines.at(i)->setRow(i);
   }
 
-  // update selection
-  if(_selectionStart >= 0) {
-    int offset = end - start + 1;
-    if(_selectionStart >= start)
-      _selectionStart -= offset;
-    if(_selectionEnd >= start)
-      _selectionEnd -= offset;
-    if(_firstSelectionRow >= start)
-      _firstSelectionRow -= offset;
-    if(_lastSelectionRow >= start)
-      _lastSelectionRow -= offset;
-  }
-
-  // reposition remaining chatlines
-  if(h > 0) {
-    Q_ASSERT(_height >= h);
-    _height -= h;
-    for(int i = start; i < _lines.count(); i++) {
-      _lines.at(i)->moveBy(0, -h);
-    }
-    setSceneRect(QRectF(0, 0, _width, _height));
-    emit heightChanged(_height);
-    Q_ASSERT(_lines.isEmpty() || (start < _lines.count())); // if _lines isn't empty it better contain start
-    qreal changePos = (_lines.isEmpty()) ? 0 : _lines.at(start)->y();
-    emit heightChangedAt(changePos, -h);
+  // update sceneRect
+  if(atBottom) {
+    setSceneRect(sceneRect().adjusted(0, 0, 0, -h));
+  } else {
+    setSceneRect(sceneRect().adjusted(0, h, 0, 0));
   }
 }
 
-void ChatScene::setWidth(qreal w) {
-  qreal oldh = _height;
-  _width = w;
-  _height = 0;
+void ChatScene::setWidth(qreal width, bool forceReposition) {
+  if(width == sceneRect().width() && !forceReposition)
+    return;
+
+  qreal oldHeight = sceneRect().height();
+  qreal y = sceneRect().y();
+  qreal linePos = y;
+
   foreach(ChatLine *line, _lines) {
-    line->setPos(0, _height);
-    _height += line->setGeometry(_width);
+    line->setPos(0, linePos);
+    linePos += line->setGeometry(width);
   }
-  setSceneRect(QRectF(0, 0, w, _height));
-  setHandleXLimits();
-  emit heightChanged(_height);
-  emit heightChangedAt(0, _height - oldh);
 
-}
+  qreal height = linePos - y;
+
+  setSceneRect(QRectF(0, y, width, height));
+  setHandleXLimits();
 
-void ChatScene::rectChanged(const QRectF &rect) {
-  firstColHandle->sceneRectChanged(rect);
-  secondColHandle->sceneRectChanged(rect);
+  qreal dh = height - oldHeight;
+  if(dh > 0)
+    emit sceneHeightChanged(dh);
 }
 
 void ChatScene::handlePositionChanged(qreal xpos) {
@@ -223,7 +207,7 @@ void ChatScene::handlePositionChanged(qreal xpos) {
   s.setValue(QString("ChatView/DefaultFirstColumnHandlePos"), firstColHandlePos);
   s.setValue(QString("ChatView/DefaultSecondColumnHandlePos"), secondColHandlePos);
 
-  setWidth(width());  // readjust all chatlines
+  setWidth(width(), true);  // readjust all chatlines
   // we get ugly redraw errors if we don't update this explicitly... :(
   // width() should be the same for both handles, so just use firstColHandle regardless
   //update(qMin(oldx, xpos), 0, qMax(oldx, xpos) + firstColHandle->width(), height());
index 45ab0f4..a542843 100644 (file)
@@ -37,71 +37,68 @@ class QGraphicsSceneMouseEvent;
 class ChatScene : public QGraphicsScene {
   Q_OBJECT
 
-  public:
-    ChatScene(QAbstractItemModel *model, const QString &idString, QObject *parent);
-    virtual ~ChatScene();
-
-    inline QAbstractItemModel *model() const { return _model; }
-    inline QString idString() const { return _idString; }
-
-    int sectionByScenePos(int x);
-    inline int sectionByScenePos(const QPoint &pos) { return sectionByScenePos(pos.x()); }
-    inline bool isSingleBufferScene() const { return _singleBufferScene; }
-    inline ChatLine *chatLine(int row) { return (row < _lines.count()) ? _lines[row] : 0; }
-
-    inline QRectF firstColumnHandleRect() const { return firstColHandle->boundingRect().translated(firstColHandle->x(), 0); }
-    inline QRectF secondColumnHandleRect() const { return secondColHandle->boundingRect().translated(secondColHandle->x(), 0); }
-
-  public slots:
-    void setWidth(qreal);
-
-    // these are used by the chatitems to notify the scene and manage selections
-    void setSelectingItem(ChatItem *item);
-    ChatItem *selectingItem() const { return _selectingItem; }
-    void startGlobalSelection(ChatItem *item, const QPointF &itemPos);
-    void putToClipboard(const QString &);
-
-    void requestBacklog();
-
-  signals:
-    void heightChanged(qreal height);
-    void heightChangedAt(qreal ypos, qreal hdiff);
-
-  protected:
-    virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent);
-    virtual void mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent);
-    virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent);
-
-  protected slots:
-    void rowsInserted(const QModelIndex &, int, int);
-    void rowsAboutToBeRemoved(const QModelIndex &, int, int);
-
-  private slots:
-    void rectChanged(const QRectF &);
-    void handlePositionChanged(qreal xpos);
-
-  private:
-    void setHandleXLimits();
-    void updateSelection(const QPointF &pos);
-    QString selectionToString() const;
-
-    QString _idString;
-    qreal _width, _height;
-    QAbstractItemModel *_model;
-    QList<ChatLine *> _lines;
-    bool _singleBufferScene;
-
-    ColumnHandleItem *firstColHandle, *secondColHandle;
-    qreal firstColHandlePos, secondColHandlePos;
-
-    ChatItem *_selectingItem;
-    int _selectionStartCol, _selectionMinCol;
-    int _selectionStart;
-    int _selectionEnd;
-    int _firstSelectionRow, _lastSelectionRow;
-    bool _isSelecting;
-
-    int _lastBacklogSize;
+public:
+  ChatScene(QAbstractItemModel *model, const QString &idString, qreal width, QObject *parent);
+  virtual ~ChatScene();
+
+  inline QAbstractItemModel *model() const { return _model; }
+  inline QString idString() const { return _idString; }
+
+  int sectionByScenePos(int x);
+  inline int sectionByScenePos(const QPoint &pos) { return sectionByScenePos(pos.x()); }
+  inline bool isSingleBufferScene() const { return _singleBufferScene; }
+  inline ChatLine *chatLine(int row) { return (row < _lines.count()) ? _lines[row] : 0; }
+
+  inline QRectF firstColumnHandleRect() const { return firstColHandle->boundingRect().translated(firstColHandle->x(), 0); }
+  inline QRectF secondColumnHandleRect() const { return secondColHandle->boundingRect().translated(secondColHandle->x(), 0); }
+
+public slots:
+  void setWidth(qreal, bool forceReposition = false);
+
+  // these are used by the chatitems to notify the scene and manage selections
+  void setSelectingItem(ChatItem *item);
+  ChatItem *selectingItem() const { return _selectingItem; }
+  void startGlobalSelection(ChatItem *item, const QPointF &itemPos);
+  void putToClipboard(const QString &);
+
+  void requestBacklog();
+
+signals:
+  void sceneHeightChanged(qreal dh);
+
+protected:
+  virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent);
+  virtual void mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent);
+  virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent);
+
+protected slots:
+  void rowsInserted(const QModelIndex &, int, int);
+  void rowsAboutToBeRemoved(const QModelIndex &, int, int);
+
+private slots:
+  void handlePositionChanged(qreal xpos);
+
+private:
+  void setHandleXLimits();
+  void updateSelection(const QPointF &pos);
+  QString selectionToString() const;
+
+  QString _idString;
+  QAbstractItemModel *_model;
+  QList<ChatLine *> _lines;
+  bool _singleBufferScene;
+
+  ColumnHandleItem *firstColHandle, *secondColHandle;
+  qreal firstColHandlePos, secondColHandlePos;
+
+  ChatItem *_selectingItem;
+  int _selectionStartCol, _selectionMinCol;
+  int _selectionStart;
+  int _selectionEnd;
+  int _firstSelectionRow, _lastSelectionRow;
+  bool _isSelecting;
+
+  int _lastBacklogSize;
 };
 
 #endif
index 96e8d94..ff0460a 100644 (file)
@@ -50,33 +50,31 @@ void ChatView::init(MessageFilter *filter) {
   setAlignment(Qt::AlignBottom);
   setInteractive(true);
 
-  _scene = new ChatScene(filter, filter->idString(), this);
-  connect(_scene, SIGNAL(heightChangedAt(qreal, qreal)), this, SLOT(sceneHeightChangedAt(qreal, qreal)));
+  _scene = new ChatScene(filter, filter->idString(), viewport()->width(), this);
+  connect(_scene, SIGNAL(sceneHeightChanged(qreal)), this, SLOT(sceneHeightChanged(qreal)));
   setScene(_scene);
 
-  _lastScrollbarPos = 0;
+  _lastScrollbarPos = verticalScrollBar()->maximum();
   connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(verticalScrollbarChanged(int)));
 }
 
 void ChatView::resizeEvent(QResizeEvent *event) {
-  scene()->setWidth(event->size().width() - 2);  // FIXME figure out why we have to hardcode the -2 here
+//   scene()->setWidth(event->size().width() - 2);  // FIXME figure out why we have to hardcode the -2 here
+  QGraphicsView::resizeEvent(event);
+  scene()->setWidth(viewport()->width());
   verticalScrollBar()->setValue(verticalScrollBar()->maximum());
 }
 
-void ChatView::sceneHeightChangedAt(qreal ypos, qreal hdiff) {
-  setSceneRect(scene()->sceneRect());
-  int y = mapFromScene(0, ypos).y();
-  if(y <= viewport()->height() + 2) {  // be a bit tolerant here, also FIXME (why we need the 2px?)
-    verticalScrollBar()->setValue(verticalScrollBar()->value() + hdiff);
-  }
+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::verticalScrollbarChanged(int newPos) {
   QAbstractSlider *vbar = verticalScrollBar();
   Q_ASSERT(vbar);
-  
-  // FIXME dirty hack to battle the "I just scroll up a pixel on hide()/show()" problem
-  if(vbar->maximum() - vbar->value() < 5) vbar->setValue(vbar->maximum()); 
 
   if(newPos < _lastScrollbarPos) {
     int relativePos = 100;
index 27713ea..832d151 100644 (file)
@@ -48,7 +48,7 @@ protected:
   virtual void resizeEvent(QResizeEvent *event);
 
 protected slots:
-  virtual void sceneHeightChangedAt(qreal ypos, qreal hdiff);
+  virtual void sceneHeightChanged(qreal dh);
   virtual void verticalScrollbarChanged(int);
 
 private:
index 7a93c4f..cf0e5d7 100644 (file)
@@ -27,6 +27,8 @@
 #include <QPainter>
 #include <QPalette>
 
+#include <QDebug>
+
 ColumnHandleItem::ColumnHandleItem(qreal w, QGraphicsItem *parent)
   : QGraphicsItem(parent),
     _width(w),
@@ -55,8 +57,8 @@ void ColumnHandleItem::setXLimits(qreal min, qreal max) {
 }
 
 void ColumnHandleItem::sceneRectChanged(const QRectF &rect) {
-  if(rect.height() != boundingRect().height())
-    prepareGeometryChange();
+  Q_UNUSED(rect)
+  prepareGeometryChange();
 }
 
 void ColumnHandleItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
index 43ecb47..c96c687 100644 (file)
@@ -33,14 +33,16 @@ class ColumnHandleItem : public QObject, public QGraphicsItem {
     ColumnHandleItem(qreal width, QGraphicsItem *parent = 0);
 
     inline qreal width() const { return _width; }
-    inline QRectF boundingRect() const { return QRectF(-_width/2, 0, _width, scene()->height()); }
+    inline QRectF boundingRect() const { return QRectF(-_width/2, scene()->sceneRect().y(), _width, scene()->height()); }
     void setXPos(qreal xpos);
 
     void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
 
-    void sceneRectChanged(const QRectF &);
     void setXLimits(qreal min, qreal max);
 
+  public slots:
+    void sceneRectChanged(const QRectF &);
+
   protected:
     void hoverEnterEvent(QGraphicsSceneHoverEvent *event);
     void hoverLeaveEvent(QGraphicsSceneHoverEvent *event);