New FlatProxyModel
authorMarcus Eggenberger <egs@quassel-irc.org>
Thu, 21 May 2009 14:47:51 +0000 (16:47 +0200)
committerMarcus Eggenberger <egs@quassel-irc.org>
Thu, 21 May 2009 20:01:12 +0000 (22:01 +0200)
A ProxyModel that flattens a treelike model in preorder.

src/uisupport/CMakeLists.txt
src/uisupport/flatproxymodel.cpp [new file with mode: 0644]
src/uisupport/flatproxymodel.h [new file with mode: 0644]

index 92fc995..eccde9f 100644 (file)
@@ -16,6 +16,7 @@ set(SOURCES
     clickablelabel.cpp
     colorbutton.cpp
     contextmenuactionprovider.cpp
     clickablelabel.cpp
     colorbutton.cpp
     contextmenuactionprovider.cpp
+    flatproxymodel.cpp
     graphicalui.cpp
     icon.cpp
     iconloader.cpp
     graphicalui.cpp
     icon.cpp
     iconloader.cpp
@@ -43,6 +44,7 @@ set(MOC_HDRS
     clickablelabel.h
     colorbutton.h
     contextmenuactionprovider.h
     clickablelabel.h
     colorbutton.h
     contextmenuactionprovider.h
+    flatproxymodel.h
     graphicalui.h
     iconloader.h
     inputline.h
     graphicalui.h
     iconloader.h
     inputline.h
diff --git a/src/uisupport/flatproxymodel.cpp b/src/uisupport/flatproxymodel.cpp
new file mode 100644 (file)
index 0000000..ef19074
--- /dev/null
@@ -0,0 +1,667 @@
+/***************************************************************************
+ *   Copyright (C) 2005-08 by the Quassel Project                          *
+ *   devel@quassel-irc.org                                                 *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) version 3.                                           *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program; if not, write to the                         *
+ *   Free Software Foundation, Inc.,                                       *
+ *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
+ ***************************************************************************/
+
+#include "flatproxymodel.h"
+
+#include <QItemSelection>
+#include <QDebug>
+
+FlatProxyModel::FlatProxyModel(QObject *parent)
+  : QAbstractProxyModel(parent),
+    _rootSourceItem(0)
+{
+}
+
+QModelIndex FlatProxyModel::mapFromSource(const QModelIndex &sourceIndex) const {
+  if(!sourceIndex.isValid())
+    return QModelIndex();
+
+  SourceItem *sourceItem = sourceToInternal(sourceIndex);
+  Q_ASSERT(sourceItem);
+  return createIndex(sourceItem->pos(), sourceIndex.column(), sourceItem);
+}
+
+QModelIndex FlatProxyModel::mapToSource(const QModelIndex &proxyIndex) const {
+  if(!proxyIndex.isValid())
+    return QModelIndex();
+
+  Q_ASSERT(proxyIndex.model() == this);
+  Q_ASSERT(_rootSourceItem);
+
+  int row = proxyIndex.row();
+  QModelIndex sourceParent;
+  SourceItem *sourceItem = _rootSourceItem->findChild(row);
+  while(sourceItem) {
+    if(sourceItem->pos() == row) {
+      return sourceModel()->index(sourceItem->sourceRow(), proxyIndex.column(), sourceParent);
+    } else {
+      sourceParent = sourceModel()->index(sourceItem->sourceRow(), 0, sourceParent);
+      sourceItem = sourceItem->findChild(row);
+    }
+  }
+
+  qWarning() << "FlatProxyModel::mapToSource(): couldn't find source index for" << proxyIndex;
+  Q_ASSERT(false);
+  return QModelIndex(); // make compilers happy :)
+}
+
+bool FlatProxyModel::_RangeRect::operator<(const FlatProxyModel::_RangeRect &other) const {
+  if(left != other.left)
+    return left < other.left;
+
+  if(right != other.right)
+    return right < other.right;
+
+  if(top != other.top)
+    return top < other.top;
+
+  // top == other.top
+  return bottom < other.bottom;
+}
+
+QItemSelection FlatProxyModel::mapSelectionFromSource(const QItemSelection &sourceSelection) const {
+  QList<_RangeRect> newRanges;
+  QHash<QModelIndex, SourceItem *> itemLookup;
+  // basics steps of the loop:
+  // 1. convert each source ItemSelectionRange to a mapped Range (internal one for easier post processing)
+  // 2. insert it into the list of previously mapped Ranges sorted by top location
+  for(int i = 0; i < sourceSelection.count(); i++) {
+    const QItemSelectionRange &currentRange = sourceSelection[i];
+    QModelIndex currentParent = currentRange.topLeft().parent();
+    Q_ASSERT(currentParent == currentRange.bottomRight().parent());
+
+    SourceItem *parentItem = 0;
+    if(!itemLookup.contains(currentParent)) {
+       parentItem = sourceToInternal(currentParent);
+       itemLookup[currentParent] = parentItem;
+    } else {
+      parentItem = itemLookup[currentParent];
+    }
+
+    _RangeRect newRange = { currentRange.topLeft().column(), currentRange.bottomRight().column(),
+                           currentRange.topLeft().row(), currentRange.bottomRight().row(),
+                           parentItem->child(currentRange.topLeft().row()), parentItem->child(currentRange.bottomRight().row()) };
+
+    if(newRanges.isEmpty()) {
+      newRanges << newRange;
+      continue;
+    }
+
+    _RangeRect &first = newRanges[0];
+    if(newRange < first) {
+      newRanges.prepend(newRange);
+      continue;
+    }
+
+    bool inserted = false;
+    for(int j = 0; j < newRanges.count() - 1; j++) {
+      _RangeRect &a = newRanges[j];
+      _RangeRect &b = newRanges[j + 1];
+
+      if(a < newRange && newRange < b) {
+        newRanges[j + 1] = newRange;
+        inserted = true;
+        break;
+      }
+    }
+    if(inserted)
+      continue;
+
+    _RangeRect &last = newRanges[newRanges.count() - 1];
+    if(last < newRange) {
+      newRanges.append(newRange);
+      continue;
+    }
+    
+    Q_ASSERT(false);
+  }
+
+  // we've got a sorted list of ranges now. so we can easily check if there is a possibility to merge
+  for(int i = newRanges.count() - 1; i > 0; i--) {
+    _RangeRect &a = newRanges[i - 1];
+    _RangeRect &b = newRanges[i];
+    if(a.left != b.left || a.right != b.right)
+      continue;
+
+    if(a.bottom < b.top - 1) {
+      continue;
+    }
+
+    // all merge checks passed!
+    if(b.bottom > a.bottom) {
+      a.bottom = b.bottom;
+      a.bottomItem = b.bottomItem;
+    } // otherwise b is totally enclosed in a -> nothing to do but drop b.
+    newRanges.removeAt(i);
+  }
+
+  QItemSelection proxySelection;
+  for(int i = 0; i < newRanges.count(); i++) {
+    _RangeRect &r = newRanges[i];
+    proxySelection << QItemSelectionRange(createIndex(r.top, r.left, r.topItem), createIndex(r.bottom, r.right, r.bottomItem));
+  }
+  return proxySelection;
+}
+
+QItemSelection FlatProxyModel::mapSelectionToSource(const QItemSelection &proxySelection) const {
+  QItemSelection sourceSelection;
+
+  for(int i = 0; i < proxySelection.count(); i++) {
+    const QItemSelectionRange &range = proxySelection[i];
+
+    SourceItem *topLeftItem = 0;
+    SourceItem *bottomRightItem = 0;
+    SourceItem *currentItem = static_cast<SourceItem *>(range.topLeft().internalPointer());
+    int row = range.topLeft().row();
+    int left = range.topLeft().column();
+    int right = range.bottomRight().column();
+    while(currentItem && row <= range.bottomRight().row()) {
+      Q_ASSERT(currentItem->pos() == row);
+      if(!topLeftItem)
+        topLeftItem = currentItem;
+
+      if(currentItem->parent() == topLeftItem->parent()) {
+        bottomRightItem = currentItem;
+      } else {
+        Q_ASSERT(topLeftItem && bottomRightItem);
+        sourceSelection << QItemSelectionRange(mapToSource(createIndex(topLeftItem->pos(), left, topLeftItem)), mapToSource(createIndex(bottomRightItem->pos(), right, bottomRightItem)));
+        topLeftItem = 0;
+        bottomRightItem = 0;
+      }
+      
+      // update loop vars
+      currentItem = currentItem->next();
+      row++;
+    }
+
+    Q_ASSERT(topLeftItem && bottomRightItem); // there should be one range left.
+    sourceSelection << QItemSelectionRange(mapToSource(createIndex(topLeftItem->pos(), left, topLeftItem)), mapToSource(createIndex(bottomRightItem->pos(), right, bottomRightItem)));
+  }
+
+  return sourceSelection;
+}
+
+void FlatProxyModel::setSourceModel(QAbstractItemModel *sourceModel) {
+  if(QAbstractProxyModel::sourceModel()) {
+    disconnect(QAbstractProxyModel::sourceModel(), 0, this, 0);
+  }
+
+  QAbstractProxyModel::setSourceModel(sourceModel);
+
+  emit layoutAboutToBeChanged();
+  removeSubTree(QModelIndex(), false /* don't emit removeRows() */);
+  insertSubTree(QModelIndex(), false /* don't emit insertRows() */);
+  emit layoutChanged();
+
+  if(sourceModel) {
+    connect(sourceModel, SIGNAL(columnsAboutToBeInserted(const QModelIndex &, int, int)),
+            this, SLOT(on_columnsAboutToBeInserted(const QModelIndex &, int, int)));
+    connect(sourceModel, SIGNAL(columnsAboutToBeRemoved(const QModelIndex &, int, int)),
+            this, SLOT(on_columnsAboutToBeRemoved(const QModelIndex &, int, int)));
+    connect(sourceModel, SIGNAL(columnsInserted(const QModelIndex &, int, int)),
+            this, SLOT(on_columnsInserted(const QModelIndex &, int, int)));
+    connect(sourceModel, SIGNAL(columnsRemoved(const QModelIndex &, int, int)),
+            this, SLOT(on_columnsRemoved(const QModelIndex &, int, int)));
+  
+    connect(sourceModel, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)),
+            this, SLOT(on_dataChanged(const QModelIndex &, const QModelIndex &)));
+    // on_headerDataChanged(Qt::Orientation orientation, int first, int last)
+
+    connect(sourceModel, SIGNAL(layoutAboutToBeChanged()),
+            this, SLOT(on_layoutAboutToBeChanged()));
+    connect(sourceModel, SIGNAL(layoutChanged()),
+            this, SLOT(on_layoutChanged()));
+
+    connect(sourceModel, SIGNAL(modelAboutToBeReset()),
+            this, SLOT(on_modelAboutToBeReset()));
+    // void on_modelReset()
+
+    connect(sourceModel, SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)),
+            this, SLOT(on_rowsAboutToBeInserted(const QModelIndex &, int, int)));
+    connect(sourceModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)),
+            this, SLOT(on_rowsAboutToBeRemoved(const QModelIndex &, int, int)));
+    connect(sourceModel, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
+            this, SLOT(on_rowsInserted(const QModelIndex &, int, int)));
+    connect(sourceModel, SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
+            this, SLOT(on_rowsRemoved(const QModelIndex &, int, int)));
+  }
+  
+}
+
+void FlatProxyModel::insertSubTree(const QModelIndex &source_idx, bool emitInsert) {
+  SourceItem *newSubTree = new SourceItem(source_idx.row(), sourceToInternal(sourceModel()->parent(source_idx)));
+
+  if(newSubTree->parent()) {
+    newSubTree->setPos(newSubTree->parent()->pos() + source_idx.row() + 1);
+  }
+  SourceItem *lastItem = insertSubTreeHelper(newSubTree, newSubTree, source_idx);
+
+  Q_ASSERT(lastItem);
+  Q_ASSERT(lastItem->next() == 0);
+
+  if(emitInsert)
+    beginInsertRows(QModelIndex(), newSubTree->pos(), lastItem->pos());
+
+  if(newSubTree->parent()) {
+    if(newSubTree->parent()->childCount() > source_idx.row()) {
+      SourceItem *next = newSubTree->parent()->child(source_idx.row());
+      lastItem->setNext(next);
+      int nextPos = lastItem->pos() + 1;
+      while(next) {
+        next->setPos(nextPos);
+        next = next->next();
+        nextPos++;
+      }
+    }
+    if(source_idx.row() > 0) {
+      SourceItem *previous = newSubTree->parent()->child(source_idx.row() - 1);
+      while(previous->childCount() > 0) {
+        previous = previous->child(previous->childCount() - 1);
+      }
+      previous->setNext(newSubTree);
+    } else {
+      newSubTree->parent()->setNext(newSubTree);
+    }
+  } else {
+    _rootSourceItem = newSubTree;
+  }
+
+  if(emitInsert)
+    endInsertRows();
+}
+
+FlatProxyModel::SourceItem *FlatProxyModel::insertSubTreeHelper(SourceItem *parentItem, SourceItem *lastItem_, const QModelIndex &source_idx) {
+  SourceItem *lastItem = lastItem_;
+  SourceItem *newItem = 0;
+  for(int row = 0; row < sourceModel()->rowCount(source_idx); row++) {
+    newItem = new SourceItem(row, parentItem);
+    newItem->setPos(lastItem->pos() + 1);
+    lastItem->setNext(newItem);
+    lastItem = insertSubTreeHelper(newItem, newItem, sourceModel()->index(row, 0, source_idx));
+  }
+  return lastItem;
+}
+
+void FlatProxyModel::removeSubTree(const QModelIndex &source_idx, bool emitRemove) {
+  SourceItem *sourceItem = sourceToInternal(source_idx);
+  if(!sourceItem)
+    return;
+
+  SourceItem *prevItem = sourceItem->parent();
+  if(sourceItem->sourceRow() > 0) {
+    prevItem = prevItem->child(sourceItem->sourceRow() - 1);
+    while(prevItem->childCount() > 0) {
+      prevItem = prevItem->child(prevItem->childCount() - 1);
+    }
+  }
+
+  SourceItem *lastItem = sourceItem;
+  while(lastItem->childCount() > 0) {
+    lastItem = lastItem->child(lastItem->childCount() - 1);
+  }
+
+  if(emitRemove)
+    beginRemoveRows(QModelIndex(), sourceItem->pos(), lastItem->pos());
+
+  int nextPos = 0;
+  if(prevItem) {
+    prevItem->setNext(lastItem->next());
+    nextPos = prevItem->pos() + 1;
+  }
+
+  SourceItem *nextItem = lastItem->next();
+  while(nextItem) {
+    nextItem->setPos(nextPos);
+    nextPos++;
+    nextItem = nextItem->next();
+  }
+
+  sourceItem->parent()->removeChild(sourceItem);
+  delete sourceItem;
+  
+  if(emitRemove)
+    endRemoveRows();
+}
+
+QModelIndex FlatProxyModel::index(int row, int column, const QModelIndex &parent) const {
+  if(parent.isValid()) {
+    qWarning() << "FlatProxyModel::index() called with valid parent:" << parent;
+    return QModelIndex();
+  }
+
+  if(!_rootSourceItem) {
+    qWarning() << "FlatProxyModel::index() while model has no root Item";
+    return QModelIndex();
+  }
+
+  SourceItem *item = _rootSourceItem;
+  while(item->pos() != row) {
+    item = item->findChild(row);
+    if(!item) {
+      qWarning() << "FlatProxyModel::index() no such row:" << row;
+      return QModelIndex();
+    }
+  }
+
+  Q_ASSERT(item->pos() == row);
+  return createIndex(row, column, item);
+}
+
+QModelIndex FlatProxyModel::parent(const QModelIndex &index) const {
+  Q_UNUSED(index)
+  // this is a flat model
+  return QModelIndex();
+}
+
+int FlatProxyModel::rowCount(const QModelIndex &index) const {
+  if(!_rootSourceItem)
+    return 0;
+
+  if(index.isValid())
+    return 0;
+
+  SourceItem *item = _rootSourceItem;
+  while(item->childCount() > 0) {
+    item = item->child(item->childCount() - 1);
+  }
+  return item->pos() + 1;
+}
+
+int FlatProxyModel::columnCount(const QModelIndex &index) const {
+  Q_UNUSED(index)
+  if(!sourceModel()) {
+    return 0;
+  } else {
+    return sourceModel()->columnCount(QModelIndex());
+  }
+}
+
+
+FlatProxyModel::SourceItem *FlatProxyModel::sourceToInternal(const QModelIndex &sourceIndex) const {
+  QList<int> childPath;
+  for(QModelIndex idx = sourceIndex; idx.isValid(); idx = sourceModel()->parent(idx)) {
+    childPath.prepend(idx.row());
+  }
+
+  Q_ASSERT(!sourceIndex.isValid() || !childPath.isEmpty());
+
+  SourceItem *item = _rootSourceItem;
+  for(int i = 0; i < childPath.count(); i++) {
+    item = item->child(childPath[i]);
+  }
+  return item;
+}
+
+void FlatProxyModel::on_columnsAboutToBeInserted(const QModelIndex &parent, int start, int end) {
+  Q_UNUSED(parent);
+  beginInsertColumns(QModelIndex(), start, end);
+}
+
+void FlatProxyModel::on_columnsAboutToBeRemoved(const QModelIndex &parent, int start, int end) {
+  Q_UNUSED(parent);
+  beginRemoveColumns(QModelIndex(), start, end);
+}
+
+void FlatProxyModel::on_columnsInserted(const QModelIndex &parent, int start, int end) {
+  Q_UNUSED(parent);
+  Q_UNUSED(start);
+  Q_UNUSED(end);
+  endInsertColumns();
+}
+
+
+void FlatProxyModel::on_columnsRemoved(const QModelIndex &parent, int start, int end) {
+  Q_UNUSED(parent);
+  Q_UNUSED(start);
+  Q_UNUSED(end);
+  endRemoveRows();
+}
+
+void FlatProxyModel::on_dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) {
+  Q_ASSERT(sourceModel());
+  Q_ASSERT(sourceModel()->parent(topLeft) == sourceModel()->parent(bottomRight));
+
+  SourceItem *topLeftItem = sourceToInternal(topLeft);
+  Q_ASSERT(topLeftItem);
+  Q_ASSERT(topLeftItem->parent()->childCount() > bottomRight.row());
+
+  QModelIndex proxyTopLeft = createIndex(topLeftItem->pos(), topLeft.column(), topLeftItem);
+  QModelIndex proxyBottomRight = createIndex(topLeftItem->pos() + bottomRight.row() - topLeft.row(), bottomRight.column(), topLeftItem->parent()->child(bottomRight.row()));
+  emit dataChanged(proxyTopLeft, proxyBottomRight);
+}
+
+void FlatProxyModel::on_layoutAboutToBeChanged() {
+  emit layoutAboutToBeChanged();
+  removeSubTree(QModelIndex(), false /* don't emit removeRows() */);
+}
+
+void FlatProxyModel::on_layoutChanged() {
+  insertSubTree(QModelIndex(), false /* don't emit insertRows() */);
+  emit layoutChanged();
+}
+
+void FlatProxyModel::on_rowsAboutToBeInserted(const QModelIndex &parent, int start, int end) {
+  Q_ASSERT(sourceModel());
+  Q_ASSERT(_rootSourceItem);
+
+  SourceItem *sourceItem = sourceToInternal(parent);
+  Q_ASSERT(sourceItem);
+
+  beginInsertRows(QModelIndex(), sourceItem->pos() + start + 1, sourceItem->pos() + end + 1);
+
+  SourceItem *prevItem = sourceItem;
+  if(start > 0) {
+    prevItem = sourceItem->child(start - 1);
+    while(prevItem->childCount() > 0) {
+      prevItem = prevItem->child(prevItem->childCount() - 1);
+    }
+  }
+  Q_ASSERT(prevItem);
+
+  SourceItem *nextItem = prevItem->next();
+
+  SourceItem *newItem = 0;
+  int newPos = prevItem->pos() + 1;
+  for(int row = start; row <= end; row++) {
+    newItem = new SourceItem(row, sourceItem);
+    newItem->setPos(newPos);
+    newPos++;
+    prevItem->setNext(newItem);
+    prevItem = newItem;
+  }
+  prevItem->setNext(nextItem);
+
+  while(nextItem) {
+    nextItem->setPos(newPos);
+    newPos++;
+    nextItem = nextItem->next();
+  }
+
+}
+
+void FlatProxyModel::on_rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) {
+  Q_ASSERT(sourceModel());
+  Q_ASSERT(_rootSourceItem);
+
+  SourceItem *sourceItem = sourceToInternal(parent);
+  beginRemoveRows(QModelIndex(), sourceItem->pos() + start + 1, sourceItem->pos() + end + 1);
+
+  // sanity check - if that check fails our indexes would be messed up
+  for(int row = start; row <= end; row++) {
+    if(sourceItem->child(row)->childCount() > 0) {
+      qWarning() << "on_rowsAboutToBeRemoved(): sourceModel() removed rows which have childs on their own!" << sourceModel()->index(row, 0, parent);
+      Q_ASSERT(false);
+    }
+  }
+}
+
+void FlatProxyModel::on_rowsInserted(const QModelIndex &parent, int start, int end) {
+  Q_ASSERT(sourceModel());
+  Q_ASSERT(_rootSourceItem);
+
+  SourceItem *sourceItem = sourceToInternal(parent);
+  Q_ASSERT(sourceItem);
+
+  // sanity check - if that check fails our indexes would be messed up
+  for(int row = start; row <= end; row++) {
+    QModelIndex child = sourceModel()->index(row, 0, parent);
+    if(sourceModel()->rowCount(child) > 0) {
+      qWarning() << "on_rowsInserted(): sourceModel() inserted rows which already have childs on their own!" << child;
+      Q_ASSERT(false);
+    }
+  }
+
+  endInsertRows();
+}
+
+void FlatProxyModel::on_rowsRemoved(const QModelIndex &parent, int start, int end) {
+  Q_ASSERT(sourceModel());
+  Q_ASSERT(_rootSourceItem);
+
+  SourceItem *sourceItem = sourceToInternal(parent);
+  Q_ASSERT(sourceItem);
+
+  SourceItem *prevItem = sourceItem;
+  if(start > 0) {
+    prevItem = sourceItem->child(start - 1);
+    while(prevItem->childCount() > 0) {
+      prevItem = prevItem->child(prevItem->childCount() - 1);
+    }
+  }
+  Q_ASSERT(prevItem);
+
+  SourceItem *nextItem = sourceItem->child(end)->next();
+
+  int newPos = prevItem->pos() + 1;
+  prevItem->setNext(nextItem);
+
+  while(nextItem) {
+    nextItem->setPos(newPos);
+    newPos++;
+    nextItem = nextItem->next();
+  }
+
+  SourceItem *childItem;
+  for(int row = start; row <= end; row++) {
+    childItem = sourceItem->_childs.takeAt(start);
+    delete childItem;
+  }
+
+  endRemoveRows();
+}
+
+// integrity Tets
+void FlatProxyModel::linkTest() const {
+  qDebug() << "Checking FlatProxyModel for linklist integrity";
+  if(!_rootSourceItem)
+    return;
+
+  int pos = -1;
+  SourceItem *item = _rootSourceItem;
+  while(true) {
+    qDebug() << item << ":" << item->pos() << "==" << pos;
+    Q_ASSERT(item->pos() == pos);
+    pos++;
+    if(!item->next())
+      break;
+    item = item->next();
+  }
+  qDebug() << "Last item in linklist:" << item << item->pos();
+
+  int lastPos = item->pos();
+  item = _rootSourceItem;
+  while(item->childCount() > 0) {
+    item = item->child(item->childCount() - 1);
+  }
+  qDebug() << "Last item in tree:" << item << item->pos();
+  Q_ASSERT(lastPos == item->pos());
+
+  qDebug() << "success!";
+}
+
+void FlatProxyModel::completenessTest() const {
+  qDebug() << "Checking FlatProxyModel for Completeness:";
+  int pos = -1;
+  checkChildCount(QModelIndex(), _rootSourceItem, pos);
+  qDebug() << "success!";
+}
+
+void FlatProxyModel::checkChildCount(const QModelIndex &index, const SourceItem *item, int &pos) const {
+  if(!sourceModel())
+    return;
+
+  qDebug() << index  << "(Item:" << item << "):" << sourceModel()->rowCount(index) << "==" << item->childCount();
+  qDebug() << "ProxyPos:" << item->pos() << "==" << pos;
+  Q_ASSERT(sourceModel()->rowCount(index) == item->childCount());
+
+  for(int row = 0; row < sourceModel()->rowCount(index); row++) {
+    pos++;
+    checkChildCount(sourceModel()->index(row, 0, index), item->child(row), pos);
+  }
+}
+
+// ========================================
+//  SourceItem
+// ========================================
+FlatProxyModel::SourceItem::SourceItem(int row, SourceItem *parent)
+  : _parent(parent),
+    _pos(-1),
+    _next(0)
+{
+  if(parent) {
+    parent->_childs.insert(row, this);
+  }
+}
+
+FlatProxyModel::SourceItem::~SourceItem() {
+  for(int i = 0; i < childCount(); i++) {
+    delete child(i);
+  }
+  _childs.clear();
+}
+
+int FlatProxyModel::SourceItem::sourceRow() const {
+  if(!parent())
+    return -1;
+  else
+    return parent()->_childs.indexOf(const_cast<FlatProxyModel::SourceItem *>(this));
+}
+
+FlatProxyModel::SourceItem *FlatProxyModel::SourceItem::findChild(int proxyPos) const {
+  Q_ASSERT(proxyPos > pos());
+  Q_ASSERT(_childs.count() > 0);
+  Q_ASSERT(proxyPos >= _childs[0]->pos());
+
+  int start = 0;
+  int end = _childs.count() - 1;
+  int pivot;
+  while(end - start != 1) {
+    pivot = (end + start) / 2;
+    if(_childs[pivot]->pos() > proxyPos)
+      end = pivot;
+    else
+      start = pivot;
+  }
+
+  if(_childs[end]->pos() <= proxyPos)
+    return _childs[end];
+  else
+    return _childs[start];
+}
diff --git a/src/uisupport/flatproxymodel.h b/src/uisupport/flatproxymodel.h
new file mode 100644 (file)
index 0000000..e006760
--- /dev/null
@@ -0,0 +1,123 @@
+/***************************************************************************
+ *   Copyright (C) 2005-08 by the Quassel Project                          *
+ *   devel@quassel-irc.org                                                 *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) version 3.                                           *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program; if not, write to the                         *
+ *   Free Software Foundation, Inc.,                                       *
+ *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
+ ***************************************************************************/
+
+#ifndef FLATPROXYMODEL_H
+#define FLATPROXYMODEL_H
+
+#include <QAbstractProxyModel>
+
+class FlatProxyModel : public QAbstractProxyModel {
+  Q_OBJECT
+
+public:
+  FlatProxyModel(QObject *parent = 0);
+
+  virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const;
+  virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const;
+
+  virtual QItemSelection mapSelectionFromSource(const QItemSelection &sourceSelection) const;
+  virtual QItemSelection mapSelectionToSource(const QItemSelection &proxySelection) const;
+
+  virtual void setSourceModel(QAbstractItemModel *sourceModel);
+
+  virtual QModelIndex index(int row, int column, const QModelIndex &parent) const;
+  virtual QModelIndex parent(const QModelIndex &index) const;
+
+  virtual int rowCount(const QModelIndex &index) const;
+  virtual int columnCount(const QModelIndex &index) const;
+
+public slots:
+  void linkTest() const;
+  void completenessTest() const;
+
+private slots:
+  void on_columnsAboutToBeInserted(const QModelIndex &parent, int start, int end);
+  void on_columnsAboutToBeRemoved(const QModelIndex &parent, int start, int end);
+  void on_columnsInserted(const QModelIndex &parent, int start, int end);
+  void on_columnsRemoved(const QModelIndex &parent, int start, int end);
+  
+  void on_dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
+//   void on_headerDataChanged(Qt::Orientation orientation, int first, int last);
+
+  void on_layoutAboutToBeChanged();
+  void on_layoutChanged();
+
+  inline void on_modelAboutToBeReset() { reset(); }
+  // void on_modelReset();
+
+  void on_rowsAboutToBeInserted(const QModelIndex &parent, int start, int end);
+  void on_rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end);
+  void on_rowsInserted(const QModelIndex &parent, int start, int end);
+  void on_rowsRemoved(const QModelIndex &parent, int start, int end);
+
+private:
+  QList<int> _childCount;
+
+  class SourceItem;
+  SourceItem *_rootSourceItem;
+
+  void insertSubTree(const QModelIndex &source_idx, bool emitInsert = true);
+  SourceItem *insertSubTreeHelper(SourceItem *parentItem, SourceItem *lastItem_, const QModelIndex &source_idx);
+
+  void removeSubTree(const QModelIndex &source_idx, bool emitRemove = true);
+
+  SourceItem *sourceToInternal(const QModelIndex &sourceIndex) const;
+
+  void checkChildCount(const QModelIndex &index, const SourceItem *item, int &pos) const;
+
+  class _RangeRect {
+  public:
+    int left, right, top, bottom;
+    SourceItem *topItem, *bottomItem;
+    bool operator<(const _RangeRect &other) const;
+  };
+};
+
+
+class FlatProxyModel::SourceItem {
+public:
+  SourceItem(int row = 0, SourceItem *parent = 0);
+  ~SourceItem();
+
+  inline SourceItem *parent() const { return _parent; }
+  inline SourceItem *child(int i) const { return _childs[i]; }
+  inline int childCount() const { return _childs.count(); }
+
+  inline int pos() const { return _pos; }
+  inline SourceItem *next() const { return _next; }
+
+  int sourceRow() const;
+  SourceItem *findChild(int proxyPos) const;
+
+private:
+  inline void removeChild(SourceItem *item) { _childs.removeAt(_childs.indexOf(item)); }
+  inline void setPos(int i) { _pos = i; }
+  inline void setNext(SourceItem *next) { _next = next; }
+
+  SourceItem *_parent;
+  QList<SourceItem *> _childs;
+  int _pos;
+  SourceItem *_next;
+
+  friend class FlatProxyModel;
+};
+
+
+#endif //FLATPROXYMODEL_H