lockEvent.cpp

//-
// ==========================================================================
// 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; 
}