From 830de764b3f041a99580c5867de4241670d44bf3 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Tue, 2 Jan 2024 17:01:01 +0000 Subject: [PATCH] BackgroundTask/Editor : Coordinate to find cancellation subject This hack allows us to find the cancellation subject for the internal plugs of the new ImageInspector. Although it's a tiny bit hacky, I'm reasonably content with it because it's pretty isolated and the new "Use Editor.Settings to store your settings" guidance moves us closer to where we want to be with regard to Editors being Nodes eventually. I suspect that when we get there we'll be able to replace it with a single cleaner mechanism for BackgroundTask to recover the ScriptNode from a UI element. --- Changes.md | 1 + python/GafferImageUI/ImageInspector.py | 14 ++++++------- python/GafferSceneUI/LightEditor.py | 25 +++++++---------------- python/GafferUI/Editor.py | 28 ++++++++++++++++++++++++++ src/Gaffer/BackgroundTask.cpp | 13 +++++++++--- 5 files changed, 52 insertions(+), 29 deletions(-) diff --git a/Changes.md b/Changes.md index cff482c8334..dc7d25211bd 100644 --- a/Changes.md +++ b/Changes.md @@ -74,6 +74,7 @@ API - Added `shuffleWithExtraSources()` method. - ShufflePlugValueWidget : Widgets for the `source` and `destination` plugs can now be customised using standard `plugValueWidget:type` metadata. - ImageTestCase : in `assertImageEqual` function, maxDifference may now be a tuple, to specify an asymmetric range. +- Editor : Added `Settings` class, which should be used to store settings for subclasses. See LightEditor and ImageInspector for examples. Breaking Changes ---------------- diff --git a/python/GafferImageUI/ImageInspector.py b/python/GafferImageUI/ImageInspector.py index 1b71e495b4b..d55e6dffd22 100644 --- a/python/GafferImageUI/ImageInspector.py +++ b/python/GafferImageUI/ImageInspector.py @@ -47,11 +47,11 @@ class ImageInspector( GafferUI.NodeSetEditor ) : # for the ImageInspector. ## \todo Eventually we want `GafferUI.Editor` to derive from Node itself, # in which case we wouldn't need a separate settings object. - class Settings( Gaffer.Node ) : + class Settings( GafferUI.Editor.Settings ) : - def __init__( self ) : + def __init__( self, script ) : - Gaffer.Node.__init__( self, "ImageInspectorSettings" ) + GafferUI.Editor.Settings.__init__( self, "ImageInspectorSettings", script ) self["in"] = GafferImage.ImagePlug() self["view"] = Gaffer.StringPlug( defaultValue = "default" ) @@ -85,7 +85,7 @@ def __init__( self ) : self["__sampleStats"]["in"].setInput( self["__sampleCounts"]["out"] ) self["__sampleStats"]["area"].setInput( self["__formatQuery"]["format"]["displayWindow"] ) - IECore.registerRunTimeTyped( Settings, typeName = "ImageInspector::Settings" ) + IECore.registerRunTimeTyped( Settings, typeName = "GafferImageUI::ImageInspector::Settings" ) def __init__( self, scriptNode, **kw ) : @@ -93,7 +93,7 @@ def __init__( self, scriptNode, **kw ) : GafferUI.NodeSetEditor.__init__( self, column, scriptNode, nodeSet = scriptNode.focusSet(), **kw ) - self.__settingsNode = self.Settings() + self.__settingsNode = self.Settings( scriptNode ) Gaffer.NodeAlgo.applyUserDefaults( self.__settingsNode ) with column : @@ -262,9 +262,7 @@ def copy( self ) : def cancellationSubject( self ) : - ## \todo We really just want to return `self._image` here, but we - # can't because BackgroundTask can't find the right ScriptNode from it. - return self.__scriptNode["fileName"] + return self._image def _children( self, canceller ) : diff --git a/python/GafferSceneUI/LightEditor.py b/python/GafferSceneUI/LightEditor.py index 40f4448ec2e..c0b9af0099a 100644 --- a/python/GafferSceneUI/LightEditor.py +++ b/python/GafferSceneUI/LightEditor.py @@ -60,29 +60,18 @@ # with HierarchyView. class LightEditor( GafferUI.NodeSetEditor ) : - # We store our settings as plugs on a node for a few reasons : - # - # - We want to use an EditScopePlugValueWidget, and that requires it. - # - We get a bunch of useful widgets and signals for free. - # - Longer term we want to refactor all Editors to derive from Node, - # in the same way that View does already. This will let us serialise - # _all_ layout state in the same format we serialise node graphs in. - # - The `userDefault` metadata provides a convenient way of configuring - # defaults. - # - The PlugLayout we use to display the settings allows users to add - # their own widgets to the UI. - class Settings( Gaffer.Node ) : - - def __init__( self ) : - - Gaffer.Node.__init__( self, "LightEditorSettings" ) + class Settings( GafferUI.Editor.Settings ) : + + def __init__( self, script ) : + + GafferUI.Editor.Settings.__init__( self, "LightEditorSettings", script ) self["in"] = GafferScene.ScenePlug() self["attribute"] = Gaffer.StringPlug( defaultValue = "light" ) self["section"] = Gaffer.StringPlug( defaultValue = "" ) self["editScope"] = Gaffer.Plug() - IECore.registerRunTimeTyped( Settings, typeName = "LightEditor::Settings" ) + IECore.registerRunTimeTyped( Settings, typeName = "GafferSceneUI::LightEditor::Settings" ) def __init__( self, scriptNode, **kw ) : @@ -90,7 +79,7 @@ def __init__( self, scriptNode, **kw ) : GafferUI.NodeSetEditor.__init__( self, column, scriptNode, nodeSet = scriptNode.focusSet(), **kw ) - self.__settingsNode = self.Settings() + self.__settingsNode = self.Settings( scriptNode ) Gaffer.NodeAlgo.applyUserDefaults( self.__settingsNode ) self.__setFilter = _GafferSceneUI._HierarchyViewSetFilter() diff --git a/python/GafferUI/Editor.py b/python/GafferUI/Editor.py index 48529b5f4d8..ed62d94be45 100644 --- a/python/GafferUI/Editor.py +++ b/python/GafferUI/Editor.py @@ -49,6 +49,34 @@ # or its children. These make up the tabs in the UI layout. class Editor( GafferUI.Widget ) : + ## Base class used to store settings for an Editor. We store our settings + # as plugs on a node for a few reasons : + # + # - Some editors want to use an EditScopePlugValueWidget, and that requires + # it. + # - We get a bunch of useful widgets and signals for free. + # - Longer term we want to refactor all Editors to derive from Node, in the + # same way that View does already. This will let us serialise _all_ layout + # state in the same format we serialise node graphs in. + # - The `userDefault` metadata provides a convenient way of configuring + # defaults. + # - The PlugLayout we use to display the settings allows users to add their + # own widgets to the UI. + class Settings( Gaffer.Node ) : + + def __init__( self, name, script ) : + + Gaffer.Node.__init__( self, name ) + + # Hack to allow BackgroundTask to recover ScriptNode for + # cancellation support - see `BackgroundTask.cpp`. + ## \todo Perhaps we can make this more natural at the point we derive + # Editor from Node? + self["__scriptNode"] = Gaffer.Plug( flags = Gaffer.Plug.Flags.Default & ~Gaffer.Plug.Flags.Serialisable ) + self["__scriptNode"].setInput( script["fileName"] ) + + IECore.registerRunTimeTyped( Settings, typeName = "GafferUI::Editor::Settings" ) + def __init__( self, topLevelWidget, scriptNode, **kw ) : GafferUI.Widget.__init__( self, topLevelWidget, **kw ) diff --git a/src/Gaffer/BackgroundTask.cpp b/src/Gaffer/BackgroundTask.cpp index bceebb131ee..9d88197ed72 100644 --- a/src/Gaffer/BackgroundTask.cpp +++ b/src/Gaffer/BackgroundTask.cpp @@ -90,9 +90,9 @@ const ScriptNode *scriptNode( const GraphComponent *subject ) } } - // The `GafferUI::View` classes house internal nodes which might not be - // directly connected to the graph. This hack recovers the ScriptNode from - // the node the view is currently connected to. + // The `GafferUI::View` and `GafferUI.Editor` classes house internal nodes + // which might not be directly connected to the graph. This hack recovers + // the ScriptNode from such classes. while( subject ) { if( subject->isInstanceOf( "GafferUI::View" ) ) @@ -102,6 +102,13 @@ const ScriptNode *scriptNode( const GraphComponent *subject ) return scriptNode( inPlug->getInput() ); } } + else if( subject->isInstanceOf( "GafferUI::Editor::Settings" ) ) + { + if( auto scriptPlug = subject->getChild( "__scriptNode" ) ) + { + return scriptNode( scriptPlug->getInput() ); + } + } subject = subject->parent(); } return nullptr;