Skip to content

Commit

Permalink
PathColumn : Add contextMenuSignal()
Browse files Browse the repository at this point in the history
This allows columns to contribute to their own context menu, combined with the menu items defined by `PathListingWidget.columnContextMenuSignal()`.
  • Loading branch information
johnhaddon committed Aug 7, 2024
1 parent 9599500 commit 06c0274
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 4 deletions.
1 change: 1 addition & 0 deletions Changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ 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.

Breaking Changes
----------------
Expand Down
28 changes: 28 additions & 0 deletions 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,17 @@ 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>>;
ContextMenuSignal &contextMenuSignal();

private :

PathColumnSignal m_changedSignal;

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

SizeMode m_sizeMode;

Expand Down Expand Up @@ -274,4 +279,27 @@ class 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;

};

} // 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
5 changes: 5 additions & 0 deletions src/GafferUI/PathColumn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ PathColumn::ButtonSignal &PathColumn::buttonDoubleClickSignal()
return m_buttonDoubleClickSignal;
}

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

//////////////////////////////////////////////////////////////////////////
// StandardPathColumn
//////////////////////////////////////////////////////////////////////////
Expand Down
111 changes: 111 additions & 0 deletions src/GafferUIModule/PathColumnBinding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
#include "IECorePython/RefCountedBinding.h"
#include "IECorePython/ScopedGILLock.h"

#include "boost/mpl/vector.hpp"
#include "boost/python/suite/indexing/container_utils.hpp"

using namespace boost::python;
Expand Down Expand Up @@ -147,6 +148,83 @@ class PathListingWidgetAccessor : public GafferUI::PathListingWidget

} // namespace

//////////////////////////////////////////////////////////////////////////
// MenuDefinitionAccessor class
//////////////////////////////////////////////////////////////////////////

namespace
{

struct GILReleaseMenuCommand
{

GILReleaseMenuCommand( MenuDefinition::MenuItem::Command command )
: m_command( command )
{
}

void operator()()
{
IECorePython::ScopedGILRelease gilRelease;
m_command();
}

private :

MenuDefinition::MenuItem::Command m_command;

};

// Provides a C++ interface to the functionality implemented in the Python
// MenuDefinition class.
class MenuDefinitionAccessor : public GafferUI::MenuDefinition
{

public :

MenuDefinitionAccessor( object menuDefinition )
: m_menuDefinition( menuDefinition )
{
}

object menuDefinition()
{
return m_menuDefinition;
}

void append( const std::string &path, const MenuItem &item ) override
{
IECorePython::ScopedGILLock gilLock;

dict pythonItem;

if( item.command != nullptr )
{
pythonItem["command"] = make_function(
GILReleaseMenuCommand( item.command ),
boost::python::default_call_policies(),
boost::mpl::vector<void>()
);
}

pythonItem["description"] = item.description;
pythonItem["icon"] = item.icon;
pythonItem["shortCut"] = item.shortCut;
pythonItem["divider"] = item.divider;
pythonItem["active"] = item.active;

m_menuDefinition.attr( "append" )( path, pythonItem );
}

private :

// The Python MenuDefinition object.
object m_menuDefinition;

};

} // namespace

//////////////////////////////////////////////////////////////////////////
// Bindings
//////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -330,6 +408,37 @@ struct ButtonSignalSlotCaller
}
};

struct ContextMenuSignalCaller
{
static void call( PathColumn::ContextMenuSignal &s, PathColumn &column, object pathListingWidget, object menuDefinition )
{
PathListingWidgetAccessor pathListingWidgetAccessor( pathListingWidget );
MenuDefinitionAccessor menuDefinitionAccessor( menuDefinition );
IECorePython::ScopedGILRelease gilRelease;
s( column, pathListingWidgetAccessor, menuDefinitionAccessor );
}
};

struct ContextMenuSignalSlotCaller
{
void operator()( boost::python::object slot, PathColumn &column, PathListingWidget &pathListingWidget, MenuDefinition &menuDefinition )
{
try
{
slot(
PathColumnPtr( &column ),
static_cast<PathListingWidgetAccessor &>( pathListingWidget ).widget(),
static_cast<MenuDefinitionAccessor &>( menuDefinition ).menuDefinition()

);
}
catch( const error_already_set & )
{
IECorePython::ExceptionAlgo::translatePythonException();
}
}
};

template<typename T>
const char *pathColumnProperty( const T &column )
{
Expand Down Expand Up @@ -384,6 +493,7 @@ void GafferUIModule::bindPathColumn()

SignalClass<PathColumn::PathColumnSignal, DefaultSignalCaller<PathColumn::PathColumnSignal>, ChangedSignalSlotCaller>( "PathColumnSignal" );
SignalClass<PathColumn::ButtonSignal, ButtonSignalCaller, ButtonSignalSlotCaller>( "ButtonSignal" );
SignalClass<PathColumn::ContextMenuSignal, ContextMenuSignalCaller, ContextMenuSignalSlotCaller>( "ContextMenuSignal" );
}

pathColumnClass.def( init<PathColumn::SizeMode>( arg( "sizeMode" ) = PathColumn::SizeMode::Default ) )
Expand All @@ -393,6 +503,7 @@ void GafferUIModule::bindPathColumn()
.def( "buttonPressSignal", &PathColumn::buttonPressSignal, return_internal_reference<1>() )
.def( "buttonReleaseSignal", &PathColumn::buttonReleaseSignal, return_internal_reference<1>() )
.def( "buttonDoubleClickSignal", &PathColumn::buttonDoubleClickSignal, return_internal_reference<1>() )
.def( "contextMenuSignal", &PathColumn::contextMenuSignal, return_internal_reference<1>() )
.def( "getSizeMode", (PathColumn::SizeMode (PathColumn::*)() const )&PathColumn::getSizeMode )
.def( "setSizeMode", &PathColumn::setSizeMode, ( arg( "sizeMode" ) ) )
;
Expand Down

0 comments on commit 06c0274

Please sign in to comment.