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

Prompt user when exiting would lose unsaved changes. #69

Merged
merged 5 commits into from
Apr 1, 2013
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
10 changes: 7 additions & 3 deletions include/Gaffer/ScriptNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@ class ScriptNode : public Node
/// operations.
StringPlug *fileNamePlug();
const StringPlug *fileNamePlug() const;
/// Returns a plug which is used to flag when the script has had changes
/// made since the last call to save().
BoolPlug *unsavedChangesPlug();
const BoolPlug *unsavedChangesPlug() const;
/// Loads the script specified in the filename plug.
virtual void load();
/// Saves the script to the file specified by the filename plug.
Expand Down Expand Up @@ -243,13 +247,13 @@ class ScriptNode : public Node

ScriptExecutedSignal m_scriptExecutedSignal;
ScriptEvaluatedSignal m_scriptEvaluatedSignal;

StringPlugPtr m_fileNamePlug;


ContextPtr m_context;

void childRemoved( GraphComponent *parent, GraphComponent *child );
void plugSet( Plug *plug );

static size_t g_firstPlugIndex;

};

Expand Down
53 changes: 53 additions & 0 deletions python/GafferTest/ScriptNodeTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,59 @@ def testLoadingMovedScriptDoesntKeepOldFileName( self ) :
s.load()

self.assertEqual( s["fileName"].getValue(), "/tmp/test2.gfr" )

def testUnsavedChanges( self ) :

s = Gaffer.ScriptNode()

self.assertEqual( s["unsavedChanges"].getValue(), False )

s["node"] = GafferTest.AddNode()
self.assertEqual( s["unsavedChanges"].getValue(), True )

s["fileName"].setValue( "/tmp/test.gfr" )
s.save()
self.assertEqual( s["unsavedChanges"].getValue(), False )

s["node"]["op1"].setValue( 10 )
self.assertEqual( s["unsavedChanges"].getValue(), True )

s.save()
self.assertEqual( s["unsavedChanges"].getValue(), False )

with Gaffer.UndoContext( s ) :
s["node"]["op1"].setValue( 20 )
self.assertEqual( s["unsavedChanges"].getValue(), True )

s.save()
self.assertEqual( s["unsavedChanges"].getValue(), False )

s.undo()
self.assertEqual( s["unsavedChanges"].getValue(), True )

s.save()
self.assertEqual( s["unsavedChanges"].getValue(), False )

s.redo()
self.assertEqual( s["unsavedChanges"].getValue(), True )

s.save()
self.assertEqual( s["unsavedChanges"].getValue(), False )

s["node2"] = GafferTest.AddNode()
self.assertEqual( s["unsavedChanges"].getValue(), True )

s.save()
self.assertEqual( s["unsavedChanges"].getValue(), False )

s["node2"]["op1"].setInput( s["node"]["sum"] )
self.assertEqual( s["unsavedChanges"].getValue(), True )

s.save()
self.assertEqual( s["unsavedChanges"].getValue(), False )

s.load()
self.assertEqual( s["unsavedChanges"].getValue(), False )

def tearDown( self ) :

Expand Down
23 changes: 21 additions & 2 deletions python/GafferUI/ApplicationMenu.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,26 @@ def quit( menu ) :
scriptWindow = menu.ancestor( GafferUI.ScriptWindow )
application = scriptWindow.scriptNode().ancestor( Gaffer.ApplicationRoot.staticTypeId() )

## \todo Check scripts aren't modified
unsavedNames = []
for script in application["scripts"].children() :
if script["unsavedChanges"].getValue() :
f = script["fileName"].getValue()
f = f.rpartition( "/" )[2] if f else "untitled"
unsavedNames.append( f )

if unsavedNames :

dialogue = GafferUI.ConfirmationDialogue(
"Discard Unsaved Changes?",
"The following files have unsaved changes : \n\n" +
"\n".join( [ " - " + n for n in unsavedNames ] ) +
"\n\nDo you want to discard the changes and quit?",
confirmLabel = "Discard and Quit"
)

if not dialogue.waitForConfirmation() :
return

for script in application["scripts"].children() :
application["scripts"].removeChild( script )

Expand Down Expand Up @@ -105,4 +124,4 @@ def __savePreferences( button ) :
application = scriptWindow.scriptNode().ancestor( Gaffer.ApplicationRoot.staticTypeId() )
application.savePreferences()
button.ancestor( type=GafferUI.Window ).setVisible( False )


9 changes: 8 additions & 1 deletion python/GafferUI/ScriptNodeUI.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
##########################################################################
#
# Copyright (c) 2012, John Haddon. All rights reserved.
# Copyright (c) 2013, Image Engine Design Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
Expand Down Expand Up @@ -37,8 +38,14 @@
import Gaffer
import GafferUI

GafferUI.PlugValueWidget.registerCreator(
Gaffer.ScriptNode.staticTypeId(),
"unsavedChanges",
None
)

GafferUI.PlugValueWidget.registerCreator(
Gaffer.ScriptNode.staticTypeId(),
"frameRange",
GafferUI.CompoundNumericPlugValueWidget
)
)
23 changes: 20 additions & 3 deletions python/GafferUI/ScriptWindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,21 @@ def getLayout( self ) :

return self.__listContainer[1]

def _acceptsClose( self ) :

if not self.__script["unsavedChanges"].getValue() :
return True

f = self.__script["fileName"].getValue()
f = f.rpartition( "/" )[2] if f else "untitled"

dialogue = GafferUI.ConfirmationDialogue(
"Discard Unsaved Changes?",
"The file %s has unsaved changes. Do you want to discard them?" % f,
confirmLabel = "Discard"
)
return dialogue.waitForConfirmation()

def __closed( self, widget ) :

scriptParent = self.__script.parent()
Expand All @@ -95,7 +110,7 @@ def __closed( self, widget ) :

def __scriptPlugChanged( self, plug ) :

if plug.isSame( self.__script["fileName"] ) :
if plug.isSame( self.__script["fileName"] ) or plug.isSame( self.__script["unsavedChanges"] ) :
self.__updateTitle()

def __updateTitle( self ) :
Expand All @@ -107,8 +122,10 @@ def __updateTitle( self ) :
else :
d, n, f = f.rpartition( "/" )
d = " - " + d

self.setTitle( "Gaffer : %s %s" % ( f, d ) )

u = " *" if self.__script["unsavedChanges"].getValue() else ""

self.setTitle( "Gaffer : %s%s%s" % ( f, u, d ) )

__instances = [] # weak references to all instances - used by acquire()
## Returns the ScriptWindow for the specified script, creating one
Expand Down
8 changes: 8 additions & 0 deletions src/Gaffer/Action.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,19 @@ void Action::enact( GraphComponentPtr subject, const Function &doFn, const Funct
ActionPtr a = new Action( doFn, undoFn );
a->doAction();
s->m_actionAccumulator->push_back( a );
{
UndoContext undoDisabled( s, UndoContext::Disabled );
s->unsavedChangesPlug()->setValue( true );
}
s->actionSignal()( s, a.get(), Do );
}
else
{
doFn();
if( s && subject != s->unsavedChangesPlug() )
{
s->unsavedChangesPlug()->setValue( true );
}
}

}
Expand Down
38 changes: 30 additions & 8 deletions src/Gaffer/ScriptNode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,15 @@ GAFFER_DECLARECONTAINERSPECIALISATIONS( ScriptContainer, ScriptContainerTypeId )

IE_CORE_DEFINERUNTIMETYPED( ScriptNode );

size_t ScriptNode::g_firstPlugIndex = 0;

ScriptNode::ScriptNode( const std::string &name )
: Node( name ), m_selection( new StandardSet ), m_undoIterator( m_undoList.end() ), m_context( new Context )
{
m_fileNamePlug = new StringPlug( "fileName", Plug::In, "", Plug::Default & ~Plug::Serialisable );
addChild( m_fileNamePlug );
storeIndexOfNextChild( g_firstPlugIndex );

addChild( new StringPlug( "fileName", Plug::In, "", Plug::Default & ~Plug::Serialisable ) );
addChild( new BoolPlug( "unsavedChanges", Plug::In, false, Plug::Default & ~Plug::Serialisable ) );

CompoundPlugPtr frameRangePlug = new CompoundPlug( "frameRange", Plug::In );
IntPlugPtr frameStartPlug = new IntPlug( "start", Plug::In, 1 );
Expand Down Expand Up @@ -133,6 +137,10 @@ void ScriptNode::undo()
for( ActionVector::reverse_iterator it=(*m_undoIterator)->rbegin(); it!=(*m_undoIterator)->rend(); it++ )
{
(*it)->undoAction();
{
UndoContext undoDisabled( this, UndoContext::Disabled );
unsavedChangesPlug()->setValue( true );
}
actionSignal()( this, it->get(), Action::Undo );
}
}
Expand All @@ -151,6 +159,10 @@ void ScriptNode::redo()
for( ActionVector::iterator it=(*m_undoIterator)->begin(); it!=(*m_undoIterator)->end(); it++ )
{
(*it)->doAction();
{
UndoContext undoDisabled( this, UndoContext::Disabled );
unsavedChangesPlug()->setValue( true );
}
actionSignal()( this, it->get(), Action::Redo );
}
m_undoIterator++;
Expand Down Expand Up @@ -227,14 +239,24 @@ void ScriptNode::deleteNodes( Node *parent, const Set *filter )

StringPlug *ScriptNode::fileNamePlug()
{
return m_fileNamePlug;
return getChild<StringPlug>( g_firstPlugIndex );
}

const StringPlug *ScriptNode::fileNamePlug() const
{
return m_fileNamePlug;
return getChild<StringPlug>( g_firstPlugIndex );
}

BoolPlug *ScriptNode::unsavedChangesPlug()
{
return getChild<BoolPlug>( g_firstPlugIndex + 1 );
}

const BoolPlug *ScriptNode::unsavedChangesPlug() const
{
return getChild<BoolPlug>( g_firstPlugIndex + 1 );
}

void ScriptNode::execute( const std::string &pythonScript, Node *parent )
{
throw IECore::Exception( "Cannot execute scripts on a ScriptNode not created in Python." );
Expand Down Expand Up @@ -282,22 +304,22 @@ const Context *ScriptNode::context() const

IntPlug *ScriptNode::frameStartPlug()
{
return getChild<CompoundPlug>( "frameRange" )->getChild<IntPlug>( "start" );
return getChild<CompoundPlug>( g_firstPlugIndex + 2 )->getChild<IntPlug>( 0 );
}

const IntPlug *ScriptNode::frameStartPlug() const
{
return getChild<CompoundPlug>( "frameRange" )->getChild<IntPlug>( "start" );
return getChild<CompoundPlug>( g_firstPlugIndex + 2 )->getChild<IntPlug>( 0 );
}

IntPlug *ScriptNode::frameEndPlug()
{
return getChild<CompoundPlug>( "frameRange" )->getChild<IntPlug>( "end" );
return getChild<CompoundPlug>( g_firstPlugIndex + 2 )->getChild<IntPlug>( 1 );
}

const IntPlug *ScriptNode::frameEndPlug() const
{
return getChild<CompoundPlug>( "frameRange" )->getChild<IntPlug>( "end" );
return getChild<CompoundPlug>( g_firstPlugIndex + 2 )->getChild<IntPlug>( 1 );
}

void ScriptNode::childRemoved( GraphComponent *parent, GraphComponent *child )
Expand Down
6 changes: 6 additions & 0 deletions src/GafferBindings/ScriptNodeBinding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ class ScriptNodeWrapper : public NodeWrapper<ScriptNode>

deleteNodes();
execute( s );

UndoContext undoDisabled( this, UndoContext::Disabled );
unsavedChangesPlug()->setValue( false );
}

virtual void save() const
Expand All @@ -146,6 +149,9 @@ class ScriptNodeWrapper : public NodeWrapper<ScriptNode>
{
throw IECore::IOException( "Failed to write to \"" + fileName + "\"" );
}

UndoContext undoDisabled( const_cast<ScriptNodeWrapper *>( this ), UndoContext::Disabled );
const_cast<BoolPlug *>( unsavedChangesPlug() )->setValue( false );
}

private :
Expand Down