//-
// ==========================================================================
// Copyright 1995,2006,2008 Autodesk, Inc. All rights reserved.
//
// Use of this software is subject to the terms of the Autodesk
// license agreement provided at the time of installation or download,
// or which otherwise accompanies this software in either electronic
// or hard copy form.
// ==========================================================================
//+

// lockEvent.cpp 
// 
// This plug-in demonstrates the API callbacks for node and plug locking. 
// These callbacks allow you to receive notification when the locked 
// status of a plug or node is queried internally. The API programmer has 
// option, upon receipt of the callback, to 'override' the lock state of 
// node or plug.  This is 'override' is controlled via a 'decision' variable 
// passed into the callback function.  The variable can hold two values 
//
//  1) decision = true  --> You want to accept the lock state and do  
//     whatever the internal default behavior is. 
//  2) decision = false --> You want to deny the lock state and do the
//     opposite of what Maya would usually do. 
//
// The flow of execution would be as follows ... 
//
// 1) Received a callback from Maya.  
// 2) What kind of event is this?
// 3) Do I want to allow this event?
// |-- 4) Yes, I do not want to OVERRIDE this event. decision = true.
// |-- 4) No, I want to OVERRIDE this event. decision = false.
// 5) Return from callback.  
//
//
// Example usage: 
//   sphere ; 
//
//   // Watch the translateX plug on the sphere we just created ...
//   lockEvent -a 3 nurbsSphere1.translateX; 
//   // Do not allow any changes to the plug. 
//   lockEvent -o true 
//
//   // Now you can try changes nurbsSphere1.translateX 's value 
//   // but you will not be allowed to do so.  
//   // 
//   setAttr "nurbsSphere1.translateX" 22
//


#include <maya/MIOStream.h>
#include <maya/MString.h> 
#include <maya/MArgList.h> 
#include <maya/MFnPlugin.h> 
#include <maya/MPxCommand.h> 
#include <maya/MSyntax.h> 
#include <maya/MArgDatabase.h> 
#include <maya/MMessage.h> 
#include <maya/MLockMessage.h> 
#include <maya/MObject.h> 
#include <maya/MSelectionList.h> 
#include <maya/MFn.h> 
#include <maya/MItSelectionList.h> 
#include <maya/MFnDependencyNode.h> 
#include <maya/MStringArray.h> 
#include <maya/MString.h>
#include <maya/MGlobal.h> 
#include <maya/MDagPath.h> 
#include <maya/MCallbackIdArray.h>

/////////
// Macro Definitions 
//
#define MEL_COMMAND_NAME  "lockEvent"
#define VENDOR_TAG		  "Autodesk"
#define PLUGIN_VERSION	  "1.0"

#define checkStdError(stat,msg)		\
    if ( MS::kSuccess != stat ) {	\
	        cerr << msg;			\
	        return MS::kFailure;    \
    }

////////
// Flags 
//

// Clear the registered callbacks ... 
//
#define kClearCBLong 	  "-clearCB" 
#define kClearCB          "-ccb" 
#define kClearCBDV		  false

// Toggle the override flag. Dictates if we should 
// obey the lock or override the lock 
// 
#define kOverrideLong     "-override"
#define kOverride         "-o"
#define kOverrideDV       false  

// Attach a new callback to a node. 
// 
#define kAttachLong       "-attach"
#define kAttach           "-a"
#define kAttachDV         0

// Macro used to save a little typing 
//
#define MLM               MLockMessage

/////////
// Class Definition 
//
class lockEvent : public MPxCommand 
{
public: 
    lockEvent( ); 
    virtual ~lockEvent( ); 
    
    MStatus        doIt( const MArgList & ); 
    MStatus        parseArgs( const MArgList &args ); 
	MCallbackId    installCallback( MItSelectionList & ); 
	static MSyntax newSyntax( );  	
    static void*   creator( ); 

	// Clear callback ids 
	static bool    clearCallbackIds();

private: 
	MSelectionList theList; 
	unsigned int fAttach;
	bool fOverrideFlag, fOverrideVal, fClearCB; 
}; 

static MCallbackIdArray callbackIds; 
static bool overrideMode = false; 

// Callback for DAG locking events. 
// 
void lockDagDecision( MDagPath &path, MDagPath &other, 
					  void *clientData, MLM::LockDAGEvent,
					  bool &decision ); 

// Other, node related events ..  
// 
void lockDecision( MObject &node, MObject &aux, 
				   void *clientData, MLM::LockEvent, 
				   bool &decision ); 

// Events relating to plugs. 
// 
void plugDecision( MPlug &p1, MPlug &p2, 
				   void *clientData, MLM::LockPlugEvent, 
				   bool &decision ); 

// Callback for global watchers (i.e. watch all plugs on a node).
//
void nodePlugDecision( MPlug &p1, MPlug &p2, 
						void *clientData, MLM::LockPlugEvent, 
						bool &decision ); 

// Standard API entry and exit points. 
//
MStatus initializePlugin( MObject obj );
MStatus uninitializePlugin( MObject obj );

// -------- 
// METHODS 
// 

lockEvent::lockEvent( ) : 
	fOverrideFlag(kOverrideDV), 
	fOverrideVal(false),
	fAttach(kAttachDV)
{
}

lockEvent::~lockEvent( ) 
{
}

MCallbackId lockEvent::installCallback( MItSelectionList &iter )
// 
// Description: 
//  Uses given iterator and callback type to attach a new callback on
//  a node, dag path, or plug. The selection iterator must contain a
//  valid selection item for the target callback type (fAttach). That is, 
//  if the callback type is three, then the iterator must contain 
//  a dependency node on it the next list item. 
//
{
	MStatus status; 

	MCallbackId id = 0; 
	MObject node, component; 
	MDagPath path; 

	switch (fAttach) { 
	case 1: { 
		status = iter.getDependNode( node ); 
		if ( status ) { 
			// Try to set the callback. Note: we check the status
			// flag at the end of the switch statement. 
			// 
			id = MLM::setNodeLockQueryCallback( node, lockDecision, 
												NULL, &status );
		}
	}	break;
	case 2: { 
		status = iter.getDagPath( path, component );
		if ( status ) { 
			// Try to set the callback. Note: we check the status
			// flag at the end of the switch statement. 
			// 
			id = MLM::setNodeLockDAGQueryCallback( path, lockDagDecision, 
												   NULL, &status ); 
		}
	} 	break;
	case 3: { 
		status = iter.getDependNode( node ); 
		MStringArray plugName; 
		iter.getStrings( plugName ); 
		// Now we have to parse the plug string. 
		// 
		if ( status && plugName.length() > 0 ) { 
			MFnDependencyNode depNode( node ); 
			MStringArray attrName; 
			plugName[0].split( '.', attrName ); 
			
			MPlug plug = depNode.findPlug( attrName[1], &status ); 
			if ( status ) { 
				// Try to set the callback. Note: we check the status
				// flag at the end of the switch statement. 
				// 
				id = MLM::setPlugLockQueryCallback( plug, plugDecision,	
													NULL,&status );
			}
		} else { 
			status = MS::kFailure; 
		}
	} break;	
	case 4: {
		status = iter.getDependNode( node ); 
		if ( status ) { 
			// Try to set the callback. Note: we check the status
			// flag at the end of the switch statement. 
			// 
			id = MLM::setPlugLockQueryCallback( node, nodePlugDecision, 
												NULL, &status ); 
		}
	} break; 
	default: 
		MGlobal::displayError( "Invalid callback attach type" ); 
		status = MS::kFailure; 
	}; 	

	MFnDependencyNode fnNode( node ); 
	
	// Check the status flag here and report any particular problems 
	// encountered.  It is possible for the callback attach routines 
	// to fail.
	//
	// This typically occurs when a callback has already been attached
	// to the node or plug. 
	//
	if ( !status || !id ) { 
		MString msg; 
		msg = "Unable to add callback for node "; 
		msg += fnNode.name(); 
		MGlobal::displayError( msg );
		status.perror( msg ); 
	} else { 
		// Store the result -- so we can clean up later -- and 
		// echo some useful information. 
		// 
		cerr << "Callback attached to " << fnNode.name(); 	
		cerr << "; attachment type = " << fAttach << endl;  
		callbackIds.append( (int)id ); 
	}
	return id; 
}

MStatus lockEvent::doIt( const MArgList &args ) 
//
// Description: 
//  Entry point 
//
{
	MStatus status; 
	int result = 0;
	
	// First check our arguments 
	//
	if ( !parseArgs( args ) ) { 
		return MS::kFailure; 
	}

	if ( fAttach ) { 
		MItSelectionList iter( theList, MFn::kDependencyNode, &status ); 
		for ( ; status && !iter.isDone(); iter.next() ) { 
			MCallbackId id = installCallback( iter ); 
			if ( id ) { 
				result ++; 
			} else { 
				status = MS::kFailure; 
			}
		}
	} else if ( fOverrideFlag ) { 
		// What to do when callback occurs. 
		overrideMode = fOverrideVal; 
	} else if ( fClearCB ) { 
		clearCallbackIds(); 
		result++; 
	}	 

	clearResult(); 
	// Let the caller know if the operation was successful. 
	// We just use an integer value here.  Anything > 0 is 
	// a success. 
	//
	setResult( result ); 

	return status; 
}

void *lockEvent::creator( )
// 
// Description: 
//  Create a new instance of this command. 
// 
{
	return new lockEvent; 
}

MStatus lockEvent::parseArgs( const MArgList &args )
{
	MStatus status; 
	MArgDatabase argData( syntax(), args ); 
	
	fAttach = kAttachDV; 
	fOverrideFlag = kOverrideDV; 
	fClearCB = kClearCBDV; 

	// begin-parse-args 

	if ( argData.isFlagSet( kClearCB ) ) { 
		fClearCB = !kClearCBDV; 
 	} 	

	if ( argData.isFlagSet( kOverride ) ) { 
		bool tmp;
		status = argData.getFlagArgument( kOverride, 0, tmp ); 
		if ( !status ) { 
			MGlobal::displayError( "override flag parsing failed" ); 
			return status;
		}
		fOverrideFlag = !kOverrideDV; 
		fOverrideVal = tmp; 
	}

	if ( argData.isFlagSet( kAttach ) ) { 
		unsigned int tmp;
		status = argData.getFlagArgument( kAttach, 0, tmp ); 
		if ( !status ) { 
			MGlobal::displayError( "attach flag parsing failed" ); 
			return status;
		}
		fAttach = tmp; 
	} 

	if ( fAttach ) { 
		status = argData.getObjects( theList ); 
		if ( theList.length() == 0 ) { 
			MString msg = "You must specify a node/plug to attach to!"; 
			MGlobal::displayError(msg); 
			status = MS::kFailure; 
		}
	}

	// Ensure that the caller did not specify too many arguments! 
	//
	if ( status && fAttach && fOverrideFlag ) { 
		MString msg = "You specified too many flags!" ;
		MGlobal::displayError(msg);  
		status = MS::kFailure; 
	} 
	
	// end-parse-args 
	
	return status; 
}

MSyntax lockEvent::newSyntax( )
{
	MSyntax syntax; 
	
	// begin-syntax
	syntax.addFlag( kClearCB, kClearCBLong ); 
	syntax.addFlag( kOverride, kOverrideLong, MSyntax::kBoolean ); 
	syntax.addFlag( kAttach, kAttachLong, MSyntax::kUnsigned ); 

	syntax.useSelectionAsDefault( true ); 
	syntax.setObjectType( MSyntax::kSelectionList, 0 ); 
	// end-syntax 

	return syntax; 
}

void nodePlugDecision( MPlug &p1, MPlug &p2, 
					   void *clientData, MLM::LockPlugEvent event, 
					   bool &decision )
//
// Description:  
//  The watcher for plug callbacks on entire nodes. This callback
//  is invoked whenever lock query occurs on any plug in a node. 
//
{ 
	MString msg, eventString;
	msg = "nodePlugDecision called"; 

	// Echo the received event type. 
	//
	
	switch (event) { 
	case MLM::kPlugLockAttr: 
		eventString = "kPlugLockAttr"; 
		break; 
	case MLM::kPlugUnlockAttr: 
		eventString = "kPlugUnlockAttr";
		break; 
	case MLM::kPlugAttrValChange:
		eventString = "kPlugAttrValChange"; 
		break;
	case MLM::kPlugRemoveAttr: 
		eventString = "kPlugRemoveAttr"; 
		break; 
	case MLM::kPlugRenameAttr:
		eventString = "kPlugRenameAttr";
		break;
	case MLM::kPlugConnect: 
		eventString = "kPlugConnect"; 
		break;
	case MLM::kPlugDisconnect: 
		eventString = "kPlugDisconnect"; 
		break; 
	default:
		eventString = "kLastPlug"; 
		break;
	}; 
			
	cerr << msg << "; event = " << eventString;	
	cerr << "; override = " << overrideMode << endl; 

	decision = !overrideMode; 
}	

void plugDecision( MPlug &p1, MPlug &p2, 
				   void *clientData, MLM::LockPlugEvent event, 
				   bool &decision )
// 
// Description: 
//   Callback function for plug locking events.  This callback 
//   is only invoked when the plug, p1, has its lock status 
//   queried. This callback shares the same event types 
//   as the nodePlugDecision callback. 
//
{ 
	MString msg, eventString;
	msg = "plugDecision called";

	switch (event) { 
	case MLM::kPlugLockAttr: 
		eventString = "kPlugLockAttr"; 
		break; 
	case MLM::kPlugUnlockAttr: 
		eventString = "kPlugUnlockAttr";
		break; 
	case MLM::kPlugAttrValChange:
		eventString = "kPlugAttrValChange"; 
		break;
	case MLM::kPlugRemoveAttr: 
		eventString = "kPlugRemoveAttr"; 
		break; 
	case MLM::kPlugRenameAttr:
		eventString = "kPlugRenameAttr";
		break;
	case MLM::kPlugConnect: 
		eventString = "kPlugConnect"; 
		break;
	case MLM::kPlugDisconnect: 
		eventString = "kPlugDisconnect"; 
		break; 
	default:
		eventString = "kInvalidPlug"; 
		break;
	}; 
			
	cerr << msg << "; event = " << eventString;	
	cerr << "; override = " << overrideMode << endl; 
	
	decision = !overrideMode;  
}	
	
void lockDagDecision( MDagPath &path, MDagPath &other, 
					  void *clientData, MLM::LockDAGEvent event, 
					  bool &decision )
// 
// Description: 
//  Callback that is invoked whenever a DAG element is involved
//  in a locking event. 
//  
{
	MString eventString; 
	cerr << "lockDagDecision called ";

	switch (event) { 
	case MLM::kGroup: 
		eventString = "kGroup"; 
		break; 
	case MLM::kUnGroup:
		eventString = "kUnGroup";
		break;
	case MLM::kReparent: 
		eventString = "kReparent"; 
		break;
	case MLM::kChildReorder: 
		eventString = "kChildReorder"; 
		break;
	case MLM::kCreateNodeInstance: 
		eventString = "kCreateNodeInstance"; 
		break;
	case MLM::kCreateChildInstance:
		eventString = "kCreateChildInstance"; 
		break;
	case MLM::kCreateParentInstance:
		eventString = "kCreateParentInstance"; 
		break;
	case MLM::kInvalidDAG: 
	default: 
		eventString = "kInvalid"; 	
	}; 
		
	cerr << "on " << eventString << " event"; 
	cerr << "; overrideMode = " << overrideMode << endl; 

	decision = !overrideMode; 
}

void lockDecision( MObject &node, MObject &attr, 
				   void *clientData, MLM::LockEvent event,
				   bool &decision )
// 
// Description: 
//  All other lock callback events are convered in this routine. 
//  This includes everything that is not DAG related.  
//
{
	MString eventString; 
	cerr << "lockDecision called "; 
	
	switch ( event ) { 
	case MLM::kDelete: 
		eventString = "kDelete"; 
		break;
	case MLM::kRename: 
		eventString = "kRename"; 
		break;
	case MLM::kLockNode: 
		eventString = "kLockNode"; 
		break; 
	case MLM::kUnlockNode: 
		eventString = "kUnlockNode"; 
		break; 
	case MLM::kAddAttr: 
		eventString = "kAddAttr"; 
		break;
	case MLM::kRemoveAttr: 
		eventString = "kRemoveAttr"; 
		break;
	case MLM::kRenameAttr: 
		eventString = "kRemoveAttr"; 
		break; 
	case MLM::kUnlockAttr: 
		eventString = "kUnlockAttr"; 
		break;
	case MLM::kLockAttr: 
		eventString = "kLockAttr"; 
		break;
	case MLM::kInvalid: 
	default: 
		eventString = "kInvalid"; 
	}; 

	cerr << "on " << eventString << " event";
	cerr << "; overrideMode = " << overrideMode << endl; 

	decision = !overrideMode; 
}

bool lockEvent::clearCallbackIds( ) 
// 
// Description: 
//  Removes all currently attached callbacks.
//
{
	unsigned int idCount = callbackIds.length(); 
	for ( unsigned int i = 0; i < idCount; i ++ ) { 
		cerr << "callback #" << i << "; id = " << (unsigned)callbackIds[i] << endl; 
		MMessage::removeCallback( (MCallbackId) callbackIds[i] );
	} 
	callbackIds.clear(); 
	return true; 
}

MStatus initializePlugin( MObject obj )
// 
// Load the plugin ...
//
{
	MStatus status;
	
	MFnPlugin plugin( obj, VENDOR_TAG, PLUGIN_VERSION, "Any" ); 

	status = plugin.registerCommand( MEL_COMMAND_NAME, 
									 lockEvent::creator, 
									 lockEvent::newSyntax ); 

	callbackIds.clear(); 
	return status; 
}

MStatus uninitializePlugin( MObject obj )
// 
// Unload the plugin ...
//
{
	MFnPlugin plugin( obj ); 
	
	MStatus status; 

	status = plugin.deregisterCommand( MEL_COMMAND_NAME ); 
	if ( status ) { 
		lockEvent::clearCallbackIds(); 
	}

	return status; 
}
