
/* selbox.js
   Draw a selection box over an image on a web page
     OR
   Click a selection point over an image on a web page

   S.W. Adcock, February 2008
     rev July 2010 to resolve IE8 compatibility issues

   This file contains a set of javascript functions that will draw a 
   selection box on top of an image within a web page.
   The functions are intended to work on recent versions of browsers
   and have been tested on IE6, IE7 and Firefox 2.
   The technique employed for the box drawing is to display a div
   element (objDiv) on top of the image element (objImg).  The width
   and colour of the selection box can be modified by editing the
   corresponding entry in the css file.  The entry in the css file 
   should look something like:
     #selectionBox {
         position: absolute;
         visibility: hidden;
         width: 0px; height: 0px;
         border: 1px solid #ff0000;
     }

   There are three event-handling functions for the box drawing:
    1. capture the mouse position                      : mousemove
    2. capture when the user depresses a mouse button  : engage
    3. capture when the mouse button is released       : disengage

   For the point selection, there is just one event-handling
    function, to capture a mouse click: getPoint
*/

SELECTIONBOX = "selectionBox";      /* The id of the div element      */
IMAGE = "mapa";                     /* The id of the img element      */

var objDiv = null;                  /* The div element that displays 
                                       the selection box              */
var objImg = null;                  /* The img element underlying 
                                       the selection box              */
var begXY = { x: 0, y: 0 };         /* The page coordinates
                                       corresponding to the mousedown
                                       event                          */
var endXY = { x: 0, y: 0 };         /* The page coordinates
                                       corresponding to the most
                                       recent mousemove event         */
var begImgXY = { x: 0, y: 0 };      /* The image coordinates
                                       corresponding to the mousedown
                                       event                          */
var endImgXY = { x: 0, y: 0 };      /* The image coordinates
                                       corresponding to the 
                                       mouseup event                  */

var sDebug = false;

/* ------------------------------------------------------------------ */

/*  boxInit() should be called after the user initiates the 
      selection box option
    In the current mapserver application, this will be triggered by
      clicking on either the ZoomIn or Query icons
                                                                      */

/* ------------------------------------------------------------------ */

function boxInit()
{
  if ( checkObjExists(SELECTIONBOX) && checkObjExists(IMAGE) )
  { 
    objDiv = document.getElementById(SELECTIONBOX);
    objImg = document.getElementById(IMAGE);

    // Initially, capture only the mousedown event
    objImg.onmousedown = engage;
    return true;  
  }
  else
  {
    return false;
  } 
}

/* ------------------------------------------------------------------ */

/*  boxClose is called after the user terminates the selection box
      option.
    In addition to the cleanup performed by the boxEnd function, the
      mousedown event handler is released and the div and image
      objects are set to null                                         */

/* ------------------------------------------------------------------ */

function boxClose()
{
  boxEnd();

  if (objImg) objImg.onmousedown = null;

  objDiv = null;
  objImg = null;
}

/* ------------------------------------------------------------------ */

/*  boxEnd is called when either
    (a) a problem has been encountered in the drawing of the
        selection box.  This may arise if the user moves the mouse
        off the image.
    (b) the selection box has been successfully drawn
    The document-wide mouseup and mousemove event handlers are
    released, and the selection box is erased.
/* ------------------------------------------------------------------ */

function boxEnd()
{
  if (objDiv)
  { 
    objDiv.style.visibility = "hidden"; 
    objDiv.style.left = "0px";
    objDiv.style.top =  "0px";
    objDiv.style.width = "0px";
    objDiv.style.height = "0px";
  }
   
  document.onmouseup = null;    
  document.onmousemove = null;   
}

/* ------------------------------------------------------------------ */

/*  pointInit() should be called after the user initiates the 
      selection point option
    In the current mapserver application, this will be triggered by
      clicking on either the ZoomOut or Pan icons
                                                                      */

/* ------------------------------------------------------------------ */

function pointInit()
{
  if ( checkObjExists(IMAGE) )
  { 
    objImg = document.getElementById(IMAGE);

    // Capture only the mouse click event
    objImg.onclick = getPoint;
    return true;  
  }
  else
  {
    return false;
  } 
}

/* ------------------------------------------------------------------ */

/*  pointClose is called after the user terminates the selection
      point option.
    The mouseclick event handler is released and the image object is
      set to null                                                     */

/* ------------------------------------------------------------------ */

function pointClose()
{
  if (objImg) objImg.onclick = null;

  objImg = null;
}

/* ------------------------------------------------------------------ */

/* engage captures the mouseDown event and shows the zoom box.
   It is only active when the mouse is located over the img element.
                                                                      */ 

/* ------------------------------------------------------------------ */

function engage(evt)
{
  if ( evt == null ) evt = event;

  /* respond only to a left mouse button                              */
  var sButton = getButton(evt);

  if (sButton == "LEFT")
  {
    begXY = getPosition_XY(evt);
    begImgXY = getImageXY(begXY);

    /* double-check that the mouse position lies within the image box */
    if ( imgSanityCheck(evt, begXY, begImgXY, "engage:") )
    {
      /* turn on the display of the div element                       */
      objDiv.style.visibility = "visible"; 

      /* start capturing the mouseup and mousemove events for
         the entire document                                          */
      document.onmouseup = disengage;    
      document.onmousemove = getMouse;   
    }

  } // button = LEFT

  if (evt.preventDefault)
       evt.preventDefault();
    else
        evt.returnValue= false;
  return false;
}

/* ------------------------------------------------------------------ */

/* getMouse captures the mouse position, and moves the zoom box 
     If the mouse moves off the image (and also off the selection
     box div element), then abort the selection box drawing.
                                                                      */

/* ------------------------------------------------------------------ */

function getMouse(evt)
{
  if ( evt == null ) evt = event;

  if (evt.target)
  {
    targ = evt.target;
  }
  else if (evt.srcElement)
  {
    targ = evt.srcElement;
  }

  if (sDebug)
  {
    displaycoordIE(evt);
  }

  if (targ.id == IMAGE || targ.id == SELECTIONBOX)
  {
    endXY = getPosition_XY(evt);

    boxIt( objDiv, begXY, endXY);

    if (evt.preventDefault)
      evt.preventDefault();
    else
      evt.returnValue= false;
    return false;
    }
  else
  {
    boxEnd(); 
  }
}

/* ------------------------------------------------------------------ */

/* disengage handles the mouseup event
     It re-calculates the rectangle coordinates in terms of the
     map image.

     This event handler is cancelled if the mouse moves off the image.
                                                                      */

/* ------------------------------------------------------------------ */

function disengage(evt)
{
  if ( evt == null ) evt = event; 

  endXY = getPosition_XY(evt);

  boxIt( objDiv, begXY, endXY);

  endImgXY = getImageXY(endXY);

  if (imgSanityCheck(evt, endXY, endImgXY, "disengage:")) 
  {
    // take appropriate action on completing the selection box
    boxGo(begXY, endXY, begImgXY, endImgXY);
  }

  boxEnd();
 
  if (evt.preventDefault)
    evt.preventDefault();
  else
    evt.returnValue= false;
  
  return false;
}

/* ------------------------------------------------------------------ */

/* getPoint captures the mouseClick event.
   It is only active when the mouse is located over the img element.
                                                                      */ 

/* ------------------------------------------------------------------ */

function getPoint(evt)
{
  if ( evt == null ) evt = event;

  /* respond only to a left mouse button                              */
  var sButton = getButton(evt);

  if (sButton == "LEFT")
  {
    begXY = getPosition_XY(evt);
    begImgXY = getImageXY(begXY);

    /* double-check that the mouse position lies within the image box */
    if ( imgSanityCheck(evt, begXY, begImgXY, "engage:") )
    {
      // perform the selection point action
      pointGo(begXY,begImgXY);
    }

  } // button = LEFT

  if (evt.preventDefault)
       evt.preventDefault();
    else
        evt.returnValue= false;
  return false;
}

/* ------------------------------------------------------------------ */

function checkObjExists(objID)
{
  var obj = null;
  obj = document.getElementById(objID);
  if(obj)
  {
    return true;
  }
  else
  {
    alert ("Critical error: \n" +
           "element ID: '" + objID + "' does not exist!");
    return false; 
  }
}

/* ------------------------------------------------------------------ */

/* imgSanityCheck
     Check to be sure that the event's X,Y coordinates lie within
     the image

/* ------------------------------------------------------------------ */

function imgSanityCheck(evt, mouseXY, imageXY, msgPreamble)
{
  /* The coordinates of imageXY must lie within the range of 
     the image size
     The top left corner of the image is at (0,0)
     The lower right corner will be (img.width, img.height)          */

  if (sDebug)
  { 
    alert( "Image size: \n" + 
           "    X: " + objImg.width +
           ",   Y: " + objImg.height + "\n\n" + 
           "Mouse coordinates: \n\n" +
           "Relative to window: \n" + 
           "    X: " + mouseXY.x +
           ",   Y: " + mouseXY.y + "\n\n" + 
           "Relative to image: \n" + 
           "    X: " + imageXY.x +
           ",   Y: " + imageXY.y  );
  }

  if ( imageXY.x < 0 || imageXY.x > objImg.width ||
       imageXY.y < 0 || imageXY.y > objImg.height )
  { 
    alert( msgPreamble + "\n\n" +
           "Critical error: \n" +
           "Calculated position lies outside image box! \n\n" + 
           "Window coordinates: \n" + 
           "    X: " + mouseXY.x +
           ",   Y: " + mouseXY.y + "\n\n" + 
           "Image size: \n" + 
           "    X: " + objImg.width +
           ",   Y: " + objImg.height + "\n\n" + 
           "Image coordinates: \n" + 
           "    X: " + imageXY.x +
           ",   Y: " + imageXY.y  );
    return false;
  }

  return true;
}

/* ------------------------------------------------------------------ */

/* boxGo
     Successful completion of selection box drawing.  Do something
     with the results.
                                                                      */

/* ------------------------------------------------------------------ */

function boxGo(begXY, endXY, begImgXY, endImgXY)
{
  oRect = { left : 0, top : 0, right : 0, bottom : 0 };

  oRect = setRect(begImgXY, endImgXY);

  if (sDebug)
  { 
    alert( "Success!! \n\n" +
           "Window coordinates: \n" + 
           "    X(1): " + begXY.x +
           ",   Y(1): " + begXY.y + "\n" + 
           "    X(2): " + endXY.x +
           ",   Y(2): " + endXY.y + "\n\n" + 
           "Image size: \n" + 
           "    X: " + objImg.width +
           ",   Y: " + objImg.height + "\n\n" + 
           "Image box: \n" + 
           "    X(1): " + begImgXY.x +
           ",   Y(1): " + begImgXY.y + "\n" + 
           "    X(2): " + endImgXY.x +
           ",   Y(2): " + endImgXY.y + "\n\n" + 
           "Map box: \n" + 
           "    W: " + oRect.left +
           ",   N: " + oRect.top +
           "    E: " + oRect.right +
           ",   S: " + oRect.bottom  );
  }

  /* call the function (from gmap.js) to pass the image coordinates
     into the form boxes, to be passed back to the server            */
  boxOnDrawEnd(oRect);

  return;
}

/* ------------------------------------------------------------------ */

/* pointGo
     Successful completion of selection point action.  Do something
     with the results.
                                                                      */

/* ------------------------------------------------------------------ */

function pointGo(begXY, begImgXY)
{
  if (sDebug)
  { 
    alert( "Success!! \n\n" +
           "Window coordinates: \n" + 
           "    X: " + begXY.x +
           ",   Y: " + begXY.y + "\n\n" + 
           "Image size: \n" + 
           "    X: " + objImg.width +
           ",   Y: " + objImg.height + "\n\n" + 
           "Image point: \n" + 
           "    X: " + begImgXY.x +
           ",   Y: " + begImgXY.y  );
  }

  /* call the function (from gmap.js) to pass the image coordinate
     into the form boxes, to be passed back to the server            */
  boxOnPointEnd(begImgXY);

  return;
}

/* ------------------------------------------------------------------ */

/*  Transform the two X,Y coordinate pairs into a rectangle object,
     and set the display attributes of the div element accordingly    */
    
/* ------------------------------------------------------------------ */

function boxIt(r, begXY, endXY)
{
  oRect = { left : 0, top : 0, right : 0, bottom : 0 };

  oRect = setRect(begXY, endXY);

  r.style.left = oRect.left + "px";
  r.style.top = oRect.top + "px";
  r.style.width = (oRect.right - oRect.left) + "px" ;
  r.style.height = (oRect.bottom  - oRect.top) +"px";
}

/* ------------------------------------------------------------------ */

/*  Take two x,y coordinate pairs, and sort them into
     left, right, top, bottom coordinates of a rectangle              */

/* ------------------------------------------------------------------ */

function setRect(begXY, endXY)
{
  oRect = { left : 0, top : 0, right : 0, bottom : 0 };

  oRect.left = ( (endXY.x > begXY.x) ? begXY.x : endXY.x );
  oRect.top = ((endXY.y > begXY.y) ? begXY.y : endXY.y);
  oRect.right = ((endXY.x > begXY.x) ? endXY.x : begXY.x);
  oRect.bottom = ((endXY.y > begXY.y) ? endXY.y : begXY.y);

  return oRect;
}

/* ------------------------------------------------------------------ */

/* getButton: determine which mouse button was pressed                */

/* ------------------------------------------------------------------ */

function getButton(evt)
{
  var button = null;

  if (evt.which == null)
    /* IE case */    
    button = (evt.button < 2) ? "LEFT" :
                 ((evt.button == 4) ? "MIDDLE" : "RIGHT");
  else
    /* All others */
    button = (evt.which < 2) ? "LEFT" :
                 ((evt.which == 2) ? "MIDDLE" : "RIGHT");

  return button;
}

/* ------------------------------------------------------------------ */

/*  get the X,Y coordinates of the mouse within the image, given the
      coordinates of the images upper left corner relative to the 
      whole page
                                                                      */

/* ------------------------------------------------------------------ */

/* get the position of the mouse event within the image
     imgUL holds the x,y coordinates of the top left corner of the
     image, relative to the whole page.  The returned x,y 
     coordinates should lie between (0,0) and (img.width, img.height) */
/* ------------------------------------------------------------------ */

function getImageXY(ptMouse)
{
  var pt = { x:0, y:0};

  var imgUpperLeft = { x: 0, y: 0 };
  imgUpperLeft.x = findPosX(objImg);
  imgUpperLeft.y = findPosY(objImg);

  pt.x = ptMouse.x - imgUpperLeft.x;
  pt.y = ptMouse.y - imgUpperLeft.y;

  return pt;
}

/* ------------------------------------------------------------------ */

/*  get the x,y position of the mouse event, relative to the page as 
    a whole (the page, NOT the screen or the browser window)          */

/* ------------------------------------------------------------------ */

function getPositionXY(evt)
{
  var pt = { x:0, y:0};
  if (evt.pageX == null)
  { 
    /* IE case */ 
/*
    pt.x = evt.clientX + document.body.scrollLeft
			+ document.documentElement.scrollLeft
                        - document.body.clientLeft;
    pt.y = evt.clientY + document.body.scrollTop
			+ document.documentElement.scrollTop
                        - document.body.clientTop;
*/
    pt.x = evt.clientX + document.body.scrollLeft
			+ document.documentElement.scrollLeft
                        - document.body.clientLeft;
    pt.y = evt.clientY + document.body.scrollTop
			+ document.documentElement.scrollTop
                        - document.body.clientTop;

  }
  else
  {
    /* All others */
    pt.x = evt.pageX;
    pt.y = evt.pageY;
  }
  return pt;
}

/* ------------------------------------------------------------------ */

/*  get the x,y position of the mouse event, relative to the page as 
    a whole (the page, NOT the screen or the browser window)         

    IE7's clientX and clientY measurements may sometimes be a couple
    of pixels out. It turns out this is because in IE7 the clientX
    and clientY measurements start from (2,2) in standards mode,
    and (0,0) in quirks mode.
    
    The behaviour may be due to the different box models in quirks
    and strict modes. The viewport border is 2px wide.

    IE7 stores the offset in its document.documentElement.clientLeft
    and document.documentElement.clientTop properties. This code
    should calculate the correct cursor position in all current
    browsers.
                                                                      */

/* ------------------------------------------------------------------ */

function getPosition_XY(evt)
{
  evt = evt || window.event;

  var pt = {x:0, y:0};

  if (evt.pageX || evt.pageY)
  {
        pt.x = evt.pageX;
        pt.y = evt.pageY;
  } 
  else
  {
    pt.x = evt.clientX + 
            (document.documentElement.scrollLeft || 
            document.body.scrollLeft) - 
            document.documentElement.clientLeft;
    pt.y = evt.clientY + 
            (document.documentElement.scrollTop || 
            document.body.scrollTop) - 
            document.documentElement.clientTop;
  }
  return pt;
}

/* ------------------------------------------------------------------ */

/* get the x,y position of the top left corner of the named element

   getPosition and findPosX, findPosY do the same thing.  Both
   approaches yielded the same coordinates in my testing with IE6,
   IE7 and Firefox 2.  However, they may give different results with
   other browsers.
   getPosition is rather more complicated than findPosX, findPosY     */

/* ------------------------------------------------------------------ */

function getPosition(elementId)
{
  var element = document.getElementById(elementId);
  var left = 0;
  var top = 0;

  if (element != null)
  {
    // Try because sometimes errors on offsetParent after DOM changes.
    try
    {
      while (element.offsetParent)
      {
        // While we haven't got the top element in the DOM hierarchy
        // Add the offsetLeft           
        left += element.offsetLeft;
        // If my parent scrolls, then subtract the left scroll position
        if (element.offsetParent.scrollLeft)
        {
          left -= element.offsetParent.scrollLeft;
        }

        // Add the offsetTop
        top += element.offsetTop;
        // If my parent scrolls, then subtract the top scroll position
        if (element.offsetParent.scrollTop)
        {
          top -= element.offsetParent.scrollTop;
        }

        // Grab
        element = element.offsetParent;
      }
    }
    catch (e)
    {
        // Do nothing
    }
    
    // Add the top element left offset and the windows left scroll
    left += element.offsetLeft + document.body.scrollLeft;

    // Add the top element topoffset and the windows top scroll
    top += element.offsetTop + document.body.scrollTop;

    // Subtract the body's client left position.
    // Subtract the body's client top position.
    if (document.body.clientLeft)
    { 
      /* IE only */
      left -= document.body.clientLeft;
      top -= document.body.clientTop;
    }
  }
  return {x:left, y:top};
}

/* ------------------------------------------------------------------ */

function findPosX(obj)
{
  var curleft = 0;
  if (obj.offsetParent)
    while(1) 
    {
      curleft += obj.offsetLeft;
      if(!obj.offsetParent)
        break;
      obj = obj.offsetParent;
    }
  else if(obj.x)
    curleft += obj.x;
  return curleft;
}

/* ------------------------------------------------------------------ */

function findPosY(obj)
{
  var curtop = 0;
  if(obj.offsetParent)
    while(1)
    {
      curtop += obj.offsetTop;
      if(!obj.offsetParent)
        break;
      obj = obj.offsetParent;
    }
  else if(obj.y)
    curtop += obj.y;
  return curtop;
}

/* ------------------------------------------------------------------ */

/* For this debugging option to work in IE7 and IE8, the user must
   override the default behaviour of not allowing status bar updates
   via script:
   Choose Tools, Internet Options, Security, Custom Level and enable
   the option labelled "Allow status bar updates via script".
                                                                      */ 
/* ------------------------------------------------------------------ */

function displaycoordIE(evt)
{
  var pt = { x:0, y:0};
  if (evt.pageX == null)
  { 
    /* IE only */ 

    pt.x = evt.clientX + document.body.scrollLeft
			+ document.documentElement.scrollLeft
                        - document.body.clientLeft;
    pt.y = evt.clientY + document.body.scrollTop
			+ document.documentElement.scrollTop
                        - document.body.clientTop;

    msg1 = pt.x + " : " + pt.y;
    msg2 = evt.clientX + " : " + evt.clientY;
    msg3 = document.body.scrollLeft + " : " + document.body.scrollTop;
    msg4 = document.documentElement.scrollLeft + " : " + document.documentElement.scrollTop;
    msg5 = document.body.clientLeft + " : " + document.body.clientTop;
    msg6 = evt.x + " : " + evt.y;
    msg7 = findPosX(objImg) + " : " + findPosY(objImg);

    window.status = msg1 + " | " + msg2 + " | " + msg3 + " | " + msg4 + " | " + msg5 + " | " + msg6 + " | " + msg7;

  }

}

