Commit cf8212ba authored by finetjul's avatar finetjul

ENH: Speed-up qMRMLSceneModel node observation

Don't listen to nodes that are filtered out by the sort&filter proxy model.
Issue #2642

Example of performance on MacBookPro laptop:
AddData (BrainAtlas2012.mrb) took 22741 msecs -10000msecs
CloseScene () took 11827 msecs  -10000msecs
AddData (BrainAtlas2012.mrb) took 20909 msecs
ModifyNode (vtkMRMLScalarVolumeNode1) took 4 msecs
ModifyNode (vtkMRMLScalarVolumeNode2) took 24 msecs
ModifyNode (vtkMRMLScalarVolumeDisplayNode2) took 22 msecs
ModifyNode (vtkMRMLScalarVolumeNode3) took 15 msecs
ModifyNode (vtkMRMLModelHierarchyNode2) took 10 msecs
ModifyNode (vtkMRMLModelNode4) took 0 msecs
ModifyNode (vtkMRMLModelDisplayNode5) took 11 msecs
ModifyNode (vtkMRMLModelHierarchyNode3) took 10 msecs
ModifyNode (vtkMRMLModelStorageNode1) took 0 msecs
AddNode (vtkMRMLModelNode302) took 20 msecs
Layout (3) took 51 msecs
Layout (2) took 24 msecs
RestoreSceneView (vtkMRMLSceneViewNode15) took 865 msecs  -1000msecs
RestoreSceneView (vtkMRMLSceneViewNode15) took 622 msecs

git-svn-id: http://svn.slicer.org/Slicer4/trunk@21586 3bd1e089-480b-0410-8dfb-8563597acbee
parent 73dfb761
......@@ -53,7 +53,7 @@ private slots:
void qMRMLSceneModelTester::testDefaults()
{
qMRMLSceneModel sceneModel;
QCOMPARE(sceneModel.listenNodeModifiedEvent(), true);
QCOMPARE(sceneModel.listenNodeModifiedEvent(), qMRMLSceneModel::OnlyVisibleNodes);
QCOMPARE(sceneModel.lazyUpdate(), false);
QCOMPARE(sceneModel.nameColumn(), 0);
QCOMPARE(sceneModel.idColumn(), -1);
......@@ -71,11 +71,11 @@ void qMRMLSceneModelTester::testSetsAndGets()
{
qMRMLSceneModel sceneModel;
sceneModel.setListenNodeModifiedEvent(false);
QCOMPARE(sceneModel.listenNodeModifiedEvent(), false);
sceneModel.setListenNodeModifiedEvent(qMRMLSceneModel::NoNodes);
QCOMPARE(sceneModel.listenNodeModifiedEvent(), qMRMLSceneModel::NoNodes);
sceneModel.setListenNodeModifiedEvent(true);
QCOMPARE(sceneModel.listenNodeModifiedEvent(), true);
sceneModel.setListenNodeModifiedEvent(qMRMLSceneModel::AllNodes);
QCOMPARE(sceneModel.listenNodeModifiedEvent(), qMRMLSceneModel::AllNodes);
sceneModel.setLazyUpdate(true);
QCOMPARE(sceneModel.lazyUpdate(), true);
......
......@@ -116,7 +116,6 @@ int qMRMLTreeViewEventTranslatorPlayerTest1(int argc, char * argv [] )
modelNode2->SetAndObserveDisplayNodeID(displayModelNode2->GetID());
widget2.setSceneModelType("ModelHierarchy");
widget2.setListenNodeModifiedEvent(true);
widget2.setMRMLScene(scene2);
QAction* insertTransformAction = new QAction("Insert transform", 0);
......
......@@ -94,7 +94,6 @@ void qMRMLNodeComboBoxPrivate::init(QAbstractItemModel* model)
rootModel = qobject_cast<QAbstractProxyModel*>(rootModel)->sourceModel();
}
this->MRMLSceneModel = qobject_cast<qMRMLSceneModel*>(rootModel);
this->MRMLSceneModel->setListenNodeModifiedEvent(true);
Q_ASSERT(this->MRMLSceneModel);
// no need to reset the root model index here as the model is not yet set
this->updateNoneItem(false);
......
......@@ -40,9 +40,11 @@
qMRMLSceneModelPrivate::qMRMLSceneModelPrivate(qMRMLSceneModel& object)
: q_ptr(&object)
{
qRegisterMetaType<qMRMLSceneModel::NodeTypes>("qMRMLSceneModel::NodeTypes");
this->CallBack = vtkSmartPointer<vtkCallbackCommand>::New();
this->LazyUpdate = false;
this->ListenNodeModifiedEvent = false;
this->ListenNodeModifiedEvent = qMRMLSceneModel::NoNodes;
this->PendingItemModified = -1; // -1 means not updating
this->NameColumn = -1;
......@@ -82,7 +84,7 @@ void qMRMLSceneModelPrivate::init()
q, SLOT(onItemChanged(QStandardItem*)));
q->setNameColumn(0);
q->setListenNodeModifiedEvent(true);
q->setListenNodeModifiedEvent(qMRMLSceneModel::OnlyVisibleNodes);
}
//------------------------------------------------------------------------------
......@@ -125,7 +127,7 @@ void qMRMLSceneModelPrivate::listenNodeModifiedEvent()
{
vtkMRMLNode* node = q->mrmlNodeFromIndex(sceneIndex.child(i,0));
q->qvtkDisconnect(node, vtkCommand::NoEvent, q, 0);
if (this->ListenNodeModifiedEvent)
if (this->ListenNodeModifiedEvent == qMRMLSceneModel::AllNodes)
{
q->observeNode(node);
}
......@@ -560,7 +562,7 @@ bool qMRMLSceneModel
}
//------------------------------------------------------------------------------
void qMRMLSceneModel::setListenNodeModifiedEvent(bool listen)
void qMRMLSceneModel::setListenNodeModifiedEvent(qMRMLSceneModel::NodeTypes listen)
{
Q_D(qMRMLSceneModel);
if (d->ListenNodeModifiedEvent == listen)
......@@ -572,7 +574,7 @@ void qMRMLSceneModel::setListenNodeModifiedEvent(bool listen)
}
//------------------------------------------------------------------------------
bool qMRMLSceneModel::listenNodeModifiedEvent()const
qMRMLSceneModel::NodeTypes qMRMLSceneModel::listenNodeModifiedEvent()const
{
Q_D(const qMRMLSceneModel);
return d->ListenNodeModifiedEvent;
......@@ -790,7 +792,7 @@ QStandardItem* qMRMLSceneModel::insertNode(vtkMRMLNode* node, QStandardItem* par
this->insertRow(row,items);
}
// TODO: don't listen to nodes that are hidden from editors ?
if (d->ListenNodeModifiedEvent)
if (d->ListenNodeModifiedEvent == AllNodes)
{
this->observeNode(node);
}
......@@ -1132,8 +1134,8 @@ void qMRMLSceneModel::onMRMLSceneNodeAboutToBeRemoved(vtkMRMLScene* scene, vtkMR
qvtkDisconnect(node, vtkCommand::ModifiedEvent,
this, SLOT(onMRMLNodeModified(vtkObject*)));
Q_ASSERT_X((!d->ListenNodeModifiedEvent && connectionsRemoved == 0) ||
(d->ListenNodeModifiedEvent && connectionsRemoved == 1),
Q_ASSERT_X(((d->ListenNodeModifiedEvent == NoNodes) && connectionsRemoved == 0) ||
(d->ListenNodeModifiedEvent != NoNodes && connectionsRemoved <= 1),
"qMRMLSceneModel::onMRMLSceneNodeAboutToBeRemoved()",
"A node has been removed from the scene but the scene model has "
"never been notified it has been added in the first place. Maybe"
......
......@@ -60,12 +60,14 @@ class QMRML_WIDGETS_EXPORT qMRMLSceneModel : public QStandardItemModel
Q_OBJECT
QVTK_OBJECT
Q_ENUMS(NodeTypes)
/// This property controls whether to observe or not the modified event of
/// the node and update the node item data accordingly.
/// It can be useful when the modified property is displayed
/// (name, id, visibility...)
/// True by default
Q_PROPERTY (bool listenNodeModifiedEvent READ listenNodeModifiedEvent WRITE setListenNodeModifiedEvent)
/// OnlyVisibleNodes by default
Q_PROPERTY (NodeTypes listenNodeModifiedEvent READ listenNodeModifiedEvent WRITE setListenNodeModifiedEvent)
/// Control wether the model actively listens to the scene.
/// If LazyUpdate is true, the model ignores added node events when the
......@@ -97,7 +99,14 @@ public:
qMRMLSceneModel(QObject *parent=0);
virtual ~qMRMLSceneModel();
enum ItemDataRole{
enum NodeTypes
{
NoNodes = 0,
AllNodes,
OnlyVisibleNodes
};
enum ItemDataRole
{
/// Unique ID of the item. For nodes, it is the node ID.
UIDRole = Qt::UserRole + 1,
/// Pointer (as long long) of the item if it is a scene or a node.
......@@ -134,8 +143,11 @@ public:
/// Option that activates the expensive listening of the vtkMRMLNode Modified
/// events. When listening, the signal itemDataChanged() is fired when a
/// vtkMRMLNode is modified.
void setListenNodeModifiedEvent(bool listen);
bool listenNodeModifiedEvent()const;
/// \sa listenNodeModifiedEvent, listenNodeModifiedEvent()
void setListenNodeModifiedEvent(NodeTypes nodesToListen);
/// Get the types of nodes that are observed.
/// \sa listenNodeModifiedEvent, setListenNodeModifiedEvent()
NodeTypes listenNodeModifiedEvent()const;
bool lazyUpdate()const;
void setLazyUpdate(bool lazy);
......@@ -201,6 +213,10 @@ public:
/// \sa isParentNode()
bool isAffiliatedNode(vtkMRMLNode* nodeA, vtkMRMLNode* nodeB)const;
/// Observe node and update item when the node is modified.
/// \sa listenNodeModifiedEvent
virtual void observeNode(vtkMRMLNode* node);
protected slots:
virtual void onMRMLSceneNodeAboutToBeAdded(vtkMRMLScene* scene, vtkMRMLNode* node);
......@@ -251,7 +267,6 @@ protected:
virtual void populateScene();
virtual QStandardItem* insertNode(vtkMRMLNode* node);
virtual QStandardItem* insertNode(vtkMRMLNode* node, QStandardItem* parent, int row = -1);
virtual void observeNode(vtkMRMLNode* node);
virtual bool isANode(const QStandardItem* item)const;
virtual QFlags<Qt::ItemFlag> nodeFlags(vtkMRMLNode* node, int column)const;
......@@ -318,6 +333,7 @@ private:
Q_DECLARE_PRIVATE(qMRMLSceneModel);
Q_DISABLE_COPY(qMRMLSceneModel);
};
Q_DECLARE_METATYPE(qMRMLSceneModel::NodeTypes)
void printStandardItem(QStandardItem* item, const QString& offset);
......
......@@ -60,7 +60,7 @@ public:
void reparentItems(QList<QStandardItem*>& children, int newIndex, QStandardItem* newParent);
vtkSmartPointer<vtkCallbackCommand> CallBack;
bool ListenNodeModifiedEvent;
qMRMLSceneModel::NodeTypes ListenNodeModifiedEvent;
bool LazyUpdate;
int PendingItemModified;
......
......@@ -58,33 +58,15 @@ qMRMLSortFilterHierarchyProxyModel::~qMRMLSortFilterHierarchyProxyModel()
}
//------------------------------------------------------------------------------
bool qMRMLSortFilterHierarchyProxyModel
::filterAcceptsRow(int source_row, const QModelIndex &source_parent)const
qMRMLSortFilterProxyModel::AcceptType qMRMLSortFilterHierarchyProxyModel
::filterAcceptsNode(vtkMRMLNode* node)const
{
//Q_D(const qMRMLSortFilterHierarchyProxyModel);
bool res = this->Superclass::filterAcceptsRow(source_row, source_parent);
if (!res)
AcceptType res = this->Superclass::filterAcceptsNode(node);
if (res == Accept || res == AcceptButPotentiallyRejectable)
{
return res;
}
// shouldn't fail because Superclass::filterAcceptsRow returned true
QStandardItem* parentItem = this->sourceItem(source_parent);
Q_ASSERT(parentItem);
// shouldn't fail because Superclass::filterAcceptsRow returned true
QStandardItem* item = 0;
// Sometimes the row is not complete, search for a non null item
for (int childIndex = 0; childIndex < parentItem->columnCount(); ++childIndex)
{
item = parentItem->child(source_row, childIndex);
if (item)
{
break;
}
}
Q_ASSERT(item);
qMRMLSceneModel* sceneModel = qobject_cast<qMRMLSceneModel*>(
this->sourceModel());
vtkMRMLNode* node = sceneModel->mrmlNodeFromItem(item);
vtkMRMLHierarchyNode* hNode = vtkMRMLHierarchyNode::SafeDownCast(node);
if (!hNode)
{
......@@ -95,7 +77,7 @@ bool qMRMLSortFilterHierarchyProxyModel
// vtkMRMLHierarchyNode (tree parent) or empty (tree parent to be)
if (hNode->GetAssociatedNode())
{
return false;
return RejectButPotentiallyAcceptable;
}
return res;
}
......@@ -40,7 +40,7 @@ protected:
// Don't show vtkMRMLHierarchyNode if they are tied to a vtkMRMLModelNode
// The only vtkMRMLHierarchyNode to display are the ones who reference other
// vtkMRMLHierarchyNode (tree parent) or empty (tree parent to be)
virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent)const;
virtual AcceptType filterAcceptsNode(vtkMRMLNode* node)const;
protected:
QScopedPointer<qMRMLSortFilterHierarchyProxyModelPrivate> d_ptr;
......
......@@ -145,7 +145,6 @@ void qMRMLSortFilterProxyModel::addAttribute(const QString& nodeType,
//------------------------------------------------------------------------------
bool qMRMLSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent)const
{
Q_D(const qMRMLSortFilterProxyModel);
QStandardItem* parentItem = this->sourceItem(source_parent);
if (parentItem == 0)
{
......@@ -169,21 +168,43 @@ bool qMRMLSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelInd
}
qMRMLSceneModel* sceneModel = qobject_cast<qMRMLSceneModel*>(this->sourceModel());
vtkMRMLNode* node = sceneModel->mrmlNodeFromItem(item);
AcceptType accept = this->filterAcceptsNode(node);
bool acceptRow = (accept == Accept);
if (accept == AcceptButPotentiallyRejectable)
{
acceptRow = this->QSortFilterProxyModel::filterAcceptsRow(source_row,
source_parent);
}
if (node &&
sceneModel->listenNodeModifiedEvent() == qMRMLSceneModel::OnlyVisibleNodes &&
accept != Reject)
{
sceneModel->observeNode(node);
}
return acceptRow;
}
//------------------------------------------------------------------------------
qMRMLSortFilterProxyModel::AcceptType qMRMLSortFilterProxyModel
::filterAcceptsNode(vtkMRMLNode* node)const
{
Q_D(const qMRMLSortFilterProxyModel);
qMRMLSceneModel* sceneModel = qobject_cast<qMRMLSceneModel*>(this->sourceModel());
if (!node)
{
return true;
return Accept;
}
if (this->showAll())
{
return true;
return Accept;
}
if (this->hideAll())
{
return false;
return Reject;
}
if (d->HiddenNodeIDs.contains(node->GetID()))
{
return false;
return Reject;
}
// HideFromEditors property
if (!d->ShowHidden && node->GetHideFromEditors())
......@@ -199,7 +220,7 @@ bool qMRMLSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelInd
}
if (hide)
{
return false;
return Reject;
}
}
......@@ -210,7 +231,7 @@ bool qMRMLSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelInd
bool affiliated = sceneModel->isAffiliatedNode(node, theNode);
if (!affiliated)
{
return false;
return Reject;
}
}
......@@ -218,8 +239,7 @@ bool qMRMLSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelInd
if (d->NodeTypes.isEmpty())
{
// Apply filter if any
return this->QSortFilterProxyModel::filterAcceptsRow(source_row,
source_parent);
return AcceptButPotentiallyRejectable;
}
foreach(const QString& nodeType, d->NodeTypes)
{
......@@ -242,7 +262,7 @@ bool qMRMLSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelInd
{
if (node->IsA(hideChildNodeType.toAscii().data()))
{
return false;
return Reject;
}
}
}
......@@ -265,7 +285,7 @@ bool qMRMLSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelInd
// fail if the attribute isn't defined on the node at all
if (nodeAttribute == 0)
{
return false;
return RejectButPotentiallyAcceptable;
}
// if the filter value is null, any node attribute value will match
if (!d->Attributes[nodeType].second.isNull())
......@@ -273,15 +293,14 @@ bool qMRMLSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelInd
// otherwise, the node and filter attributes have to match
if (testAttribute != nodeAttribute)
{
return false;
return RejectButPotentiallyAcceptable;
}
}
}
// Apply filter if any
return this->QSortFilterProxyModel::filterAcceptsRow(source_row,
source_parent);
return AcceptButPotentiallyRejectable;
}
return false;
return Reject;
}
//-----------------------------------------------------------------------------
......
......@@ -207,11 +207,20 @@ public slots:
// TODO Add setMRMLScene() to propagate to the scene model
protected:
enum AcceptType
{
Reject = 0,
Accept,
RejectButPotentiallyAcceptable,
AcceptButPotentiallyRejectable,
};
//virtual bool filterAcceptsColumn(int source_column, const QModelIndex & source_parent)const;
virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent)const;
virtual AcceptType filterAcceptsNode(vtkMRMLNode* node)const;
//virtual bool lessThan(const QModelIndex &left, const QModelIndex &right)const;
QStandardItem* sourceItem(const QModelIndex& index)const;
protected:
QScopedPointer<qMRMLSortFilterProxyModelPrivate> d_ptr;
......
......@@ -382,7 +382,7 @@ void qMRMLTreeView::onCurrentRowChanged(const QModelIndex& index)
}
//------------------------------------------------------------------------------
void qMRMLTreeView::setListenNodeModifiedEvent(bool listen)
void qMRMLTreeView::setListenNodeModifiedEvent(qMRMLSceneModel::NodeTypes listen)
{
Q_D(qMRMLTreeView);
Q_ASSERT(d->SceneModel);
......@@ -390,10 +390,10 @@ void qMRMLTreeView::setListenNodeModifiedEvent(bool listen)
}
//------------------------------------------------------------------------------
bool qMRMLTreeView::listenNodeModifiedEvent()const
qMRMLSceneModel::NodeTypes qMRMLTreeView::listenNodeModifiedEvent()const
{
Q_D(const qMRMLTreeView);
return d->SceneModel ? d->SceneModel->listenNodeModifiedEvent() : true;
return d->SceneModel ? d->SceneModel->listenNodeModifiedEvent() : qMRMLSceneModel::OnlyVisibleNodes;
}
// --------------------------------------------------------------------------
......
......@@ -30,10 +30,10 @@ class QShowEvent;
#include <ctkVTKObject.h>
// MRMLWidgets includes
#include "qMRMLSceneModel.h"
#include "qMRMLSortFilterProxyModel.h"
#include "qMRMLWidgetsExport.h"
class qMRMLSceneModel;
class qMRMLTreeViewPrivate;
class vtkMRMLNode;
class vtkMRMLScene;
......@@ -60,8 +60,9 @@ class QMRML_WIDGETS_EXPORT qMRMLTreeView : public QTreeView
/// This property controls whether to actively listen to the nodes
/// to synchronize their representation. As it can be time consuming, you
/// can disable it if you want a lazy update.
/// True by default.
Q_PROPERTY(bool listenNodeModifiedEvent READ listenNodeModifiedEvent WRITE setListenNodeModifiedEvent)
/// OnlyVisibleNodes by default.
/// \sa qMRMLSceneModel::listenNodeModifiedEvent
Q_PROPERTY(qMRMLSceneModel::NodeTypes listenNodeModifiedEvent READ listenNodeModifiedEvent WRITE setListenNodeModifiedEvent)
/// This property controls which node types are visible in the view.
/// This behaves as a filter, the nodes that have a type not included in the
/// list will be hidden.
......@@ -133,10 +134,12 @@ public:
/// \sa setSceneModel()
QString sceneModelType()const;
/// \sa qMRMLSceneModel::setListenNodeModifiedEvent
void setListenNodeModifiedEvent(bool listen);
/// Set the node types on the scene model.
/// \sa listenNodeModifiedEvent qMRMLSceneModel::setListenNodeModifiedEvent
void setListenNodeModifiedEvent(qMRMLSceneModel::NodeTypes listen);
/// Get the node types of the scene model.
/// \sa qMRMLSceneModel::listenNodeModifiedEvent
bool listenNodeModifiedEvent()const;
qMRMLSceneModel::NodeTypes listenNodeModifiedEvent()const;
/// Customize the model
void setSceneModel(qMRMLSceneModel* newSceneModel, const QString& modelType);
......
......@@ -41,9 +41,6 @@
<property name="indentation">
<number>10</number>
</property>
<property name="listenNodeModifiedEvent">
<bool>true</bool>
</property>
<property name="editMenuActionVisible">
<bool>true</bool>
</property>
......
......@@ -55,9 +55,6 @@
<property name="sceneModelType">
<string>ModelHierarchy</string>
</property>
<property name="listenNodeModifiedEvent">
<bool>true</bool>
</property>
<property name="nodeTypes">
<stringlist>
<string>vtkMRMLModelHierarchyNode</string>
......
......@@ -78,7 +78,6 @@ qMRMLSceneTractographyDisplayModel::qMRMLSceneTractographyDisplayModel(QObject *
Q_D(qMRMLSceneTractographyDisplayModel);
d->init();
this->setListenNodeModifiedEvent(true);
this->setVisibilityColumn(-1);
this->setOpacityColumn(-1);
this->setCheckableColumn(-1);
......
......@@ -244,9 +244,6 @@
<property name="headerHidden">
<bool>true</bool>
</property>
<property name="listenNodeModifiedEvent">
<bool>true</bool>
</property>
<property name="nodeTypes">
<stringlist>
<string>vtkMRMLTransformableNode</string>
......@@ -290,9 +287,6 @@
<property name="headerHidden">
<bool>true</bool>
</property>
<property name="listenNodeModifiedEvent">
<bool>true</bool>
</property>
<property name="fitSizeToVisibleIndexes">
<bool>false</bool>
</property>
......
......@@ -22,7 +22,7 @@ if(NOT DEFINED OpenIGTLinkIF_SOURCE_DIR)
#message(STATUS "${__indent}Adding project ${proj}")
ExternalProject_Add(${proj}
SVN_REPOSITORY "http://svn.na-mic.org/NAMICSandBox/trunk/IGTLoadableModules/QtModules/OpenIGTLinkIF/"
SVN_REVISION -r "8022"
SVN_REVISION -r "8024"
"${slicer_external_update}"
#GIT_REPOSITORY "${git_protocol}://github.com/Slicer/OpenIGTLinkIF.git"
#GIT_TAG "8330b769cc8c607067134296d577b64ae7c92b87"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment