From 2f6f491419b7bbfd095461318afa2a8ab8571ffd Mon Sep 17 00:00:00 2001 From: John Haddon Date: Mon, 5 Jan 2015 08:55:43 +0000 Subject: [PATCH] Implemented error display for StandardNodeGadget. 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 #1115. --- include/GafferUI/StandardNodeGadget.h | 6 +- src/GafferUI/StandardNodeGadget.cpp | 139 ++++++++++++++++++++++++-- 2 files changed, 133 insertions(+), 12 deletions(-) diff --git a/include/GafferUI/StandardNodeGadget.h b/include/GafferUI/StandardNodeGadget.h index ee5b8bbd3c..1ab5176a95 100644 --- a/include/GafferUI/StandardNodeGadget.h +++ b/include/GafferUI/StandardNodeGadget.h @@ -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 : @@ -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; diff --git a/src/GafferUI/StandardNodeGadget.cpp b/src/GafferUI/StandardNodeGadget.cpp index eb5f5e4d13..066f5b6af5 100644 --- a/src/GafferUI/StandardNodeGadget.cpp +++ b/src/GafferUI/StandardNodeGadget.cpp @@ -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() ) + : 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() ) + { + removeError( static_cast( plug ) ); + } + } + + ImageGadgetPtr m_image; + + struct PlugEntry + { + std::string error; + boost::signals::scoped_connection parentChangedConnection; + }; + + typedef std::map PlugErrors; + PlugErrors m_errors; + +}; + +////////////////////////////////////////////////////////////////////////// +// StandardNodeGadget implementation +////////////////////////////////////////////////////////////////////////// + IE_CORE_DEFINERUNTIMETYPED( StandardNodeGadget ); NodeGadget::NodeGadgetTypeDescription StandardNodeGadget::g_nodeGadgetTypeDescription( Gaffer::Node::staticTypeId() ); @@ -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 ), @@ -184,6 +269,7 @@ 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( node.get() ) ) { @@ -191,8 +277,8 @@ StandardNodeGadget::StandardNodeGadget( Gaffer::NodePtr node, LinearContainer::O 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 ) ); @@ -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( 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 @@ -501,6 +578,11 @@ void StandardNodeGadget::plugDirtied( const Gaffer::Plug *plug ) m_nodeEnabled = static_cast( plug )->getValue(); renderRequestSignal()( this ); } + + if( ErrorGadget *e = errorGadget( /* createIfMissing = */ false ) ) + { + e->removeError( plug ); + } } void StandardNodeGadget::enter( Gadget *gadget ) @@ -702,3 +784,38 @@ void StandardNodeGadget::updatePadding() contentsContainer()->setPadding( Box3f( V3f( -padding ), V3f( padding ) ) ); } + +StandardNodeGadget::ErrorGadget *StandardNodeGadget::errorGadget( bool createIfMissing ) +{ + if( ErrorGadget *result = getChild( 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 ), message ); +}