diff --git a/Modules/QtWidgets/include/QmitkDataStorageListModel.h b/Modules/QtWidgets/include/QmitkDataStorageListModel.h index cd40046796..01b44957e0 100755 --- a/Modules/QtWidgets/include/QmitkDataStorageListModel.h +++ b/Modules/QtWidgets/include/QmitkDataStorageListModel.h @@ -1,126 +1,151 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ - -#ifndef QMITKDATASTORAGELISTMODEL_H_ -#define QMITKDATASTORAGELISTMODEL_H_ +#ifndef QmitkDataStorageListModel_h +#define QmitkDataStorageListModel_h #include -//# Own includes -// mitk +// MITK #include "mitkDataStorage.h" #include "mitkNodePredicateBase.h" -// Qmitk -//# Toolkit includes // Qt #include -// stl -#include -/// \ingroup QmitkModule +//! \ingroup QmitkModule +//! Qt list model for the (optionally filtered) nodes in a DataStorage. +//! +//! Given a data storage instance, this model will observe the storage +//! for its list of nodes and keep the provided Qt model up to date. +//! When given a NodePredicateBase instance, the Qt model will only +//! contain nodes that satisfy the predicate. This is useful to +//! display lists of a certain data type only, for example. +//! +//! Developer notes: +//! - class should be reviewed by somebody who knows well Qt models +//! - The OnSomethingModifedAddedRemoved() methods are declared virtual. They are required +//! to be executed on event reception, though! They should not be virtual. +//! - Is there any valid use case for sub-classing? Declare class final? +//! - Is GetDataNodes needed? DataStorage or Qt model would yield the same result. class MITKQTWIDGETS_EXPORT QmitkDataStorageListModel: public QAbstractListModel { public: - //# Ctors / Dtor - /// - /// The NodePredicate is owned by the model - /// - QmitkDataStorageListModel(mitk::DataStorage::Pointer dataStorage = nullptr, mitk::NodePredicateBase* pred = nullptr, QObject* parent = nullptr); - ~QmitkDataStorageListModel(); + //! \param dataStorage the data storage to represent + //! \param predicate the optional predicate to filter filters + //! \param parent the Qt parent of this Qt object + QmitkDataStorageListModel(mitk::DataStorage* dataStorage = nullptr, + mitk::NodePredicateBase::Pointer pred = nullptr, + QObject* parent = nullptr); + + virtual ~QmitkDataStorageListModel(); - //# Getter / Setter + + //! Change the data storage to represent void SetDataStorage(mitk::DataStorage::Pointer dataStorage); - mitk::DataStorage::Pointer GetDataStorage() const; + //! Get the represented data storage + mitk::DataStorage* GetDataStorage() const; + + //! Change the filter predicate void SetPredicate(mitk::NodePredicateBase* pred); + + //! Get the filter predicate in use mitk::NodePredicateBase* GetPredicate() const; + //! Get all current data nodes std::vector GetDataNodes() const; + //! Return the node for given model index mitk::DataNode::Pointer getNode(const QModelIndex &index) const; - //# From QAbstractListModel + //! Return the model index of the given node + QModelIndex getIndex(const mitk::DataNode* node) const; + + //! Implements QAbstractListModel Qt::ItemFlags flags(const QModelIndex& index) const override; + + //! Implements QAbstractListModel QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + + //! Implements QAbstractListModel QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + //! Implements QAbstractListModel int rowCount(const QModelIndex& parent = QModelIndex()) const override; - /// - /// Called when a DataStorage Add Event was thrown. May be reimplemented - /// by deriving classes. - /// - virtual void NodeAdded(const mitk::DataNode* node); - /// - /// Called when a DataStorage Remove Event was thrown. May be reimplemented - /// by deriving classes. - /// - virtual void NodeRemoved(const mitk::DataNode* node); - - /// - /// \brief Called when a itk::Object that is hold as a member variable was - /// modified in order to react to it. - /// - virtual void OnModified(const itk::Object *caller, const itk::EventObject &event); - - /// - /// \brief Called when a itk::Object that is hold as a member variable is about to be - /// deleted in order to react to it. - /// - virtual void OnDelete(const itk::Object *caller, const itk::EventObject &event); + //! Called when a DataStorage Add Event was thrown. May be reimplemented + //! by deriving classes. + //! + //! \warning When sub-classing, call this class' method first! Otherwise the node + //! addition will not be reflected in the Qt model! + virtual void OnDataStorageNodeAdded(const mitk::DataNode* node); + + //! Called when a DataStorage Remove Event was thrown. May be reimplemented + //! by deriving classes. + //! + //! \warning When sub-classing, call this class' method first! Otherwise the node + //! removal will not be reflected in the Qt model! + virtual void OnDataStorageNodeRemoved(const mitk::DataNode* node); + + //! Callback entry for observed DataNodes' ModifiedEvent(). + //! + //! Emits signal dataChanged(). + //! + //! \warning When sub-classing, call this class' method first! Otherwise the node + //! removal will not be reflected in the Qt model! + virtual void OnDataNodeModified(const itk::Object *caller, const itk::EventObject &event); + + //! Callback entry for DataStorage's DeleteEvent(). + //! + //! Clears the model. + virtual void OnDataStorageDeleted(const itk::Object *caller, const itk::EventObject &event); protected: - /// - /// \brief Resets the whole model. Get all nodes matching the predicate from the data storage. - /// + + //! \brief Resets the whole model. Get all nodes matching the predicate from the data storage. void reset(); - /// - /// Holds the predicate that defines this SubSet of Nodes. If m_Predicate - /// is NULL all Nodes will be selected. *Attention: this class owns the predicate and deletes it* - /// + //! Internal helper: adds given node to end of list + void AddNodeToInternalList(mitk::DataNode* node); + + //! Internal helper: remove given node + void RemoveNodeFromInternalList(mitk::DataNode* node); + + //! Internal helper: Clear complete model list + void ClearInternalNodeList(); + +private: + + //! Holds the predicate that defines what nodes are part of the model. mitk::NodePredicateBase::Pointer m_NodePredicate; - /// - /// Pointer to the DataStorage from which the nodes are selected (remember: in BlueBerry there - /// might be more than one DataStorage). - /// + //! The DataStorage that is represented in the model. + //! We keep only a weak pointer and observe the storage for deletion. mitk::DataStorage* m_DataStorage; - /// - /// \brief Holds the tag of the datastorage-delete observer. - /// + //! ITK observer tag for the storage's DeleteEvent() unsigned long m_DataStorageDeleteObserverTag; - /// - /// Holds all selected Nodes. Dont hold smart pointer as we are in a GUI class. - /// - std::vector m_DataNodes; - - /// - /// \brief Holds the tags of the node-modified observers. - /// - std::vector m_DataNodesModifiedObserversTags; + //! List of the current model's DataNodes along with their ModifiedEvent observer tags. + std::vector> m_NodesAndObserverTags; - /// - /// Saves if this model is currently working on events to prevent endless event loops. - /// + //! Prevents infinite loops. bool m_BlockEvents; }; -#endif /* QMITKDATASTORAGELISTMODEL_H_ */ +#endif diff --git a/Modules/QtWidgets/src/QmitkDataStorageListModel.cpp b/Modules/QtWidgets/src/QmitkDataStorageListModel.cpp index 44774c2a57..fae9fca0c4 100755 --- a/Modules/QtWidgets/src/QmitkDataStorageListModel.cpp +++ b/Modules/QtWidgets/src/QmitkDataStorageListModel.cpp @@ -1,274 +1,312 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "QmitkDataStorageListModel.h" //# Own includes // mitk #include "mitkStringProperty.h" //# Toolkit includes // itk #include "itkCommand.h" -QmitkDataStorageListModel::QmitkDataStorageListModel(mitk::DataStorage::Pointer dataStorage - , mitk::NodePredicateBase* pred, QObject* parent) - : QAbstractListModel(parent), m_NodePredicate(nullptr), m_DataStorage(nullptr), m_BlockEvents(false) +QmitkDataStorageListModel::QmitkDataStorageListModel(mitk::DataStorage* dataStorage, + mitk::NodePredicateBase::Pointer pred, + QObject* parent) +:QAbstractListModel(parent), + m_NodePredicate(nullptr), + m_DataStorage(nullptr), + m_BlockEvents(false) { this->SetPredicate(pred); this->SetDataStorage(dataStorage); } QmitkDataStorageListModel::~QmitkDataStorageListModel() { - // set data storage to 0 so that event listener get removed + // set data storage to nullptr so that the event listener gets removed this->SetDataStorage(nullptr); - - if (m_NodePredicate) delete m_NodePredicate; } void QmitkDataStorageListModel::SetDataStorage(mitk::DataStorage::Pointer dataStorage) { - if( m_DataStorage != dataStorage) + if( m_DataStorage == dataStorage) { - // remove old listeners - if(m_DataStorage != nullptr) - { - this->m_DataStorage->AddNodeEvent.RemoveListener( mitk::MessageDelegate1( this, &QmitkDataStorageListModel::NodeAdded ) ); + return; + } - this->m_DataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1( this, &QmitkDataStorageListModel::NodeRemoved ) ); + // remove old listeners + if(m_DataStorage != nullptr) + { + m_DataStorage->AddNodeEvent.RemoveListener( + mitk::MessageDelegate1( + this, &QmitkDataStorageListModel::OnDataStorageNodeAdded)); - // remove delete observer - m_DataStorage->RemoveObserver(m_DataStorageDeleteObserverTag); - // this is good coding style ! reset variables whenever they are not used anymore. - m_DataStorageDeleteObserverTag = 0; - } + m_DataStorage->RemoveNodeEvent.RemoveListener( + mitk::MessageDelegate1( + this, &QmitkDataStorageListModel::OnDataStorageNodeRemoved)); - m_DataStorage = dataStorage; + m_DataStorage->RemoveObserver(m_DataStorageDeleteObserverTag); + m_DataStorageDeleteObserverTag = 0; + } - // remove event listeners - if(m_DataStorage != nullptr) - { - // subscribe for node added/removed events - this->m_DataStorage->AddNodeEvent.AddListener( mitk::MessageDelegate1( this, &QmitkDataStorageListModel::NodeAdded ) ); - - this->m_DataStorage->RemoveNodeEvent.AddListener( mitk::MessageDelegate1( this, &QmitkDataStorageListModel::NodeRemoved ) ); - - // add itk delete listener on datastorage - itk::MemberCommand::Pointer deleteCommand = - itk::MemberCommand::New(); - deleteCommand->SetCallbackFunction(this, &QmitkDataStorageListModel::OnDelete); - // add observer - m_DataStorageDeleteObserverTag = m_DataStorage->AddObserver(itk::DeleteEvent(), deleteCommand); - } + m_DataStorage = dataStorage; - // reset model - reset(); + if(m_DataStorage != nullptr) + { + // subscribe for node added/removed events + m_DataStorage->AddNodeEvent.AddListener( + mitk::MessageDelegate1( + this, &QmitkDataStorageListModel::OnDataStorageNodeAdded)); + + m_DataStorage->RemoveNodeEvent.AddListener( + mitk::MessageDelegate1( + this, &QmitkDataStorageListModel::OnDataStorageNodeRemoved)); + + // add ITK delete listener on data storage + itk::MemberCommand::Pointer deleteCommand = + itk::MemberCommand::New(); + deleteCommand->SetCallbackFunction(this, + &QmitkDataStorageListModel::OnDataStorageDeleted); + m_DataStorageDeleteObserverTag = m_DataStorage->AddObserver(itk::DeleteEvent(), deleteCommand); } + + // reset/rebuild model + reset(); } Qt::ItemFlags QmitkDataStorageListModel::flags(const QModelIndex&) const { return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } QVariant QmitkDataStorageListModel::data(const QModelIndex& index, int role) const { - if(index.isValid()) + if(role == Qt::DisplayRole && index.isValid()) { - switch ( role ) - { - case Qt::DisplayRole: - { - const mitk::DataNode* node = m_DataNodes.at(index.row()); - std::string name = node->GetName(); - return QVariant(QString::fromStdString(name)); - } - break; - - } - } // index.isValid() - - return QVariant(); + const mitk::DataNode* node = m_NodesAndObserverTags.at(index.row()).first; + return QVariant(QString::fromStdString(node->GetName())); + } + else + { + return QVariant(); + } } QVariant QmitkDataStorageListModel::headerData(int /*section*/, Qt::Orientation /*orientation*/, int /*role*/) const { - return QVariant("Nodes"); + return QVariant(tr("Nodes")); } int QmitkDataStorageListModel::rowCount(const QModelIndex& /*parent*/) const { - return m_DataNodes.size(); + return m_NodesAndObserverTags.size(); } std::vector QmitkDataStorageListModel::GetDataNodes() const { - return m_DataNodes; + auto size = m_NodesAndObserverTags.size(); + std::vector result(size); + for (int i = 0; i < size; ++i) + { + result[i] = m_NodesAndObserverTags[i].first; + } + return result; } -mitk::DataStorage::Pointer QmitkDataStorageListModel::GetDataStorage() const +mitk::DataStorage* QmitkDataStorageListModel::GetDataStorage() const { return m_DataStorage; } void QmitkDataStorageListModel::SetPredicate(mitk::NodePredicateBase* pred) { m_NodePredicate = pred; - reset(); + + // in a prior implementation the call to beginResetModel() been after reset(). + // Should this actually be the better order of calls, please document! QAbstractListModel::beginResetModel(); + reset(); QAbstractListModel::endResetModel(); } mitk::NodePredicateBase* QmitkDataStorageListModel::GetPredicate() const { return m_NodePredicate; } void QmitkDataStorageListModel::reset() { - if(m_DataStorage != nullptr) + if(m_DataStorage == nullptr) { - mitk::DataStorage::SetOfObjects::ConstPointer setOfObjects; - - if (m_NodePredicate) - setOfObjects = m_DataStorage->GetSubset(m_NodePredicate); - else - setOfObjects = m_DataStorage->GetAll(); - - // remove all observes - unsigned int i = 0; - for(auto it=m_DataNodes.begin() - ; it!=m_DataNodes.end() - ; ++it, ++i) - { - (*it)->RemoveObserver(m_DataNodesModifiedObserversTags[i]); - } + return; + } + + mitk::DataStorage::SetOfObjects::ConstPointer modelNodes; + + if ( m_NodePredicate ) + modelNodes = m_DataStorage->GetSubset(m_NodePredicate); + else + modelNodes = m_DataStorage->GetAll(); + + ClearInternalNodeList(); + + // add all filtered nodes to our list + for (auto& node : *modelNodes) + { + AddNodeToInternalList(node); + } +} - // clear vector with nodes - m_DataNodesModifiedObserversTags.clear(); - m_DataNodes.clear(); +void QmitkDataStorageListModel::AddNodeToInternalList(mitk::DataNode* node) +{ + if (m_DataStorage != nullptr) + { itk::MemberCommand::Pointer modifiedCommand; - // copy all selected nodes the vector - for (mitk::DataStorage::SetOfObjects::ConstIterator nodeIt = setOfObjects->Begin() - ; nodeIt != setOfObjects->End(); ++nodeIt, ++i) // for each node - { - // add modified observer - modifiedCommand = itk::MemberCommand::New(); - modifiedCommand->SetCallbackFunction(this, &QmitkDataStorageListModel::OnModified); - m_DataNodesModifiedObserversTags.push_back( m_DataStorage->AddObserver(itk::ModifiedEvent(), modifiedCommand) ); - m_DataNodes.push_back( nodeIt.Value().GetPointer()); + // add modified observer + modifiedCommand = itk::MemberCommand::New(); + modifiedCommand->SetCallbackFunction(this, &QmitkDataStorageListModel::OnDataNodeModified); + unsigned long observerTag = m_DataStorage->AddObserver(itk::ModifiedEvent(), modifiedCommand); - } // for + m_NodesAndObserverTags.push_back( std::make_pair(node, observerTag) ); + } +} - } // m_DataStorage != 0 +void QmitkDataStorageListModel::ClearInternalNodeList() +{ + for(auto& iter : m_NodesAndObserverTags) + { + iter.first->RemoveObserver(iter.second); + } + m_NodesAndObserverTags.clear(); +} -} // reset() +void QmitkDataStorageListModel::RemoveNodeFromInternalList(mitk::DataNode* node) +{ + for(auto iter = m_NodesAndObserverTags.begin(); + iter != m_NodesAndObserverTags.end(); + ++iter) + { + if (iter->first == node) + { + node->RemoveObserver(iter->second); + m_NodesAndObserverTags.erase(iter); // invalidate iter + break; + } + } +} -void QmitkDataStorageListModel::NodeAdded( const mitk::DataNode* node ) +void QmitkDataStorageListModel::OnDataStorageNodeAdded( const mitk::DataNode* node ) { - // garantuee no recursions when a new node event is thrown + // guarantee no recursions when a new node event is thrown if(!m_BlockEvents) { m_BlockEvents = true; // check if node should be added to the model bool addNode = true; if(m_NodePredicate && !m_NodePredicate->CheckNode(node)) addNode = false; if(addNode) { - beginInsertRows(QModelIndex(), m_DataNodes.size(), m_DataNodes.size()); - //reset(); - m_DataNodes.push_back(const_cast(node)); + int newIndex = m_NodesAndObserverTags.size(); + beginInsertRows(QModelIndex(), newIndex, newIndex); + AddNodeToInternalList(const_cast(node)); endInsertRows(); } m_BlockEvents = false; } } -void QmitkDataStorageListModel::NodeRemoved( const mitk::DataNode* node ) +void QmitkDataStorageListModel::OnDataStorageNodeRemoved( const mitk::DataNode* node ) { - // garantuee no recursions when a new node event is thrown + // guarantee no recursions when a new node event is thrown if(!m_BlockEvents) { m_BlockEvents = true; - int row = -1; - //bool removeNode = false; - // check if node is contained in current list, if yes: reset model - for (std::vector::const_iterator nodeIt = m_DataNodes.begin() - ; nodeIt != m_DataNodes.end(); nodeIt++) // for each node + int row = 0; + for(auto iter = m_NodesAndObserverTags.begin(); + iter != m_NodesAndObserverTags.end(); + ++iter, ++row) { - row++; - if( (*nodeIt) == node ) + if (iter->first == node) { // node found, remove it beginRemoveRows(QModelIndex(), row, row); - m_DataNodes.erase(std::find(m_DataNodes.begin(), m_DataNodes.end(), (*nodeIt))); + RemoveNodeFromInternalList(iter->first); endRemoveRows(); break; } } - - m_BlockEvents = false; } + + m_BlockEvents = false; } -void QmitkDataStorageListModel::OnModified( const itk::Object *caller, const itk::EventObject & /*event*/ ) +void QmitkDataStorageListModel::OnDataNodeModified( const itk::Object *caller, const itk::EventObject & /*event*/ ) { if(m_BlockEvents) return; const mitk::DataNode* modifiedNode = dynamic_cast(caller); if(modifiedNode) { - int row = std::distance(std::find(m_DataNodes.begin(), m_DataNodes.end(), modifiedNode), m_DataNodes.end()); - QModelIndex indexOfChangedProperty = index(row, 1); - - emit dataChanged(indexOfChangedProperty, indexOfChangedProperty); + QModelIndex changedIndex = getIndex(modifiedNode); + if (changedIndex.isValid()) + { + emit dataChanged(changedIndex, changedIndex); + } } } -void QmitkDataStorageListModel::OnDelete( const itk::Object *caller, const itk::EventObject & /*event*/ ) +void QmitkDataStorageListModel::OnDataStorageDeleted( const itk::Object *caller, const itk::EventObject & /*event*/ ) { if(m_BlockEvents) return; - const mitk::DataStorage* dataStorage = dynamic_cast(caller); - if(dataStorage) - { - // set datastorage to 0 -> empty model - this->SetDataStorage(nullptr); - } + // set data storage to nullptr -> empty model + this->SetDataStorage(nullptr); } mitk::DataNode::Pointer QmitkDataStorageListModel::getNode( const QModelIndex &index ) const { - mitk::DataNode::Pointer node; - if(index.isValid()) { - node = m_DataNodes.at(index.row()); + return m_NodesAndObserverTags.at(index.row()).first; + } + else + { + return nullptr; } +} - return node; +QModelIndex QmitkDataStorageListModel::getIndex(const mitk::DataNode* node) const +{ + int row = 0; + for ( auto iter = m_NodesAndObserverTags.begin(); + iter != m_NodesAndObserverTags.end(); + ++iter, ++row ) + { + if ( iter->first == node ) + { + return index(row); + } + } + return QModelIndex(); }