Selaa lähdekoodia

Add ctkFlatProxyModel to squash tree levels

Julien Finet 13 vuotta sitten
vanhempi
commit
3bb624cb89

+ 6 - 1
Libs/Core/ctkModelTester.cpp

@@ -293,7 +293,10 @@ void ctkModelTester::testParent(const QModelIndex& vparent)const
       {
       this->test(d->Model->hasIndex(i, j, vparent), "hasIndex should return true for int range {0->rowCount(), 0->columnCount()}");
       QModelIndex child = vparent.child(i, j);
-      this->test(child.parent() == vparent, "A child's parent can't be different from its parent");
+      this->test(child.row() == i, "A child's row must be the same as given");
+      this->test(child.column() == j, "A child's column must be the same as given");
+      QModelIndex childParent = child.parent();
+      this->test(childParent == vparent, "A child's parent can't be different from its parent");
       this->testModelIndex(child);
       }
     }
@@ -325,6 +328,8 @@ void ctkModelTester::testModel()const
       {
       this->test(d->Model->hasIndex(i, j), "hasIndex should return true for int range {0->rowCount(), 0->columnCount()}");
       QModelIndex child = d->Model->index(i, j);
+      this->test(child.row() == i, "Row should be consistent");
+      this->test(child.column() == j, "Column should be consistent");
       this->test(!child.parent().isValid(), "A child's parent can't be different from its parent");
       this->testModelIndex(child);
       }

+ 3 - 0
Libs/Widgets/CMakeLists.txt

@@ -71,6 +71,8 @@ set(KIT_SRCS
   ctkFileDialog.h
   ctkFittedTextBrowser.cpp
   ctkFittedTextBrowser.h
+  ctkFlatProxyModel.cpp
+  ctkFlatProxyModel.h
   ctkFlowLayout.cpp
   ctkFlowLayout.h
   ctkFontButton.cpp
@@ -198,6 +200,7 @@ set(KIT_MOC_SRCS
   ctkExpandButton.h
   ctkFileDialog.h
   ctkFittedTextBrowser.h
+  ctkFlatProxyModel.h
   ctkFlowLayout.h
   ctkFontButton.h
   ctkIconEnginePlugin.h

+ 6 - 1
Libs/Widgets/Testing/Cpp/CMakeLists.txt

@@ -34,6 +34,7 @@ set(TEST_SOURCES
   ctkErrorLogWidgetTest1.cpp
   ctkExpandButtonTest1.cpp
   ctkFileDialogTest1.cpp
+  ctkFlatProxyModelTest.cpp
   ctkFittedTextBrowserTest1.cpp
   ctkFlowLayoutTest1.cpp
   ctkFontButtonTest1.cpp
@@ -116,7 +117,10 @@ set(Tests_MOC_SRCS
 
 set(Tests_MOC_CPP)
 QT4_WRAP_CPP(Tests_MOC_CPP ${Tests_MOC_SRCS})
-QT4_GENERATE_MOCS(ctkRangeSliderTest.cpp)
+QT4_GENERATE_MOCS(
+  ctkRangeSliderTest.cpp
+  ctkFlatProxyModelTest.cpp
+  )
 
 add_executable(${KIT}CppTests ${Tests} ${Tests_SRCS} ${Tests_MOC_CPP})
 target_link_libraries(${KIT}CppTests ${LIBRARY_NAME})
@@ -160,6 +164,7 @@ SIMPLE_TEST( ctkErrorLogWidgetTest1 )
 SIMPLE_TEST( ctkExampleUseOfWorkflowWidgetUsingDerivedSteps )
 SIMPLE_TEST( ctkExampleUseOfWorkflowWidgetUsingSignalsAndSlots )
 SIMPLE_TEST( ctkExpandButtonTest1 )
+SIMPLE_TEST( ctkFlatProxyModelTest )
 SIMPLE_TEST( ctkFileDialogTest1 )
 SIMPLE_TEST( ctkFittedTextBrowserTest1 )
 SIMPLE_TEST( ctkFlowLayoutTest1 )

+ 271 - 0
Libs/Widgets/Testing/Cpp/ctkFlatProxyModelTest.cpp

@@ -0,0 +1,271 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) Kitware Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=========================================================================*/
+
+// Qt includes
+#include <QDebug>
+#include <QApplication>
+#include <QEventLoop>
+#include <QTreeView>
+#include <QStandardItem>
+#include <QStandardItemModel>
+#include <QTimer>
+
+// CTK includes
+#include "ctkFlatProxyModel.h"
+#include "ctkModelTester.h"
+#include "ctkTest.h"
+
+// ----------------------------------------------------------------------------
+class ctkFlatProxyModelTester: public QObject
+{
+  Q_OBJECT
+private slots:
+  void testModel();
+  void testModel_data();
+private:
+  QStandardItem* createItem(const QString& name, QVariant& children)const;
+};
+
+// ----------------------------------------------------------------------------
+void ctkFlatProxyModelTester::testModel_data()
+{
+  QTest::addColumn<QVariant >("hashModel");
+  QTest::addColumn<int>("startHideLevel");
+  QTest::addColumn<int>("endFlattenLevel");
+  QTest::addColumn<int>("level0ExpectedRowCount");
+  QTest::addColumn<int>("level1ExpectedRowCount");
+
+  // -\ top
+  //  +--\ item1
+  //  |  +--\ subItem1
+  //  |  |  +--\ subSubItem1
+  //  |  |  |  +-- leaf1
+  //  |  |  +--\ subSubItem2
+  //  |  |  |  +-- leaf2
+  //  |  |  +--\ subSubItem3
+  //  |  |     +-- leaf3
+  //  |  +--\ subItem2
+  //  |  |  +--\ subSubItem1
+  //  |  |  |  +-- leaf1
+  //  |  |  +--\ subSubItem2
+  //  |  |  |  +-- leaf2
+  //  |  |  +--\ subSubItem3
+  //  |  |     +-- leaf3
+  //  |  +--\ subItem3
+  //  |     +--\ subSubItem1
+  //  |     |  +-- leaf1
+  //  |     +--\ subSubItem2
+  //  |     |  +-- leaf2
+  //  |     +--\ subSubItem3
+  //  |        +-- leaf3
+  //  +--\ item2
+  //  |  +--\ subItem1
+  //  |  |  +--\ subSubItem1
+  //  |  |  |  +-- leaf1
+  //  |  |  +--\ subSubItem2
+  //  |  |  |  +-- leaf2
+  //  |  |  +--\ subSubItem3
+  //  |  |     +-- leaf3
+  //  |  +--\ subItem2
+  //  |  |  +--\ subSubItem1
+  //  |  |  |  +-- leaf1
+  //  |  |  +--\ subSubItem2
+  //  |  |  |  +-- leaf2
+  //  |  |  +--\ subSubItem3
+  //  |  |     +-- leaf3
+  //  |  +--\ subItem3
+  //  |     +--\ subSubItem1
+  //  |     |  +-- leaf1
+  //  |     +--\ subSubItem2
+  //  |     |  +-- leaf2
+  //  |     +--\ subSubItem3
+  //  |        +-- leaf3
+  //  +--\ item3
+  //     +--\ subItem1
+  //     |  +--\ subSubItem1
+  //     |  |  +-- leaf1
+  //     |  +--\ subSubItem2
+  //     |  |  +-- leaf2
+  //     |  +--\ subSubItem3
+  //     |     +-- leaf3
+  //     +--\ subItem2
+  //     |  +--\ subSubItem1
+  //     |  |  +-- leaf1
+  //     |  +--\ subSubItem2
+  //     |  |  +-- leaf2
+  //     |  +--\ subSubItem3
+  //     |     +-- leaf3
+  //     +--\ subItem3
+  //        +--\ subSubItem1
+  //        |  +-- leaf1
+  //        +--\ subSubItem2
+  //        |  +-- leaf2
+  //        +--\ subSubItem3
+  //           +-- leaf3
+  QMap<QString, QVariant> subSubModel;
+  subSubModel["subSubItem1"] = QString("leaf1");
+  subSubModel["subSubItem2"] = QString("leaf2");
+  QMap<QString, QVariant> subModel;
+  subModel["subItem1"] = subSubModel;
+  subModel["subItem2"] = subSubModel;
+  subModel["subItem3"] = subSubModel;
+  QMap<QString, QVariant> model;
+  model["item1"] = subModel;
+  model["item2"] = subModel;
+  model["item3"] = subModel;
+  model["item4"] = subModel;
+
+  // -\ top
+  //  +--\ subItem1 (item1)
+  //  |  +--\ subSubItem1
+  //  |  |  +-- leaf1
+  //  |  +--\ subSubItem2
+  //  |  |  +-- leaf2
+  //  |  +--\ subSubItem3
+  //  |     +-- leaf3
+  //  +--\ subItem2 (item1)
+  //  |  +--\ subSubItem1
+  //  |  |  +-- leaf1
+  //  |  +--\ subSubItem2
+  //  |  |  +-- leaf2
+  //  |  +--\ subSubItem3
+  //  |     +-- leaf3
+  //  +--\ subItem3 (item1)
+  //  |  +--\ subSubItem1
+  //  |  |  +-- leaf1
+  //  |  +--\ subSubItem2
+  //  |  |  +-- leaf2
+  //  |  +--\ subSubItem3
+  //  |     +-- leaf3
+  //  +--\ subItem1 (item2)
+  //  |  +--\ subSubItem1
+  //  |  |  +-- leaf1
+  //  |  +--\ subSubItem2
+  //  |  |  +-- leaf2
+  //  |  +--\ subSubItem3
+  //  |     +-- leaf3
+  //  +--\ subItem2 (item2)
+  //  |  +--\ subSubItem1
+  //  |  |  +-- leaf1
+  //  |  +--\ subSubItem2
+  //  |  |  +-- leaf2
+  //  |  +--\ subSubItem3
+  //  |     +-- leaf3
+  //  +--\ subItem3 (item2)
+  //  |  +--\ subSubItem1
+  //  |  |  +-- leaf1
+  //  |  +--\ subSubItem2
+  //  |  |  +-- leaf2
+  //  |  +--\ subSubItem3
+  //  |     +-- leaf3
+  //  +--\ subItem1 (item3)
+  //  |  +--\ subSubItem1
+  //  |  |  +-- leaf1
+  //  |  +--\ subSubItem2
+  //  |  |  +-- leaf2
+  //  |  +--\ subSubItem3
+  //  |     +-- leaf3
+  //  +--\ subItem2 (item3)
+  //  |  +--\ subSubItem1
+  //  |  |  +-- leaf1
+  //  |  +--\ subSubItem2
+  //  |  |  +-- leaf2
+  //  |  +--\ subSubItem3
+  //  |     +-- leaf3
+  //  +--\ subItem3 (item3)
+  //     +--\ subSubItem1
+  //     |  +-- leaf1
+  //     +--\ subSubItem2
+  //     |  +-- leaf2
+  //     +--\ subSubItem3
+  //        +-- leaf3
+  QTest::newRow("flatten level 0") << QVariant(model) << -1 << 0 << 12 << 2;
+  // Don't work yet.
+  //QTest::newRow("flatten level 1") << QVariant(model) << -1<< 1 <<  24 << 1;
+  //QTest::newRow("flatten level 0, hide 2") << QVariant(model) << 0 << 0 << 12 << 2;
+  //QTest::newRow("flatten level 1, hide 2") << QVariant(model) << 0 << 1 << 12 << 2;
+}
+
+// ----------------------------------------------------------------------------
+QStandardItem* ctkFlatProxyModelTester
+::createItem(const QString& name, QVariant& children)const
+{
+  QStandardItem* item = new QStandardItem(name);
+  if (children.canConvert<QString>())
+    {
+    QStandardItem* leaf = new QStandardItem(children.toString());
+    item->appendRow(leaf);
+    return item;
+    }
+  QMap<QString, QVariant> hash = children.toMap();
+  QMap<QString, QVariant>::iterator i = hash.begin();
+  for ( ; i != hash.end(); ++i)
+    {
+    QStandardItem* childItem = this->createItem(i.key(), i.value());
+    item->appendRow(childItem);
+    }
+  return item;
+}
+
+// ----------------------------------------------------------------------------
+void ctkFlatProxyModelTester::testModel()
+{
+  QStandardItemModel model;
+
+  QFETCH(QVariant, hashModel);
+  QFETCH(int, endFlattenLevel);
+  QFETCH(int, startHideLevel);
+  QFETCH(int, level0ExpectedRowCount);
+  QFETCH(int, level1ExpectedRowCount);
+
+  QMap<QString, QVariant> hash = hashModel.toMap();
+  QMap<QString, QVariant>::iterator i = hash.begin();
+  for ( ; i != hash.end(); ++i)
+    {
+    QStandardItem* childItem = this->createItem(i.key(), i.value());
+    model.appendRow(childItem);
+    }
+
+  ctkFlatProxyModel flattenModel;
+  flattenModel.setEndFlattenLevel(endFlattenLevel);
+  flattenModel.setHideLevel(startHideLevel);
+  flattenModel.setSourceModel(&model);
+
+  QCOMPARE( flattenModel.rowCount(QModelIndex()), level0ExpectedRowCount);
+  QModelIndex subIndex1 = flattenModel.index(0,0, QModelIndex());
+  QCOMPARE( flattenModel.rowCount(subIndex1), level1ExpectedRowCount);
+
+  ctkModelTester tester;
+  tester.setTestDataEnabled(false);
+  tester.setModel(&flattenModel);
+
+  /*
+  QTreeView view(0);
+  view.setModel(&flattenModel);
+  view.show();
+
+  QEventLoop eventLoop;
+  eventLoop.exec();
+  */
+}
+
+// ----------------------------------------------------------------------------
+CTK_TEST_MAIN(ctkFlatProxyModelTest)
+#include "moc_ctkFlatProxyModelTest.cpp"

+ 370 - 0
Libs/Widgets/ctkFlatProxyModel.cpp

@@ -0,0 +1,370 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) Kitware Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=========================================================================*/
+// QT includes
+#include <QDebug>
+
+// CTK includes
+#include "ctkFlatProxyModel.h"
+
+// ----------------------------------------------------------------------------
+class ctkFlatProxyModelPrivate
+{
+  Q_DECLARE_PUBLIC(ctkFlatProxyModel);
+protected:
+  ctkFlatProxyModel* const q_ptr;
+public:
+  ctkFlatProxyModelPrivate(ctkFlatProxyModel& object);
+  void init();
+  int indexLevel(const QModelIndex& index)const;
+  int levelRowCount(const QModelIndex& index)const;
+  int nextLevelRowCount(const QModelIndex& index)const;
+  int rowCount(const QModelIndex& sourceIndex, int depth = 0)const;
+  QModelIndex sourceParent(const QModelIndex& index)const;
+  QModelIndex grandChild(const QModelIndex& parent, int& row, int depth)const;
+
+  int StartFlattenLevel;
+  int EndFlattenLevel;
+  int HideLevel;
+};
+
+// ----------------------------------------------------------------------------
+//  Methods ctkFlatProxyModelPrivate
+
+// ----------------------------------------------------------------------------
+ctkFlatProxyModelPrivate::ctkFlatProxyModelPrivate(ctkFlatProxyModel &object)
+  : q_ptr(&object)
+{
+  this->StartFlattenLevel = -1;
+  this->EndFlattenLevel = -1;
+  this->HideLevel = -1;
+}
+
+// ----------------------------------------------------------------------------
+void ctkFlatProxyModelPrivate::init()
+{
+  Q_Q(ctkFlatProxyModel);
+}
+
+// ----------------------------------------------------------------------------
+int ctkFlatProxyModelPrivate::indexLevel(const QModelIndex& index)const
+{
+  int level = -1;
+  QModelIndex parent = index;
+  while (parent.isValid())
+    {
+    parent = parent.parent();
+    ++level;
+    }
+  return level;
+}
+
+// ----------------------------------------------------------------------------
+int ctkFlatProxyModelPrivate::levelRowCount(const QModelIndex& sourceIndex)const
+{
+  Q_Q(const ctkFlatProxyModel);
+  if (!sourceIndex.isValid()
+      || this->StartFlattenLevel > this->indexLevel(sourceIndex)
+      || this->EndFlattenLevel < this->indexLevel(sourceIndex))
+    {
+    return 0;
+    }
+  int previousRows = 0;
+  for (int row = 0; row != sourceIndex.row() ; ++row)
+    {
+    previousRows += q->sourceModel()->rowCount(sourceIndex.sibling(row, sourceIndex.column()));
+    }
+  return previousRows + this->levelRowCount(sourceIndex.parent());
+}
+
+// ----------------------------------------------------------------------------
+int ctkFlatProxyModelPrivate::nextLevelRowCount(const QModelIndex& sourceIndex)const
+{
+  Q_Q(const ctkFlatProxyModel);
+  if (!sourceIndex.isValid()
+      || this->StartFlattenLevel > this->indexLevel(sourceIndex)
+      || this->EndFlattenLevel < this->indexLevel(sourceIndex))
+    {
+    return q->sourceModel()->rowCount(sourceIndex);
+    }
+  int rowCount = 0;
+  QModelIndex sibling = sourceIndex.sibling(0, sourceIndex.column());
+  for (int row = 0; sibling.isValid() ; ++row)
+    {
+    sibling = sourceIndex.sibling(row, sourceIndex.column());
+    rowCount += q->sourceModel()->rowCount(sibling);
+    }
+  return rowCount;
+}
+
+// ----------------------------------------------------------------------------
+int ctkFlatProxyModelPrivate::rowCount(const QModelIndex& sourceIndex, int depth)const
+{
+  Q_Q(const ctkFlatProxyModel);
+  int rows = 0;
+  if (depth < 0)
+    {
+    rows = 1;
+    }
+  else
+    {
+    --depth;
+    for (int row = 0; row < q->sourceModel()->rowCount(sourceIndex); ++row)
+      {
+      QModelIndex child = q->sourceModel()->index(row, 0, sourceIndex);
+      rows += this->rowCount(child, depth);
+      }
+    }
+  return rows;
+}
+
+// ----------------------------------------------------------------------------
+QModelIndex ctkFlatProxyModelPrivate
+::sourceParent(const QModelIndex& index)const
+{
+  Q_Q(const ctkFlatProxyModel);
+  QModelIndexList sourceIndexes;
+  sourceIndexes << QModelIndex();
+  QMap<int, int> rowCountsPerLevel;
+  while (!sourceIndexes.isEmpty())
+    {
+    QModelIndex sourceIndex = sourceIndexes.takeFirst();
+    const int rowCount = q->sourceModel()->rowCount(sourceIndex);
+    for (int row = 0; row < rowCount; ++row)
+      {
+      QModelIndex child = q->sourceModel()->index(row, 0, sourceIndex);
+      if (child.internalPointer() == index.internalPointer())
+        {
+        return sourceIndex;
+        }
+      else
+        {
+        sourceIndexes << child;
+        }
+      }
+    }
+  Q_ASSERT(false);
+  return QModelIndex();
+}
+
+// ----------------------------------------------------------------------------
+QModelIndex ctkFlatProxyModelPrivate
+::grandChild(const QModelIndex& parent, int& row, int depth)const
+{
+  Q_Q(const ctkFlatProxyModel);
+  const int rowCount = q->sourceModel()->rowCount(parent);
+
+  if (depth > 0)
+    {
+    for (int i = 0; i < rowCount; ++i)
+      {
+      QModelIndex child = q->sourceModel()->index(i, 0, parent);
+      QModelIndex found = this->grandChild(child, row, depth - 1);
+      if (found.isValid())
+        {
+        return found;
+        }
+      }
+    }
+  else
+    {
+    if (row < rowCount)
+      {
+      QModelIndex sourceIndex = q->sourceModel()->index(
+        row, 0, parent);
+      return sourceIndex;
+      }
+    else
+      {
+      row -= rowCount;
+      }
+    }
+  return QModelIndex();
+}
+
+// ----------------------------------------------------------------------------
+//  Methods ctkFlatProxyModel
+
+// ----------------------------------------------------------------------------
+ctkFlatProxyModel::ctkFlatProxyModel(QObject *parentObject)
+  : Superclass(parentObject)
+  , d_ptr(new ctkFlatProxyModelPrivate(*this))
+{
+  Q_D(ctkFlatProxyModel);
+  d->init();
+}
+
+// ----------------------------------------------------------------------------
+ctkFlatProxyModel::~ctkFlatProxyModel()
+{
+}
+
+// ----------------------------------------------------------------------------
+int ctkFlatProxyModel::startFlattenLevel() const
+{
+  Q_D(const ctkFlatProxyModel);
+  return d->StartFlattenLevel;
+}
+
+// ----------------------------------------------------------------------------
+void ctkFlatProxyModel::setStartFlattenLevel(int level)
+{
+  Q_D(ctkFlatProxyModel);
+  d->StartFlattenLevel = level;
+  Q_ASSERT( d->StartFlattenLevel <= d->EndFlattenLevel);
+}
+
+// ----------------------------------------------------------------------------
+int ctkFlatProxyModel::endFlattenLevel() const
+{
+  Q_D(const ctkFlatProxyModel);
+  return d->EndFlattenLevel;
+}
+
+// ----------------------------------------------------------------------------
+void ctkFlatProxyModel::setEndFlattenLevel(int level)
+{
+  Q_D(ctkFlatProxyModel);
+  d->EndFlattenLevel = level;
+  Q_ASSERT( d->EndFlattenLevel >= d->EndFlattenLevel);
+}
+
+// ----------------------------------------------------------------------------
+int ctkFlatProxyModel::hideLevel()const
+{
+  Q_D(const ctkFlatProxyModel);
+  return d->HideLevel;
+}
+
+// ----------------------------------------------------------------------------
+void ctkFlatProxyModel::setHideLevel(int level)
+{
+  Q_D(ctkFlatProxyModel);
+  d->HideLevel = level;
+}
+
+// ----------------------------------------------------------------------------
+QModelIndex ctkFlatProxyModel::mapFromSource( const QModelIndex& sourceIndex ) const
+{
+  Q_D(const ctkFlatProxyModel);
+  if (!sourceIndex.isValid())
+    {
+    return QModelIndex();
+    }
+  int level = d->indexLevel(sourceIndex);
+  if (d->HideLevel != -1
+      && level >= d->HideLevel)
+    {
+    return QModelIndex();
+    }
+  if (d->EndFlattenLevel != -1
+      && level <= d->EndFlattenLevel)
+    {
+    return QModelIndex();
+    }
+  int row = sourceIndex.row();
+  if (d->EndFlattenLevel != -1)
+    {
+    row += d->levelRowCount(sourceIndex.parent());
+    }
+  return this->createIndex(row, sourceIndex.column(),
+                           sourceIndex.internalPointer());
+}
+
+// ----------------------------------------------------------------------------
+QModelIndex ctkFlatProxyModel::mapToSource( const QModelIndex& proxyIndex ) const
+{
+  Q_D(const ctkFlatProxyModel);
+  if (!proxyIndex.isValid())
+    {
+    return QModelIndex();
+    }
+  QModelIndex sourceParent = d->sourceParent(proxyIndex);
+  int level = d->indexLevel(sourceParent);
+  int levelRowCount = 0;
+  if ((d->StartFlattenLevel != -1 || d->EndFlattenLevel != -1) &&
+      (d->StartFlattenLevel != -1 || level >= d->StartFlattenLevel) &&
+      (d->EndFlattenLevel != -1 || level <= d->EndFlattenLevel))
+    {
+    levelRowCount = d->levelRowCount(sourceParent);
+    }
+  QModelIndex sourceIndex = this->sourceModel()->index(
+    proxyIndex.row() - levelRowCount, proxyIndex.column(), sourceParent);
+  Q_ASSERT(sourceIndex.isValid());
+  return sourceIndex;
+}
+
+// ----------------------------------------------------------------------------
+QModelIndex ctkFlatProxyModel::index(int row, int column, const QModelIndex &parent) const
+{
+  Q_D(const ctkFlatProxyModel);
+  if (row < 0 || column < 0)
+    {
+    return QModelIndex();
+    }
+
+  QModelIndex sourceParent = this->mapToSource(parent); // parent is already mapped at this point
+  int sourceRow = row;
+  QModelIndex sourceGrandChild = d->grandChild(
+    sourceParent, sourceRow, qMax(0, d->EndFlattenLevel - d->indexLevel(sourceParent)));
+  return this->createIndex(row, column, sourceGrandChild.internalPointer());
+}
+
+// ----------------------------------------------------------------------------
+QModelIndex ctkFlatProxyModel::parent(const QModelIndex &child) const
+{
+  Q_D(const ctkFlatProxyModel);
+  if (!child.isValid())
+    {
+    return QModelIndex();
+    }
+  QModelIndex sourceChild = this->mapToSource(child);
+  QModelIndex sourceParent = sourceChild.parent();
+  QModelIndex proxyParent = this->mapFromSource(sourceParent);
+  return proxyParent;
+}
+
+// ----------------------------------------------------------------------------
+int ctkFlatProxyModel::rowCount(const QModelIndex &parent) const
+{
+  Q_D(const ctkFlatProxyModel);
+  QModelIndex sourceParent = this->mapToSource(parent);
+  int sourceParentLevel = d->indexLevel(sourceParent);
+  int depth = 0;
+  if (sourceParentLevel >= d->StartFlattenLevel &&
+      sourceParentLevel <= d->EndFlattenLevel)
+    {
+    depth = d->EndFlattenLevel - d->StartFlattenLevel;
+    }
+  return d->rowCount(sourceParent, depth);
+}
+
+// ----------------------------------------------------------------------------
+int ctkFlatProxyModel::columnCount(const QModelIndex &parent) const
+{
+  Q_D(const ctkFlatProxyModel);
+  QModelIndex proxyChild = this->index(0, 0, parent);
+  if (parent.isValid() && !proxyChild.internalPointer())
+    {
+    Q_ASSERT(!parent.isValid() || proxyChild.internalPointer());
+    }
+  QModelIndex sourceChild = this->mapToSource(proxyChild);
+  QModelIndex sourceParent = sourceChild.parent();
+  return this->sourceModel()->columnCount(sourceParent);
+}

+ 81 - 0
Libs/Widgets/ctkFlatProxyModel.h

@@ -0,0 +1,81 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) Kitware Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=========================================================================*/
+
+#ifndef __ctkFlatProxyModel_h
+#define __ctkFlatProxyModel_h
+
+// QT includes
+#include <QAbstractProxyModel>
+
+// CTK includes
+#include "ctkWidgetsExport.h"
+
+class ctkFlatProxyModelPrivate;
+
+///
+/// ctkFlatProxyModel intends to flatten contiguous hierarchies within a model.
+/// For now, it only supports the toplevel hierarchy flatten with the 1 degree
+/// children.
+/// The items in the levels being flatten don't appear in the model anymore, 
+/// however their children will be visible.
+class CTK_WIDGETS_EXPORT ctkFlatProxyModel : public QAbstractProxyModel
+{
+  Q_OBJECT
+  /// level for which to start flattening the rows. -1 by default
+  /// Not supported yet.
+  Q_PROPERTY(int startFlattenLevel READ startFlattenLevel WRITE setStartFlattenLevel)
+  /// level for which to stop flattening the rows. -1 by default
+  Q_PROPERTY(int endFlattenLevel READ endFlattenLevel WRITE setEndFlattenLevel)
+  /// level for which to stop flattening the rows. -1 by default
+  /// Not supported yet.
+  Q_PROPERTY(int hideLevel READ hideLevel WRITE setHideLevel)
+
+public:
+  typedef QAbstractProxyModel Superclass;
+
+  ctkFlatProxyModel(QObject *parentObject = 0);
+  virtual ~ctkFlatProxyModel();
+
+  void setStartFlattenLevel(int level);
+  int startFlattenLevel() const;
+
+  void setEndFlattenLevel(int level);
+  int endFlattenLevel() const;
+
+  void setHideLevel(int level);
+  int hideLevel() const;
+
+  virtual QModelIndex mapFromSource( const QModelIndex& sourceIndex ) const;
+  virtual QModelIndex mapToSource( const QModelIndex& sourceIndex ) const;
+
+  virtual QModelIndex index(int row, int column, const QModelIndex &parent) const;
+  virtual QModelIndex parent(const QModelIndex &child) const;
+  virtual int rowCount(const QModelIndex &parent) const;
+  virtual int columnCount(const QModelIndex &parent) const;
+
+protected:
+  QScopedPointer<ctkFlatProxyModelPrivate> d_ptr;
+
+private:
+  Q_DECLARE_PRIVATE(ctkFlatProxyModel);
+  Q_DISABLE_COPY(ctkFlatProxyModel);
+};
+
+#endif