Is it possible to trigger a custom event when double-right clicking on a slice viewer? I saw that it was possible to observe the crosshair node, but this is not exactly what I’m after. I know right clicking and double clicking trigger their own events (i.e. maximize/restore view) but I’m not sure how this functionality is implemented. Any advice would be greatly appreciated.
After doing some digging in the script repository & on the forum, I found some helpful answers (see here and here). However, much of what I found advises that one use the markups module for interactions with the slice viewer. I’m sure this might work, but it seems a little roundabout for what I actually need to do. Is there no way to:
-
set an event translation via something like:
sliceViewWidget = slicer.app.layoutManager().sliceWidget(“Red”)
displayableManager = sliceViewWidget.sliceView().displayableManagerByClassName(“vtkMRMLCrosshairDisplayableManager”)
widget = displayableManager.GetSliceIntersectionWidget()
widget.SetEventTranslation(widget.WidgetStateAny, vtk.vtkCommand.RightButtonDoubleClickEvent, vtk.vtkEvent.NoModifier, widget.WidgetEventCustomAction1) -
Take the translation and set an observer on a vtkMRMLsliceNode or a vtkMRMLcrosshairNode?
i.e. sliceNode.AddObserver(sliceNode.CustomActionEvent1 (DOES NOT EXIST), GenericCallbackFn)
Event handling in the viewers is very complex, because there are lots of interactive widgets in the viewer, each having several internal states and responding to different events in each state. When a new interaction event is invoked then there first there is arbitration process: CanProcessInteractionEvent
is called for each each displayable manager (that maps MRML nodes to rendering actors). Displayable managers ask all their widgets if they can process the event and if they can, then provide a “distance” value (usually physical distance from the mouse pointer in screen space). The displayable manager that can process the event and reported the shortest distance gets to process the event. If this displayable manager is not the same as the displayable manager that currently has the focus then the previous displayable manager is notified that it no longer has the focus, the new displayable manager gets the focus, and its ProcessInteractionEvent
method is called.
You could add a low-level high-priority observation to the VTK interactor and hijack some mouse events. It would be simple, but because you would just ignore all the existing event arbitration and displayable manager and widget state mechanisms, you would cause many small, often not immediately noticeable errors in the viewer.
If you used an event that does not interfere with any other widget’s state then you may get away without any serious issues. However, a right-double-click causes many events: right button down, right button click, right button up, right button down, right button double-click, right button up. If you don’t consume all these events then widget states will be messed up. The double click may also have different side effects, depending on what widget your mouse pointer is hovered over.
What would you like to trigger by double-right-click?
Would it be acceptable to use an event that is less likely to cause havoc (e.g., some keyboard combinations or right-click with Ctrl/Alt/Shift modifiers, …)?
Would you double-click over a markup, slice intersection, or just over blank screen region?
Would you need to interact in both slice and 3D views?
Do you need 3D click position in 3D views?
Thanks so much for the reply! To answer those questions:
- What would you like to trigger by double-right-click?
I want to pause scrolling on the slice viewer similar to:
interactorStyle = slicer.app.layoutManager().sliceWidget(“Red”)
interactorStyle.SetActionEnabled(interactorStyle.BrowseSlice = False
but stricter such that the user cannot move the slice viewer under any circumstances (unless they repeat the action) - Would it be acceptable to use an event that is less likely to cause havoc (e.g., some keyboard combinations or right-click with Ctrl/Alt/Shift modifiers, …)?
Absolutely. Any reasonable combination of keys with any click will do. - Would you double-click over a markup, slice intersection, or just over blank screen region?
Slice view. - Would you need to interact in both slice and 3D views?
Just the slice view. - Do you need 3D click position in 3D views?
N/A
Why would you like to activate the scroll lock using double-click? Mouse gestures are not discoverable and by repeatedly clicking too quickly may trigger double-click events, too.
If you can lock/unlock all slice views at once then adding a button to your module would be the simplest. A dedicated button would also make the feature easily discoverable and you would always see the current state of the views.
If you must lock/unlock a single slice view then I would recommend adding a right-click menu item for toggling this, or maybe add a button to the slice view controller (next to the slice selector slider).
Unfortunately, it is necessary to lock single slice views. I appreciate the suggestion to use a right-click menu item; it seems to work well for my purpose. I am curious though–if I were to go with a checkbox/button option instead, is there a convenient way to add it to the slice viewer?
As for locking the slice, I am aware only of the interactorStyle.BrowseSlice option from above. Is there anything that won’t let the slice scroll even if it is linked to the other slices from that view group?
To clarify the first question–I am able to add a checkbox to the slice viewer using:
checkbox = qt.QCheckBox()
sc = slicer.app.layoutManager().sliceWidget(“Red”).sliceController()
sc.layout().addWidget(checkBox)
But this places the checkbox underneath the slice controller widget. Is there any way to put it next to the slice selector slider like you had proposed? Thank you.
You can add buttons anywhere in the widget. See examples in the script repository.
Thanks that works great! As for the freezing slices question–is it possible to freeze a slice that is linked to another slice? interactorStyle.BrowseSlice alone is not sufficient to achieve this.
You can change the group ID of the view node to unlink it from the other views.
Thank you so much! These are all incredibly helpful suggestions.