diff --git a/Changes.md b/Changes.md index 4c123db8dca..97c2f43ddfc 100644 --- a/Changes.md +++ b/Changes.md @@ -21,6 +21,12 @@ Fixes ----- - MergeScenes : Fixed bug handling input connections not originating from the output of another node. These could cause locations provided by other inputs to lose all their properties. +- PathFilter : Fixed bug allowing dropping paths onto read-only `PathFilter` nodes in the graph. +- VectorDataWidget : Fixed bug allowing dropping paths onto read-only widgets. +- GraphEditor : Fixed errors when dragging an unknown file type into the GraphEditor. +- Widget : Fixed `event.sourceWidget` for DragDropEvents generated from a Qt native drag within the same Gaffer process. This will now reference the `GafferUI.Widget` that the Qt source widget belongs to, if any. +- Catalogue : Fixed bug which "stole" drags that crossed the image listing but which were destined elsewhere, for instance a drag from the HierarchyView to a PathFilter in the GraphEditor. +- GadgetWidget : Fixed signal handling bug in `setViewportGadget()`. This could cause the widget to attempt to redraw unnecessarily when the _old_ viewport requested a redraw. API --- @@ -336,10 +342,25 @@ Build - Zstandard : Added version 1.5.0. - Windows : Updated compiler to Visual Studio 2022 / MSVC 17.8 / Runtime library 14.3. -1.4.15.x (relative to 1.4.15.1) +1.4.15.x (relative to 1.4.15.2) ======== +Fixes +----- + +- GraphEditor : Fixed errors when dragging an unknown file type into the GraphEditor. +- Widget : Fixed `event.sourceWidget` for DragDropEvents generated from a Qt native drag within the same Gaffer process. This will now reference the `GafferUI.Widget` that the Qt source widget belongs to, if any. +- Catalogue : Fixed bug which "stole" drags that crossed the image listing but which were destined elsewhere, for instance a drag from the HierarchyView to a PathFilter in the GraphEditor. +- GadgetWidget : Fixed signal handling bug in `setViewportGadget()`. This could cause the widget to attempt to redraw unnecessarily when the _old_ viewport requested a redraw. + +1.4.15.2 (relative to 1.4.15.1) +======== + +Fixes +----- +- PathFilter : Fixed bug allowing dropping paths onto read-only `PathFilter` nodes in the graph. +- VectorDataWidget : Fixed bug allowing dropping paths onto read-only widgets. 1.4.15.1 (relative to 1.4.15.0) ======== diff --git a/python/GafferImageUI/CatalogueUI.py b/python/GafferImageUI/CatalogueUI.py index 66cb9077960..4f65e8d82a3 100644 --- a/python/GafferImageUI/CatalogueUI.py +++ b/python/GafferImageUI/CatalogueUI.py @@ -1079,7 +1079,7 @@ def __dropImage( self, eventData ) : def __pathListingDragEnter( self, widget, event ) : - if isinstance( event.data, IECore.StringVectorData ) : + if event.sourceWidget is widget and isinstance( event.data, IECore.StringVectorData ) and event.data : # Allow reordering of images self.__moveToPath = None self.__mergeGroupId += 1 @@ -1101,7 +1101,7 @@ def __pathListingDragLeave( self, widget, event ) : def __pathListingDragMove( self, listing, event ) : - if not event.data or not isinstance( event.data, IECore.StringVectorData ) : + if not ( event.sourceWidget is listing and isinstance( event.data, IECore.StringVectorData ) and event.data ) : return targetPath = self.__pathListing.pathAt( event.line.p0 ) diff --git a/python/GafferSceneUI/PathFilterUI.py b/python/GafferSceneUI/PathFilterUI.py index 65857ba96f3..2bc565c20a3 100644 --- a/python/GafferSceneUI/PathFilterUI.py +++ b/python/GafferSceneUI/PathFilterUI.py @@ -236,7 +236,7 @@ def __popupMenu( menuDefinition, plugValueWidget ) : GafferUI.Pointer.registerPointer( "removeObjects", GafferUI.Pointer( "removeObjects.png", imath.V2i( 53, 14 ) ) ) GafferUI.Pointer.registerPointer( "replaceObjects", GafferUI.Pointer( "replaceObjects.png", imath.V2i( 53, 14 ) ) ) -__DropMode = enum.Enum( "__DropMode", [ "None_", "Add", "Remove", "Replace" ] ) +__DropMode = enum.Enum( "__DropMode", [ "None_", "Add", "Remove", "Replace", "NotEditable" ] ) __originalDragPointer = None @@ -255,9 +255,14 @@ def __filterPlug( node ) : return filterPlugs[0] return None +def __editable( plug ) : + + return not Gaffer.MetadataAlgo.readOnly( plug ) and plug.settable() + def __dropMode( nodeGadget, event ) : - if __pathsPlug( nodeGadget.node() ) is None : + pathsPlug = __pathsPlug( nodeGadget.node() ) + if pathsPlug is None : filter = None filterPlug = __filterPlug( nodeGadget.node() ) @@ -267,9 +272,13 @@ def __dropMode( nodeGadget, event ) : if filterPlug.getInput() is not None : filter = filterPlug.source().node() if filter is None : - return __DropMode.Replace + return __DropMode.Replace if __editable( filterPlug ) else __DropMode.NotEditable elif not isinstance( filter, GafferScene.PathFilter ) : return __DropMode.None_ + pathsPlug = __pathsPlug( filter ) + + if not __editable( pathsPlug ) : + return __DropMode.NotEditable if event.modifiers & event.Modifiers.Shift : return __DropMode.Add @@ -342,7 +351,10 @@ def __dragMove( nodeGadget, event ) : if __originalDragPointer is None : return False - GafferUI.Pointer.setCurrent( __dropMode( nodeGadget, event ).name.lower() + "Objects" ) + dropMode = __dropMode( nodeGadget, event ) + GafferUI.Pointer.setCurrent( + dropMode.name.lower() + "Objects" if dropMode != __DropMode.NotEditable else "notEditable" + ) return True @@ -352,6 +364,9 @@ def __drop( nodeGadget, event ) : if __originalDragPointer is None : return False + if __dropMode( nodeGadget, event ) == __DropMode.NotEditable : + return True + pathsPlug = __pathsPlug( nodeGadget.node() ) if pathsPlug is None : pathsPlug = __pathsPlug( __filterPlug( nodeGadget.node() ).source().node() ) diff --git a/python/GafferSceneUI/SetFilterUI.py b/python/GafferSceneUI/SetFilterUI.py index 356422be8af..d8dad35fc66 100644 --- a/python/GafferSceneUI/SetFilterUI.py +++ b/python/GafferSceneUI/SetFilterUI.py @@ -111,7 +111,7 @@ GafferUI.Pointer.registerPointer( "removeSets", GafferUI.Pointer( "pointerRemoveSets.png", imath.V2i( 53, 14 ) ) ) GafferUI.Pointer.registerPointer( "replaceSets", GafferUI.Pointer( "pointerReplaceSets.png", imath.V2i( 53, 14 ) ) ) -__DropMode = enum.Enum( "__DropMode", [ "None_", "Add", "Remove", "Replace" ] ) +__DropMode = enum.Enum( "__DropMode", [ "None_", "Add", "Remove", "Replace", "NotEditable" ] ) __originalDragPointer = None @@ -146,13 +146,13 @@ def __dropMode( nodeGadget, event ) : if nodeGadget.node()["filter"].getInput() is not None : filter = nodeGadget.node()["filter"].source().node() if filter is None : - return __DropMode.Replace if __editable( nodeGadget.node()["filter"] ) else __DropMode.None_ + return __DropMode.Replace if __editable( nodeGadget.node()["filter"] ) else __DropMode.NotEditable elif not isinstance( filter, GafferScene.SetFilter ) : return __DropMode.None_ setsPlug = filter["setExpression"] if not __editable( setsPlug ) : - return __DropMode.None_ + return __DropMode.NotEditable if event.modifiers & event.Modifiers.Shift : return __DropMode.Add @@ -201,7 +201,10 @@ def __dragMove( nodeGadget, event ) : if __originalDragPointer is None : return False - GafferUI.Pointer.setCurrent( __dropMode( nodeGadget, event ).name.lower() + "Sets" ) + dropMode = __dropMode( nodeGadget, event ) + GafferUI.Pointer.setCurrent( + dropMode.name.lower() + "Sets" if dropMode != __DropMode.NotEditable else "notEditable" + ) return True @@ -211,6 +214,9 @@ def __drop( nodeGadget, event ) : if __originalDragPointer is None : return False + if __dropMode( nodeGadget, event ) == __DropMode.NotEditable : + return True + setsPlug = __setsPlug( nodeGadget.node() ) if setsPlug is None : setsPlug = __setsPlug( nodeGadget.node()["filter"].source().node() ) diff --git a/python/GafferUI/GadgetWidget.py b/python/GafferUI/GadgetWidget.py index 1dd898c1c1a..d886712be5f 100644 --- a/python/GafferUI/GadgetWidget.py +++ b/python/GafferUI/GadgetWidget.py @@ -106,7 +106,7 @@ def setViewportGadget( self, viewportGadget ) : self.__viewportGadget.setVisible( False ) self.__viewportGadget = viewportGadget - self.__viewportGadget.renderRequestSignal().connect( Gaffer.WeakMethod( self.__renderRequest ) ) + self.__renderRequestConnection = self.__viewportGadget.renderRequestSignal().connect( Gaffer.WeakMethod( self.__renderRequest ), scoped = True ) size = self.size() if size.x and size.y : self.__viewportGadget.setViewport( size ) diff --git a/python/GafferUI/VectorDataWidget.py b/python/GafferUI/VectorDataWidget.py index b2d075365eb..f76e40e7f81 100644 --- a/python/GafferUI/VectorDataWidget.py +++ b/python/GafferUI/VectorDataWidget.py @@ -621,6 +621,9 @@ def __addRows( self, button ) : def __dragEnter( self, widget, event ) : + if not self.getEditable() : + return False + if event.sourceWidget is self.__tableViewHolder and widget is not self.__buttonRow[1]: # we don't accept drags from ourself unless the target is the remove button return False diff --git a/python/GafferUI/Widget.py b/python/GafferUI/Widget.py index dd2bb713a39..66c44d71943 100644 --- a/python/GafferUI/Widget.py +++ b/python/GafferUI/Widget.py @@ -1578,9 +1578,13 @@ def __foreignDragEnter( self, qObject, qEvent ) : Widget._modifiers( qEvent.keyboardModifiers() ), ) dragDropEvent.data = data - dragDropEvent.sourceWidget = None dragDropEvent.destinationWidget = None + if isinstance( qEvent.source(), QtWidgets.QWidget ) : + dragDropEvent.sourceWidget = GafferUI.Widget._owner( qEvent.source() ) + else : + dragDropEvent.sourceWidget = None + if widget._dragEnterSignal( widget, dragDropEvent ) : qEvent.acceptProposedAction() self.__foreignDragDropEvent = dragDropEvent diff --git a/python/GafferUITest/GadgetWidgetTest.py b/python/GafferUITest/GadgetWidgetTest.py index aaadcc88556..3f8c8bbfb77 100644 --- a/python/GafferUITest/GadgetWidgetTest.py +++ b/python/GafferUITest/GadgetWidgetTest.py @@ -66,5 +66,18 @@ def testViewportVisibility( self ) : self.assertFalse( vg1.visible() ) self.assertFalse( vg2.visible() ) + def testConnectionLifetime( self ) : + + gadgetWidget = GafferUI.GadgetWidget() + viewportGadget1 = gadgetWidget.getViewportGadget() + self.assertEqual( viewportGadget1.renderRequestSignal().numSlots(), 1 ) + + viewportGadget2 = GafferUI.ViewportGadget() + self.assertEqual( viewportGadget2.renderRequestSignal().numSlots(), 0 ) + + gadgetWidget.setViewportGadget( viewportGadget2 ) + self.assertEqual( viewportGadget1.renderRequestSignal().numSlots(), 0 ) + self.assertEqual( viewportGadget2.renderRequestSignal().numSlots(), 1 ) + if __name__ == "__main__": unittest.main() diff --git a/startup/gui/graphEditor.py b/startup/gui/graphEditor.py index 18716a17c0b..395633b8319 100644 --- a/startup/gui/graphEditor.py +++ b/startup/gui/graphEditor.py @@ -245,7 +245,8 @@ def __dropLocationData( event ) : if ( not isinstance( event.data, IECore.StringVectorData ) or len( event.data ) != 1 or - not event.data[0].startswith( "/" ) + not event.data[0].startswith( "/" ) or + event.sourceWidget is None ) : return None