Skip to content

Commit

Permalink
Implemented error display for StandardNodeGadget.
Browse files Browse the repository at this point in the history
Errors are displayed as an alert icon overlaid on the node gadget. The alert is cleared automatically when the error plug is dirtied, or if it is removed from the node.

Fixes GafferHQ#1115.
  • Loading branch information
johnhaddon committed Jan 8, 2015
1 parent 6de146b commit 2f6f491
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 12 deletions.
6 changes: 5 additions & 1 deletion include/GafferUI/StandardNodeGadget.h
Expand Up @@ -100,7 +100,6 @@ class StandardNodeGadget : public NodeGadget
void setLabelsVisibleOnHover( bool labelsVisible );
bool getLabelsVisibleOnHover() const;

virtual bool acceptsChild( const Gaffer::GraphComponent *potentialChild ) const;
virtual Imath::Box3f bound() const;

protected :
Expand Down Expand Up @@ -146,6 +145,11 @@ class StandardNodeGadget : public NodeGadget
bool updateUserColor();
void updatePadding();

IE_CORE_FORWARDDECLARE( ErrorGadget );
ErrorGadget *errorGadget( bool createIfMissing = true );
void error( Gaffer::ConstPlugPtr plug, Gaffer::ConstPlugPtr source, const std::string &message );
void displayError( Gaffer::ConstPlugPtr plug, const std::string &message );

const LinearContainer::Orientation m_orientation;
bool m_nodeEnabled;
bool m_labelsVisibleOnHover;
Expand Down
139 changes: 128 additions & 11 deletions src/GafferUI/StandardNodeGadget.cpp
Expand Up @@ -55,11 +55,95 @@
#include "GafferUI/CompoundNodule.h"
#include "GafferUI/StandardNodule.h"
#include "GafferUI/SpacerGadget.h"
#include "GafferUI/ImageGadget.h"

using namespace GafferUI;
using namespace Gaffer;
using namespace Imath;

//////////////////////////////////////////////////////////////////////////
// ErrorGadget implementation
//////////////////////////////////////////////////////////////////////////

class StandardNodeGadget::ErrorGadget : public Gadget
{

public :

ErrorGadget( const std::string &name = defaultName<ErrorGadget>() )
: Gadget( name ), m_image( new ImageGadget( "gadgetError.png" ) )
{
m_image->setTransform( M44f().scale( V3f( .025 ) ) );
addChild( m_image );
}

void addError( PlugPtr plug, const std::string &error )
{
PlugEntry &entry = m_errors[plug];
if( entry.error.empty() || error != "Previous attempt to get item failed." )
{
// Update the error message. Unfortunately the IECore::LRUCache at the
// heart of Gaffer's caching does not remember the details of exceptions that
// occurred when the cache entry is in error - instead it throws a different
// exception saying "Previous attempt to get item failed.". We ignore these less
// helpful messages in favour of a previous messages if one exists.
/// \todo Improve LRUCache behaviour and remove this workaround.
entry.error = error;
}
if( !entry.parentChangedConnection.connected() )
{
entry.parentChangedConnection = plug->parentChangedSignal().connect( boost::bind( &ErrorGadget::parentChanged, this, ::_1 ) );
}
m_image->setVisible( true );
}

void removeError( const Plug *plug )
{
m_errors.erase( plug );
m_image->setVisible( m_errors.size() );
}

virtual std::string getToolTip( const IECore::LineSegment3f &position ) const
{
std::string result = Gadget::getToolTip( position );
if( !result.empty() )
{
return result;
}
for( PlugErrors::const_iterator it = m_errors.begin(); it != m_errors.end(); ++it )
{
result += it->second.error;
}
return result;
}

private :

void parentChanged( GraphComponent *plug )
{
if( !plug->parent<GraphComponent>() )
{
removeError( static_cast<Plug *>( plug ) );
}
}

ImageGadgetPtr m_image;

struct PlugEntry
{
std::string error;
boost::signals::scoped_connection parentChangedConnection;
};

typedef std::map<ConstPlugPtr, PlugEntry> PlugErrors;
PlugErrors m_errors;

};

//////////////////////////////////////////////////////////////////////////
// StandardNodeGadget implementation
//////////////////////////////////////////////////////////////////////////

IE_CORE_DEFINERUNTIMETYPED( StandardNodeGadget );

NodeGadget::NodeGadgetTypeDescription<StandardNodeGadget> StandardNodeGadget::g_nodeGadgetTypeDescription( Gaffer::Node::staticTypeId() );
Expand All @@ -71,6 +155,7 @@ static IECore::InternedString g_minWidthKey( "nodeGadget:minWidth" );
static IECore::InternedString g_paddingKey( "nodeGadget:padding" );
static IECore::InternedString g_nodulePositionKey( "nodeGadget:nodulePosition" );
static IECore::InternedString g_colorKey( "nodeGadget:color" );
static IECore::InternedString g_errorGadgetName( "__error" );

StandardNodeGadget::StandardNodeGadget( Gaffer::NodePtr node, LinearContainer::Orientation orientation )
: NodeGadget( node ),
Expand Down Expand Up @@ -184,15 +269,16 @@ StandardNodeGadget::StandardNodeGadget( Gaffer::NodePtr node, LinearContainer::O

node->childAddedSignal().connect( boost::bind( &StandardNodeGadget::childAdded, this, ::_1, ::_2 ) );
node->childRemovedSignal().connect( boost::bind( &StandardNodeGadget::childRemoved, this, ::_1, ::_2 ) );
node->errorSignal().connect( boost::bind( &StandardNodeGadget::error, this, ::_1, ::_2, ::_3 ) );

if( DependencyNode *dependencyNode = IECore::runTimeCast<DependencyNode>( node.get() ) )
{
const Gaffer::BoolPlug *enabledPlug = dependencyNode->enabledPlug();
if( enabledPlug )
{
m_nodeEnabled = enabledPlug->getValue();
node->plugDirtiedSignal().connect( boost::bind( &StandardNodeGadget::plugDirtied, this, ::_1 ) );
}
node->plugDirtiedSignal().connect( boost::bind( &StandardNodeGadget::plugDirtied, this, ::_1 ) );
}

dragEnterSignal().connect( boost::bind( &StandardNodeGadget::dragEnter, this, ::_1, ::_2 ) );
Expand All @@ -218,18 +304,9 @@ StandardNodeGadget::~StandardNodeGadget()
{
}

bool StandardNodeGadget::acceptsChild( const Gaffer::GraphComponent *potentialChild ) const
{
if( !NodeGadget::acceptsChild( potentialChild ) )
{
return false;
}
return children().size()==0;
}

Imath::Box3f StandardNodeGadget::bound() const
{
Box3f b = NodeGadget::bound();
Box3f b = getChild<Gadget>( 0 )->bound();

// cheat a little - shave a bit off to make it possible to
// select the node by having the drag region cover only the
Expand Down Expand Up @@ -501,6 +578,11 @@ void StandardNodeGadget::plugDirtied( const Gaffer::Plug *plug )
m_nodeEnabled = static_cast<const Gaffer::BoolPlug *>( plug )->getValue();
renderRequestSignal()( this );
}

if( ErrorGadget *e = errorGadget( /* createIfMissing = */ false ) )
{
e->removeError( plug );
}
}

void StandardNodeGadget::enter( Gadget *gadget )
Expand Down Expand Up @@ -702,3 +784,38 @@ void StandardNodeGadget::updatePadding()

contentsContainer()->setPadding( Box3f( V3f( -padding ), V3f( padding ) ) );
}

StandardNodeGadget::ErrorGadget *StandardNodeGadget::errorGadget( bool createIfMissing )
{
if( ErrorGadget *result = getChild<ErrorGadget>( g_errorGadgetName ) )
{
return result;
}

if( !createIfMissing )
{
return NULL;
}

ErrorGadgetPtr g = new ErrorGadget;
setChild( g_errorGadgetName, g );
return g.get();
}

void StandardNodeGadget::error( ConstPlugPtr plug, ConstPlugPtr source, const std::string &message )
{
// We could be on any thread at this point, so we
// use an idle callback to do the work of displaying the error
// on the main thread.
executeOnUIThread( boost::bind( &StandardNodeGadget::displayError, this, plug, message ) );
}

void StandardNodeGadget::displayError( ConstPlugPtr plug, const std::string &message )
{
// We need the const cast, because addError() needs non-const access to the plug
// in order to be able to connect to its signals. The plug we were passed in
// StandardNodeGadget::error() was const for a very good reason - we could be
// on any thread so modifying the plug would be a big no-no. But now we're back
// on the main thread, converting to non-const access is OK.
errorGadget()->addError( boost::const_pointer_cast<Plug>( plug ), message );
}

0 comments on commit 2f6f491

Please sign in to comment.