My QActions hangs the Slicer application

I’m creating 2 layouts for manual segmentation. And I’m trying to create QtAction s to trigger these layouts (slicerrc.py-file can be found here):

# XML definitions (definitions omitted)
LAYOUTXML_MANUALSEGMENTATION_3D = "..."
LAYOUTXML_MANUALSEGMENATION = "..."
# out-of-the-blue values
LAYOUTID_MANUALSEGMENTATION_3D = 440
LAYOUTID_MANUALSEGMENTATION    = 442


# switch layout
def setLayout(idx):
    slicer.app.layoutManager().setLayout( idx )


# global variables to access our layout actions
actionSegmentation3D = None
actionSegmentation   = None


# callback for action
def triggerLayoutManualSegmentation():
    # set layout
    setLayout( LAYOUTID_MANUALSEGMENTATION )

# callback for action
def triggerLayoutManualSegmentation3D():
    # set layout
    setLayout( LAYOUTID_MANUALSEGMENTATION_3D )

# callback for shortcut
def toggleLayoutManualSegmentation():
    lm = slicer.app.layoutManager()
    if lm.layout == LAYOUTID_MANUALSEGMENTATION_3D:
        actionSegmentation.triggered()
    else: 
        actionSegmentation3D.triggered()


# create our custom layouts, add actions in layout menu, create shortcut
def createCustomLayouts():
    # create layouts
    slicer.app.layoutManager().layoutLogic().GetLayoutNode().AddLayoutDescription( LAYOUTID_MANUALSEGMENTATION_3D, LAYOUTXML_MANUALSEGMENTATION_3D )
    slicer.app.layoutManager().layoutLogic().GetLayoutNode().AddLayoutDescription( LAYOUTID_MANUALSEGMENTATION, LAYOUTXML_MANUALSEGMENTATION )
    # create menu entries
    global actionSegmentation3D
    global actionSegmentation
    actionSegmentation3D = mainWindow().findChild('QMenu', 'LayoutMenu').addAction( "Segmentation+3D" ) # TODO: create Icon: #.setIcon(qt.QIcon(':Icons/Go.png'))
    actionSegmentation3D.setToolTip("Manual Segmentation")
    actionSegmentation3D.connect('triggered()', lambda: triggerLayoutManualSegmentation3D() )
    actionSegmentation = mainWindow().findChild('QMenu', 'LayoutMenu').addAction( "Segmentation" ) # TODO: create Icon: #.setIcon(qt.QIcon(':Icons/Go.png'))
    actionSegmentation.setToolTip("Manual Segmentation, fullscreen")
    actionSegmentation.connect('triggered()', lambda: triggerLayoutManualSegmentation() )
    # create toggle shortcut
    shortcutToggleSeg = qt.QShortcut( mainWindow() )
    shortcutToggleSeg.setKey( qt.QKeySequence('g') )
    shortcutToggleSeg.connect( 'activated()', lambda: toggleLayoutManualSegmentation() )
    # set custom layout right now. this makes sure volumes are loaded into our custom Slice Views
    #actionSegmentation3D.triggered() # TODO: enable when slicerrc.py works

createCustomLayouts()

However, I can’t get this working. And if I do actionSegmentation3D.triggered() and then actionSegmentation.triggered() in the Python console, Slicer hangs and stops working. What am I doing wrong? I don’t know Python, so help would be appreciated.

The problem is that you missed the layoutSwitchAction.setData(layoutId) call. The example code in the script repository was misleading because you don’t actually need to do anything else (no need to connect a custom function to the trigger). I’ve updated the example now.

Thank you, it worked!

I see you made a fix for this. I guess you can remove the “find” code in void qSlicerMainWindow::onLayoutActionTriggered(QAction* action) since the QAction is contained in LayoutMenu according to QMenu documentation.

Also, connecting the submenus menuConventionalQuantitative etc. to this slot can be removed since the parent menu (LayoutMenu) will handle the children. And in fact should be removed since it will set the layout twice. If I read the documentation right.

The example in Wiki has to be changed since a QAction not part of LayoutMenu (i.e. viewToolBar) will not be able to switch layout. I suggest to only use LayoutMenu. This will also reflect the current layout by showing the corresponding icon/text in LayoutMenu:

# Add button to LayoutMenu
menu = mainWindow().findChild('QMenu', 'LayoutMenu')
action = menu.addAction('Custom layout') 
action.setData(layoutId) # 'layoutId' defines the number for your layout
action.setIcon(qt.QIcon(':Icons/Go.png'))
action.setToolTip('Switch to my custom layout')

:slight_smile:

1 Like

Yes, it is just an extra check. Unfortunately, it is not described in code comments or commit comments why it was added. We can remove it when we make significant changes to that part of the code anyway (e.g., when implementing custom “favorite views”).

Thank you for the suggestion, we have already removed these. The pull request has not been merged yet, as it was part of a larger effort that requires some more work (https://github.com/Slicer/Slicer/pull/4904).

The example is for “You can use this code snippet to add a button to the layout selector toolbar” and the corresponding code is correct. We could add example to show how to define QActions in general, but I don’t think it is necessary.

1 Like