// Author: Siegfried Puchbauer <rails-spinoffs@lists.rubyonrails.org>
Ajax.History = Class.create({
  initialize: function(options) {
	this.options = Object.extend({
	  currentHash: '',
	  interval: 200,
	  iframeSrc: '/blank.html',
	  urlTemplate: '#{hash}'
	}, options || {});
	this.callback = this.options.callback || Prototype.emtpyFunction;

	if (!Prototype.Browser.IE) this.locator = new Ajax.History.Hash();
	else this.locator = new Ajax.History.Iframe('ajaxHistoryHandler', this.options.iframeSrc);

	this.currentHash = '';
	if (this.options.currentHash) this.add(this.options.currentHash);

	this.locked = false;
  },
  hashStorage : {},
  add: function(hash) {
	this.locked = true;
	clearTimeout(this.timer);

	var hashMD5 = hex_md5( ( typeof( hash ) == 'object' ? $H( hash ).toQueryString() : hash ) );
	this.hashStorage[ hashMD5 ] = hash;
	this.currentHash = hashMD5;
	this.locator.setHash(hashMD5);
	this.timer = setTimeout(this.checkHash.bind(this), this.options.interval);
	this.locked = false;
  },
  checkHash: function() {
	if (!this.locked) {
	  var check = this.locator.getHash();

	  if (check != this.currentHash) {
		var myhash = new Template(this.options.urlTemplate).evaluate({hash: check});
		if (check) this.callback( this.hashStorage[ myhash ] );
		this.currentHash = check;
	  }
	}
	this.timer = setTimeout(this.checkHash.bind(this), this.options.interval);
  },
  getBookmark: function() {
	return this.locator.getBookmark();
  }
});

// Hash Handler for IE (Tested with IE6)
Ajax.History.Iframe = Class.create({
  initialize: function(id, src) {
	this.url = '';
	this.id = id || 'ajaxHistoryHandler';
	this.src = src || '';
	document.write('<iframe src="'+this.src+'" id="'+this.id+'" name="'+this.id+'" style="display: none;" ></iframe>');
  },
  setHash: function(hash) {
	try {
	  $(this.id).setAttribute('src', this.src + '?' + hash);
	} catch(e) {}
	window.location.href = this.url + '#' + hash;
  },
  getHash: function() {
	try {
	  return (document.frames[this.id].location.href||'?').split('?')[1];
	} catch(e) { return ''; }
  },
  getBookmark: function() {
	try {
	  return window.location.href.split('#')[1]||'';
	} catch(e) { return ''; }
  }
});

// Hash Handler for a modern browser (tested with firefox 1.5)
Ajax.History.Hash = Class.create({
  initialize: function() {
	//
  },
  setHash: function(hash) {
	window.location.hash = hash;
  },
  getHash: function() {
	return window.location.hash.substring(1)||'';
  },
  getBookmark: function() {
	try {
	  return window.location.hash.substring(1)||'';
	} catch(e) { return ''; }
  }
});

// Singleton class TooltipWindow
// This class works with special className. The tooltip content could be in your HTML page as an hidden element or
// can be retreive by an AJAX call.
//
// To work, You just need to set two class name on elements that should show tooltips
// - One to say to TooltipManager that this element must have a tooltip ('tooltip' by default)
// - Another to indicate how to find the tooltip content
//   It could be html_XXXX if tootltip content is somewhere hidden in your page, XXX must be DOM ID of this hidden element
//   It could be ajax_XXXX if tootltip content must be find by an ajax request, XXX will be the string send as id parameter to your server. 
// Check samples/tooltips/tooltip.html to see how it works
//
TooltipManager = {
  options: {cssClassName: 'tooltip', delayOver: 200, delayOut: 1000, shiftX: 10, shiftY: 10,
            className: 'alphacube', width: 200, height: null, 
            draggable: false, minimizable: false, maximizable: false, showEffect: Element.show, hideEffect: Element.hide},
  ajaxInfo: null,
  elements: null,
  showTimer: null,
  hideTimer: null,

  // Init tooltip manager
  // parameters:
  // - cssClassName (string) : CSS class name where tooltip should be shown. 
  // - ajaxOptions  (hash)   : Ajax options for ajax tooltip. 
  //                           For examples {url: "/tooltip/get.php", options: {method: 'get'}} 
  //                           see Ajax.Request documentation for details
  //- tooltipOptions (hash)  : available keys
  //                           - delayOver: int in ms (default 10) delay before showing tooltip
  //                           - delayOut:  int in ms (default 1000) delay before hidding tooltip
  //                           - shiftX:    int in pixels (default 10) left shift of the tooltip window 
  //                           - shiftY:    int in pixels (default 10) top shift of the tooltip window 
  //                           and All window options like showEffect: Element.show, hideEffect: Element.hide to remove animation
  //                           default: {className: 'alphacube', width: 200, height: null, draggable: false, minimizable: false, maximizable: false}
  
  init: function(cssClassName, ajaxInfo, tooltipOptions) {
    TooltipManager.options = Object.extend(TooltipManager.options, tooltipOptions || {});
    
    cssClassName = TooltipManager.options.cssClassName || "tooltip";
    TooltipManager.ajaxInfo = ajaxInfo;
    TooltipManager.elements = $$("." + cssClassName);
    TooltipManager.elements.each(function(element) {
      element = $(element)
      var info = TooltipManager._getInfo(element);
      if (info.ajax) {
        element.ajaxId = info.id;
        element.ajaxInfo = ajaxInfo;
      }
      else {
        element.tooltipElement = $(info.id);
      }
      element.observe("mouseover", TooltipManager._mouseOver);
      element.observe("mouseout", TooltipManager._mouseOut);
    });
    Windows.addObserver(this);
  },
  
  addHTML: function(element, tooltipElement) {
    element = $(element);
    tooltipElement = $(tooltipElement);
    element.tooltipElement = tooltipElement;
    
    element.observe("mouseover", TooltipManager._mouseOver);
    element.observe("mouseout", TooltipManager._mouseOut);
  },
  
  addAjax: function(element, ajaxInfo) {
    element = $(element);
    element.ajaxInfo = ajaxInfo;
    element.observe("mouseover", TooltipManager._mouseOver);
    element.observe("mouseout", TooltipManager._mouseOut);    
  },
    
  addURL: function(element, url, width, height) {
    element = $(element);
    element.url = url;
    element.frameWidth = width;
    element.frameHeight = height;
    element.observe("mouseover", TooltipManager._mouseOver);
    element.observe("mouseout", TooltipManager._mouseOut);    
  },
    
  close: function() {
    if (TooltipManager.tooltipWindow)
      TooltipManager.tooltipWindow.hide();
  },
  
  preloadImages: function(path, images, extension) {
    if (!extension)
      extension = ".gif";
      
    //preload images
    $A(images).each(function(i) {
      var image = new Image(); 
      image.src= path + "/" + i + extension; 
    });
  },
  
  _showTooltip: function(element) {
    if (this.element == element)
      return;
    // Get original element
    while (element && (!element.tooltipElement && !element.ajaxInfo && !element.url)) 
      element = element.parentNode;
    this.element = element;
    
    TooltipManager.showTimer = null;
    if (TooltipManager.hideTimer)
      clearTimeout(TooltipManager.hideTimer);
    
    var position = Position.cumulativeOffset(element);
    var dimension = element.getDimensions();

    if (! this.tooltipWindow)
      this.tooltipWindow = new Window("__tooltip__", TooltipManager.options);
      
    this.tooltipWindow.hide();
    this.tooltipWindow.setLocation(position[1] + dimension.height + TooltipManager.options.shiftY, position[0] + TooltipManager.options.shiftX);

    Event.observe(this.tooltipWindow.element, "mouseover", function(event) {TooltipManager._tooltipOver(event, element)});
    Event.observe(this.tooltipWindow.element, "mouseout", function(event) {TooltipManager._tooltipOut(event, element)});

    // Reset width/height for computation
    this.tooltipWindow.height = TooltipManager.options.height;
    this.tooltipWindow.width = TooltipManager.options.width;

    // Ajax content
    if (element.ajaxInfo) {
      var p = element.ajaxInfo.options.parameters;
      var saveParam = p;
      
      // Set by CSS
      if (element.ajaxId) {
        if (p)
          p += "&id=" + element.ajaxId;
        else
          p = "id=" + element.ajaxId;
      }
      element.ajaxInfo.options.parameters = p || "";
      this.tooltipWindow.setHTMLContent("");
      this.tooltipWindow.setAjaxContent(element.ajaxInfo.url, element.ajaxInfo.options);
      element.ajaxInfo.options.parameters = saveParam;    
    } 
    // URL content
    else if (element.url) {
      this.tooltipWindow.setURL(element.url);
      this.tooltipWindow.setSize(element.frameWidth, element.frameHeight);

      // Set tooltip size
      this.tooltipWindow.height = element.frameHeight;
      this.tooltipWindow.width = element.frameWidth;
    }
    // HTML content
    else
      this.tooltipWindow.setHTMLContent(element.tooltipElement.innerHTML);

    if (!element.ajaxInfo) {
      this.tooltipWindow.show();
      this.tooltipWindow.toFront();
    }
  },
  
  _hideTooltip: function(element) {
    if (this.tooltipWindow) {
      this.tooltipWindow.hide();
      this.element = null;
    }
  },
  
  _mouseOver: function (event) {
    var element = Event.element(event);
    if (TooltipManager.showTimer) 
      clearTimeout(TooltipManager.showTimer);
    
    TooltipManager.showTimer = setTimeout(function() {TooltipManager._showTooltip(element)}, TooltipManager.options.delayOver)
  },
  
  _mouseOut: function(event) {
    var element = Event.element(event);
    if (TooltipManager.showTimer) {
      clearTimeout(TooltipManager.showTimer);
      TooltipManager.showTimer = null;
      return;
    }
    if (TooltipManager.tooltipWindow)
      TooltipManager.hideTimer = setTimeout(function() {TooltipManager._hideTooltip(element)}, TooltipManager.options.delayOut)
  },
  
  _tooltipOver: function(event, element) {
    if (TooltipManager.hideTimer) {
      clearTimeout(TooltipManager.hideTimer);
      TooltipManager.hideTimer = null;
    }
  },
  
  _tooltipOut: function(event, element) {
    if (TooltipManager.hideTimer == null)
      TooltipManager.hideTimer = setTimeout(function() {TooltipManager._hideTooltip(element)}, TooltipManager.options.delayOut)
  },
  
  _getInfo: function(element) {
    // Find html_ for static content
    var id = element.className.split(' ').detect(function(name) {return name.indexOf("html_") == 0});
    var ajax = true;
    if (id)
      ajax = false;
    else 
      // Find ajax_ for ajax content
      id = element.className.split(' ').detect(function(name) {return name.indexOf("ajax_") == 0});
    
    id = id.substr(id.indexOf('_')+1, id.length)
    return id ? {ajax: ajax, id: id} : null;
  },
  
  // by oh sang min 2008-12-15
  onBeforeShow: function(eventName, win) {
     var top = parseFloat(win.getLocation().top);
     var dim = win.element.getDimensions();
    
     if (top + dim.height > TooltipManager._getScrollTop() + TooltipManager._getPageHeight()) {
       if( this.element ) {
		   var position = $A( this.element ).cumulativeOffset();
		   var top = position[1] - TooltipManager.options.shiftY - dim.height;
		   win.setLocation(top, position[0] + TooltipManager.options.shiftX)
	   }
	   else {
		   win.setLocation( 0 , 0 );
	   }
     }
   },

	_getPageWidth: function(){
		return window.innerWidth || document.documentElement.clientWidth || 0;
	},
	
	_getPageHeight: function(){
		return window.innerHeight || document.documentElement.clientHeight || 0;
	},

	_getScrollTop: function(){
		return document.documentElement.scrollTop || window.pageYOffset || 0;
	},

	_getScrollLeft: function(){
		return document.documentElement.scrollLeft || window.pageXOffset || 0;
	}	
};

// Copyright (c) 2006 Sébastien Gruhier (http://xilinus.com, http://itseb.com)
// 
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// VERSION 1.3

var Window = Class.create();

Window.keepMultiModalWindow = false;
Window.hasEffectLib = (typeof Effect != 'undefined');
Window.resizeEffectDuration = 0.4;

Window.prototype = {
  // Constructor
  // Available parameters : className, blurClassName, title, minWidth, minHeight, maxWidth, maxHeight, width, height, top, left, bottom, right, resizable, zIndex, opacity, recenterAuto, wiredDrag
  //                        hideEffect, showEffect, showEffectOptions, hideEffectOptions, effectOptions, url, draggable, closable, minimizable, maximizable, parent, onload
  //                        add all callbacks (if you do not use an observer)
  //                        onDestroy onStartResize onStartMove onResize onMove onEndResize onEndMove onFocus onBlur onBeforeShow onShow onHide onMinimize onMaximize onClose
  
  initialize: function() {
    var id;
    var optionIndex = 0;
    // For backward compatibility like win= new Window("id", {...}) instead of win = new Window({id: "id", ...})
    if (arguments.length > 0) {
      if (typeof arguments[0] == "string" ) {
        id = arguments[0];
        optionIndex = 1;
      }
      else
        id = arguments[0] ? arguments[0].id : null;
    }
    
    // Generate unique ID if not specified
    if (!id)
      id = "window_" + new Date().getTime();
      
    if ($(id))
      alert("Window " + id + " is already registered in the DOM! Make sure you use setDestroyOnClose() or destroyOnClose: true in the constructor");

    this.options = Object.extend({
      className:         "dialog",
      blurClassName:     null,
      minWidth:          100, 
      minHeight:         20,
      resizable:         true,
      closable:          true,
      minimizable:       true,
      maximizable:       true,
      draggable:         true,
      userData:          null,
      showEffect:        (Window.hasEffectLib ? Effect.Appear : Element.show),
      hideEffect:        (Window.hasEffectLib ? Effect.Fade : Element.hide),
      showEffectOptions: {},
      hideEffectOptions: {},
      effectOptions:     null,
      parent:            document.body,
      title:             "&nbsp;",
      url:               null,
      onload:            Prototype.emptyFunction,
      width:             200,
      height:            300,
      opacity:           1,
      recenterAuto:      true,
      wiredDrag:         false,
      closeCallback:     null,
      destroyOnClose:    false,
      gridX:             1, 
      gridY:             1      
    }, arguments[optionIndex] || {});
    if (this.options.blurClassName)
      this.options.focusClassName = this.options.className;
      
    if (typeof this.options.top == "undefined" &&  typeof this.options.bottom ==  "undefined") 
      this.options.top = this._round(Math.random()*500, this.options.gridY);
    if (typeof this.options.left == "undefined" &&  typeof this.options.right ==  "undefined") 
      this.options.left = this._round(Math.random()*500, this.options.gridX);

    if (this.options.effectOptions) {
      Object.extend(this.options.hideEffectOptions, this.options.effectOptions);
      Object.extend(this.options.showEffectOptions, this.options.effectOptions);
      if (this.options.showEffect == Element.Appear)
        this.options.showEffectOptions.to = this.options.opacity;
    }
    if (Window.hasEffectLib) {
      if (this.options.showEffect == Effect.Appear)
        this.options.showEffectOptions.to = this.options.opacity;
    
      if (this.options.hideEffect == Effect.Fade)
        this.options.hideEffectOptions.from = this.options.opacity;
    }
    if (this.options.hideEffect == Element.hide)
      this.options.hideEffect = function(){ Element.hide(this.element); if (this.options.destroyOnClose) this.destroy(); }.bind(this)
    
    if (this.options.parent != document.body)  
      this.options.parent = $(this.options.parent);
      
    this.element = this._createWindow(id);       
    this.element.win = this;
    
    // Bind event listener
    this.eventMouseDown = this._initDrag.bindAsEventListener(this);
    this.eventMouseUp   = this._endDrag.bindAsEventListener(this);
    this.eventMouseMove = this._updateDrag.bindAsEventListener(this);
    this.eventOnLoad    = this._getWindowBorderSize.bindAsEventListener(this);
    this.eventMouseDownContent = this.toFront.bindAsEventListener(this);
    this.eventResize = this._recenter.bindAsEventListener(this);
 
    this.topbar = $(this.element.id + "_top");
    this.bottombar = $(this.element.id + "_bottom");
    this.content = $(this.element.id + "_content");
    
    Event.observe(this.topbar, "mousedown", this.eventMouseDown);
    Event.observe(this.bottombar, "mousedown", this.eventMouseDown);
    Event.observe(this.content, "mousedown", this.eventMouseDownContent);
    Event.observe(window, "load", this.eventOnLoad);
    Event.observe(window, "resize", this.eventResize);
    Event.observe(window, "scroll", this.eventResize);
    Event.observe(this.options.parent, "scroll", this.eventResize);
    
    if (this.options.draggable)  {
      var that = this;
      [this.topbar, this.topbar.up().previous(), this.topbar.up().next()].each(function(element) {
        element.observe("mousedown", that.eventMouseDown);
        element.addClassName("top_draggable");
      });
      [this.bottombar.up(), this.bottombar.up().previous(), this.bottombar.up().next()].each(function(element) {
        element.observe("mousedown", that.eventMouseDown);
        element.addClassName("bottom_draggable");
      });
      
    }    
    
    if (this.options.resizable) {
      this.sizer = $(this.element.id + "_sizer");
      Event.observe(this.sizer, "mousedown", this.eventMouseDown);
    }  
    
    this.useLeft = null;
    this.useTop = null;
    if (typeof this.options.left != "undefined") {
      this.element.setStyle({left: parseFloat(this.options.left) + 'px'});
      this.useLeft = true;
    }
    else {
      this.element.setStyle({right: parseFloat(this.options.right) + 'px'});
      this.useLeft = false;
    }
    
    if (typeof this.options.top != "undefined") {
      this.element.setStyle({top: parseFloat(this.options.top) + 'px'});
      this.useTop = true;
    }
    else {
      this.element.setStyle({bottom: parseFloat(this.options.bottom) + 'px'});      
      this.useTop = false;
    }
      
    this.storedLocation = null;
    
    this.setOpacity(this.options.opacity);
    if (this.options.zIndex)
      this.setZIndex(this.options.zIndex)

    if (this.options.destroyOnClose)
      this.setDestroyOnClose(true);

    this._getWindowBorderSize();
    this.width = this.options.width;
    this.height = this.options.height;
    this.visible = false;
    
    this.constraint = false;
    this.constraintPad = {top: 0, left:0, bottom:0, right:0};
    
    if (this.width && this.height)
      this.setSize(this.options.width, this.options.height);
    this.setTitle(this.options.title)
    Windows.register(this);      
  },
  
  // Destructor
  destroy: function() {
    this._notify("onDestroy");
    Event.stopObserving(this.topbar, "mousedown", this.eventMouseDown);
    Event.stopObserving(this.bottombar, "mousedown", this.eventMouseDown);
    Event.stopObserving(this.content, "mousedown", this.eventMouseDownContent);
    
    Event.stopObserving(window, "load", this.eventOnLoad);
    Event.stopObserving(window, "resize", this.eventResize);
    Event.stopObserving(window, "scroll", this.eventResize);
    
    Event.stopObserving(this.content, "load", this.options.onload);

    if (this._oldParent) {
      var content = this.getContent();
      var originalContent = null;
      for(var i = 0; i < content.childNodes.length; i++) {
        originalContent = content.childNodes[i];
        if (originalContent.nodeType == 1) 
          break;
        originalContent = null;
      }
      if (originalContent)
        this._oldParent.appendChild(originalContent);
      this._oldParent = null;
    }

    if (this.sizer)
        Event.stopObserving(this.sizer, "mousedown", this.eventMouseDown);

    if (this.options.url) 
      this.content.src = null

     if(this.iefix) 
      Element.remove(this.iefix);

    Element.remove(this.element);
    Windows.unregister(this);      
  },
    
  // Sets close callback, if it sets, it should return true to be able to close the window.
  setCloseCallback: function(callback) {
    this.options.closeCallback = callback;
  },
  
  // Gets window content
  getContent: function () {
    return this.content;
  },
  
  // Sets the content with an element id
  setContent: function(id, autoresize, autoposition) {
    var element = $(id);
    if (null == element) throw "Unable to find element '" + id + "' in DOM";
    this._oldParent = element.parentNode;

    var d = null;
    var p = null;

    if (autoresize) 
      d = Element.getDimensions(element);
    if (autoposition) 
      p = Position.cumulativeOffset(element);

    var content = this.getContent();
    // Clear HTML (and even iframe)
    this.setHTMLContent("");
    content = this.getContent();
    
    content.appendChild(element);
    element.show();
    if (autoresize) 
      this.setSize(d.width, d.height);
    if (autoposition) 
      this.setLocation(p[1] - this.heightN, p[0] - this.widthW);    
  },
  
  setHTMLContent: function(html) {
    // It was an url (iframe), recreate a div content instead of iframe content
    if (this.options.url) {
      this.content.src = null;
      this.options.url = null;
      
  	  var content ="<div id=\"" + this.getId() + "_content\" class=\"" + this.options.className + "_content\"> </div>";
      $(this.getId() +"_table_content").innerHTML = content;
      
      this.content = $(this.element.id + "_content");
    }
      
    this.getContent().innerHTML = html;
  },
  
  setAjaxContent: function(url, options, showCentered, showModal) {
    this.showFunction = showCentered ? "showCenter" : "show";
    this.showModal = showModal || false;
  
    options = options || {};

    // Clear HTML (and even iframe)
    this.setHTMLContent("");
 
    this.onComplete = options.onComplete;
    if (! this._onCompleteHandler)
      this._onCompleteHandler = this._setAjaxContent.bind(this);
    options.onComplete = this._onCompleteHandler;

    new Ajax.Request(url, options);    
    options.onComplete = this.onComplete;
  },
  
  _setAjaxContent: function(originalRequest) {
    Element.update(this.getContent(), originalRequest.responseText);
    if (this.onComplete)
      this.onComplete(originalRequest);
    this.onComplete = null;
    this[this.showFunction](this.showModal)
  },
  
  setURL: function(url) {
    // Not an url content, change div to iframe
    if (this.options.url) 
      this.content.src = null;
    this.options.url = url;
    var content= "<iframe frameborder='0' name='" + this.getId() + "_content'  id='" + this.getId() + "_content' src='" + url + "' width='" + this.width + "' height='" + this.height + "'> </iframe>";
    $(this.getId() +"_table_content").innerHTML = content;
    
    this.content = $(this.element.id + "_content");
  },

  getURL: function() {
  	return this.options.url ? this.options.url : null;
  },

  refresh: function() {
    if (this.options.url)
	    $(this.element.getAttribute('id') + '_content').src = this.options.url;
  },
  
  // Stores position/size in a cookie, by default named with window id
  setCookie: function(name, expires, path, domain, secure) {
    name = name || this.element.id;
    this.cookie = [name, expires, path, domain, secure];
    
    // Get cookie
    var value = WindowUtilities.getCookie(name)
    // If exists
    if (value) {
      var values = value.split(',');
      var x = values[0].split(':');
      var y = values[1].split(':');

      var w = parseFloat(values[2]), h = parseFloat(values[3]);
      var mini = values[4];
      var maxi = values[5];

      this.setSize(w, h);
      if (mini == "true")
        this.doMinimize = true; // Minimize will be done at onload window event
      else if (maxi == "true")
        this.doMaximize = true; // Maximize will be done at onload window event

      this.useLeft = x[0] == "l";
      this.useTop = y[0] == "t";

      this.element.setStyle(this.useLeft ? {left: x[1]} : {right: x[1]});
      this.element.setStyle(this.useTop ? {top: y[1]} : {bottom: y[1]});
    }
  },
  
  // Gets window ID
  getId: function() {
    return this.element.id;
  },
  
  // Detroys itself when closing 
  setDestroyOnClose: function() {
    this.options.destroyOnClose = true;
  },
  
  setConstraint: function(bool, padding) {
    this.constraint = bool;
    this.constraintPad = Object.extend(this.constraintPad, padding || {});
    // Reset location to apply constraint
    if (this.useTop && this.useLeft)
      this.setLocation(parseFloat(this.element.style.top), parseFloat(this.element.style.left));
  },
  
  // initDrag event

  _initDrag: function(event) {
    // No resize on minimized window
    if (Event.element(event) == this.sizer && this.isMinimized())
      return;

    // No move on maximzed window
    if (Event.element(event) != this.sizer && this.isMaximized())
      return;
      
    if (Prototype.Browser.IE && this.heightN == 0)
      this._getWindowBorderSize();
    
    // Get pointer X,Y
    this.pointer = [this._round(Event.pointerX(event), this.options.gridX), this._round(Event.pointerY(event), this.options.gridY)];
    if (this.options.wiredDrag) 
      this.currentDrag = this._createWiredElement();
    else
      this.currentDrag = this.element;
      
    // Resize
    if (Event.element(event) == this.sizer) {
      this.doResize = true;
      this.widthOrg = this.width;
      this.heightOrg = this.height;
      this.bottomOrg = parseFloat(this.element.getStyle('bottom'));
      this.rightOrg = parseFloat(this.element.getStyle('right'));
      this._notify("onStartResize");
    }
    else {
      this.doResize = false;

      // Check if click on close button, 
      var closeButton = $(this.getId() + '_close');
      if (closeButton && Position.within(closeButton, this.pointer[0], this.pointer[1])) {
        this.currentDrag = null;
        return;
      }

      this.toFront();

      if (! this.options.draggable) 
        return;
      this._notify("onStartMove");
    }    
    // Register global event to capture mouseUp and mouseMove
    Event.observe(document, "mouseup", this.eventMouseUp, false);
    Event.observe(document, "mousemove", this.eventMouseMove, false);
    
    // Add an invisible div to keep catching mouse event over iframes
    WindowUtilities.disableScreen('__invisible__', '__invisible__', this.overlayOpacity);

    // Stop selection while dragging
    document.body.ondrag = function () { return false; };
    document.body.onselectstart = function () { return false; };
    
    this.currentDrag.show();
    Event.stop(event);
  },
  
  _round: function(val, round) {
    return round == 1 ? val  : val = Math.floor(val / round) * round;
  },

  // updateDrag event
  _updateDrag: function(event) {
    var pointer =  [this._round(Event.pointerX(event), this.options.gridX), this._round(Event.pointerY(event), this.options.gridY)];  
    var dx = pointer[0] - this.pointer[0];
    var dy = pointer[1] - this.pointer[1];
    
    // Resize case, update width/height
    if (this.doResize) {
      var w = this.widthOrg + dx;
      var h = this.heightOrg + dy;
      
      dx = this.width - this.widthOrg
      dy = this.height - this.heightOrg
      
      // Check if it's a right position, update it to keep upper-left corner at the same position
      if (this.useLeft) 
        w = this._updateWidthConstraint(w)
      else 
        this.currentDrag.setStyle({right: (this.rightOrg -dx) + 'px'});
      // Check if it's a bottom position, update it to keep upper-left corner at the same position
      if (this.useTop) 
        h = this._updateHeightConstraint(h)
      else
        this.currentDrag.setStyle({bottom: (this.bottomOrg -dy) + 'px'});
        
      this.setSize(w , h);
      this._notify("onResize");
    }
    // Move case, update top/left
    else {
      this.pointer = pointer;
      
      if (this.useLeft) {
        var left =  parseFloat(this.currentDrag.getStyle('left')) + dx;
        var newLeft = this._updateLeftConstraint(left);
        // Keep mouse pointer correct
        this.pointer[0] += newLeft-left;
        this.currentDrag.setStyle({left: newLeft + 'px'});
      }
      else 
        this.currentDrag.setStyle({right: parseFloat(this.currentDrag.getStyle('right')) - dx + 'px'});
      
      if (this.useTop) {
        var top =  parseFloat(this.currentDrag.getStyle('top')) + dy;
        var newTop = this._updateTopConstraint(top);
        // Keep mouse pointer correct
        this.pointer[1] += newTop - top;
        this.currentDrag.setStyle({top: newTop + 'px'});
      }
      else 
        this.currentDrag.setStyle({bottom: parseFloat(this.currentDrag.getStyle('bottom')) - dy + 'px'});

      this._notify("onMove");
    }
    if (this.iefix) 
      this._fixIEOverlapping(); 
      
    this._removeStoreLocation();
    Event.stop(event);
  },

   // endDrag callback
   _endDrag: function(event) {
    // Remove temporary div over iframes
     WindowUtilities.enableScreen('__invisible__');
    
    if (this.doResize)
      this._notify("onEndResize");
    else
      this._notify("onEndMove");
    
    // Release event observing
    Event.stopObserving(document, "mouseup", this.eventMouseUp,false);
    Event.stopObserving(document, "mousemove", this.eventMouseMove, false);

    Event.stop(event);
    
    this._hideWiredElement();

    // Store new location/size if need be
    this._saveCookie()
      
    // Restore selection
    document.body.ondrag = null;
    document.body.onselectstart = null;
  },

  _updateLeftConstraint: function(left) {
    if (this.constraint && this.useLeft && this.useTop) {
      var width = this.options.parent == document.body ? WindowUtilities.getPageSize().windowWidth : this.options.parent.getDimensions().width;

      if (left < this.constraintPad.left)
        left = this.constraintPad.left;
      if (left + this.width + this.widthE + this.widthW > width - this.constraintPad.right) 
        left = width - this.constraintPad.right - this.width - this.widthE - this.widthW;
    }
    return left;
  },
  
  _updateTopConstraint: function(top) {
    if (this.constraint && this.useLeft && this.useTop) {        
      var height = this.options.parent == document.body ? WindowUtilities.getPageSize().windowHeight : this.options.parent.getDimensions().height;
      
      var h = this.height + this.heightN + this.heightS;

      if (top < this.constraintPad.top)
        top = this.constraintPad.top;
      if (top + h > height - this.constraintPad.bottom) 
        top = height - this.constraintPad.bottom - h;
    }
    return top;
  },
  
  _updateWidthConstraint: function(w) {
    if (this.constraint && this.useLeft && this.useTop) {
      var width = this.options.parent == document.body ? WindowUtilities.getPageSize().windowWidth : this.options.parent.getDimensions().width;
      var left =  parseFloat(this.element.getStyle("left"));

      if (left + w + this.widthE + this.widthW > width - this.constraintPad.right) 
        w = width - this.constraintPad.right - left - this.widthE - this.widthW;
    }
    return w;
  },
  
  _updateHeightConstraint: function(h) {
    if (this.constraint && this.useLeft && this.useTop) {
      var height = this.options.parent == document.body ? WindowUtilities.getPageSize().windowHeight : this.options.parent.getDimensions().height;
      var top =  parseFloat(this.element.getStyle("top"));

      if (top + h + this.heightN + this.heightS > height - this.constraintPad.bottom) 
        h = height - this.constraintPad.bottom - top - this.heightN - this.heightS;
    }
    return h;
  },
  
  
  // Creates HTML window code
  _createWindow: function(id) {
    var className = this.options.className;
    var win = document.createElement("div");
    win.setAttribute('id', id);
    win.className = "dialog";

    var content;
    if (this.options.url)
      content= "<iframe frameborder=\"0\" name=\"" + id + "_content\"  id=\"" + id + "_content\" src=\"" + this.options.url + "\"> </iframe>";
    else
      content ="<div id=\"" + id + "_content\" class=\"" +className + "_content\"> </div>";

    var closeDiv = this.options.closable ? "<div class='"+ className +"_close' id='"+ id +"_close' onclick='Windows.close(\""+ id +"\", event)'> </div>" : "";
    var minDiv = this.options.minimizable ? "<div class='"+ className + "_minimize' id='"+ id +"_minimize' onclick='Windows.minimize(\""+ id +"\", event)'> </div>" : "";
    var maxDiv = this.options.maximizable ? "<div class='"+ className + "_maximize' id='"+ id +"_maximize' onclick='Windows.maximize(\""+ id +"\", event)'> </div>" : "";
    var seAttributes = this.options.resizable ? "class='" + className + "_sizer' id='" + id + "_sizer'" : "class='"  + className + "_se'";
    var blank = "../themes/default/blank.gif";
    
    win.innerHTML = closeDiv + minDiv + maxDiv + "\
      <table id='"+ id +"_row1' class=\"top table_window\">\
        <tr>\
          <td class='"+ className +"_nw'></td>\
          <td class='"+ className +"_n'><div id='"+ id +"_top' class='"+ className +"_title title_window'>"+ this.options.title +"</div></td>\
          <td class='"+ className +"_ne'></td>\
        </tr>\
      </table>\
      <table id='"+ id +"_row2' class=\"mid table_window\">\
        <tr>\
          <td class='"+ className +"_w'></td>\
            <td id='"+ id +"_table_content' class='"+ className +"_content' valign='top'>" + content + "</td>\
          <td class='"+ className +"_e'></td>\
        </tr>\
      </table>\
        <table id='"+ id +"_row3' class=\"bot table_window\">\
        <tr>\
          <td class='"+ className +"_sw'></td>\
            <td class='"+ className +"_s'><div id='"+ id +"_bottom' class='status_bar'><span style='float:left; width:1px; height:1px'></span></div></td>\
            <td " + seAttributes + "></td>\
        </tr>\
      </table>\
    ";
    Element.hide(win);
    this.options.parent.insertBefore(win, this.options.parent.firstChild);
    Event.observe($(id + "_content"), "load", this.options.onload);
    return win;
  },
  
  
  changeClassName: function(newClassName) {    
    var className = this.options.className;
    var id = this.getId();
    $A(["_close", "_minimize", "_maximize", "_sizer", "_content"]).each(function(value) { this._toggleClassName($(id + value), className + value, newClassName + value) }.bind(this));
    this._toggleClassName($(id + "_top"), className + "_title", newClassName + "_title");
    $$("#" + id + " td").each(function(td) {td.className = td.className.sub(className,newClassName); });
    this.options.className = newClassName;
  },
  
  _toggleClassName: function(element, oldClassName, newClassName) { 
    if (element) {
      element.removeClassName(oldClassName);
      element.addClassName(newClassName);
    }
  },
  
  // Sets window location
  setLocation: function(top, left) {
    top = this._updateTopConstraint(top);
    left = this._updateLeftConstraint(left);

    var e = this.currentDrag || this.element;
    e.setStyle({top: top + 'px'});
    e.setStyle({left: left + 'px'});

    this.useLeft = true;
    this.useTop = true;
  },
    
  getLocation: function() {
    var location = {};
    if (this.useTop)
      location = Object.extend(location, {top: this.element.getStyle("top")});
    else
      location = Object.extend(location, {bottom: this.element.getStyle("bottom")});
    if (this.useLeft)
      location = Object.extend(location, {left: this.element.getStyle("left")});
    else
      location = Object.extend(location, {right: this.element.getStyle("right")});
    
    return location;
  },
  
  // Gets window size
  getSize: function() {
    return {width: this.width, height: this.height};
  },
    
  // Sets window size
  setSize: function(width, height, useEffect) {    
    width = parseFloat(width);
    height = parseFloat(height);
    
    // Check min and max size
    if (!this.minimized && width < this.options.minWidth)
      width = this.options.minWidth;

    if (!this.minimized && height < this.options.minHeight)
      height = this.options.minHeight;
      
    if (this.options. maxHeight && height > this.options. maxHeight)
      height = this.options. maxHeight;

    if (this.options. maxWidth && width > this.options. maxWidth)
      width = this.options. maxWidth;

    
    if (this.useTop && this.useLeft && Window.hasEffectLib && Effect.ResizeWindow && useEffect) {
      new Effect.ResizeWindow(this, null, null, width, height, {duration: Window.resizeEffectDuration});
    } else {
      this.width = width;
      this.height = height;
      var e = this.currentDrag ? this.currentDrag : this.element;

      e.setStyle({width: width + this.widthW + this.widthE + "px"})
      e.setStyle({height: height  + this.heightN + this.heightS + "px"})

      // Update content size
      if (!this.currentDrag || this.currentDrag == this.element) {
        var content = $(this.element.id + '_content');
        content.setStyle({height: height  + 'px'});
        content.setStyle({width: width  + 'px'});
      }
    }
  },
  
  updateHeight: function() {
    this.setSize(this.width, this.content.scrollHeight, true);
  },
  
  updateWidth: function() {
    this.setSize(this.content.scrollWidth, this.height, true);
  },
  
  // Brings window to front
  toFront: function() {
    if (this.element.style.zIndex < Windows.maxZIndex)  
      this.setZIndex(Windows.maxZIndex + 1);
    if (this.iefix) 
      this._fixIEOverlapping(); 
  },
   
  getBounds: function(insideOnly) {
    if (! this.width || !this.height || !this.visible)  
      this.computeBounds();
    var w = this.width;
    var h = this.height;

    if (!insideOnly) {
      w += this.widthW + this.widthE;
      h += this.heightN + this.heightS;
    }
    var bounds = Object.extend(this.getLocation(), {width: w + "px", height: h + "px"});
    return bounds;
  },
      
  computeBounds: function() {
     if (! this.width || !this.height) {
      var size = WindowUtilities._computeSize(this.content.innerHTML, this.content.id, this.width, this.height, 0, this.options.className)
      if (this.height)
        this.width = size + 5
      else
        this.height = size + 5
    }

    this.setSize(this.width, this.height);
    if (this.centered)
      this._center(this.centerTop, this.centerLeft);    
  },
  
  // Displays window modal state or not
  show: function(modal) {
    this.visible = true;
    if (modal) {
      // Hack for Safari !!
      if (typeof this.overlayOpacity == "undefined") {
        var that = this;
        setTimeout(function() {that.show(modal)}, 10);
        return;
      }
      Windows.addModalWindow(this);
      
      this.modal = true;      
      this.setZIndex(Windows.maxZIndex + 1);
      Windows.unsetOverflow(this);
    }
    else    
      if (!this.element.style.zIndex) 
        this.setZIndex(Windows.maxZIndex + 1);        
      
    // To restore overflow if need be
    if (this.oldStyle)
      this.getContent().setStyle({overflow: this.oldStyle});
      
    this.computeBounds();
    
    this._notify("onBeforeShow");   
    if (this.options.showEffect != Element.show && this.options.showEffectOptions)
      this.options.showEffect(this.element, this.options.showEffectOptions);  
    else
      this.options.showEffect(this.element);  
      
//    this._checkIEOverlapping(); <- 이거 왜 이런겨?
    WindowUtilities.focusedWindow = this
    this._notify("onShow");   
  },
  
  // Displays window modal state or not at the center of the page
  showCenter: function(modal, top, left) {
    this.centered = true;
    this.centerTop = top;
    this.centerLeft = left;

    this.show(modal);
  },
  
  isVisible: function() {
    return this.visible;
  },
  
  _center: function(top, left) {    
    var windowScroll = WindowUtilities.getWindowScroll(this.options.parent);    
    var pageSize = WindowUtilities.getPageSize(this.options.parent);    
    if (typeof top == "undefined")
      top = (pageSize.windowHeight - (this.height + this.heightN + this.heightS))/2;
    top += windowScroll.top
    
    if (typeof left == "undefined")
      left = (pageSize.windowWidth - (this.width + this.widthW + this.widthE))/2;
    left += windowScroll.left      
    this.setLocation(top, left);
    this.toFront();
  },
  
  _recenter: function(event) {     
    if (this.centered) {
      var pageSize = WindowUtilities.getPageSize(this.options.parent);
      var windowScroll = WindowUtilities.getWindowScroll(this.options.parent);    

      // Check for this stupid IE that sends dumb events
      if (this.pageSize && this.pageSize.windowWidth == pageSize.windowWidth && this.pageSize.windowHeight == pageSize.windowHeight && 
          this.windowScroll.left == windowScroll.left && this.windowScroll.top == windowScroll.top) 
        return;
      this.pageSize = pageSize;
      this.windowScroll = windowScroll;
      // set height of Overlay to take up whole page and show
      if ($('overlay_modal')) 
        $('overlay_modal').setStyle({height: (pageSize.pageHeight + 'px')});
      
      if (this.options.recenterAuto)
        this._center(this.centerTop, this.centerLeft);    
    }
  },
  
  // Hides window
  hide: function() {
    this.visible = false;
    if (this.modal) {
      Windows.removeModalWindow(this);
      Windows.resetOverflow();
    }
    // To avoid bug on scrolling bar
    this.oldStyle = this.getContent().getStyle('overflow') || "auto"
    this.getContent().setStyle({overflow: "hidden"});

    this.options.hideEffect(this.element, this.options.hideEffectOptions);  

     if(this.iefix) 
      this.iefix.hide();

    if (!this.doNotNotifyHide)
      this._notify("onHide");
  },

  close: function() {
    // Asks closeCallback if exists
    if (this.visible) {
      if (this.options.closeCallback && ! this.options.closeCallback(this)) 
        return;

      if (this.options.destroyOnClose) {
        var destroyFunc = this.destroy.bind(this);
        if (this.options.hideEffectOptions.afterFinish) {
          var func = this.options.hideEffectOptions.afterFinish;
          this.options.hideEffectOptions.afterFinish = function() {func();destroyFunc() }
        }
        else 
          this.options.hideEffectOptions.afterFinish = function() {destroyFunc() }
      }
      Windows.updateFocusedWindow();
      
      this.doNotNotifyHide = true;
      this.hide();
      this.doNotNotifyHide = false;
      this._notify("onClose");
    }
  },
  
  minimize: function() {
    if (this.resizing)
      return;
    
    var r2 = $(this.getId() + "_row2");
    
    if (!this.minimized) {
      this.minimized = true;

      var dh = r2.getDimensions().height;
      this.r2Height = dh;
      var h  = this.element.getHeight() - dh;

      if (this.useLeft && this.useTop && Window.hasEffectLib && Effect.ResizeWindow) {
        new Effect.ResizeWindow(this, null, null, null, this.height -dh, {duration: Window.resizeEffectDuration});
      } else  {
        this.height -= dh;
        this.element.setStyle({height: h + "px"});
        r2.hide();
      }

      if (! this.useTop) {
        var bottom = parseFloat(this.element.getStyle('bottom'));
        this.element.setStyle({bottom: (bottom + dh) + 'px'});
      }
    } 
    else {      
      this.minimized = false;
      
      var dh = this.r2Height;
      this.r2Height = null;
      if (this.useLeft && this.useTop && Window.hasEffectLib && Effect.ResizeWindow) {
        new Effect.ResizeWindow(this, null, null, null, this.height + dh, {duration: Window.resizeEffectDuration});
      }
      else {
        var h  = this.element.getHeight() + dh;
        this.height += dh;
        this.element.setStyle({height: h + "px"})
        r2.show();
      }
      if (! this.useTop) {
        var bottom = parseFloat(this.element.getStyle('bottom'));
        this.element.setStyle({bottom: (bottom - dh) + 'px'});
      }
      this.toFront();
    }
    this._notify("onMinimize");
    
    // Store new location/size if need be
    this._saveCookie()
  },
  
  maximize: function() {
    if (this.isMinimized() || this.resizing)
      return;
  
    if (Prototype.Browser.IE && this.heightN == 0)
      this._getWindowBorderSize();
      
    if (this.storedLocation != null) {
      this._restoreLocation();
      if(this.iefix) 
        this.iefix.hide();
    }
    else {
      this._storeLocation();
      Windows.unsetOverflow(this);
      
      var windowScroll = WindowUtilities.getWindowScroll(this.options.parent);
      var pageSize = WindowUtilities.getPageSize(this.options.parent);    
      var left = windowScroll.left;
      var top = windowScroll.top;
      
      if (this.options.parent != document.body) {
        windowScroll =  {top:0, left:0, bottom:0, right:0};
        var dim = this.options.parent.getDimensions();
        pageSize.windowWidth = dim.width;
        pageSize.windowHeight = dim.height;
        top = 0; 
        left = 0;
      }
      
      if (this.constraint) {
        pageSize.windowWidth -= Math.max(0, this.constraintPad.left) + Math.max(0, this.constraintPad.right);
        pageSize.windowHeight -= Math.max(0, this.constraintPad.top) + Math.max(0, this.constraintPad.bottom);
        left +=  Math.max(0, this.constraintPad.left);
        top +=  Math.max(0, this.constraintPad.top);
      }
      
      var width = pageSize.windowWidth - this.widthW - this.widthE;
      var height= pageSize.windowHeight - this.heightN - this.heightS;

      if (this.useLeft && this.useTop && Window.hasEffectLib && Effect.ResizeWindow) {
        new Effect.ResizeWindow(this, top, left, width, height, {duration: Window.resizeEffectDuration});
      }
      else {
        this.setSize(width, height);
        this.element.setStyle(this.useLeft ? {left: left} : {right: left});
        this.element.setStyle(this.useTop ? {top: top} : {bottom: top});
      }
        
      this.toFront();
      if (this.iefix) 
        this._fixIEOverlapping(); 
    }
    this._notify("onMaximize");

    // Store new location/size if need be
    this._saveCookie()
  },
  
  isMinimized: function() {
    return this.minimized;
  },
  
  isMaximized: function() {
    return (this.storedLocation != null);
  },
  
  setOpacity: function(opacity) {
    if (Element.setOpacity)
      Element.setOpacity(this.element, opacity);
  },
  
  setZIndex: function(zindex) {
    this.element.setStyle({zIndex: zindex});
    Windows.updateZindex(zindex, this);
  },

  setTitle: function(newTitle) {
    if (!newTitle || newTitle == "") 
      newTitle = "&nbsp;";
      
    Element.update(this.element.id + '_top', newTitle);
  },
   
  getTitle: function() {
    return $(this.element.id + '_top').innerHTML;
  },
  
  setStatusBar: function(element) {
    var statusBar = $(this.getId() + "_bottom");

    if (typeof(element) == "object") {
      if (this.bottombar.firstChild)
        this.bottombar.replaceChild(element, this.bottombar.firstChild);
      else
        this.bottombar.appendChild(element);
    }
    else
      this.bottombar.innerHTML = element;
  },

  _checkIEOverlapping: function() {
    if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0) && (navigator.userAgent.indexOf('Opera')<0) && (this.element.getStyle('position')=='absolute')) {
        new Insertion.After(this.element.id, '<iframe id="' + this.element.id + '_iefix" '+ 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' + 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
        this.iefix = $(this.element.id+'_iefix');
    }
    if(this.iefix) 
      setTimeout(this._fixIEOverlapping.bind(this), 50);
  },

  _fixIEOverlapping: function() {
      Position.clone(this.element, this.iefix);
      this.iefix.style.zIndex = this.element.style.zIndex - 1;
      this.iefix.show();
  },
  
  _getWindowBorderSize: function(event) {
    // Hack to get real window border size!!
    var div = this._createHiddenDiv(this.options.className + "_n")
    this.heightN = Element.getDimensions(div).height;    
    div.parentNode.removeChild(div)

    var div = this._createHiddenDiv(this.options.className + "_s")
    this.heightS = Element.getDimensions(div).height;    
    div.parentNode.removeChild(div)

    var div = this._createHiddenDiv(this.options.className + "_e")
    this.widthE = Element.getDimensions(div).width;    
    div.parentNode.removeChild(div)

    var div = this._createHiddenDiv(this.options.className + "_w")
    this.widthW = Element.getDimensions(div).width;
    div.parentNode.removeChild(div);
    
    var div = document.createElement("div");
    div.className = "overlay_" + this.options.className ;
    document.body.appendChild(div);
    //alert("no timeout:\nopacity: " + div.getStyle("opacity") + "\nwidth: " + document.defaultView.getComputedStyle(div, null).width);
    var that = this;
    
    // Workaround for Safari!!
    setTimeout(function() {that.overlayOpacity = ($(div).getStyle("opacity")); div.parentNode.removeChild(div);}, 10);
    
    // Workaround for IE!!
    if (Prototype.Browser.IE) {
      this.heightS = $(this.getId() +"_row3").getDimensions().height;
      this.heightN = $(this.getId() +"_row1").getDimensions().height;
    }

    // Safari size fix
    if (Prototype.Browser.WebKit && Prototype.Browser.WebKitVersion < 420)
      this.setSize(this.width, this.height);
    if (this.doMaximize)
      this.maximize();
    if (this.doMinimize)
      this.minimize();
  },
 
  _createHiddenDiv: function(className) {
    var objBody = document.body;
    var win = document.createElement("div");
    win.setAttribute('id', this.element.id+ "_tmp");
    win.className = className;
    win.style.display = 'none';
    win.innerHTML = '';
    objBody.insertBefore(win, objBody.firstChild);
    return win;
  },
  
  _storeLocation: function() {
    if (this.storedLocation == null) {
      this.storedLocation = {useTop: this.useTop, useLeft: this.useLeft, 
                             top: this.element.getStyle('top'), bottom: this.element.getStyle('bottom'),
                             left: this.element.getStyle('left'), right: this.element.getStyle('right'),
                             width: this.width, height: this.height };
    }
  },
  
  _restoreLocation: function() {
    if (this.storedLocation != null) {
      this.useLeft = this.storedLocation.useLeft;
      this.useTop = this.storedLocation.useTop;
      
      if (this.useLeft && this.useTop && Window.hasEffectLib && Effect.ResizeWindow)
        new Effect.ResizeWindow(this, this.storedLocation.top, this.storedLocation.left, this.storedLocation.width, this.storedLocation.height, {duration: Window.resizeEffectDuration});
      else {
        this.element.setStyle(this.useLeft ? {left: this.storedLocation.left} : {right: this.storedLocation.right});
        this.element.setStyle(this.useTop ? {top: this.storedLocation.top} : {bottom: this.storedLocation.bottom});
        this.setSize(this.storedLocation.width, this.storedLocation.height);
      }
      
      Windows.resetOverflow();
      this._removeStoreLocation();
    }
  },
  
  _removeStoreLocation: function() {
    this.storedLocation = null;
  },
  
  _saveCookie: function() {
    if (this.cookie) {
      var value = "";
      if (this.useLeft)
        value += "l:" +  (this.storedLocation ? this.storedLocation.left : this.element.getStyle('left'))
      else
        value += "r:" + (this.storedLocation ? this.storedLocation.right : this.element.getStyle('right'))
      if (this.useTop)
        value += ",t:" + (this.storedLocation ? this.storedLocation.top : this.element.getStyle('top'))
      else
        value += ",b:" + (this.storedLocation ? this.storedLocation.bottom :this.element.getStyle('bottom'))
        
      value += "," + (this.storedLocation ? this.storedLocation.width : this.width);
      value += "," + (this.storedLocation ? this.storedLocation.height : this.height);
      value += "," + this.isMinimized();
      value += "," + this.isMaximized();
      WindowUtilities.setCookie(value, this.cookie)
    }
  },
  
  _createWiredElement: function() {
    if (! this.wiredElement) {
      if (Prototype.Browser.IE)
        this._getWindowBorderSize();
      var div = document.createElement("div");
      div.className = "wired_frame " + this.options.className + "_wired_frame";
      
      div.style.position = 'absolute';
      this.options.parent.insertBefore(div, this.options.parent.firstChild);
      this.wiredElement = $(div);
    }
    if (this.useLeft) 
      this.wiredElement.setStyle({left: this.element.getStyle('left')});
    else 
      this.wiredElement.setStyle({right: this.element.getStyle('right')});
      
    if (this.useTop) 
      this.wiredElement.setStyle({top: this.element.getStyle('top')});
    else 
      this.wiredElement.setStyle({bottom: this.element.getStyle('bottom')});

    var dim = this.element.getDimensions();
    this.wiredElement.setStyle({width: dim.width + "px", height: dim.height +"px"});

    this.wiredElement.setStyle({zIndex: Windows.maxZIndex+30});
    return this.wiredElement;
  },
  
  _hideWiredElement: function() {
    if (! this.wiredElement || ! this.currentDrag)
      return;
    if (this.currentDrag == this.element) 
      this.currentDrag = null;
    else {
      if (this.useLeft) 
        this.element.setStyle({left: this.currentDrag.getStyle('left')});
      else 
        this.element.setStyle({right: this.currentDrag.getStyle('right')});

      if (this.useTop) 
        this.element.setStyle({top: this.currentDrag.getStyle('top')});
      else 
        this.element.setStyle({bottom: this.currentDrag.getStyle('bottom')});

      this.currentDrag.hide();
      this.currentDrag = null;
      if (this.doResize)
        this.setSize(this.width, this.height);
    } 
  },
  
  _notify: function(eventName) {
    if (this.options[eventName])
      this.options[eventName](this);
    else
      Windows.notify(eventName, this);
  }
};

// Windows containers, register all page windows
var Windows = {
  windows: [],
  modalWindows: [],
  observers: [],
  focusedWindow: null,
  maxZIndex: 0,
  overlayShowEffectOptions: {duration: 0.5},
  overlayHideEffectOptions: {duration: 0.5},

  addObserver: function(observer) {
    this.removeObserver(observer);
    this.observers.push(observer);
  },
  
  removeObserver: function(observer) {  
    this.observers = this.observers.reject( function(o) { return o==observer });
  },
  
  // onDestroy onStartResize onStartMove onResize onMove onEndResize onEndMove onFocus onBlur onBeforeShow onShow onHide onMinimize onMaximize onClose
  notify: function(eventName, win) {  
    this.observers.each( function(o) {if(o[eventName]) o[eventName](eventName, win);});
  },

  // Gets window from its id
  getWindow: function(id) {
    return this.windows.detect(function(d) { return d.getId() ==id });
  },

  // Gets the last focused window
  getFocusedWindow: function() {
    return this.focusedWindow;
  },

  updateFocusedWindow: function() {
    this.focusedWindow = this.windows.length >=2 ? this.windows[this.windows.length-2] : null;    
  },
  
  // Registers a new window (called by Windows constructor)
  register: function(win) {
    this.windows.push(win);
  },
    
  // Add a modal window in the stack
  addModalWindow: function(win) {
    // Disable screen if first modal window
    if (this.modalWindows.length == 0) {
      WindowUtilities.disableScreen(win.options.className, 'overlay_modal', win.overlayOpacity, win.getId(), win.options.parent);
    }
    else {
      // Move overlay over all windows
      if (Window.keepMultiModalWindow) {
        $('overlay_modal').style.zIndex = Windows.maxZIndex + 1;
        Windows.maxZIndex += 1;
        WindowUtilities._hideSelect(this.modalWindows.last().getId());
      }
      // Hide current modal window
      else
        this.modalWindows.last().element.hide();
      // Fucking IE select issue
      WindowUtilities._showSelect(win.getId());
    }      
    this.modalWindows.push(win);    
  },
  
  removeModalWindow: function(win) {
    this.modalWindows.pop();
    
    // No more modal windows
    if (this.modalWindows.length == 0)
      WindowUtilities.enableScreen();     
    else {
      if (Window.keepMultiModalWindow) {
        this.modalWindows.last().toFront();
        WindowUtilities._showSelect(this.modalWindows.last().getId());        
      }
      else
        this.modalWindows.last().element.show();
    }
  },
  
  // Registers a new window (called by Windows constructor)
  register: function(win) {
    this.windows.push(win);
  },
  
  // Unregisters a window (called by Windows destructor)
  unregister: function(win) {
    this.windows = this.windows.reject(function(d) { return d==win });
  }, 
  
  // Closes all windows
  closeAll: function() {  
    this.windows.each( function(w) {Windows.close(w.getId())} );
  },
  
  closeAllModalWindows: function() {
    WindowUtilities.enableScreen();     
    this.modalWindows.each( function(win) {if (win) win.close()});    
  },

  // Minimizes a window with its id
  minimize: function(id, event) {
    var win = this.getWindow(id)
    if (win && win.visible)
      win.minimize();
    Event.stop(event);
  },
  
  // Maximizes a window with its id
  maximize: function(id, event) {
    var win = this.getWindow(id)
    if (win && win.visible)
      win.maximize();
    Event.stop(event);
  },

  // Closes a window with its id
  close: function(id, event) {
    var win = this.getWindow(id);
    if (win) 
      win.close();
    if (event)
      Event.stop(event);
  },
  
  blur: function(id) {
    var win = this.getWindow(id);  
    if (!win)
      return;
    if (win.options.blurClassName)
      win.changeClassName(win.options.blurClassName);
    if (this.focusedWindow == win)  
      this.focusedWindow = null;
    win._notify("onBlur");  
  },
  
  focus: function(id) {
    var win = this.getWindow(id);  
    if (!win)
      return;       
    if (this.focusedWindow)
      this.blur(this.focusedWindow.getId())

    if (win.options.focusClassName)
      win.changeClassName(win.options.focusClassName);  
    this.focusedWindow = win;
    win._notify("onFocus");
  },
  
  unsetOverflow: function(except) {    
    this.windows.each(function(d) { d.oldOverflow = d.getContent().getStyle("overflow") || "auto" ; d.getContent().setStyle({overflow: "hidden"}) });
    if (except && except.oldOverflow)
      except.getContent().setStyle({overflow: except.oldOverflow});
  },

  resetOverflow: function() {
    this.windows.each(function(d) { if (d.oldOverflow) d.getContent().setStyle({overflow: d.oldOverflow}) });
  },

  updateZindex: function(zindex, win) { 
    if (zindex > this.maxZIndex) {   
      this.maxZIndex = zindex;    
      if (this.focusedWindow) 
        this.blur(this.focusedWindow.getId())
    }
    this.focusedWindow = win;
    if (this.focusedWindow) 
      this.focus(this.focusedWindow.getId())
  }
};

var Dialog = {
  dialogId: null,
  onCompleteFunc: null,
  callFunc: null, 
  parameters: null, 
    
  confirm: function(content, parameters) {
    // Get Ajax return before
    if (content && typeof content != "string") {
      Dialog._runAjaxRequest(content, parameters, Dialog.confirm);
      return 
    }
    content = content || "";
    
    parameters = parameters || {};
    var okLabel = parameters.okLabel ? parameters.okLabel : "Ok";
    var cancelLabel = parameters.cancelLabel ? parameters.cancelLabel : "Cancel";

    // Backward compatibility
    parameters = Object.extend(parameters, parameters.windowParameters || {});
    parameters.windowParameters = parameters.windowParameters || {};

    parameters.className = parameters.className || "alert";

    var okButtonClass = "class ='" + (parameters.buttonClass ? parameters.buttonClass + " " : "") + " ok_button'" 
    var cancelButtonClass = "class ='" + (parameters.buttonClass ? parameters.buttonClass + " " : "") + " cancel_button'" 
    var content = "\
      <div class='" + parameters.className + "_message'>" + content  + "</div>\
        <div class='" + parameters.className + "_buttons'>\
          <input type='button' value='" + okLabel + "' onclick='Dialog.okCallback()' " + okButtonClass + "/>\
          <input type='button' value='" + cancelLabel + "' onclick='Dialog.cancelCallback()' " + cancelButtonClass + "/>\
        </div>\
    ";
    return this._openDialog(content, parameters)
  },
  
  alert: function(content, parameters) {
    // Get Ajax return before
    if (content && typeof content != "string") {
      Dialog._runAjaxRequest(content, parameters, Dialog.alert);
      return 
    }
    content = content || "";
    
    parameters = parameters || {};
    var okLabel = parameters.okLabel ? parameters.okLabel : "Ok";

    // Backward compatibility    
    parameters = Object.extend(parameters, parameters.windowParameters || {});
    parameters.windowParameters = parameters.windowParameters || {};
    
    parameters.className = parameters.className || "alert";
    
    var okButtonClass = "class ='" + (parameters.buttonClass ? parameters.buttonClass + " " : "") + " ok_button'" 
    var content = "\
      <div class='" + parameters.className + "_message'>" + content  + "</div>\
        <div class='" + parameters.className + "_buttons'>\
          <input type='button' value='" + okLabel + "' onclick='Dialog.okCallback()' " + okButtonClass + "/>\
        </div>";                  
    return this._openDialog(content, parameters)
  },
  
  info: function(content, parameters) {   
    // Get Ajax return before
    if (content && typeof content != "string") {
      Dialog._runAjaxRequest(content, parameters, Dialog.info);
      return 
    }
    content = content || "";
     
    // Backward compatibility
    parameters = parameters || {};
    parameters = Object.extend(parameters, parameters.windowParameters || {});
    parameters.windowParameters = parameters.windowParameters || {};
    
    parameters.className = parameters.className || "alert";
    
    var content = "<div id='modal_dialog_message' class='" + parameters.className + "_message'>" + content  + "</div>";
    if (parameters.showProgress)
      content += "<div id='modal_dialog_progress' class='" + parameters.className + "_progress'>  </div>";

    parameters.ok = null;
    parameters.cancel = null;
    
    return this._openDialog(content, parameters)
  },
  
  setInfoMessage: function(message) {
    $('modal_dialog_message').update(message);
  },
  
  closeInfo: function() {
    Windows.close(this.dialogId);
  },
  
  _openDialog: function(content, parameters) {
    var className = parameters.className;
    
    if (! parameters.height && ! parameters.width) {
      parameters.width = WindowUtilities.getPageSize(parameters.options.parent || document.body).pageWidth / 2;
    }
    if (parameters.id)
      this.dialogId = parameters.id;
    else { 
      var t = new Date();
      this.dialogId = 'modal_dialog_' + t.getTime();
      parameters.id = this.dialogId;
    }

    // compute height or width if need be
    if (! parameters.height || ! parameters.width) {
      var size = WindowUtilities._computeSize(content, this.dialogId, parameters.width, parameters.height, 5, className)
      if (parameters.height)
        parameters.width = size + 5
      else
        parameters.height = size + 5
    }
    parameters.effectOptions = parameters.effectOptions ;
    parameters.resizable   = parameters.resizable || false;
    parameters.minimizable = parameters.minimizable || false;
    parameters.maximizable = parameters.maximizable ||  false;
    parameters.draggable   = parameters.draggable || false;
    parameters.closable    = parameters.closable || false;
    
    var win = new Window(parameters);
    win.getContent().innerHTML = content;
    
    win.showCenter(true, parameters.top, parameters.left);  
    win.setDestroyOnClose();
    
    win.cancelCallback = parameters.onCancel || parameters.cancel; 
    win.okCallback = parameters.onOk || parameters.ok;
    
    return win;    
  },
  
  _getAjaxContent: function(originalRequest)  {
      Dialog.callFunc(originalRequest.responseText, Dialog.parameters)
  },
  
  _runAjaxRequest: function(message, parameters, callFunc) {
    if (message.options == null)
      message.options = {}  
    Dialog.onCompleteFunc = message.options.onComplete;
    Dialog.parameters = parameters;
    Dialog.callFunc = callFunc;
    
    message.options.onComplete = Dialog._getAjaxContent;
    new Ajax.Request(message.url, message.options);
  },
  
  okCallback: function() {
    var win = Windows.focusedWindow;
    if (!win.okCallback || win.okCallback(win)) {
      // Remove onclick on button
      $$("#" + win.getId()+" input").each(function(element) {element.onclick=null;})
      win.close();
    }
  },

  cancelCallback: function() {
    var win = Windows.focusedWindow;
    // Remove onclick on button
    $$("#" + win.getId()+" input").each(function(element) {element.onclick=null})
    win.close();
    if (win.cancelCallback)
      win.cancelCallback(win);
  }
}
/*
  Based on Lightbox JS: Fullsize Image Overlays 
  by Lokesh Dhakar - http://www.huddletogether.com

  For more information on this script, visit:
  http://huddletogether.com/projects/lightbox/

  Licensed under the Creative Commons Attribution 2.5 License - http://creativecommons.org/licenses/by/2.5/
  (basically, do anything you want, just leave my name and link)
*/

if (Prototype.Browser.WebKit) {
  var array = navigator.userAgent.match(new RegExp(/AppleWebKit\/([\d\.\+]*)/));
  Prototype.Browser.WebKitVersion = parseFloat(array[1]);
}

var WindowUtilities = {  
  // From dragdrop.js
  getWindowScroll: function(parent) {
    var T, L, W, H;
    parent = parent || document.body;              
    if (parent != document.body) {
      T = parent.scrollTop;
      L = parent.scrollLeft;
      W = parent.scrollWidth;
      H = parent.scrollHeight;
    } 
    else {
      var w = window;
      with (w.document) {
        if (w.document.documentElement && documentElement.scrollTop) {
          T = documentElement.scrollTop;
          L = documentElement.scrollLeft;
        } else if (w.document.body) {
          T = body.scrollTop;
          L = body.scrollLeft;
        }
        if (w.innerWidth) {
          W = w.innerWidth;
          H = w.innerHeight;
        } else if (w.document.documentElement && documentElement.clientWidth) {
          W = documentElement.clientWidth;
          H = documentElement.clientHeight;
        } else {
          W = body.offsetWidth;
          H = body.offsetHeight
        }
      }
    }
    return { top: T, left: L, width: W, height: H };
  }, 
  //
  // getPageSize()
  // Returns array with page width, height and window width, height
  // Core code from - quirksmode.org
  // Edit for Firefox by pHaez
  //
  getPageSize: function(parent){
    parent = parent || document.body;              
    var windowWidth, windowHeight;
    var pageHeight, pageWidth;
    if (parent != document.body) {
      windowWidth = parent.getWidth();
      windowHeight = parent.getHeight();                                
      pageWidth = parent.scrollWidth;
      pageHeight = parent.scrollHeight;                                
    } 
    else {
      var xScroll, yScroll;

      if (window.innerHeight && window.scrollMaxY) {  
        xScroll = document.body.scrollWidth;
        yScroll = window.innerHeight + window.scrollMaxY;
      } else if (document.body.scrollHeight > document.body.offsetHeight){ // all but Explorer Mac
        xScroll = document.body.scrollWidth;
        yScroll = document.body.scrollHeight;
      } else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
        xScroll = document.body.offsetWidth;
        yScroll = document.body.offsetHeight;
      }


      if (self.innerHeight) {  // all except Explorer
        windowWidth = self.innerWidth;
        windowHeight = self.innerHeight;
      } else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
        windowWidth = document.documentElement.clientWidth;
        windowHeight = document.documentElement.clientHeight;
      } else if (document.body) { // other Explorers
        windowWidth = document.body.clientWidth;
        windowHeight = document.body.clientHeight;
      }  

      // for small pages with total height less then height of the viewport
      if(yScroll < windowHeight){
        pageHeight = windowHeight;
      } else { 
        pageHeight = yScroll;
      }

      // for small pages with total width less then width of the viewport
      if(xScroll < windowWidth){  
        pageWidth = windowWidth;
      } else {
        pageWidth = xScroll;
      }
    }             
    return {pageWidth: pageWidth ,pageHeight: pageHeight , windowWidth: windowWidth, windowHeight: windowHeight};
  },

  disableScreen: function(className, overlayId, overlayOpacity, contentId, parent) {
    WindowUtilities.initLightbox(overlayId, className, function() {this._disableScreen(className, overlayId, overlayOpacity, contentId)}.bind(this), parent || document.body);
  },

  _disableScreen: function(className, overlayId, overlayOpacity, contentId) {
    // prep objects
    var objOverlay = $(overlayId);

    var pageSize = WindowUtilities.getPageSize(objOverlay.parentNode);

    // Hide select boxes as they will 'peek' through the image in IE, store old value
    if (contentId && Prototype.Browser.IE) {
      WindowUtilities._hideSelect();
      WindowUtilities._hideObject();
      WindowUtilities._showSelect(contentId);
    }  
  
    // set height of Overlay to take up whole page and show
    objOverlay.style.height = (pageSize.pageHeight + 'px');
    objOverlay.style.display = 'none'; 
    if (overlayId == "overlay_modal" && Window.hasEffectLib && Windows.overlayShowEffectOptions) {
      objOverlay.overlayOpacity = overlayOpacity;
      new Effect.Appear(objOverlay, Object.extend({from: 0, to: overlayOpacity}, Windows.overlayShowEffectOptions));
    }
    else
      objOverlay.style.display = "block";
  },
  
  enableScreen: function(id) {
    id = id || 'overlay_modal';
    var objOverlay =  $(id);
    if (objOverlay) {
      // hide lightbox and overlay
      if (id == "overlay_modal" && Window.hasEffectLib && Windows.overlayHideEffectOptions)
        new Effect.Fade(objOverlay, Object.extend({from: objOverlay.overlayOpacity, to:0}, Windows.overlayHideEffectOptions));
      else {
        objOverlay.style.display = 'none';
        objOverlay.parentNode.removeChild(objOverlay);
      }
      
      // make select boxes visible using old value
      if (id != "__invisible__") 
        WindowUtilities._showSelect();
        WindowUtilities._showObject();
    }
  },

  _hideObject: function(id) {
    if (Prototype.Browser.IE) {
      id = id ==  null ? "" : "#" + id + " ";
      $$(id + 'object').each(function(element) {
        if (! WindowUtilities.isDefined(element.oldVisibility)) {
          element.oldVisibility = element.style.visibility ? element.style.visibility : "visible";
          element.style.visibility = "hidden";
        }
      });
    }
  },

  _hideSelect: function(id) {
    if (Prototype.Browser.IE) {
      id = id ==  null ? "" : "#" + id + " ";
      $$(id + 'select').each(function(element) {
        if (! WindowUtilities.isDefined(element.oldVisibility)) {
          element.oldVisibility = element.style.visibility ? element.style.visibility : "visible";
          element.style.visibility = "hidden";
        }
      });
    }
  },

  _showObject: function(id) {
    if (Prototype.Browser.IE) {
      id = id ==  null ? "" : "#" + id + " ";
      $$(id + 'object').each(function(element) {
        if (WindowUtilities.isDefined(element.oldVisibility)) {
          // Why?? Ask IE
          try {
            element.style.visibility = element.oldVisibility;
          } catch(e) {
            element.style.visibility = "visible";
          }
          element.oldVisibility = null;
        }
        else {
          if (element.style.visibility)
            element.style.visibility = "visible";
        }
      });
    }
  },

  _showSelect: function(id) {
    if (Prototype.Browser.IE) {
      id = id ==  null ? "" : "#" + id + " ";
      $$(id + 'select').each(function(element) {
        if (WindowUtilities.isDefined(element.oldVisibility)) {
          // Why?? Ask IE
          try {
            element.style.visibility = element.oldVisibility;
          } catch(e) {
            element.style.visibility = "visible";
          }
          element.oldVisibility = null;
        }
        else {
          if (element.style.visibility)
            element.style.visibility = "visible";
        }
      });
    }
  },

  isDefined: function(object) {
    return typeof(object) != "undefined" && object != null;
  },
  
  // initLightbox()
  // Function runs on window load, going through link tags looking for rel="lightbox".
  // These links receive onclick events that enable the lightbox display for their targets.
  // The function also inserts html markup at the top of the page which will be used as a
  // container for the overlay pattern and the inline image.
  initLightbox: function(id, className, doneHandler, parent) {
    // Already done, just update zIndex
    if ($(id)) {
      Element.setStyle(id, {zIndex: Windows.maxZIndex + 1});
      Windows.maxZIndex++;
      doneHandler();
    }
    // create overlay div and hardcode some functional styles (aesthetic styles are in CSS file)
    else {
      var objOverlay = document.createElement("div");
      objOverlay.setAttribute('id', id);
      objOverlay.className = "overlay_" + className
      objOverlay.style.display = 'none';
      objOverlay.style.position = 'absolute';
      objOverlay.style.top = '0';
      objOverlay.style.left = '0';
      objOverlay.style.zIndex = Windows.maxZIndex + 1;
      Windows.maxZIndex++;
      objOverlay.style.width = '100%';
      parent.insertBefore(objOverlay, parent.firstChild);
      if (Prototype.Browser.WebKit && id == "overlay_modal") {
        setTimeout(function() {doneHandler()}, 10);
      }
      else
        doneHandler();
    }    
  },
  
  setCookie: function(value, parameters) {
    document.cookie= parameters[0] + "=" + escape(value) +
      ((parameters[1]) ? "; expires=" + parameters[1].toGMTString() : "") +
      ((parameters[2]) ? "; path=" + parameters[2] : "") +
      ((parameters[3]) ? "; domain=" + parameters[3] : "") +
      ((parameters[4]) ? "; secure" : "");
  },

  getCookie: function(name) {
    var dc = document.cookie;
    var prefix = name + "=";
    var begin = dc.indexOf("; " + prefix);
    if (begin == -1) {
      begin = dc.indexOf(prefix);
      if (begin != 0) return null;
    } else {
      begin += 2;
    }
    var end = document.cookie.indexOf(";", begin);
    if (end == -1) {
      end = dc.length;
    }
    return unescape(dc.substring(begin + prefix.length, end));
  },
    
  _computeSize: function(content, id, width, height, margin, className) {
    var objBody = document.body;
    var tmpObj = document.createElement("div");
    tmpObj.setAttribute('id', id);
    tmpObj.className = className + "_content";

    if (height)
      tmpObj.style.height = height + "px"
    else
      tmpObj.style.width = width + "px"
  
    tmpObj.style.position = 'absolute';
    tmpObj.style.top = '0';
    tmpObj.style.left = '0';
    tmpObj.style.display = 'none';

    tmpObj.innerHTML = content;
    objBody.insertBefore(tmpObj, objBody.firstChild);

    var size;
    if (height)
      size = $(tmpObj).getDimensions().width + margin;
    else
      size = $(tmpObj).getDimensions().height + margin;
    objBody.removeChild(tmpObj);
    return size;
  }  
}

Effect.ResizeWindow = Class.create();
Object.extend(Object.extend(Effect.ResizeWindow.prototype, Effect.Base.prototype), {
  initialize: function(win, top, left, width, height) {
    this.window = win;
    this.window.resizing = true;
    
    var size = win.getSize();
    this.initWidth    = parseFloat(size.width);
    this.initHeight   = parseFloat(size.height);

    var location = win.getLocation();
    this.initTop    = parseFloat(location.top);
    this.initLeft   = parseFloat(location.left);

    this.width    = width != null  ? parseFloat(width)  : this.initWidth;
    this.height   = height != null ? parseFloat(height) : this.initHeight;
    this.top      = top != null    ? parseFloat(top)    : this.initTop;
    this.left     = left != null   ? parseFloat(left)   : this.initLeft;

    this.dx     = this.left   - this.initLeft;
    this.dy     = this.top    - this.initTop;
    this.dw     = this.width  - this.initWidth;
    this.dh     = this.height - this.initHeight;
    
    this.r2      = $(this.window.getId() + "_row2");
    this.content = $(this.window.getId() + "_content");
        
    this.contentOverflow = this.content.getStyle("overflow") || "auto";
    this.content.setStyle({overflow: "hidden"});
    
    // Wired mode
    if (this.window.options.wiredDrag) {
      this.window.currentDrag = win._createWiredElement();
      this.window.currentDrag.show();
      this.window.element.hide();
    }

    this.start(arguments[5]);
  },
  
  update: function(position) {
    var width  = Math.floor(this.initWidth  + this.dw * position);
    var height = Math.floor(this.initHeight + this.dh * position);
    var top    = Math.floor(this.initTop    + this.dy * position);
    var left   = Math.floor(this.initLeft   + this.dx * position);

    if (window.ie) {
      if (Math.floor(height) == 0)  
        this.r2.hide();
      else if (Math.floor(height) >1)  
        this.r2.show();
    }      
    this.r2.setStyle({height: height});
    this.window.setSize(width, height);
    this.window.setLocation(top, left);
  },
  
  finish: function(position) {
    // Wired mode
    if (this.window.options.wiredDrag) {
      this.window._hideWiredElement();
      this.window.element.show();
    }

    this.window.setSize(this.width, this.height);
    this.window.setLocation(this.top, this.left);
    this.r2.setStyle({height: null});
    
    this.content.setStyle({overflow: this.contentOverflow});
      
    this.window.resizing = false;
  }
});

Effect.ModalSlideDown = function(element) {
  var windowScroll = WindowUtilities.getWindowScroll();    
  var height = element.getStyle("height");  
  element.setStyle({top: - (parseFloat(height) - windowScroll.top) + "px"});
  
  element.show();
  return new Effect.Move(element, Object.extend({ x: 0, y: parseFloat(height) }, arguments[1] || {}));
};


Effect.ModalSlideUp = function(element) {
  var height = element.getStyle("height");
  return new Effect.Move(element, Object.extend({ x: 0, y: -parseFloat(height) }, arguments[1] || {}));
};

PopupEffect = Class.create();
PopupEffect.prototype = {    
  initialize: function(htmlElement) {
    this.html = $(htmlElement);      
    this.options = Object.extend({className: "popup_effect", duration: 0.4}, arguments[1] || {});
    
  },
  show: function(element, options) { 
    var position = Position.cumulativeOffset(this.html);      
    var size = this.html.getDimensions();
    var bounds = element.win.getBounds();
    this.window =  element.win;      
    // Create a div
    if (!this.div) {
      this.div = document.createElement("div");
      this.div.className = this.options.className;
      this.div.style.height = size.height + "px";
      this.div.style.width  = size.width  + "px";
      this.div.style.top    = position[1] + "px";
      this.div.style.left   = position[0] + "px";   
      this.div.style.position = "absolute"
      document.body.appendChild(this.div);
    }                                                   
    if (this.options.fromOpacity)
      this.div.setStyle({opacity: this.options.fromOpacity})
    this.div.show();          
    var style = "top:" + bounds.top + ";left:" +bounds.left + ";width:" + bounds.width +";height:" + bounds.height;
    if (this.options.toOpacity)
      style += ";opacity:" + this.options.toOpacity;
    
    new Effect.Morph(this.div ,{style: style, duration: this.options.duration, afterFinish: this._showWindow.bind(this)});    
  },

  hide: function(element, options) {     
    var position = Position.cumulativeOffset(this.html);      
    var size = this.html.getDimensions();    
    this.window.visible = true; 
    var bounds = this.window.getBounds();
    this.window.visible = false; 

    this.window.element.hide();

    this.div.style.height = bounds.height;
    this.div.style.width  = bounds.width;
    this.div.style.top    = bounds.top;
    this.div.style.left   = bounds.left;
    
    if (this.options.toOpacity)
      this.div.setStyle({opacity: this.options.toOpacity})

    this.div.show();                                 
    var style = "top:" + position[1] + "px;left:" + position[0] + "px;width:" + size.width +"px;height:" + size.height + "px";

    if (this.options.fromOpacity)
      style += ";opacity:" + this.options.fromOpacity;
    new Effect.Morph(this.div ,{style: style, duration: this.options.duration, afterFinish: this._hideDiv.bind(this)});    
  },
  
  _showWindow: function() {
    this.div.hide();
    this.window.element.show(); 
  },
  
  _hideDiv: function() {
    this.div.hide();
  }
}

// Copyright (c) 2006 Sébastien Gruhier (http://xilinus.com, http://itseb.com)
// YOU MUST INCLUDE window.js BEFORE
//
// Object to store hide/show windows status in a cookie
// Just add at the end of your HTML file this javascript line: WindowStore.init()
WindowStore = {
  doSetCookie: false,
  cookieName:  "__window_store__",
  expired:     null,
  
  // Init function with two optional parameters
  // - cookieName (default = __window_store__)
  // - expiration date (default 3 years from now)
  init: function(cookieName, expired) {
    WindowStore.cookieName = cookieName || WindowStore.cookieName

    if (! expired) {
      var today = new Date();
      today.setYear(today.getYear()+1903);
      WindowStore.expired = today;
    }
    else
      WindowStore.expired = expired;

    Windows.windows.each(function(win) {
      win.setCookie(win.getId(), WindowStore.expired);
    });

    // Create observer on show/hide events
    var myObserver = {
    	onShow: function(eventName, win) {
    	  WindowStore._saveCookie();
    	},
    	
    	onClose: function(eventName, win) {
    	  WindowStore._saveCookie();
  	  },
  	  
    	onHide: function(eventName, win) {
    	  WindowStore._saveCookie();
    	}
    }
    Windows.addObserver(myObserver);

    WindowStore._restoreWindows();
    WindowStore._saveCookie();
  },
  
  show: function(win) {
    eval("var cookie = " + WindowUtilities.getCookie(WindowStore.cookieName));
    if (cookie != null) {
      if (cookie[win.getId()])
        win.show();
    }
    else
      win.show();
  },

  // Function to store windows show/hide status in a cookie 
  _saveCookie: function() {
    if (!doSetCookie)
      return;
    
    var cookieValue = "{";
    Windows.windows.each(function(win) {
      if (cookieValue != "{")
        cookieValue += ","
      cookieValue += win.getId() + ": " + win.isVisible();
    });
    cookieValue += "}"
  
    WindowUtilities.setCookie(cookieValue, [WindowStore.cookieName, WindowStore.expired]);  
  },

  // Function to restore windows show/hide status from a cookie if exists
  _restoreWindows: function() {
    eval("var cookie = " + WindowUtilities.getCookie(WindowStore.cookieName));
    if (cookie != null) {
      doSetCookie = false;
      Windows.windows.each(function(win) {
        if (cookie[win.getId()])
          win.show();
      });
    }
    doSetCookie = true;
  }
}

// Object to set a close key an all windows
WindowCloseKey = {
  keyCode: Event.KEY_ESC,
  
  init: function(keyCode) {
    if (keyCode)
      WindowCloseKey.keyCode = keyCode;      
      
    Event.observe(document, 'keydown', this._closeCurrentWindow.bindAsEventListener(this));   
  },
  
  _closeCurrentWindow: function(event) {
    var e = event || window.event
  	var characterCode = e.which || e.keyCode;
  	
  	// Check if there is a top window (it means it's an URL content)
  	var win = top.Windows.focusedWindow;
    if (characterCode == WindowCloseKey.keyCode && win) {
      if (win.cancelCallback) 
        top.Dialog.cancelCallback();      
      else if (win.okCallback) 
        top.Dialog.okCallback();
      else
        top.Windows.close(top.Windows.focusedWindow.getId());
    }
  }
}
// Copyright (c) 2006 - 2008 Gabriel Lanzani (http://www.glanzani.com.ar)
// 
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// SEE CHANGELOG FOR A COMPLETE CHANGES OVERVIEW
// VERSION 0.4

Autocompleter.SelectBox = Class.create();
Autocompleter.SelectBox.prototype = Object.extend(new Autocompleter.Base(), {
  initialize: function(select, options) {

	var maxLen = 0;
	$A( $( select ).options ).each( function( opt ) {
		var optStr = opt.innerHTML;
		if( !optStr ) return;
		var m = optStr.match( /([가-힣]{1})/g ) || [];
		maxLen = ( maxLen > ( optStr.length +( m.length || 0 ) ) ) ? maxLen : ( optStr.length +( m.length || 0 ) );
	} );

	this.element = "<input type=\"text\" id=\"" + $(select).id + "_combo\" " + ( maxLen ? "size=\"" + ( maxLen +4 ) + "\"" : "" ) + "/>";
	new Insertion.Before(select, this.element);
	var inputClasses = Element.classNames(select);
	
	this.update = "<div id=\"" + $(select).id + "_options\" class=\"" + inputClasses + "\"></div>"	
	new Insertion.Before(select, this.update)
			
    this.baseInitialize($(select).id + "_combo", $(select).id + "_options", options);
    this.select = select;
	this.selectOptions = [];
		
	$(this.element.id).setAttribute('readonly','readonly');
	this.element.readOnly = true;
	
	if(this.options.debug) var debugText = 'Debug input ' + this.element.id + ' and div ' + this.update.id + ' created, Autocompleter.Base() initialized\r\n';
	if(!this.options.debug)Element.hide(select);
	Element.addClassName(this.element.id, this.options.css) ;
	
	var optionList = $(this.select).getElementsByTagName('option');
	var nodes = $A(optionList);

	for(i=0; i<nodes.length;i++){
		this.selectOptions.push("<li id=\"" + nodes[i].value + "\">" + nodes[i].innerHTML + '</li>');
		if (nodes[i].getAttribute("selected")) this.element.value = nodes[i].innerHTML;
		
		if(this.options.debug) debugText += 'option ' + nodes[i].innerHTML + ' added to '+ this.update.id + "\r\n";
	}
	
	Event.observe(this.element, "click", this.activate.bindAsEventListener(this));
	
	if ($(select).selectedIndex >= 0)this.element.value = $(select).options[$(select).selectedIndex].innerHTML;
	
	var self = this;
	this.options.updateElement = function( li ) {
		self.element.value = li.innerHTML;
		self.element.focus();

		if (self.options.afterUpdateElement)
		  self.options.afterUpdateElement(self.element, li);
	}

	this.options.afterUpdateElement = function(text, li) {
		var optionList = $(select).getElementsByTagName('option');
		var nodes = $A(optionList);

		var opt = nodes.find( function(node){
			return (node.value == li.id);
		});
		$(select).selectedIndex=opt.index;

		if(self.options.redirect) document.location.href = opt.value;
		if(self.options.submit != '') $(self.options.submit).submit();
	}
	
	if(this.options.debug) alert(debugText);
  },

  getUpdatedChoices: function() {
  		this.updateChoices(this.setValues());
  },

  setValues : function(){
		return ("<ul>" + this.selectOptions.join('') + "</ul>");
  },
  
  setOptions: function(options) {
    this.options = Object.extend({
		//MORE OPTIONS TO EXTEND THIS CLASS
		submit		: false, //form Id to submit after change 
		redirect	: false, // redirects to option value
		debug		: false, //show alerts with information
		css			: 'combo'	 //css class for new element 
	}, options || {});
  }
})

// Copyright (c) 2006 Tafel, Fabien Tafelmacher
//
// See http://membres.lycos.fr/tafelmak/arbre.php for more info
// 
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// This is distributed under Free-BSD licence.

/*
@todo
------------------
check-restore default
gestion xml (load + save)
multidrop
scrolldrop
*/






/**
 *------------------------------------------------------------------------------
 *							TafelTree Class
 *------------------------------------------------------------------------------
 */

var TAFELTREE_WRONG_BRANCH_STRUCTURE = "La structure de la branche n'est pas correcte. Il faut au moins un id et un texte";
var TAFELTREE_NO_BODY_TAG = "Il n'y a pas de balise BODY!";
var TAFELTREE_DEBUG = false;

var TafelTree = Class.create();

/**
 * Fragment de script pour supprimer les <ul></ul> lors du loadFromUL()
 */
//TafelTree.scriptFragment = /(?:<ul.*?>)((\n|\r|.)*)*/img;

TafelTree.version = '1.9';
TafelTree.lastUpdate = '2007-06-05';

TafelTree.scriptFragment = /[\s]*<[/]?[ul|li].*>.*/ig;

TafelTree.debugReturn = '<br />';
TafelTree.debugTab = '&nbsp;&nbsp;&nbsp;&nbsp;';

/**
 * Attributs des LI
 */
TafelTree.prefixAttribute = 'T';
TafelTree.textAttributes = [
	TafelTree.prefixAttribute + 'img',
	TafelTree.prefixAttribute + 'imgopen',
	TafelTree.prefixAttribute + 'imgclose',
	TafelTree.prefixAttribute + 'imgselect',
	TafelTree.prefixAttribute + 'imgselectopen',
	TafelTree.prefixAttribute + 'imgselectclose',
	TafelTree.prefixAttribute + 'style',
	TafelTree.prefixAttribute + 'droplink',
	TafelTree.prefixAttribute + 'openlink',
	TafelTree.prefixAttribute + 'editlink',
	TafelTree.prefixAttribute + 'tooltip',
	TafelTree.prefixAttribute + 'title'
];
TafelTree.numericAttributes = [
	TafelTree.prefixAttribute + 'canhavechildren',
	TafelTree.prefixAttribute + 'acceptdrop',
	TafelTree.prefixAttribute + 'draggable',
	TafelTree.prefixAttribute + 'editable',
	TafelTree.prefixAttribute + 'open',
	TafelTree.prefixAttribute + 'check',
	TafelTree.prefixAttribute + 'checkbox',
	TafelTree.prefixAttribute + 'last'
];
TafelTree.functionAttributes = [
	TafelTree.prefixAttribute + 'onbeforecheck',
	TafelTree.prefixAttribute + 'oncheck',
	TafelTree.prefixAttribute + 'onclick',
	TafelTree.prefixAttribute + 'ondblclick',
	TafelTree.prefixAttribute + 'onbeforeopen',
	TafelTree.prefixAttribute + 'onopen',
	TafelTree.prefixAttribute + 'onedit',
	TafelTree.prefixAttribute + 'oneditajax',
	TafelTree.prefixAttribute + 'onmouseover',
	TafelTree.prefixAttribute + 'onmouseout',
	TafelTree.prefixAttribute + 'onmousedown',
	TafelTree.prefixAttribute + 'onmouseup',
	TafelTree.prefixAttribute + 'ondrop',
	TafelTree.prefixAttribute + 'ondragstarteffect',
	TafelTree.prefixAttribute + 'ondragendeffect',
	TafelTree.prefixAttribute + 'onerrorajax',
	TafelTree.prefixAttribute + 'ondropajax',
	TafelTree.prefixAttribute + 'onopenpopulate'
];
	


/**
 *------------------------------------------------------------------------------
 *							TafelTree static methods
 *------------------------------------------------------------------------------
 */

/**
 * Constructeur d'un nouvel arbre via UL
 *
 * @access	public
 * @param	string				id						L'id de l'élément HTML conteneur
 * @param	string				imgBase					Le path vers les images, ou les options
 * @param	integer				width					La largeur de l'arbre
 * @param	integer				height					La hauteur de l'arbre
 * @param	object				options					Les options de génération
 * @return	TafelTree									L'arbre créé sur la base des UL - LI
 */	
TafelTree.loadFromUL = function (id, imgBase, width, height, options, debug) {
	// 2006-11-25 : "options" est maintenant à la place de "imgBase"
	if (typeof(imgBase) == 'object') {
		options = imgBase;
		debug = width;
		imgBase = (options.imgBase) ? options.imgBase : 'imgs/';
		width = (options.width) ? options.width : '100%';
		height = (options.height) ? options.height : 'auto';
	}

	// Suite
	var obj = $(id);
	var load = document.createElement('img');
	load.setAttribute('title', 'load');
	load.setAttribute('alt', 'load');
	load.src = ((imgBase) ? imgBase : 'imgs/') + 'load.gif';
	obj.parentNode.insertBefore(load, obj);
	Element.hide(obj);
	
	var tab = '';
	var tabModel = (debug) ? TafelTree.debugTab : '';
	var rt = (debug) ? TafelTree.debugReturn : '';
	var virgule = '';
	var str = (debug) ? 'var struct = [' : '([';
	for (var i = 0; i < obj.childNodes.length; i++) {
		if (obj.childNodes[i].nodeName.toLowerCase() == 'li') {
			str += this._loadFromUL(obj.childNodes[i], virgule, rt, tab, tabModel);
			virgule = ',';
		}
	}
	str += rt + ((debug) ? '];' : '])');
	
	var div = document.createElement('div');
	div.id = obj.id;
	obj.id += '____todelete';
	obj.parentNode.insertBefore(div, obj);
	if (!debug) {
		var m = TafelTree.prefixAttribute;
		var struct = eval(str);
		var _tree = new TafelTree(id, struct, options);
	} else {
		div.innerHTML = str.replace(/</img, '&lt;');
		var _tree = str;
	}
	obj.parentNode.removeChild(load);
	obj.parentNode.removeChild(obj);
	return _tree;
};

/**
 * Méthode récursive qui va récupérer les infos de tous les LI
 *
 * @access	private
 * @param	HTMLLiElement		obj						La LI courante
 * @param	string				virgule					Détermine s'il y a une virgule avant l'accolade ouvrante
 * @param	string				rt						Retour de ligne (pour le debug)
 * @param	string				tab						La tabulation courante (pour le debug)
 * @param	string				tabModel				La grandeur d'une tabulation (pour le debug)
 * @return	string										La string JSON dérivée de la structure UL - LI
 */
TafelTree._loadFromUL = function (obj, virgule, rt, tab, tabModel) {
	tab += tabModel;
	var contenu = TafelTree.trim(obj.innerHTML.replace(TafelTree.scriptFragment, ''));
	var str = virgule + rt + tab + '{' + rt;
	// Définition de toutes les propriétés
	str += tab + "'id' : '" + obj.id + "'";
	if (contenu) {
		str += "," + rt + tab + "'txt' : '" + contenu + "'";
	}
	TafelTree.textAttributes.each(function (attr) {
		if (obj.getAttribute(attr)) str += "," + rt + tab + "'" + attr.replace(TafelTree.prefixAttribute, '') + "' : '" + obj.getAttribute(attr) + "'";
	});
	TafelTree.numericAttributes.each(function (attr) {
		if (obj.getAttribute(attr)) str += "," + rt + tab + "'" + attr.replace(TafelTree.prefixAttribute, '') + "' : " + obj.getAttribute(attr);
	});
	TafelTree.functionAttributes.each(function (attr) {
		if (obj.getAttribute(attr)) str += "," + rt + tab + "'" + attr.replace(TafelTree.prefixAttribute, '') + "' : " + obj.getAttribute(attr);
	});
	// Définition des enfants
	for (var i = 0; i < obj.childNodes.length; i++) {
		if (obj.childNodes[i].nodeName.toLowerCase() == 'ul') {
			virgule = '';
			str += ',' + rt + tab + "'items' : [";
			for (var j = 0; j < obj.childNodes[i].childNodes.length; j++) {
				if (obj.childNodes[i].childNodes[j].nodeName.toLowerCase() == 'li') {
					str += this._loadFromUL(obj.childNodes[i].childNodes[j], virgule, rt, tab, tabModel);
					virgule = ',';
				}
			}
			str += rt + tab + ']';
		}			
	}
	str += rt + tab + '}';
	return str;
};

TafelTree.trim = function (string) {
	return string.replace(/(^\s*)|(\s*$)|\n|\r|\t/g,'');
};




/**
 *------------------------------------------------------------------------------
 *							TafelTree Class Constructor
 *------------------------------------------------------------------------------
 */

TafelTree.prototype = {
	/**
	 * Constructeur d'un nouvel arbre. Supporte facilement 700 éléments
	 *
	 * 2006-11-25 : changement des paramètres dans le constructeur. On peut mettre les options
	 * à la place de "imgBase". Les autres paramètres peuvent maintenant être passé via
	 * "options"
	 *
	 * @access	public
	 * @param	string				id						L'id de l'élément HTML conteneur
	 * @param	object				struct					La structure de l'arbre
	 * @param	string				imgBase					Le path vers les images (ou les options)
	 * @param	integer				width					La largeur de l'arbre
	 * @param	integer				height					La hauteur de l'arbre
	 * @param	object				options					Les fonctions de load et autres binz de génération
	 */	
	initialize : function (id, struct, imgBase, width, height, options) {
		// 2006-11-25 : "options" est maintenant à la place de "imgBase"
		if (typeof(imgBase) == 'object') {
			options = imgBase;
			imgBase = (options.imgBase) ? options.imgBase : 'imgs/';
			width = (options.width) ? options.width : '100%';
			height = (options.height) ? options.height : 'auto';
		}
		// Variables images
		this.imgBase = (imgBase) ? imgBase : 'imgs/';
		this.setLineStyle('line');
		this.options = (options) ? options : {};
		this.copyName = ' (%n)';
		this.copyNameBreak = '_';
		
		// Variables CSS
		this.classTree = 'tafelTree';
		this.classTreeRoot = this.classTree + '_root';
		this.classTreeBranch = this.classTree + '_row';
		this.classCopy = (this.options.copyCSS) ? this.options.copyCSS : null;
		this.classCut = (this.options.cutCSS) ? this.options.cutCSS : null;
		this.classDrag = 'tafelTreedrag';
		this.classSelected = 'tafelTreeselected';
		this.classEditable = 'tafelTreeeditable';
		this.classContent = 'tafelTreecontent';
		this.classCanevas = 'tafelTreecanevas';
		this.classDragOver = 'tafelTreedragOver';
		this.classTooltip = 'tafelTreetooltip';
		this.classOpenable = 'tafelTreeopenable';

		// Default structure
		this.defaultStruct = [];
		/*for (var i = 0; i < struct.length; i++) {
			this.defaultStruct.push(Object.clone(struct[i]));
		}*/
		
		// Autres variables
		this.idTree = 0;
		this.behaviourDrop = 0;
		this.durationTooltipShow = 1000;
		this.durationTooltipHide = 100;
		this.baseStruct = struct;
		this.width = (width) ? width : '100%';
		this.height = (height) ? height : 'auto';
		this.div = $(id);
		this.div.style.width = this.width;
		this.div.style.height = this.height;
		this.id = this.div.id;
		this.isTree = true;
		this.dropALT = true;
		this.openAll = false;
		this.rtlMode = false;
		this.dropCTRL = true;
		this.multiline = false;
		this.checkboxes = false;
		this.propagation = true;
		this.dragRevert = true;
		this.dragGhosting = true;
		this.bigTreeLoading = -1;
		this.dropAsSibling = true;
		this.onlyOneOpened = false;
		this.openedAfterAdd = true;
		this.editableBranches = true;
		this.reopenFromServer = true;
		this.selectedBranchShowed = true,
		this.checkboxesThreeState = false;
		this.roots = [];
		this.icons = [null, null, null];
		this.iconsSelected = [null, null, null];
		this.otherTrees = [];
		this.cuttedBranches = [];
		this.copiedBranches = [];
		this.checkedBranches = [];
		this.selectedBranches = [];
		this.idTreeBranch = this.classTree + '_' + this.id + '_id_';
        this.loaded = false;
		Element.addClassName(this.div, this.classTree);

		// Cookie
		this.useCookie = true;
		this.cookieSeparator = '|';
		this.cookieCheckSeparator = '[check]';
		this.cookieOpened = null;
		this.cookieChecked = null;
        this.setOptions(this.options);
		var fromCookie = this.getCookie(this.classTree + this.id);
		if (fromCookie) {
			var branches = fromCookie.split(this.cookieCheckSeparator);
			// Branches ouvertes
			this.cookieOpened = [];
			this.cookieOpened = branches[0].split(this.cookieSeparator);
			this.cookieOpened.shift();
			// Branches checkées (avec anti-bug pour les anciennes versions et anciens cookies)
			this.cookieChecked = [];
			if (branches.length > 1) {
				this.cookieChecked = branches[1].split(this.cookieSeparator);
			}
		}

		// Instance de debug
		this.debugObj = document.createElement('div');
		this.debugObj.setAttribute('id', this.classTree + '_debug');
		Element.hide(this.debugObj);
		this.div.appendChild(this.debugObj);
		// Instance Ajax
		this.ajaxObj = document.createElement('div');
		this.ajaxObj.setAttribute('id', this.classTree + '_ajax');
		Element.hide(this.ajaxObj);
		this.div.appendChild(this.ajaxObj);
		Event.observe(this.div, 'mousedown', this.evt_setAsCurrent.bindAsEventListener(this), false);
		Event.observe(this.div, 'focus', this.evt_setAsCurrent.bindAsEventListener(this), false);
		// don't generate if cookie is on server side
        if (!this.serverCookie) {
            if (this.options.generate) {
    			this.generate();
    		}
    		if (this.options.generateBigTree) {
    			this.generate(true);
    		}
        }
		TafelTreeManager.add(this);
	},


	/**
	 *------------------------------------------------------------------------------
	 *							TafelTree global events management
	 *------------------------------------------------------------------------------
	 */

	/**
	 * Set l'arbre comme étant l'arbre courant
	 *
	 * Toutes les actions claviers auront effet seulement sur cet arbre et non sur les autres*
	 *
	 * @access	public
	 * @param	Event			ev						L'événement déclencheur
	 * @return	void
	 */
	evt_setAsCurrent : function (ev) {
		var obj = Event.element(ev);
		TafelTreeManager.setCurrentTree(this);
	},
	
	/**
	 * Retourne une liste de branches ordrée (en fonction de leur position dans l'arbre)
	 *
	 * @access	public
	 * @param	array				list					Un tableau de TafelTreeBranch
	 * @return	array										Un tableau ordré de TafelTreeBranch
	 */
	orderListBranches : function (list) {
		var ordered = [];
		var level = [];
		var nivmin = 100;
		var nivmax = 0;
		// On ordre par niveau
		var niv = 0;
		for (var i = 0; i < list.length; i++) {
			niv = list[i].getLevel();
			if (typeof(level[niv]) == 'undefined') {
				level[niv] = [];
			}
			level[niv].push(list[i]);
			if (niv > nivmax) nivmax = niv;
			if (niv < nivmin) nivmin = niv;
		}
		// On enlève le cheni de m... à cause de la gestion tableau javascript
		for (var i = nivmin; i <= nivmax; i++) {
			if (level[i]) {
				ordered.push(level[i]);
			}
		}
		// Pour chaque niveau, on ordre par position dans l'arbre

		// On ne récupère que les 1er niveaux. S'il y a des enfants, on les ignore
		return ordered;
	},
	
	/**
	 * Retourne les branches copiées de l'arbre, ou d'un arbre lié
	 *
	 * Si le tableau n'a pas de branches, la méthode va voir dans les arbres liés
	 * pour récupérer les branches qui seraient copiées dans l'autre arbre
	 *
	 * @access	public
	 * @return	array									Les branches copiées
	 */
	getCopiedBranches : function () {
		var branches = this.copiedBranches;
		if (branches.length == 0) {
			for (var i = 0; i < this.otherTrees.length; i++) {
				branches = this.otherTrees[i].copiedBranches;
				if (branches.length > 0) break;
			}
		}
		return branches;
	},
	
	/**
	 * Retourne les branches coupées de l'arbre, ou d'un arbre lié
	 *
	 * Si le tableau n'a pas de branches, la méthode va voir dans les arbres liés
	 * pour récupérer les branches qui seraient coupées dans l'autre arbre
	 *
	 * @access	public
	 * @return	array									Les branches coupées
	 */
	getCuttedBranches : function () {
		var branches = this.cuttedBranches;
		if (branches.length == 0) {
			for (var i = 0; i < this.otherTrees.length; i++) {
				branches = this.otherTrees[i].cuttedBranches;
				if (branches.length > 0) break;
			}
		}
		return branches;
	},
	
	/**
	 * Fonction qui coupe la sélection et la met dans un cache
	 *
	 * @access	public
	 * @return	return										True si ça coupe, false sinon
	 */
	cut : function () {
		this.unsetCut();
		this.unsetCopy();
		var level = this.orderListBranches(this.selectedBranches);
		//this.debug(level);
		var sel = null;
		for (var i = 0; i < level.length; i++) {
			for (var j = 0; j < level[i].length; j++) {
				sel = level[i][j];
				this._cut(sel);
				this.cuttedBranches.push(sel);
			}
		}
		return true;
	},
	
	/**
	 * Fonction qui copie la sélection dans un cache
	 *
	 * @access	public
	 * @return	return										True si ça copie, false sinon
	 */
	copy : function () {
		this.unsetCut();
		this.unsetCopy();
		var level = this.orderListBranches(this.selectedBranches);
		var sel = null;
		for (var i = 0; i < this.selectedBranches.length; i++) {
			sel = this.selectedBranches[i];
			this._copy(sel);
			this.copiedBranches[i] = sel;
		}
		return true;
	},

	/**
	 * Fonction qui colle le cache à l'endroit sélectionné. Il ne doit y avoir qu'une sélection
	 *
	 * @access	public
	 * @return	return										True si ça colle, false sinon
	 */
	paste : function () {
		if (this.selectedBranches.length != 1) return false;
		var branch = this.selectedBranches[0];
		var copied = this.getCopiedBranches();
		var cutted = this.getCuttedBranches();
		var nbCopy = copied.length;
		var nbCut = cutted.length;
		if (nbCopy > 0) {
			var list = copied;
			var b = null;
			for (var i = 0; i < list.length; i++) {
				if (this._okForPaste(branch, list, i)) {
					b = branch.insertIntoLast(list[i].clone());
				}
			}
		}
		if (nbCut > 0) {
			var list = cutted;
			for (var i = 0; i < list.length; i++) {
				if (this._okForPaste(branch, list, i)) {
					list[i].move(branch);
				}
			}
		}
		//this.unsetCopy();
		this.unsetCut();
		return true;
	},

	/**
	 * Détermine si on peut coller la partie courante du cache ou non
	 *
	 * @access	private
	 * @param	TafelTreeBranch		branch					La branche dans laquelle on colle
	 * @param	array				list					Le tableau de cache ordré par niveau
	 * @param	integer				i						La position courante dans le cache
	 * @return	boolean										True si on peut coller, false sinon
	 */
	_okForPaste : function (branch, list, i) {
		var ok = true;
		if (!branch.isChild(list[i])) {
			for (var j = 0; j < i; j++) {
				if (list[i].isChild(list[j])) {
					ok = false;
					break;
				}
			}
		} else {
			ok = false;
		}
		return ok;
	},
	
	unsetCut : function () {
		// On enlève les branches coupées des autres arbres (suppression du "presse-papier")
		var _tree = null;
		var branches = null;
		for (var i = 0; i < this.otherTrees.length; i++) {
			_tree = this.otherTrees[i];
			branches = _tree.cuttedBranches;
			for (var t = 0; t < branches.length; t++) {
				_tree._unsetCut(branches[t]);
			}
			_tree.cuttedBranches = [];
		}
		var cut = null;
		for (var i = 0; i < this.cuttedBranches.length; i++) {
			cut = this.cuttedBranches[i];
			this._unsetCut(cut);
		}
		this.cuttedBranches = [];
	},
	
	unsetCopy : function () {
		// On enlève les branches copiées des autres arbres (suppression du "presse-papier")
		var _tree = null;
		var branches = null;
		for (var i = 0; i < this.otherTrees.length; i++) {
			_tree = this.otherTrees[i];
			branches = _tree.copiedBranches;
			for (var t = 0; t < branches.length; t++) {
				_tree._unsetCopy(branches[t]);
			}
			_tree.copiedBranches = [];
		}
		var copy = null;
		for (var i = 0; i < this.copiedBranches.length; i++) {
			copy = this.copiedBranches[i];
			this._unsetCopy(copy);
		}		
		this.copiedBranches = [];
	},

	/**
	 * Annule les [n] dernières actions (todo...)
	 *
	 * @access	public
	 * @return	boolean									True si quelque chose a été annulé
	 */
	undo : function () {

	},


	/**
	 *------------------------------------------------------------------------------
	 *							TafelTree private keyboard methods
	 *------------------------------------------------------------------------------
	 */

	/**
	 * Méthode récursive qui fait l'effet "couper" sur toutes les sous-branches
	 *
	 * @access	private
	 * @param	TafelTreeBranch		branch					La branche courante
	 * @return	void
	 */
	_cut : function (branch) {
		if (!this.classCut) {
			new Effect.Opacity(branch.txt, {
				duration: 0.1, 
				transition: Effect.Transitions.linear, 
				from: 1.0, to: 0.4
			});
			new Effect.Opacity(branch.img, {
				duration: 0.1, 
				transition: Effect.Transitions.linear, 
				from: 1.0, to: 0.4
			});
		} else {
			Element.addClassName(branch.txt, this.classCut);
			Element.addClassName(branch.img, this.classCut);
		}
		if (branch.hasChildren()) {
			for (var i = 0; i < branch.children.length; i++) {
				this._cut(branch.children[i]);
			}
		}
	},

	/**
	 * Méthode récursive qui enlève l'effet "couper" sur toutes les sous-branches
	 *
	 * @access	private
	 * @param	TafelTreeBranch		branch					La branche courante
	 * @return	void
	 */
	_unsetCut : function (branch) {
		if (!this.classCut) {
			new Effect.Opacity(branch.txt, {
				duration: 0.1, 
				transition: Effect.Transitions.linear, 
				from: 0.4, to: 1.0
			});
			new Effect.Opacity(branch.img, {
				duration: 0.1, 
				transition: Effect.Transitions.linear, 
				from: 0.4, to: 1.0
			});
		} else {
			Element.removeClassName(branch.txt, this.classCut);
			Element.removeClassName(branch.img, this.classCut);
		}
		if (branch.hasChildren()) {
			for (var i = 0; i < branch.children.length; i++) {
				this._unsetCut(branch.children[i]);
			}
		}
	},

	/**
	 * Méthode récursive qui fait l'effet "copier" sur toutes les sous-branches
	 *
	 * @access	private
	 * @param	TafelTreeBranch		branch					La branche courante
	 * @return	void
	 */
	_copy : function (branch) {
		if (this.classCopy) {
			Element.addClassName(branch.txt, this.classCopy);
			Element.addClassName(branch.img, this.classCopy);
		}
		if (branch.hasChildren()) {
			for (var i = 0; i < branch.children.length; i++) {
				this._copy(branch.children[i]);
			}
		}
	},

	/**
	 * Méthode récursive qui enlève l'effet "copier" sur toutes les sous-branches
	 *
	 * @access	private
	 * @param	TafelTreeBranch		branch					La branche courante
	 * @return	void
	 */
	_unsetCopy : function (branch) {
		if (this.classCopy) {
			Element.removeClassName(branch.txt, this.classCopy);
			Element.removeClassName(branch.img, this.classCopy);
		}
		if (branch.hasChildren()) {
			for (var i = 0; i < branch.children.length; i++) {
				this._unsetCopy(branch.children[i]);
			}
		}
	},

	/**
	 *------------------------------------------------------------------------------
	 *							TafelTree getters and setters methods
	 *------------------------------------------------------------------------------
	 */
	
	enableMultiline : function (multiline) {
		this.multiline = (multiline) ? true : false;
	},
	
	enableRTL : function (rtl) {
		this.rtlMode = (rtl) ? true : false;
		if (this.rtlMode) {
			// plante sous Safari...
            //this.div.style.float = 'right';
			this.div.style.textAlign = 'right';
			this.div.style.direction = 'rtl';
		} else {
            // plante sous Safari
			//this.div.style.float = 'left';
			this.div.style.textAlign = 'left';
			this.div.style.direction = 'ltr';
		}
		this.setLineStyle(this.lineStyle);
	},
	
	isRTL : function () {
		return this.rtlMode;
	},

	disableDropALT : function (alt) {
		this.dropALT = (alt) ? true : false;
	},
	
	disableDropCTRL : function (copy) {
		this.dropCTRL = (copy) ? true : false;
	},

	enableCheckboxes : function (enable) {
		this.checkboxes = (enable) ? true : false;
	},

	enableCheckboxesThreeState : function (enable) {
		this.enableCheckboxes(enable);
		this.checkboxesThreeState = (enable) ? true : false;
	},
	
	/**
	 * Permet d'utiliser les cookies ou non. Le séparateur est optionnel, '|' par défaut
	 *
	 * @access	public
	 * @param	boolean				enable					True (par défaut) pour gérer les cookies
	 * @param	string				separator				Le séparateur dans le cookie )
	 */
	enableCookies : function (enable, separator) {
		this.useCookie = (enable) ? true : false;
		if (typeof(separator) != 'undefined') {
			this.cookieSeparator = separator;
		}
	},

	openOneAtOnce : function (yes) {
		this.onlyOneOpened = (yes) ? true : false;
	},

	openAfterAdd : function (yes) {
		this.openedAfterAdd = (yes) ? true : false;
	},

	reopenFromServerAfterLoad : function (yes) {
		this.reopenFromServer = (yes) ? true : false;
	},
	
	/**
	 * Fonction qui set par défaut l'état des branches (ouvert ou fermé)
	 *
	 * @access	public
	 * @param	boolean				open					True pour tout ouvrir, false pour tout fermer
	 * @return	void
	 */
	openAtLoad : function (open) {
		this.openAll = (open) ? true : false;
	},

	showSelectedBranch : function (show) {
		this.selectedBranchShowed = (show) ? true : false;
	},
	
	/**
	 * Permet de choisir quel comportement par defaut le drop aura
	 *
	 * L'autre comportement s'obtient en gardant la touche ALT appuyée et/ou CTRL
	 *
	 * @access	public
	 * @param	string			def							'sibling', 'siblingcopy', 'child' ou 'childcopy'
	 * @return	void
	 */
	setBehaviourDrop : function (def) {
		switch (def) {
			case 'sibling' : this.behaviourDrop = 1; break;
			case 'childcopy' : this.behaviourDrop = 2; break;
			case 'siblingcopy' : this.behaviourDrop = 3; break;
			case 'child' :
			default :
				this.behaviourDrop = 0;
		}
	},
	
	/**
	 * Set les icônes par défaut pour toutes les branches
	 *
	 * Si imgopen n'est pas défini, il prend la valeur d'img. Pareil pour imgclose.
	 *
	 * @access	public
	 * @param	string				img						L'image quand la branche n'a pas d'enfants
	 * @param	string				imgopen					L'image quand la branche des enfants et est ouverte
	 * @param	string				imgclose				L'image quand la branche a des enfants et est fermée
	 * @return	void
	 */
	setIcons : function (img, imgopen, imgclose) {
		this.icons[0] = img;
		this.icons[1] = (imgopen) ? imgopen : img;
		this.icons[2] = (imgclose) ? imgclose : img;
	},
	
	/**
	 * Set les icônes de sélection par défaut pour toutes les branches
	 *
	 * @access	public
	 * @param	string				img						L'image quand la branche n'a pas d'enfants
	 * @param	string				imgopen					L'image quand la branche des enfants et est ouverte
	 * @param	string				imgclose				L'image quand la branche a des enfants et est fermée
	 * @return	void
	 */
	setIconsSelected : function (img, imgopen, imgclose) {
		this.iconsSelected[0] = img;
		this.iconsSelected[1] = (imgopen) ? imgopen : null;
		this.iconsSelected[2] = (imgclose) ? imgclose : null;
	},

	/**
	 * Fonction qui permet de choisir le style des lignes entre les branches
	 *
	 * @access	public
	 * @param	string				style					LE style des lignes, 'none' ou 'line'
	 * @return	void
	 */
	setLineStyle : function (style) {
		this.lineStyle = style;
		switch (style) {
			case 'none' :
				this.imgLine0 = 'empty.gif'; this.imgLine1 = 'empty.gif'; this.imgLine2 = 'empty.gif'; 
				this.imgLine3 = 'empty.gif'; this.imgLine4 = 'empty.gif'; this.imgLine5 = 'empty.gif';
				this.imgWait = 'wait.gif'; this.imgEmpty = 'empty.gif';
				this.imgMinus1 = 'minus0.gif'; this.imgMinus2 = 'minus0.gif'; this.imgMinus3 = 'minus0.gif';
				this.imgMinus4 = 'minus0.gif'; this.imgMinus5 = 'minus0.gif';
				this.imgPlus1 = 'plus0.gif'; this.imgPlus2 = 'plus0.gif'; this.imgPlus3 = 'plus0.gif';
				this.imgPlus4 = 'plus0.gif'; this.imgPlus5 = 'plus0.gif';
				this.imgCheck1 = 'check1.gif'; this.imgCheck2 = 'check2.gif'; this.imgCheck3 = 'check3.gif';
				// Les 2 premiers sont des noms d'images, les 2 autres de classes CSS
				this.imgMulti1 = 'empty.gif'; this.imgMulti2 = 'empty.gif';
				this.imgMulti3 = ''; this.imgMulti4 = '';
				break;
			case 'full' :
				if (this.isRTL()) {
				this.imgLine0 = 'rtl_linefull0.gif'; this.imgLine1 = 'rtl_linefull1.gif'; this.imgLine2 = 'rtl_linefull2.gif';
				this.imgLine3 = 'rtl_linefull3.gif'; this.imgLine4 = 'rtl_linefull4.gif'; this.imgLine5 = 'rtl_linefull5.gif';
				this.imgWait = 'wait.gif'; this.imgEmpty = 'empty.gif';
				this.imgMinus1 = 'rtl_minusfull1.gif'; this.imgMinus2 = 'rtl_minusfull2.gif'; this.imgMinus3 = 'rtl_minusfull3.gif';
				this.imgMinus4 = 'rtl_minusfull4.gif'; this.imgMinus5 = 'rtl_minusfull5.gif';
				this.imgPlus1 = 'rtl_plusfull1.gif'; this.imgPlus2 = 'rtl_plusfull2.gif'; this.imgPlus3 = 'rtl_plusfull3.gif';
				this.imgPlus4 = 'rtl_plusfull4.gif'; this.imgPlus5 = 'rtl_plusfull5.gif';
				this.imgCheck1 = 'check1.gif'; this.imgCheck2 = 'check2.gif'; this.imgCheck3 = 'check3.gif';
				// Les 2 premiers sont des noms d'images, les 2 autres de classes CSS
				this.imgMulti1 = 'rtl_linebgfull.gif'; this.imgMulti2 = 'rtl_linebgfull2.gif';
				this.imgMulti3 = 'multiline'; this.imgMulti4 = 'multiline2';
				} else {
				this.imgLine0 = 'linefull0.gif'; this.imgLine1 = 'linefull1.gif'; this.imgLine2 = 'linefull2.gif';
				this.imgLine3 = 'linefull3.gif'; this.imgLine4 = 'linefull4.gif'; this.imgLine5 = 'linefull5.gif';
				this.imgWait = 'wait.gif'; this.imgEmpty = 'empty.gif';
				this.imgMinus1 = 'minusfull1.gif'; this.imgMinus2 = 'minusfull2.gif'; this.imgMinus3 = 'minusfull3.gif';
				this.imgMinus4 = 'minusfull4.gif'; this.imgMinus5 = 'minusfull5.gif';
				this.imgPlus1 = 'plusfull1.gif'; this.imgPlus2 = 'plusfull2.gif'; this.imgPlus3 = 'plusfull3.gif';
				this.imgPlus4 = 'plusfull4.gif'; this.imgPlus5 = 'plusfull5.gif';
				this.imgCheck1 = 'check1.gif'; this.imgCheck2 = 'check2.gif'; this.imgCheck3 = 'check3.gif';
				// Les 2 premiers sont des noms d'images, les 2 autres de classes CSS
				this.imgMulti1 = 'linebgfull.gif'; this.imgMulti2 = 'linebgfull2.gif';
				this.imgMulti3 = 'multiline'; this.imgMulti4 = 'multiline2';
				}
				break;
			case 'line' :
			default :
				if (this.isRTL()) {
				this.imgLine0 = 'rtl_line0.gif'; this.imgLine1 = 'rtl_line1.gif'; this.imgLine2 = 'rtl_line2.gif';
				this.imgLine3 = 'rtl_line3.gif'; this.imgLine4 = 'rtl_line4.gif'; this.imgLine5 = 'rtl_line5.gif';
				this.imgWait = 'wait.gif'; this.imgEmpty = 'empty.gif';
				this.imgMinus1 = 'rtl_minus1.gif'; this.imgMinus2 = 'rtl_minus2.gif'; this.imgMinus3 = 'rtl_minus3.gif';
				this.imgMinus4 = 'rtl_minus4.gif'; this.imgMinus5 = 'rtl_minus5.gif';
				this.imgPlus1 = 'rtl_plus1.gif'; this.imgPlus2 = 'rtl_plus2.gif'; this.imgPlus3 = 'rtl_plus3.gif';
				this.imgPlus4 = 'rtl_plus4.gif'; this.imgPlus5 = 'rtl_plus5.gif';
				this.imgCheck1 = 'check1.gif'; this.imgCheck2 = 'check2.gif'; this.imgCheck3 = 'check3.gif';
				// Les 2 premiers sont des noms d'images, les 2 autres de classes CSS
				this.imgMulti1 = 'rtl_linebg.gif'; this.imgMulti2 = 'rtl_linebg2.gif';
				this.imgMulti3 = 'multiline'; this.imgMulti4 = 'multiline2';
				} else {
				this.imgLine0 = 'line0.gif'; this.imgLine1 = 'line1.gif'; this.imgLine2 = 'line2.gif';
				this.imgLine3 = 'line3.gif'; this.imgLine4 = 'line4.gif'; this.imgLine5 = 'line5.gif';
				this.imgWait = 'wait.gif'; this.imgEmpty = 'empty.gif';
				this.imgMinus1 = 'minus1.gif'; this.imgMinus2 = 'minus2.gif'; this.imgMinus3 = 'minus3.gif';
				this.imgMinus4 = 'minus4.gif'; this.imgMinus5 = 'minus5.gif';
				this.imgPlus1 = 'plus1.gif'; this.imgPlus2 = 'plus2.gif'; this.imgPlus3 = 'plus3.gif';
				this.imgPlus4 = 'plus4.gif'; this.imgPlus5 = 'plus5.gif';
				this.imgCheck1 = 'check1.gif'; this.imgCheck2 = 'check2.gif'; this.imgCheck3 = 'check3.gif';
				// Les 2 premiers sont des noms d'images, les 2 autres de classes CSS
				this.imgMulti1 = 'linebg.gif'; this.imgMulti2 = 'linebg2.gif';
				this.imgMulti3 = 'multiline'; this.imgMulti4 = 'multiline2';
				}
		}
	},
	
	setTooltipDuration : function (show, hide) {
		this.durationTooltipShow = show;
		this.durationTooltipHide = hide;
	},

	/**
	 * Méthode qui permet d'interdir les mouvements dans la branche dragguée
	 *
	 * @access	public
	 * @param	boolean				propagateRestiction		True pour interdir le mouvement des enfants de la branche droppée
	 * @return	void
	 */
	propagateRestriction : function (propagate) {
		this.propagation = (typeof(propagate) == 'undefined') ? true : propagate;
	},
	
	getSelectedBranches : function () {
		return this.selectedBranches;
	},

	setContextMenu : function (menu) {
		var div = document.createElement('div');
		div.innerHTML = menu;
		this.div.appendChild(div);
	},


	/**
	 *------------------------------------------------------------------------------
	 *							TafelTree public methods
	 *------------------------------------------------------------------------------
	 */
	
	/**
	 * Fonction pour générer l'arbre
	 *
	 * @access	public
	 * @param	boolean				bigTree					True pour spécifier que c'est un gros arbre
	 * @return	void
	 */
	generate : function (bigTree) {
		if (!bigTree) {
			var isNotFirst = false;
			var isNotLast = false;
			for (var i = 0; i < this.baseStruct.length; i++) {
				isNotFirst = (i > 0) ? true : false;
				isNotLast = (i < this.baseStruct.length - 1) ? true : false;
				this.roots[i] = new TafelTreeRoot(this, this.baseStruct[i], 0, isNotFirst, isNotLast, i);
				this.div.appendChild(this.roots[i].obj);
			}
			this.loadComplete();
		} else {
			this.bigTreeLoading = 0;
			setTimeout(this._checkLoad.bind(this), 100);
			setTimeout(this._generateBigTree.bind(this), 10);
		}
	},
	
	/**
	 * Lance les fonctions générales de l'arbre
	 *
	 * @access	public
	 * @param	object				options					Les fonctions et autres ouvertures automatiques*
	 * @return	void
	 */
	setOptions : function (options) {
		// Fonctions événementielles
		if (options.onLoad) this.setOnLoad(options.onLoad);
		if (options.onDebug) this.setOnDebug(options.onDebug);
		if (options.onCheck) this.setOnCheck(options.onCheck);
		if (options.onBeforeCheck) this.setOnBeforeCheck(options.onBeforeCheck);
		if (options.onClick) this.setOnClick(options.onClick);
		if (options.onMouseDown) this.setOnMouseDown(options.onMouseDown);
		if (options.onMouseUp) this.setOnMouseUp(options.onMouseUp);
		if (options.onDblClick) this.setOnDblClick(options.onDblClick);
		if (options.onBeforeOpen) this.setOnBeforeOpen(options.onBeforeOpen);
		if (options.onOpen) this.setOnOpen(options.onOpen);
		if (options.onMouseOver) this.setOnMouseOver(options.onMouseOver);
		if (options.onMouseOut) this.setOnMouseOut(options.onMouseOut);
		if (options.onDrop) this.setOnDrop(options.onDrop);
		if (options.onDragStartEffect) this.setOnDragStartEffect(options.onDragStartEffect);
		if (options.onDragEndEffect) this.setOnDragEndEffect(options.onDragEndEffect); 
		if (options.onErrorAjax) this.setOnDropAfter(options.onErrorAjax);
		if (options.onEdit) this.setOnEdit(options.onEdit);
		if (options.onEditAjax) this.setOnEditAjax(options.onEditAjax[0], options.onEditAjax[1]);
		if (options.onDropAjax) this.setOnDropAjax(options.onDropAjax[0], options.onDropAjax[1]);
		if (options.onOpenPopulate) this.setOnOpenPopulate(options.onOpenPopulate[0], options.onOpenPopulate[1]);
		// Fonctions avancées
		if (typeof(options.rtlMode) != 'undefined') this.enableRTL(options.rtlMode);
		if (typeof(options.dropALT) != 'undefined') this.disableDropALT(options.dropALT);
		if (typeof(options.dropCTRL) != 'undefined') this.disableDropCTRL(options.dropCTRL);
		if (typeof(options.multiline) != 'undefined') this.enableMultiline(options.multiline);
		if (typeof(options.checkboxes) != 'undefined') this.enableCheckboxes(options.checkboxes);
		if (typeof(options.checkboxesThreeState) != 'undefined') this.enableCheckboxesThreeState(options.checkboxesThreeState);
		if (typeof(options.cookies) != 'undefined') this.enableCookies(options.cookies);
		if (typeof(options.openOneAtOnce) != 'undefined') this.openOneAtOnce(options.openOneAtOnce);
		if (typeof(options.openAtLoad) != 'undefined') this.openAtLoad(options.openAtLoad);
		if (typeof(options.openAfterAdd) != 'undefined') this.openAfterAdd(options.openAfterAdd);
		if (typeof(options.showSelectedBranch) != 'undefined') this.showSelectedBranch(options.showSelectedBranch);
		if (typeof(options.reopenFromServer) != 'undefined') this.reopenFromServerAfterLoad(options.reopenFromServer);
		if (typeof(options.propagateRestriction) != 'undefined') this.propagateRestriction(options.propagateRestriction);
		if (options.lineStyle) this.setLineStyle(options.lineStyle);
		if (options.behaviourDrop) this.setBehaviourDrop(options.behaviourDrop);
		if (options.contextMenu) this.setContextMenu(options.contextMenu);
		// Fonctions diverses
		if (options.bind) {
			for (var i = 0; i < options.bind.length; i++) {
				this.bind(options.bind[i]);
			}
		}
		if (options.bindAsUnidirectional) {
			for (var i = 0; i < options.bindAsUnidirectional.length; i++) {
				this.bind(options.bindAsUnidirectional[i]);
			}
		}
		if (options.defaultImg) {
			var imgopen = (options.defaultImgOpen) ? options.defaultImgOpen : options.defaultImg;
			var imgclose = (options.defaultImgClose) ? options.defaultImgClose : options.defaultImg;
			this.setIcons(options.defaultImg, imgopen, imgclose);
		}
		if (options.defaultImgSelected || options.defaultImgOpenSelected || options.defaultImgCloseSelected) {
			var img = (options.defaultImgSelected) ? options.defaultImgSelected : null;
			var imgopen = (options.defaultImgOpenSelected) ? options.defaultImgOpenSelected : null;
			var imgclose = (options.defaultImgCloseSelected) ? options.defaultImgCloseSelected :null;
			this.setIconsSelected(img, imgopen, imgclose);
		}
        this.serverCookie = (options.serverCookie) ? options.serverCookie : false;
	},

	loadComplete : function () {
		this._adjustOpening();
		this._adjustCheck();
		this.setCookie(this.classTree + this.id);
        this.loaded = true;
		if (typeof(this.onLoad) == 'function') {
			this.onLoad();
		}
	},

	loadRunning : function (loaded) {
		if (typeof(this.onLoading) == 'function') {
			this.onLoading(loaded);
		}
	},

	replace : function (modelBranch, replacedBranch, copy) {
		var branch1 = this.getBranchById(modelBranch);
		if (!branch1) return false;
		return branch1.replace(replacedBranch, copy);
	},

	switchBranches : function (branch1, branch2) {
		var branch1 = this.getBranchById(branch1);
		if (!branch1) return false;
		branch1.switchWith(branch2);
	},

	/**
	 * Restaure les valeurs par défaut d'ouverture et check (selon type)
	 *
	 * Le type peut prendre la valeur "open", "check" ou "all"
	 *
	 * @access	public
	 * @param	string			type					Le type de default (open, check ou all)
	 * @return	void
	 */
	restoreDefault : function (type) {
		var s = this.defaultStruct;
		this._restaureDefault(s, type);
	},

	_restaureDefault : function (s, type) {
		var b = null;
		var open = false;
		var check = 0;
		for (var i = 0; i < s.length; i++) {
			b = this.getBranchById(s[i].id);
			if (b) {
				open = (s[i].open) ? true : this.openAll;
				check = (s[i].check == 1) ? 1 : 0;
				switch (type) {
					case 'open' :
						if (b.hasChildren()) {
							b.openIt(open);
						}
						break;
					case 'check' :
						b.check(check);
						b._adjustParentCheck();
						break;
					case 'all' :
					default :
						if (b.hasChildren()) {
							b.openIt(open);
						}
						//alert(b.getText() + ' : ' + check);
						b.check(check);
						b._adjustParentCheck();
				}
				// S'il y a des enfants, on va les restaurer aussi
				if (typeof(s[i].items) != 'undefined') {
					this._restaureDefault(s[i].items, type);
				}
			}
		}
	},
	
	/**
	 * Fonction qui lie des arbres TafelTree entre eux, bidirecitonnellement
	 *
	 * On lui passe autant de TafelTree voulu, en les séparant par des virgules
	 *
	 * @access	public
	 * @param	TafelTree			argument				Un ou plusieurs TafelTree
	 * @return	void
	 */
	bind : function () {
		var trees = this.bind.arguments;
		for (var i = 0; i < trees.length; i++) {
			if (!this.isBindedWith(trees[i])) {
				this.otherTrees.push(trees[i]);
				if (!trees[i].isBindedWith(this)) {
					trees[i].bind(this);
				}
			}
		}
	},
	
	/**
	 * Fonction qui lie des arbres TafelTree entre eux, mais unnidirecitonnel
	 *
	 * On lui passe autant de TafelTree voulu, en les séparant par des virgules
	 *
	 * @access	public
	 * @param	TafelTree			argument				Un ou plusieurs TafelTree
	 * @return	void
	 */
	bindAsUnidirectional : function () {
		var trees = this.bindAsUnidirectional.arguments;
		for (var i = 0; i < trees.length; i++) {
			if (!this.isBindedWith(trees[i])) {
				this.otherTrees.push(trees[i]);
			}
		}
	},

	isBindedWith : function (_tree) {
		var binded = false;
		for (var i = 0; i < this.otherTrees.length; i++) {
			if (this.otherTrees[i].id == _tree.id) {
				binded = true;
				break;
			}
		}
		return binded;
	},
	
	unselect : function () {
		var branch = null;
		for (var i = 0; i < this.selectedBranches.length; i++) {
			branch = this.selectedBranches[i];
			Element.removeClassName(branch.txt, this.classSelected);
			// On set l'icône s'il doit changer
			if (branch.getIconSelected() || branch.getOpenIconSelected() || branch.getCloseIconSelected()) {
				if (branch.hasChildren()) {
					branch.img.src = (branch.isOpened()) ? branch.tree.imgBase + branch.struct.imgopen : branch.tree.imgBase + branch.struct.imgclose;
				} else {
					branch.img.src = branch.tree.imgBase + branch.struct.img;
				}
			}
		}
		this.selectedBranches = [];
	},

	/**
	 * Retourne toutes les branches contenues entre deux d'entre-elles
	 *
	 * @access	public
	 * @param	TafelTreeBranch		branch1				La première borne
	 * @param	TafelTreeBranch		branch2				La deuxième borne
	 * @return	array									Un tableau de branche, ou false si rien n'a été trouvé
	 */
	getBranchesBetween : function (branch1, branch2) {
		var branch1 = this.getBranchById(branch1);
		var branch2 = this.getBranchById(branch2);
		if (!branch1 || !branch2) return false;
		// On quitte si ce n'est pas le même arbre
		if (branch1.tree.id != branch2.tree.id) return false;
		var found = false;
		var selected = [];
		var pos1 = branch1.getWithinOffset();
		var pos2 = branch2.getWithinOffset();
		var next = (pos1[1] <= pos2[1]) ? true : false;
		// On va chercher l'autre branche plus bas
		branch = (next) ? branch1.getNextBranch() : branch1.getPreviousBranch();
		while (branch) {
			selected.push(branch);
			if (branch.getId() == branch2.getId()) {
				found = true;
				break;
			}
			branch = (next) ? branch.getNextBranch() : branch.getPreviousBranch();
		}
		return (found) ? selected : false;
	},

	/**
	 * Retourne le nombre de branche comprises dans l'arbre
	 *
	 * @access	public
	 * @return	integer										Le nombre de branches
	 */
	countBranches : function () {
		var nb = this.roots.length;
		for (var i = 0; i < this.roots.length; i++) {
			nb += this.roots[i].countBranches();
		}
		return nb;
	},
	
	/**
	 * Retourne toutes les branches de l'arbre
	 *
	 * @access	public
	 * @param	function		filter						Le filtre des branches
	 * @return	array										Un tableau des branches de l'arbre
	 */
	getBranches : function (filter) {
		var branches = [];
		for (var i = 0; i < this.roots.length; i++) {
			if (typeof(filter) == 'function') {
				if (filter(this.roots[i])) {
					branches.push(this.roots[i]);
				}
			} else {
				branches.push(this.roots[i]);
			}
			branches = this.roots[i].getBranches(filter, branches);
		}
		return branches;
	},
	
	/**
	 * Récupère toutes les branches ouvertes
	 *
	 * @access	public
	 * @return	void
	 */
	getOpenedBranches : function () {
		var openedBranches = [];
		for (var i = 0; i < this.roots.length; i++) {
			if (this.roots[i].isOpened() && this.roots[i].hasChildren()) {
				openedBranches.push(this.roots[i]);
			}
			openedBranches = this.roots[i].getOpenedBranches(openedBranches);
		}
		return openedBranches;
	},
	
	/**
	 * Récupère toutes les branches checkées
	 *
	 * @access	public
	 * @return	void
	 */
	getCheckedBranches : function () {
		var checkedBranches = [];
		for (var i = 0; i < this.roots.length; i++) {
			if (this.roots[i].isChecked() == 1) {
				checkedBranches.push(this.roots[i]);
			}
			checkedBranches = this.roots[i].getCheckedBranches(checkedBranches);
		}
		return checkedBranches;
	},
	
	/**
	 * Récupère toutes les branches non checkées
	 *
	 * @access	public
	 * @return	void
	 */
	getUnCheckedBranches : function () {
		var uncheckedBranches = [];
		for (var i = 0; i < this.roots.length; i++) {
			if (this.roots[i].isChecked() == 0) {
				uncheckedBranches.push(this.roots[i]);
			}
			uncheckedBranches = this.roots[i].getUnCheckedBranches(uncheckedBranches);
		}
		return uncheckedBranches;
	},
	
	/**
	 * Récupère toutes les branches partiellement checkées
	 *
	 * @access	public
	 * @return	void
	 */
	getPartCheckedBranches : function () {
		var uncheckedBranches = [];
		for (var i = 0; i < this.roots.length; i++) {
			if (this.roots[i].isChecked() == -1) {
				uncheckedBranches.push(this.roots[i]);
			}
			uncheckedBranches = this.roots[i].getPartCheckedBranches(uncheckedBranches);
		}
		return uncheckedBranches;
	},

	getParentBranches : function () {
		var parents = [];
		for (var i = 0; i < this.roots.length; i++) {
			if (this.roots[i].hasChildren()) {
				parents.push(this.roots[i]);
			}
			parents = this.roots[i].getParentBranches(parents);
		}
		return parents;
	},

	getLeafBranches : function () {
		var leafs = [];
		for (var i = 0; i < this.roots.length; i++) {
			if (!this.roots[i].hasChildren()) {
				leafs.push(this.roots[i]);
			}
			leafs = this.roots[i].getLeafBranches(leafs);
		}
		return leafs;
	},

	/**
	 * Fonction qui ouvre tout l'arbre d'un coup
	 *
	 * @access	public
	 * @return	void
	 */
	expend : function () {
		for (var i = 0; i < this.roots.length; i++) {
			this.roots[i].expend();
		}
	},

	/**
	 * Fonction qui ferme tout l'arbre d'un coup
	 *
	 * @access	public
	 * @return	void
	 */
	collapse : function () {
		for (var i = 0; i < this.roots.length; i++) {
			this.roots[i].collapse();
		}
	},

	/**
	 * Méthode pour récupérer une branche en fonction de son id
	 *
	 * @access	public
	 * @param	string				position				L'id de la branche soeur ou parente, ou l'objet
	 * @param	object				item					La nouvelle branche
	 * @param	boolean				sibling					True pour 'sibling', false pour 'child'
	 * @param	boolean				isFirst					True pour l'insérer, soit comme 1er enfant, soit avant la soeur
	 * @return	void
	 */
	insertBranch : function (position, item, sibling, isFirst) {
		var position = this.getBranchById(position);
		if (!position) return false;
		if (!sibling) {
			if (!isFirst) {
				position.insertIntoLast(item);
			} else {
				position.insertIntoFirst(item);
			}
		} else {
			if (!isFirst) {
				position.insertAfter(item);
			} else {
				position.insertBefore(item);
			}
		}
	},

	moveBranch : function (position, item, sibling, isFirst) {
		var position = this.getBranchById(position);
		if (!position) return false;
		if (!sibling) {
			if (!isFirst) {
				position.moveIntoLast(item);
			} else {
				position.moveIntoFirst(item);
			}
		} else {
			if (!isFirst) {
				position.moveAfter(item);
			} else {
				position.moveBefore(item);
			}
		}
	},

	/**
	 * Fonction qui efface la branche
	 *
	 * @access	public
	 * @return	void
	 */
	removeBranch : function (branch) {
		try {
			var branch = this.getBranchById(branch);
			if (!branch) return false;
			// On enlève le drag&drop
			if (branch.objDrag) {
				branch.removeDragDrop();
			}
			if (!branch.isRoot) {
				// On supprime le noeud HTML
				branch.parent.obj.removeChild(branch.obj);
				// On l'enlève de la structure Javacript
				branch.parent.children.splice(branch.pos, 1);
				branch.parent.struct.items.splice(branch.pos, 1);
				if (branch.parent.children.length == 0) {
					branch.parent.setOpenableIcon(false);
					if (branch.tree.multiline) {
						branch._manageMultiline(branch.parent.tdImg, 2, false);
					}
				}
				// On repositionne la structure
				branch.parent._manageLine();
			} else {
				// On supprime le noeud HTML
				this.div.removeChild(branch.obj);
				// On l'enlève de la structure Javacript
				this.roots.splice(branch.pos, 1);
				if (this.roots[branch.pos-1]) {
					this.roots[branch.pos-1]._manageAfterRootInsert();
				}
			}
			branch = null;
		} catch (err) {
			throw new Error ('remove(base) : ' + err.message);
		}
	},

	/**
	 * Méthode pour récupérer une branche en fonction de son id généré
	 *
	 * @access	public
	 * @param	string				id						L'id généré de la branche
	 * @return	TafelBranch									La branche sélectionnée
	 */
	getBranchByIdObj : function (id) {
		try {
			var obj = null;
			for (var r = 0; r < this.roots.length; r++) {
				obj = this._getBranchByIdObj(id, this.roots[r]);
				if (obj) {
					break;
				}
			}
			return obj;
		} catch (err) {
			throw new Error ('getBranchByIdObj(func) : ' + err.message);
		}
	},

	/**
	 * Méthode pour récupérer une branche en fonction de son id utilisateur
	 *
	 * @access	public
	 * @param	string				id						L'id utilisateur de la branche
	 * @return	TafelBranch									La branche sélectionnée
	 */
	getBranchById : function (id) {
		try {
			if (typeof(id) == 'object') return id;
			var obj = null;
			for (var r = 0; r < this.roots.length; r++) {
				obj = this._getBranchById(id, this.roots[r]);
				if (obj) break;
			}
			if (!obj) {
				// On magouille avec les roots pour ne pas passer
				// dans une boucle infinie (à cause du getBranchById)
				var ro = null;
				for (var i = 0; i < this.otherTrees.length; i++) {
					ro = this.otherTrees[i].roots;
					for (var r = 0; r < ro.length; r++) {
						obj = this.otherTrees[i]._getBranchById(id, ro[r]);
						if (obj) break;
					}
					if (obj) break;
				}
			}
			return obj;
		} catch (err) {
			throw new Error ('getBranchById(func) : ' + err.message);
		}
	},

	/**
	 * Méthode de gestion de debug
	 *
	 * @access	public
	 * @param	string				str						Une string à afficher (optionnel)
	 * @return	void
	 */
	debug : function (str) {
		try {
			this.debugObj.style.display = 'block';
			if (typeof(this.onDebug) == 'function') {
				this.onDebug(this, this.debugObj, (str) ? str : '');
			} else {
				this.debugObj.innerHTML += str;
			}
		} catch (err) {
			throw new Error ('debug(func) : ' + err.message);
		}
	},

	/**
	 * Fonction pour afficher l'ojbet de manière cool
	 *
	 * @access	public
	 * @return	string										La string de l'objet
	 */
	toString : function () {
		var obj = {
			'id' : this.id,
			'width' : this.div.offsetWidth,
			'height' : this.div.offsetHeight,
			'imgPath' : this.imgBase,
			'roots' : this.roots.length
		};
		var str = 'TafelTree {';
		for (var i in obj) {
			str += TafelTree.debugReturn + TafelTree.debugTab + i + ' : ' + obj[i];
		}
		str += TafelTree.debugReturn + '}';
		return str;
	},

	/**
	 * Fonction qui sérialise l'arbre pour en faire une string JSON
	 *
	 * @access	public
	 * @return	string										La string de l'objet
	 */	
	serialize : function (debug) {
		var rt = (debug) ? TafelTree.debugReturn : '';
		var str = (debug) ? 'TafelTree (' + this.id + ') [' : '[';
		for (var i = 0; i < this.roots.length; i++) {
			str += this.roots[i].serialize(debug, true);
			if (i < this.roots.length - 1) {
				str += ',';
			}
		}
		str += rt + ((debug) ? '];' : ']');
		if (debug) {
			return str;
		} else {
			return encodeURIComponent(str);
		}
	},
	
	/**
	 * Fonction qui renvoie les paramètres de l'URL
	 *
	 * ils sont renvoyés sous cette forme :
			params[0] = {
				'name' : 'paramName',
				'value': 'paramValue'
			}
	 *
	 * @access	public
	 * @param	string				url						L'url à decomposer
	 * @return	array										Le tableau de paramètres
	 */
	getURLParams : function (url) {
		var params = [];
		if (url.indexOf('?') > -1) {
			var a1 = url.split('?');
			var a2 = a1[1].split('&');
			var a3 = '';
			for (var i = 0; i < a2.length; i++) {
				a3 = a2[i].split('=');
				if (a3.length == 2) {
					params.push({
						'name' : a3[0],
						'value': a3[1]
					})
				}
			}
		}
		return params;
	},


	/**
	 *------------------------------------------------------------------------------
	 *							TafelTree private methods
	 *------------------------------------------------------------------------------
	 */

	_generateBigTree : function () {
		var i = this.bigTreeLoading;
		var isNotFirst = false;
		var isNotLast = false;
		if (i < this.baseStruct.length) {
			isNotFirst = (i > 0) ? true : false;
			isNotLast = (i < this.baseStruct.length - 1) ? true : false;
			this.roots[i] = new TafelTreeRoot(this, this.baseStruct[i], 0, isNotFirst, isNotLast, i);
			this.div.appendChild(this.roots[i].obj);
			this.loadRunning(this.roots[i]);
			this.bigTreeLoading++;
			setTimeout(this._generateBigTree.bind(this), 10);
		} else {
			this.loaded = true;
		}
	},

	_checkLoad : function () {
		var complete = true;
		if (this.loaded) {
			for (var i = 0; i < this.roots.length; i++) {
				if (!this.roots[i].loaded || !this._checkLoadChildren(this.roots[i])) {
					complete = false;
					break;
				}
			}
		} else {
			complete = false;
		}
		if (!complete){
			setTimeout(this._checkLoad.bind(this), 100);
		} else {
			this.loadComplete();
		}
	},
	
	_checkLoadChildren : function (branch) {
		var complete = true;
		if (branch.loaded) {
			for (var i = 0; i < branch.children.length; i++) {
				if (!branch.children[i].loaded || !this._checkLoadChildren(branch.children[i])) {
					complete = false;
					break;
				}
			}
		} else {
			complete = false;
		}
		return complete;
	},
	
	_adjustOpening : function () {
		// Si on utilise les cookies, on s'en sert pour ouvrir ou fermer les branches
		if (this.useCookie && this.cookieOpened) {
			var branch = null;
			for (var i = 0; i < this.cookieOpened.length; i++) {
				branch = this.getBranchById(this.cookieOpened[i]);
				if (typeof(branch) == 'object' && branch.hasChildren()) {
					if (branch.children.length > 0) {
						// Cette branche est une branche normale
						branch.openIt(true);
					} else {
						// Cette branche est une branche qui a ses enfants sur le serveur
						// On va donc les récupérer
						if (typeof(branch.struct.onopenpopulate) == 'function' && branch.eventable) {
							branch._openPopulate();
							branch.openIt(true);
						}
					}
				}
			}
		}
	},

	_adjustCheck : function () {
		// On ajuste les checks d'après les cookies
		var branch = null;
		if (this.checkboxes && this.useCookie && this.cookieChecked) {
			for (var i = 0; i < this.cookieChecked.length; i++) {
				branch = this.getBranchById(this.cookieChecked[i]);
				if (typeof(branch) == 'object') {
					branch.check(1);
				}
			}
		}
		// Si on a des checkboxes, on corrige les images en fonction des checks
		if (this.checkboxes && this.checkboxesThreeState) {
			var checked = this.getCheckedBranches();
			for (var i = 0; i < checked.length; i++) {
				checked[i]._adjustParentCheck();
			}
		}
	},

	/**
	 * Méthode récursive pour récupérer une branche en fonction de son id généré
	 *
	 * @access	private
	 * @param	string				id						L'id généré de la branche recherchée
	 * @param	TafelTreeBranch		obj						La branche courante
	 * @return	TafelBranch									La branche sélectionnée
	 */
	_getBranchByIdObj : function (id, obj) {
		try {
			var ob = '';
			if (obj.idObj == id) {
				return obj;
			}
			if (typeof(obj.children) == 'object') {
				for (var c = 0; c < obj.children.length; c++) {
					ob = this._getBranchByIdObj(id, obj.children[c]);
					if (ob) {
						return ob;
					}
				}
			}
			return ob;
		} catch (err) {
			throw new Error ('_getBranchByIdObj(func) : ' + err.message);
		}
	},

	/**
	 * Méthode récursive pour récupérer une branche en fonction de son id utilisateur
	 *
	 * @access	private
	 * @param	string				id						L'id utilisateur de la branche recherchée
	 * @param	TafelTreeBranch		obj						La branche courante
	 * @return	TafelBranch									La branche sélectionnée
	 */
	_getBranchById : function (id, obj) {
		try {
			var ob = '';
			if (obj.getId() == id) {
				return obj;
			}
			if (typeof(obj.children) == 'object') {
				for (var c = 0; c < obj.children.length; c++) {
					ob = this._getBranchById(id, obj.children[c]);
					if (ob) {
						return ob;
					}
				}
			}
			return ob;
		} catch (err) {
			throw new Error ('_getBranchById(func) : ' + err.message);
		}
	},

	/**
	 * Fonction qui change la structure de la branche
	 *
	 * @access	private
	 * @param	TafelTreeBranch		branch					La nouvelle structure
	 * @return	void
	 */
	_changeStruct : function (branch) {
		try {
			while (typeof(branch.parent) != 'undefined') {
				branch.parent.struct.items.splice(branch.pos, 1, branch.struct);
				if (typeof(branch.parent) != 'undefined') {
					branch = branch.parent;
				}
			}
		} catch (err) {
			throw new Error ('_changeStruct(func) : ' + err.message);
		}
	},

	/**
	 * Méthode pour ajouter l'élément principal
	 *
	 * @access	private
	 * @return	HTMLDivElement								L'élément DIV créé
	 */
	_addTree : function () {
		var div = document.createElement('div');
		div.className = this.classTree;
		return div;
	},


	/**
	 *------------------------------------------------------------------------------
	 *							TafelTree Cookies Management
	 *------------------------------------------------------------------------------
	 */
	
	/**
	 * Méthode qui sauve le contenu dans le cookie. Propre à l'application
	 *
	 * @access	public
	 * @param	string				name					Nom du cookie
	 * @return	void
	 */
	setCookie : function (name) {
		try {
			var str = 'cookieactivate' + this.cookieSeparator;
			// Les branches ouvertes
			var arr = this.getOpenedBranches();
			for (var i = 0; i < arr.length; i++) {
				str = str + arr[i].getId() + this.cookieSeparator;
			}
			// Les branches checkées
			str += this.cookieCheckSeparator;
			var arr = this.getCheckedBranches();
			for (var i = 0; i < arr.length; i++) {
				str = str + arr[i].getId() + this.cookieSeparator;
			}
            if (!this.serverCookie) {
                this._saveCookie(name, str, '', '/', '', '');
            } else {
                // send cookie server only if tree is loaded (optimization)
                if (this.loaded) {
                    new Ajax.Request(this.serverCookie, {
                        'method' : 'post',
                        'parameters' : 'type=set&cookieString=' + str,
                        'onComplete' : this._cookieSend.bind(this),
                        'onFailure' : this._cookieFailure.bind(this)
                    });
                }
            }
		} catch (err) {
			throw new Error ('setCookie(func) : ' + err.message);
		}
	},
	
	/**
	 * Méthode qui récupère le contenu d'un cookie en fonction du nom
	 *
	 * @access	public
	 * @param	string				name					Nom du cookie
	 * @return	string										Le contenu du cookie
	 */
	getCookie : function (name) {
		try {
            if (!this.serverCookie) {
    			if (name != ''){
    				var start = document.cookie.indexOf(name + '=');
    				var len = start + name.length + 1;
    				if ((!start) && (name != document.cookie.substring(0, name.length))){
    					return null;
    				}
    				if ( start == -1 ) return null;
    				var end = document.cookie.indexOf(';', len);
    				if (end == -1){
    					end = document.cookie.length;
    				}
    				return unescape(document.cookie.substring(len, end));
    			}
            } else {
                new Ajax.Request(this.serverCookie, {
                    'method' : 'post',
                    'parameters' : 'type=get',
                    'onComplete' : this._getCookieComplete.bind(this),
                    'onFailure' : this._cookieFailure.bind(this)
                });
            }
            return null;
		} catch (err) {
			throw new Error ('getCookie(func) : ' + err.message);
		}
	},
    
    _cookieSend : function (response) {
        alert('ok');
    },
    
    _getCookieComplete : function (response) {
        var fromCookie = response.responseText;
        if (fromCookie) {
            var branches = fromCookie.split(this.cookieCheckSeparator);
            // Branches ouvertes
            this.cookieOpened = [];
            this.cookieOpened = branches[0].split(this.cookieSeparator);
            this.cookieOpened.shift();
            // Branches checkées (avec anti-bug pour les anciennes versions et anciens cookies)
            this.cookieChecked = [];
            if (branches.length > 1) {
                this.cookieChecked = branches[1].split(this.cookieSeparator);
            }
        }    
        if (this.options.generate) {
			this.generate();
		}
		if (this.options.generateBigTree) {
			this.generate(true);
		}
    },
    
    _cookieFailure : function (response) {
        // do nothing
    },
	
	/**
	 * Méthode qui supprime un cookie. Seul le nom est obligatoire
	 *
	 * @access	public
	 * @param	string				name					Nom du cookie
	 * @param	string				path					Le chemin
	 * @param	string				domain					Le domaine
	 * @return	void
	 */
	deleteCookie : function (name, path, domain) {
		try {
			if (get_cookie(name)) document.cookie = name + '=' +
			( ( path ) ? ';path=' + path : "") +
			( ( domain ) ? ';domain=' + domain : '') +
			';expires=Thu, 01-Jan-1970 00:00:01 GMT';
		} catch (err) {
			throw new Error ('deleteCookie(func) : ' + err.message);
		}
	},
	
	/**
	 * Méthode qui sauve le contenu dans le cookie. Propre à toute application
	 *
	 * @access	private
	 * @param	string				name					Nom du cookie
	 * @param	string				value					La valeur à enregistrer
	 * @param	integer				expires					La durée de vie du cookie, en jour
	 * @param	string				path					Le chemin
	 * @param	string				domain					Le domaine
	 * @param	string				secure					?
	 * @return	void
	 */
	_saveCookie : function (name, value, expires, path, domain, secure) {
		try {
			// set time, it's in milliseconds
			var today = new Date();
			today.setTime(today.getTime());
			if (expires){
				expires = expires * 1000 * 60 * 60 * 24;
			}
			var expires_date = new Date(today.getTime() + (expires));
			
			document.cookie = name + '=' +escape(value) +
			( ( expires ) ? ';expires=' + expires_date.toGMTString() : '') + 
			( ( path ) ? ';path=' + path : '') + 
			( ( domain ) ? ';domain=' + domain : '') +
			( ( secure ) ? ';secure' : '');
		} catch (err) {
			throw new Error ('_saveCookie(func) : ' + err.message);
		}
	},


	/**
	 *------------------------------------------------------------------------------
	 *							TafelTree Events Management
	 *------------------------------------------------------------------------------
	 */

	/**
	 * Méthode qui appelle la méthode utilisateur après le load de l'arbre
	 *
	 * @access	public
	 * @param	function			func					La fonction utilisateur
	 * @return	void
	 */
	setOnLoad : function (func) {
		this.onLoad = eval(func);
	},

	/**
	 * Méthode qui appelle la méthode utilisateur pendant le load de l'arbre
	 *
	 * @access	public
	 * @param	function			func					La fonction utilisateur
	 * @return	void
	 */
	setOnLoading : function (func) {
		this.onLoading = eval(func);
	},

	/**
	 * Méthode qui appelle la méthode utilisateur après l'ouverture ou fermeture d'un noeud
	 *
	 * @access	public
	 * @param	function			func					La fonction utilisateur
	 * @return	void
	 */
	setOnOpen : function (func) {
		this.onOpen = eval(func);
	},

	/**
	 * Méthode qui appelle la méthode utilisateur avant l'ouverture ou fermeture d'un noeud
	 *
	 * @access	public
	 * @param	function			func					La fonction utilisateur
	 * @return	void
	 */
	setOnBeforeOpen : function (func) {
		this.onBeforeOpen = eval(func);
	},

	/**
	 * Méthode qui appelle la méthode utilisateur lorsque la souris est sur un noeud
	 *
	 * @access	public
	 * @param	function			func					La fonction utilisateur
	 * @return	void
	 */
	setOnMouseOver : function (func) {
		this.onMouseOver = eval(func);
	},

	/**
	 * Méthode qui appelle la méthode utilisateur lorsque la souris quitte le noeud
	 *
	 * @access	public
	 * @param	function			func					La fonction utilisateur
	 * @return	void
	 */
	setOnMouseOut : function (func) {
		this.onMouseOut = eval(func);
	},

	/**
	 * Méthode qui appelle la méthode utilisateur après un clic sur un noeud
	 *
	 * @access	public
	 * @param	function			func					La fonction utilisateur
	 * @return	void
	 */
	setOnClick : function (func) {
		this.onClick = eval(func);
	},

	/**
	 * Méthode qui appelle la méthode utilisateur après un mouse down
	 *
	 * @access	public
	 * @param	function			func					La fonction utilisateur
	 * @return	void
	 */
	setOnMouseDown : function (func) {
		this.onMouseDown = eval(func);
	},

	/**
	 * Méthode qui appelle la méthode utilisateur après un mouse up
	 *
	 * @access	public
	 * @param	function			func					La fonction utilisateur
	 * @return	void
	 */
	setOnMouseUp : function (func) {
		this.onMouseUp = eval(func);
	},

	/**
	 * Méthode qui appelle la méthode utilisateur lors d'un double-clic sur un noeud
	 *
	 * @access	public
	 * @param	function			func					La fonction utilisateur
	 * @return	void
	 */
	setOnDblClick : function (func) {
		this.onDblClick = eval(func);
	},

	/**
	 * Méthode qui appelle la méthode utilisateur lors de la fin de l'édition d'une branche
	 *
	 * @access	public
	 * @param	function			func					La fonction utilisateur
	 * @param	string				link					Le lien de la page ajax
	 * @return	void
	 */
	setOnEdit : function (func, link) {
		if (link) {
			this.onEditAjax = {
				'func' : eval(func),
				'link' : link
			};
		} else {
			this.onEdit = eval(func);
		}
		this.editableBranches = true;
	},

	/**
	 * Méthode qui appelle la méthode utilisateur lorsqu'on clique sur une checkbox, avant que celle-ci change de status
	 *
	 * @access	public
	 * @param	function			func					La fonction utilisateur
	 * @return	void
	 */
	setOnBeforeCheck : function (func) {
		this.onBeforeCheck = eval(func);
	},

	/**
	 * Méthode qui appelle la méthode utilisateur lorsqu'on clique sur une checkbox, après qu'elle ait changé de status
	 *
	 * @access	public
	 * @param	function			func					La fonction utilisateur
	 * @return	void
	 */
	setOnCheck : function (func) {
		this.onCheck = eval(func);
	},

	/**
	 * Méthode qui appelle la méthode utilisateur lors d'un drop sur un noeud
	 *
	 * @access	public
	 * @param	function			func					La fonction utilisateur
	 * @return	void
	 */
	setOnDrop : function (func) {
		this.onDrop = eval(func);
	},

	/**
	 * Méthode qui appelle la méthode utilisateur après un drop sur un noeud
	 *
	 * @access	public
	 * @param	function			func					La fonction utilisateur
	 * @return	void
	 */
	setOnDropAfter : function (func) {
		this.onErrorAjax = eval(func);
	},

	/**
	 * Méthode qui appelle la méthode utilisateur lors d'un drop sur un noeud
	 *
	 * @access	public
	 * @param	function			func					La fonction utilisateur
	 * @param	string				link					Le lien de la page ajax
	 * @param	boolean				propagateRestiction		True pour interdir le mouvement des enfants de la branche droppée
	 * @return	void
	 */
	setOnDropAjax : function (func, link) {
		this.onDropAjax = {
			'func' : eval(func),
			'link' : link
		};
	},
	
	/**
	 * Fonction appelée au retour de la requête Ajax après un open de la branche
	 *
	 * @access	public
	 * @param	function|boolean	func					La fonction utilisateur ou true
	 * @param	string				link					Le lien de la page ajax
	 * @return	void
	 */
	setOnOpenPopulate : function (func, link) {
		this.onOpenPopulate = {
			'func' : eval(func),
			'link' : link
		};
	},

	/**
	 * Méthode qui appelle la méthode utilisateur lors de la fin de l'édition d'une branche
	 *
	 * @access	public
	 * @param	function			func					La fonction utilisateur
	 * @param	string				link					Le lien de la page ajax
	 * @return	void
	 */
	setOnEditAjax : function (func, link) {
		this.onEditAjax = {
			'func' : eval(func),
			'link' : link
		};
		this.editableBranches = true;
	},
	
	setOnDragStartEffect : function (func) {
		this.onDragStartEffect = eval(func);
	},
	
	setOnDragEndEffect : function (func) {
		this.onDragEndEffect = eval(func);
	} 
};















































/**
 *------------------------------------------------------------------------------
 *							Abstract TafelTreeBaseBranch Class
 *------------------------------------------------------------------------------
 */

var TafelTreeBaseBranch = Class.create();

TafelTreeBaseBranch.prototype = {
	
	initialize : function () {},


	/**
	 *------------------------------------------------------------------------------
	 *							TafelTreeBaseBranch getters & setters
	 *------------------------------------------------------------------------------
	 */
	
	getId : function () {
		return this.struct.id;
	},
	
	getText : function () {
		return this.struct.txt;
	},
	
	getLevel : function () {
		return this.level;
	},
	
	getTree : function () {
		return this.tree;
	},

	getParent : function () {
		return (this.isRoot) ? null : this.parent;
	},

	/**
	 * Retourne la racine parente, null s'il n'y en a pas
	 *
	 * @access	public
	 * @return	TafelTreeRoot							La racine parente
	 */
	getAncestor : function () {
		return (this.isRoot) ? null : this.root;
	},

	/**
	 * Retourne tous les parents, racine comprise.
	 *
	 * Le 1er élément du tableau est le parent direct, le dernier étant
	 * la racine
	 *
	 * @access	public
	 * @return	array									Les parents
	 */
	getParents : function () {
		var parents = [];
		var branch = this;
		while (branch.parent) {
			parents.push(branch.parent);
			branch = branch.parent;
		}
		return parents;
	},
	
	getChildren : function () {
		return this.children;
	},
	
	getIcon : function () {
		return this.struct.img;
	},
	
	getOpenIcon : function () {
		return this.struct.imgopen;
	},
	
	getCloseIcon : function () {
		return this.struct.imgclose;
	},
	
	getIconSelected : function () {
		return this.struct.imgselected;
	},
	
	getOpenIconSelected : function () {
		return this.struct.imgopenselected;
	},
	
	getCloseIconSelected : function () {
		return this.struct.imgcloseselected;
	},
	
	getCurrentIcon : function () {
		var img = this._getImgInfo(this.img);
		return img.fullName;
	},
	
	setText : function (text) {
		this.struct.txt = text;
		this.txt.innerHTML = text;
	},
	
	setIcons : function (icon, iconOpen, iconClose) {
		this.struct.img = icon;
		this.struct.imgopen = (iconOpen) ? iconOpen : icon;
		this.struct.imgclose = (iconClose) ? iconClose : icon;
		if (this.hasChildren()) {
			this.img.src = (this.isOpened()) ? this.tree.imgBase + this.struct.imgopen : this.tree.imgBase + this.struct.imgclose;
		} else {
			this.img.src = this.tree.imgBase + this.struct.img;
		}
	},
	
	setIconsSelected : function (icon, iconOpen, iconClose) {
		this.struct.imgselected = icon;
		this.struct.imgopenselected = (iconOpen) ? iconOpen : null;
		this.struct.imgcloseselected = (iconClose) ? iconClose : null;
		if (this.isSelected()) {
			if (this.hasChildren()) {
				this.img.src = (this.isOpened()) ? this.tree.imgBase + this.struct.imgopenselected : this.tree.imgBase + this.struct.imgcloseselected;
			} else {
				this.img.src = this.tree.imgBase + this.struct.imgselected;
			}
		}
	},
	
	/**
	 * Méthode qui change l'id de l'élément. A utiliser avec parcimonie
	 *
	 * @access	public
	 * @param	string			newId					Le nouvel id
	 * @return	boolean									True si tout est ok, false si l'id existe déjà dans l'arbre
	 */
	changeId : function (newId) {
		var used = this.tree.getBranchById(newId);
		if (!used) {
			this.struct.id = newId;
			this.tree._changeStruct(this);
			return true;
		} else {
			return false;
		}
	},
	
	/**
	 * Méthode qui détermine si l'élément a des enfants ou non*
	 *
	 * @access	public
	 * @return	boolean									True s'il a des enfants, false sinon
	 */
	hasChildren : function () {
		return (this.struct.items.length > 0 || this.struct.canhavechildren) ? true : false;
	},

	isOpened : function () {
		return (this.struct.open) ? true : false;
	},

	isAlwaysLast : function () {
		return (this.struct.last) ? true : false;
	},

	isOpenedInCookie : function () {
		if (this.tree.useCookie && this.tree.cookieOpened) {
			for (var i = 0; i < this.tree.cookieOpened.length; i++) {
				if (this.getId() == this.tree.cookieOpened[i]) return true;
			}
		}
		return false;
	},

	/**
	 * Retourne true si la branche est visible
	 *
	 * @access	public
	 * @return	boolean									True si la branche est visible, false sinon
	 */
	isVisible : function () {
		var visible = true;
		var branch = this;
		while (branch.parent) {
			if (branch.parent.isOpened()) {
				branch = branch.parent;
			} else {
				visible = false;
				break;
			}
		}
		return visible;
	},

	/**
	 * Retourne TRUE si la branche est sélectionnée
	 *
	 * @access	public
	 * @return	boolean										True si la branche est sélectionnée, false sinon
	 */
	isSelected : function () {
		return (Element.hasClassName(this.txt, this.tree.classSelected)) ? true : false;
	},


	/**
	 *------------------------------------------------------------------------------
	 *							TafelTreeBaseBranch public functions
	 *------------------------------------------------------------------------------
	 */

	/**
	 * Rafraichit les enfants de la branche en fonction de ce qu'il y a sur le serveur
	 *
	 * @access	public
	 * @return	void
	 */
	refreshChildren : function () {
		this.removeChildren();
		this._openPopulate();
	},
	
	/**
	 * Clone toute la structure de la branche
	 *
	 * @access	public*
	 * @param	boolean			withDefaultFunc			True pour copier les fonctions par defaut de l'arbre
	 * @return	object									La structure JSON de la branche
	 */
	clone : function (withDefaultFunc) {
		var struct = {};
		for (var property in this.struct) {
			if (property != 'items') {
				// On prend les fonctions seulement si elles sont définies pour la branche
				if (!withDefaultFunc && typeof(this.struct[property]) == 'function') {
					if (!eval('this.' + property + 'Default')) {
						struct[property] = this.struct[property];
					}
				} else {
					struct[property] = this.struct[property];
				}
			}
		}
		if (this.hasChildren()) {
			struct.items = [];
			for (var i = 0; i < this.children.length; i++) {
				struct.items.push(this.children[i].clone(withDefaultFunc)); 
			}
		}
		this.copiedTimes++;
		struct.id = struct.id + this.tree.copyNameBreak + this.tree.idTree;
		struct.txt = struct.txt + this.tree.copyName.replace('%n', this.copiedTimes);
		return struct;
	},

	/**
	 * Retourne le premier enfant de la branche, ou null s'il y en a pas
	 *
	 * @access	public
	 * @return	TafelTreeBranch								Le premier enfant de la branche
	 */
	getFirstBranch : function () {
		return (this.children.length > 0) ? this.children[0] : null;
	},

	/**
	 * Retourne le dernier enfant de la branche, ou null s'il y en a pas
	 *
	 * @access	public
	 * @return	TafelTreeBranch								Le dernier enfant de la branche
	 */
	getLastBranch : function () {
		var pos = this.children.length - 1;
		return (pos >= 0) ? this.children[pos] : null;
	},
	
	/**
	 * Fonction qui récupère la branche précédente du même niveau
	 *
	 * @access	public
	 * @return	TafelTreeBranch								La branche si elle existe, null sinon
	 */
	getPreviousSibling : function () {
		var pos = this.pos - 1;
		var branch = null;
		if (this.isRoot) {
			if (pos >= 0) branch = this.tree.roots[pos];
		} else {
			if (pos >= 0) branch = this.parent.children[pos];
		}
		return branch;
	},
	
	/**
	 * Fonction qui récupère la branche suivante du même niveau
	 *
	 * @access	public
	 * @return	TafelTreeBranch								La branche si elle existe, null sinon
	 */
	getNextSibling : function () {
		var pos = this.pos + 1;
		var branch = null;
		if (this.isRoot) {
			if (pos < this.tree.roots.length) branch = this.tree.roots[pos];
		} else {
			if (pos < this.parent.children.length) branch = this.parent.children[pos];
		}
		return branch;
	},

	/**
	 * Retourne la branche précédente dans l'arbre, pas forcément de même niveau
	 *
	 * @access	public
	 * @return	TafelTreeBranch								La branche précédente, null s'il n'y en a pas
	 */
	getPreviousBranch : function () {
		var branch = null;
		var previous = this.getPreviousSibling();
		// Si elle a une soeur précédente
		if (previous) {
			// On regarde si elle a des enfants et est ouverte
			if (previous.hasChildren()) {
				// Si oui, on prend son dernier enfant
				while (previous.hasChildren()) {
					previous = previous.getLastBranch();
				}
				branch = previous;
			} else {
				// Si ce n'est pas le cas, on la prend elle
				branch = previous;
			}
		} else {
			// Si elle n'a pas de soeur précédente, on prend le parent (s'il existe)
			if (this.parent) {
				branch = this.parent;
			}
		}
		return branch;
	},

	/**
	 * Retourne la branche suivante dans l'arbre, pas forcément de même niveau
	 *
	 * @access	public
	 * @return	TafelTreeBranch								La branche suivante, null s'il n'y en a pas
	 */
	getNextBranch : function () {
		var branch = null;
		// Récupère le premier enfant, s'il y en a un
		branch = this.getFirstBranch();
		if (!branch) {
			// Récupère sa prochaine soeur
			branch = this.getNextSibling();
			if (!branch) {
				// Récupère la soeur du parent ou tout du moins d'un ancêtre
				var b = null;
				branch = this.parent;
				while (!b && branch) {
					b = branch.getNextSibling();
					branch = branch.parent;
				}
				branch = b;
			}
		}
		return branch;
	},
	
	/**
	 * Retourne la branch ouverte précédente (pas forcément du même niveau)
	 *
	 * @access	public
	 * @return	TafelTreeBranch								La branche précédente ouverte, null s'il n'y en a pas
	 */
	getPreviousOpenedBranch : function () {
		var branch = null;
		var previous = this.getPreviousSibling();
		// Si elle a une soeur précédente
		if (previous) {
			// On regarde si elle a des enfants et est ouverte
			if (previous.hasChildren() && previous.isOpened()) {
				// Si oui, on prend son dernier enfant
				while (previous.hasChildren() && previous.isOpened()) {
					previous = previous.getLastBranch();
				}
				branch = previous;
			} else {
				// Si ce n'est pas le cas, on la prend elle
				branch = previous;
			}
		} else {
			// Si elle n'a pas de soeur précédente, on prend le parent (s'il existe)
			if (this.parent) {
				branch = this.parent;
			}
		}
		return branch;
	},
	
	/**
	 * Retourne la branch ouverte suivante (pas forcément du même niveau)
	 *
	 * @access	public
	 * @return	TafelTreeBranch								La branche suivante ouverte, null s'il n'y en a pas
	 */
	getNextOpenedBranch : function () {
		var branch = null;
		// Si elle a des enfants et qu'elle est ouverte, on prend le 1er
		if (this.hasChildren() && this.isOpened()) {
			branch = this.getFirstBranch();
		} else {
			// Si elle a pas d'enfants, on prend sa prochaine soeur
			var next = this;
			while (!branch) {
				branch = next.getNextSibling();
				next = next.parent;
				if (!next) break;
			}
		}
		return branch;
	},

	/**
	 * Fonction qui supprime tous les enfants
	 *
	 * @access	public
	 * @return	boolean										True si la branche est un enfant de elem
	 */
	removeChildren : function () {
		// On utilise concat() pour ne pas faire de référence sur this.children
		var children = this.children.concat();
		for (var i = 0; i < children.length; i++) {
			this.tree.removeBranch(children[i]);
		}
	},

	/**
	 * Fonction qui détermine si la branche est enfant de elem
	 *
	 * @access	public
	 * @param	TafelTreeBranch		elem					La branche dont on veut savoir si elle est un ancêtre
	 * @return	boolean										True si la branche est un enfant de elem
	 */
	isChild : function (elem) {
		var elem = this.tree.getBranchById(elem);
		if (!elem) return false;
		return this._isChild(this, elem);
	},

	/**
	 * Fonction qui ouvre ou ferme la branche
	 *
	 * @access	public
	 * @param	boolean				open					True pour ouvrir la branche
	 * @return	void
	 */
	openIt : function (open) {
		try {
			if (!open) {
				this._closeChild();
				if (this.tree.multiline) {
					this._manageMultiline(this.tdImg, 2, false);
				}
			} else {
				if (this.tree.onlyOneOpened) {
					this.closeSiblings();
				}
				this._openChild();
				if (this.tree.multiline) {
					this._manageMultiline(this.tdImg, 2, true);
				}
			}
			if (this.tree.useCookie) {
				this.tree.setCookie(this.tree.classTree + this.tree.id);
			}
		} catch (err) {
			throw new Error ('openIt(base) : ' + err.message);
		}
	},

	/**
	 * Fonction qui insère une branche comme enfant, en fin de liste
	 *
	 * @access	public
	 * @param	object				item					La nouvelle branche
	 * @return	void
	 */
	insert : function (item) {
		return this.insertIntoLast(item);
	},
	insertIntoLast : function (item) {
		var pos = this.children.length;
		var isNotFirst = (this.hasChildren()) ? true : false;
		this.children[pos] = new TafelTreeBranch((this.isRoot) ? this : this.root, this, item, this.level + 1, isNotFirst, false, pos);
		this.struct.items[pos] = item;
		this.obj.appendChild(this.children[pos].obj);
		this._manageAfterInsert(pos);
		return this.children[pos];
	},

	insertIntoFirst : function (item) {
		var pos = 0;
		var posBefore = 1;
		var isNotLast = (this.hasChildren()) ? false : true;
		this._movePartStruct(pos);
		this.struct.items[pos] = item;
		this.children[pos] = new TafelTreeBranch((this.isRoot) ? this : this.root, this, item, this.level + 1, false, isNotLast, pos);
		try {
			this.obj.insertBefore(this.children[pos].obj, this.children[posBefore].obj);
		} catch (err) {
			this.obj.appendChild(this.children[pos].obj);
		}
		this._manageAfterInsert(pos);
		return this.children[pos];
	},
	
	/**
	 * Fonction qui ferme toutes les branches soeurs
	 *
	 * @access	public
	 * @return	void
	 */
	closeSiblings : function () {
		var obj = null;
		if (this.parent) {
			for (var i = 0; i < this.parent.children.length; i++) {
				obj = this.parent.children[i];
				if (obj.idObj != this.idObj && obj.hasChildren()) {
					obj.openIt(false);
				}
			}
		} else if (this.isRoot) {
			for (var i = 0; i < this.tree.roots.length; i++) {
				obj = this.tree.roots[i];
				if (obj.idObj != this.idObj && obj.hasChildren()) {
					obj.openIt(false);
				}
			}
		}
	},
	
	/**
	 * Ajoute une classe CSS au texte
	 *
	 * @access	public
	 * @param	string			style					Le style CSS à ajouter
	 * @return	void
	 */
	addClass : function (style) {
		Element.addClassName(this.txt, style);
	},
	
	/**
	 * Retire une classe CSS du texte
	 *
	 * @access	public
	 * @param	string			style					Le style CSS à enlever
	 * @return	void
	 */
	removeClass : function (style) {
		Element.removeClassName(this.txt, style);
	},

	/**
	 * Retourne un objet anonyme représentant l'image précédant l'icône (un plus, par ex.)
	 *
	 * L'objet a cette structure :
		var obj = {
			'img'     : HTMLimgElement,
			'number'  : Le numéro de l'image (juste avant l'extension),
			'type'    : le nom de l'image sans le numéro et sans l'extension
			'name'    : Le nom de l'image sans l'extension,
			'fullName': Le nom de l'image avec l'extension
		};
	 *
	 * @access	public
	 * @return	object										L'objet anonyme de l'image
	 */
	getImgBeforeIcon : function () {
		try {
			var img = this.beforeIcon.getElementsByTagName('img')[0];
			return this._getImgInfo(img);
		} catch (err) {
			throw new Error ('getImgBeforeIcon(base) : ' + err.message);
		}
	},
	
	/**
	 * Fonction change l'icône en fonction des enfants, s'il y en a ou pas
	 *
	 * @access	public
	 * @param	boolean			openable					True pour mettre l'icone d'ouverture
	 * @return	void
	 */
	setOpenableIcon : function (openable) {
		var im = this.getImgBeforeIcon();
		var img = im.img;
		if (openable) {
			this.struct.open = true;
			this.img.src = this.tree.imgBase + this.struct.imgopen;
			if (!this.isRoot) {
				img.src = (this.hasSiblingsAfter) ? this.tree.imgBase + this.tree.imgMinus3 : this.tree.imgBase + this.tree.imgMinus2;
			} else {
				if (this.hasSiblingsBefore) {
					img.src = (this.hasSiblingsAfter) ? this.tree.imgBase + this.tree.imgMinus3 : this.tree.imgBase + this.tree.imgMinus2;
				} else {
					img.src = (this.hasSiblingsAfter) ? this.tree.imgBase + this.tree.imgMinus4 : this.tree.imgBase + this.tree.imgMinus5;
				}
			}
			Event.observe(img, 'click', this.setOpen.bindAsEventListener(this), false);
			Event.observe(img, 'mouseover', this.evt_openMouseOver.bindAsEventListener(this), false);
			Event.observe(img, 'mouseout', this.evt_openMouseOut.bindAsEventListener(this), false);
		} else {
			this.struct.open = false;
			this.struct.canhavechildren = false;
			this.img.src = this.tree.imgBase + this.struct.img;
			var td = img.parentNode;
			var newImg = document.createElement('img');
			td.removeChild(img);
			if (!this.isRoot) {
				newImg.src = (this.hasSiblingsAfter) ? this.tree.imgBase + this.tree.imgLine3 : this.tree.imgBase + this.tree.imgLine2;
			} else {
				if (this.hasSiblingsBefore) {
					newImg.src = (this.hasSiblingsAfter) ? this.tree.imgBase + this.tree.imgLine3 : this.tree.imgBase + this.tree.imgLine2;
				} else {
					newImg.src = (this.hasSiblingsAfter) ? this.tree.imgBase + this.tree.imgLine4 : this.tree.imgBase + this.tree.imgLine5;
				}
			}
			td.appendChild(newImg);
		}
	},

	/**
	 * Fonction qui affiche la branche de manière cool
	 *
	 * @access	public
	 * @return	string										La string à afficher
	 */
	toString : function () {		
		var str = (this.isRoot) ? 'TafelTreeRoot {' : 'TafelTreeBranch {';
		// Définition de toutes les propriétés
		var strSave = '';
		for (var attr in this.struct) {
			if (attr != 'items') {
				strSave = (typeof(this.struct[attr]) != 'function') ? this.struct[attr] : true;
				str += TafelTree.debugReturn + TafelTree.debugTab + attr + ' : ' + strSave;
			}
		}
		str += TafelTree.debugReturn + TafelTree.debugTab + 'children : ' + this.children.length;
		str += TafelTree.debugReturn + '}';
		return str;
	},
	
	isChecked : function (dbg) {
		if (this.tree.checkboxes && this.checkbox) {
			var img = this._getImgInfo(this.checkbox);
			if (img.fullName.replace('_over', '') == this.tree.imgCheck2) {
				return 1;
			}
			if (img.fullName.replace('_over', '') == this.tree.imgCheck3) {
				return -1;
			}
			return 0;
		}
		return 1;
	},

	getCheckbox : function () {
		return (this.checkbox) ? this.checkbox : false;
	},
	
	check : function (checked) {
		if (this.checkbox) {
			if (checked == -1) {
				this.checkbox.src = this.tree.imgBase + this.tree.imgCheck3;
				this.struct.check = -1;
			} else if (checked) {
				this.checkbox.src = this.tree.imgBase + this.tree.imgCheck2;
				this.struct.check = 1;
				if (this.tree.useCookie) {
					this.tree.setCookie(this.tree.classTree + this.tree.id);
				}
			} else {
				this.checkbox.src = this.tree.imgBase + this.tree.imgCheck1;
				this.struct.check = 0;
				if (this.tree.useCookie) {
					this.tree.setCookie(this.tree.classTree + this.tree.id);
				}
			}
		}
	},
	
	/**
	 * Fonction qui retourne 1 si tous les enfants sont checkés, 0 si aucun et -1 si quelques uns
	 *
	 * @access	public
	 * @return	integer										1 si tout check, 0 si aucun, -1 si pas tous
	 */
	hasAllChildrenChecked : function () {
		var allChecked = false;
		var anyChecked = false;
		for (var i = 0; i < this.children.length; i++) {
			if (this.children[i].isChecked() == -1) {
				allChecked = true;
				anyChecked = true;
				break;
			}
			if (this.children[i].isChecked() == 1) allChecked = true;
			else anyChecked = true;
		}
		if (allChecked && anyChecked) return -1;
		if (allChecked) return 1;
		else return 0;
	},

	/**
	 * Permet d'intervertir les deux branches. Elles conserveront toutes leurs
	 * caractéristiques (heureusement)
	 *
	 * @access	public
	 * @param	TafelTreeBranch		branchId			L'id de l'autre branche ou l'autre branche elle-même
	 * @return	void
	 */
	switchWith : function (branchId) {
		var branch = this.tree.getBranchById(branchId);
		if (!branch) return false;

		var copyThis = this.copiedTimes;
		var newThis = this.clone();
		var txtThis = this.getText();
		var idThis = this.getId();
		var copyBanch = branch.copiedTimes;
		var newBranch = branch.clone();
		var txtBranch = branch.getText();
		var idBranch = branch.getId();
		// On change l'id de la branche courante
		this.changeId('temp_switch_change_' + this.tree.idTree);
		// Inversion des branches
		var n1 = branch.insertBefore(newThis);
		this.tree.removeBranch(branch);
		n1.setText(txtThis);
		n1.changeId(idThis);
		n1.copiedTimes = copyThis;
		var n2 = this.insertBefore(newBranch);
		this.tree.removeBranch(this);
		n2.setText(txtBranch);
		n2.changeId(idBranch);
		n2.copiedTimes = copyBranch;
	},

	/**
	 * Remplace la branche passée en paramètre par l'autre.
	 *
	 * @access	public
	 * @param	TafelTreeBranch		branchId			L'id de la branche à remplacer ou l'objet lui-même
	 * @param	boolean				copy				True pour faire une copie de la branche qui remplace
	 * @return	TafelTreeBranch							La branche remplacée
	 */
	replace : function (branchId, copy) {
		var branch = this.tree.getBranchById(branchId);
		if (!branch) return false;

		// Préparation du remplacement
		var copyThis = this.copiedTimes;
		var newThis = this.clone();
		var n1 = branch.insertBefore(newThis);
		this.tree.removeBranch(branch);
		if (!copy) {
			var idThis = this.getId();
			n1.setText(this.getText())
			this.tree.removeBranch(this);
			n1.changeId(idThis);
			n1.copiedTimes = copyThis;
		}
		return n1;
	},

	/**
	 * Méthode récursive qui ouvre la branche
	 *
	 * @access	public
	 * @return	void
	 */
	expend : function () {
		if (this.isOpened() != true && this.hasChildren()) {
			this.openIt(true);
		}
		for (var i = 0; i < this.children.length; i++) {
			this.children[i].expend();
		}
	},
	
	/**
	 * Méthode récursive qui ferme la branche
	 *
	 * @access	public
	 * @return	void
	 */
	collapse : function () {
		if (this.isOpened() != false && this.hasChildren()) {
			this.openIt(false);
		}
		for (var i = 0; i < this.children.length; i++) {
			this.children[i].collapse();
		}
	},
	
	/**
	 * Retourne toutes les branches de l'arbre
	 *
	 * @access	public
	 * @param	function		filter					Le filtre des branches
	 * @param	array			[branches]				Optionnel, le tableau des branches incomplet
	 * @return	array									Un tableau des branches de l'arbre
	 */
	getBranches : function (filter, branches) {
		if (!branches) branches = [];
		for (var i = 0; i < this.children.length; i++) {
			if (typeof(filter) == 'function') {
				if (filter(this.children[i])) {
					branches.push(this.children[i]);
				}
			} else {
				branches.push(this.children[i]);
			}
			branches = this.children[i].getBranches(filter, branches);
		}
		return branches;
	},

	/**
	 * Retourne les branches parentes (qui ont des enfants)
	 *
	 * @access	public
	 * @param	array			[parent]				Optionnel, le tableau des branches parentes incomplet
	 * @return	array									Le tableau des branches parentes complet
	 */
	getParentBranches : function (parents) {
		if (!parents) parents = [];
		for (var i = 0; i < this.children.length; i++) {
			if (this.children[i].hasChildren()) {
				parents.push(this.children[i]);
			}
			parents = this.children[i].getParentBranches(parents);
		}
		return parents;
	},

	/**
	 * Retourne les branches qui n'ont pas d'enfants
	 *
	 * @access	public
	 * @param	array			[leafs]					Optionnel, le tableau des branches incomplet
	 * @return	array									Le tableau des branches complet
	 */
	getLeafBranches : function (leafs) {
		if (!leafs) leafs = [];
		for (var i = 0; i < this.children.length; i++) {
			if (!this.children[i].hasChildren()) {
				leafs.push(this.children[i]);
			}
			leafs = this.children[i].getLeafBranches(leafs);
		}
		return leafs;
	},

	/**
	 * Retourne le nombre de branche comprises dans la branche courante
	 *
	 * @access	public
	 * @return	integer										Le nombre de branches
	 */
	countBranches : function () {
		var nb = this.children.length;
		for (var i = 0; i < this.children.length; i++) {
			nb += this.children[i].countBranches();
		}
		return nb;
	},
	
	/**
	 * Méthode récursive qui détermine si la branche est ouverte ou non
	 *
	 * @access	public
	 * @param	array				openedBranches			Le tableau des branches ouvertes
	 * @return	void
	 */
	getOpenedBranches : function (openedBranches) {
		if (!openedBranches) openedBranches = [];
		for (var i = 0; i < this.children.length; i++) {
			if (this.children[i].isOpened() && this.children[i].hasChildren()) {
				openedBranches.push(this.children[i]);
			}
			openedBranches = this.children[i].getOpenedBranches(openedBranches);
		}
		return openedBranches;
	},
	
	/**
	 * Méthode récursive qui détermine si la branche est checkée
	 *
	 * @access	private
	 * @param	array				checkedBranches			Le tableau des branches checkées
	 * @return	void
	 */
	getCheckedBranches : function (checkedBranches) {
		return this._getCheckedBranches(checkedBranches, 1);
	},
	
	/**
	 * Méthode récursive qui détermine si la branche est checkée
	 *
	 * @access	private
	 * @param	array				checkedBranches			Le tableau des branches checkées
	 * @return	void
	 */
	getUnCheckedBranches : function (checkedBranches) {
		return this._getCheckedBranches(checkedBranches, 0);
	},
	
	/**
	 * Méthode récursive qui détermine si la branche est checkée
	 *
	 * @access	private
	 * @param	array				checkedBranches			Le tableau des branches checkées
	 * @return	void
	 */
	getPartCheckedBranches : function (checkedBranches) {
		return this._getCheckedBranches(checkedBranches, -1);
	},

	/**
	 * Sélectionne la branche
	 *
	 * @access	public
	 * @param	Event			ev							L'événement déclencheur
	 * @return	void
	 */
	select : function (ev) {
		var ctrl = (ev) ? TafelTreeManager.ctrlOn(ev) : false;
		var shift = (ev) ? TafelTreeManager.shiftOn(ev) : false;
		if (ctrl) {
			this.tree.selectedBranches.push(this);
		} else if (shift && this.tree.selectedBranches.length > 0) {
			var last = this.tree.selectedBranches.length - 1;
			var sel = this.tree.getBranchesBetween(this.tree.selectedBranches[last], this);
			for (var i = 0; i < sel.length; i++) {
				this.tree.selectedBranches.push(sel[i]);
				Element.addClassName(sel[i].txt, this.tree.classSelected);
			}
		} else {
			this.tree.unselect();
			this.tree.selectedBranches.push(this);
		}
		Element.addClassName(this.txt, this.tree.classSelected);
		// On set l'icône s'il doit changer
		if (this.isOpened() && this.hasChildren() && this.getOpenIconSelected()) {
			this.img.src = this.tree.imgBase + this.getOpenIconSelected();
		} else if (!this.isOpened() && this.hasChildren() && this.getCloseIconSelected()) {
			this.img.src = this.tree.imgBase + this.getCloseIconSelected();
		} else if (!this.hasChildren() && this.getIconSelected()) {
			this.img.src = this.tree.imgBase + this.getIconSelected();
		}
		if (ev) Event.stop(ev);
	},

	/**
	 * Désélectionne la branche
	 *
	 * @access	public
	 * @return	boolean											True si la branche a pu être déselectionnée, false sinon
	 */
	unselect : function () {
		var ln = this.tree.selectedBranches.length;
		if (ln > 0) {
			for (var i = 0; i < ln; i++) {
				if (this.tree.selectedBranches[i].getId() == this.getId()) {
					this.tree.selectedBranches.splice(i, 1);
					Element.removeClassName(this.txt, this.tree.classSelected);
					// On set l'icône s'il doit changer
					if (this.hasChildren()) {
						this.img.src = (this.isOpened()) ? this.tree.imgBase + this.struct.imgopen : this.tree.imgBase + this.struct.imgclose;
					} else {
						this.img.src = this.tree.imgBase + this.struct.img;
					}
					return true;
				}
			}
		}
		return false;
	},
	
	/** 
	 * Calcule la position de la branche à l'intérieur de l'arbre
	 *
	 * @access	public
	 * @return	array									[0] Left pos, [1] Top pos
	 */
	getWithinOffset : function () {
		var realPos = Position.positionedOffset(this.txt);
		var posTree = Position.positionedOffset(this.tree.div);
		var pos = [
			realPos[0] - posTree[0],
			realPos[1] - posTree[1]
		];
		return pos;
	},
	
	/** 
	 * Calcule la position de la branche dans l'écran
	 *
	 * @access	public
	 * @return	array									[0] Left pos, [1] Top pos
	 */
	getAbsoluteOffset : function () {
		return Position.positionedOffset(this.txt);
	},	
	
	/**
	 * Permet de sérialiser la branche, pour en faire une string au format JSON
	 *
	 * Les fonctions ne sont pas encodées dans la string (comme onopen, onclick, etc.). Par contre, on indique
	 * true si la fonction existe bel et bien pour la branche
	 *
	 * @access	public
	 * @param	boolean				debug					True pour afficher le debug de la string
	 * @param	boolean				noEncoding				True pour ne pas encoder la string automatiquement
	 * @return	string										La string JSON de la branche
	 */
	serialize : function (debug, noEncoding) {
		var tab = '';
		var rt = '';
		if (debug) {
			rt = TafelTree.debugReturn;
			for (var i = 0; i < this.level; i++) {
				tab += TafelTree.debugTab;
			}
		}
		var strSave = '';
		var str = rt + tab + '{' + rt;
		// Définition de toutes les propriétés
		str += tab + '"id":"' + this._encode(this.struct.id) + '"';
		for (var attr in this.struct) {
			if (attr != 'items' && attr != 'id') {
				strSave = (typeof(this.struct[attr]) != 'function') ? this.struct[attr] : true;
				if (this.isBool(strSave)) {
					str += "," + rt + tab + '"' + attr + '":' + this._encode(strSave);
				} else {
					str += "," + rt + tab + '"' + attr + '":"' + this._encode(strSave) + '"';
				}
			}
		}
		// Définition des enfants
		if (this.hasChildren()) {
			str += ',' + rt + tab + '"items":[';
			for (var i = 0; i < this.children.length; i++) {
				str += this.children[i].serialize(debug, true);
				if (i < this.children.length - 1) {
					str += ',';
				}
			}
			str += rt + tab + ']';
		}
		str += rt + tab + '}';
		if (!noEncoding) {
			return encodeURIComponent(str);
		} else {
			return str;
		}
	},

	isBool : function (str) {
		switch (str) {
			case 'true':	case 'false':
			case true:		case false:
			case '1':		case '0' :
			case 1:			case 0 :
				return true;
			default :
				return false;
		}
	},
	
	showTooltip : function () {
		if (this.displayTooltip) {
			this.tooltip.style.display = 'block';
		}
	},
	
	hideTooltip : function () {
		if (!this.displayTooltip) {
			Element.hide(this.tooltip);
		}
	},

	/**
	 * Fonction récursive qui supprime les liens avec Droppables et Draggable
	 *
	 * @access	public
	 * @param	TafelTreeBranch			obj					La branche courante
	 * @return	void
	 */
	removeDragDrop : function () {
		if (this.objDrag) {
			this.objDrag.destroy();
		}
		Droppables.remove(this.txt);
		for (var i = 0; i < this.children.length; i++) {
			this.children[i].removeDragDrop();
		}
	},
	
	
	/**
	 *------------------------------------------------------------------------------
	 *							TafelTreeBaseBranch private methods
	 *------------------------------------------------------------------------------
	 */
	
	/**
	 * Fonction qui met à jour l'élément en terme de multiline
	 *
	 * @access	private
	 * @param	HTMLElement		element					L'élément HTML incriminé
	 * @param	integer			type					1 ou 2 (suivant le type de ligne)
	 * @param	boolean			add						True si on ajoute le multiline, false si on l'enlève
	 * @return	void
	 */
	_manageMultiline : function (element, type, add) {
		switch (type) {
			case 2 :
				if (!add) {
					Element.removeClassName(element, this.tree.imgMulti4);
					element.style.background = 'none';
				} else {
					Element.addClassName(element, this.tree.imgMulti4);
					element.style.background = 'url("' + this.tree.imgBase + this.tree.imgMulti2 + '")';
					element.style.backgroundRepeat = 'repeat-y';
				}
				break;
			case 1 :
			default :
				if (!add) {
					Element.removeClassName(element, this.tree.imgMulti3);
					element.style.background = 'none';
				} else {
					Element.addClassName(element, this.tree.imgMulti3);
					element.style.background = 'url("' + this.tree.imgBase + this.tree.imgMulti1 + '")';
					element.style.backgroundRepeat = 'repeat-y';
				}
		}
	},
	
	_createTooltip : function () {
		var div = document.createElement('div');
		div.className = this.tree.classTooltip;
		div.innerHTML = (this.struct.tooltip) ? this.struct.tooltip : '&nbsp;';
		Event.observe(div, 'mouseover', this.showTooltip.bindAsEventListener(this), false);
		return div;
	},

	_manageAfterInsert : function (pos) {
		this.tree._changeStruct(this);
		this._manageLine();
		// Si on a des checkboxes, on corrige les images en fonction des checks par défaut
		if (this.tree.checkboxes && this.tree.checkboxesThreeState) {
			this.children[pos]._adjustParentCheck();
		}
		if (this.children.length == 1 && !this.struct.canhavechildren) {
			this.setOpenableIcon(true);
		}
		this.openIt((!this.tree.openedAfterAdd && !this.isOpened()) ? false : true);
	},
	
	_movePartStruct : function (pos) {
		var nb = this.struct.items.length - 1;
		var newPos = 0;
		for (var i = nb; i >= pos; i--) {
			newPos = i + 1;
			this.struct.items[newPos] = this.struct.items[i];
			this.children[newPos] = this.children[i];
			this.children[newPos].pos = newPos;
		}
	},

	/**
	 * Méthode récursive qui détermine si la branche est checkée ou non
	 *
	 * @access	private
	 * @param	array				checkedBranches			Le tableau des branches checkées
	 * @param	boolean				checked					1 pour récupérer les branches checkées
	 * @return	void
	 */
	 _getCheckedBranches : function (checkedBranches, checked) {
		if (!checkedBranches) checkedBranches = [];
		for (var i = 0; i < this.children.length; i++) {
			if (this.children[i].isChecked() == checked) {
				checkedBranches.push(this.children[i]);
			}
			checkedBranches = this.children[i]._getCheckedBranches(checkedBranches, checked);
		}
		return checkedBranches;
	 },

	_generate : function () {
		var i = this.bigTreeLoading;
		if (i < this.struct.items.length) {
			if (this.tree.checkboxesThreeState && this.struct.check && typeof(this.struct.items[i].check) == 'undefined') {
				this.struct.items[i].check = 1;
			}
			isNotFirst = (i > 0) ? true : false;
			isNotLast = (i < this.struct.items.length - 1) ? true : false;
			this.children[i] = new TafelTreeBranch((this.isRoot) ? this : this.root, this, this.struct.items[i], this.level + 1, isNotFirst, isNotLast, i);
			this.obj.appendChild(this.children[i].obj);
			this.openIt((this.tree.useCookie) ?  this.isOpenedInCookie : this.struct.open);
			this.tree.loadRunning(this.children[i]);
			this.bigTreeLoading++;
			setTimeout(this._generate.bind(this), 10);
		} else {
			this.loaded = true;
		}
	},
	
	_getPos : function () {
		pos = this.children.length;
		for (var i = 0; i < this.children.length; i++) {
			if (this.children[i].isAlwaysLast()) {
				pos--;
			}
		}
		if (pos < 0 ) pos = 0;
		return pos;
	},
	
	/**
	 * Fonction qui ajuste le check des parent de la branche, suite à un changement
	 *
	 * @access	private
	 * @param	boolean			fromBranch				True pour commencer l'ajustement depuis la branche même
	 * @return	void
	 */
	_adjustParentCheck : function (fromBranch) {
		if (this.parent) {
			var branch = (!fromBranch) ? this.parent : this;
			while (branch && branch.checkbox) {
				branch.check(branch.hasAllChildrenChecked());
				branch = branch.parent;
			}
		}
	},
	
	/**
	 * Fonction récursive qui va changer le status des checkboxes enfants
	 *
	 * @access	private
	 * @param	TafelTreeBranch		branch					La branche courante
	 * @param	boolean				checked					True ou false
	 * @return	void
	 */
	_manageCheckThreeState : function (branch, checked) {
		for (var i = 0; i < branch.children.length; i++) {
			if (branch.tree.checkboxes && branch.children[i].checkbox) {
				branch.children[i].check(checked);
				branch._manageCheckThreeState(branch.children[i], checked);
			}
		}
	},
	
	_getImgInfo : function (img) {
		var url = img.src.split('/');
		var name = url[url.length-1].split('.');
		var obj = {
			'img': img,
			'number': name[0].charAt(name[0].length-1),
			'type': name[0].substr(0, name[0].length-1),
			'name': name[0],
			'fullName': url[url.length-1]
		};
		return obj;
	},
	
	/**
	 * Permet d'encoder la string avant l'envoi en JSON
	 *
	 * @access	private
	 * @param	string				str						La string correspondant à la propriété (this.struct.*)
	 * @return	string										La valeur de la propriété encodée
	 */
	_encode : function (str) {
		//var obj = eval(str);
		var obj = (str === null) ? '' : str;
		return obj.toString().replace(/\"/g, '\\"');
	},
	
	_closeChild : function (img) {
		try {
			img = this.getImgBeforeIcon().img;
			this.struct.open = false;
			if (this.isSelected() && this.getCloseIconSelected()) {
				this.img.src = this.tree.imgBase + this.getCloseIconSelected();
			} else {
				this.img.src = this.tree.imgBase + this.struct.imgclose;
			}
			for (var i = 0; i < this.obj.childNodes.length; i++) {
				if (this.obj.childNodes[i].nodeName.toLowerCase() == 'div') {
					Element.hide(this.obj.childNodes[i]);
				}
			}
			if (!this.isRoot) {
				img.src = (this.hasSiblingsAfter) ? this.tree.imgBase + this.tree.imgPlus3 : this.tree.imgBase + this.tree.imgPlus2;
			} else {
				if (this.hasSiblingsBefore) {
					img.src = (this.hasSiblingsAfter) ? this.tree.imgBase + this.tree.imgPlus3 : this.tree.imgBase + this.tree.imgPlus2;
				} else {
					img.src = (this.hasSiblingsAfter) ? this.tree.imgBase + this.tree.imgPlus4 : this.tree.imgBase + this.tree.imgPlus5;
				}
			}
		} catch (err) {
			throw new Error ('_closeChild(base) : ' + err.message);
		}
	},
	
	_openChild : function (img) {
		try {
			img = this.getImgBeforeIcon().img;
			this.struct.open = true;
			if (this.isSelected() && this.getOpenIconSelected()) {
				this.img.src = this.tree.imgBase + this.getOpenIconSelected();
			} else {
				this.img.src = this.tree.imgBase + this.struct.imgopen;
			}
			for (var i = 0; i < this.obj.childNodes.length; i++) {
				if (this.obj.childNodes[i].nodeName.toLowerCase() == 'div') {
					this.obj.childNodes[i].style.display = '';
				}
			}
			if (!this.isRoot) {
				img.src = (this.hasSiblingsAfter) ? this.tree.imgBase + this.tree.imgMinus3 : this.tree.imgBase + this.tree.imgMinus2;
			} else {
				if (this.hasSiblingsBefore) {
					img.src = (this.hasSiblingsAfter) ? this.tree.imgBase + this.tree.imgMinus3 : this.tree.imgBase + this.tree.imgMinus2;
				} else {
					img.src = (this.hasSiblingsAfter) ? this.tree.imgBase + this.tree.imgMinus4 : this.tree.imgBase + this.tree.imgMinus5;
				}
			}
		} catch (err) {
			throw new Error ('_openChild(base) : ' + err.message);
		}
	},
	
	/**
	 * Fonction qui gère les lignes verticales après un drag and drop
	 *
	 * @access	private
	 * @return	void
	 */
	_manageLine : function () {
		try {
			for (var i = 0; i < this.children.length; i++) {
				this.children[i].pos = i;
				// Si on est au dernier enfant et que celui-ci n'était pas le dernier avant le remove
				if (i == this.children.length - 1 && this.children[i].hasSiblingsAfter) {
					this.children[i].hasSiblingsAfter = false;
					this._manageMultiline(this.children[i].beforeIcon, 1, false);
					this._clearLine(this.children[i], this.level);
				}
				// Si on n'est pas au dernier enfant et que celui-ci était le dernier avant le remove
				if (i < this.children.length - 1 && !this.children[i].hasSiblingsAfter) {
					this.children[i].hasSiblingsAfter = true;
					this._manageMultiline(this.children[i].beforeIcon, 1, true);
					this._addLine(this.children[i], this.level);
				}
			}
			this.tree._changeStruct(this);
		} catch (err) {
			throw new Error ('_manageLine(base) : ' + err.message);
		}			
	},
	
	_manageLineForRoot : function (add) {
		for (var i = 0; i < this.children.length; i++) {
			this.children[i]._manageLineForRoot(add);
		}
		var td = this.table.getElementsByTagName('td')[0];
		var img = td.getElementsByTagName('img')[0];
		if (add) {
			img.src = this.tree.imgBase + this.tree.imgLine1;
		} else {
			img.src = this.tree.imgBase + this.tree.imgEmpty;
		}
	},
	
	/**
	 * Fonction qui supprime des lignes au bon endroit*
	 *
	 * @param	TafelTreeBranch		obj						La branche courante
	 * @param	integer				level					Le niveau où supprimer des lignes
	 * @param	boolean				ok						False pour le 1er niveau de branche
	 * @return	void
	 */
	_clearLine : function (obj, level, ok) {
		try {
			for (var i = 0; i < obj.children.length; i++) {
				this._clearLine(obj.children[i], level, true);
			}
			// On récupère la bonne TD et la bonne image
			var img = obj.table.getElementsByTagName('img')[level+1];
			if (ok) {
				img.src = this.tree.imgBase + this.tree.imgEmpty;
				if (this.tree.multiline) {
					this._manageMultiline(img.parentNode, 1, false);
				}
			} else {
				var old = obj.getImgBeforeIcon();
				switch (old.fullName.replace('_over', '')) {
					case this.tree.imgLine1 :
					case this.tree.imgLine3 : newImg = this.tree.imgLine2; break;
					case this.tree.imgPlus1 :
					case this.tree.imgPlus3 : newImg = this.tree.imgPlus2; break;
					case this.tree.imgMinus1:
					case this.tree.imgMinus3: newImg = this.tree.imgMinus2; break;
					default:
						newImg = obj.fullName;
				}
				img.src = this.tree.imgBase + newImg;
			}
		} catch (err) {
			throw new Error ('_clearLine(base) : ' + err.message);
		}			
	},
	
	/**
	 * Fonction qui ajoute des lignes au bon endroit*
	 *
	 * @param	TafelTreeBranch		obj						La branche courante
	 * @param	integer				level					Le niveau où ajouter des lignes
	 * @param	boolean				ok						False pour le 1er niveau de branche
	 * @return	void
	 */
	_addLine : function (obj, level, ok) {
		try {
			for (var i = 0; i < obj.children.length; i++) {
				this._addLine(obj.children[i], level, true);
			}
			// On récupère la bonne TD et la bonne image
			var img = obj.table.getElementsByTagName('img')[level+1];
			if (ok) {
				img.src = this.tree.imgBase + this.tree.imgLine1;
				if (this.tree.multiline) {
					this._manageMultiline(img.parentNode, 1, true);
				}
			} else {
				var old = obj.getImgBeforeIcon();
				switch (old.fullName.replace('_over', '')) {
					case this.tree.imgLine1 :
					case this.tree.imgLine2 : newImg = this.tree.imgLine3; break;
					case this.tree.imgPlus1 :
					case this.tree.imgPlus2 : newImg = this.tree.imgPlus3; break;
					case this.tree.imgMinus1:
					case this.tree.imgMinus2: newImg = this.tree.imgMinus3; break;
					default:
						newImg = obj.fullName;
				}
				img.src = this.tree.imgBase + newImg;
			}
		} catch (err) {
			throw new Error ('_addLine(base) : ' + err.message);
		}			
	},
	
	_isChild : function (child, parent) {
		try {
			if (parent.idObj == child.idObj) return true;
			if (child.parent) {
				return this._isChild(child.parent, parent);
			}
			return false;
		} catch (err) {
			throw new Error ('_isChild(base) : ' + err.message);
		}	
	},

	/**
	 * Set les propriétés utilisateur de la branche, ou celles par défaut
	 *
	 * @access	private
	 * @return	void
	 */
	_setProperties : function () {
		// Images
		if ((typeof(this.struct.img) == 'undefined')) {
			this.struct.img = (this.tree.icons[0]) ? this.tree.icons[0] : this.tree.imgLine0;
		}
		if ((typeof(this.struct.imgopen) == 'undefined')) {
			this.struct.imgopen = (this.tree.icons[1]) ? this.tree.icons[1] : this.struct.img;
		}
		if ((typeof(this.struct.imgclose) == 'undefined')) {
			this.struct.imgclose = (this.tree.icons[2]) ? this.tree.icons[2] : this.struct.img;
		}
		if ((typeof(this.struct.imgselected) == 'undefined')) {
			this.struct.imgselected = (this.tree.iconsSelected[0]) ? this.tree.iconsSelected[0] : null;
		}
		if ((typeof(this.struct.imgopenselected) == 'undefined')) {
			this.struct.imgopenselected = (this.tree.iconsSelected[1]) ? this.tree.iconsSelected[1] : null;
		}
		if ((typeof(this.struct.imgcloseselected) == 'undefined')) {
			this.struct.imgcloseselected = (this.tree.iconsSelected[2]) ? this.tree.iconsSelected[2] : null;
		}
		// Fonctions
		if (typeof(this.struct.open) == 'undefined') {
			this.struct.open = (this.tree.useCookie && this.tree.cookieOpened) ? false : this.tree.openAll;
		} else if (this.tree.useCookie && this.tree.cookieOpened) {
			this.struct.open = false;
		}
		
		if (typeof(this.struct.check) == 'undefined' || (this.tree.useCookie && this.tree.cookieChecked)) this.struct.check = 0;

		if (typeof(this.struct.items) == 'undefined') {
			this.struct.items = [];
		}
		if (typeof(this.struct.canhavechildren) == 'undefined') this.struct.canhavechildren = false;
		if (typeof(this.struct.id) == 'undefined') this.struct.id = this.idObj;
		if (typeof(this.struct.acceptdrop) == 'undefined') this.struct.acceptdrop = true;
		if (typeof(this.struct.last) == 'undefined') this.struct.last = false;
		if (typeof(this.struct.editable) == 'undefined') this.struct.editable = this.tree.editableBranches;
		if (typeof(this.struct.checkbox) == 'undefined') this.struct.checkbox = true;
	},

	/**
	 * Set les fonctions utilisateur et les fonctions par défaut, s'il y en a
	 *
	 * @access	private
	 * @return	void
	 */
	_setFunctions : function () {
		if (typeof(this.struct.ondragstarteffect) == 'undefined') {
			if (typeof(this.tree.onDragStartEffect) == 'function') {
				this.struct.ondragstarteffect = this.tree.onDragStartEffect;
				this.ondragstarteffectDefault = true;
			}
		} else {this.struct.ondragstarteffect = eval(this.struct.ondragstarteffect);}
		if (typeof(this.struct.ondragendeffect) == 'undefined') {
			if (typeof(this.tree.onDragEndEffect) == 'function') {
				this.struct.ondragendeffect = this.tree.onDragEndEffect;
				this.ondragendeffectDefault = true;
			}
		} else {this.struct.ondragendeffect = eval(this.struct.ondragendeffect);}
		if (typeof(this.struct.onerrorajax) == 'undefined') {
			if (typeof(this.tree.onErrorAjax) == 'function') {
				this.struct.onerrorajax = this.tree.onErrorAjax;
				this.onerrorajaxDefault = true;
			}
		} else {this.struct.onerrorajax = eval(this.struct.onerrorajax);}
		if (typeof(this.struct.oneditajax) == 'undefined') {
			if (this.tree.onEditAjax && typeof(this.tree.onEditAjax.func) == 'function') {
				this.struct.oneditajax = this.tree.onEditAjax.func;
				this.struct.editlink = this.tree.onEditAjax.link;
				this.oneditajaxDefault = true;
			}
		} else {this.struct.oneditajax = eval(this.struct.oneditajax);}
		if (typeof(this.struct.onopenpopulate) == 'undefined') {
			if (this.tree.onOpenPopulate && typeof(this.tree.onOpenPopulate.func) == 'function') {
				this.struct.onopenpopulate = this.tree.onOpenPopulate.func;
				this.struct.openlink = this.tree.onOpenPopulate.link;
				this.onopenpopulateDefault = true;
			}
		} else {this.struct.onopenpopulate = eval(this.struct.onopenpopulate);}
		if (typeof(this.struct.onedit) == 'undefined') {
			if (typeof(this.tree.onEdit) == 'function') {
				this.struct.onedit = this.tree.onEdit;
				this.oneditDefault = true;
			}
		} else {this.struct.onedit = eval(this.struct.onedit);}
		if (typeof(this.struct.oncheck) == 'undefined') {
			if (typeof(this.tree.onCheck) == 'function') {
				this.struct.oncheck = this.tree.onCheck;
				this.oncheckDefault = true;
			}
		} else {this.struct.oncheck = eval(this.struct.oncheck);}
		if (typeof(this.struct.onbeforecheck) == 'undefined') {
			if (typeof(this.tree.onBeforeCheck) == 'function') {
				this.struct.onbeforecheck = this.tree.onBeforeCheck;
				this.onbeforecheckDefault = true;
			}
		} else {this.struct.onbeforecheck = eval(this.struct.onbeforecheck);}
		if (typeof(this.struct.onopen) == 'undefined') {
			if (typeof(this.tree.onOpen) == 'function') {
				this.struct.onopen = this.tree.onOpen;
				this.onopenDefault = true;
			}
		} else {this.struct.onopen = eval(this.struct.onopen);}
		if (typeof(this.struct.onbeforeopen) == 'undefined') {
			if (typeof(this.tree.onBeforeOpen) == 'function') {
				this.struct.onbeforeopen = this.tree.onBeforeOpen;
				this.onbeforeopenDefault = true;
			}
		} else {this.struct.onbeforeopen = eval(this.struct.onbeforeopen);}
		if (typeof(this.struct.onmouseover) == 'undefined') {
			if (typeof(this.tree.onMouseOver) == 'function') {
				this.struct.onmouseover = this.tree.onMouseOver;
				this.onmouseoverDefault = true;
			}
		} else {this.struct.onmouseover = eval(this.struct.onmouseover);}
		if (typeof(this.struct.onmouseout) == 'undefined') {
			if (typeof(this.tree.onMouseOut) == 'function') {
				this.struct.onmouseout = this.tree.onMouseOut;
				this.onmouseoutDefault = true;
			}
		} else {this.struct.onmouseout = eval(this.struct.onmouseout);}
		if (typeof(this.struct.onmousedown) == 'undefined') {
			if (typeof(this.tree.onMouseDown) == 'function') {
				this.struct.onmousedown = this.tree.onMouseDown;
				this.onmousedownDefault = true;
			}
		} else {this.struct.onmousedown = eval(this.struct.onmousedown);}
		if (typeof(this.struct.onmouseup) == 'undefined') {
			if (typeof(this.tree.onMouseUp) == 'function') {
				this.struct.onmouseup = this.tree.onMouseUp;
				this.onmouseupDefault = true;
			}
		} else {this.struct.onmouseup = eval(this.struct.onmouseup);}
		if (typeof(this.struct.onclick) == 'undefined') {
			if (typeof(this.tree.onClick) == 'function') {
				this.struct.onclick = this.tree.onClick;
				this.onclickDefault = true;
			}
		} else {this.struct.onclick = eval(this.struct.onclick);}
		if (typeof(this.struct.ondblclick) == 'undefined') {
			if (typeof(this.tree.onDblClick) == 'function') {
				this.struct.ondblclick = this.tree.onDblClick;
				this.ondblclickDefault = true;
			}
		} else {this.struct.ondblclick = eval(this.struct.ondblclick);}
	},
	
	/**
	 * Fonction qui set les divers événements en fonction des données utilisateur
	 *
	 * @access	private
	 * @param	HTMLTdElement		event					La cellule qui contient le texte
	 * @param	HTMLTdElement		tdImg					La cellule qui contient l'icône
	 * @return	void
	 */
	_setEvents : function (event, tdImg) {
		// Le onclick se fait de toutes façon
		Event.observe(this.txt, 'mousedown', this.setMouseDown.bindAsEventListener(this), false);
		Event.observe(this.txt, 'mouseup', this.setMouseUp.bindAsEventListener(this), false);
		// On set les événements
		if (typeof(this.struct.onclick) == 'function') {
			Event.observe(event, 'click', this.setClick.bindAsEventListener(this), false);
		}
        //if (typeof(this.struct.ondblclick) == 'function' || this.struct.editable) {
        //  Event.observe(event, 'dblclick', this.setDblClick.bindAsEventListener(this), false);
        //}
		if (typeof(this.struct.onmouseover) == 'function') {
			Event.observe(event, 'mouseover', this.setMouseOver.bindAsEventListener(this), false);
		}
		if (typeof(this.struct.onmouseout) == 'function') {
			Event.observe(event, 'mouseout', this.setMouseOut.bindAsEventListener(this), false);
		}
		if (this.struct.editable && (typeof(this.struct.onedit) == 'function' || typeof(this.struct.oneditajax) == 'function')) {
			this.editableInput = document.createElement('input');
			this.editableInput.setAttribute('type', 'text');
			this.editableInput.setAttribute('autocomplete', 'off');
			this.editableInput.className = this.tree.classEditable;
			event.appendChild(this.editableInput);
            /*
			Event.observe(this.editableInput, 'blur', this.hideEditable.bindAsEventListener(this), false);
			Event.observe(this.editableInput, 'keypress', function( kEvt ) {
				if( kEvt.keyCode == 13 ) {
					this.hideEditable(kEvt);
				}
			}.bindAsEventListener(this), false);
            */
		}
		// On set l'option drag and drop
		if (!this.isRoot) {
			if (this.struct.draggable && (typeof(this.struct.ondrop) == 'function' || typeof(this.struct.ondropajax) == 'function')) {
				//this.objDrag = new Draggable(this.txt, {revert: this.tree.dragRevert, scroll: this.tree.div, ghosting: this.tree.dragGhosting});
				this.objDrag = new Draggable(this.txt, {
					revert: this.tree.dragRevert,
					starteffect:this.ondragstarteffect.bindAsEventListener(this),
					endeffect:this.ondragendeffect.bindAsEventListener(this)
				}); 
				Element.addClassName(this.txt, this.tree.classDrag);
			}
		}
		if (this.struct.acceptdrop) {
			Droppables.add(this.txt, {hoverclass: this.tree.classDragOver, onDrop: this.setDrop.bindAsEventListener(this)});
		}
		if (this.struct.tooltip) {
			Event.observe(event, 'mouseover', this.evt_showTooltip.bindAsEventListener(this), false);
			Event.observe(event, 'mouseout', this.evt_hideTooltip.bindAsEventListener(this), false);
		}
		// On s'occupe des checkboxes, le cas échéant
		if (this.tree.checkboxes && this.struct.checkbox) {
			if (this.struct.check == 1) imgc = this.tree.imgCheck2;
			else if (this.struct.check == -1) imgc = this.tree.imgCheck3;
			else imgc = this.tree.imgCheck1;
			this.checkbox = document.createElement('img');
			this.checkbox.src = this.tree.imgBase + imgc;
			tdImg.appendChild(this.checkbox);
			Event.observe(this.checkbox, 'click', this.checkOnClick.bindAsEventListener(this), false);
			Event.observe(this.checkbox, 'mouseover', this.evt_openMouseOver.bindAsEventListener(this), false);
			Event.observe(this.checkbox, 'mouseout', this.evt_openMouseOut.bindAsEventListener(this), false);
		} else if (this.tree.checkboxes) {
			// On met éventuellement une image vide au lieu de la checkbox
			var vide = document.createElement('img');
			vide.src = this.tree.imgBase + this.tree.imgEmpty;
			tdImg.appendChild(vide);
		}
	},
	
	_getImgBeforeIcon : function () {
		try {
			var td = document.createElement('td');
			var img = document.createElement('img');
			Element.addClassName(img, this.tree.classOpenable);
			// On détermine s'il y a des frères
			if (this.hasSiblingsAfter) {
				// On détermine s'il y a des enfants
				if (!this.hasChildren()) {
					if (this.isRoot) {
						img.src = this.tree.imgBase + ((this.hasSiblingsBefore) ? this.tree.imgLine3 : this.tree.imgLine4);
					} else {
						img.src = this.tree.imgBase + this.tree.imgLine3;
					}
				} else {
					Event.observe(img, 'click', this.setOpen.bindAsEventListener(this), false);
					Event.observe(img, 'mouseover', this.evt_openMouseOver.bindAsEventListener(this), false);
					Event.observe(img, 'mouseout', this.evt_openMouseOut.bindAsEventListener(this), false);
					if (this.isRoot) {
						img.src = this.tree.imgBase + ((this.hasSiblingsBefore) ? this.tree.imgMinus3 : this.tree.imgMinus4);
					} else {
						img.src = this.tree.imgBase + this.tree.imgMinus3;
					}
				}
				if (this.tree.multiline) {
					this._manageMultiline(td, (this.isRoot ? 2 : 1), true);
				}
			} else {
				// On détermine s'il y a des enfants
				if (!this.hasChildren()) {
					if (this.isRoot) {
						img.src = this.tree.imgBase + ((this.hasSiblingsBefore) ? this.tree.imgLine2 : this.tree.imgEmpty);
					} else {
						img.src = this.tree.imgBase + this.tree.imgLine2;
					}
				} else {
					Event.observe(img, 'click', this.setOpen.bindAsEventListener(this), false);
					Event.observe(img, 'mouseover', this.evt_openMouseOver.bindAsEventListener(this), false);
					Event.observe(img, 'mouseout', this.evt_openMouseOut.bindAsEventListener(this), false);
					if (this.isRoot) {
						img.src = this.tree.imgBase + ((this.hasSiblingsBefore) ? this.tree.imgMinus2 : this.tree.imgMinus5);
					} else {
						img.src = this.tree.imgBase + this.tree.imgMinus2;
					}
				}
			}
			td.appendChild(img);
			return td;
		} catch (err) {
			throw new Error ('_getImgBeforeIcon(base) : ' + err.message);
		}			
	},

	/**
	 * Insère les enfants de la branche
	 *
	 * @access	private
	 * @param	TafelTreeRoot	root					L'élément racine parent
	 * @return	void
	 */
	_setChildren : function (root) {
		if (this.hasChildren()) {
			if (this.tree.bigTreeLoading >= 0) {
				this.loaded = false;
				this.bigTreeLoading = 0;
				setTimeout(this._generate.bind(this), 10);
			} else {
				for (var i = 0; i < this.struct.items.length; i++) {
					if (this.tree.checkboxesThreeState && this.struct.check && typeof(this.struct.items[i].check) == 'undefined') {
						this.struct.items[i].check = 1;
					}
					isNotFirst = (i > 0) ? true : false;
					isNotLast = (i < this.struct.items.length - 1) ? true : false;
					this.children[i] = new TafelTreeBranch(root, this, this.struct.items[i], this.level + 1, isNotFirst, isNotLast, i);
					this.obj.appendChild(this.children[i].obj);
				}
				this.openIt(this.struct.open);
			}
		}
	},
	
	/**
	 * Set l'image de la branche à wait ainsi que ses enfants
	 *
	 * @access	private
	 * @param	TafelTreeBranch		branch					La branche courante
	 * @param	boolean				wait					True pour afficher l'image d'attente
	 * @param	boolean				localPropagationStop	True pour ne pas avoir de propagation, false par défaut
	 * @return	void
	 */
	_setWaitImg : function (branch, wait, localPropagationStop) {
		try {
			this.inProcess = wait;
			if (wait) {
				branch.oldImgSrc = branch.img.src;
				branch.img.src = branch.tree.imgBase + branch.tree.imgWait;
				branch.eventable = false;
			} else {
				branch.eventable = true;
				branch.img.src = branch.oldImgSrc;
			}
			if (this.tree.propagation && !localPropagationStop) {
				for (var i = 0; i < branch.children.length; i++) {
					this._setWaitImg(branch.children[i], wait);
				}
			}
		} catch (err) {
			throw new Error ('_setWaitImg(base) : ' + err.message);
		}
	},
	
	/**
	 * Envoi d'une requête Ajax suite à une ouverture de branche
	 *
	 * @access	private
	 * @return	void
	 */
	_openPopulate : function (ev) {
		try {
			this._setWaitImg(this, true);
			var params = 'branch=' + this.serialize() + '&branch_id=' + this.getId() + '&tree_id=' + this.tree.id;
			var otherParams = this.tree.getURLParams(this.struct.openlink);
			for (var i = 0; i < otherParams.length; i++) {
				params += '&' + otherParams[i].name + '=' + otherParams[i].value;
			}
			new Ajax.Updater (
				this.tree.ajaxObj,
				this.struct.openlink,
				{
					'method'     : 'post',
					'parameters' : params,
					'evalScripts': true,
					'onComplete' : function(event){this._completeOpenPopulate(event);}.bind(this),
					'onFailure' : function(event){this._failureOpenPopulate(event);}.bind(this)
				}
			);
		} catch (err) {
			this._setWaitImg(this, false);
			throw ('_openPopulate(base) : ' + err.message);
		}
	},
	
	_failureOpenPopulate : function () {
		this._setWaitImg(this, false);
		if (typeof(this.struct.onerrorajax) == 'function') {
			this.struct.onerrorajax('open', 'failure request', this);
		}
	},
	
	/**
	 * Méthode appelée lorsque le retour ajax est effectué
	 *
	 * Pour pailler aux éventualités : str.match(/(?:\(\[)((\n|\r|.)*?)(?:\]\))/)[0]
	 *
	 * @access	private
	 * @param	XMLhttpResquest		response				L'objet Ajax
	 * @return	void
	 */
	_completeOpenPopulate : function (response) {
		try {
			this._setWaitImg(this, false);
			var rep = this.struct.onopenpopulate(this, response.responseText);
			if (rep) {
				rep = (rep === true) ? response.responseText : rep;
				var items = eval(rep);
				if (items) {
					var ok = [];
					for (var i = 0 ; i < items.length; i++) {
                        // unicity test
                        if (this.tree.getBranchById(items[i].id)) continue; 
						if (typeof(items[i].id) == 'undefined' || typeof(items[i].txt) == 'undefined') {
							throw new Error (TAFELTREE_WRONG_BRANCH_STRUCTURE);
						}
						ok.push(this.insertIntoLast(items[i]));
					}
					// Permet d'ouvrir les branches qui viennent du serveur au load de la page
					if (this.tree.useCookie && this.tree.cookieOpened && this.tree.reopenFromServer) {
						var okay = false;
						for (var o = 0; o < ok.length; o++) {
							okay = false;
							for (var i = 0; i < this.tree.cookieOpened.length; i++) {
								if (this.tree.cookieOpened[i] == ok[o].getId()) {
									okay = true;
									break;
								}
							}
							if (okay) {
								if (typeof(ok[o].struct.onopenpopulate) == 'function' && ok[o].eventable) {
									ok[o]._openPopulate();
									ok[o].openIt(true);
								}
							}
						}
					}
				}
			}
		} catch (err) {
			this._setWaitImg(this, false);
			if (typeof(this.struct.onerrorajax) == 'function') {
				this.struct.onerrorajax('open', response.responseText, this);
			} else {
				alert ('_completeOpenPopulate(' + response.responseText + ') : ' + err.message);
			}
		}
	},
	
	/**
	 * Envoi d'une requête Ajax suite à un drop
	 *
	 * @access	private
	 * @param	TafelTreeBranch		newParentObj			Le nouveau parent
	 * @param	boolean				asSibling				True pour dropper l'élément comme frère
	 * @param	boolean				copydrag				True si on fait un copy-drag
	 * @return	void
	 */
	_setDropAjax : function (newParentObj, asSibling, copydrag, ev) {
		try {
			this._setWaitImg(this, true);
			var sibling = (asSibling) ? 1 : 0;
			var cdrag = (copydrag) ? 1 : 0;
			var params = 'drag=' + this.serialize() + '&drag_id=' + this.getId() + '&drop=' + newParentObj.serialize() + '&drop_id=' + newParentObj.getId();
			params += '&treedrag_id=' + this.tree.id + '&treedrop_id=' + newParentObj.tree.id + '&sibling=' + sibling + '&copydrag=' + cdrag;
			// On passe le futur id de l'élément copié s'il s'agit d'une copie
			if (cdrag) {
				var cdragId = this.id + this.tree.copyNameBreak + this.tree.idTree;
				params += '&copydrag_id=' + cdragId;
			}
			var otherParams = this.tree.getURLParams(this.struct.droplink);
			for (var i = 0; i < otherParams.length; i++) {
				params += '&' + otherParams[i].name + '=' + otherParams[i].value;
			}
			this.newParent = newParentObj;
			this.asSibling = asSibling;
			this.copyDrag = cdrag;
			new Ajax.Updater (
				this.tree.ajaxObj,
				this.struct.droplink,
				{
					'method'     : 'post',
					'parameters' : params,
					'evalScripts': true,
					'onComplete' : function(event){this._completeDropAjax(event);}.bind(this),
					'onFailure' : function(event){this._failureDropAjax(event);}.bind(this)
				}
			);
		} catch (err) {
			this._setWaitImg(this, false);
			throw ('_setDropAjax(base) : ' + err.message);
		}
	},
	
	_failureDropAjax : function () {
		this._setWaitImg(this, false);
		if (typeof(this.struct.onerrorajax) == 'function') {
			this.struct.onerrorajax('drop', 'failure request', this, this.newParent);
		}
	},
	
	/**
	 * Méthode appelée lorsque le retour ajax est effectué
	 *
	 * @access	private
	 * @param	XMLhttpResquest		response				L'objet Ajax
	 * @return	void
	 */
	_completeDropAjax : function (response) {
		try {
			if (this.struct.ondropajax(this, this.newParent, response.responseText, false, null)) {
				var newBranch = null;
				if (!this.asSibling) {
					if (!this.copyDrag) {
						this.move(this.newParent);
					} else {
						newBranch = this.newParent.insertIntoLast(this.clone());
					}
				} else {
					if (!this.copyDrag) {
						this.moveBefore(this.newParent);
					} else {
						newBranch = this.newParent.insertBefore(this.clone());
					}
				}
				this.struct.ondropajax(this, this.newParent, response.responseText, true, newBranch);
			}
			this._setWaitImg(this, false);
		} catch (err) {
			if (typeof(this.struct.onerrorajax) == 'function') {
				this.struct.onerrorajax('drop', response.responseText, this, this.newParent);
			} else {
				alert ('_completeDropAjax(base) : ' + err.message);
			}
		}
	},
	
	/**
	 * Envoi d'une requête Ajax suite à une édition de branche
	 *
	 * @access	private
	 * @return	void
	 */
	_editAjax : function (newValue, oldValue, ev) {
		try {
			this._setWaitImg(this, true, true);
			var params = 'branch=' + this.serialize() + '&branch_id=' + this.getId() + '&tree_id=' + this.tree.id;
			params += '&new_value=' + newValue + '&old_value=' + oldValue;
			var otherParams = this.tree.getURLParams(this.struct.editlink);
			for (var i = 0; i < otherParams.length; i++) {
				params += '&' + otherParams[i].name + '=' + otherParams[i].value;
			}
			new Ajax.Updater (
				this.tree.ajaxObj,
				this.struct.editlink,
				{
					'method'     : 'post',
					'parameters' : params,
					'evalScripts': true,
					'onComplete' : function(event){this._completeEditAjax(event);}.bind(this),
					'onFailure' : function(event){this._failureEditAjax(event);}.bind(this)
				}
			);
		} catch (err) {
			this._setWaitImg(this, false, true);
			throw ('_editAjax(base) : ' + err.message);
		}
	},
	
	_failureEditAjax : function () {
		this._setWaitImg(this, false);
		if (typeof(this.struct.onerrorajax) == 'function') {
			this.struct.onerrorajax('edit', 'failure request', this);
		}
	},
	
	/**
	 * Méthode appelée lorsque le retour ajax est effectué
	 *
	 * @access	private
	 * @param	XMLhttpResquest		response				L'objet Ajax
	 * @return	void
	 */
	_completeEditAjax : function (response) {
		try {
			this._setWaitImg(this, false, true);
			var rep = this.struct.oneditajax(this, response.responseText, this.txt.innerHTML);
			if (rep) {
				this.setText((rep === true ? response.responseText : rep));
			}
			this.hideEditableElement();
		} catch (err) {
			this._setWaitImg(this, false, true);
			if (typeof(this.struct.onerrorajax) == 'function') {
				this.struct.onerrorajax('edit', response.responseText, this);
			} else {
				alert ('_completeOpenPopulate(' + response.responseText + ') : ' + err.message);
			}
		}
	},
	
	
	/**
	 *------------------------------------------------------------------------------
	 *							TafelTreeBaseBranch Events Management
	 *------------------------------------------------------------------------------
	 */
	
	evt_openMouseOver : function (ev) {
		if (Event.element) {
			var obj = Event.element(ev);
			var img = this._getImgInfo(obj);
			obj.src = this.tree.imgBase + img.type + '_over' + img.number + '.gif';
		}
	},
	
	evt_openMouseOut : function (ev) {
		if (Event.element) {
			var obj = Event.element(ev);
			var img = this._getImgInfo(obj);
			obj.src = this.tree.imgBase + img.type.replace(/_over/g, '') + img.number + '.gif';
		}
	},
	
	evt_showTooltip : function (ev) {
		this.displayTooltip = true;
		setTimeout(this.showTooltip.bind(this), this.tree.durationTooltipShow);
	},
	
	evt_hideTooltip : function (ev) {
		this.displayTooltip = false;
		setTimeout(this.hideTooltip.bind(this), this.tree.durationTooltipHide);
	},
	
	/**
	 * Méthode appelée lorsque la souris passe sur le noeud
	 *
	 * @access	public
	 * @param	Element				ev						L'élément déclencheur
	 * @return	boolean										True si le changement s'est fait, false sinon
	 */
	setMouseOver : function (ev) {
		if (typeof(this.struct.onmouseover) == 'function') {
			return this.struct.onmouseover(this, ev);
		}
	},
	
	/**
	 * Méthode appelée lorsqu'on clic sur le noeud
	 *
	 * @access	public
	 * @param	Element				ev						L'élément déclencheur
	 * @return	boolean										True si le changement s'est fait, false sinon
	 */
	setMouseOut : function (ev) {
		if (typeof(this.struct.onmouseout) == 'function') {
			return this.struct.onmouseout(this, ev);
		}
	},
	
	/**
	 * Méthode appelée lorsqu'on clique sur le noeud (mousedown)
	 *
	 * @access	public
	 * @param	Element				ev						L'élément déclencheur
	 * @return	void
	 */
	setMouseDown : function (ev) {
		// Pour une raison ou une autre, le mousedown du div principal n'est pas appelé...
		this.tree.evt_setAsCurrent(ev);
		if (this.tree.selectedBranchShowed) {
			if (!this.isSelected()) {
				this.select(ev);
				this.okayForUnselect = false;
			} else {
				this.okayForUnselect = true;
			}
		}
		if (this.tooltip) {
			this.displayTooltip = false;
			this.hideTooltip();
		}
		if (typeof(this.struct.onmousedown) == 'function') {
			this.struct.onmousedown(this, ev);
		}
	},
	
	/**
	 * Méthode appelée lorsqu'on "déclique"
	 *
	 * @access	public
	 * @param	Element				ev						L'élément déclencheur
	 * @return	void
	 */
	setMouseUp : function (ev) {
		if (this.tree.lastEdited) {
			this.tree.lastEdited.hideEditable(ev);
		}
		// Si la branche est déjà sélectionnée, on la déselectionne
		if (this.isSelected() && this.okayForUnselect) {
			//this.unselect();
			return true;
		}
		this.okayForUnselect = true;
		if (typeof(this.struct.onmouseup) == 'function') {
			this.struct.onmouseup(this, ev);
		}
	},
	
	/**
	 * Méthode appelée lorsqu'on clique sur le noeud
	 *
	 * @access	public
	 * @param	Element				ev						L'élément déclencheur
	 * @return	void
	 */
	setClick : function (ev) {
		if (this.tree.lastEdited) return false;
		if (typeof(this.struct.onclick) == 'function') {
			return this.struct.onclick(this, ev);
		}
	},
	
	/**
	 * Fonction appelée lorsqu'on clique sur une checkbox
	 *
	 * @access	public
	 * @param	HTMLimgElement		ev						L'élément déclencheur
	 * @return	boolean										True si le changement s'est fait, false sinon
	 */
	checkOnClick : function (ev) {
		if (this.tree.checkboxes && this.checkbox) {
			var checked = (this.isChecked() > 0) ? 0 : 1;
			var ok = true;
			if (typeof(this.struct.onbeforecheck) == 'function') {
				ok = this.struct.onbeforecheck(this, checked, ev);
			}
			if (ok) {
				this.check(checked);
				if (this.tree.checkboxesThreeState) {
					this._manageCheckThreeState(this, checked);
					this._adjustParentCheck();
				}
				if (typeof(this.struct.oncheck) == 'function') {
					this.struct.oncheck(this, checked, ev);
				}
			}
		}
	},
	
	/**
	 * Méthode appelée lors de l'ouverture ou fermeture d'un noeud
	 *
	 * @access	public
	 * @param	HTMLimgElement		ev						L'élément déclencheur
	 * @return	boolean										True si le changement s'est fait, false sinon
	 */
	setOpen : function (ev) {
		if (!this.hasChildren()) return false;
		var ok = true;
		if (typeof(this.struct.onbeforeopen) == 'function') {
			ok = this.struct.onbeforeopen(this, this.struct.open, ev);
		}
		if (!ok) return false;
		// On ne peut pas fermer la branche si elle subit un événement d'ouverture
		if (typeof(this.struct.onopenpopulate) == 'function' && !this.eventable) return false;
		this.openIt((this.isOpened()) ? false : true);
		if (typeof(this.struct.onopen) == 'function') {
			return this.struct.onopen(this, this.struct.open, ev);
		} else if (typeof(this.struct.onopenpopulate) == 'function' && this.isOpened() && this.children.length == 0) {
			if (!this.eventable) return false;
			return this._openPopulate(ev);
		}
		return true;
	},
	
	/**
	 * Ajoute une fonction utilisateur au start du drag
	 *
	 * @access public
	 * @author coucoudom
	 * @param HTMLObject drag l'objet draggé
	 * @param HTMLObject dragbis
	 */
	ondragstarteffect : function (drag, dragbis) {
		var dragObj = this.tree.getBranchByIdObj(drag.id);
		if (!dragObj) {
			for (var i = 0; i < this.tree.otherTrees.length; i++) {
				dragObj = this.tree.otherTrees[i].getBranchByIdObj(drag.id);
				if (dragObj) break;
			}
			if (!dragObj) return false;
		}
		// appel de la function user
		if (typeof(dragObj.struct.ondragstarteffect) == 'function') {
			var ok = dragObj.struct.ondragstarteffect(dragObj);
		}
	},
	
	/**
	 * Ajoute une fonction utilisateur au end du drag
	 *
	 * @access public
	 * @author coucoudom
	 * @param HTMLObject drag l'objet draggé
	 * @param HTMLObject dragbis
	 */
	ondragendeffect : function (drag, dragbis) {
		var dragObj = this.tree.getBranchByIdObj(drag.id);
		if (!dragObj) {
			for (var i = 0; i < this.tree.otherTrees.length; i++) {
				dragObj = this.tree.otherTrees[i].getBranchByIdObj(drag.id);
				if (dragObj) break;
			}
			if (!dragObj) return false;
		}
		// appel de la function user
		if (typeof(dragObj.struct.ondragendeffect) == 'function') {
			var ok = dragObj.struct.ondragendeffect(dragObj);
		}
	},
	
	/**
	 * Méthode appelée lorsqu'on drop sur le noeud
	 *
	 * Ici, le this correspond à l'objet qui réceptionne le drag
	 *
	 * @access	public
	 * @param	HTMLElement			drag					L'élément draggué
	 * @return	boolean										True si le changement s'est fait, false sinon
	 */
	setDrop : function (drag, html, html2, ev) {
		var dragObj = this.tree.getBranchByIdObj(drag.id);
		// Si l'objet n'est pas dans l'arbre courant, on va chercher dans les autres liés
		if (!dragObj) {
			for (var i = 0; i < this.tree.otherTrees.length; i++) {
				dragObj = this.tree.otherTrees[i].getBranchByIdObj(drag.id);
				if (dragObj) break;
			}
			if (!dragObj) return false;
		}
		var alt = (dragObj.tree.dropALT) ? TafelTreeManager.altOn(ev) : false;
		var ctrl = (dragObj.tree.dropCTRL) ? TafelTreeManager.ctrlOn(ev) || TafelTreeManager.metaOn(ev) : false;
		var ok = true;
		if ((this.tree.id == dragObj.tree.id && this.isChild(dragObj)) || !dragObj.eventable || !this.eventable) return false;
		// Fonction utilisateur avant le drop
		if (typeof(dragObj.struct.ondrop) == 'function') {
			ok = dragObj.struct.ondrop(dragObj, this, false, null, ev);
		}
		if (ok) {
			var asSibling = ((dragObj.tree.behaviourDrop == 1 || dragObj.tree.behaviourDrop == 3) && !alt || (dragObj.tree.behaviourDrop == 0 || dragObj.tree.behaviourDrop == 2) && alt) ? true : false;
			var copyDrag = ((dragObj.tree.behaviourDrop == 2 || dragObj.tree.behaviourDrop == 3) && !ctrl || (dragObj.tree.behaviourDrop == 0 || dragObj.tree.behaviourDrop == 1) && ctrl) ? true : false;
			// On va chercher les noeuds sur le serveur s'il y en a
			if (!asSibling && typeof(this.struct.onopenpopulate) == 'function' && !this.isOpened() && this.children.length == 0) {
				this._openPopulate(ev);
			}
			if (typeof(dragObj.struct.ondropajax) == 'function') {
				dragObj._setDropAjax(this, asSibling, copyDrag, ev);
			} else {
				// Drop normal
				var newBranch = null;
				if (!asSibling) {
					if (!copyDrag) {
						dragObj.move(this);
					} else {
						newBranch = this.insertIntoLast(dragObj.clone());
					}
				} else {
					if (!copyDrag) {
						dragObj.moveBefore(this);
					} else {
						newBranch = this.insertBefore(dragObj.clone());
					}
				}
				// Fonction utilisateur après le drop
				if (typeof(dragObj.struct.ondrop) == 'function') {
					ok = dragObj.struct.ondrop(dragObj, this, true, newBranch, ev);
				}				
			}
		}
	},
	
	/**
	 * Méthode appelée lorsqu'on double-clic sur le noeud
	 *
	 * @access	public
	 * @param	Element				ev						L'élément déclencheur
	 * @return	boolean										True si le changement s'est fait, false sinon
	 */
	setDblClick : function (ev) {
		if (this.tree.lastEdited) return false;
		if (typeof(this.struct.ondblclick) == 'function') {
			this.struct.ondblclick(this, ev);
		}
		if (this.struct.editable && this.editableInput) {
			if (!this.tree.lastEdited || this.tree.lastEdited.getId() != this.getId()) {
                this.editableInput.style.width = (this.txt.offsetWidth + 60) + 'px';
			}
			Element.hide(this.txt);
			this.editableInput.value = this.txt.innerHTML;
			this.editableInput.style.display = 'block';
			this.editableInput.focus();
			this.tree.lastEdited = this;
		}
	},
	
    //webmail tree
    setDblClick2 : function (ev) {
        if (typeof(this.struct.ondblclick) == 'function') {
            this.struct.ondblclick(this, ev);
        }
        if (this.struct.editable && this.editableInput) {
            if (!this.tree.lastEdited || this.tree.lastEdited.getId() != this.getId()) {
                this.editableInput.style.width = (this.txt.offsetWidth + 60) + 'px';
            }
            Element.hide(this.txt);
            this.editableInput.value = this.txt.innerHTML;
            this.editableInput.style.display = 'block';
            this.editableInput.focus();
        }
    },

	/**
	 * Enlève l'édition de la branche
	 *
	 * @access	public
	 * @param	Event			ev						L'événement déclencheur
	 * @return	boolean									True si l'élément a été caché, false sinon
	 */
	hideEditable : function (ev) {
        /*
		if (this.editableInput && this.struct.editable) {
			var obj = this.editableInput;
			var value = obj.value;
			if (this.struct.oneditajax) {
				if (!this.eventable) return false;
				this._editAjax(obj.value, this.txt.innerHTML, ev);
			} else {
				if (typeof(this.struct.onedit) == 'function') {
					value = this.struct.onedit(this, obj.value, this.txt.innerHTML, ev);
				}
				this.setText(value);
				this.hideEditableElement();
			}
			return true;
		}
		return false;
        */
	},
	
	hideEditableElement : function () {
		Element.hide(this.editableInput);
		this.editableInput.value = this.getText();
		this.txt.style.display = 'block';
		this.tree.lastEdited = null;
	}
};


















































/**
 *------------------------------------------------------------------------------
 *							TafelTreeRoot Class
 *------------------------------------------------------------------------------
 */

var TafelTreeRoot = Class.create();

TafelTreeRoot.prototype = Object.extend(new TafelTreeBaseBranch, {
	/**
	 * Constructeur d'un élément racine
	 *
	 * @access	public
	 * @param	TafelTree			tree					L'objet TafelTree courant
	 * @param	object				struct					Les infos concernant la racine est ses enfants
	 * @param	integer				level					Le niveau du noeud (0 pour la racine)
	 * @param	boolean				before					True s'il y a des noeuds avant
	 * @param	boolean				after					True s'il y a des noeuds après
	 */
	initialize : function (tree, struct, level, before, after, pos) {
		this.isRoot = true;
		this.tree = tree;
		this.pos = pos;
		this.level = level;
		this.struct = struct;
		this.tree.idTree++;
		this.idObj = this.tree.idTreeBranch + this.tree.idTree;
		this.hasSiblingsBefore = before;
		this.hasSiblingsAfter = after;
		this.eventable = true;
		this.loaded = true;
		this.children = [];
		this.copiedTimes = 0;

		this._setProperties();
		this._setFunctions();
		
		this.obj = this._addRoot();
		this.content = this._addContent();
		this.obj.appendChild(this.table);
		this._setChildren(this);
	},

	/**
	 * Méthode qui insère une branche avant celle courante
	 *
	 * @access	public
	 * @param	object				item					Un objet au format TafelTreeBranch
	 * @return	TafelTreeRoot								La nouvelle racine insérée
	 */
	insertBefore : function (item) {
		if (this.parent) return false;
		var pos = this.pos;
		var posBefore = pos + 1;
		var isNotFirst = (pos == 0) ? false : true;
		this._movePartStructRoot(pos);
		this.tree.roots[pos] = new TafelTreeRoot(this.tree, item, this.level, isNotFirst, true, pos);
		this.tree.div.insertBefore(this.tree.roots[pos].obj, this.obj);
		this._manageAfterRootInsert(pos);
		return this.tree.roots[pos];
	},

	/**
	 * Méthode qui insère une branche après celle courante
	 *
	 * @access	public
	 * @param	object				item					Un objet au format TafelTreeBranch
	 * @return	TafelTreeRoot								La nouvelle racine insérée
	 */
	insertAfter : function (item) {
		if (this.parent) return false;
		var pos = this.pos + 1;
		var posBefore = pos + 1;
		var isNotLast = (pos == this.tree.roots.length) ? false : true;
		this._movePartStructRoot(pos);
		this.tree.roots[pos] = new TafelTreeRoot(this.tree, item, this.level, true, isNotLast, pos);
		try {
			this.tree.div.insertBefore(this.tree.roots[pos].obj, this.tree.roots[posBefore].obj);
		} catch (err) {
			this.tree.div.appendChild(this.tree.roots[pos].obj);
		}
		this._manageAfterRootInsert(pos);
		return this.tree.roots[pos];
	},


	/**
	 *------------------------------------------------------------------------------
	 *							TafelTreeRoot private methods
	 *------------------------------------------------------------------------------
	 */
	
	_manageAfterRootInsert : function (pos) {
		for (var i = 0; i < this.tree.roots.length; i++) {
			if (i < this.tree.roots.length - 1) {
				this.tree.roots[i].hasSiblingsAfter = true;
			}
			if (i > 0) {
				this.tree.roots[i].hasSiblingsBefore = true;
			}
		}
		for (var i = 0; i < this.children.length; i++) {
			this.children[i]._manageLineForRoot(this.hasSiblingsAfter);
		}
	},
	
	_movePartStructRoot : function (pos) {
		var nb = this.tree.roots.length - 1;
		var newPos = 0;
		for (var i = nb; i >= pos; i--) {
			newPos = i + 1;
			this.tree.roots[newPos] = this.tree.roots[i];
			this.tree.roots[newPos].pos = newPos;
		}
	},
	
	/**
	 * Méthode pour ajouter l'élément principal
	 *
	 * @access	private
	 * @return	HTMLDivElement								L'élément DIV créé
	 */
	_addRoot : function () {
		var div = document.createElement('div');
		div.className = this.tree.classTreeRoot;
		return div;
	},
	
	/**
	 * Méthode pour ajouter le contenu de l'élément (images + textes)
	 *
	 * Créé une structure comme suit :
	 *	<table><tbody><tr>
	 *		<td><img /></td>
	 *		<td><![-- CDATA --]></td>
	 *	</tr></tbody></table>
	 *
	 * @access	private
	 * @return	HTMLTbodyElement							L'élément TBODY créé
	 */
	_addContent : function () {
		var table = document.createElement('table');
		var tbody = document.createElement('tbody');
		var tr = document.createElement('tr');
		var tdImg = document.createElement('td');
		var tdTxt = document.createElement('td');
		var img = document.createElement('img');
		var span = document.createElement('div');
		var txt = document.createTextNode(txt);
		img.src = this.tree.imgBase + this.struct.img;
		span.innerHTML = this.struct.txt;
		// Ajout du title (de NoisetteProd)
		if (this.struct.title) {
			span.setAttribute('title', this.struct.title);
		}
		// Fin ajout du title
		span.setAttribute('id', this.idObj);
		Element.addClassName(span, this.tree.classContent);
		Element.addClassName(tdTxt, this.tree.classCanevas);
		tdTxt.appendChild(span);
		tdImg.appendChild(img);
		// Insertion du tooltip, s'il existe
		if (this.struct.tooltip) {
			this.tooltip = this._createTooltip();
			tdTxt.appendChild(this.tooltip);
		}
		// Insertion de l'image avant l'icône
		this.tdImg = tdImg;
		this.beforeIcon = this._getImgBeforeIcon();
		tr.appendChild(this.beforeIcon);
		tr.appendChild(tdImg);
		tr.appendChild(tdTxt);
		tbody.appendChild(tr);
		table.appendChild(tbody);
		if (this.tree.multiline) {
			tdTxt.style.whiteSpace = 'normal';
			if (this.hasChildren()) this._manageMultiline(this.tdImg, 2, true);
		}
		if (this.struct.style) {
			Element.addClassName(tdTxt, this.struct.style);
		}
		this.txt = span;
		this.img = img;
		this.table = table;
		this._setEvents(tdTxt, tdImg);
		return tbody;
	}
});












































/**
 *------------------------------------------------------------------------------
 *							TafelTreeBranch Class
 *------------------------------------------------------------------------------
 */

/**
Properties description

this.tree     : l'arbre de la branche
this.root     : la racine de la branche
this.parent   : le parent de la branche (peut être this.root)
this.level    : le niveau de la branche. Le 1er niveau sous la racine est le 1
this.pos      : la position de la branche au sein des enfants du parent
this.idObj    : l'id attribué automatiquement. Ne pas se baser dessus pour les developpements externes
this.children : les enfants de la branche
this.objDrag  : L'objet Draggable

this.struct           : la structure de la branche avec toutes les infos utilisateur

this.eventable :
Détermine si la branche peut être drag n' droppée ou non.
Utilisé uniquement lors du dragndrop ajax et open ajax.

this.obj        : HTMLDivElement qui symbolise la branche
this.content    : HTMLTBodyElement qui symbolise le contenu de la branche
this.beforeIcon : HTMLTdElement qui symbolise la TD contenant le picto avant l'icône
this.img        : HTMLImgElement qui représente l'icône avant le texte
this.txt        : HTMLTextNodeElement qui représente le texte de la branche
this.table      : HTMLTableElement qui est le parent du Tbody (this.content)
*/

var TafelTreeBranch = Class.create();

TafelTreeBranch.prototype = Object.extend(new TafelTreeBaseBranch, {
	/**
	 * Constructeur d'une branche de l'arbre
	 *
	 * @access	public
	 * @param	TafelRoot			root					L'objet racine parent
	 * @param	TafelBranch			parent					L'objet directement parent
	 * @param	object				struct					Les infos concernant la branche est ses enfants
	 * @param	integer				level					Le niveau du noeud
	 * @param	boolean				before					True s'il y a des noeuds avant
	 * @param	boolean				after					True s'il y a des noeuds après
	 * @param	integer				pos						La position dans le tableau children[] du parent
	 */
	initialize : function (root, parent, struct, level, before, after, pos) {
		this.tree = root.tree;
		this.root = root;
		this.level = level;
		this.pos = pos;
		this.parent = parent;
		this.tree.idTree++;
		this.idObj = this.tree.idTreeBranch + this.tree.idTree;
		this.hasSiblingsBefore = before;
		this.hasSiblingsAfter = after;
		this.struct = struct;
		this.eventable = true;
		this.loaded = true;
		this.inProcess = false;
		this.children = [];
		this.copiedTimes = 0;

		if (typeof(this.struct.draggable) == 'undefined') this.struct.draggable = 1;
		this._setProperties();

		// Fonctions utilisateurs
		if (typeof(this.struct.ondrop) == 'undefined') {
			if (typeof(this.tree.onDrop) == 'function') {
				this.struct.ondrop = this.tree.onDrop;
				this.ondropDefault = true;
			}
		} else {this.struct.ondrop = eval(this.struct.ondrop);}
		if (typeof(this.struct.ondropajax) == 'undefined') {
			if (this.tree.onDropAjax && typeof(this.tree.onDropAjax.func) == 'function') {
				this.struct.ondropajax = this.tree.onDropAjax.func;
				this.struct.droplink = this.tree.onDropAjax.link;
				this.ondropajaxDefault = true;
			}
		} else {this.struct.ondropajax = eval(this.struct.ondropajax);}
		this._setFunctions();

		// Initialisation de la branche
		this.obj = this._addBranch();
		this.content = this._addContent();
		this.obj.appendChild(this.table);
		this._setChildren(root);
	},

	insertBefore : function (item) {
		if (!this.parent) return false;
		var pos = this.pos;
		var posBefore = pos + 1;
		var isNotFirst = (pos == 0) ? false : true;
		this.parent._movePartStruct(pos);
		this.parent.struct.items[pos] = item;
		this.parent.children[pos] = new TafelTreeBranch(this.root, this.parent, item, this.level, isNotFirst, true, pos);
		this.parent.obj.insertBefore(this.parent.children[pos].obj, this.obj);
		this.parent._manageAfterInsert(pos);
		return this.parent.children[pos];
	},

	insertAfter : function (item) {
		if (!this.parent) return false;
		var pos = this.pos + 1;
		var posBefore = pos + 1;
		var isNotLast = (pos == this.parent.children.length) ? false : true;
		this.parent._movePartStruct(pos);
		this.parent.struct.items[pos] = item;
		this.parent.children[pos] = new TafelTreeBranch(this.root, this.parent, item, this.level, true, isNotLast, pos);
		try {
			this.parent.obj.insertBefore(this.parent.children[pos].obj, this.parent.children[posBefore].obj);
		} catch (err) {
			this.parent.obj.appendChild(this.parent.children[pos].obj);
		}
		this.parent._manageAfterInsert(pos);
		return this.parent.children[pos];
	},
	
	/**
	 * Méthode pour déplacer une branche dans l'arbre comme fille
	 *
	 * @access	public
	 * @param	string				hereb					L'id de la nouvelle branche parente
	 * @return	TafelTreeBranch								La branche bougée
	 */
	move : function (hereb) {
		return this.moveIntoLast(hereb);
	},
	moveIntoLast : function (hereb) {
		// On récupère l'objet "here"
		var here = this.tree.getBranchById(hereb);
		if (!here) return false

		var pos = here._getPos();
		var id = this.getId();
		var txt = this.getText();
		if (pos == here.children.length) {
			obj = here.insertIntoLast(this.struct);
		} else {
			obj = here.children[pos].insertBefore(this.struct);
		}
		this.tree.removeBranch(this);
		return obj;
	},
	
	moveIntoFirst : function (hereb) {
		// On récupère l'objet "here"
		var here = this.tree.getBranchById(hereb);
		if (!here) return false

		var id = this.getId();
		var txt = this.getText();
		var obj = here.insertIntoFirst(this.struct);
		this.tree.removeBranch(this);
		return obj;
	},
	
	/**
	 * Méthode pour déplacer une branche dans l'arbre comme soeur
	 *
	 * @access	public
	 * @param	string				hereb					L'id de la nouvelle branche soeur
	 * @return	void
	 */
	moveBefore : function (hereb) {
		// On récupère l'objet "here"
		var here = this.tree.getBranchById(hereb);
		if (!here) return false;

		var id = this.getId();
		var txt = this.getText();
		var obj = here.insertBefore(this.struct);
		this.tree.removeBranch(this);
		return obj;
	},

	moveAfter : function (hereb) {
		// On récupère l'objet "here"
		var here = this.tree.getBranchById(hereb);
		if (!here) return false;

		var id = this.getId();
		var txt = this.getText();
		var obj = here.insertAfter(this.struct);
		this.tree.removeBranch(this);
		return obj;
	},


	/**
	 *------------------------------------------------------------------------------
	 *							TafelTreeBranch private methods
	 *------------------------------------------------------------------------------
	 */
	
	/**
	 * Méthode pour ajouter l'élément principal
	 *
	 * @access	private
	 * @return	HTMLDivElement								L'élément DIV créé
	 */
	_addBranch : function () {
		var div = document.createElement('div');
		div.className = this.tree.classTreeBranch;
		return div;
	},
	
	/**
	 * Méthode pour ajouter le contenu de l'élément (images + textes)
	 *
	 * Créé une structure comme suit :
	 *	<table><tbody><tr>
	 *		<td><img /></td>
	 *		<td><img /></td>
	 *		<td>etc. (relatif au niveau de l'élément courant)</td>
	 *		<td><![-- CDATA --]></td>
	 *	</tr></tbody></table>
	 *
	 * @access	private
	 * @return	HTMLTbodyElement							L'élément TBODY créé
	 */
	_addContent : function () {
		var table = document.createElement('table');
		var tbody = document.createElement('tbody');
		var tr = document.createElement('tr');
		var img = document.createElement('img');
		// Toutes les images jusqu'à celle avant l'icône
		var imgs = this._addImgs();
		var nbImgs = imgs.length;
		for (var i = nbImgs - 1; i >= 0; i--) {
			tr.appendChild(imgs[i]);	
		}
		// On récupère l'image avant l'icône
		this.beforeIcon = this._getImgBeforeIcon();
		tr.appendChild(this.beforeIcon);
		
		// On créé l'icone et le texte
		var tdImg = document.createElement('td');
		var tdTxt = document.createElement('td');
		var img = document.createElement('img');
		var span = document.createElement('div');
		span.innerHTML = this.struct.txt;
		// Ajout du title (de NoisetteProd)
		if (this.struct.title) {
			span.setAttribute('title', this.struct.title);
		}
		// Fin ajout du title
		span.setAttribute('id', this.idObj);
		Element.addClassName(span, this.tree.classContent);
		Element.addClassName(tdTxt, this.tree.classCanevas);
		img.src = this.tree.imgBase + this.struct.img;
		this.tdImg = tdImg;
		if (this.tree.multiline) {
			tdTxt.style.whiteSpace = 'normal';
			if (this.hasChildren()) this._manageMultiline(this.tdImg, 2, true);
		}
		if (this.struct.style) {
			Element.addClassName(tdTxt, this.struct.style);
		}
		// On append l'ensemble à la table HTML
		tdTxt.appendChild(span);
		// Insertion du tooltip, s'il existe
		if (this.struct.tooltip) {
			this.tooltip = this._createTooltip();
			tdTxt.appendChild(this.tooltip);
		}
		tdImg.appendChild(img);
		tr.appendChild(tdImg);
		tr.appendChild(tdTxt);
		tbody.appendChild(tr);
		table.appendChild(tbody);
		this.tdImg = tdImg;
		this.txt = span;
		this.img = img;
		this.table = table;
		this._setEvents(tdTxt, tdImg);
		return tbody;
	},
	
	/**
	 * Fonction qui permet de gérer toutes les lignes verticales qui précèdent l'icone
	 *
	 * @access	private
	 * @return	array										Les images à mettre avant l'icône
	 */
	_addImgs : function () {
		var obj = this.parent;
		var cpt = 0;
		var imgs = [];
		var img = null;
		// On détermine s'il y a des lignes verticales avant l'icone et le texte
		var td = null;
		while (obj.parent) {
			td = document.createElement('td');
			img = document.createElement('img');
			if (!obj.hasSiblingsAfter) {
				img.src = this.tree.imgBase + this.tree.imgEmpty;
			} else {
				img.src = this.tree.imgBase + this.tree.imgLine1;
				if (this.tree.multiline) {
					this._manageMultiline(td, 1, true);
				}
			}
			td.appendChild(img);
			imgs[cpt] = td;
			cpt++;
			obj = obj.parent;
		}
		// On teste si le root à des soeurs. Si oui, on ajoute encore une ligne
		td = document.createElement('td');
		img = document.createElement('img');
		if (!this.root.hasSiblingsAfter) {
			img.src = this.tree.imgBase + this.tree.imgEmpty;
		} else {
			img.src = this.tree.imgBase + this.tree.imgLine1;
			if (this.tree.multiline) {
				this._manageMultiline(td, 1, true);
			}
		}
		td.appendChild(img);
		imgs[cpt] = td;
		return imgs;
	}
});



























/**
 *------------------------------------------------------------------------------
 *							TafelTrees Management
 *------------------------------------------------------------------------------
 */

var TafelTreeManager = {
	/**
	 *------------------------------------------------------------------------------
	 *							TafelTreeManager properties
	 *------------------------------------------------------------------------------
	 */
	
	/**
	 * @var		boolean			stopEvent				Stoppe la propagation de l'événement
	 */
	stopEvent : true,
	
	/**
	 * @var		boolean			keyboardEvents			True pour activer la gestion clavier
	 */
	keyboardEvents : true,
	
	/**
	 * @var		boolean			keyboardStructEvents	True pour activer la gestion clavier relative à la structure
	 */
	keyboardStructuralEvents : true,
	
	/**
	 * @var		array			trees					Les arbres actuellement loadés
	 */
	trees : [],
	
	/**
	 * @var		TafelTree		currentTree				L'arbre actuellement actif
	 */
	currentTree : null,
	
	/**
	 * @var		array			userKeys				Les touches utilisateur
	 */
	userKeys : [],
	
	
	
	/**
	 *------------------------------------------------------------------------------
	 *							TafelTreeManager public methods
	 *------------------------------------------------------------------------------
	 */
	
	/**
	 * Permet de setter des fonctions utilisateur pour les touches voulues
	 *
	 * L'objet keys est formé comme ceci :
	 *	- keys[0].key = code de la touche
	 *	- keys[0].func = fonction utilisateur
	 *
	 * @access	public
	 * @param	array			keys					Les touchces et leur fonction
	 * @return	void
	 */
	setKeys : function (keys) {
		this.userKeys = keys;
	},
	
	/**
	 * Ajoute un arbre dans le manager
	 *
	 * @access	public
	 * @param	TafelTree		tree					L'arbre à ajouter
	 * @return	void
	 */
	add : function (tree) {
		this.trees.push(tree);
	},

	disableKeyboardEvents : function () {
		this.keyboardEvents = false;
	},

	disableKeyboardStructuralEvents : function () {
		this.keyboardStructuralEvents = false;
	},
	
	/**
	 * Retourne l'arbre actuellement actif
	 *
	 * @access	public
	 * @return	TafelTree								L'arbre actuellement actif
	 */
	getCurrentTree : function () {
		return this.currentTree;
	},
	
	/**
	 * Set l'arbre actuellement actif
	 *
	 * @access	public
	 * @param	TafelTree								L'arbre actuellement actif
	 * @return	void
	 */
	setCurrentTree : function (tree) {
		this.currentTree = tree;
	},
	
	/**
	 * Retourne true si la touche POMME est appuyée (sur Mac Safari)
	 *
	 * @access	public
	 * @return	boolean									True si POMME est appuyé
	 */
	metaOn : function (ev) {
		var ok = false;
		if (ev && (ev.metaKey)) {
			ok = true;
		}
		return ok;
	},
	
	/**
	 * Retourne true si la touche CTRL est appuyée
	 *
	 * @access	public
	 * @return	boolean									True si CTRL est appuyé
	 */
	ctrlOn : function (ev) {
		var ok = false;
		if (ev && (ev.ctrlKey || ev.modifier == 2)) {
			ok = true;
		}
		return ok;
	},
	
	/**
	 * Retourne true si la touche ALT est appuyée
	 *
	 * @access	public
	 * @return	boolean									True si ALT est appuyé
	 */
	altOn : function (ev) {
		var ok = false;
		if (ev && (ev.altKey || ev.modifier == 1)) {
			ok = true;
		}
		return ok;
	},
	
	/**
	 * Retourne true si la touche SHIFT est appuyée
	 *
	 * @access	public
	 * @return	boolean									True si ALT est appuyé
	 */
	shiftOn : function (ev) {
		var ok = false;
		if (ev && (ev.shiftKey || ev.modifier == 3)) {
			ok = true;
		}
		return ok;
	},
	
	/**
	 * Retourne le code clavier
	 *
	 * @access	public
	 * @param	Event			ev						L'événement déclencheur
	 * @return	void
	 */
	getCode : function (ev) {
		return (ev.which) ? ev.which : ev.keyCode;
	},
	
	/**
	 * Assigne tous les événements nécessaires
	 *
	 * @access	public
	 * @return	void
	 */
	setControlEvents : function () {
		Event.observe(document, 'keypress', this.evt_keyPress.bindAsEventListener(this), false);
		var body = document.getElementsByTagName('body');
		if (!body || !body[0]) {
			throw new Error(TAFELTREE_NO_BODY_TAG);
		} else {
			Event.observe(body[0], 'mouseup', this.evt_unselect.bindAsEventListener(this), false);
		}
	},
	
	
	/**
	 *------------------------------------------------------------------------------
	 *							TafelTreeManager events management
	 *------------------------------------------------------------------------------
	 */

	/**
	 * Déselectionne l'arbre courant
	 *
	 * @access	public
	 * @param	Event			ev						L'événement déclencheur
	 * @return	void
	 */
	evt_unselect : function (ev) {
		var obj = Event.element(ev);
		var current = this.getCurrentTree();
		if (current) {
			if (!Element.hasClassName(obj, current.classSelected) && !Element.hasClassName(obj, current.classOpenable)) {
				current.unselect();
				this.setCurrentTree(null);
			}
		}
	},
	
	/**
	 * Appel lors de la touche ENTER
	 *
	 * @access	public
	 * @param	TafelTree		tree					L'arbre incriminé
	 * @param	integer			code					Le code de la touche
	 * @param	Object			keys					Les infos des "metakeys" ctrl, shift, alt et meta
	 * @param	Event			ev						L'événement déclencheur
	 * @return	void
	 */
	enter : function (tree, code, keys, ev) {
		if (tree.lastEdited) {
			tree.lastEdited.editableInput.blur();
			if (this.stopEvent) Event.stop(ev);
		}
	},
	
	/**
	 * Appel lors de la touche ESCAPE
	 *
	 * @access	public
	 */
	escape : function (tree, code, keys, ev) {
		var selected = tree.getSelectedBranches();
		var lastPos = selected.length - 1;
		var nounselect = false;
		if (lastPos == 0 && tree.lastEdited) {
			if (tree.lastEdited.hideEditable(ev)) {
				nounselect = true;
			}
		}
		if (!nounselect) {
			tree.unselect();
		}
		tree.unsetCut();
		tree.unsetCopy();
		if (this.stopEvent) Event.stop(ev);
	},
	
	/**
	 * Appel lors de la touche HOME
	 *
	 * @access	public
	 */
	moveStart : function (tree, code, keys, ev) {
		if (!tree.lastEdited) {
			tree.unselect();
			if (tree.roots.length > 0) {
				var branch = tree.roots[0];
				branch.select();
			}
			if (this.stopEvent) Event.stop(ev);
		}
	},
	
	/**
	 * Appel lors de la touche END
	 *
	 * @access	public
	 */
	moveEnd : function (tree, code, keys, ev) {
		if (!tree.lastEdited) {
			tree.unselect();
			if (tree.roots.length > 0) {
				var last = tree.roots.length - 1;
				var branch = tree.roots[last];
				while (branch.hasChildren()) {
					branch = branch.getLastBranch();
				}
				branch.select();
			}
			if (this.stopEvent) Event.stop(ev);
		}
	},
	
	/**
	 * Appel lors de la touche Fleche haut
	 *
	 * @access	public
	 */
	moveUp : function (tree, code, keys, ev) {
		var selected = tree.getSelectedBranches();
		var lastPos = selected.length - 1;
		if (!tree.lastEdited) {
			if (lastPos >= 0) {
				var branch = selected[lastPos].getPreviousOpenedBranch();
				if (branch) branch.select(ev);
			} else {
				// On sélectionne automatiquement le 1er élément de l'arbre
				if (typeof(tree.roots[0]) != 'undefined') tree.roots[0].select();
			}
			if (this.stopEvent) Event.stop(ev);
		}
	},
	
	/**
	 * Appel lors de la touche Fleche bas
	 *
	 * @access	public
	 */
	moveDown : function (tree, code, keys, ev) {
		var selected = tree.getSelectedBranches();
		var lastPos = selected.length - 1;
		if (!tree.lastEdited) {
			if (lastPos >= 0) {
				var branch = selected[lastPos].getNextOpenedBranch();
				if (branch) branch.select(ev);
			} else {
				// On sélectionne automatiquement le 1er élément de l'arbre
				if (typeof(tree.roots[0]) != 'undefined') tree.roots[0].select();
			}
			if (this.stopEvent) Event.stop(ev);
		}
	},
	
	/**
	 * Appel lors de la touche Fleche droite
	 *
	 * @access	public
	 */
	moveRight : function (tree, code, keys, ev) {
		var selected = tree.getSelectedBranches();
		var lastPos = selected.length - 1;
		if (!tree.lastEdited) {
			if (lastPos >= 0) {
				var branch = selected[lastPos];
				if (branch.hasChildren() && !branch.isOpened()) {
					branch.setOpen(ev);
				} else {
					if (branch.hasChildren()) {
						var sel = branch.getFirstBranch();
						var sel = sel.select(ev);
					}
				}
			} else {
				// On sélectionne automatiquement le 1er élément de l'arbre
				if (typeof(tree.roots[0]) != 'undefined') tree.roots[0].select();
			}
			if (this.stopEvent) Event.stop(ev);
		}	
	},
	
	/**
	 * Appel lors de la touche Fleche gauche
	 *
	 * @access	public
	 */
	moveLeft : function (tree, code, keys, ev) {
		var selected = tree.getSelectedBranches();
		var lastPos = selected.length - 1;
		if (!tree.lastEdited) {
			if (lastPos >= 0) {
				var branch = selected[lastPos];
				if (lastPos == 0 && branch.hasChildren() && branch.isOpened()) {
					branch.openIt(false);
				} else {
					if (!branch.isRoot) branch.parent.select(ev);
				}
			} else {
				// On sélectionne automatiquement le 1er élément de l'arbre
				if (typeof(tree.roots[0]) != 'undefined') tree.roots[0].select();
			}
			if (this.stopEvent) Event.stop(ev);
		}		
	},
	
	/**
	 * Appel lors de la touche F2
	 *
	 * @access	public
	 */
	edit : function (tree, code, keys, ev) {
		var selected = tree.getSelectedBranches();
		var lastPos = selected.length - 1;
		if (lastPos == 0) {
			selected[lastPos].setDblClick(ev);
		}
		if (this.stopEvent) Event.stop(ev);
	},
	
	/**
	 * Appel lors de la touche DELETE
	 *
	 * @access	public
	 */
	remove : function (tree, code, keys, ev) {
		if (!tree.lastEdited) {
			var selected = tree.getSelectedBranches();
			for (var i = 0; i < selected.length; i++) {
				tree.removeBranch(selected[i]);
			}
			if (this.stopEvent) Event.stop(ev);
		}
	},
	
	/**
	 * Appel lors de la touche x ou X
	 *
	 * @access	public
	 */
	cut : function (tree, code, keys, ev) {
		if (keys.ctrlKey || keys.metaKey) {
			tree.cut();
			if (this.stopEvent) Event.stop(ev);
		}
	},
	
	/**
	 * Appel lors de la touche c ou C
	 *
	 * @access	public
	 */
	copy : function (tree, code, keys, ev) {
		if (keys.ctrlKey || keys.metaKey) {
			tree.copy();
			if (this.stopEvent) Event.stop(ev);
		}
	},
	
	/**
	 * Appel lors de la touche v ou V
	 *
	 * @access	public
	 */
	paste : function (tree, code, keys, ev) {
		if (keys.ctrlKey || keys.metaKey) {
			tree.paste();
			if (this.stopEvent) Event.stop(ev);
		}
	},
	
	/**
	 * Appel lors de la touche z ou Z
	 *
	 * @access	public
	 */
	undo : function (tree, code, keys, ev) {
		if (keys.ctrlKey || keys.metaKey) {
			tree.undo();
			if (this.stopEvent) Event.stop(ev);
		}
	},
	
	/**
	 * Gestion du clavier
	 *
	 * @access	private
	 * @param	Event					ev							L'événement déclencheur
	 * @return	void
	 */
	evt_keyPress : function (ev) {
		var current = this.getCurrentTree();
		if (current && this.keyboardEvents) {
			// Check Control Ctrl
			var keys = {
				'ctrlKey'  : this.ctrlOn(ev),
				'metaKey'  : this.metaOn(ev),
				'altKey'   : this.altOn(ev),
				'shiftKey' : this.shiftOn(ev)
			};
			// Check de la touche appuyée
			var code = this.getCode(ev);
			// Check si l'utilisateur a fourni une fonction particulière
			for (var i = 0; i < this.userKeys.length; i++) {
				if (code == this.userKeys[i].key && typeof(this.userKeys[i].func) == 'function') {
					if (!this.userKeys[i].func(current, code, keys, ev)) {
						return false;
					}
					break;
				}
			}
			switch (code) {
				// Retour au début (Home) et fin (End)
				case Event.KEY_HOME : this.moveStart(current, code, keys, ev); break;
				case Event.KEY_END : this.moveEnd(current, code, keys, ev); break;
				// Mouvements haut, bas, gauche et droite
				case Event.KEY_UP : this.moveUp(current, code, keys, ev); break;
				case Event.KEY_DOWN : this.moveDown(current, code, keys, ev); break;
				case Event.KEY_RIGHT : this.moveRight(current, code, keys, ev); break;
				case Event.KEY_LEFT : this.moveLeft(current, code, keys, ev); break;
			}
			if (this.keyboardStructuralEvents) {
				switch (code) {
					// Fin de l'édition d'une branche
					case Event.KEY_RETURN : this.enter(current, code, keys, ev); break;
					// Déselection
					case Event.KEY_ESC : this.escape(current, code, keys, ev); break;
					// Effacer (Del)
					case Event.KEY_DELETE : this.remove(current, code, keys, ev); break;
					// Editer (F2)
					case 113: this.edit(current, code, keys, ev); break;
					// Couper (x-X)
					case 120: case 88: this.cut(current, code, keys, ev); break;
					// Copier (c-C)
					case 99 : case 67: this.copy(current, code, keys, ev); break;
					// Coller (v-V)
					case 118: case 86: this.paste(current, code, keys, ev); break;
					// Annuler (z-Z)
					case 122: case 90: this.undo(current, code, keys, ev); break;
				}
			}
		}
	}
};

