diff --git a/Modules/QtWidgets/src/QmitkDataStorageListModel.cpp b/Modules/QtWidgets/src/QmitkDataStorageListModel.cpp index fae9fca0c4..d73747e442 100755 --- a/Modules/QtWidgets/src/QmitkDataStorageListModel.cpp +++ b/Modules/QtWidgets/src/QmitkDataStorageListModel.cpp @@ -1,312 +1,313 @@ /*=================================================================== 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* 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 nullptr so that the event listener gets removed this->SetDataStorage(nullptr); } void QmitkDataStorageListModel::SetDataStorage(mitk::DataStorage::Pointer dataStorage) { if( m_DataStorage == dataStorage) { return; } // remove old listeners if(m_DataStorage != nullptr) { m_DataStorage->AddNodeEvent.RemoveListener( mitk::MessageDelegate1( this, &QmitkDataStorageListModel::OnDataStorageNodeAdded)); m_DataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1( this, &QmitkDataStorageListModel::OnDataStorageNodeRemoved)); m_DataStorage->RemoveObserver(m_DataStorageDeleteObserverTag); m_DataStorageDeleteObserverTag = 0; } m_DataStorage = dataStorage; 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(role == Qt::DisplayRole && index.isValid()) { 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(tr("Nodes")); } int QmitkDataStorageListModel::rowCount(const QModelIndex& /*parent*/) const { return m_NodesAndObserverTags.size(); } std::vector QmitkDataStorageListModel::GetDataNodes() const { 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* QmitkDataStorageListModel::GetDataStorage() const { return m_DataStorage; } void QmitkDataStorageListModel::SetPredicate(mitk::NodePredicateBase* pred) { m_NodePredicate = pred; // 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) - { - return; - } - mitk::DataStorage::SetOfObjects::ConstPointer modelNodes; - if ( m_NodePredicate ) - modelNodes = m_DataStorage->GetSubset(m_NodePredicate); - else - modelNodes = m_DataStorage->GetAll(); + if ( m_DataStorage != nullptr ) + { + if ( m_NodePredicate != nullptr ) + modelNodes = m_DataStorage->GetSubset(m_NodePredicate); + else + modelNodes = m_DataStorage->GetAll(); + } ClearInternalNodeList(); // add all filtered nodes to our list - for (auto& node : *modelNodes) + if (modelNodes != nullptr) { + for (auto& node : *modelNodes) + { AddNodeToInternalList(node); + } } } void QmitkDataStorageListModel::AddNodeToInternalList(mitk::DataNode* node) { if (m_DataStorage != nullptr) { itk::MemberCommand::Pointer modifiedCommand; // add modified observer modifiedCommand = itk::MemberCommand::New(); modifiedCommand->SetCallbackFunction(this, &QmitkDataStorageListModel::OnDataNodeModified); unsigned long observerTag = m_DataStorage->AddObserver(itk::ModifiedEvent(), modifiedCommand); m_NodesAndObserverTags.push_back( std::make_pair(node, observerTag) ); } } void QmitkDataStorageListModel::ClearInternalNodeList() { for(auto& iter : m_NodesAndObserverTags) { iter.first->RemoveObserver(iter.second); } m_NodesAndObserverTags.clear(); } 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::OnDataStorageNodeAdded( const mitk::DataNode* node ) { // 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) { int newIndex = m_NodesAndObserverTags.size(); beginInsertRows(QModelIndex(), newIndex, newIndex); AddNodeToInternalList(const_cast(node)); endInsertRows(); } m_BlockEvents = false; } } void QmitkDataStorageListModel::OnDataStorageNodeRemoved( const mitk::DataNode* node ) { // guarantee no recursions when a new node event is thrown if(!m_BlockEvents) { m_BlockEvents = true; int row = 0; for(auto iter = m_NodesAndObserverTags.begin(); iter != m_NodesAndObserverTags.end(); ++iter, ++row) { if (iter->first == node) { // node found, remove it beginRemoveRows(QModelIndex(), row, row); RemoveNodeFromInternalList(iter->first); endRemoveRows(); break; } } } m_BlockEvents = false; } void QmitkDataStorageListModel::OnDataNodeModified( const itk::Object *caller, const itk::EventObject & /*event*/ ) { if(m_BlockEvents) return; const mitk::DataNode* modifiedNode = dynamic_cast(caller); if(modifiedNode) { QModelIndex changedIndex = getIndex(modifiedNode); if (changedIndex.isValid()) { emit dataChanged(changedIndex, changedIndex); } } } void QmitkDataStorageListModel::OnDataStorageDeleted( const itk::Object *caller, const itk::EventObject & /*event*/ ) { if(m_BlockEvents) return; // set data storage to nullptr -> empty model this->SetDataStorage(nullptr); } mitk::DataNode::Pointer QmitkDataStorageListModel::getNode( const QModelIndex &index ) const { if(index.isValid()) { return m_NodesAndObserverTags.at(index.row()).first; } else { return nullptr; } } 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(); } diff --git a/Modules/QtWidgets/test/QmitkDataStorageListModelTest.cpp b/Modules/QtWidgets/test/QmitkDataStorageListModelTest.cpp new file mode 100644 index 0000000000..7fe9482fed --- /dev/null +++ b/Modules/QtWidgets/test/QmitkDataStorageListModelTest.cpp @@ -0,0 +1,107 @@ +/*=================================================================== + +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 +#include +#include +#include +#include + +#include +#include + +//! Tests for QmitkDataStorageListModel +//! +//! \todo could need quite some more tests, feel free to add test cases +class QmitkDataStorageListModelTestSuite : public mitk::TestFixture +{ + CPPUNIT_TEST_SUITE(QmitkDataStorageListModelTestSuite); + MITK_TEST(InstantiationTest); + MITK_TEST(FlexibleInputTest); + CPPUNIT_TEST_SUITE_END(); + + mitk::DataStorage::Pointer m_DataStorage; + +public: + + void setUp() override + { + m_DataStorage = mitk::StandaloneDataStorage::New(); + mitk::Image::Pointer image = mitk::Image::New(); + mitk::DataNode::Pointer imageNode = mitk::DataNode::New(); + imageNode->SetData(image); + + mitk::Surface::Pointer surface = mitk::Surface::New(); + mitk::DataNode::Pointer surfaceNode = mitk::DataNode::New(); + surfaceNode->SetData(surface); + + m_DataStorage->Add(imageNode); + m_DataStorage->Add(surfaceNode); + } + + void tearDown() override + { + } + + //! Test the c'tor variants + void InstantiationTest() + { + QmitkDataStorageListModel emptyModel; + CPPUNIT_ASSERT_EQUAL(0, emptyModel.rowCount()); + + QmitkDataStorageListModel unfilteredModel(m_DataStorage); + CPPUNIT_ASSERT_EQUAL(2, unfilteredModel.rowCount()); + + mitk::TNodePredicateDataType::Pointer imagePredicate = + mitk::TNodePredicateDataType::New(); + QmitkDataStorageListModel imageFilteredModel(m_DataStorage, imagePredicate.GetPointer()); + CPPUNIT_ASSERT_EQUAL(1, imageFilteredModel.rowCount()); + } + + //! Test that all combinations of + //! - data storage + //! - predicate + //! are evaluated, cause reasonable row numbers, and do not crash + void FlexibleInputTest() + { + QmitkDataStorageListModel model; + CPPUNIT_ASSERT_EQUAL(0, model.rowCount()); + CPPUNIT_ASSERT_EQUAL(std::size_t(0), model.GetDataNodes().size()); + + mitk::TNodePredicateDataType::Pointer imagePredicate = + mitk::TNodePredicateDataType::New(); + + model.SetPredicate(imagePredicate.GetPointer()); + CPPUNIT_ASSERT_EQUAL(0, model.rowCount()); + CPPUNIT_ASSERT_EQUAL(std::size_t(0), model.GetDataNodes().size()); + + model.SetDataStorage(m_DataStorage); + CPPUNIT_ASSERT_EQUAL(1, model.rowCount()); + CPPUNIT_ASSERT_EQUAL(std::size_t(1), model.GetDataNodes().size()); + + model.SetPredicate(nullptr); + CPPUNIT_ASSERT_EQUAL(2, model.rowCount()); + CPPUNIT_ASSERT_EQUAL(std::size_t(2), model.GetDataNodes().size()); + + model.SetDataStorage(nullptr); + CPPUNIT_ASSERT_EQUAL(0, model.rowCount()); + CPPUNIT_ASSERT_EQUAL(std::size_t(0), model.GetDataNodes().size()); + } + + +}; + +MITK_TEST_SUITE_REGISTRATION(QmitkDataStorageListModel) diff --git a/Modules/QtWidgets/test/files.cmake b/Modules/QtWidgets/test/files.cmake index 226473e131..e915c0c8b2 100644 --- a/Modules/QtWidgets/test/files.cmake +++ b/Modules/QtWidgets/test/files.cmake @@ -1,8 +1,13 @@ # test fails easily on MacOS, rarely on Windows, needs to be fixed before permanent activation (bug 15479) if (BUG_15479_FIXED) set(MODULE_TESTS QmitkThreadedLogTest.cpp ) endif() + + +set(MODULE_TESTS ${MODULE_TESTS} + QmitkDataStorageListModelTest.cpp +)