diff --git a/Changes.md b/Changes.md index 5d179bf0b9..931acac957 100644 --- a/Changes.md +++ b/Changes.md @@ -14,6 +14,12 @@ Fixes - WidgetAlgo : Fixed issue preventing `grab()` from capturing popup menus on Windows. - ShowURL : Fixed opening of "file://" URLs on Windows (#5861). +API +--- + +- PathListingWidget : Added `columnContextMenuSignal()`, allowing multiple clients to collaborate on the creation of a column-specific context menu. +- HierarchyView : Added `sceneListing()` method, to allow the context menu to be customised. + Documentation ------------- diff --git a/python/GafferSceneUI/HierarchyView.py b/python/GafferSceneUI/HierarchyView.py index dd2d11040d..47c3495152 100644 --- a/python/GafferSceneUI/HierarchyView.py +++ b/python/GafferSceneUI/HierarchyView.py @@ -88,7 +88,7 @@ def __init__( self, scriptNode, **kw ) : self.__selectionChangedConnection = self.__pathListing.selectionChangedSignal().connect( Gaffer.WeakMethod( self.__selectionChanged ), scoped = False ) self.__expansionChangedConnection = self.__pathListing.expansionChangedSignal().connect( Gaffer.WeakMethod( self.__expansionChanged ), scoped = False ) - self.__pathListing.contextMenuSignal().connect( Gaffer.WeakMethod( self.__contextMenuSignal ), scoped = False ) + self.__pathListing.columnContextMenuSignal().connect( Gaffer.WeakMethod( self.__columnContextMenuSignal ), scoped = False ) self.keyPressSignal().connect( Gaffer.WeakMethod( self.__keyPressSignal ), scoped = False ) self.__plug = None @@ -100,6 +100,17 @@ def scene( self ) : return self.__plug + ## Returns the widget used for showing the main scene listing, with the + # intention that clients can add custom context menu items via + # `sceneListing.columnContextMenuSignal()`. + # + # > Caution : This currently returns a PathListingWidget, but in future + # > will probably return a more specialised widget with fewer privileges. + # > Please limit usage to `columnContextMenuSignal()`. + def sceneListing( self ) : + + return self.__pathListing + def __repr__( self ) : return "GafferSceneUI.HierarchyView( scriptNode )" @@ -209,11 +220,9 @@ def __keyPressSignal( self, widget, event ) : return False - def __contextMenuSignal( self, widget ) : - - menuDefinition = IECore.MenuDefinition() + def __columnContextMenuSignal( self, column, pathListing, menuDefinition ) : - selection = self.__pathListing.getSelection() + selection = pathListing.getSelection() menuDefinition.append( "Copy Path%s" % ( "" if selection.size() == 1 else "s" ), { @@ -231,11 +240,6 @@ def __contextMenuSignal( self, widget ) : } ) - self.__contextMenu = GafferUI.Menu( menuDefinition ) - self.__contextMenu.popup( widget ) - - return True - def __copySelectedPaths( self, *unused ) : if self.__plug is None : diff --git a/python/GafferUI/PathListingWidget.py b/python/GafferUI/PathListingWidget.py index ea5774c6c4..03a25c2375 100644 --- a/python/GafferUI/PathListingWidget.py +++ b/python/GafferUI/PathListingWidget.py @@ -148,6 +148,7 @@ def __init__( self.__displayModeChangedSignal = GafferUI.WidgetSignal() self.__expansionChangedSignal = GafferUI.WidgetSignal() self.__updateFinishedSignal = GafferUI.WidgetSignal() + self.__columnContextMenuSignal = Gaffer.Signal3() # Connections for implementing selection and drag and drop. self.keyPressSignal().connect( Gaffer.WeakMethod( self.__keyPress ), scoped = False ) @@ -157,6 +158,7 @@ def __init__( self.mouseMoveSignal().connect( Gaffer.WeakMethod( self.__mouseMove ), scoped = False ) self.dragBeginSignal().connect( Gaffer.WeakMethod( self.__dragBegin ), scoped = False ) self.dragEndSignal().connect( Gaffer.WeakMethod( self.__dragEnd ), scoped = False ) + self.contextMenuSignal().connect( Gaffer.WeakMethod( self.__contextMenu ), scoped = False ) self.__dragPointer = "paths" self.__path = None @@ -423,6 +425,19 @@ def pathSelectedSignal( self ) : return self.__pathSelectedSignal + ## Signal emitted to generate a context menu for a column. This allows + # multiple clients to collaborate in the construction of a menu, with each + # providing different items. It should be preferred to the generic + # `Widget.contextMenuSignal()`. + # + # Slots must have the following signature, with the `menuDefinition` being + # edited directly in place : + # + # `slot( column, pathListingWidget, menuDefinition )` + def columnContextMenuSignal( self ) : + + return self.__columnContextMenuSignal + def setDragPointer( self, dragPointer ) : self.__dragPointer = dragPointer @@ -749,6 +764,47 @@ def __dragEnd( self, widget, event ) : GafferUI.Pointer.setCurrent( None ) + def __contextMenu( self, widget ) : + + if not self.columnContextMenuSignal().numSlots() : + # Allow legacy clients connected to `Widget.contextMenuSignal()` to + # do their own thing instead. + return False + + # Select the path under the mouse, if it's not already selected. + # The user will expect to be operating on the thing under the mouse. + + mousePosition = GafferUI.Widget.mousePosition( relativeTo = self ) + column = self.columnAt( mousePosition ) + + path = self.pathAt( mousePosition ) + if path is not None : + path = str( path ) + selection = self.getSelection() + if isinstance( selection, IECore.PathMatcher ) : + # Row or Rows mode. + if not selection.match( path ) & IECore.PathMatcher.Result.ExactMatch : + selection = IECore.PathMatcher( [ path ] ) + self.setSelection( selection ) + else : + # Cell or Cells mode. + columnIndex = self.getColumns().index( column ) + if not selection[columnIndex].match( path ) & IECore.PathMatcher.Result.ExactMatch : + for i in range( 0, len( selection ) ) : + selection[i] = IECore.PathMatcher() if columnIndex != i else IECore.PathMatcher( [ path ] ) + self.setSelection( selection ) + + # Use signals to build menu and display it. + + menuDefinition = IECore.MenuDefinition() + self.columnContextMenuSignal()( column, self, menuDefinition ) + + if menuDefinition.size() : + self.__columnContextMenu = GafferUI.Menu( menuDefinition ) + self.__columnContextMenu.popup( self ) + + return True + def __indexAt( self, position ) : point = self._qtWidget().viewport().mapFrom(