- Main Page
- Related Pages
- Modules
- Namespaces
- Classes
- Examples

# 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;
    }