/*
JSgui/ygwm.js
created Feb  5 08:20:34 2009 by whygee@f-cpu.org
version Feb  6 14:19:55 2009
version Feb  6 22:44:56 2009 with minimize/maximize
it took less than 2 days of work to get this :-)
version Feb  9 02:15:33 2009  added ygwm.erase_window
   and a callback to close the window at the end of a move
version Feb  9 14:42:32 2009 : scroll side-effect mitigated
version Feb 11 00:11:06 2009 : feedback from Stéphane Moriaux, style changes, focus etc.
version Feb 11 22:27:33 2009 : getElement bug found by Fred on Chrome
version Feb 12 02:21:19 2009 : ygwm.slowDisplay
version Feb 13 03:13:42 2009 : dynamic detection of slow display
version Feb 15 01:46:31 2009 : Seamonkey "selection issue" spotted
version Feb 15 04:09:09 2009 : autoscroll added \o/
version Feb 16 01:23:49 2009 : autoscroll with threshold, getByName != getByClass
version Feb 19 00:19:55 2009
20090707 : moved to JSgui
20090709 : added some features :
  obj.reframe : externally set boolean, set to true if you don't want it to go outside the window
  obj.hide_all(), obj.show_all() : as the name says, called internally or externally to hide/show the whole window
  new calling option that allows the window to be hidden when clicked on the minimize button
20090830 : new_window can now be called with "where" set to null
Known issues :
- Seamonkey=> mouseup (read mouse button status) : incurable
- Opera : detect ability to reach coordinates > 32k*32k (i'll see later)
*/

function getById(name,tag,base) {
  if (!base)
    base=document;
  if (!tag)
    tag="*";

  var t=base.getElementsByTagName(tag);
  for (var i=0; i<t.length; i++) {
    if (t[i].id && (t[i].id == name))
      return t[i];
  }
  return null;
};

function getByClass(name,tag,base) {
  if (!base)
    base=document;
  if (!tag)
    tag="*";

  var t=base.getElementsByTagName(tag);
  for (var i=0; i<t.length; i++) {
    if(t[i].className && (t[i].className==name))
      return t[i];
  }
  return null;
};

function mousePos(ev) {
  ev = ev || window.event;
  var body = document.body || document.documentElement;
  return {
    x: ev.pageX || ev.clientX + (document.body.scrollLeft || document.documentElement.scrollLeft),
    y: ev.pageY || ev.clientY + (document.body.scrollTop  || document.documentElement.scrollTop)
  };
};

function getTarget(ev) {
  // where is the cursor pointing to ?
  if (ev.target)
    return ev.target;
  else
    if (ev.srcElement) // MSIE
      return ev.srcElement;
  return null;
};


if (typeof(disable_ygwm)=="undefined")

////////////////////////////////////////
// a placeholder for all ygwm-related stuff, that prevents namespace clutter

ygwm={

mouse_coords_id:null,
mouse_coords_timeout:null,
enableAutoscroll:true,
autoScrollThreshold:50, //px
max_coord:32300,  // 32767 minus some margin
noScrollIndex:100, // z-index of elements that prevent autoscroll onmouseover

   // Set to 0 to prevent boundary checks/clips.
slowDisplay:false,
lastTimeStamp:0,
latencyTimeOut: null,
action_function: null,
maxDisplayLatency:70, // milliseconds
redrawDelay:400,  // milliseconds
last_focus : null,
moved_div : null,
mouse_xoffset : 0,
mouse_xoffset2 : 0,
mouse_yoffset : 0,

min_height:60,
min_width:60,
max_height:20000, // arbitrary, can be changed
max_width:3000,

getParent : function(where){
  // get back to the root of the tree, searching a content_id
  if (where==null)
    return null;
  while (!where.contents_id) {
    if (where==null)
      return null;
    where=where.parentNode;
  }
  return where;
},

// called by onclick() on a button INSIDE a window or by some
// other function (but then ev is null and "where" is the window that must be erased)
erase_window : function(ev,where) {
  if (!where)
    var where=this;
  var parent=ygwm.getParent(where);
  if (parent!=null)
    parent.parentNode.removeChild(parent);
  last_focus=null;
  return false; // ?
},

// Latency measurement of the window display :
empty_callback : function() {
  ygwm.latencyTimeOut=null;
},

redraw_callback : function() {
  // restore the display
  ygwm.slowDisplay=false;
  ygwm.moved_div.contents_id.firstChild.style.display="";
  ygwm.latencyTimeOut=setTimeout(ygwm.empty_callback,0);
},

//////// Auto-Scroll :
autoScroll: function(ev) {
  var scrollX=0;
  var scrollY=0;
  ev=ev||window.event;
  var p = mousePos(ev);

  // Don't scroll if the cursor is over an element in foreground
  var over=getTarget(ev);
  if (over && over.style && (over.style.zIndex >= ygwm.noScrollIndex))
    return;

  // scroll right/left ?
  var w=ev.clientX - ygwm.autoScrollThreshold;
  if (w < 0)
    scrollX=w;
  else {
    w=window.innerWidth || document.body.clientWidth;
    w=(ev.clientX + ygwm.autoScrollThreshold) - w;
    if ((w > 0) && (p.x+w < ygwm.max_coord))
      scrollX=w;
  }

  // scroll down/up ?
  var h=ev.clientY - ygwm.autoScrollThreshold;
  if (h < 0)
    scrollY=h;
  else {
    h=window.innerHeight || document.body.clientHeight;
    h =(ev.clientY + ygwm.autoScrollThreshold) - h;
    if ((h > 0) && (p.y+h < ygwm.max_coord))
      scrollY=h;
  }

  window.scrollBy(scrollX,scrollY);

  if (ygwm.mouse_coords_id){
    clearTimeout(ygwm.mouse_coords_timeout);
    ygwm.mouse_coords_id.innerHTML="cursor@"+p.x+":"+p.y;
    ygwm.mouse_coords_timeout=setTimeout(function(){
      ygwm.mouse_coords_id.innerHTML="";
    },1000);
  }
},

// used by resize and move
mouse_move_callback: function(ev) {
  // get a timestamp;
  var d=new Date();
  d=d.getTime();

  if((ygwm.slowDisplay==false)    // contents is displayed
    &&(ygwm.latencyTimeOut!=null) // and is being redisplayed
    &&(ygwm.lastTimeStamp>0)) {   // and the timestamp is initialised by a previous run.
      if (d < ygwm.lastTimeStamp) // has the time expired ?
        return false; // if not, drop the event for this time, retry later.

      // if expired, hide the display
      ygwm.moved_div.contents_id.firstChild.style.display="none";
      ygwm.slowDisplay=true;
  }
  clearTimeout(ygwm.latencyTimeOut); // disable the redraw for this time too
  ygwm.latencyTimeOut=null;
  ygwm.lastTimeStamp = d + ygwm.maxDisplayLatency; // compute the timeout for the next turn

  var p = mousePos(ev);
  // call the action-specific code
  ygwm.action_function(p.x, p.y);

  ygwm.autoScroll(ev);

  // fire the proper timeout
  if (ygwm.slowDisplay)
    ygwm.latencyTimeOut=setTimeout(ygwm.redraw_callback,ygwm.redrawDelay);
  else
    ygwm.latencyTimeOut=setTimeout(ygwm.empty_callback,0);
  return false;
},

// the callbacks used by resize and move
mouse_cornerSE : function(x, y) {
  var w = x - ygwm.mouse_xoffset;
  var h = y - ygwm.mouse_yoffset;
  ygwm.changeDimensions(w,h,ygwm.moved_div);
},

mouse_cornerSW : function(x, y) {
  var h = y - ygwm.mouse_yoffset;

  // This one is more complex and tricky...
  // 1) don't let the width go past the left border of the browser
  var l = x - ygwm.mouse_xoffset;
  if (l<3) {
    l=3;
    x=ygwm.mouse_xoffset+3;
  }
  // don't let the width go below min_width
  var w = -( x - ygwm.mouse_xoffset2 );
  if (w < ygwm.min_width) {
    w = ygwm.min_width;
    l = ygwm.mouse_xoffset2-(ygwm.min_width+ygwm.mouse_xoffset);
  }

  if (l>ygwm.max_coord)
    return;
  ygwm.moved_div.move(l,ygwm.moved_div.y);
  ygwm.changeDimensions(w,h,ygwm.moved_div);
},

mouse_drag: function(x, y) {
  // X
  var left=x-ygwm.mouse_xoffset;
  if (left<3)
    left=3;

  // Y
  var top = y - ygwm.mouse_yoffset;
  if (top<3)
    top=3;

  if ((left>ygwm.max_coord)||(top>ygwm.max_coord))
    return;
  ygwm.moved_div.move(left,top);
},

mouse_drop : function (ev) {
  var y=ygwm.moved_div;
  // erase the window ?
  var c=y.erase_callback;
  if ((ygwm.action_function==ygwm.mouse_drag) && (c) && (c()))
    ygwm.erase_window(null,y);
  // restore the display
  if (ygwm.slowDisplay)
    ygwm.moved_div.contents_id.firstChild.style.display="";
  clearTimeout(ygwm.latencyTimeOut);
  ygwm.latencyTimeOut=null;
  ygwm.moved_div = null;

  document.onmousemove=null;
  document.onmouseup=null;
  ygwm.action_function=null;
  return false; // ?
},

// These are called when the user clicks on one of the move/resize handles

//hook : function(ev, where, callback,ev) {
hook : function(ev, where, callback) {
// anti-window-manager-hickup :
  if (document.onmouseup!=null)
    document.onmouseup(ev);
  document.onmouseup=ygwm.mouse_drop;
  document.onmousemove=ygwm.mouse_move_callback;
  ygwm.moved_div = where;
  ygwm.action_function=callback;
  ygwm.slowDisplay=false;
  return false; // !!!!!!!! prevents selection of the header's text,
  // along with -moz-user-select: none; in the CSS
},

hook_move : function(ev) {
  var p = mousePos(ev);
  var w = ygwm.getParent(this);
  ygwm.mouse_xoffset = p.x - w.offsetLeft;
  ygwm.mouse_yoffset = p.y - w.offsetTop;
  return ygwm.hook(ev, w, ygwm.mouse_drag);
},

hook_resizeSE : function(ev) {
  var p = mousePos(ev);
  var w = ygwm.getParent(this);
  ygwm.mouse_xoffset = p.x - w.contents_id.offsetWidth;
  ygwm.mouse_yoffset = p.y - w.contents_id.offsetHeight;
  return ygwm.hook(ev, w, ygwm.mouse_cornerSE);
},

hook_resizeSW : function(ev) {
  var p = mousePos(ev);
  var w = ygwm.getParent(this);
  ygwm.mouse_xoffset = p.x - w.offsetLeft;
  ygwm.mouse_xoffset2= p.x + w.contents_id.offsetWidth;
  ygwm.mouse_yoffset = p.y - w.contents_id.offsetHeight;
  return ygwm.hook(ev, w, ygwm.mouse_cornerSW);
},

////////////////////////////////////////:
// helper functions :

showhide : function(ev) {
  if (document.onmouseup!=null)
    document.onmouseup(ev);

  var p=ygwm.getParent(this);
  if (p.contents_id.style.display=="none") {
    this.className="ygwm_corner_minimize";
    p.contents_id.style.display="block";
    if (p.footer_id)
        p.footer_id.style.display="block";
  }
  else {
    this.className="ygwm_corner_maximize";
    p.contents_id.style.display="none";
    if (p.footer_id)
        p.footer_id.style.display="none";
  }
  return false;
},

focus_window : function(ev,where){
  if (!where) // is it an event ?
    where=ygwm.getParent(this);
  if (ygwm.last_focus != where) {
    // save the scroll
    var sl=where.contents_id.scrollLeft;
    var st=where.contents_id.scrollTop;

    // bring on top
    if (ygwm.last_focus) {
      if (ygwm.last_focus.header_id)
        ygwm.last_focus.header_id.className="ygwm_header";
      where.parentNode.insertBefore(where, ygwm.last_focus.nextSibling);
    }

    // restore the scroll
    where.contents_id.scrollLeft=sl;
    where.contents_id.scrollTop=st;
    if (where.header_id)
      where.header_id.className="ygwm_selected";
    ygwm.last_focus=where;
  }
  return true;
},

changeDimensions : function(width,height,where) {
  if (width<ygwm.min_width)
    width=ygwm.min_width;
  else
    if (width>ygwm.max_width)
      width=ygwm.max_width;
  if (height<ygwm.min_height)
    height=ygwm.min_height;
  else
    if (height>ygwm.max_height)
      height=ygwm.max_height;
  if (where.header_id)
    where.header_id.style.width =width+'px';
  where.contents_id.style.height=height+'px';
  where.contents_id.style.width =width+'px';
  if (where.footer_id)
    where.footer_id.style.width =width+'px';
  where.width=width;
  where.height=height;
},

// The big function
new_window : function(
    name,    // string for the title
    where,   // ID before which to put the window
      // (necessary because the DIV must be mapped before it is fully initialised
      // can now be null if the object is not mapped yet (this triggers document.body.appendChild)
    contents,// element ID (could be null or "none")
         // when null, then contents==where
    width, height, // integer / size of the contents
    X, Y,    // coordinates where it will appear
    erase_callback, // function that returns true if the window must be erased after a move
    footer,  // boolean : add a footer when true, which adds msg and resize capabilities
    header,  // false if no header (error message ?)
    close,   // true if a "cross" button is added
    really_hide // true if a click on the minimize button completely masks the window
) {

  if (ygwm.mouse_coords_id == null)
    ygwm.mouse_coords_id=document.getElementById("coord");

  // create and populate the root DIV
  var new_id=document.createElement("DIV");

  new_id.className="ygwm_div";
  // the "structure" of the window
  var h="";
  if (header) {
h+='<div class="ygwm_header">';
    if (close)
h+=' <div class="ygwm_corner_close"><\/div>';
h+=' <div class="ygwm_corner_minimize"><\/div>'
  +' <div class="ygwm_header_name"><\/div>'
  +'<\/div>'; }
h+='<div class="ygwm_contents"><\/div>';
  if (footer)
h+='<div class="ygwm_footer">'
  +' <div class="ygwm_cornerSW"><\/div>'
  +' <div class="ygwm_cornerSE"><\/div>'
  +' <div class="ygwm_msg"><\/div>'
  +'<\/div>';
  new_id.innerHTML=h;

  // register the new structure
  if (where == null) {
//    where=document.body.lastChild;
    document.body.appendChild(new_id);
  }
  else
    where.parentNode.insertBefore(new_id, where);

  new_id.hide_all=function(){
    new_id.style.display="none";
  }

  new_id.show_all=function(){
    new_id.style.display="";
  }

  if (header) {
    new_id.header_id=getByClass("ygwm_header","DIV",new_id);

    // hook the move function
    new_id.header_id.onmousedown=ygwm.hook_move;

    if (close)
      getByClass("ygwm_corner_close","DIV",new_id.header_id).onmousedown=ygwm.erase_window;

    if (erase_callback)
      new_id.erase_callback=erase_callback;
    else
      new_id.erase_callback=null;
    // show/hide hook
    var t=getByClass("ygwm_corner_minimize","DIV",new_id.header_id);
    if(really_hide)
      t.onmousedown=new_id.hide_all;
    else
      t.onmousedown=ygwm.showhide; // prevent move
    // change the title
    getByClass("ygwm_header_name","DIV",new_id.header_id).innerHTML=name;
  }

  // move the contents there
  new_id.contents_id=getByClass("ygwm_contents","DIV",new_id);
  if (contents) {
    if (contents!="none")
      new_id.contents_id.appendChild(contents);
  }
  else // contents==null => avoids repetition of getElementById
    new_id.contents_id.appendChild(where);

  // update the footer
  if (footer) {
    new_id.footer_id=getByClass("ygwm_footer",  "DIV",new_id);
    // msg
    new_id.messsage_id=getByClass("ygwm_msg","DIV",new_id.footer_id);
    new_id.shortmsg=function(msg) {
      new_id.messsage_id.innerHTML=msg;
    };
    new_id.erase_msg=function() {
      new_id.messsage_id.innerHTML="";
    };
    // right resize hook
    getByClass("ygwm_cornerSE","DIV",new_id.footer_id).onmousedown=ygwm.hook_resizeSE;
    // left resize hook
    getByClass("ygwm_cornerSW","DIV",new_id.footer_id).onmousedown=ygwm.hook_resizeSW;
  }
  else {
    new_id.shortmsg=function(msg) { /* empty */ }
  }

  ygwm.changeDimensions(width,height,new_id);

  new_id.reframe=false;

  new_id.move=function(x,y){
    if((new_id.reframe==true)
     &&(x+new_id.width > document.width)) {
      x=document.width-new_id.width;
      if (x<3)
        x=3;
    }
    new_id.style.left=x+'px';
    new_id.style.top=y+'px';
    new_id.x=x;
    new_id.y=y;
  };
  new_id.move(X,Y);

  // click to focus
  new_id.onmousedown=ygwm.focus_window;
  ygwm.focus_window(null,new_id);

  return new_id;
}

}; // end of ygwm object declaration

/*
Useful for later :
window.onerror=function(msg, url, linenumber){
 alert('Error message: '+msg+'\nURL: '+url+'\nLine Number: '+linenumber)
 return true
}
*/
