/**
 * This class provides grab and drag aspect services for objects.  
 * An aspect is the central element
 * of a software engineering paradigm, Aspect Oriented Programming.  
 * Aspect Oriented Programming complements object-oriented
 * software engineering.  The aspect properties of this aspect are woven into target objects
 * as specified by the point cut definitions.
 * <br><br>
 * This aspect makes liberal use of the lexical scoping features of JavaScript, for performance reasons
 * during event handling for 'mousemove'. 
 * Some methods bound to document object model objects
 * contain lexically scoped references (i.e., <code>wgtPlate</code>) back to a custom js object, 
 * but the references are via non-object
 * values (lexically scoped <code>var</code>s, not <code>this.</code> references).  So memory leakage due to 
 * circular reference should be non-existant or minimal.  For a description of lexical scoping as it applies
 * to JavaScript, see David Flanagan's "JavaScript: The Definitive Guide", 4th Edition (O'Reilly : Sebastapol),
 * November 2001.
 * <br><br>
 * <a href="mailto:ehudsons@andrew.cmu.edu">Ellen Hudson-Snyder</a>, 
 * <a href="mailto:evedar@andrew.cmu.edu">Elvin Vedar</a>,
 * <a href="mailto:cbalz@andrew.cmu.edu">Christopher M. Balz</a>.
 * <br><br>CVS Version Info:<br>
 *  $Id: GrabAndDrag.js,v 1.7 2005/12/03 18:13:11 cbalz Exp $     
 * <br><br>
 * @extends <code>gov.nasa.centers.ames.gigapan.utils.aspect_js.Aspect</code>
 * @aspect-point-cut <code>wgtPlate</code> woven by <code>makeInterTypeDeclarations</code>  Object properties 
 *                                         woven into <code>wgtPlate</code>.
 * @aspect-prop <code>booGrabbed</code> of aspect-point-cut <code>wgtPlate</code> Whether or not the draggable 
 *                                      object has been grabbed.
 * @aspect-prop <code>intDraggedXpx</code> of aspect-point-cut <code>wgtPlate</code> How much the draggable 
 *                                         object has been dragged in the horizontal direction.
 * @aspect-prop <code>intDraggedYpx</code> of aspect-point-cut <code>wgtPlate</code> How much the draggable 
 *                                         object has been dragged in the vertical direction.
 * @aspect-prop <code>GrabAndDrag_funOnMouseDownHandler</code> of aspect-point-cut <code>wgtPlate</code> 
 *                                                             What the draggable object should do when grabbed.
 * @aspect-prop <code>GrabAndDrag_funOnMouseUpHandler</code> of aspect-point-cut <code>wgtPlate</code>  What 
 *                                        the draggable object should do when released.
 * @aspect-prop <code>onmousedown</code> of aspect-point-cut <code>wgtPlate</code>  What the draggable object 
 *                                       should do when dragged.  Note that this method is 
 *                                       an empty function when the draggable object is not grabbed, and a different
 *                                       function when grabbed.
 * @aspect-point-cut <code>initImageLayers</code> woven by <code>insert-after</code> A place to do 
 *                                       event registration.
 * @aspect-prop <code>strRegisterForOnMouseDown + strRegisterForOnMouseUp</code> of aspect-point-cut
 *                                                                               <code>initImageLayers</code> 
 *                                                                               Code to register for events 
 *                                                                               on mouse-down and on mouse-up.
 * @aspect-point-cut <code>receiveEvent</code> woven by <code>insertIntoSwitchStmt</code> <code>switch</code> 
 *                                             statements used for handling events in <code>wgtPlate</code>.
 */

GrabAndDrag.prototype = new Aspect();


/**
 * Create a new <code>GrabAndDrag</code> aspect.
 * @param pObjParent <code>Object</code>  The parent, if any, of this aspect.
 * @param pHshTargets <code>Object</code>  A hash of names of target objects and references to those objects.
 */
function GrabAndDrag(pObjParent, pHshTargets) {
    this.superC();
    
    var 


        /**
         * This method is run on an interval of 333 milliseconds under Internet Explorer v6 and below, on drag
         * in order to deliver some improvement in cursor style change behavior (i.e., actually
         * showing the <code>move</code> cursor style when set to do so).
         */
        Window_funSetHandCursor = function() {
            window.document.getElementById('mainBody').style.cursor = "move";
        },

        wgtPlate = pHshTargets[ pObjParent.wgtPlate.strId ],         
            domPlate = wgtPlate.getDomRef(),
            domContainerStyle = document.getElementById('mainBody').style,
    
            strRegisterForOnMouseDown = " \n  self.gloScope.registerEvent( '" + wgtPlate.strId + "', " + 
            " this, '" + wgtPlate.strId + "', 'onmousedown' ); ", 
    
            strRegisterForOnMouseUp = " \n  self.gloScope.registerEvent( '" + wgtPlate.strId + "', " + 
            " this, '" + wgtPlate.strId + "', 'onmouseup' ); \n", 
    
            booIsIE6OrLess = (self.gloScope.objIs.ie && self.gloScope.objIs.major < 7),


            /**
             * A lexically-scoped 'var' method to grab the draggable object.
             * @param pStrEventSourceElementId <code>string</code> The i.d. of the custom JavaScript widget 
             *                                                     that originated the event.
             * @param pStrEventType <code>string</code>  The type of event.
             * @param pObjEvent <code>string</code>  The document object model event object itself.
             */
            GrabAndDrag_funOnMouseDownHandler = function(pStrEventSourceElementId, pStrEventType, pObjEvent) {
                var domPlate = this.getDomRef(), wgtPlate = this, cachedOnmouseup = domPlate.onmouseup;
        
                this.booGrabbed = true;
                domContainerStyle.cursor = "move";
                wgtPlate.intOldX = domPlate.intOldX = pObjEvent.x; // pObjEvent.x;
                wgtPlate.intOldY = domPlate.intOldY = pObjEvent.y; // pObjEvent.y;
                if ( booIsIE6OrLess ) { 
                    self.gloScope.intSetCursorToHandInterval = setInterval(Window_funSetHandCursor, 333);
                }
                // window.status = "at onmousedown; grabbed: " + wgtPlate.booGrabbed;
                if ( booIsIE6OrLess ) {
                    domPlate.onmousemove = GrabAndDrag_gigamousemove;
                } else {
                    wgtPlate.onmousemove = GrabAndDrag_gigamousemove;
                }
                return false;
            },
    
    
            /**
             * A lexically-scoped 'var' method to perform the actual drag movement.  We have
             * two similar methods to handle action: one for the Opera web browser and the others
             * for all other browsers.  This has been necessary because Opera 8.5 
             * unfortunately "blows up" when 
             * touching the parameter to this method.
             * @param pObjEvent <code>string</code>  The document object model event object itself.
             */
            GrabAndDrag_gigamousemove = (self.gloScope.objIs.opera) ? 
                function(pObjEvent) {
                    // Touch 'pObjEvent' on Opera 8.5 and it blows up.
                    var x = window.event.clientX;
                    var y = window.event.clientY;
        
                    var intNewTopPosPx, intNewLeftPosPx, 
                    myStyle = (booIsIE6OrLess ? this.style : this.getDomRef().style), 
                    intOldTopPx = parseInt(myStyle.top), 
                    intOldLeft = parseInt(myStyle.left),
                    intMoveXAmount, intMoveYAmount;
        
                    intMoveYAmount = y - this.intOldY;
                    intMoveXAmount = x - this.intOldX;
        
                    intNewTopPosPx  = intOldTopPx  + intMoveYAmount;
                    intNewLeftPosPx = intOldLeft   + intMoveXAmount;
        
                    if (intMoveYAmount != 0) {
                        if (intMoveXAmount > 0) {
                            wgtPlate.movePlateRight( (intMoveXAmount) );
                        } else {
                            wgtPlate.movePlateLeft( (intMoveXAmount  * -1) );
                        }
                    }
        
                    if (intMoveYAmount != 0) {
                        if (intMoveYAmount > 0) {
                            wgtPlate.movePlateDown( (intMoveYAmount) );
                        } else {
                            wgtPlate.movePlateUp( (intMoveYAmount  * -1) );
                        }
                    }
        
                    this.intOldY = y;
                    this.intOldX = x;
                    domContainerStyle.cursor = "move";

                    if ( pObjEvent.stopPropagation) {  // DOM Level 2                    
                       pObjEvent.stopPropagation(); 
                    } else {  // IE 6 and below
                       pObjEvent.cancelBubble = true;                 
                    }
                    return false;
                } 
    
                : 


                /**
                 * A lexically-scoped 'var' method to perform the actual drag movement.  We have
                 * two similar methods to handle action: one for the Opera web browser and the others
                 * for all other browsers.  This has been necessary because Opera 8.5 
                 * unfortunately "blows up" when 
                 * touching the parameter to this method.
                 * @param pObjEvent <code>string</code>  The document object model event object itself.
                 */
                GrabAndDrag_gigamousemove = function(pObjEvent) {
                    // window.status = "at onmousemove; grabbed: " + wgtPlate.booGrabbed;
                    if (typeof pObjEvent == "undefined" || !pObjEvent) {
                        pObjEvent = window.event; // IE 6 and below event model; IE 7?                         
                    } else {                
                        pObjEvent.x = pObjEvent.clientX;
                        pObjEvent.y = pObjEvent.clientY;
                    }
                    // window.status = "x: " + pObjEvent.x; 
        
                    var intNewTopPosPx, intNewLeftPosPx, 
                    myStyle = (booIsIE6OrLess ? this.style : this.getDomRef().style), 
                    intOldTopPx = parseInt(myStyle.top), 
                    intOldLeft = parseInt(myStyle.left),
                    intMoveXAmount, intMoveYAmount;
        
                    intMoveYAmount = pObjEvent.y - this.intOldY;
                    intMoveXAmount = pObjEvent.x - this.intOldX;
        
                    intNewTopPosPx  = intOldTopPx  + intMoveYAmount;
                    intNewLeftPosPx = intOldLeft   + intMoveXAmount;
        
                    if (intMoveYAmount != 0) {
                        if (intMoveXAmount > 0) {
                            wgtPlate.movePlateRight( (intMoveXAmount) );
                        } else {
                            wgtPlate.movePlateLeft( (intMoveXAmount  * -1) );
                        }
                    }
        
                    if (intMoveYAmount != 0) {
                        if (intMoveYAmount > 0) {
                            wgtPlate.movePlateDown( (intMoveYAmount) );
                        } else {
                            wgtPlate.movePlateUp( (intMoveYAmount  * -1) );
                        }
                    }
        
                    this.intOldY = pObjEvent.y;
                    this.intOldX = pObjEvent.x;

                    if ( pObjEvent.stopPropagation) {  // DOM Level 2                    
                       pObjEvent.stopPropagation(); 
                    } else {  // IE 6 and below
                       pObjEvent.cancelBubble = true;                 
                    }
                    return false;
                },
    

                /**
                 * A lexically-scoped 'var' method to release the draggable object.
                 * @param pStrEventSourceElementId <code>string</code> The i.d. of the custom JavaScript widget 
                 *                                                     that originated the event.
                 * @param pStrEventType <code>string</code>  The type of event.
                 * @param pObjEvent <code>string</code>  The document object model event object.
                 */                               
                GrabAndDrag_funOnMouseUpHandler = function(pStrEventSourceElementId, pStrEventType, pObjEvent) {
                    this.booGrabbed = false;
                    if (booIsIE6OrLess) {
                        domPlate.onmousemove = null;
                    } else {  
                        wgtPlate.onmousemove = function() {};
                    }
                    if ( booIsIE6OrLess ) { 
                        clearInterval(self.gloScope.intSetCursorToHandInterval);
                    }
                    domContainerStyle.cursor = "default";
                    return false;
                };
    
    

                /**
                 * A method bound to the document object model <code>Plate</code> object for performing
                 * low-level tasks pertaining to grabbing the <code>plate</code> object.  
                 * The document object model object of each target of this aspect would need this method.
                 * @param pObjEvent <code>string</code>  The document object model event object.
                 */
                domPlate.onmousedown = function(pObjEvent) { 
                    if (typeof pObjEvent == "undefined" || !pObjEvent) {
                        pObjEvent = window.event;
                    }
                    self.Environment.handleEvent("plate", "onmousedown", pObjEvent); 
                    return false;
                };   

    
                /**
                 * A method bound to the document object model <code>Plate</code> object for performing
                 * low-level tasks pertaining to releasing the <code>plate</code> object.  
                 * The document object model object of each target of this aspect would need this method.
                 * @param pObjEvent <code>string</code>  The document object model event object.
                 */
                domPlate.onmouseup = function(pObjEvent) { 
                    if (typeof pObjEvent == "undefined" || !pObjEvent) {
                        pObjEvent = window.event;
                    }
                    self.Environment.handleEvent("plate", "onmouseup", pObjEvent); 
                    return false;
                };
    
                // strOnMouseDown = "function() { self.Environment.handleEvent(this.id, 'onmousedown', null); return true; } ",
    
                this.definePointCut(
                    wgtPlate, "makeInterTypeDeclarations", { 
                    "booGrabbed"      : false, 
                        "intDraggedXpx"   : 0,
                        "intDraggedYpx"   : 0,
                        "onmousedown"     : GrabAndDrag_funOnMouseDownHandler,
                        "onmouseup"       : GrabAndDrag_funOnMouseUpHandler,
                        "onmousemove"     : function() {} } );                       
        
                this.definePointCut(wgtPlate, "insertAfter", 
                                    new MethodYarn( "initImageLayers", wgtPlate.initImageLayers, strRegisterForOnMouseDown + strRegisterForOnMouseUp ) );    
        
        
                this.definePointCut(wgtPlate, "insertIntoSwitchStmt", 
                                    new SwitchYarn("receiveEvent", wgtPlate.receiveEvent, 
                                                   "  case 'onmousedown' : \n this.onmousedown(pStrEventSourceElementId, pStrEventType, pObjEvent); \n break; \n " +
                                                   "  case 'onmouseup' : \n   this.onmouseup(pStrEventSourceElementId, pStrEventType, pObjEvent); \n break; \n ", 
                                                   " case \"plate\" :  ", /case "plate"\s?:/, 
                                                   "switch (pStrEventType) {", /switch\s*\(pStrEventType\)\s*\{/ ) );
        
                this.weave();
                // alert(wgtPlate.initImageLayers);
                // alert(wgtPlate.receiveEvent);
}
