Skip to content

Commit

Permalink
ENH: Improve empty folder visibility logic
Browse files Browse the repository at this point in the history
- The SH tree view saves the "current tree view" pointer in the SH plugin handler
- When the user creates a new folder, the folder plugin enables showing empty folders in the sort filter proxy model of the current tree view, and stores the tree view pointer in a list
- Right-clicking on scene or any item, the folder plugin shows a new "Show empty folders" action if the user has created a new folder from that tree view (i.e. the current tree view is in the list)
- Make the DICOM SH plugin behave the same way as the Folder plugin
- Fix issue in sort filter proxy model where the folder disappeared from Markups module when changing visibility. (If the ShowEmptyHierarchyItems flag is on, we expect all the folder type items to show up even if they do not have children or none of them are visible. However, since a vtkMRMLFolderDisplayNode type data node is added to the folder item after changing visibility, it may be filtered out by a node type filter, which is applied for example in the Markups tree view. In this case, the folder would only show up if any of its children were visible. This commit makes sure that if the ShowEmptyHierarchyItems flag is on, the data node type filter does not override it.)

Re Slicer#6596
  • Loading branch information
cpinter committed Sep 19, 2023
1 parent b1702a5 commit 5f7ae8d
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1001,10 +1001,19 @@ qMRMLSortFilterSubjectHierarchyProxyModel::AcceptType qMRMLSortFilterSubjectHier
}
}

// Hide hierarchy item if none of its children are accepted and the corresponding filter is turned on
if (!d->ShowEmptyHierarchyItems && (!dataNode || dataNode->IsA("vtkMRMLFolderDisplayNode")))
if (!dataNode || dataNode->IsA("vtkMRMLFolderDisplayNode"))
{
onlyAcceptIfAnyChildIsAccepted = true;
if (d->ShowEmptyHierarchyItems)
{
// Reset this flag if item is a hierarchy item and it should be shown even if empty.
// (The flag may have been set before if node type filter was set, which did not include the folder display node)
onlyAcceptIfAnyChildIsAccepted = false;
}
else
{
// Hide hierarchy item if none of its children are accepted and the corresponding filter is turned on
onlyAcceptIfAnyChildIsAccepted = true;
}
}

// If the visibility of an item depends on whether any of its children are shown, then evaluate that condition
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2633,6 +2633,9 @@ void qMRMLSubjectHierarchyTreeView::onCustomContextMenu(const QPoint& point)
}
}

// Save tree view for the plugins to access if needed
qSlicerSubjectHierarchyPluginHandler::instance()->setCurrentTreeView(this);

// Get subject hierarchy item at mouse click position
vtkIdType itemID = this->sortFilterProxyModel()->subjectHierarchyItemFromIndex(index);
// Populate context menu for the current item
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
#include "vtkMRMLSubjectHierarchyConstants.h"
#include "vtkMRMLSubjectHierarchyNode.h"

// SubjectHierarchy Plugins includes
// SubjectHierarchy Widgets includes
#include "qMRMLSortFilterSubjectHierarchyProxyModel.h"
#include "qMRMLSubjectHierarchyTreeView.h"
#include "qSlicerSubjectHierarchyPluginHandler.h"
#include "qSlicerSubjectHierarchyFolderPlugin.h"
#include "qSlicerSubjectHierarchyDefaultPlugin.h"
Expand Down Expand Up @@ -63,8 +65,10 @@ class qSlicerSubjectHierarchyFolderPluginPrivate: public QObject
QAction* CreateFolderUnderSceneAction;
QAction* CreateFolderUnderNodeAction;
QAction* ApplyColorToBranchAction;
QAction* ShowEmptyFoldersAction;

QString AddedByFolderPluginAttributeName;
QList<qMRMLSubjectHierarchyTreeView*> TreeViewsToShowEmptyFolderAction;
};

//-----------------------------------------------------------------------------
Expand All @@ -79,6 +83,7 @@ qSlicerSubjectHierarchyFolderPluginPrivate::qSlicerSubjectHierarchyFolderPluginP
this->CreateFolderUnderSceneAction = nullptr;
this->CreateFolderUnderNodeAction = nullptr;
this->ApplyColorToBranchAction = nullptr;
this->ShowEmptyFoldersAction = nullptr;

std::string addedByFolderPluginAttributeNameStr =
vtkMRMLSubjectHierarchyConstants::GetSubjectHierarchyAttributePrefix()
Expand Down Expand Up @@ -108,6 +113,14 @@ void qSlicerSubjectHierarchyFolderPluginPrivate::init()
qSlicerSubjectHierarchyAbstractPlugin::SectionFolder, 7);
QObject::connect(this->ApplyColorToBranchAction, SIGNAL(toggled(bool)), q, SLOT(onApplyColorToBranchToggled(bool)));
this->ApplyColorToBranchAction->setCheckable(true);

this->ShowEmptyFoldersAction = new QAction(qSlicerSubjectHierarchyFolderPlugin::tr("Show empty folders"), q);
this->ShowEmptyFoldersAction->setToolTip(
qSlicerSubjectHierarchyFolderPlugin::tr("If on, then folders that do not contain nodes (allowed by any filter) are shown, otherwise not"));
qSlicerSubjectHierarchyAbstractPlugin::setActionPosition(this->ShowEmptyFoldersAction,
qSlicerSubjectHierarchyAbstractPlugin::SectionFolder, 8);
QObject::connect(this->ShowEmptyFoldersAction, SIGNAL(toggled(bool)), q, SLOT(onShowEmptyFoldersToggled(bool)));
this->ShowEmptyFoldersAction->setCheckable(true);
}

//-----------------------------------------------------------------------------
Expand Down Expand Up @@ -384,7 +397,7 @@ QList<QAction*> qSlicerSubjectHierarchyFolderPlugin::itemContextMenuActions()con
Q_D(const qSlicerSubjectHierarchyFolderPlugin);

QList<QAction*> actions;
actions << d->CreateFolderUnderNodeAction;
actions << d->CreateFolderUnderNodeAction << d->ShowEmptyFoldersAction;
return actions;
}

Expand All @@ -394,7 +407,7 @@ QList<QAction*> qSlicerSubjectHierarchyFolderPlugin::sceneContextMenuActions()co
Q_D(const qSlicerSubjectHierarchyFolderPlugin);

QList<QAction*> actions;
actions << d->CreateFolderUnderSceneAction << d->ApplyColorToBranchAction;
actions << d->CreateFolderUnderSceneAction << d->ApplyColorToBranchAction << d->ShowEmptyFoldersAction;
return actions;
}

Expand All @@ -414,13 +427,20 @@ void qSlicerSubjectHierarchyFolderPlugin::showContextMenuActionsForItem(vtkIdTyp
if (itemID == shNode->GetSceneItemID())
{
d->CreateFolderUnderSceneAction->setVisible(true);

// Show the Show empty folder action if the current tree view has created a folder before. Update checked state if visible.
this->updateShowEmptyFoldersAction();

return;
}

// Folder can be created under any node
if (itemID)
{
// Folder can be created under any node
d->CreateFolderUnderNodeAction->setVisible(true);

// Show the Show empty folder action if the current tree view has created a folder before. Update checked state if visible.
this->updateShowEmptyFoldersAction();
}

// Folder
Expand All @@ -435,6 +455,26 @@ void qSlicerSubjectHierarchyFolderPlugin::showContextMenuActionsForItem(vtkIdTyp
}
}

//---------------------------------------------------------------------------
void qSlicerSubjectHierarchyFolderPlugin::updateShowEmptyFoldersAction()
{
Q_D(const qSlicerSubjectHierarchyFolderPlugin);

qMRMLSubjectHierarchyTreeView* currentTreeView = qSlicerSubjectHierarchyPluginHandler::instance()->currentTreeView();
if (currentTreeView)
{
qMRMLSortFilterSubjectHierarchyProxyModel* sortFilterProxyModel = currentTreeView->sortFilterProxyModel();
d->ShowEmptyFoldersAction->blockSignals(true);
d->ShowEmptyFoldersAction->setChecked(sortFilterProxyModel->showEmptyHierarchyItems());
d->ShowEmptyFoldersAction->blockSignals(false);
d->ShowEmptyFoldersAction->setVisible(d->TreeViewsToShowEmptyFolderAction.contains(currentTreeView));
}
else
{
d->ShowEmptyFoldersAction->setVisible(false);
}
}

//---------------------------------------------------------------------------
QList<QAction*> qSlicerSubjectHierarchyFolderPlugin::visibilityContextMenuActions()const
{
Expand Down Expand Up @@ -482,6 +522,7 @@ void qSlicerSubjectHierarchyFolderPlugin::editProperties(vtkIdType itemID)
//---------------------------------------------------------------------------
vtkIdType qSlicerSubjectHierarchyFolderPlugin::createFolderUnderItem(vtkIdType parentItemID)
{
Q_D(qSlicerSubjectHierarchyFolderPlugin);
vtkMRMLScene* scene = qSlicerSubjectHierarchyPluginHandler::instance()->mrmlScene();
if (!scene)
{
Expand All @@ -502,6 +543,15 @@ vtkIdType qSlicerSubjectHierarchyFolderPlugin::createFolderUnderItem(vtkIdType p
vtkIdType childItemID = shNode->CreateFolderItem(parentItemID, name);
emit requestExpandItem(childItemID);

// Make sure empty folders are shown in the tree view it was created in
qMRMLSubjectHierarchyTreeView* currentTreeView = qSlicerSubjectHierarchyPluginHandler::instance()->currentTreeView();
if (currentTreeView)
{
qMRMLSortFilterSubjectHierarchyProxyModel* sortFilterProxyModel = currentTreeView->sortFilterProxyModel();
sortFilterProxyModel->setShowEmptyHierarchyItems(true);
this->emptyFolderCreatedFromTreeView(currentTreeView);
}

return childItemID;
}

Expand Down Expand Up @@ -550,6 +600,20 @@ void qSlicerSubjectHierarchyFolderPlugin::onApplyColorToBranchToggled(bool on)
this->setApplyColorToBranchEnabledForItem(currentItemID, on);
}

//-----------------------------------------------------------------------------
void qSlicerSubjectHierarchyFolderPlugin::onShowEmptyFoldersToggled(bool on)
{
qMRMLSubjectHierarchyTreeView* currentTreeView = qSlicerSubjectHierarchyPluginHandler::instance()->currentTreeView();
if (!currentTreeView)
{
qCritical() << Q_FUNC_INFO << ": Failed to access current subject hierarchy tree view";
return;
}

qMRMLSortFilterSubjectHierarchyProxyModel* sortFilterProxyModel = currentTreeView->sortFilterProxyModel();
sortFilterProxyModel->setShowEmptyHierarchyItems(on);
}

//-----------------------------------------------------------------------------
vtkMRMLDisplayNode* qSlicerSubjectHierarchyFolderPlugin::displayNodeForItem(vtkIdType itemID)const
{
Expand Down Expand Up @@ -663,3 +727,15 @@ void qSlicerSubjectHierarchyFolderPlugin::setApplyColorToBranchEnabledForItem(vt

folderDisplayNode->SetApplyDisplayPropertiesOnBranch(enabled);
}

//-----------------------------------------------------------------------------
void qSlicerSubjectHierarchyFolderPlugin::emptyFolderCreatedFromTreeView(qMRMLSubjectHierarchyTreeView* treeView)
{
Q_D(qSlicerSubjectHierarchyFolderPlugin);

if (!d->TreeViewsToShowEmptyFolderAction.contains(treeView))
{
// Only add the tree view in the list if not already there
d->TreeViewsToShowEmptyFolderAction.push_back(treeView);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
// CTK includes
#include <ctkVTKObject.h>

class qMRMLSubjectHierarchyTreeView;
class qSlicerSubjectHierarchyFolderPluginPrivate;

class vtkMRMLDisplayNode;
Expand Down Expand Up @@ -149,6 +150,10 @@ class Q_SLICER_MODULE_SUBJECTHIERARCHY_WIDGETS_EXPORT qSlicerSubjectHierarchyFol
/// node, then create a display node associated to that. Otherwise create display node for folder item
vtkMRMLDisplayNode* createDisplayNodeForItem(vtkIdType itemID);

/// Add tree view to the list of view from which empty folders have been created.
/// This function is called from the DICOM plugin, which can create patient and study items (which are special folders).
void emptyFolderCreatedFromTreeView(qMRMLSubjectHierarchyTreeView* treeView);

protected slots:
/// Create folder node under the scene
void createFolderUnderScene();
Expand All @@ -159,6 +164,9 @@ protected slots:
/// Toggle apply color to branch
void onApplyColorToBranchToggled(bool);

/// Toggle empty folder visibility (\sa showEmptyHierarchyItems) in the sort filter proxy model of the current tree view.
void onShowEmptyFoldersToggled(bool);

protected:
/// Retrieve model display node for given item. If the folder item has an associated model display
/// node (created by the plugin), then return that. Otherwise see if it has a model hierarchy node
Expand All @@ -170,6 +178,10 @@ protected slots:
/// Determine if apply color to branch option is enabled to a given item or not
void setApplyColorToBranchEnabledForItem(vtkIdType itemID, bool enabled);

/// Update show empty folders context menu item visibility and checked state.
/// The update needs to happen in more than one place, which is handled by this method.
void updateShowEmptyFoldersAction();

protected:
QScopedPointer<qSlicerSubjectHierarchyFolderPluginPrivate> d_ptr;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ void qSlicerSubjectHierarchyPluginHandler::setCurrentItem(vtkIdType itemID)
}

//-----------------------------------------------------------------------------
vtkIdType qSlicerSubjectHierarchyPluginHandler::currentItem()
vtkIdType qSlicerSubjectHierarchyPluginHandler::currentItem()const
{
if (this->m_CurrentItems.size() != 1)
{
Expand All @@ -502,13 +502,13 @@ void qSlicerSubjectHierarchyPluginHandler::setCurrentItems(QList<vtkIdType> item
}

//-----------------------------------------------------------------------------
QList<vtkIdType> qSlicerSubjectHierarchyPluginHandler::currentItems()
QList<vtkIdType> qSlicerSubjectHierarchyPluginHandler::currentItems()const
{
return this->m_CurrentItems;
}

//------------------------------------------------------------------------------
void qSlicerSubjectHierarchyPluginHandler::currentItems(vtkIdList* selectedItems)
void qSlicerSubjectHierarchyPluginHandler::currentItems(vtkIdList* selectedItems)const
{
if (!selectedItems)
{
Expand All @@ -522,6 +522,18 @@ void qSlicerSubjectHierarchyPluginHandler::currentItems(vtkIdList* selectedItems
}
}

//-----------------------------------------------------------------------------
void qSlicerSubjectHierarchyPluginHandler::setCurrentTreeView(qMRMLSubjectHierarchyTreeView* treeView)
{
this->m_CurrentTreeView = treeView;
}

//-----------------------------------------------------------------------------
qMRMLSubjectHierarchyTreeView* qSlicerSubjectHierarchyPluginHandler::currentTreeView()const
{
return this->m_CurrentTreeView;
}

//-----------------------------------------------------------------------------
bool qSlicerSubjectHierarchyPluginHandler::autoDeleteSubjectHierarchyChildren()const
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@

class vtkMRMLScene;
class vtkCallbackCommand;
class qMRMLSubjectHierarchyTreeView;
class qSlicerSubjectHierarchyPluginLogic;
class qSlicerSubjectHierarchyAbstractPlugin;
class qSlicerSubjectHierarchyDefaultPlugin;
Expand Down Expand Up @@ -106,7 +107,7 @@ class Q_SLICER_MODULE_SUBJECTHIERARCHY_WIDGETS_EXPORT qSlicerSubjectHierarchyPlu
/// IMPORTANT NOTE: This function is solely used for plugin-provided context menus. This is NOT to be used for getting the
/// selected item of individual widgets (tree views, comboboxes).
/// \return Current item if only one is selected, otherwise INVALID_ITEM_ID
Q_INVOKABLE vtkIdType currentItem();
Q_INVOKABLE vtkIdType currentItem()const;

/// Set current subject hierarchy items in case of multi-selection
/// IMPORTANT NOTE: This function will not change the selection in individual widgets (tree views, comboboxes). This is
Expand All @@ -116,8 +117,14 @@ class Q_SLICER_MODULE_SUBJECTHIERARCHY_WIDGETS_EXPORT qSlicerSubjectHierarchyPlu
/// Get current subject hierarchy items in case of multi-selection
/// IMPORTANT NOTE: This function is solely used for plugin-provided context menus. This is NOT to be used for getting the
/// selected items of individual widgets (tree views, comboboxes).
Q_INVOKABLE QList<vtkIdType> currentItems();
Q_INVOKABLE void currentItems(vtkIdList* selectedItems);
Q_INVOKABLE QList<vtkIdType> currentItems()const;
Q_INVOKABLE void currentItems(vtkIdList* selectedItems)const;

/// Set current tree view. The tree view where context menu was requested sets this.
/// It allows accessing the tree view the user is using.
Q_INVOKABLE void setCurrentTreeView(qMRMLSubjectHierarchyTreeView* treeView);
/// Get current tree view.
Q_INVOKABLE qMRMLSubjectHierarchyTreeView* currentTreeView()const;

Q_INVOKABLE bool autoDeleteSubjectHierarchyChildren()const;
Q_INVOKABLE void setAutoDeleteSubjectHierarchyChildren(bool flag);
Expand Down Expand Up @@ -217,6 +224,9 @@ class Q_SLICER_MODULE_SUBJECTHIERARCHY_WIDGETS_EXPORT qSlicerSubjectHierarchyPlu
/// (selected items in the tree view e.g. for context menu request)
QList<vtkIdType> m_CurrentItems;

/// Most recently right-clicked subject hierarchy tree view
qMRMLSubjectHierarchyTreeView* m_CurrentTreeView{nullptr};

/// MRML scene (to get new subject hierarchy node if the stored one is deleted)
vtkWeakPointer<vtkMRMLScene> m_MRMLScene;

Expand Down

0 comments on commit 5f7ae8d

Please sign in to comment.