Przeglądaj źródła

Merge remote branch 'finetjul/checkable-dicom-model'

* finetjul/checkable-dicom-model:
  ctkCheckableHeaderView propagates checkstate up and down
  Add depth in the propagation of ctkCheckableHeaderView

Conflicts:
	Libs/DICOM/Core/ctkDICOMModel.cpp
	Libs/DICOM/Widgets/ctkDICOMQueryRetrieveWidget.cpp
Julien Finet 14 lat temu
rodzic
commit
360f0471e5

+ 53 - 11
Libs/DICOM/Core/ctkDICOMModel.cpp

@@ -77,6 +77,7 @@ public:
 
 //------------------------------------------------------------------------------
 // 1 node per row
+// TBD: should probably use the QStandardItems instead.
 struct Node
 {
   ~Node()
@@ -88,14 +89,15 @@ struct Node
     this->Children.clear();
     }
   ctkDICOMModelPrivate::IndexType Type;
-  Node*     Parent;
-  QVector<Node*> Children;
-  int       Row;
-  QSqlQuery Query;
-  QString   UID;
-  int       RowCount;
-  bool      AtEnd;
-  bool      Fetching;
+  Node*                           Parent;
+  QVector<Node*>                  Children;
+  int                             Row;
+  QSqlQuery                       Query;
+  QString                         UID;
+  int                             RowCount;
+  bool                            AtEnd;
+  bool                            Fetching;
+  QMap<int, QVariant>             Data;
 };
 
 //------------------------------------------------------------------------------
@@ -426,7 +428,16 @@ QVariant ctkDICOMModel::data ( const QModelIndex & dataIndex, int role ) const
 
   if (role != Qt::DisplayRole && role != Qt::EditRole)
     {
-    return QVariant();
+    if (dataIndex.column() != 0)
+      {
+      return QVariant();
+      }
+    Node* node = d->nodeFromIndex(dataIndex);
+    if (!node)
+      {
+      return QVariant();
+      }
+    return node->Data[role];
     }
   QModelIndex parentIndex = this->parent(dataIndex);
   Node* parentNode = d->nodeFromIndex(parentIndex);
@@ -457,8 +468,21 @@ void ctkDICOMModel::fetchMore ( const QModelIndex & parentValue )
 //------------------------------------------------------------------------------
 Qt::ItemFlags ctkDICOMModel::flags ( const QModelIndex & modelIndex ) const
 {
-  Q_UNUSED(modelIndex);
-  return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
+  Q_D(const ctkDICOMModel);
+  Qt::ItemFlags indexFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
+  if (modelIndex.column() != 0)
+    {
+    return indexFlags;
+    }
+  Node* node = d->nodeFromIndex(modelIndex);
+  if (!node)
+    {
+    return indexFlags;
+    }
+  bool checkable = false;
+  node->Data[Qt::CheckStateRole].toInt(&checkable);
+  indexFlags = indexFlags | (checkable ? Qt::ItemIsUserCheckable : Qt::NoItemFlags);
+  return indexFlags;
 }
 
 //------------------------------------------------------------------------------
@@ -591,6 +615,24 @@ int ctkDICOMModel::rowCount ( const QModelIndex & parentValue ) const
 }
 
 //------------------------------------------------------------------------------
+bool ctkDICOMModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+  Q_D(const ctkDICOMModel);
+  if (role != Qt::CheckStateRole)
+    {
+    return false;
+    }
+  Node* node = d->nodeFromIndex(index);
+  if (!node || node->Data[role] == value)
+    {
+    return false;
+    }
+  node->Data[role] = value;
+  emit dataChanged(index, index);
+  return true;
+}
+
+//------------------------------------------------------------------------------
 void ctkDICOMModel::setDatabase(const QSqlDatabase &db)
 {
   Q_D(ctkDICOMModel);

+ 1 - 0
Libs/DICOM/Core/ctkDICOMModel.h

@@ -56,6 +56,7 @@ public:
   virtual QModelIndex index ( int row, int column, const QModelIndex & parent = QModelIndex() ) const;
   virtual QModelIndex parent ( const QModelIndex & index ) const;
   virtual int rowCount ( const QModelIndex & parent = QModelIndex() ) const;
+  virtual bool setData(const QModelIndex &index, const QVariant &value, int role);
   virtual bool setHeaderData ( int section, Qt::Orientation orientation, const QVariant & value, int role = Qt::EditRole );
   // Sorting resets the model because fetched/unfetched items could disappear/appear respectively.
   virtual void sort(int column, Qt::SortOrder order = Qt::AscendingOrder);

+ 173 - 81
Libs/Widgets/ctkCheckableHeaderView.cpp

@@ -73,13 +73,19 @@ protected:
 public:
   ctkCheckableHeaderViewPrivate(ctkCheckableHeaderView& object);
   ~ctkCheckableHeaderViewPrivate();
+
   void init();
+  void setIndexCheckState(const QModelIndex& index, Qt::CheckState checkState);
+  int indexDepth(const QModelIndex& modelIndex)const;
+  void updateCheckState(const QModelIndex& modelIndex);
+  void propagateCheckStateToChildren(const QModelIndex& modelIndex);
 
-  int Pressed;
+  int                 Pressed;
   ctkCheckBoxPixmaps* CheckBoxPixmaps;
-  bool HeaderIsUpdating;
-  bool ItemsAreUpdating;
-  bool PropagateToItems;
+  bool                HeaderIsUpdating;
+  bool                ItemsAreUpdating;
+  bool                PropagateToItems;
+  int                 PropagateDepth;
 };
 
 //----------------------------------------------------------------------------
@@ -91,6 +97,7 @@ ctkCheckableHeaderViewPrivate::ctkCheckableHeaderViewPrivate(ctkCheckableHeaderV
   this->CheckBoxPixmaps = 0;
   this->Pressed = -1;
   this->PropagateToItems = true;
+  this->PropagateDepth = -1;
 }
 
 //-----------------------------------------------------------------------------
@@ -111,6 +118,138 @@ void ctkCheckableHeaderViewPrivate::init()
 }
 
 //----------------------------------------------------------------------------
+void ctkCheckableHeaderViewPrivate::setIndexCheckState(
+  const QModelIndex& index, Qt::CheckState checkState)
+{
+  Q_Q(ctkCheckableHeaderView);
+  int depth = this->indexDepth(index);
+  if (depth > this->PropagateDepth && this->PropagateDepth != -1)
+    {
+    return;
+    }
+  QVariant indexCheckState = q->model()->data(index, Qt::CheckStateRole);
+  bool checkable = false;
+  int state = indexCheckState.toInt(&checkable);
+  if (checkable && state == checkState)
+    {// TODO: here if you don't want to overwrite the uncheckability of indexes
+    return;
+    }
+  q->model()->setData(index, checkState, Qt::CheckStateRole);
+  this->propagateCheckStateToChildren(index);
+}
+
+//-----------------------------------------------------------------------------
+int ctkCheckableHeaderViewPrivate::indexDepth(const QModelIndex& modelIndex)const
+{
+  int depth = 0;
+  QModelIndex parentIndex = modelIndex;
+  while (parentIndex.isValid())
+    {
+    ++depth;
+    parentIndex = parentIndex.parent();
+    }
+  return depth;
+}
+
+//-----------------------------------------------------------------------------
+void ctkCheckableHeaderViewPrivate
+::updateCheckState(const QModelIndex& modelIndex)
+{
+  Q_Q(ctkCheckableHeaderView);
+  
+  Qt::CheckState newCheckState = Qt::PartiallyChecked;
+  bool firstCheckableChild = true;
+  bool checkable = false;
+  const int rowCount = q->orientation() == Qt::Horizontal ?
+    q->model()->rowCount(modelIndex) : 1;
+  const int columnCount = q->orientation() == Qt::Vertical ?
+    q->model()->columnCount(modelIndex) : 1;
+  for (int r = 0; r < rowCount; ++r)
+    {
+    for (int c = 0; c < columnCount; ++c)
+      {
+      QModelIndex child = q->model()->index(r, c, modelIndex);
+      QVariant childCheckState = q->model()->data(child, Qt::CheckStateRole);
+      int childState = childCheckState.toInt(&checkable);
+      if (!checkable)
+        {
+        continue;
+        }
+      if (firstCheckableChild)
+        {
+        newCheckState = static_cast<Qt::CheckState>(childState);
+        firstCheckableChild = false;
+        }
+      if (newCheckState != childState)
+        {
+        newCheckState = Qt::PartiallyChecked;
+        }
+      if (newCheckState == Qt::PartiallyChecked)
+        {
+        break;
+        }
+      }
+    if (!firstCheckableChild && newCheckState == Qt::PartiallyChecked)
+      {
+      break;
+      } 
+    }
+  QVariant indexCheckState = modelIndex != q->rootIndex() ?
+    q->model()->data(modelIndex, Qt::CheckStateRole):
+    q->model()->headerData(0, q->orientation(), Qt::CheckStateRole);
+  int oldCheckState = indexCheckState.toInt(&checkable);
+  Q_ASSERT(checkable);
+  if (oldCheckState == newCheckState)
+    {
+    return;
+    }
+  if (modelIndex != q->rootIndex())
+    {
+    q->model()->setData(modelIndex, newCheckState, Qt::CheckStateRole);
+    this->updateCheckState(modelIndex.parent());
+    }
+  else
+    {
+    q->model()->setHeaderData(0, q->orientation(), newCheckState, Qt::CheckStateRole);
+    }
+}
+
+//-----------------------------------------------------------------------------
+void ctkCheckableHeaderViewPrivate
+::propagateCheckStateToChildren(const QModelIndex& modelIndex)
+{
+  Q_Q(ctkCheckableHeaderView);
+  int indexDepth = this->indexDepth(modelIndex);
+  if (indexDepth > this->PropagateDepth && this->PropagateDepth != -1)
+    {
+    return;
+    }
+
+  QVariant indexCheckState = (modelIndex != q->rootIndex() ?
+    q->model()->data(modelIndex, Qt::CheckStateRole):
+    q->model()->headerData(0, q->orientation(), Qt::CheckStateRole));
+  bool checkable = false;
+  Qt::CheckState checkState =
+    static_cast<Qt::CheckState>(indexCheckState.toInt(&checkable));
+  if (!checkable || checkState == Qt::PartiallyChecked)
+    {
+    return;
+    }
+  const int rowCount = q->orientation() == Qt::Horizontal ?
+    q->model()->rowCount(modelIndex) : 1;
+  const int columnCount = q->orientation() == Qt::Vertical ?
+    q->model()->columnCount(modelIndex) : 1;
+  for (int r = 0; r < rowCount; ++r)
+    {
+    for (int c = 0; c < columnCount; ++c)
+      {
+      QModelIndex child = q->model()->index(r, c, modelIndex);
+      this->setIndexCheckState(child, checkState);
+      }
+    }
+}
+
+//----------------------------------------------------------------------------
 ctkCheckableHeaderView::ctkCheckableHeaderView(
   Qt::Orientation orient, QWidget *widgetParent)
   : QHeaderView(orient, widgetParent)
@@ -246,6 +385,21 @@ bool ctkCheckableHeaderView::propagateToItems()const
 }
 
 //-----------------------------------------------------------------------------
+void ctkCheckableHeaderView::setPropagateDepth(int depth)
+{
+  Q_D(ctkCheckableHeaderView);
+  d->PropagateDepth = depth;
+  //TODO: rescan the model
+}
+
+//-----------------------------------------------------------------------------
+int ctkCheckableHeaderView::propagateDepth()const
+{
+  Q_D(const ctkCheckableHeaderView);
+  return d->PropagateDepth;
+}
+
+//-----------------------------------------------------------------------------
 void ctkCheckableHeaderView::toggleCheckState(int section)
 {
   // If the section is checkable, toggle the check state.
@@ -261,6 +415,7 @@ void ctkCheckableHeaderView::toggleCheckState(int section)
 //-----------------------------------------------------------------------------
 void ctkCheckableHeaderView::setCheckState(int section, Qt::CheckState checkState)
 {
+  Q_D(ctkCheckableHeaderView);
   // If the section is checkable, toggle the check state.
   QAbstractItemModel *current = this->model();
   if(current == 0)
@@ -271,6 +426,9 @@ void ctkCheckableHeaderView::setCheckState(int section, Qt::CheckState checkStat
   // should be changed to checked.
   current->setHeaderData(section, this->orientation(),
                          checkState, Qt::CheckStateRole);
+  d->ItemsAreUpdating = true;
+  d->propagateCheckStateToChildren(this->rootIndex());
+  d->ItemsAreUpdating = false;
 }
 
 //-----------------------------------------------------------------------------
@@ -314,18 +472,6 @@ void ctkCheckableHeaderView::updateHeaders(int firstSection, int lastSection)
     if (this->checkState(i, checkState))
       {
       decoration = d->CheckBoxPixmaps->pixmap(checkState, active);
-      if (d->PropagateToItems && 
-          checkState != Qt::PartiallyChecked &&
-          !d->ItemsAreUpdating)
-        {
-        for (int j = 0 ; j < maxJ; ++j)
-          {
-          QModelIndex index = this->orientation() == Qt::Horizontal ? 
-            current->index(j, i,this->rootIndex()) :
-            current->index(i, j,this->rootIndex()) ;
-          current->setData(index, checkState, Qt::CheckStateRole);
-          }
-        }
       }
     current->setHeaderData(i, this->orientation(), decoration,
                            Qt::DecorationRole);
@@ -354,79 +500,25 @@ void ctkCheckableHeaderView::updateHeadersFromItems(const QModelIndex & topLeft,
 {
   Q_UNUSED(bottomRight);
   Q_D(ctkCheckableHeaderView);
-  if(d->ItemsAreUpdating || !d->PropagateToItems || 
-     topLeft.parent() != this->rootIndex())
+  if(d->ItemsAreUpdating || !d->PropagateToItems)
     {
     return;
     }
-  d->ItemsAreUpdating = true;
-  
-  QAbstractItemModel *current = this->model();
-  Q_ASSERT(current);
-
-  int lastI;
-  int lastJ;
-  if (this->orientation() == Qt::Horizontal)
-    {
-    lastI = this->count();
-    lastJ = current->rowCount();
-    }
-  else
-    {
-    lastI = this->count();
-    lastJ = current->columnCount();
-    }
-  
-  for(int i = 0; i <= lastI; ++i)
+  bool checkable = false;
+  QVariant topLeftCheckState = this->model()->data(topLeft, Qt::CheckStateRole);
+  int topLeftState = topLeftCheckState.toInt(&checkable);
+  if (!checkable)
     {
-    Qt::CheckState sectionState;
-    if (!this->checkState(i, sectionState))
-      {
-      continue;
-      }
-    bool itemIsCheckable = false;
-    // get the first item state
-    Qt::CheckState itemsState;
-    int j = 0;
-    for ( ; j <= lastJ; ++j)
-      {
-      QModelIndex index = this->orientation() == Qt::Horizontal ? 
-        current->index(j, i, topLeft.parent()) : 
-        current->index(i, j, topLeft.parent());
-      itemsState = static_cast<Qt::CheckState>(
-        index.data(Qt::CheckStateRole).toInt(&itemIsCheckable));
-      if (itemIsCheckable)
-        {
-        break;
-        }
-      }
-    if (j > lastJ)
-      {// the first item check state couldn't be found
-      continue;
-      }
-    // check the other states to make sure it is the same state
-    for (; j <= lastJ; ++j)
-      {
-      QModelIndex index = this->orientation() == Qt::Horizontal ? 
-        current->index(j, i, topLeft.parent()) : 
-        current->index(i, j, topLeft.parent());
-      Qt::CheckState itemState = 
-        static_cast<Qt::CheckState>(index.data(Qt::CheckStateRole).toInt(&itemIsCheckable));
-      if (itemIsCheckable && itemState!= itemsState)
-        {// there is at least 1 item with a different state
-        this->setCheckState(i, Qt::PartiallyChecked);
-        break;
-        }
-      }
-    if (j > lastJ)
-      {
-      this->setCheckState(i, itemsState);
-      }
+    return;
     }
+  d->ItemsAreUpdating = true;
+
+  d->propagateCheckStateToChildren(topLeft);
+  d->updateCheckState(topLeft.parent());
+
   d->ItemsAreUpdating = false;
 }
 
-
 //-----------------------------------------------------------------------------
 void ctkCheckableHeaderView::insertHeaderSection(const QModelIndex &parentIndex,
     int first, int last)

+ 8 - 0
Libs/Widgets/ctkCheckableHeaderView.h

@@ -69,10 +69,13 @@ class ctkCheckableHeaderViewPrivate;
 /// all items in the header row/column of the QAbstractItemModel if the 
 /// items are checkable.
 /// ctkCheckableHeaderView also supports row/column sorting.
+/// TBD: It should probably be a QSortFilterProxyModel that adds a checkability
+/// data on top of the indexes.
 class CTK_WIDGETS_EXPORT ctkCheckableHeaderView : public QHeaderView
 {
   Q_OBJECT;
   Q_PROPERTY(bool propagateToItems READ propagateToItems WRITE setPropagateToItems);
+  Q_PROPERTY(int propagateDepth READ propagateDepth WRITE setPropagateDepth);
 public:
   ctkCheckableHeaderView(Qt::Orientation orient, QWidget *parent=0);
   virtual ~ctkCheckableHeaderView();
@@ -118,6 +121,11 @@ public:
   void setPropagateToItems(bool propagate);
   bool propagateToItems()const;
 
+  /// How deep in the model(tree) do you want the check state to be propagated
+  /// A value of -1 correspond to the deepest level of the model.
+  void setPropagateDepth(int depth);
+  int  propagateDepth()const;
+
 public slots:
   ///
   /// if the check state is PartiallyChecked, the section becomes Checked