diff --git a/Libs/MRML/Core/vtkMRMLFolderDisplayNode.cxx b/Libs/MRML/Core/vtkMRMLFolderDisplayNode.cxx index bfb30472050..8ad731fd9ce 100644 --- a/Libs/MRML/Core/vtkMRMLFolderDisplayNode.cxx +++ b/Libs/MRML/Core/vtkMRMLFolderDisplayNode.cxx @@ -23,6 +23,7 @@ // MRML includes #include "vtkMRMLDisplayableNode.h" +#include "vtkMRMLScene.h" #include "vtkMRMLSubjectHierarchyNode.h" // VTK includes @@ -198,6 +199,13 @@ void vtkMRMLFolderDisplayNode::ChildDisplayNodesModified() // Get items in branch std::vector childItemIDs; shNode->GetItemChildren(folderItemId, childItemIDs, true); + + bool batchProcessing = (childItemIDs.size() > 10); + if (batchProcessing) + { + this->GetScene()->StartState(vtkMRMLScene::BatchProcessState); + } + std::vector::iterator childIt; for (childIt=childItemIDs.begin(); childIt!=childItemIDs.end(); ++childIt) { @@ -217,12 +225,17 @@ void vtkMRMLFolderDisplayNode::ChildDisplayNodesModified() } } // For all display nodes } + + if (batchProcessing) + { + this->GetScene()->EndState(vtkMRMLScene::BatchProcessState); + } } //--------------------------------------------------------------------------- vtkMRMLDisplayNode* vtkMRMLFolderDisplayNode::GetOverridingHierarchyDisplayNode(vtkMRMLDisplayableNode* node) { - if (!node) + if (!node || !node->GetScene() || node->GetScene()->IsImporting()) { return nullptr; } diff --git a/Modules/Loadable/SubjectHierarchy/Widgets/qMRMLSubjectHierarchyModel.cxx b/Modules/Loadable/SubjectHierarchy/Widgets/qMRMLSubjectHierarchyModel.cxx index 2df34bcc802..2da5add4beb 100644 --- a/Modules/Loadable/SubjectHierarchy/Widgets/qMRMLSubjectHierarchyModel.cxx +++ b/Modules/Loadable/SubjectHierarchy/Widgets/qMRMLSubjectHierarchyModel.cxx @@ -278,7 +278,7 @@ void qMRMLSubjectHierarchyModel::setSubjectHierarchyNode(vtkMRMLSubjectHierarchy this->setColumnCount(oldColumnCount); // Update whole subject hierarchy - this->updateFromSubjectHierarchy(); + this->rebuildFromSubjectHierarchy(); if (shNode) { @@ -677,7 +677,7 @@ bool qMRMLSubjectHierarchyModel::dropMimeData( const QMimeData *data, Qt::DropAc } //------------------------------------------------------------------------------ -void qMRMLSubjectHierarchyModel::updateFromSubjectHierarchy() +void qMRMLSubjectHierarchyModel::rebuildFromSubjectHierarchy() { Q_D(qMRMLSubjectHierarchyModel); @@ -757,6 +757,50 @@ void qMRMLSubjectHierarchyModel::updateFromSubjectHierarchy() emit subjectHierarchyUpdated(); } +//------------------------------------------------------------------------------ +void qMRMLSubjectHierarchyModel::updateFromSubjectHierarchy() +{ + Q_D(qMRMLSubjectHierarchyModel); + + if (!d->SubjectHierarchyNode) + { + // Remove all items + const int oldColumnCount = this->columnCount(); + this->removeRows(0, this->rowCount()); + this->setColumnCount(oldColumnCount); + return; + } + else if (!this->subjectHierarchySceneItem()) + { + this->rebuildFromSubjectHierarchy(); + return; + } + else + { + // Update the scene item index in case subject hierarchy node has changed + this->subjectHierarchySceneItem()->setData( + QVariant::fromValue(d->SubjectHierarchyNode->GetSceneItemID()), qMRMLSubjectHierarchyModel::SubjectHierarchyItemIDRole ); + d->RowCache[d->SubjectHierarchyNode->GetSceneItemID()] = this->subjectHierarchySceneItem()->index(); + } + + + // Get all subject hierarchy items + std::vector allItemIDs; + d->SubjectHierarchyNode->GetItemChildren(d->SubjectHierarchyNode->GetSceneItemID(), allItemIDs, true); + + // Update expanded states (during inserting the update calls did not find valid indices, so + // expand and collapse statuses were not set in the tree view) + for (std::vector::iterator itemIt=allItemIDs.begin(); itemIt!=allItemIDs.end(); ++itemIt) + { + vtkIdType itemID = (*itemIt); + // Expanded states are handled with the name column + QStandardItem* item = this->itemFromSubjectHierarchyItem(itemID, this->nameColumn()); + this->updateItemDataFromSubjectHierarchyItem(item, itemID, this->nameColumn()); + } + + emit subjectHierarchyUpdated(); +} + //------------------------------------------------------------------------------ QStandardItem* qMRMLSubjectHierarchyModel::insertSubjectHierarchyItem(vtkIdType itemID) { @@ -897,6 +941,11 @@ void qMRMLSubjectHierarchyModel::updateItemDataFromSubjectHierarchyItem(QStandar qCritical() << Q_FUNC_INFO << ": Invalid subject hierarchy"; return; } + if (!item) + { + qCritical() << Q_FUNC_INFO << ": Invalid item"; + return; + } if (shItemID == d->SubjectHierarchyNode->GetSceneItemID()) { return; @@ -1432,7 +1481,7 @@ void qMRMLSubjectHierarchyModel::onSubjectHierarchyItemRemoved(vtkIdType removed { return; } - // The removed item may had children, if they haven't been updated, they are likely to be lost + // The removed item may have had children, if they haven't been updated, they are likely to be lost // (not reachable when browsing the model), we need to reparent them. foreach(QList orphans, d->Orphans) { @@ -1466,7 +1515,7 @@ void qMRMLSubjectHierarchyModel::onSubjectHierarchyItemModified(vtkIdType itemID void qMRMLSubjectHierarchyModel::onMRMLSceneImported(vtkMRMLScene* scene) { Q_UNUSED(scene); - this->updateFromSubjectHierarchy(); + this->rebuildFromSubjectHierarchy(); } //------------------------------------------------------------------------------ @@ -1670,7 +1719,7 @@ void qMRMLSubjectHierarchyModel::updateColumnCount() this->setColumnCount(max + 1); if (oldColumnCount == 0) { - this->updateFromSubjectHierarchy(); + this->rebuildFromSubjectHierarchy(); } else { diff --git a/Modules/Loadable/SubjectHierarchy/Widgets/qMRMLSubjectHierarchyModel.h b/Modules/Loadable/SubjectHierarchy/Widgets/qMRMLSubjectHierarchyModel.h index 778ca678ece..a7048ac2aa3 100644 --- a/Modules/Loadable/SubjectHierarchy/Widgets/qMRMLSubjectHierarchyModel.h +++ b/Modules/Loadable/SubjectHierarchy/Widgets/qMRMLSubjectHierarchyModel.h @@ -202,7 +202,13 @@ protected slots: /// Set the subject hierarchy node found in the given scene. Called only internally. virtual void setSubjectHierarchyNode(vtkMRMLSubjectHierarchyNode* shNode); + /// Rebuild model from scratch. + /// This is a hard-update that is uses more resources. Use sparingly. + virtual void rebuildFromSubjectHierarchy(); + /// Updates properties in the model based on subject hierarchy. + /// This is a soft update that is quick. Calls \sa rebuildFromSubjectHierarchy if necessary. virtual void updateFromSubjectHierarchy(); + virtual QStandardItem* insertSubjectHierarchyItem(vtkIdType itemID); virtual QStandardItem* insertSubjectHierarchyItem(vtkIdType itemID, QStandardItem* parent, int row=-1); diff --git a/Modules/Loadable/SubjectHierarchy/Widgets/qMRMLSubjectHierarchyTreeView.cxx b/Modules/Loadable/SubjectHierarchy/Widgets/qMRMLSubjectHierarchyTreeView.cxx index e0bd875f467..9879491700e 100644 --- a/Modules/Loadable/SubjectHierarchy/Widgets/qMRMLSubjectHierarchyTreeView.cxx +++ b/Modules/Loadable/SubjectHierarchy/Widgets/qMRMLSubjectHierarchyTreeView.cxx @@ -880,6 +880,15 @@ void qMRMLSubjectHierarchyTreeView::toggleSubjectHierarchyItemVisibility(vtkIdTy return; } + // If more than 10 item visibilities are changed, then enter in batch processing state + vtkNew childItemsList; + d->SubjectHierarchyNode->GetItemChildren(itemID, childItemsList, true); + bool batchProcessing = (childItemsList->GetNumberOfIds() > 10); + if (batchProcessing) + { + d->SubjectHierarchyNode->GetScene()->StartState(vtkMRMLScene::BatchProcessState); + } + qSlicerSubjectHierarchyAbstractPlugin* ownerPlugin = qSlicerSubjectHierarchyPluginHandler::instance()->getOwnerPluginForSubjectHierarchyItem(itemID); if (!ownerPlugin) @@ -891,6 +900,14 @@ void qMRMLSubjectHierarchyTreeView::toggleSubjectHierarchyItemVisibility(vtkIdTy int visible = (ownerPlugin->getDisplayVisibility(itemID) > 0 ? 0 : 1); ownerPlugin->setDisplayVisibility(itemID, visible); + + if (batchProcessing) + { + d->SubjectHierarchyNode->GetScene()->EndState(vtkMRMLScene::BatchProcessState); + } + + // Trigger view update for the modified item + d->SubjectHierarchyNode->ItemModified(itemID); } //------------------------------------------------------------------------------ @@ -1714,6 +1731,15 @@ void qMRMLSubjectHierarchyTreeView::onMRMLSceneCloseEnded(vtkObject* sceneObject return; } + Q_D(qMRMLSubjectHierarchyTreeView); + + // Remove selection + QList emptySelection; + this->setCurrentItems(emptySelection); + d->SelectedItems.clear(); + d->HighlightedItems.clear(); + + // Get new subject hierarchy node (or if not created yet then trigger creating it, because // scene close removed the pseudo-singleton subject hierarchy node), and set it to the tree view this->setSubjectHierarchyNode(vtkMRMLSubjectHierarchyNode::ResolveSubjectHierarchy(scene)); diff --git a/Modules/Loadable/SubjectHierarchy/Widgets/qSlicerSubjectHierarchyPluginHandler.cxx b/Modules/Loadable/SubjectHierarchy/Widgets/qSlicerSubjectHierarchyPluginHandler.cxx index 6e678cc302a..494e67343bd 100644 --- a/Modules/Loadable/SubjectHierarchy/Widgets/qSlicerSubjectHierarchyPluginHandler.cxx +++ b/Modules/Loadable/SubjectHierarchy/Widgets/qSlicerSubjectHierarchyPluginHandler.cxx @@ -340,7 +340,10 @@ qSlicerSubjectHierarchyAbstractPlugin* qSlicerSubjectHierarchyPluginHandler::get std::string ownerPluginName = this->m_MRMLScene->GetSubjectHierarchyNode()->GetItemOwnerPluginName(itemID); if (ownerPluginName.empty()) { - qCritical() << Q_FUNC_INFO << ": Item '" << this->m_MRMLScene->GetSubjectHierarchyNode()->GetItemName(itemID).c_str() << "' is not owned by any plugin!"; + if (itemID != this->m_MRMLScene->GetSubjectHierarchyNode()->GetSceneItemID()) + { + qCritical() << Q_FUNC_INFO << ": Item '" << this->m_MRMLScene->GetSubjectHierarchyNode()->GetItemName(itemID).c_str() << "' is not owned by any plugin!"; + } return nullptr; } diff --git a/Modules/Loadable/SubjectHierarchy/Widgets/qSlicerSubjectHierarchyPluginLogic.cxx b/Modules/Loadable/SubjectHierarchy/Widgets/qSlicerSubjectHierarchyPluginLogic.cxx index d164dad6061..eaee576de50 100644 --- a/Modules/Loadable/SubjectHierarchy/Widgets/qSlicerSubjectHierarchyPluginLogic.cxx +++ b/Modules/Loadable/SubjectHierarchy/Widgets/qSlicerSubjectHierarchyPluginLogic.cxx @@ -138,11 +138,16 @@ void qSlicerSubjectHierarchyPluginLogic::setMRMLScene(vtkMRMLScene* scene) // Connect scene node removed event so if the subject hierarchy node is removed, it is re-created and the hierarchy rebuilt qvtkReconnect( scene, vtkMRMLScene::NodeRemovedEvent, this, SLOT( onNodeRemoved(vtkObject*,vtkObject*) ) ); // Connect scene import ended event so that subject hierarchy items can be created for supported data nodes if missing (backwards compatibility) - qvtkReconnect( scene, vtkMRMLScene::EndImportEvent, this, SLOT( onSceneImportEnded(vtkObject*) ) ); + // Called with high priority so that it is processed here before the model is updated + qvtkReconnect( scene, vtkMRMLScene::EndImportEvent, this, SLOT( onSceneImportEnded(vtkObject*) ), 10.0 ); // Connect scene close ended event so that subject hierarchy can be cleared qvtkReconnect( scene, vtkMRMLScene::EndCloseEvent, this, SLOT( onSceneCloseEnded(vtkObject*) ) ); // Connect scene restore ended event so that restored subject hierarchy node containing only unresolved items can be resolved qvtkReconnect( scene, vtkMRMLScene::EndRestoreEvent, this, SLOT( onSceneRestoreEnded(vtkObject*) ) ); + // Connect scene batch process ended event so that subject hierarchy is updated after batch processing, when nodes + // may be added/removed without individual events. + // Called with high priority so that it is processed here before the model is updated + qvtkReconnect( scene, vtkMRMLScene::EndBatchProcessEvent, this, SLOT( onSceneBatchProcessEnded(vtkObject*) ), 10.0 ); } //----------------------------------------------------------------------------- @@ -301,6 +306,28 @@ void qSlicerSubjectHierarchyPluginLogic::onSceneImportEnded(vtkObject* sceneObje this->addSupportedDataNodesToSubjectHierarchy(); } +//----------------------------------------------------------------------------- +void qSlicerSubjectHierarchyPluginLogic::onSceneCloseEnded(vtkObject* sceneObject) +{ + vtkMRMLScene* scene = vtkMRMLScene::SafeDownCast(sceneObject); + if (!scene) + { + return; + } + + // Trigger creating new subject hierarchy node + // (scene close removed the pseudo-singleton subject hierarchy node) + vtkMRMLSubjectHierarchyNode* shNode = vtkMRMLSubjectHierarchyNode::ResolveSubjectHierarchy(scene); + if (!shNode) + { + qCritical() << Q_FUNC_INFO << ": There must be a subject hierarchy node in the scene"; + return; + } + + // Set subject hierarchy node to plugin handler + qSlicerSubjectHierarchyPluginHandler::instance()->observeSubjectHierarchyNode(shNode); +} + //----------------------------------------------------------------------------- void qSlicerSubjectHierarchyPluginLogic::onSceneRestoreEnded(vtkObject* sceneObject) { @@ -316,25 +343,44 @@ void qSlicerSubjectHierarchyPluginLogic::onSceneRestoreEnded(vtkObject* sceneObj } //----------------------------------------------------------------------------- -void qSlicerSubjectHierarchyPluginLogic::onSceneCloseEnded(vtkObject* sceneObject) +void qSlicerSubjectHierarchyPluginLogic::onSceneBatchProcessEnded(vtkObject* sceneObject) { vtkMRMLScene* scene = vtkMRMLScene::SafeDownCast(sceneObject); if (!scene) { return; } - - // Trigger creating new subject hierarchy node - // (scene close removed the pseudo-singleton subject hierarchy node) - vtkMRMLSubjectHierarchyNode* shNode = vtkMRMLSubjectHierarchyNode::ResolveSubjectHierarchy(scene); + vtkMRMLSubjectHierarchyNode* shNode = scene->GetSubjectHierarchyNode(); if (!shNode) { qCritical() << Q_FUNC_INFO << ": There must be a subject hierarchy node in the scene"; return; } - // Set subject hierarchy node to plugin handler - qSlicerSubjectHierarchyPluginHandler::instance()->observeSubjectHierarchyNode(shNode); + // Go through items, delete the ones that are not folders or virtual items and don't have data nodes + std::vector allItemIDs; + shNode->GetItemChildren(shNode->GetSceneItemID(), allItemIDs, true); + for (std::vector::iterator itemIt=allItemIDs.begin(); itemIt!=allItemIDs.end(); ++itemIt) + { + vtkIdType itemID = (*itemIt); + if (!shNode->GetItemLevel(itemID).empty()) + { + continue; // Folder type item + } + if ( shNode->HasItemAttribute( itemID, + vtkMRMLSubjectHierarchyConstants::GetSubjectHierarchyVirtualBranchAttributeName()) ) + { + continue; // In virtual branch + } + if (shNode->GetItemDataNode(itemID) == nullptr) + { + shNode->RemoveItem(itemID, false, false); + } + } + + // Add data nodes that are supported (i.e. there is a plugin that can claim it) and were not + // in the imported subject hierarchy node to subject hierarchy + this->addSupportedDataNodesToSubjectHierarchy(); } //----------------------------------------------------------------------------- diff --git a/Modules/Loadable/SubjectHierarchy/Widgets/qSlicerSubjectHierarchyPluginLogic.h b/Modules/Loadable/SubjectHierarchy/Widgets/qSlicerSubjectHierarchyPluginLogic.h index fc084b1e11e..d3967f8b1c4 100644 --- a/Modules/Loadable/SubjectHierarchy/Widgets/qSlicerSubjectHierarchyPluginLogic.h +++ b/Modules/Loadable/SubjectHierarchy/Widgets/qSlicerSubjectHierarchyPluginLogic.h @@ -102,6 +102,9 @@ protected slots: /// Called when scene restore is finished. As the restored node contains only unresolved /// items, they need to be resolved when restoring ended void onSceneRestoreEnded(vtkObject* sceneObject); + /// Called when batch processing is ended. Subject hierarchy is updated after batch processing, + /// when nodes may be added/removed without individual events + void onSceneBatchProcessEnded(vtkObject* sceneObject); protected: QScopedPointer d_ptr;