ChatScene now properly react on aboutToRemoveRows(), which should improve stability...
authorMarcus Eggenberger <egs@quassel-irc.org>
Mon, 18 Aug 2008 18:37:06 +0000 (20:37 +0200)
committerMarcus Eggenberger <egs@quassel-irc.org>
Mon, 18 Aug 2008 18:37:06 +0000 (20:37 +0200)
Also hopefully fixing backlog replay which was borked in some channels.

src/client/messagemodel.cpp
src/client/messagemodel.h
src/qtui/chatscene.cpp
src/qtui/chatscene.h

index c6605de..940dda2 100644 (file)
 
 #include "message.h"
 
-MessageModel::MessageModel(QObject *parent) : QAbstractItemModel(parent) {
-
-
-
-}
-
-MessageModel::~MessageModel() {
-
-
+MessageModel::MessageModel(QObject *parent)
+  : QAbstractItemModel(parent)
+{
 }
 
 QVariant MessageModel::data(const QModelIndex &index, int role) const {
   int row = index.row(); int column = index.column();
-  if(row < 0 || row >= _messageList.count() || column < 0) return QVariant();
-  if(role == ColumnTypeRole) return column;
+  if(row < 0 || row >= _messageList.count() || column < 0)
+    return QVariant();
+
+  if(role == ColumnTypeRole)
+    return column;
+
   return _messageList[row]->data(index.column(), role);
 }
 
 bool MessageModel::setData(const QModelIndex &index, const QVariant &value, int role) {
   int row = index.row();
-  if(row < 0 || row >= _messageList.count()) return false;
+  if(row < 0 || row >= _messageList.count())
+    return false;
+
   if(_messageList[row]->setData(index.column(), value, role)) {
     emit dataChanged(index, index);
     return true;
   }
+
   return false;
 }
 
@@ -54,10 +55,10 @@ bool MessageModel::insertMessage(const Message &msg, bool fakeMsg) {
   MsgId id = msg.msgId();
   int idx = indexForId(id);
   if(!fakeMsg && idx < _messageList.count()) { // check for duplicate
-    if(_messageList.value(idx)->data(0, MsgIdRole).value<MsgId>() == id) {
+    if(_messageList[idx]->msgId() == id)
       return false;
-    }
   }
+
   MessageModelItem *item = createMessageModelItem(msg);
   beginInsertRows(QModelIndex(), idx, idx);
   _messageList.insert(idx, item);
@@ -68,14 +69,15 @@ bool MessageModel::insertMessage(const Message &msg, bool fakeMsg) {
 void MessageModel::insertMessages(const QList<Message> &msglist) {
   if(msglist.isEmpty()) return;
   // FIXME make this more efficient by grouping msgs
-  foreach(Message msg, msglist) insertMessage(msg);
-
+  foreach(Message msg, msglist)
+    insertMessage(msg);
 }
 
 void MessageModel::clear() {
-  reset();
+  beginRemoveRows(QModelIndex(), 0, rowCount() - 1);
   qDeleteAll(_messageList);
   _messageList.clear();
+  endRemoveRows();
 }
 
 // returns index of msg with given Id or of the next message after that (i.e., the index where we'd insert this msg)
@@ -94,28 +96,26 @@ int MessageModel::indexForId(MsgId id) {
 
 /**********************************************************************************/
 
-MessageModelItem::MessageModelItem(const Message &msg) {
-  _timestamp = msg.timestamp();
-  _msgId = msg.msgId();
-  _bufferId = msg.bufferInfo().bufferId();
-  _type = msg.type();
-  _flags = msg.flags();
-
-}
-
-MessageModelItem::~MessageModelItem() {
-
+MessageModelItem::MessageModelItem(const Message &msg) :
+  _timestamp(msg.timestamp()),
+  _msgId(msg.msgId()),
+  _bufferId(msg.bufferInfo().bufferId()),
+  _type(msg.type()),
+  _flags(msg.flags())
+{
 }
 
 QVariant MessageModelItem::data(int column, int role) const {
-  if(column < MessageModel::TimestampColumn || column > MessageModel::ContentsColumn) return QVariant();
+  if(column < MessageModel::TimestampColumn || column > MessageModel::ContentsColumn)
+    return QVariant();
+
   switch(role) {
-    case MessageModel::MsgIdRole: return QVariant::fromValue<MsgId>(_msgId);
-    case MessageModel::BufferIdRole: return QVariant::fromValue<BufferId>(_bufferId);
-    case MessageModel::TypeRole: return _type;
-    case MessageModel::FlagsRole: return (int)_flags;
-    case MessageModel::TimestampRole: return _timestamp;
-    default: return QVariant();
+  case MessageModel::MsgIdRole: return QVariant::fromValue<MsgId>(_msgId);
+  case MessageModel::BufferIdRole: return QVariant::fromValue<BufferId>(_bufferId);
+  case MessageModel::TypeRole: return _type;
+  case MessageModel::FlagsRole: return (int)_flags;
+  case MessageModel::TimestampRole: return _timestamp;
+  default: return QVariant();
   }
 }
 
index c381f0f..cc61c4f 100644 (file)
@@ -33,73 +33,75 @@ struct MsgId;
 class MessageModel : public QAbstractItemModel {
   Q_OBJECT
 
-  public:
-    enum MessageRole {
-      DisplayRole = Qt::DisplayRole,
-      EditRole = Qt::EditRole,
-      MsgIdRole = Qt::UserRole,
-      BufferIdRole,
-      TypeRole,
-      FlagsRole,
-      TimestampRole,
-      FormatRole,
-      ColumnTypeRole,
-      UserRole
-    };
+public:
+  enum MessageRole {
+    DisplayRole = Qt::DisplayRole,
+    EditRole = Qt::EditRole,
+    MsgIdRole = Qt::UserRole,
+    BufferIdRole,
+    TypeRole,
+    FlagsRole,
+    TimestampRole,
+    FormatRole,
+    ColumnTypeRole,
+    UserRole
+  };
 
-    enum ColumnType {
-      TimestampColumn, SenderColumn, ContentsColumn, UserColumnType
-    };
+  enum ColumnType {
+    TimestampColumn, SenderColumn, ContentsColumn, UserColumnType
+  };
 
-    MessageModel(QObject *parent);
-    virtual ~MessageModel();
+  MessageModel(QObject *parent);
 
-    inline QModelIndex index(int row, int column, const QModelIndex &/*parent*/ = QModelIndex()) const { return createIndex(row, column); }
-    inline QModelIndex parent(const QModelIndex &) const { return QModelIndex(); }
-    inline int rowCount(const QModelIndex &/*parent*/ = QModelIndex()) const { return _messageList.count(); }
-    inline int columnCount(const QModelIndex &/*parent*/ = QModelIndex()) const { return 3; }
+  inline QModelIndex index(int row, int column, const QModelIndex &/*parent*/ = QModelIndex()) const { return createIndex(row, column); }
+  inline QModelIndex parent(const QModelIndex &) const { return QModelIndex(); }
+  inline int rowCount(const QModelIndex &/*parent*/ = QModelIndex()) const { return _messageList.count(); }
+  inline int columnCount(const QModelIndex &/*parent*/ = QModelIndex()) const { return 3; }
 
-    virtual QVariant data(const QModelIndex &index, int role) const;
-    virtual bool setData(const QModelIndex &index, const QVariant &value, int role);
+  virtual QVariant data(const QModelIndex &index, int role) const;
+  virtual bool setData(const QModelIndex &index, const QVariant &value, int role);
 
-    //virtual Qt::ItemFlags flags(const QModelIndex &index) const;
+  //virtual Qt::ItemFlags flags(const QModelIndex &index) const;
 
-    bool insertMessage(const Message &, bool fakeMsg = false);
-    void insertMessages(const QList<Message> &);
+  bool insertMessage(const Message &, bool fakeMsg = false);
+  void insertMessages(const QList<Message> &);
 
-    void clear();
+  void clear();
 
-  protected:
-    virtual MessageModelItem *createMessageModelItem(const Message &) = 0;
+protected:
+  virtual MessageModelItem *createMessageModelItem(const Message &) = 0;
 
-  private:
-    QList<MessageModelItem *> _messageList;
-
-    int indexForId(MsgId);
+private:
+  QList<MessageModelItem *> _messageList;
 
+  int indexForId(MsgId);
 };
 
 class MessageModelItem {
-
-  public:
-
-    //! Creates a MessageModelItem from a Message object.
-    /** This baseclass implementation takes care of all Message data *except* the stylable strings.
-     *  Subclasses need to provide Qt::DisplayRole at least, which should describe the plaintext
-     *  strings without formattings (e.g. for searching purposes).
-     */
-    MessageModelItem(const Message &);
-    virtual ~MessageModelItem();
-
-    virtual QVariant data(int column, int role) const;
-    virtual bool setData(int column, const QVariant &value, int role) = 0;
-
-  private:
-    QDateTime _timestamp;
-    MsgId _msgId;
-    BufferId _bufferId;
-    Message::Type _type;
-    Message::Flags _flags;
+public:
+  //! Creates a MessageModelItem from a Message object.
+  /** This baseclass implementation takes care of all Message data *except* the stylable strings.
+   *  Subclasses need to provide Qt::DisplayRole at least, which should describe the plaintext
+   *  strings without formattings (e.g. for searching purposes).
+   */
+  MessageModelItem(const Message &);
+  inline virtual ~MessageModelItem() {}
+
+  virtual QVariant data(int column, int role) const;
+  virtual bool setData(int column, const QVariant &value, int role) = 0;
+
+  inline const QDateTime &timeStamp() const { return _timestamp; }
+  inline MsgId msgId() const { return _msgId; }
+  inline BufferId bufferId() const { return _bufferId; }
+  inline Message::Type msgType() const { return _type; }
+  inline Message::Flags msgFlags() const { return _flags; }
+  
+private:
+  QDateTime _timestamp;
+  MsgId _msgId;
+  BufferId _bufferId;
+  Message::Type _type;
+  Message::Flags _flags;
 };
 
 #endif
index 67ce63c..05d26c5 100644 (file)
@@ -45,7 +45,8 @@ ChatScene::ChatScene(QAbstractItemModel *model, const QString &idString, QObject
     _singleBufferScene(false),
     _selectingItem(0),
     _selectionStart(-1),
-    _isSelecting(false)
+    _isSelecting(false),
+    _lastBacklogSize(0)
 {
   MessageFilter *filter = qobject_cast<MessageFilter*>(model);
   if(filter) {
@@ -54,8 +55,11 @@ ChatScene::ChatScene(QAbstractItemModel *model, const QString &idString, QObject
 
   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(modelAboutToBeReset()), this, SLOT(modelReset()));
+  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);
@@ -86,7 +90,6 @@ ChatScene::ChatScene(QAbstractItemModel *model, const QString &idString, QObject
 }
 
 ChatScene::~ChatScene() {
-
 }
 
 void ChatScene::rowsInserted(const QModelIndex &index, int start, int end) {
@@ -96,7 +99,9 @@ void ChatScene::rowsInserted(const QModelIndex &index, int start, int end) {
   // TODO bulk inserts, iterators
   qreal h = 0;
   qreal y = 0;
-  if(_width && start > 0) y = _lines.value(start - 1)->y() + _lines.value(start - 1)->height();
+  if(_width && start > 0)
+    y = _lines.value(start - 1)->y() + _lines.value(start - 1)->height();
+
   for(int i = start; i <= end; i++) {
     ChatLine *line = new ChatLine(i, model());
     _lines.insert(i, line);
@@ -123,21 +128,56 @@ void ChatScene::rowsInserted(const QModelIndex &index, int start, int end) {
   if(h > 0) {
     _height += h;
     for(int i = end+1; i < _lines.count(); i++) {
-      _lines.value(i)->moveBy(0, h);
+      _lines.at(i)->moveBy(0, h);
     }
     setSceneRect(QRectF(0, 0, _width, _height));
     emit heightChanged(_height);
   }
 }
 
-void ChatScene::modelReset() {
-  foreach(ChatLine *line, _lines) {
-    removeItem(line);
-    delete line;
+void ChatScene::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) {
+  Q_UNUSED(parent);
+
+  qreal h = 0; // total height of removed items;
+
+  // remove items from scene
+  QList<ChatLine *>::iterator lineIter = _lines.begin() + start;
+  int lineCount = start;
+  while(lineIter != _lines.end() && lineCount <= end) {
+    h += (*lineIter)->height();
+    delete *lineIter;
+    lineIter = _lines.erase(lineIter);
+    lineCount++;
+  }
+
+  // update rows of remaining chatlines
+  for(int i = start; i < _lines.count(); i++) {
+    _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);
   }
-  _lines.clear();
-  setSceneRect(QRectF(0, 0, _width, 0));
-  emit heightChanged(0);
 }
 
 void ChatScene::setWidth(qreal w) {
index 1fee5c2..b454e1d 100644 (file)
@@ -70,7 +70,7 @@ class ChatScene : public QGraphicsScene {
 
   protected slots:
     void rowsInserted(const QModelIndex &, int, int);
-    void modelReset();
+    void rowsAboutToBeRemoved(const QModelIndex &, int, int);
 
   private slots:
     void rectChanged(const QRectF &);