Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PathColumn : Add contextMenuSignal() and instanceCreatedSignal() #5985

Merged
merged 3 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ API
- A `DeprecationWarning` is now emitted by `_plugConnections()`. Use `_blockedUpdateFromValues()` instead.
- NodeGadget, ConnectionGadget : Added `updateFromContextTracker()` virtual methods.
- Path : Added `inspectionContext()` virtual method.
- PathColumn :
- Added `contextMenuSignal()`, allowing the creation of custom context menus.
- Added `instanceCreatedSignal()`, providing an opportunity to connect to the signals on _any_ column, no matter how it is created.

Breaking Changes
----------------
Expand Down
60 changes: 59 additions & 1 deletion include/GafferUI/PathColumn.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
namespace GafferUI
{

class MenuDefinition;
class PathListingWidget;

/// Abstract class for extracting properties from a Path in a form
Expand Down Expand Up @@ -162,13 +163,26 @@ class GAFFERUI_API PathColumn : public IECore::RefCounted, public Gaffer::Signal
ButtonSignal &buttonReleaseSignal();
ButtonSignal &buttonDoubleClickSignal();

using ContextMenuSignal = Gaffer::Signals::Signal<void ( PathColumn &column, PathListingWidget &widget, MenuDefinition &menuDefinition ), Gaffer::Signals::CatchingCombiner<void>>;
/// To retain `widget` for use in MenuItem commands, use `PathListingWidgetPtr( &widget )`.
ContextMenuSignal &contextMenuSignal();

/// Creation
/// ========

/// Signal emitted whenever a new PathColumn is created. This provides
/// an opportunity for the customisation of columns anywhere, no matter how
/// they are created or where they are hosted.
static PathColumnSignal &instanceCreatedSignal();

private :

PathColumnSignal m_changedSignal;

ButtonSignal m_buttonPressSignal;
ButtonSignal m_buttonReleaseSignal;
ButtonSignal m_buttonDoubleClickSignal;
ContextMenuSignal m_contextMenuSignal;

SizeMode m_sizeMode;

Expand Down Expand Up @@ -259,11 +273,13 @@ IE_CORE_DECLAREPTR( FileIconPathColumn )
/// C++ interface for the `GafferUI.PathListingWidget` Python class. Provided for
/// use in PathColumn event signals, so that event handling may be implemented
/// from C++ if desired.
class PathListingWidget
class PathListingWidget : public IECore::RefCounted
{

public :

IE_CORE_DECLAREMEMBERPTR( PathListingWidget )

using Columns = std::vector<PathColumnPtr>;
virtual void setColumns( const Columns &columns ) = 0;
virtual Columns getColumns() const = 0;
Expand All @@ -274,4 +290,46 @@ class PathListingWidget

};

IE_CORE_DECLAREPTR( PathListingWidget )

/// C++ interface for the `IECore.MenuDefinition` Python class. Provided for use
/// in `PathColumn::contextMenuSignal()`, so that event handling may be
/// implemented from C++ if desired.
class MenuDefinition
{

public :

struct MenuItem
{
using Command = std::function<void ()>;
Command command;
std::string description;
std::string icon;
std::string shortCut;
bool divider = false;
bool active = true;
};

virtual void append( const std::string &path, const MenuItem &item ) = 0;

};

/// Overload for the standard `intrusive_ptr_add_ref` defined in RefCounted.h.
/// This allows us to emit `instanceCreatedSignal()` once the object is fully
/// constructed and it is safe for slots (especially Python slots) to add
/// additional references.
///
/// > Caution : This won't be called if you assign a new PathColumn to
/// > RefCountedPtr rather than PathColumnPtr. Don't do that!
inline void intrusive_ptr_add_ref( PathColumn *column )
{
bool firstRef = column->refCount() == 0;
column->addRef();
if( firstRef )
{
PathColumn::instanceCreatedSignal()( column );
}
}

} // namespace GafferUI
8 changes: 4 additions & 4 deletions python/GafferUI/PathListingWidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -766,17 +766,16 @@ def __dragEnd( self, widget, event ) :

def __contextMenu( self, widget ) :

if not self.columnContextMenuSignal().numSlots() :
mousePosition = GafferUI.Widget.mousePosition( relativeTo = self )
column = self.columnAt( mousePosition )
if not column.contextMenuSignal().numSlots() and 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 )
Expand All @@ -797,6 +796,7 @@ def __contextMenu( self, widget ) :
# Use signals to build menu and display it.

menuDefinition = IECore.MenuDefinition()
column.contextMenuSignal()( column, self, menuDefinition )
self.columnContextMenuSignal()( column, self, menuDefinition )

if menuDefinition.size() :
Expand Down
43 changes: 43 additions & 0 deletions python/GafferUITest/PathColumnTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@

import IECore

import GafferTest
import GafferUI
import GafferUITest

Expand Down Expand Up @@ -136,5 +137,47 @@ def testIconPathColumnConstructors( self ) :
self.assertEqual( c.headerData().value, "label" )
self.assertEqual( c.headerData().toolTip, "help!" )

def testInstanceCreatedSignal( self ) :

cs = GafferTest.CapturingSlot( GafferUI.PathColumn.instanceCreatedSignal() )

column1 = GafferUI.StandardPathColumn( "l1", "p1" )
column2 = GafferUI.StandardPathColumn( "l2", "p2" )
column3 = GafferUI.StandardPathColumn( "l3", "p3" )

self.assertEqual( cs, [ ( column1, ), ( column2, ), ( column3, ) ] )

def testInstanceCreatedSignalWithPythonColumn( self ) :

class PythonColumn( GafferUI.PathColumn ) :

def __init__( self ) :

self.member = "preInitValue"
GafferUI.PathColumn.__init__( self )
self.member = "postInitValue"

columnCreated = None

def instanceCreated( column ) :

nonlocal columnCreated
columnCreated = column

# We can query the full derived type of the column in `instanceCreated()`.
self.assertIsInstance( column, PythonColumn )
# But we see the object in the state in which is called the base
# class `__init__()`. Column subclasses which need to be seen in a
# fully constructed state should do all their initialisation before
# calling the base class `__init__()`. Hopefully this is not too
# onerous.
self.assertEqual( column.member, "preInitValue" )

connection = GafferUI.PathColumn.instanceCreatedSignal().connect( instanceCreated, scoped = True )
column = PythonColumn()

self.assertIs( column, columnCreated )
self.assertEqual( column.member, "postInitValue" )

if __name__ == "__main__":
unittest.main()
11 changes: 11 additions & 0 deletions src/GafferUI/PathColumn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,17 @@ PathColumn::ButtonSignal &PathColumn::buttonDoubleClickSignal()
return m_buttonDoubleClickSignal;
}

PathColumn::ContextMenuSignal &PathColumn::contextMenuSignal()
{
return m_contextMenuSignal;
}

PathColumn::PathColumnSignal &PathColumn::instanceCreatedSignal()
{
static PathColumnSignal g_instanceCreatedSignal;
return g_instanceCreatedSignal;
}

//////////////////////////////////////////////////////////////////////////
// StandardPathColumn
//////////////////////////////////////////////////////////////////////////
Expand Down
Loading
Loading