Get popup widget of a 3D view

Hi,

As I can see there is qMRMLThreeDViewControllerWidget.ui wich is popup widget when one clicks on pin button of a three D view.

I want to add there some buttons.

To do that I need to get instance of it qMRMLThreeDViewControllerWidget.

There is a method LayoutManager->threeDWidget(0)->threeDController() wich returns qMRMLThreeDViewControllerWidget* but this returns the whole 3D widget instead. There are also some other methods (displayed on the picture) but there is no one that would return me ctkPopupWidget (it is initialized in qMRMLViewControllerBar.cxx). Pin button is connected to the slot wich arises popup widget but I can’t see a way to get instance of it…

Was it done intensionally?

image

There are convenience methods for getting higher-level widgets and after that you can use standard Qt introspection to access lower-level widgets. For example, you can add a button to the view controller widget like this:

controllerWidget = slicer.app.layoutManager().threeDWidget(0).threeDController()
buttonLayout = slicer.util.findChild(controllerWidget, 'qMRMLThreeDViewControllerWidget').layout()

button = qt.QToolButton()
button.text="test"
buttonLayout.addWidget(button)

You can get a list of all child widgets of a widget by using slicer.util.findChildren():

>>> findChildren(controllerWidget)
[qMRMLThreeDViewControllerWidget(0x1a6d22e27a0) ,
ctkPopupWidget(0x1a6d2496ff0, name="qMRMLThreeDViewControllerWidget") ,
QToolButton(0x1a689cca530) ,
ctkButtonGroup (ctkButtonGroup at: 0x000001A6D245A870),
QActionGroup (QActionGroup at: 0x000001A6D245A370),
QMenu(0x1a6d2498830) ,
QAction(0x1a6d245a470 text="More" toolTip="More" menuRole=TextHeuristicRole visible=true) ,
QMenu(0x1a6d2498b70, name="rulerMenu") ,
QAction(0x1a6d245a3b0 text="" menuRole=TextHeuristicRole visible=true) ,
QAction(0x1a6d245a6d0 text="Ruler" toolTip="Ruler" menuRole=TextHeuristicRole visible=true) ,
... 
]
1 Like

Thank you very much! I always forget about slicer’s util class

Also probably we could add a method to controllerWidget wich returns ctkPopupWidget? That should be quite simple and useful I think…

I could make a PR if needed

By the way I just tested and to do almost the same in C++ I needed call findChild template with ctkPopupWidget* type:

  ctkPopupWidget* popupWidget = controller->
      findChild<ctkPopupWidget*>("qMRMLThreeDViewControllerWidget");

  QGridLayout* gridLayout = qobject_cast<QGridLayout*>(popupWidget->layout()); // don't know why but this returns me NULL

  QToolButton* cubeAxesToolBtn = new QToolButton(popupWidget);

//  gridLayout->addWidget(cubeAxesToolBtn, 0, 4);  // as I can't yet cast layout to gridLayout then I comment this
  popupWidget->layout()->addWidget(cubeAxesToolBtn);

image

How do you think if I create PR where I add a method getPopupWidget() to qMRMLThreeDViewControllerWidget that would return popupWidget?
Because it is very intuitive to have a guess that there should be some method that returns that popup widget. And if there is no such method then it is difficult to understand who is the parent and the type of that widget… As for me without you I would not solve that simple task :slight_smile:

The same I could do with qMRMLSliceControllerWidget.

We try not to expose too much of the low-level Slicer API because that would limits us in how we can add features without breaking backward compatibility. When you don’t find an accessor for some low-level features then it usually means “Do Not Open - No User Serviceable Parts Inside” (we don’t make a commitment to maintain the API, we don’t want to troubleshoot problems due to developers changing those parts, etc.). It happens sometimes that some APIs just has not yet been considered to be exposed (there was no need for it).

The popup widget is fairly high-level and stable (not expected to change a lot in the near future), so it might be OK to add an accessor for it, but I would rather do it if at the same time you introduce a mechanism that allows customization of the button list more cleanly. It does not make sense to remove low-level Qt method calls to get the widget, if you still need to execute a number of similarly low-level calls to make any changes in the buttons.

For example in my case I need to move some tollbuttons inside grid layout. I’m going to add a zoomer that zooms selected region rectangle (in VTK examples there is such tool). I would like to settle all zoom buttons near each other. To do that I’m going to use QLayout::replaceWidget() in pair with findChild() (I have not tried yet I think it should work) to find a specific tool button. I don’t know what mechanism could I introduce… Maybe you have some ideas/examples?

The API could be similar to how you can specify:

  • Segment Editor effects: you can register new effects, specify order of effects, and choose to show only those effects that you included in the ordered list
  • subject hierarchy view context menu items: you can register new actions, specify their “weight” that determines their position and grouping in the list, and you can provide a whitelist of actions using allowedViewContextMenuActionNames

You would need some kind of factory mechanism that would allow creating new actions in each 3D view automatically.

I think I’m not ready yet to implement that. Let’s pospone that…

How do I want to add a command to this button and where should I operate it?

I don’t know how to program.

I just want to add a simple command to the new button, such as click it ,then get setDataProbeVisible(0)

@slicer365 what would you like to achieve? Make Data probe hidden by default?

Yes, of course, I can add this code to the startup py file,

but when I use the slicer, I want to restart DataProbe,

I need to reopen the Python and the input code, which is a bit complicated,

I want to directly add the function to a small widget .

Would simply collapsing the Data Probe instead of hiding it solve the issue?

Maybe,

but there are many such small functions, such as setMenuBarsVisible(), setStatusBarVisible(0) and so on.

Seeing this discussion, I was wondering whether I can add this small piece of code to a certain button arbitrarily to achieve personalization

You can collapse the Data Probe by default by adding this to the application startup script:

findChild(mainWindow(), "DataProbeCollapsibleWidget").collapsed=True

If you find that the menu bar, status bar, etc. take up significant amount of space then it suggests that your application font is too large. You can set text scale in your operating system, or in Slicer application settings, or specify scaling by setting QT_SCALE_FACTOR environment variable (e.g., set QT_SCALE_FACTOR=0.75).

If you still want to hide all user interface elements then you are probably better off specifying a keyboard shortcut for it.

1 Like

Hi, sorry to interrupt. I wonder how to remove the existed widget/button under the pinButton? Thank you.

@joanne40226 hi,

There is a example of doing something similar, take a look

@keri
Thank you for your reply! However, I wonder how to remove the button “under” the pinButton, not the pintButton itself. I mean, i want some of the button “under” the pinButton to be removed and some added.

1 Like

I think you know that Slicer uses Qt for displaying GUI.
All you can see are Qt widgets (widget is some base class for any GUI element in Qt)
Qt GUI uses parent-child hierarchy (some kind of a tree structure I think).
So if you have instance of a parent object, there a good chances to find child needed.

To find child needed you need to know some information about the child widget (probably button in your case) that you want to get.
For example such information may be objectName, and/or the type of a widget (QPushButton, QToolButton etc, examples below).
Then you can use these information for example using the syntax slicer.util.findChild(parent ,“objectName”)
Or sliceController.findChild("QPushButton", "objectName") (slicerController is supposed the parent in that case).
Or probably sliceController.pinButton().findChild(...)

So the problem is how to get the information about the child?

For example I usually look for a tooltip (by pointing the mouse cursor) of a widget that is located “close” to the widget I want to get. Or if I already know that the widget is a part of some module then I go to source code of Slicer and in VSCode textual editor I paste the keyword (the tooltip for example).

Usually many Slicer’s widgets are “written” in QtDesigner thus you may want to find the .ui file containing some graphically displayed widgets. There you can find most of the information needed.

If you can’t find information about the child then you try to iterate the children and the if it fits you.
Or you may ask on the forum what exactly the widget you want to find, probably for somebody it is not a problem to remember the object name of this widget.

And one note: in Slicer’s python interpreter you can press TAB button of the keyboard to get the hints about the methods of an object.
For example:
sliceController.<press TAB> a popup window will be shown
or
dir(sliceController) will print the info

But the most powerful method is to use SlicerJupyter
There you can also use TAB or SHIFT-TAB to get hints. It is a good place to work/make mistakes/learn Slicer from python side

1 Like

Understood! Thank you so much.