/**
 * Namespace principal
 * @public
 */
var AI =
{
  /**
   * Fixe les boites <select> de IE6
   * @param {boolean} S (State) Etat du fix. true pour l'activer, false pour le désactiver
   * @public
   */
  fixIESelect:function(S)
  {
    DOM[ S ? 'addCSS' : 'removeCSS' ]($D.body, 'fix_ie_select');
  },
  
  /**
   * Placeholder pour les objets dont on veut garder une référence globale
   * @public
   * @deprecated : lui préférer $G
   */
  _GLOBALS:{},

  /**
   * Placeholder pour les objets dont on veut garder une référence globale
   * @public
   */
  $G:{},

  /**
   * Définition de l'heure de démarrage du système
   */
  startingTime:(new Date()).valueOf(),

  /**
   * Liste des fichiers avec leurs éventuels callbacks et leur statut de chargement
   * @public
   */
  $f:{},

  /**
   * Inclut le fichier et déclenche les différents callbacks selon l'évolution du chargement
   * @param[in] {string} f  Nom du fichier à charger (si c'est une adresse relative, elle est relative à /js/ai/)
   * @param[in] {object} c2 Référence de la fonction (callback) a exécuter lorsque le fichier passe en state LOADED (optional)
   * @param[in] {object} c1 Référence de la fonction (callback) a exécuter lorsque le fichier passe en state LOADING (optional)
   * @param[in] {object} c3 Référence de la fonction (callback) a exécuter lorsque le fichier passe en state ERR (optional)
   * @public
   */
  require:function(f, c2, c1, c3)
  {
    var
    url = f.charAt(0) == '/' ? f : '/js/ai/' + f,
//    H = $D.getElementsByTagName("head")[0], // $tag('head')
//    S = $D.createElement("script"); // $E('script', {"id":'script_' + f.replace(/[^a-z0-9]/i, '_'),"type":'text/javascript","src":url})
//    H = $tag('head')
    S = $E('script', {"id":f.replace(/[^a-z0-9]/i, '_'),"type":'text/javascript',"src":url});

    AI.$f[f] =
    {
      state:AI.consts.UNDEF,
      onloading:c1,
      onloaded:c2,
      onerror:c3 ? c3 : function() { alert(f + CR + 'Erreur de chargement du fichier' + CR + 'Error loading file'); }
    };

//    S.id = 'script_' + f.replace(/[^a-z0-9]/i, '_');
//    S.type = "text/javascript";
//    S.src = url;

    AI.setFileState(f, AI.consts.LOADING);

//    H.appendChild(S);
    $tag('head').appendChild(S);
  },

  /**
   * Définit le statut de chargement du fichier et déclenche l'eventuel callback associé à cet état
   * @param[in] {string}  f Nom du fichier de référence
   * @param[in] {integer} s State à appliquer
   * @public
   */
  setFileState:function(f, s)
  {
    var c = null;

    AI.$f[f].state = s;

    switch ( s )
    {
      case AI.consts.LOADING:
        c = AI.$f[f].onloading;
      break;
      case AI.consts.LOADED:
        c = AI.$f[f].onloaded;
      break;
      case AI.consts.ERR:
        c = AI.$f[f].onerror;
      break;
    }

    if ( c ) { c(); }
  },

  /**
   * Définit une propriété de chargement du fichier (changement d'un callback par exemple)
   * @param[in] {string}         f Nom du fichier de référence
   * @param[in] {string}         p Nom de la propriété à manipuler (state, onloading, onloaded, onerror)
   * @param[in] {integer|object} v Valeur (integer pour state, référence de fonction pour les autres)
   * @public
   */
  setFileProperty:function(f, p, v) { AI.$f[f][p] = v; },

  /**
   * Obtient le statut courant du fichier
   * @param[in] {string} f Nom du fichier de référence
   * @public
   * @return {integer} L'état courant
   */
  getFileState:function(f) { return AI.$f[f].state; },

  /**
   * Déclenche une alerte, méthode surchargée au fur et à mesure que le système se met en place
   * @param[in] {string} t Texte du message
   * @private
   */
  _alertBox:function(t) { alert(t); },

  /**
   * Déclenche une erreur, méthode surchargée au fur et à mesure que le système se met en place
   * @param[in] {string} t Texte du message
   * @private
   */
  _throwError:function(t) { throw new Error(t); },

/**
 * Détermine si le contexte d'éxécution correspond au système principal ou un sous système
 * @public
 * @return {boolean} true si on est bien dans une fenêtre ouverte par le système principal, false dans le cas contraire
 * DEPRECATED
 */
isInsideFenetre:function() { return typeof top.isMainWidget !== 'undefined' && top.isMainWidget; },

later:function(msec, method, scope)
{
 var args = Array.prototype.slice.apply(arguments, [3]);
 if ( scope === null ) { scope = window; }
 if ( typeof method == 'string' ) { method = scope[method]; }
 return setTimeout(function() { method.apply(scope, args); }, msec);
},

getSimilarParentObject:function(obj)
{
 var
  scopes = ['top', 'parent', 'opener'],
  scope = null,
  hostname = window.location.hostname,
  i;
 for ( i = 0; i < scopes.length; i++ )
 {
   scope = window[scopes[i]];
   try
   {
     if ( scope !== self
         && typeof scope != 'undefined'
         && scope !== null
         && scope
         && scope.location
         && scope.location.hostname === hostname
         && scope.AI
         && typeof scope.AI[obj] != 'undefined' )
     {
       return scope.AI[obj];
     }
   } catch (x) {}
 }
 return false;
},

benchmark:function()
{
 var debut = 0, texte = '', ellapsed = 0;
 return {
  init:function(txt)
  {
   texte = txt || '';
   debut = (new Date()).valueOf();
  },
  end:function()
  {
   ellapsed = (new Date()).valueOf() - debut;
   return ellapsed;
  },
  setText:function(txt) { texte = txt; },
  getText:function() { return texte; },
  getEllapsed:function() { return ellapsed; }
 };
}()

  /**
   * Charge un script non AI et appelle une fonction au chargement
   * @param {string} U (Url)      Source url of the file to load
   * @param {object} C {Callback} Callback function to launch once ready (optional)
   * @param {object} O (scOpe)    Application scope for the callback function (optional)
   * @param {object} B (Bonus}    Arbitrary object send as a param to the callback function (optional)
   * @public
   */
/*
  ,loadBack:function(U, C, O, B)
  {
    var T = IS.ie() "onreadystatechange" : "onload",
      S = $E('script', {type:"text/javascript", src:U});
    if ( C )
    {
      S[T] = function()
      {
        if ( IS.ie() && ! ( /loaded|complete/.test($W.event.srcElement.readyState) ) )
        {
          return;
        }
        C.call(O ? O : this, B);
        S[T] = null;
        return;
      };
    }
    DOM.head.appendChild(S);
  }
*/
};

/**
 * Détermine si le contexte d'éxécution correspond est le système principal ou un sous système
 * @public
 * @return {boolean} true si on est bien dans une fenêtre ouverte par le système principal, false dans le cas contraire
 * DEPRECATED
 */
function isInsideFenetreAI() { DEPRECATED('isInsideFenetreAI'); return AI.isInsideFenetre(); }

/**
 * Déclenche une alerte et une erreur
 * @param[in] {string} t Texte du message
 * @public
 */
function alertError(t)
{
  AI._alertBox(t);
  AI._throwError(t);
}

/**
 * Déclenche un warning DEPRECATED dans la console d'erreur
 * @private
 */
function DEPRECATED(f) { /*if ( AI.debug ) { AI.debug('DEPRECATED : ' + f, 'warning'); }*/ }
AI.consts =
{
// statut des AI.require()
UNDEF:0, LOADING:1, LOADED:2, ERR:3,

// cible des liens permettant d'ouvrir automatiquement une fenetre
INTERNAL_TARGET:'AIcontenu',

// mode de représentation des AI.atome
SHOW_NONE:0, SHOW_LABEL:1, SHOW_ICON:2, SHOW_BOTH:3,

// blank url
BLANK_URL:'/blank.html',

// base directory des pictos
BASEDIR:'/images/pictos',

// blank image
BLANK_IMG:'/images/widgets/core/blank16x16.png',

// methode drag & drop
SILENT:0,
FRAME:1,
OPAQUE:2,
COPIE:3,
TRANSLUCIDE:4,

// mode des fenetres
MINIMIZED:0,
MAXIMIZED:1,

// type de filtre des fenetres
FIT:1,
RESIZE:2,

// etat des boutons de sauvegarde de position des fenetres
POS_SAVE:1,
POS_REMOVE:2,

// flags du drag & drop
DRAG_WAIT:1, DRAG_LISTEN:2, DRAG_MOVE:3,

// orientation
HORIZONTAL:0, VERTICAL:1,





AI_ANTI_TRAILING_COMMA_BUG:'MUST_BE_REMOVED'

};


var
/**
 * Saut de ligne pour éviter la multiplication des antislashes dans le code INSTANCE/PHP/JS/HTML
 * @public
 */
CR = "\n",

/**
 * Raccourci vers le document
 * @public
 */
$D = document,

/**
 * Raccourci vers window
 * @public
 */
$W = window;

/**
 * Définition des constantes DOM indispensables (ELEMENT_NODE et TEXT_NODE) que certains clients n'exposent pas (comme IE6)
 * @public
 */
/*
if ( typeof $D.ELEMENT_NODE == 'undefined' || $D.ELEMENT_NODE === null ) { $D.ELEMENT_NODE = 1; }
if ( typeof $D.ATTRIBUTE_NODE == 'undefined' || $D.ATTRIBUTE_NODE === null ) { $D.ATTRIBUTE_NODE = 2; }
if ( typeof $D.TEXT_NODE == 'undefined' || $D.TEXT_NODE === null ) { $D.TEXT_NODE = 3; }
if ( typeof $D.CDATA_SECTION_NODE == 'undefined' || $D.CDATA_SECTION_NODE === null ) { $D.CDATA_SECTION_NODE = 4; }
if ( typeof $D.ENTITY_REFERENCE_NODE == 'undefined' || $D.ENTITY_REFERENCE_NODE === null ) { $D.ENTITY_REFERENCE_NODE = 5; }
if ( typeof $D.ENTITY_NODE == 'undefined' || $D.ENTITY_NODE === null ) { $D.ENTITY_NODE = 6; }
if ( typeof $D.PROCESSING_INSTRUCTION_NODE == 'undefined' || $D.PROCESSING_INSTRUCTION_NODE === null ) { $D.PROCESSING_INSTRUCTION_NODE = 7; }
if ( typeof $D.COMMENT_NODE == 'undefined' || $D.COMMENT_NODE === null ) { $D.COMMENT_NODE = 8; }
if ( typeof $D.DOCUMENT_NODE == 'undefined' || $D.DOCUMENT_NODE === null ) { $D.DOCUMENT_NODE = 9; }
if ( typeof $D.DOCUMENT_TYPE_NODE == 'undefined' || $D.DOCUMENT_TYPE_NODE === null ) { $D.DOCUMENT_TYPE_NODE = 10; }
if ( typeof $D.DOCUMENT_FRAGMENT_NODE == 'undefined' || $D.DOCUMENT_FRAGMENT_NODE === null ) { $D.DOCUMENT_FRAGMENT_NODE = 11; }
if ( typeof $D.DOCUMENT_NOTATION_MODE == 'undefined' || $D.DOCUMENT_NOTATION_MODE === null ) { $D.DOCUMENT_NOTATION_MODE = 12; }
*/
(
 function()
 {
  var i, T = ['ELEMENT_NODE', 'ATTRIBUTE_NODE', 'TEXT_NODE', 'CDATA_SECTION_NODE', 'ENTITY_REFERENCE_NODE', 'ENTITY_NODE', 'PROCESSING_INSTRUCTION_NODE', 'COMMENT_NODE', 'DOCUMENT_NODE', 'DOCUMENT_TYPE_NODE', 'DOCUMENT_FRAGMENT_NODE', 'DOCUMENT_NOTATION_MODE'];
  for ( i = 0; i < T.length; i++ )
  {
   if ( typeof $D[T[i]] == 'undefined' || $D[T[i]] === null ) { $D[T[i]] = 1 + i; }
  }
 }()
);
/*
if ( top.AI && top.AI.config )
{
 AI.config = top.AI.config;
}
else
{
 AI.config =
 {
  WINDOW_MOVE:AI.consts.FRAME,
  WINDOW_MOVE_ANIMATED:true,
  WINDOW_MAXIMIZE_ANIMATED:false,
  WINDOW_RESIZE:AI.consts.FRAME,
  WINDOW_RESIZE_ANIMATED:false,
  BUREAU_MOVE:AI.consts.OPAQUE,
  menuCloseTimer:5000
 };
}


*/

AI.config = AI.getSimilarParentObject('config') ||
{
 WINDOW_MOVE              : AI.consts.FRAME,
 WINDOW_MOVE_ANIMATED     : true,
 WINDOW_MAXIMIZE_ANIMATED : false,
 WINDOW_RESIZE            : AI.consts.FRAME,
 WINDOW_RESIZE_ANIMATED   : false,
 BUREAU_MOVE              : AI.consts.OPAQUE,
 menuCloseTimer           : 5000
};
/**
 * Module utilitaire générique
 * @public
 */
 
AI.utils =
{
  /**
   * Détermine si la référence est définie
   * @param {object} o La référence
   * @param {object} k La clé ou l'index si la référence est un tableau
   * @public
   * @return {boolean} true si définie, false dans le cas contraire
   */
  defined:function(o, k)
  {
    if ( k )
    {
      return typeof o[k] != 'undefined';
    }
    return typeof o != 'undefined';
  },

  /**
   * Renvoit toujours true
   * @public
   * @return {boolean} true
   */
  returnTrue:function() { return true; },

  /**
   * Renvoit toujours false
   * @public
   * @return {boolean} false
   */
  returnFalse:function() { return false; },

  /**
   * Renvoit toujours null
   * @public
   * @return {object} null
   */
  returnNull:function() { return null; },

  /**
   * Renvoit toujours this
   * @public
   * @return {object} this
   */
  returnThis:function() { return this; },

  /**
   * Renvoit toujours 0
   * @public
   * @return {integer} 0 (zero)
   */
  returnZero:function() { return 0; },

  /**
   * Renvoit toujours -1
   * @public
   * @return {integer} -1
   */
  returnNegativeIndex:function() { return -1; },

  /**
   * Vérifie si un objet est vide
   * @param[in] {object} o Object à vérifier
   * @public
   * @return {boolean} true si vide, false si il possède au moins une clé
   */
  isObjectEmpty:function(o)
  {
    for ( var k in o )
    {
      return false;
    }
    return true;
  },

  /**
   * Calcule la taille d'un objet (le nombre de clés) sur le même principe qu'un tableau possède une propriété length
   * @param[in] {object} o Object
   * @public
   * @return {integer} La taille de l'objet
   */
  getObjectLength:function(o)
  {
    var r = 0;
    for ( var k in o )
    {
      r++;
    }
    return r;
  },
  /**
   * Convertit un objet en array
   * @param[in] {object} o Objet à transformer
   * @public
   * @return {array} Objet initial sous forme de tableau
   */
  convertObjectToArray:function(o)
  {
    var r = [];
    for ( var k in o )
    {
      r.push(k);
    }
    return r;
  },

  /**
   * Convertit un objet en string
   * @param[in] {object} o Objet à transformer
   * @param[in] {string} s Séparateur (optional)
   * @public
   * @return {array} Objet initial sous forme de string
   */
  convertObjectToString:function(o, s)
  {
    return AI.utils.convertObjectToArray(o).join(s ? s : ', ');
  },

  /**
   * Convertit les valeurs d'un objet est un tableau
   * @param[in] {object} o Objet à transformer
   * @public
   * @return {array} Objet initial sous forme de tableau
   */
  convertObjectValuesToArray:function(o)
  {
    var r = [];
    for ( var k in o )
    {
      r.push(o[k]);
    }
    return r;
  },

  /**
   * Convertit l'objet argument en un tableau
   * @param[in] {object} a Argument à transformer
   * @public
   * @return {array} Argument initial sous forme de tableau
   */
  convertArgumentsToArray:function(a)
  {
    var b = [];
    for ( var i = 0, m = a.length; i < m; i++ )
    {
      b.push(a[i]);
    }
    return b;
  },

  /**
   * Convertit une dimensions CSS exprimées en version courte (margin, padding, etc. sur 1, 2, 3 ou 4 paramètres) en un tableau indéxé
   * @param[in] D La dimensions à splitter en 4 valeurs
   * @public
   * @return {array} Les 4 dimensions [Top, Right, Bottom, Left]
   */
  convertShortHandToArray:function(D)
  {
    var m = D.length;
    if ( m > 4 )
    {
      alertError(D + CR + "Nombre d'arguments invalides");
    }

    var v;
    var R = [];

    for ( var i = 0; i < m; i++ )
    {
      v = D[i];

      if ( AI.utils.isValidNumber(v) )
      {
        R.push(v);
      }
      else if ( !AI.utils.isValidString(v) )
      {
        R.push(null);
      }
      else
      {
        alertError(v + CR + "Valeur courte invalide");
      }
    }

    // Fix Values (Shorthand)
    switch ( m )
    {
      case 1:
        R[1] = R[2] = R[3] = R[0];
      break;

      case 2:
        R[2] = R[0];
        R[3] = R[1];
      break;

      case 3:
        R[3] = R[1];
      break;
    }

    return R;
  },

  /**
   * Determine si c'est un email
   */
  isEmail:function(M)
  {
    return !M.search(/^[\w\-][\w\-\.]+@[\w\-]+\.[a-zA-Z]{2,6}$/);
  },

  /**
   * Détermine si le paramètre est valide
   * @param[in] {object} v Valeur à vérifier
   * @public
   * @return {boolean} true si le paramètre est valide
   */
  isValid:function(v)
  {
    var R = false;
    switch ( typeof v )
    {
//      case "undefined":
//        R = false;
//      break;
      case "object":
        R = v !== null;
      break;

      case "string":
        R = v !== "";
      break;

      case "number":
        R = !isNaN(v);
      break;

      case "function":
      case "boolean":
        R = true;
      break;
    }
    return R;
  },

  /**
   * Détermine si le paramètre est un nombre valide
   * @param[in] v Valeur à vérifier
   * @public
   * @return {boolean} true si le paramètre est un nombre valide
   */
  isValidNumber:function(v) { return typeof v === "number" && !isNaN(v); },

  /**
   * Détermine si le paramètre est un string valide
   * @param[in] v Valeur à vérifier
   * @public
   * @return {boolean} true si le paramètre est un string valide
   */
  isValidString:function(v) { return typeof v === "string" && v !== ""; },

  /**
   * Détermine si le paramètre est un array valide
   * @param[in] v Valeur à vérifier
   * @public
   * @return {boolean} true si le paramètre est un tableau valide
   */
  isValidArray:function(v) { return typeof v === "object" && v !== null && v instanceof Array; },

  /**
   * Détermine si le paramètre est un objet valide (sans être un array)
   * @param[in] v Valeur à vérifier
   * @public
   * @return {boolean} true si le paramètre est un objet valide et pas un tableau
   */
  isValidObject:function(v) { return typeof v === "object" && v !== null && !(v instanceof Array); },

  /**
   * Détermine si le paramètre est une fonction valide
   * @param[in] v Valeur à vérifier
   * @public
   * @return {boolean} true si le paramètre est une fonction valide
   */
  isValidFunction:function(v) { return typeof v === "function"; },
  
  /**
   * Détermine si le paramètre est un boolean valide
   * @param[in] v Valeur à vérifier
   * @public
   * @return {boolean} true si le paramètre est un boolean valide
   */
  isValidBoolean:function(v) { return typeof v === "boolean"; },

  /**
   * Détermine si le paramètre est un string ou un nombre valide
   * @param[in] v Valeur à vérifier
   * @public
   * @return {boolean} true si le paramètre est un nombre ou un string valide
   */
  isValidStringOrNumber:function(v)
  {
    if ( typeof v == 'string' )
    {
      return v !== '';
    }
    else if ( typeof v == 'number' )
    {
      return !isNaN(v);
    }
    return false;
  },

  /**
   * Détermine si le paramètre est un élément HTML valide
   * @param[in] v Valeur à vérifier
   * @public
   * @return {boolean} true si le paramètre est un HTMLElement valide
   */
  isValidElement:function(v)
  {
    return typeof v === 'object' && v !== null || v.nodeType !== document.ELEMENT_NODE;
  },

  /**
   * Génère un identifiant unique
   * @param[in] {string} p Préfixe (optional)
   * @public
   * @return {string} Un identifiant unique
   */
  uniqid:function(P)
  {
    return ( P ? P : '' ) + (new Date().valueOf());
  },

  /**
   * Gestion des cookies
   */
  Cookies:
  {
  
  /*
  FORK.Cookie.enabled = function() {
  document.cookie = "FORKtestcookie=test";  // Set test cookie
  var enabled = (document.cookie.indexOf("FORKtestcookie=test") !== -1);
  if (enabled) {
    document.cookie = "FORKtestcookie=test; expires=Fri, 02-Jan-1970 00:00:00 GMT";  // Delete test cookie
  }
  FORK.Cookie.enabled = function() {return enabled;}
  return enabled;
};*/
    /**
     * Défini un cookie
     * @param {string} N [Name]  Nom
     * @param {string} V [Value] Valeur
     * @param {string} D [Days]  Jours de validité
     * @public
     */
  	set:function(N, V, D)
    {
      var d = new Date(); /* date de travail */
      D = D || 30;
      d.setTime(d.getTime() + (D * 24 * 60 * 60 * 1000));
  		document.cookie = N + "=" + V + "; expires=" + d.toGMTString() + "; path=/";
  	},
  	/**
     * Lit un cookie
     * @param {string} N [Name] Nom
     * @public
     * @return {string} La valeur du cookie
     */
  	get:function(N)
    {
  		var ca = document.cookie.split(';'),i,c;
      N += '=';
  		for( i = 0; i < ca.length; i++ )
      {
  			c = ca[i];
  			while ( c.charAt(0) == ' ' )
        {
          c = c.substring(1,c.length);
        }
  			if ( c.indexOf(N) === 0 )
        {
          return c.substring(N.length,c.length);
        }
  		}
  		return null;
    }
  },

/**
 * Encode une variable pour la transmettre par méthode GET
 * @param {string}  C (Chaine) La chaine a encoder
 * @param {boolean} U (isUrl)  true si la chaine représente une url
 * @public
 * @return {string} La version encodée de la chaine
 */
encoder:function(C, U)
{
 if ( typeof encodeURIComponent == 'function' )
 {
  return U ? encodeURI(C) : encodeURIComponent(C);
 }
 return escape(C);
},
 
/**
 * Décode une variable encodée
 * @param {string} C {Chaine} La chaine à décoder
 * @public
 * @return {string} La version décodée de la chaine
 */
decoder:function(C) { return typeof decodeURIComponent == 'function' ? decodeURIComponent(C) : unescape(C); },

/**
 * Neutralise une chaine HTML
 * @param {string} V {Valeur} La chaine à neutraliser
 * @public
 * @return {string} La version HTML neutralisée
 */
escapeHTML : function(v)
{
 function rc(c)
 {
  switch (c)
  {
   case "<": return "&lt;";
   case ">": return "&gt;";
   case "&": return "&amp;";
   case "'": return "&#39;";
   case '"': return "&quot;";
  }
  return "?";
 }
 return String(v).replace(/[<>&"']/g, rc);
}

};

/**
 * Version plus intelligente du alert standard. Celui-ci peut être interrompu
 * @param[in] {string] t Texte du message
 * @public
 */
function alerter(t)
{
  if ( alerter.$ )
  {
    alerter.$ = confirm(t);
  }
}

/**
 * Flag privé de la fonction alerter, permet d'annuler les alertes afin d'éviter les débuggages en boucle qu'on peut plus annuler autrement que par ALT+CTLR+DEL
 * @private
 */
alerter.$ = true;

/**
 * Déclenche une alerte, redéfinition de la méthode standard prenant en charge l'arleter amélioré
 * @todo Il doit y a voir une raison, mais je sais pas pourquoi on vérifie si alerter existe. Il faut vérifier si ça sert réellement à qqchose et si ce serait pas mieux de faire bettement    alerter(t + CR + CR + 'Continuer le pistage des erreurs ?');
 * @param[in] {string} t Texte du message
 * @private
 */
AI._alertBox = function(t)
{
  var c = typeof alerter == 'function' ? alerter : alert;
  c(t + CR + CR + 'Continuer le pistage des erreurs ?');
};

/*
---------------------------------------------------------------------------
  ALIAS
---------------------------------------------------------------------------
*/

var defined = AI.utils.defined;
/**
 * Module de traduction des textes
 * @public
 */
AI.traduction =
{
  /**
   * Tableau de correspondances
   * @private
   */
  $:{},
  
  /**
   * Tableau des correspondes non trouvées, utilisé pour mettre à jour par AJAX pour les prochains appels
   * @private
   */
//  $$:[],
  
  /**
   * Ajoute une paire clé / valeur
   * @param[in] {string} K Clé
   * @param[in] {string} V Valeur
   * @public
   */
  add:function(K, V)
  {
    AI.traduction.$[K] = V;
  },
  
  /**
   * Synchronize les clés de $$ vers $
   * @private
   */
/*
   synchro:function()
   {
      if ( AI.traduction.$$.length > 0 )
      {
        if ( AI.traduction.toid )
        {
          $W.clearTimeout(AI.traduction.toid);
        }
        AJAX_POST(
          '/AJAX/traduction_synchro.php',
          {K:AI.traduction.$$.join(',')},
          // faire les callbacks
          ok, erreur, run, oScope, objAccompagne
        );
   },
*/

  /**
   * Obtient la valeur d'une clé
   * @param[in] {string} K Clé
   * @public
   * @return {string} La valeur, ou la clé si inconnue
   */
  get:function(K)
  {
    var T = AI.traduction.$;
//    return ( typeof T[K] === 'undefined' || T[K] === null ) ? K : T[K];
    return ( !defined(T, K) || T[K] === null ) ? K : T[K];
  }
};

/**
 * Alias helper
 * @see AI.traduction.get
 * @public
 */
var __ = AI.traduction.get;

/**
 * Alias helper
 * @see AI.traduction.add
 * @public
 */
var i18ln = AI.traduction.add;
/**
 * Ajout de méthodes à l'objet Array
 */

if ( !Array.prototype.push )
{
  Array.prototype.push = function()
  {
    var m = this.length, i;
    for ( i = 0; i < arguments.length; i++ )
    {
      this[m + i] = arguments[i];
    }
    return this.length;
  };
}

/*
// fix array methods - I believe I borrowed this from Dean Edwards's IE7 script - same license ;-)
if (![].push) Array.prototype.push = function() {
	for(var i=0; i<arguments.length; i++)
		this[this.length-1] = arguments[i];
	return this.length;
};
*/

// Some of them from Erik Arvidsson <http://erik.eae.net/>
// More documentation could be found here:
// http://www.webreference.com/programming/javascript/ncz/column4/
// An alternative implementation can be found here:
// http://www.nczonline.net/archive/2005/7/231

// Mozilla 1.8 has support for indexOf, lastIndexOf, forEach, filter, map, some, every
// http://developer-test.mozilla.org/docs/Core_JavaScript_1.5_Reference:Objects:Array:lastIndexOf

if ( !Array.prototype.indexOf )
{
  Array.prototype.indexOf = function(o, from)
  {
    if ( !from )
    {
      from = 0;
    }
    else if ( from < 0 )
    {
      from = Math.max(0, this.length + from);
    }

    for ( var i = from, m = this.length; i < m; i++ )
    {
      if ( this[i] === o )
      {
        return i;
      }
    }

    return -1;
  };
}

// http://developer-test.mozilla.org/docs/Core_JavaScript_1.5_Reference:Objects:Array:lastIndexOf
if ( !Array.prototype.lastIndexOf )
{
  Array.prototype.lastIndexOf = function(o, from)
  {
    if ( !from )
    {
      from = this.length - 1;
    }
    else if ( from < 0 )
    {
      from = Math.max(0, this.length + from);
    }

    for ( var i = from; i >= 0; i-- )
    {
      if ( this[i] === o )
      {
        return i;
      }
    }

    return -1;
  };
}

// http://developer-test.mozilla.org/docs/Core_JavaScript_1.5_Reference:Objects:Array:forEach
if ( !Array.prototype.forEach )
{
  Array.prototype.forEach = function(f, o)
  {
    // 'm' must be fixed during loop... see docs
    var m = this.length, i;
    for ( i = 0; i < m; i++ )
    {
      f.call(o, this[i], i, this);
    }
  };
}

if ( !Array.prototype.forEachObject )
{
  Array.prototype.forEachObject = function(f)
  {
    // 'm' must be fixed during loop... see docs
    var m = this.length, i;
    for ( i = 0; i < m; i++ )
    {
      f.call(this[i], this, i);
    }
  };
}

// http://developer-test.mozilla.org/docs/Core_JavaScript_1.5_Reference:Objects:Array:filter
if ( !Array.prototype.filter )
{
  Array.prototype.filter = function(f, o)
  {
    // 'm' must be fixed during loop... see docs
    var m = this.length, r = [], i;
    for ( i = 0; i < m; i++ )
    {
      if ( f.call(o, this[i], i, this) )
      {
        r.push( this[i] );
      }
    }
    return r;
  };
}

// http://developer-test.mozilla.org/docs/Core_JavaScript_1.5_Reference:Objects:Array:map
if ( !Array.prototype.map )
{
  Array.prototype.map = function(f, o)
  {
    // 'm' must be fixed during loop... see docs
    var m = this.length, r = [], i;
    for ( i = 0; i < m; i++ )
    {
      r.push( f.call(o, this[i], i, this) );
    }
    return res;
  };
}

// http://developer-test.mozilla.org/docs/Core_JavaScript_1.5_Reference:Objects:Array:some
if ( !Array.prototype.some )
{
  Array.prototype.some = function(f, o)
  {
    // 'm' must be fixed during loop... see docs
    var m = this.length, i;
    for ( i = 0; i < m; i++ )
    {
      if ( f.call(o, this[i], i, this) )
      {
        return true;
      }
    }

    return false;
  };
}

// http://developer-test.mozilla.org/docs/Core_JavaScript_1.5_Reference:Objects:Array:every
if ( !Array.prototype.every )
{
  Array.prototype.every = function (f, o)
  {
    // 'm' must be fixed during loop... see docs
    var m = this.length, i;
    for ( i = 0; i < m; i++ )
    {
      if ( !f.call(o, this[i], i, this) )
      {
        return false;
      }
    }

    return true;
  };
}

if ( !Array.prototype.contains )
{
  Array.prototype.contains = function(obj)
  {
    return this.indexOf(obj) != -1;
  };
}

if ( !Array.prototype.copy )
{
  Array.prototype.copy = function(obj)
  {
    return this.concat();
  };
}

/*
if (!Array.prototype.clone)
{
  Array.prototype.clone = function(obj)
  {
    return this.concat();
  };
}
*/

/*
if (!Array.prototype.append)
{
  Array.prototype.append = function ()
  {
    for (var i=0, l=arguments.length; i<l; i++)
    {
      this[this.length] = arguments[i];
    }
    return this;
  };
}
*/
if ( !Array.prototype.insertAt )
{
  Array.prototype.insertAt = function(o, i)
  {
    this.splice(i, 0, o);
  };
}

if (!Array.prototype.insertBefore)
{
  Array.prototype.insertBefore = function(o, o2)
  {
    var i = this.indexOf(o2);
    if ( i == -1 )
    {
      this.push(o);
    }
    else
    {
      this.splice(i, 0, o);
    }
  };
}

if ( !Array.prototype.insertAfter )
{
  Array.prototype.insertAfter = function(o, o2)
  {
    var i = this.indexOf(o2);
    if ( i == -1 || i == (this.length-1) )
    {
      this.push(o);
    }
    else
    {
      this.splice(i+1, 0, o);
    }
  };
}

if ( !Array.prototype.removeAt )
{
  Array.prototype.removeAt = function(i)
  {
    return this.splice(i, 1);
  };
}

if ( !Array.prototype.remove )
{
  Array.prototype.remove = function(o)
  {
    var i = this.indexOf(o);
    if ( i != -1 )
    {
      return this.splice(i, 1);
    }
    return null;
  };
}

if ( !Array.prototype.removeAll )
{
  Array.prototype.removeAll = function()
  {
    return this.splice(0, this.length);
  };
}

if ( !Array.prototype.getLast )
{
  Array.prototype.getLast = function()
  {
    return this[this.length-1];
  };
}

if ( !Array.prototype.getFirst )
{
  Array.prototype.getFirst = function()
  {
    return this[0];
  };
}

if ( !Array.prototype.in_array )
{
  Array.prototype.in_array = function(item)
  {
    return this.indexOf(item) != -1;
  };
}

function forceArray()
{
  var result = [];
  for ( var i = 0; i < arguments.length; i++ )
  {
    var elt = arguments[i];
    if ( elt )
    {
      if ( elt.length && elt.length > 0 )
      {
        for ( var j = 0; j < elt.length; j++ )
        {
          result.push(elt[j]);
        }
      }
      else
      {
        result.push(elt);
      }
    }
  }
  return result;
}
/**
 * Ajout de méthodes à l'objet String
 */
String.prototype.contains = function(s) { return this.indexOf(s) != -1; };
String.prototype.toFirstUp = function() { return this.charAt(0).toUpperCase() + this.substr(1); };

String.prototype.toCamelCase = function()
{
  var A = this.split("-");
  var L = A.length;

  if ( L == 1 )
  {
    return A[0];
  }

  var N = this.indexOf("-") === 0 ? A[0].charAt(0).toUpperCase() + A[0].substring(1) : A[0];

  for ( var p, i = 1; i < L; i++ )
  {
    p = A[i];
    N += p.charAt(0).toUpperCase() + p.substring(1);
  }

  return N;
};

String.prototype.trimLeft = function() { return this.replace(/^\s+/, ""); };
String.prototype.trimRight = function() { return this.replace(/\s+$/, ""); };
String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g, ""); };

String.prototype.add = function(v, sep)
{
  if ( this == v )
  {
    return this;
  }
  else if ( this === "" )
  {
    return v;
  }
  else
  {
    if ( !AI.utils.isValid(sep) ) { sep = ","; }

    var a = this.split(sep);

    if ( a.indexOf(v) == -1 )
    {
      a.push(v);
      return a.join(sep);
    }
    else
    {
      return this;
    }
  }
};

String.prototype.remove = function(v, sep)
{
  if ( this == v || this === "" )
  {
    return "";
  }
  else
  {
    if ( !AI.utils.isValid(sep) ) { sep = ","; }

    var a = this.split(sep);
    var p = a.indexOf(v);

    if ( p == -1 ) { return this; }

    do { a.splice(p, 1); }
    while( ( p = a.indexOf(v) ) != -1 );

    return a.join(sep);
  }
};

String.prototype.stripTags = function()
{
  return this.replace(/<\/?[^>]+>/gi, "");
};

/**
 * un string qui doit etre utilisé dans une regexp doit etre encodé d'abord
 * si ce string peut avoir des caractères spéciaux comme la chaine suivante
 * var testString = 'http://www.example/index.htm?page=faq[]()|\\+*'
 */
String.prototype.encodeRE = function()
{
  return this.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1');
};

/*

String.prototype.repeat = function(l){
	return new Array(l+1).join(this);
};

*/

/**
 * Convertit un texte CamelCase au format hyphen (BorderTopMargin = border-top-margin)
 * @param {string} t Texte à transformer
 * @public
 * @return {string} La version avec des tirets du texte CamelCase
 */
function camelToHyphen(t)
{
  var R = '';
  for ( var i = 0, m = t.length; i < m; ++i )
  {
    if ( t.charAt(i) == t.charAt(i).toUpperCase() )
    {
      R = R + '-' + t.charAt(i).toLowerCase();
    }
    else
    {
      R = R + t.charAt(i);
    }
  }
  return R;
}

/**
 * Convertit un texte avec des tirets au format CamelCase (border-top-margin = BorderTopMargin)
 * @param {string} t Texte à transformer
 * @public
 * @return {string} La version CamelCase du texte avec des tirets
 */
function HyphenToCamel(t)
{
  var R = '';
  return R;
}
/*
String.prototype.ellipse = function(maxLength){
    if(this.length > maxLength){
        return this.substr(0, maxLength-3) + '...';
    }
    return this;
}
*/

/*
function toCamel(property)
{
  function convert(prop)
  {
    var test = /(-[a-z])/i.exec(prop);
    return prop.replace(RegExp.$1, RegExp.$1.substr(1).toUpperCase());
  }

  while( property.indexOf('-') > -1 )
  {
    property = convert(property);
  }

  return property;
  //return property.replace(/-([a-z])/gi, function(m0, m1) {return m1.toUpperCase()}) // cant use function as 2nd arg yet due to safari bug
}

function toHyphen(property)
{
  if ( property.indexOf('-') > -1 ) { return property; }

  var converted = '';
  for ( var i = 0, len = property.length; i < len; ++i )
  {
    if ( property.charAt(i) == property.charAt(i).toUpperCase() )
    {
      converted = converted + '-' + property.charAt(i).toLowerCase();
    }
    else
    {
      converted = converted + property.charAt(i);
    }
  }

  return converted;
  //return property.replace(/([a-z])([A-Z]+)/g, function(m0, m1, m2) {return (m1 + '-' + m2.toLowerCase())});
}
*/
/**
 * Ajout de méthodes à l'objet Number
 */

Number.prototype.limit = function(vmin, vmax)
{
  if (vmax !== null && typeof vmax == "number" && this > vmax)
  {
    return vmax;
  }
  else if (vmin !== null && typeof vmin == "number" && this < vmin)
  {
    return vmin;
  }
  else
  {
    // Number is needed, otherwise a object will be returned
    return Number(this);
  }
};

Number.prototype.inRange = function(vmin, vmax) { return this >= vmin && this <= vmax; };
Number.prototype.betweenRange = function(vmin, vmax) { return this > vmin && this < vmax; };

function intval(i)
{
  var R = parseInt(i, 10) || 0;
  R = isNaN(R) ? 0 : R;
  return R;
}

function floatval(f, nb)
{
  var R = parseFloat(f.toString().replace(',', '.')) || 0;
  nb = nb ? nb : 2;
  return R.toFixed(nb);
}
/**
 * Ajout de méthodes à l'objet Function
 */
// est-ce qu'on s'en sert vraiment ?
// implement function apply for browsers which don't support it natively
if ( !Function.prototype.apply )
{
  Function.prototype.apply = function(oScope, args)
  {
    var sarg = [];
    var rtrn, appel;

    if (!oScope) { oScope = window; }
    if (!args) { args = []; }

    for (var i = 0; i < args.length; i++) { sarg[i] = "args["+i+"]"; }

    appel = "oScope._applyTemp_(" + sarg.join(",") + ");";

    oScope._applyTemp_ = this;
    rtrn = eval(appel);

    delete oScope._applyTemp_;

    return rtrn;
  };
}

/**
 * TEST
 

// implement leak free closure
// http://laurens.vd.oever.nl/weblog/items2005/closures/
if (!Function.prototype.closure)
{
  Function.prototype.closure = function(obj)
  {
    // Init object storage.
    if (!window.__objs)
    {
      window.__objs = [];
      window.__funs = [];
    }

    // For symmetry and clarity.
    var func = this;

    // Make sure the object has an id and is stored in the object store.
    var objId = obj.__objId;
    if (!objId)
    {
      __objs[objId = obj.__objId = __objs.length] = obj;
    }

    // Make sure the function has an id and is stored in the function store.
    var funcId = func.__funcId;
    if (!funcId)
    {
      __funcs[funcId = func.__funcId = __funcs.length] = func;
    }

    // Init closure storage.
    if (!obj.__closures)
    {
      obj.__closures = [];
    }

    // See if we previously created a closure for this object/function pair.
    var closure = obj.__closures[funcId];
    if (closure)
    {
      return closure;
    }

    // Clear references to keep them out of the closure scope.
    obj = null;
    func = null;

    // Create the closure, store in cache and return result.
    return __objs[objId].__closures[funcId] = function ()
    {
      return __funcs[funcId].apply(__objs[objId], arguments);
    };
  };
}
*/
// informe le loader principal que le chargement de ce fichier est fini
//AI.setFileState('compat/function.js', AI.STATE_LOADED);
/**
 * Module de snif du navigateur
 * @todo : refondre le système pour abandonner le snif du userAgent au profit de l'existence de méthodes et comportements
 * @todo : commenter correctement
 * @public
 */
AI.Browser = function()
{
  var
  N = navigator,
  A = N.userAgent,
  E = null, // engine
  V = null, // version
  M = 0, // major
  m = 0, // minor
  R = 0, // revision
  B = 0, // build
  Z = null, // emulation
  W; // temp

  if ( $W.opera && (/Opera[\s\/]([0-9\.]*)/.test(A)) )
  {
    E = "opera";
    V = RegExp.$1;

    // Fix Opera version to match wikipedia style
    V = V.substring(0, 3) + "." + V.substring(3);

    Z = A.contains("MSIE") ? "mshtml" : A.contains("Mozilla") ? "gecko" : null;
  }
  else if ( typeof N.vendor == 'string' && N.vendor == "KDE" )
  {
    E = "khtml";
  }
  else if ( typeof N.product == 'string' && N.product == "Gecko" && (/rv\:([^\);]+)(\)|;)/.test(A)) )
  {
    E = "gecko";
    V = RegExp.$1;
  }
  else if ( /MSIE\s+([^\);]+)(\)|;)/.test(A) )
  {
    E = "mshtml";
    V = RegExp.$1;
  }

  if ( V )
  {
    W = V.split('.');
    M = W[0] || 0;
    m = W[1] || 0;
    R = W[2] || 0;
    B = W[3] || 0;
  }

  /*
  ---------------------------------------------------------------------------
    PUBLIC HANDLERS
  ---------------------------------------------------------------------------
  */

  this.engine = E;
  this.version = V;
  this.major = M;
  this.minor = m;
  this.revision = R;
  this.build = B;
  this.emulation = Z;

  this.IE = false;
  this.IE6 = false;
  this.IE7 = false;

  this.mshtml = false;
  this.gecko = false;
  this.opera = false;
  this.khtml = false;
  this[E] = true;
  this.strict = $D.compatMode == 'CSS1Compat' ? true : false;
  this.quirks = $D.compatMode == 'CSS1Compat' ? false : true;
  /* DEPRECATED */
//  this.strictMode = this.strict;
//  this.quirksMode = this.quirks;
};

var IS = new AI.Browser();
/*@cc_on
IS.IE = true;
if ( @_jscript_version == 5.6 ) IS.IE6 = true;
if ( @_jscript_version == 5.7 ) IS.IE7 = true;

// stops IE6 repeat loading of CSS background images
// http://ajaxian.com/archives/no-more-ie6-background-flicker
if ( IS.IE6 )
{
  try { $D.execCommand("BackgroundImageCache", false, true); } catch(e) {}
}

@*/
/**
 * Basé sur QxExtend.js 1.7.2.110 du projet qooxdoo
 * LGPL 2.1: http://creativecommons.org/licenses/LGPL/2.1/
 * http://qooxdoo.oss.schlund.de
 */
var $P;

/**
 * Héritage d'objet
 * @param {object} S (Superclass) La référence vers l'objet de base
 * @param {string} C (ClassName) Le nom (complete) de la classe héritée, les . sont remplacés par des _
 * @return {object} La référence $P
 * @public
 */
/*
Function.prototype.extend = function(S, C)
{
//  var F = new Function();
  function F() {}
  F.prototype = S.prototype;
  $P = this.prototype = new F();
  // humm, est-ce que ça sert réellement ça ?
  this.superclass = S;

  $P.classname = this.classname = C.replace(/\./, '_');
  $P.constructor = this;

  return $P;
};
*/
/**
 * Héritage d'objet
 * @param {object} S (Superclass) La référence vers l'objet de base
 * @param {string} C (ClassName) Le nom (complete) de la classe héritée, les . sont remplacés par des _
 * @return {object} La référence $P
 * @public
 */
AI.extend = function(source, superclass, classname)
{
 function F() {}
 F.prototype = superclass.prototype;
 $P = source.prototype = new F();
 // humm, est-ce que ça sert réellement ça ?
 source.superclass = superclass;

 $P.classname = source.classname = classname.replace(/\./, '_');
 $P.constructor = source;

 return $P;
};

/**
 * Ajout d'un propriété à l'objet
 *
 * Exemple d'utilisation :
 *   MonObjet.addP( { N:'name'[, D:'default'[, T:'type']] } );
 * Types possibles :
 *   int || bool
 *
 * @param {object} p [Properties] Objet contenant les informations de la propriété, les clés sont N (Name), D (Default), T (Type). Seul N est obligatoire
 * @public
 */
/*
Function.prototype.addP = function(p)
{
  p.M = p.N.toFirstUp();

  if ( typeof p.D == 'undefined' )
  {
    p.D = null;
  }

  var V = '_value' + p.M, // clé de sauvegarde de la valeur dans l'objet
      AC = '_autocheck' + p.M; // clé de l'autochecker

  // apply default value
  $P[V] = p.D;

  // construit getFoo(): Returns current stored value
  $P['get' + p.M] = function() { return this[V]; };

  // construit forceFoo(): Set (override) without do anything else
  $P['force' + p.M] = function(N) { this[V] = N; return N; };

  // construit resetFoo(): Reset value to default value
  $P['reset' + p.M] = function() { return this['set' + p.M](p.D); };

  // construit autocheck qui est utilisé lorsque le type est spécifié
  // et que _check n'est pas défini pour la propriété
  $P[AC] = function(N) { return N; };

  if ( typeof p.T === 'string' )
  {
    switch ( p.T )
    {
      case 'bool':
        // construit toggleFoo(): Switch les valeurs booleennes
        $P['toggle' + p.M] = function() { return this['set' + p.M](!this[V]); };
        // construit autocheckFoo()
        $P[AC] = function(N) { return N ? true:false; };
        // construit isFoo()
        $P['is' + p.M] = $P['get' + p.M];
      break;
      case 'int':
        // construit autocheckFoo()
        $P[AC] = function(N) { return intval(N); };
      break;
    }
  }
    

  // construit setFoo(): Définition de la nouvelle valeur
  $P['set' + p.M] = function(N)
  {
    var M = '_modify' + p.M,
        C = '_check' + p.M,
        O = this[V];

    // Ne fait rien si la nouvelle valeur est égale à l'ancienne
    if ( N === O ) { return N; }

    // Vérifie et transforme la nouvelle valeur à travers le checker (ou l'autochecker) avant la sauvegarde
    N = this[ typeof this[C] !== 'undefined' ? C : AC](N);

    // Ne fait rien si la nouvelle valeur est égale à l'ancienne
    if ( N === O ) { return N; }

    // Enregistre la nouvelle valeur
    this[V] = N;

    // Vérifie si un modifier est implémenté et l'exécute
    if ( this[M] )
    {
      try { this[M](N, O); }
      catch(x) { this.error('Echec modif ' + p.N + ' (' + x + '); ' + this.toOid() + '.set' + p.M + '(' + N + ', ' + O + ')', M); }
    }

    return N;
  };

};
*/

/**
 * Ajout d'un propriété à un objet. Doit etre appelé immédiatement après un AI.extend()
 * @param {object} s [Source] Objet source sur lequel ajouter la propriété
 * @param {object} p [Proprietes] Objet contenant les informations de la propriété, les clés sont N (Name), D (Default), T (Type). Seul N est obligatoire
 * @public
 *
 * Exemple d'utilisation :
 *   AI.addProp('name'[, 'default'[, 'type']] );
 * Types possibles :
 *   int || bool
 *
 */
AI.addProp = function(nom, def, type)
{
 var
  methode = nom.toFirstUp(),
  value = '_value' + methode,
  autocheck = '_autocheck' + methode;

 if ( typeof def == 'undefined' ) { def = null; }

 // apply default value
 $P[value] = def;

 // construit getFoo(): Returns current stored value
 $P['get' + methode] = function() { return this[value]; };

 // construit forceFoo(): Set (override) without do anything else
 $P['force' + methode] = function(N) { this[value] = N; return N; };

 // construit resetFoo(): Reset value to default value
 $P['reset' + methode] = function() { return this['set' + methode](def); };

 // construit autocheck par défaut
 $P[autocheck] = function(N) { return N; };

 if ( type && typeof type === 'string' )
 {
  switch ( type )
  {
   case 'bool':
    // construit toggleFoo(): Switch les valeurs booleennes
    $P['toggle' + methode] = function() { return this['set' + methode](!this[value]); };
    // construit autocheckFoo()
    $P[autocheck] = function(N) { return N ? true:false; };
    // construit isFoo()
    $P['is' + methode] = $P['get' + methode];
   break;
   case 'int':
    // construit autocheckFoo()
    $P[autocheck] = function(N) { return intval(N); };
   break;
  }
 }


 // construit setFoo(): Définition de la nouvelle valeur
 $P['set' + methode] = function(nouveau)
 {
  var
   modifier = '_modify' + methode,
   C = '_check' + methode,
   old = this[value];

  // Ne fait rien si la nouvelle valeur est égale à l'ancienne
  if ( nouveau === old ) { return nouveau; }

  // Vérifie et transforme la nouvelle valeur à travers le checker (ou l'autochecker) avant la sauvegarde
  nouveau = this[ typeof this[C] !== 'undefined' ? C : autocheck](nouveau);

  // Ne fait rien si la nouvelle valeur est égale à l'ancienne
  if ( nouveau === old ) { return nouveau; }

  // Enregistre la nouvelle valeur
  this[value] = nouveau;

  // Vérifie si un modifier est implémenté et l'exécute
  if ( this[modifier] )
  {
    try { this[modifier](nouveau, old); }
    catch(x) { this.error('Echec modif ' + nom + ' (' + x + '); ' + this.toOid() + '.set' + methode + '(' + nouveau + ', ' + old + ')', modifier); }
  }

  return nouveau;
 };
};

AI.addMultiProp = function(A)
{
 for ( var i = 0; i < A.length; i++ )
 {
  AI.addProp(A[i][0], A[i][1] || null, A[i][2] || '');
 }
};
/**
 * CONSTRUCTEUR
 * @param {boolean} a        true si autodispose, false si dispose manuel
 * @public
 */
AI.obj = function(a)
{
  this.hashCode = AI.obj._hashCode++;
  if ( a )
  {
    AI.obj.cacheInstance(this);
  }

  return this;
};

/*
---------------------------------------------------------------------------
  CONSTANTES
---------------------------------------------------------------------------
*/

/*
---------------------------------------------------------------------------
  VARIABLES
---------------------------------------------------------------------------
*/

// compteur interne. Utilisé comme identifiant unique par chaque instance d'objet
AI.obj._hashCode = 0;

/*
---------------------------------------------------------------------------
  EXTEND OBJECT
---------------------------------------------------------------------------
*/

//AI.obj.extend(Object, 'AI.obj');
AI.extend(AI.obj, Object, 'AI.obj');

/*
---------------------------------------------------------------------------
  PROPRIETES
---------------------------------------------------------------------------
*/

//
//AI.obj.addP({ N:'enabled', T:'bool', D:true });
AI.addProp('enabled', true, 'bool');

/*
---------------------------------------------------------------------------
  UTILITIES
---------------------------------------------------------------------------
*/

/**
 * Retourne le hashCode en le forcant si il n'existe pas
 * @param {object} o   L'objet à partir duquel obtenir le hashcode
 * @return (integer} Le hashcode
 * @public
 */
AI.obj.toHashCode = function(o)
{
  if ( o.hashCode && o.hashCode !== null ) { return o.hashCode; }
  o.hashCode = AI.obj._hashCode++;
  return o.hashCode;
};

/**
 * Retourne la représentation textuelle d'un objet
 * @return {string} Le texte de l'objet
 * @todo : on pourrait pas enlever les parenthèses de la ternaire ?
 * @public
 */
$P.toString = function() { return ( this.classname ) ? "[object " + this.classname + "]" : "[object Object]"; };

/**
 * Retourne le hashCode unique de l'instance
 * @return (integer} Le hashcode
 * @public
 */
$P.toHashCode = function() { return this.hashCode; };

/**
 * Retourne un id interne plus lisible pour les humains que le hashCode
 * @return {string} L'identifiant human readable
 * @public
 */
$P.toOid = function() { return this.classname.replace(/^AI_/, '') + this.toHashCode(); };

/*
---------------------------------------------------------------------------
  INSTANCE CACHE MANAGER
---------------------------------------------------------------------------
*/

// cache des instances
AI.obj._inst = {};

// cache une instance
AI.obj.cacheInstance = function(I)
{
  AI.obj._inst[I.hashCode] = I;
};

// recupère une instance du cache par son hashCode
AI.obj.getCacheInstance = function(h)
{
  if ( AI.obj._inst[h] !== null )
  {
    return AI.obj._inst[h];
  }
  return null;
};

// recupère une instance du cache par son élément DOM
AI.obj.getCacheInstanceFromElement = function(E)
{
  if ( typeof E.hashCode !== 'undefined' )
  {
    return AI.obj.getCacheInstance(E.hashCode);
  }
  return null;
};

/*
---------------------------------------------------------------------------
  USER DATA
---------------------------------------------------------------------------
*/

// Données privées de l'objet
$P._datas = null;

// Retourne les données privées
$P.getDatas = function() { return this._datas; };

// assign les datas sous format JSON
$P.setDatas = function (d)
{
  this._datas = d;
  this._afterSetDatas();
};

// Supprime les données privées de l'objet
$P.removeDatas = function() { this._datas = null; };

// méthode vide, utilisée pour effectuer des traitements supplémentaires lorsque les datas sont assignées
$P._afterSetDatas = AI.utils.returnTrue;

// Construit les données privées de l'objet en les lisant depuis le contenu d'un élément HTML
// Nécessite une surcharge par les objets capables d'effectuer cette opération en fonction des besoins
$P.readDatas = AI.utils.returnTrue;

/*
---------------------------------------------------------------------------
  CONSOLE INTERFACE
---------------------------------------------------------------------------
*/

$P.info = function(m) { AI.console.info(m); };
$P.warning = function(m) { AI.console.warning(m); };
$P.error = function(m) { AI.console.error(m); };

/*
---------------------------------------------------------------------------
  DISPOSER
---------------------------------------------------------------------------
*/

$P.disposed = false;

// Dispose this object
$P.dispose = function()
{
  if ( this.disposed ) { return true; }

  // supprime les datas
  if ( this._datas )
  {
    for ( var p in this._datas )
    {
      delete this._datas[p];
    }

    delete this._datas;
  }

  // supprimer la référence du cache
  if ( this.hashCode && AI.obj._inst[this.hashCode] )
  {
    delete AI.obj._inst[this.hashCode];
  }

  // supprime les variables
  delete this.hashCode;

  // Marque as disposed
  this.disposed = true;
  
  return true;
};

/*
---------------------------------------------------------------------------
  COMPLETE DISPOSER // appelé depuis AI.application
---------------------------------------------------------------------------
*/

AI.obj.dispose = function()
{
  for ( var i = AI.obj._inst.length; i--; )
  {
    var o = AI.obj._inst[i];
    if ( o !== null )
    {
      o.dispose();
      delete AI.obj._inst[i];
    }
  }
  $P = null;
};
/**
 * CONSTRUCTEUR
 * @param {boolean} A [Autodispose] true si autodispose, false si dispose manuel
 * @public
 */
AI.dispatcher = function(A)
{
 // listeners
 this.$L = {};
 return AI.obj.call(this, A);
};
//AI.dispatcher.extend(AI.obj, 'AI.dispatcher');
AI.extend(AI.dispatcher, AI.obj, 'AI.dispatcher');
/*
---------------------------------------------------------------------------
  EVENT CONNECTION
---------------------------------------------------------------------------
*/

/**
 * Ajoute un listener à l'objet
 * @param {string}   T [Type]     Type de listener
 * @param {function} F [Fonction] Handler du listener
 * @param {object}   O [scOpe]    Objet d'application du listener (this par défaut)
 * @public
 */
$P.addListener = function(T, F, O)
{
  if ( this.disposed ) { return false; }

  if ( typeof this.$L[T] === 'undefined' )
  {
    this.$L[T] = {};
  }

  // Create a special vKey string to allow identification of each bound action
  var K = AI.dispatcher._boundKey(F, O);

  // Finally set up the listeners object
  this.$L[T][K] =
  {
    f : F,
    o : AI.utils.isValid(O) ? O : this
  };
  return true;
};

/**
 * Supprime un listener de l'objet
 * @param {string}   [T]   Type de listener
 * @param {function} [F]   Handler du listener
 * @param {object}   [O]   Objet d'application du listener (this par défaut)
 * @public
 */
$P.removeListener = function(T, F, O)
{
  var L = this.$L;
  if ( this._disposed  || !L || typeof L[T] === 'undefined' ) { return false; }

  // Create a special vKey string to allow identification of each bound action
  var K = AI.dispatcher._boundKey(F, AI.utils.isValid(O) ? O : this);

  // Delete object entry for this action
  delete this.$L[T][K];
  
  return true;
};

/*
---------------------------------------------------------------------------
  EVENT DISPATCH
---------------------------------------------------------------------------
*/

/**
 * Dispatch un listener
 * @param {string} [T] Type de listener
 * @param {object} [A] Objet supplémentaire fourni au listener
 */
// Internal dispatch implementation
$P.dispatch = function(T, A)
{
  if ( this.disposed ) { return false; }

  this._canBubbleDispatch = true;
  if ( this.hasListeners(T) )
  {
    var L = this.$L;
    if ( L )
    {
      // Shortcut for listener data
      var tL = L[T];

      if ( tL )
      {
        var F, O;

        // Handle all events for the specified type
        for ( var h in tL )
        {
          // Shortcuts for handler and object
          F = tL[h].f;
          O = tL[h].o;

          // Call object function
          try
          {
            if ( typeof F === 'function' )
            {
              F.call(O, T, A);
            }
          }
          catch (x)
          {
            this.error('Impossible de dispatcher "' + T + '": ' + x, 'dispatch');
          }
        }
      }
    }
  }

  // Bubble event to parent
  if ( this._canBubbleDispatch && this.getParent )
  {
    var P = this.getParent();
    if ( this._canBubbleDispatch && P !== null )
    {
      P.dispatch(T, A);
    }
  }
  return true;
};

/*
---------------------------------------------------------------------------
  UTILITIES
---------------------------------------------------------------------------
*/

// créé la clé de liaison
AI.dispatcher._boundKey = function(F, o)
{
  return 'evt' + AI.obj.toHashCode(F) + (o ? '_' + AI.obj.toHashCode(o) : '');
};

// Check if there are one or more listeners for an event type
$P.hasListeners = function(T)
{
  return this.$L && typeof this.$L[T] !== 'undefined' && !AI.utils.isObjectEmpty(this.$L[T]);
};

/*
---------------------------------------------------------------------------
  MISCELLEANEOUS
---------------------------------------------------------------------------
*/

// Internal placeholder for bubbling phase of an event.
$P.getParent = AI.utils.returnNull;

/*
---------------------------------------------------------------------------
  DISPOSER
---------------------------------------------------------------------------
*/

$P.dispose = function()
{
  if ( this.disposed ) { return true; }

  for ( var T in this.$L)
  {
    for ( var K in this.$L[T] )
    {
      delete this.$L[T][K];
    }
    delete this.$L[T];
  }
  delete this.$L;
  delete this._canBubbleDispatch;

  return AI.obj.prototype.dispose.call(this);
};
/**
 * CONSTRUCTEUR
 * @param {integer} I (Intervalle) Intervalle initiale
 * @public
 */
AI.timer = function(I)
{
  AI.dispatcher.call(this, false);

  this.setEnabled(false);

  if ( AI.utils.isValidNumber(I) )
  {
    this.setInterval(I);
  }

  // Object wrapper to timer event
  var o = this;
  this.$$I = function() { o._onint(); };

  return this;
};

/*
---------------------------------------------------------------------------
  EXTEND OBJECT
---------------------------------------------------------------------------
*/

//AI.timer.extend(AI.dispatcher, "AI.timer");
AI.extend(AI.timer, AI.dispatcher, "AI.timer");
/*
---------------------------------------------------------------------------
  PROPRIETES
---------------------------------------------------------------------------
*/

//AI.timer.addP({ N:"interval", T:'int', D:1000 });
AI.addProp("interval", 1000, 'int');

/*
---------------------------------------------------------------------------
  MODIFIER
---------------------------------------------------------------------------
*/

// handler d'intervalle
$P.$intH = null;
$P._modifyEnabled = function(V, O)
{
  if ( !this.disposed )
  {
    if ( O )
    {
      clearInterval(this.$intH);
      this.$intH = null;
    }
    else if ( V )
    {
      this.$intH = setInterval(this.$$I, this.getInterval());
    }
  }
};

// humm, je crois qu'il sert pas lui ici
//$P.getId = function() { return this.toHashCode(); };

/*
---------------------------------------------------------------------------
  PUBLIC
---------------------------------------------------------------------------
*/

/**
 * Démarre le timer
 * @public
 */
$P.start = function()
{
  this.setEnabled(true);
};

/**
 * Démarre le timer avec une nouvelle intervalle
 * @param {integer} I (Interval) La valeur de la nouvelle intervalle à appliquer
 * @public
 */
$P.startWith = function(I)
{
  this.setInterval(I);
  this.start();
};

/**
 * Stop le timer
 * @public
 */
$P.stop = function()
{
  this.setEnabled(false);
};

/**
 * Stop et relance le timer
 * @public
 */
$P.restart = function()
{
  this.stop();
  this.start();
};

/**
 * Stop et relance le timer avec une nouvelle intervalle
 * @param {integer} I (Interval) La valeur de la nouvelle intervalle à appliquer
 * @public
 */
$P.restartWith = function(I)
{
  this.stop();
  this.startWith(I);
};

/*
---------------------------------------------------------------------------
  EVENT-MAPPER
---------------------------------------------------------------------------
*/

$P._onint = function()
{
  if ( this.getEnabled() )
  {
    this.dispatch('interval');
  }
};

/*
---------------------------------------------------------------------------
  DISPOSER
---------------------------------------------------------------------------
*/

$P.dispose = function()
{
  if ( this.disposed ) { return true; }

  // Stop interval
  this.stop();

  // Clear handle
  if ( this.$intH )
  {
    clearInterval(this.$intH);
    this.$intH = null;
    try { delete this.$intH; } catch(x) {}
  }

  // Clear object wrapper function
  this.$$I = null;
  try { delete this.$$I; } catch(x) {}

  return AI.dispatcher.prototype.dispose.call(this);
};

/**
 * HELPER
 * @param {object}  F (Fonction)   La référence vers la fonction à appeler
 * @param {object}  S (Scope)      Le scope d'application
 * @param {integer} I (Intervalle) Période d'attente en millisecondes
 */
AI.timer.once = function(F, S, I)
{
  var T = new AI.timer(I);
  T.addListener('interval', function(e) { F.call(S, e); T.dispose(); S = T = null; }, S);
  T.start();
};
/**
 * CONSTRUCTEUR
 * @param {boolean} a        true si autodispose, false si dispose manuel
 * @public
 */
AI.parent = function(a)
{
  AI.dispatcher.call(this, a);

  // Contient tous les childrens
  this._children = [];

  this._layoutTimer = new AI.timer(1000);
  this._layoutTimer.addListener('interval', AI.parent._onLayoutTimer, this);

  return this;
};
//AI.parent.extend(AI.dispatcher, 'AI.parent');
AI.extend(AI.parent, AI.dispatcher, 'AI.parent');

/*
---------------------------------------------------------------------------
  PROPRIETES
---------------------------------------------------------------------------
*/

// Le widget parent (le réel objet, pas un ID ou autre chose)
//AI.parent.addP({ N:'parent' });
AI.addProp('parent');

// Le widget parent (le réel objet, pas un ID ou autre chose)
//AI.parent.addP({ N:'focusManager' });
//AI.addProp('focusManager');

/*
---------------------------------------------------------------------------
  QUEUE DE TRAITEMENT ([append|remove]Child)
---------------------------------------------------------------------------
*/
/*
AI.parent._queue = [];

AI.parent._addToQueue = function(vParent, vChild, vRemplace)
{
  AI.parent._queue.push({P:vParent, C:vChild, R:vRemplace});
};

AI.parent._flushQueue = function()
{
  var Q = AI.parent._queue;
  var instance_cible, instance_parent, elt;
  for ( var i=0, imax=Q.length; i<imax; i++ )
  {
    var q = Q[i];
    var p = q.P;
    var c = q.C;
    if ( q.R )
    {
      p.parentNode.replaceChild(c, p);
    }
    else
    {
      instance_cible = AI.obj.getCacheInstanceFromElement(c);
      if ( instance_cible && AI.utils.isValidNumber(instance_cible._insertIndex) )
      {
        try
        {
          instance_parent = AI.obj.getCacheInstanceFromElement(p);
          elt = instance_parent.getChildren()[instance_cible._insertIndex].getElement();
          p.insertBefore(c, elt);
        }
        catch(ex)
        {
          p.appendChild(c);
        }
      }
      else
      {
        p.appendChild(c);
      }
    }
  }
  AI.parent._queue = [];
};
*/
/*
---------------------------------------------------------------------------
  MODIFIERS
---------------------------------------------------------------------------
*/

// IE perd elt.style.filter (utilisé pour les transparences) lors des déplacements du node dans le document
// donc quand on modifie le parent, on sauvegarde l'info et on informe les childs de faire pareil
$P._saveFilter = function(){};
$P._restoreFilter = function(){};
if ( IS.IE )
{
 $P._saveFilter = function()
 {
  this._savedFilter = this.getElement().runtimeStyle.filter;
  this.forEachChild(function() { this._saveFilter(); });
 };
 $P._restoreFilter = function()
 {
  if ( typeof this._savedFilter != 'undefined' && this._savedFilter !== '' )
  {
   this.getElement().runtimeStyle.filter = this._savedFilter;
  }
  this.forEachChild(function() { this._restoreFilter(); });
 };
}

$P._hasParent = false;

$P._modifyParent = function(V, O)
{
 var refInsert = -1;
  this._hasParent = false;
  if ( O )
  {
    // Supprime du tableau des childrens
    O.getChildren().removeAt( O.getChildren().indexOf(this) );

    this.dispatch('beforeRemoveDom');
    this._saveFilter();
    this.getElement().parentNode.removeChild(this.getElement());
    this.dispatch('afterRemoveDom');
  }

  if ( V )
  {
    this._hasParent = true;

    if ( this._insertIndex && AI.utils.isValidNumber(this._insertIndex) )
    {
      V.getChildren().insertAt(this, this._insertIndex);
      refInsert = this._insertIndex + 1;
      delete this._insertIndex;
    }
    else
    {
      V.getChildren().push(this);
    }

    if ( !this.created )
    {
      this._createElementImpl();
    }

    this.dispatch('beforeInsertDom');
    if ( this.getIdReplace() )
    {
      var c = $(this.getIdReplace());
      this.getElement().id = this.getIdReplace();
      c.parentNode.replaceChild(this.getElement(), c);
//      AI.parent._addToQueue(c, this.getElement(), true);
    }
    else
    {
     if ( refInsert < 0 || refInsert >= V.getChildren().length )
     {
      V.getElement().appendChild(this.getElement());
//      AI.parent._addToQueue(V.getElement(), this.getElement());
     }
     else
     {
//      AI.console.warning('refInsert = ' + refInsert + '/' + V.getChildren().length + '/' + V.getChildren()[refInsert]);
//      AI.console.warning('elt = ' + V.getChildren()[refInsert].$elt.id + '/' + this.getElement().id);
      var xxx = V.getChildren()[refInsert].$elt;
//      AI.console.warning(this.toOid() + '.insertion = ' + xxx + '/' + xxx.id + '/' + xxx.className + '/' + refInsert);
      V.getElement().insertBefore(this.getElement(), xxx);
     }
    }
    this._restoreFilter();
    this.dispatch('afterInsertDom');
  }
};

/*
---------------------------------------------------------------------------
  UTILITIES
---------------------------------------------------------------------------
*/

/*
---------------------------------------------------------------------------
  CHILDREN MANAGMENT: FIRST CHILD
---------------------------------------------------------------------------
*/

// Retourne le premier child
$P.getFirstChild = function() { return this.getChildren().getFirst(); };

// Retourne le premier child visible
//$P.getFirstVisibleChild = function() { return this.getVisibleChildren().getFirst(); };

/*
---------------------------------------------------------------------------
  CHILDREN MANAGMENT: LAST CHILD
---------------------------------------------------------------------------
*/

// Retourne le dernier child
$P.getLastChild = function() { return this.getChildren().getLast(); };

// Retourne le dernier child visible
//$P.getLastVisibleChild = function() { return this.getVisibleChildren().getLast(); };

/*
---------------------------------------------------------------------------
  CHILDREN MANAGMENT: LOOP UTILS
---------------------------------------------------------------------------
*/

// Exécute une fonction sur tous les childs
$P.forEachChild = function(F)
{
/*
  var C=this.getChildren(), chc, i=-1;
  while(chc=C[++i])
  {
    F.call(chc, i);
  }
*/

  var C = this.getChildren();
  for ( var i = 0, m = C.length; i < m; i++ )
  {
    F.call(C[i], i);
  }
};

// Exécute une fonction sur tous les childs visibles
//$P.forEachVisibleChild = function(vFunc)
//{
/*
  var ch=this.getVisibleChildren(), chc, i=-1;
  while(chc=ch[++i])
  {
    vFunc.call(chc, i);
  }
*/

//  var ch=this.getVisibleChildren();
//  for (var i=0, ilen=ch.length; i<ilen; i++)
//  {
//    vFunc.call(ch[i], i);
//  }

  /*
   jslint aime pas cette syntaxe chc=ch[++i],
   voir si ca c'est mieux sans casser
  var ch=this.getVisibleChildren();
  for (var i=0, ilen=ch.length; i<ilen; i++)
  {
    vFunc.call(ch[i], ++i);
  }
  */
//};

/*
---------------------------------------------------------------------------
  CHILDREN MANAGMENT
---------------------------------------------------------------------------
*/

// Retourne le parent de plus haut niveau
// Hummm, ce serait pas *TOUJOURS* window.$$app par hasard ??
$P.getTopLevelWidget = function()
{
  return this._hasParent ? this.getParent().getTopLevelWidget() : null;
};

/**
 * Retourne le parent de plus haut niveau du type "inst"
 * Par exemple, pour qu'un bouton dans une toolbar retrouve son plus haut pere,
 * de type AI.window on fait : (this étant l'instance de AI.button)
 *  this.getParentInstanceOf(AI.window);
 * @todo : ben le faire :)
 * @public
 */
/*
$P.getParentInstanceOf = function(inst)
{
  return null;
};
*/
// Retourne le tableau de tous les childrens
$P.getChildren = function() { return this._children; };

// Retourne le décompte des childrens
$P.getChildrenLength = function() { return this.getChildren().length; };

// Vérifie si le widget à un child
$P.hasChildren = function() { return this.getChildrenLength() > 0; };

// Verifie si il y a des childrens à l'intérieur
$P.isEmpty = function() { return this.getChildrenLength() === 0; };

// Retourne la position du child
$P.indexOf = function(c) { return this.getChildren().indexOf(c); };

// Vérifie si le widget fourni est déjà un child
$P.contains = function(W)
{
  switch( W )
  {
    case null:
      return false;

    case this:
      return true;

    default:
      // try the next parent of the widget (recursive until found)
      return this.contains(W.getParent());
  }
};

// Vérifie si le child fourni est dans la hierarchie ascendante ou descendante
$P.isInHierarchy = function(c)
{
  return this.contains(c) || c.contains(this);
};


/*
---------------------------------------------------------------------------
  CHILDREN MANAGMENT: MANAGE VISIBLE ONES

  uses a cached private property
---------------------------------------------------------------------------
*/

/*
// Return the array of all visible children
// (which are configured as visible=true)
$P._computeVisibleChildren = function()
{
  var vVisible = [];
  var vChildren = this.getChildren();
  var vLength = vChildren.length;

  for (var i=0; i<vLength; i++)
  {
    var vChild = vChildren[i];
    if ( vChild.seeable )
    {
      vVisible.push(vChild);
    }
  }

  return vVisible;
};

// Get length of visible children
$P.getVisibleChildrenLength = function()
{
  return this.getVisibleChildren().length;
};

// Check if the widget has any visible children
$P.hasVisibleChildren = function()
{
  return this.getVisibleChildrenLength() > 0;
};

// Check if there are any visible childrens inside
$P.isVisibleEmpty = function()
{
  return this.getVisibleChildrenLength() === 0;
};
*/

/*
---------------------------------------------------------------------------
  CHILDREN MANAGMENT: ADD
---------------------------------------------------------------------------
*/

/**
 * Ajoute un widget. Ajout multiple autorisé
 * @public
 */
$P.add = function()
{
  for ( var i = 0, m = arguments.length; i < m; i++ )
  {
    var W = arguments[i];
    W.setParent(this);
  }
  
  this.layoutUpdate();

  return this;
};

/**
 * Ajoute un widget à un index spécifique
 * @param {object}  c [Child] Widget a ajouter
 * @param {integer} i [Index] Position d'insertion
 * @public
 */
$P.addAt = function(c, i)
{
  if ( c.getParent() == this )
  {
    var C = this.getChildren(), /* tous les fils */
        I = C.indexOf(c); /* ancien index du widget à insérer */
    if ( I != i )
    {
      if ( I != -1 ) { C.removeAt(I); }
      C.insertAt(c, i);
    }
  }
  else
  {
    c._insertIndex = i;
    c.setParent(this);
  }

  this.layoutUpdate();
};

/**
 * Ajoute un widget devant tous les autres
 * @param {object}  c [Child] Widget a ajouter
 * @public
 */
$P.addAtBegin = function(c) { return this.addAt(c, 0); };

/**
 * Ajoute un widget après tous les autres
 * we need to fix here, when the child is already inside myself, but
 * want to change its position
 * @param {object}  c [Child] Widget a ajouter
 * @public
 */
$P.addAtEnd = function(c)
{
  var L = this.getChildrenLength();
  return this.addAt(c, c.getParent() == this ? L - 1 : L);
};

/**
 * Ajoute un widget devant un autre widget déjà ajouté
 * @param {object}  c [Child]  Widget a ajouter
 * @param {object}  b [Before] Widget de référence
 * @public
 */
$P.addBefore = function(c, b)
{
  var C = this.getChildren(), /* tous les fils */
      T = C.indexOf(b), /* index du widget de référence */
      S = C.indexOf(c); /* index du widget à insérer */

  if ( T == -1 )
  {
    throw new Error('addBefore(' + c + ', ' + b + '); Ref erreur');
  }


  if ( S == -1 || S > T )
  {
    T++;
  }

  return this.addAt(c, Math.max(0, T - 1));
};

/**
 * Ajoute un widget derrière un autre widget déjà ajouté
 * @param {object}  c [Child]  Widget a ajouter
 * @param {object}  a [After] Widget de référence
 * @public
 */
$P.addAfter = function(c, a)
{
  var C = this.getChildren(), /* tous les fils */
      T = C.indexOf(a), /* index du widget de référence */
      S = C.indexOf(c); /* index du widget à insérer */

  if ( T == -1 )
  {
    throw new Error('addAfter(' + c + ', ' + a + '); Ref erreur');
  }

  if ( S != -1 && S < T )
  {
    T--;
  }

  return this.addAt(c, Math.min(C.length, T + 1));
};

/*
---------------------------------------------------------------------------
  CHILDREN MANAGMENT: REMOVE
---------------------------------------------------------------------------
*/

/**
 * Supprime un ou plusieurs childrens
 * @public
 */
$P.remove = function()
{
  for ( var i = 0, m = arguments.length; i < m; i++ )
  {
    var W = arguments[i];
    if ( W.getParent() == this )
    {
      W.setParent(null);
    }
  }
  this.layoutUpdate();
};

/**
 * Supprime le child à l'index spécifié
 * @param {integer} I [Index] Position de suppression
 * @public
 */
$P.removeAt = function(I)
{
  var c = this.getChildren()[I];
  if ( c )
  {
    delete c._insertIndex;
    c.setParent(null);
  }
  this.layoutUpdate();
};

/**
 * Supprime tous les childrens
 * @public
 */
$P.removeAll = function()
{
  var C = this.getChildren(), /* tous les fils */
      W = C[0]; /* la première référence de widget */

  while ( W )
  {
    this.remove(W);
    W = C[0];
  }
};


/*
---------------------------------------------------------------------------
  WIDGET FROM POINT SUPPORT
---------------------------------------------------------------------------
*/

AI.parent._getElementAbsolutePoint = function(chc, x, y)
{
  var xstart, ystart, xstop, ystop;
  if ( !chc || chc.nodeType != 1 ) { return false; }
  xstart = DOM.getPageBoxLeft(chc);
  if ( x > xstart )
  {
    ystart = DOM.getPageBoxTop(chc);
    if ( y > ystart )
    {
      xstop = xstart + chc.offsetWidth;
      if (x < xstop)
      {
        ystop = ystart + chc.offsetHeight;
        if ( y < ystop )
        {
          return [ xstart, xstop, ystart, ystop ];
        }
      }
    }
  }
  return false;
};

$P._getWidgetFromPointHelper = function(x, y)
{
  var ch = this.getChildren();
  for ( var chl=ch.length, i=0; i<chl; i++ )
  {
    if ( AI.parent._getElementAbsolutePoint(ch[i].getElement(), x, y))
    {
      return ch[i]._getWidgetFromPointHelper(x, y);
    }
  }
  return this;
};

$P.getWidgetFromPoint = function(x, y)
{
  var ret = this._getWidgetFromPointHelper(x, y);
//  return ret && ret != this ? ret : null;
  return ret ? ret : null;
};



/*
---------------------------------------------------------------------------
  REMAPPING
---------------------------------------------------------------------------
*/

/**
 * Remap les méthodes parents vers un nouvel objet
 * @param {object} [T] Cible du remap
 * @public
 */
/*
$P.remapChildrenHandlingTo = function(T)
{
 var i, s, t = [ "add", "remove", "addAt", "addAtBegin", "addAtEnd", "removeAt", "addBefore", "addAfter", "removeAll" ];
 this._remap = T;
 for ( i = 0, m = t.length; i < m; i++ )
 {
  s = t[i];
  this[s] = new Function("return this._remap." + s + ".apply(this._remap, arguments)");
 }
 delete this._remap;
};
*/

/*
---------------------------------------------------------------------------
  LAYOUT UPDATER
---------------------------------------------------------------------------
*/


/**
 * Dispatch l'event de changement de layout
 * @param {boolean} I [Instant] Instantanné oui/non, true pour un layoutUpdate immédiat, false pour différer de 1000 ms
 * @public
 */
$P.layoutUpdate = function(I)
{
  this._layoutTimer.stop();

  if ( I )
  {
    AI.parent._onLayoutTimer.call(this);
  }
  else
  {
    this._layoutTimer.start();
  }
};

AI.parent._onLayoutTimer = function()
{
  this.dispatch('layoutUpdate');
  this._layoutTimer.stop();
};

/*
---------------------------------------------------------------------------
  DISPOSER
---------------------------------------------------------------------------
*/

$P.dispose = function()
{
  if ( this.disposed ) { return true; }

  if ( this._layoutTimer !== null )
  {
    this._layoutTimer.dispose();
  }

  if ( this._children )
  {
    for ( var i = this._children.length; i--; )
    {
      this._children[i].dispose();
      delete this._children[i];
    }
    delete this._children;
  }

  this._remapTarget = null;

  return AI.dispatcher.prototype.dispose.call(this);
};
AI.comparaison =
{
 "byString":function(a, b) { return a == b ? 0 : a > b ? 1 : -1; },
 "byStringCaseInsensitive":function(a, b) { return AI.comparaison.byString(a.toLowerCase(), b.toLowerCase()); },
 "byNumber":function(a, b) { return a - b; },
 "byIntegerString":function(a, b) { return intval(a) - intval(b); },
 "byNumberString":function(a, b) { return parseFloat(a) - parseFloat(b); },
 "byIPv4":function(a, b)
 {
  var
   ipA = a.split('.', 4),
   ipB = b.split('.', 4),
   i;

  for ( i = 0; i < 3; i++ )
  {
   a = intval(ipA[i]);
   b = intval(ipB[i]);
   if ( a != b ) { return a - b; }
  }

  return intval(ipA[3]) - intval(ipB[3]);
 },
 "byZIndex":function(a, b) { return a.getZIndex() - b.getZIndex(); }
};

AI.comparaison.byInteger = AI.comparaison.byFloat = AI.comparaison.byNumber;
AI.comparaison.byFloatString = AI.comparaison.byNumberString;
/*
---------------------------------------------------------------------------
  GLOBAL
---------------------------------------------------------------------------
*/

/**
 * Retourne une référence HTMLElement
 * @param {string} e ID de l'élément dont on veut la référence DOM
 * @public
 * @return {HTMLElement} Une référence DOM vers un élément HTML.
 * @todo : a déplacer dans util.js ou carrément dans AI.js
 */
function $(e, D)
{
//  D = D ? D : document;
  D = D || document;
  return typeof e == 'string' ? D.getElementById(e) : e;
}

function $tags(T, D)
{
  D = D || document;
  return D.getElementsByTagName(T);
}
function $tag(T, i, D) { return $tags(T, D)[i || 0]; }

/**
 * Retourne la valeur ou le texte selectionné d'un <select-simple>
 * @param {string} e ID de l'élément select
 * @param {string} t Type d'extraction (value|text)
 * @private
 * @return {string} La valeur ou le texte sélectionné
 * @see selectedValue, selectedText
 * @todo : a déplacer dans util.js ou carrément dans AI.js
 */
function $$selected(e, t)
{
  var E = $(e), O;
  if ( E )
  {
    O = E.options;
    if ( O && O.selectedIndex != -1 )
    {
      return O[O.selectedIndex][t == 'value' ? t : 'text'];
    }
  }
  return '';
}

/**
 * Retourne la valeur sélectionnée d'un <select-simple>
 * @param {String} [e] ID de l'élément select
 * @public
 * @return {String} La valeur selectionnée
 * @see selectedTxt, $$selected
 * @todo : a déplacer dans util.js ou carrément dans AI.js
 */
function selectedValue(e) { return $$selected(e, 'value'); }

/**
 * Retourne le texte sélectionné d'un <select-simple>
 * @param {String} [e] ID de l'élément select
 * @public
 * @return {String} Le texte selectionné
 * @see selectedValue, $$selected
 * @todo : a déplacer dans util.js ou carrément dans AI.js
 */
function selectedTxt(e) { return $$selected(e, 'text'); }

/*
---------------------------------------------------------------------------
  MODIFICATION DE L'ELEMENT NODE
---------------------------------------------------------------------------
*/

/**
 * Détermine si un node est dans un autre node
 * @todo S'en débarrasser dans la version publique, je crois pas qu'on s'en serve, mais à garder sous le coude, c'est une belle solution
 * http://www.quirksmode.org/blog/archives/2006/01/contains_for_mo.html
 */
if ( window.Node && Node.prototype && !Node.prototype.contains )
{
	Node.prototype.contains = function (e)
  {
		return !!(this.compareDocumentPosition(e) & 16);
	};
}

/*
---------------------------------------------------------------------------
  AI.DOM
---------------------------------------------------------------------------
*/

/**
 * Module de gestion du Document Object Model
 * @public
 */
var DOM =
{
  /**
   * Référence vers le tag <head>
   * @public
   */
  head:$tag('head'),

  /**
   * Cache des éléments créés par document.createElement
   * Le clone d'un élément étant plus rapide que la création d'un nouveau,
   * ce cache permet de stocker les éléments créés afin de ne les créer qu'une
   * seule fois.
   * @private
   */
  $$c:{},

  /**
   * Cache des propriétés CSS
   * @private
   */
  $$pc:{},

  /**
   * Convertie et cache une propriété CSS
   * @param {string} P Propriété
   * @private
   */
  cacheProperty:function(P)
  {
    if ( !DOM.$$pc[P] )
    {
      DOM.$$pc[P] =
      {
        camel: toCamel(P),
        hyphen: toHyphen(P)
      };
    }
  },
  
  /**
   * Obtient la propriété au format camelCase (utilise le cache)
   * @param {string} P Propriété
   * @public
   * @return {string}
   */
  getCamel:function(P) { return DOM.$$pc[P].camel; },

  /**
   * Obtient la propriété au format hyphen-case (utilise le cache)
   * @param {string} P Propriété
   * @public
   * @return {string}
   */
  getHyphen:function(P) { return DOM.$$pc[P].hyphen; },

  /**
   * Détermine la position X (left) d'un élément
   * @param {string|HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {integer} La position X de l'élément
   * @see getY
   */
  getX:function(e)
  {
    var E = $(e), C = 0;
    if ( !E ) { return 0; }
    if ( E.offsetParent )
    {
      while ( E.offsetParent )
      {
        C += E.offsetLeft;
        E = E.offsetParent;
      }
    }
    return C;
  },

  /**
   * Détermine la position Y (top) d'un élément
   * @param {string|HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {integer} La position Y de l'élément
   * @see getX
   */
  getY:function(e)
  {
    var E = $(e), C = 0;
    if ( !E ) { return 0; }
    if ( E.offsetParent )
    {
      while ( E.offsetParent )
      {
        C += E.offsetTop;
        E = E.offsetParent;
      }
    }
    return C;
  },

  /**
   * Insère un noeud en première position
   * @param {string|HTMLElement} e Id de l'élément ou directement sa référence
   * @param {string|HTMLElement} p Id du père ou directement sa référence
   */
  insertFirst:function(e, p)
  {
    var E = $(e), P = $(p);
    if ( E && P )
    {
      // humm, c'est pas plutot || !P.firstChild ???
      if ( P.childNodes.length === 0 && P.firstChild )
      {
        P.appendChild(E);
      }
      else
      {
        P.insertBefore(E, P.firstChild);
      }
    }
  },
  
  /**
   * Déplace un noeud devant un autre
   * @param {string|HTMLElement} s Id de l'élément source ou directement sa référence
   * @param {string|HTMLElement} c Id de l'élément cible ou directement sa référence
   * @public
   * @return {HTMLElement} La référence de l'élément déplacé, c'est s
   */
  moveBefore:function(s, c)
  {
    var S = $(s), C = $(c), P;
    if ( C && S && S.parentNode )
    {
    	P = S.parentNode;
    	P.removeChild(S);
    	return P.insertBefore(S, C);
    }
    return null;
  },

  /**
   * Déplace un noeud après un autre
   * @param {string|HTMLElement} s Id de l'élément source ou directement sa référence
   * @param {string|HTMLElement} c Id de l'élément cible ou directement sa référence
   * @public
   * @return {HTMLElement} La référence de l'élément déplacé, c'est s
   */
  moveAfter:function(s, c)
  {
    var S = $(s), C = $(c), P;
    if ( S && S.parentNode )
    {
      P = S.parentNode;
    	P.removeChild(S);
  	  return P.insertBefore(S, C ? C.nextSibling : null);
   }
   return null;
  },

  /**
   * Créé un noeud texte visuellement vide (IE n'aime pas les élément réellement vides)
   * @public
   * @return {HTMLElement} Un noeud texte vide
   */
  emptyTextNode:function()
  {
    return $dct('\u00a0');
  },

  /**
   * Nettoie les noeuds phantômes du document
   * @param {string|HTMLElement} s Id de l'élément ou directement sa référence
   * @param {boolean} r Recusivité, false par défaut (optional)
   * @public
   * @todo Déplacer dans DOM_whitespace.js
   */
  cleanWhitespace:function(e, r)
  {
    var E = $(e), i, N, V;
    if ( E && E.childNodes )
    {
      for ( i = E.childNodes.length - 1; i >= 0; i-- )
      {
        N = E.childNodes[i];
        if ( N.nodeType == document.TEXT_NODE )
        {
          V = N.nodeValue.trim();
          if ( V === '' || V == '\n' || V == '\t' || V == '\r' || V == '\r\n' || V == '\n\r' )
          {
            E.removeChild(N);
          }
        }
        else if ( N.nodeType == document.ELEMENT_NODE && r )
        {
          DOM.cleanWhitespace(N, r);
        }
      }
    }
  },

  /**
   * Retourne le contenu texte de l'élément et de tous ses fils, un peu comme le innerText de IE mais en version DOM
   * @param {string|HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {string} le contenu textuel de l'élément
   * http://slayeroffice.com/code/functions/so_getText.html
   */
  getInnerText:function(e)
  {
    var E = $(e), R = [], i = 0;
    if ( !E) { return ''; }
  	if ( E.textContent ) { return E.textContent; }
  	if ( E.nodeType == document.TEXT_NODE ) { return E.data; }
  	while ( E.childNodes[i] )
    {
  		R.push( DOM.getInnerText(E.childNodes[i]) );
  		i++;
  	}
    return R.join('');
  },

  /**
   * Vide un node de son contenu
   * @param {string|HTMLElement} e Id de l'élément ou directement sa référence
   * @param {array}              p Propriétés à nullifier (optional)
   * @public
   */
  flush:function(e, p)
  {
    var E = $(e);
    if ( E )
    {
    	while ( E.firstChild )
      {
        if ( p )
        {
          for ( var i = p.length; i--; )
          {
            E.firstChild[p] = null;
          }
        }
        E.removeChild(E.firstChild);
      }
    }
  },

  /**
   * Methode de remplacement pour DOM.flush, Vide un node de son contenu
   * http://slayeroffice.com/test/clone_vs_firstchild.html
   * @param {string|HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @todo Vérifier si ca marche bien partout et si ca va réellement plus vite
   */
  flushQuick:function(e)
  {
    var E = $(e), C;
    if ( E )
    {
    	C = E.cloneNode(false);
    	E.parentNode.insertBefore(C, E);
    	E.parentNode.removeChild(E);
    	// ou peut etre mieux :
    	// E.replaceChild(C, E);
    }
  },


  /**
   * Obtient le prochain élément de type n (nodeName) à partir de l'élément e
   * @param {string|HTMLElement} e Id de l'élément ou directement sa référence
   * @param {string}             n Le nodeName recherché (div, li, td, etc)
   * @public
   * @return {HTMLElement} La référence de l'élément suivant
   */
  suivant:function(e, n)
  {
    var E = $(e), S;
    n = n.toUpperCase();
    if ( !E ) { return null; }
    // S = Suivant
  	S = E.nextSibling;
  	while ( S !== null )
    {
  		if ( S.nodeName.toUpperCase() == n )
      {
        return S;
      }
  		S = S.nextSibling;
  	}
  	return null;
  },

  /**
   * Obtient le précédent élément de type n (nodeName) à partir de l'élément e
   * @param {string|HTMLElement} e Id de l'élément ou directement sa référence
   * @param {string}             n Le nodeName recherché (div, li, td, etc)
   * @public
   * @return {HTMLElement} La référence de l'élément précédent
   */
  precedent:function(e, n)
  {
    var E = $(e), P;
    n = n.toUpperCase();
    if ( !E ) { return null; }
  	P = E.previousSibling;
  	while ( P !== null )
    {
  		if ( P.nodeName.toUpperCase() == n )
      {
        return P;
      }
  		P = P.previousSibling;
  	}
  	return null;
  },

  /**
   * Obtient le parent de l'élément e (element) avec le t (tagName) fourni
   * @param {string|HTMLElement} e Id de l'élément ou directement sa référence
   * @param {string}             t Le tagName recherché (div, li, td, etc)
   * @public
   * @return {HTMLElement} La référence de l'élément parent du type désiré
   */
  parent:function(e, t)
  {
    var E = $(e);
    if ( !E ) { return null; }
    t = t.toUpperCase();
    while ( E !== null && E.parentNode )
    {
      // E.tagName ou E.nodeName ???
      if ( E.tagName.toUpperCase() == t )
      {
        return E;
      }
      E = E.parentNode;
    }
    return null;
  },

  /**
   * Obtient le nom réel de la propriété CSS adapté au navigateur
   * La version initiale ne fait que retourner le paramètre, elle est ensuite surchargée selon le navigateur
   * @param {string} [N] Nom de la propriété de style dont on veut le nom réel pour le navigateur (version W3C par défaut)
   * @private
   * @return {string} Le nom a utiliser, spécifique au navigateur courant
   */
  _realCss:function(N) { return N; },

  /**
   * Calcul la somme en pixel de tous les arguments (variables)
   * @public
   * @return {string} L'addition entière des arguments suivi par 'px'
   */
  pix:function()
  {
    var p = 0, i;
    for ( i = arguments.length; i--; )
    {
      p += intval(arguments[i]);
    }
    return p + 'px';
  },

/*
---------------------------------------------------------------------------
  A REFONDRE, A VERIFIER SI UN INTERET EXISTE OU PAS, etc.
---------------------------------------------------------------------------
*/
  /**
   * Obtient les éléments par la class
   * @todo A refaire si on en a vraiment besoin, mais j'en doute
   */
/*
  getElementsByClassName:function(className)
  {
    var children = document.getElementsByTagName('*') || document.all;
    var elements = [];
    for (var i=0, ilen=children.length; i<ilen; i++)
    {
      var child = children[i];
      var classNames = child.className.split(' ');
      for (var j=0, jlen=classNames.length; j<jlen; j++)
      {
        if (classNames[j] == className)
        {
          elements.push(child);
          break;
        }
      }
    }
    return elements;
  },
*/

  /**
   * Obtient les éléments par selector CSS
   * @todo a maintenir avec les dernières versions, si on vient a en avoir besoin
   */
/*
  getElementsBySelector:function(selector) {
    var i;
    var s = [];
    var selid = "";
    var selclass = "";
    var tag = selector;
    var objlist = [];
    if (selector.indexOf(" ")>0) {  //descendant selector like "tag#id tag"
      s = selector.split(" ");
      var fs = s[0].split("#");
      if (fs.length==1) { return objlist; }
      return document.getElementById(fs[1]).getElementsByTagName(s[1]);
    }
    if (selector.indexOf("#")>0) { //id selector like "tag#id"
      s = selector.split("#");
      tag = s[0];
      selid = s[1];
    }
    if (selid!=="") {
      objlist.push($(selid));
      return objlist;
    }
    if (selector.indexOf(".")>0) {  //class selector like "tag.class"
      s = selector.split(".");
      tag = s[0];
      selclass = s[1];
    }
    var v = document.getElementsByTagName(tag);  // tag selector like "tag"
    if (selclass==="") { return v; }
    for (i=0; i<v.length; i++) {
      if (v[i].className==selclass) {
        objlist.push(v[i]);
      }
    }
    return objlist;
  },
*/

/*
  getElementsComputedStyle = function(E, cssProperty, mozillaEquivalentCSS)
  {
    if ( arguments.length == 2 ) { mozillaEquivalentCSS = cssProperty; }
    if ( typeof E == 'string' ) { E = $(E); }
    return (E.currentStyle)? E.currentStyle[cssProperty]:document.defaultView.getComputedStyle(E, null).getPropertyValue(mozillaEquivalentCSS);
  },
*/

  /**
   * Obtient la valeur du style en normalisant currentStyle et ComputedStyle
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @param {String}               P La propriété recherchée
   * @public
   * @return {String} La valeur current de la propriété de style
   */
  getStyle:function(e, P)
  {
    var
    E = $(e),
    V = null,
    d = document.defaultView;

    if ( !E ) { return V; }

    // IE opacity, gestion différente
    // todo : appeller DOM.getOpacity() quand P == 'opacity'
    // todo : créer DOM.getOpacity()
    if ( P == 'opacity' && E.filters )
    { 
      V = 1;
      try
      {
        V = E.filters.item('DXImageTransform.Microsoft.Alpha').opacity / 100;
      }
      catch(x)
      {
        try { V = E.filters.item('alpha').opacity / 100; } catch(x) {}
      }
    }
    else if ( E.style[P] )
    {
       V = E.style[P];
    }
    else if ( E.currentStyle && E.currentStyle[P] )
    {
       V = E.currentStyle[P];
    }
    else if ( d && d.getComputedStyle )
    {
      var c = camelToHyphen(P);

      if ( d.getComputedStyle(E, '').getPropertyValue(c) )
      {
        V = d.getComputedStyle(E, '').getPropertyValue(c);
      }
    }

    return V;
  },
  
  /**
   * Obtient la valeur du style en normalisant currentStyle et ComputedStyle
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @param {String}               P La propriété recherchée
   * @public
   * @return {String} La valeur current de la propriété de style
   * TEST NON EFFECTUE : cette version utilise DOM.$$pc (cache des propriétés) et est censée être plus rapide
   */
  getStyle_PLEASE_TEST_ME:function(e, P)
  {
    var
    E = $(e),
    V = null,
    dv = document.defaultView,
    camel = DOM.getCamel(P),
    hyphen = DOM.getHyphen(P);

    // IE opacity
    if ( P == 'opacity' && E.filters )
    {
      V = 1;
      try
      {
        V = E.filters.item('DXImageTransform.Microsoft.Alpha').opacity / 100;
      }
      catch(x)
      {
        try { V = E.filters.item('alpha').opacity / 100; } catch(x) {}
      }
    }
    // camelCase for valid styles
    else if ( E.style[camel])
    {
      V = E.style[camel];
    }
    // camelCase for currentStyle; isIE to workaround broken Opera 9 currentStyle
    else if ( IS.IE && E.currentStyle && E.currentStyle[camel] )
    { 
      V = E.currentStyle[camel];
    }
    // hyphen-case for computedStyle
    else if ( dv && dv.getComputedStyle )
    {
      if ( dv.getComputedStyle(E, '').getPropertyValue(c) )
      {
        V = dv.getComputedStyle(E, '').getPropertyValue(c);
      }
    }

    return V;
  },

  /**
   * Comme DOM.getStyle(), mais s'assure que le retour est un entier
   *
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @param {String}               P La propriété recherchée
   * @public
   * @return {String} La valeur current de la propriété de style ou 0
   */
  getStyleInt:function(e, P) { return intval(DOM.getStyle(e, P)) || 0; },

  /**
   * Check si un élément possède une classe
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @param {string}               C Nom de la classe à vérifier
   * @public
   * @return {boolean} true si l'élément possède la classe
   */
  hasCSS:function(e, C)
  {
    var E = $(e), c;
    if ( !E || !E.className ) { return false; }
    c = E.className.split(' ');
    return c.contains(C);
  },

  /**
   * Définit la classe d'un élément
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @param {string}               C Nom de la classe à définir
   * @public
   */
  setCSS:function(e, C)
  {
    var E = $(e);
    if ( E )
    {
      E.className = C;
    }
  },

  /**
   * Ajoute une classe à un élément
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @param {string}               C Nom de la classe à ajouter
   * @public
   */
  addCSS:function(e, C)
  {
    var E = $(e), c;
    if ( E )
    {
      if ( E.className )
      {
        c = E.className.split(' ');
        if ( !c.contains(C) )
        {
          c.push(C);
          E.className = c.join(' ');
        }
      }
      else
      {
        E.className = C;
      }
    }
  },

  /**
   * Supprime une classe d'un élément
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @param {string}               C Nom de la classe à supprimer
   * @public
   */
  removeCSS:function(e, C)
  {
    var E = $(e), c;
    if ( E && E.className )
    {
      c = E.className.split(' ');
      if ( c.contains(C) )
      {
        c.remove(C);
        E.className = c.join(' ');
      }
    }
  },

  /**
   * Cache un élément
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   */
  hide:function(e) { DOM.addCSS(e, 'cacher'); },

  /**
   * Affiche un élément
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   */
  show:function(e) { DOM.removeCSS(e, 'cacher'); },

  /**
   * Vérifie si l'élément est caché ou pas (ne gère pas la visibilité des parents)
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {boolean} true si l'élément est caché, false dans le cas contraire
   */
  isHide:function(e) { return DOM.hasCSS(e, 'cacher'); },

  /**
   * Toggle l'affichage d'un élément
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   */
  toggle:function(e) { return DOM.isHide(e) ? DOM.show(e) : DOM.hide(e); },

  /**
   * Toggle l'affichage des childs d'un élément
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   */
  toggleChilds:function(e)
  {
    var C = $(e).childNodes, i;
    for ( i = 0; i < C.length; i++ )
    {
      if ( !DOM.hasCSS(C[i], 'ne_pas_cacher') )
      {
        DOM[ DOM.isHide(C[i]) ? 'show' : 'hide' ](C[i]);
      }
    }
  },

  /**
   * Obtient la taille de la marge gauche
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {integer} La taille de la marge gauche
   */
  getMarginLeft:function(E) { return DOM.getStyleInt(E, 'marginLeft'); },

  /**
   * Obtient la taille de la marge supérieure
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {integer} La taille de la marge supérieure
   */
  getMarginTop:function(E) { return DOM.getStyleInt(E, 'marginTop'); },

  /**
   * Obtient la taille de la marge droite
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {integer} La taille de la marge droite
   */
  getMarginRight:function(E) { return DOM.getStyleInt(E, 'marginRight'); },

  /**
   * Obtient la taille de la marge inférieure
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {integer} La taille de la marge inférieure
   */
  getMarginBottom:function(E) { return DOM.getStyleInt(E, 'marginBottom'); },

  /**
   * Obtient la taille du padding gauche
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {integer} La taille du padding gauche
   */
  getPaddingLeft:function(E) { return DOM.getStyleInt(E, 'paddingLeft'); },

  /**
   * Obtient la taille du padding supérieur
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {integer} La taille du padding supérieur
   */
  getPaddingTop:function(E) { return DOM.getStyleInt(E, 'paddingTop'); },

  /**
   * Obtient la taille du padding droit
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {integer} La taille du padding droit
   */
  getPaddingRight:function(E) { return DOM.getStyleInt(E, 'paddingRight'); },

  /**
   * Obtient la taille du padding inférieur
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {integer} La taille du padding inférieur
   */
  getPaddingBottom:function(E) { return DOM.getStyleInt(E, 'paddingBottom'); },

  /**
   * Obtient la taille de la bordure gauche
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {integer} La taille de la bordure gauche
   */
  getBorderLeft:function(E) { return DOM.getStyle(E, "borderLeftStyle")   == 'none' ? 0 : DOM.getStyleInt(E, "borderLeftWidth"); },

  /**
   * Obtient la taille de la bordure supérieure
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {integer} La taille de la bordure supérieure
   */
  getBorderTop:function(E) { return DOM.getStyle(E, "borderTopStyle")    == 'none' ? 0 : DOM.getStyleInt(E, "borderTopWidth"); },

  /**
   * Obtient la taille de la bordure droite
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {integer} La taille de la bordure droite
   */
  getBorderRight:function(E) { return DOM.getStyle(E, "borderRightStyle")  == 'none' ? 0 : DOM.getStyleInt(E, "borderRightWidth"); },

  /**
   * Obtient la taille de la bordure inférieure
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {integer} La taille de la bordure inférieure
   */
  getBorderBottom:function(E) { return DOM.getStyle(E, "borderBottomStyle") == 'none' ? 0 : DOM.getStyleInt(E, "borderBottomWidth"); },

  /**
   * Applique un style à un élément
   * DOM.applyStyle($('element_id'), 'border:1px solid red; margin:25px;');
   * http://slayeroffice.com/code/functions/so_applyStyleString.html
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   */
  applyStyle:function(e, S)
  {
    var E = $(e);
    if ( E )
    {
      E.setAttribute('style', S);
    }
  },

  /**
   * Définit l'opacité d'un élément
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @param {float}                V Valeur de l'opacite à appliquer, de 0.00 à 1.00
   * @public
   */
  setOpacity:function(e, V)
  {
    var E = $(e);
    if ( E )
    {
      if ( V === null || V >= 1 || V < 0 )
      {
        DOM.removeOpacity(E);
      }
      else
      {
        E.style.opacity = V;
        DOM._setOpacity(E, V);
      }
    }
  },
  
  /**
   * Définit l'opacité d'un élément spécifiquement par navigateur, surchargé selon le navigateur courant
   * @param {HTMLElement} E La référence de l'élément
   * @param {float}       V Valeur de l'opacite à appliquer, de 0.00 à 1.00
   * @private
   */
  _setOpacity:function(E, V) {},

  /**
   * Supprime l'opacité d'un élément
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   */
  removeOpacity:function(e)
  {
    var E = $(e);
    if ( E )
    {
      E.style.opacity = null;
      DOM._removeOpacity(E);
    }
  },

  /**
   * Supprime l'opacité d'un élément spécifiquement par navigateur, surchargé selon le navigateur courant
   * @param {HTMLElement} E La référence de l'élément
   * @private
   */
  _removeOpacity:function(E) {},

/*
function getParentByTagName(el, tagName){
var p = el.parent;
tagName = tagName.toLowerCase();
(while p && tagName != p.tagName.toLowerCase()){
p = p.parentNode;
}
return p;
}
*/

/*
---------------------------------------------------------------------------
  NODES FANTOMES (saut de ligne, commentaires, etc)
---------------------------------------------------------------------------
*/
  whitespace :
  {
    /**
     * Determine whether a node's text content is entirely whitespace.
     *
     * @param nod  A node implementing the |CharacterData| interface (i.e.,
     *             a |Text|, |Comment|, or |CDATASection| node
     * @return     True if all of the text content of |nod| is whitespace,
     *             otherwise false.
     */
    isAll : function(nod)
    {
      // Use ECMA-262 Edition 3 String and RegExp features
      return !(/[^\t\n\r ]/.test(nod.data));
    },

    /**
     * Determine if a node should be ignored by the iterator functions.
     *
     * @param nod  An object implementing the DOM1 |Node| interface.
     * @return     true if the node is:
     *                1) A |Text| node that is all whitespace
     *                2) A |Comment| node
     *             and otherwise false.
     */

    isIgnorable : function( nod )
    {
      return ( nod.nodeType == document.COMMENT_NODE ) ||
             ( ( nod.nodeType == document.TEXT_NODE ) && DOM.whitespace.isAll(nod) );
    },

    /**
     * Version of |previousSibling| that skips nodes that are entirely
     * whitespace or comments.  (Normally |previousSibling| is a property
     * of all DOM nodes that gives the sibling node, the node that is
     * a child of the same parent, that occurs immediately before the
     * reference node.)
     *
     * @param sib  The reference node.
     * @return     Either:
     *               1) The closest previous sibling to |sib| that is not
     *                  ignorable according to |is_ignorable|, or
     *               2) null if no such node exists.
     */
    nodeBefore : function( sib )
    {
      while ( (sib = sib.previousSibling) )
      {
        if ( !DOM.whitespace.isIgnorable(sib) ) { return sib; }
      }
      return null;
    },

    /**
     * Version of |nextSibling| that skips nodes that are entirely
     * whitespace or comments.
     *
     * @param sib  The reference node.
     * @return     Either:
     *               1) The closest next sibling to |sib| that is not
     *                  ignorable according to |is_ignorable|, or
     *               2) null if no such node exists.
     */
    nodeAfter : function( sib )
    {
      while ( (sib = sib.nextSibling) )
      {
        if ( !DOM.whitespace.isIgnorable(sib) ) { return sib; }
      }
      return null;
    },

    /**
     * Version of |lastChild| that skips nodes that are entirely
     * whitespace or comments.  (Normally |lastChild| is a property
     * of all DOM nodes that gives the last of the nodes contained
     * directly in the reference node.)
     *
     * @param sib  The reference node.
     * @return     Either:
     *               1) The last child of |sib| that is not
     *                  ignorable according to |is_ignorable|, or
     *               2) null if no such node exists.
     */
    lastChild : function( par )
    {
      var res = par.lastChild;
      while ( res )
      {
        if ( !DOM.whitespace.isIgnorable(res) ) { return res; }
        res = res.previousSibling;
      }
      return null;
    },

    /**
     * Version of |firstChild| that skips nodes that are entirely
     * whitespace and comments.
     *
     * @param sib  The reference node.
     * @return     Either:
     *               1) The first child of |sib| that is not
     *                  ignorable according to |is_ignorable|, or
     *               2) null if no such node exists.
     */
    firstChild : function( par )
    {
      var res = par.firstChild;
      while ( res )
      {
        if ( !DOM.whitespace.isIgnorable(res) ) { return res; }
        res = res.nextSibling;
      }
      return null;
    },

    /**
     * Version of |data| that doesn't include whitespace at the beginning
     * and end and normalizes all whitespace to a single space.  (Normally
     * |data| is a property of text nodes that gives the text of the node.)
     *
     * @param txt  The text node whose data should be returned
     * @return     A string giving the contents of the text node with
     *             whitespace collapsed.
     */
    dataOf : function( txt )
    {
      // Use ECMA-262 Edition 3 String and RegExp features
      var data = txt.data.replace(/[\t\n\r ]+/g, " ");
      if ( data.charAt(0) == " " )
      {
        data = data.substring(1, data.length);
      }
      if ( data.charAt(data.length - 1) == " " )
      {
        data = data.substring(0, data.length - 1);
      }
      return data;
    }
  },
 disableSelection:function(elt, cursor)
 {
  elt.onselectstart = function() { return false; };
  elt.unselectable = 'on';
  elt.style.MozUserSelect = 'none';
  elt.style.cursor = cursor || 'default';
 }
};

/*
---------------------------------------------------------------------------
  AI.DOM SPECIFICITES DES NAVIGATEURS
---------------------------------------------------------------------------
  IE6
---------------------------------------------------------------------------
*/

if ( IS.IE )
{
  DOM._realCss = function(N)
  {
    if ( N == 'cssFloat' )
    {
      return 'styleFloat';
    }
    return N;
  };

  DOM.applyStyle = function(e, S)
  {
    var E = $(e);
    if ( E )
    {
//      E.style.setAttribute('cssText', S);
      if ( typeof S == 'object' ) { S = S.cssText; }
      E.style.cssText = S;
    }
  };

  DOM._removeOpacity = function(E) { E.style.filter = null; };
  DOM._setOpacity = function(E, V)
  {
    // les filtres ne s'appliquent que sur les éléments qui on hasLayout == true
    // http://www.satzansatz.de/cssd/onhavinglayout.html
    if ( ! ( E.currentStyle && E.currentStyle.hasLayout ) )
    {
      E.style.zoom = 1;
    }
    E.style.filter = 'alpha(opacity=' + Math.round(V * 100) + ')';
    //      E.style.filter = 'alpha(opacity=20)';
  //      E.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + E.src + "',sizingMethod='scale')";
  //      E.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='http://www.internet-ai.com/images/admin/generic/transparent.png',sizingMethod='scale')";

  };
}
/*
---------------------------------------------------------------------------
  GECKO
---------------------------------------------------------------------------
*/
else if ( IS.gecko )
{
  // Fx < 1.5
  if ( IS.major <= 1 && IS.minor < 5 )
  {
    DOM._removeOpacity = function(E) { E.style.MozOpacity = null; };
    DOM._setOpacity = function(E, V) { E.style.MozOpacity = V; };
  }
}
/*
---------------------------------------------------------------------------
  KHTML
---------------------------------------------------------------------------
*/
else if ( IS.khtml )
{
  DOM._removeOpacity = function(E) { E.style.KhtmlOpacity = null; };
  DOM._setOpacity = function(E, V) { E.style.KhtmlOpacity = V; };
}

/*
---------------------------------------------------------------------------
  MODIFICATION DE L'ELEMENT document
---------------------------------------------------------------------------
*/

/**
 * Copie de la fonction d'origine (document.createElement)
 * @private
 */
document._createElement = document.createElement;

/**
 * Copie de la fonction d'origine (document.createDocumentFragment)
 * @private
 */
document._createDocumentFragment = document.createDocumentFragment;

/**
 * Fonction de remplacement de document.createElement
 * Permet de de cloner l'élément une fois qu'une version a déjà été créée au lieu
 * d'en créer un nouveau, le clonage étant plus rapide que la création.
 * @param {string} e Le tagName de l'élément à créer
 * @public
 * @return {HTMLElement} L'élément créé
 */
document.createElement = function(e)
{
	e = e.toLowerCase();

	if ( !DOM.$$c[e] )
  {
		DOM.$$c[e] = document._createElement(e);
	}

	return DOM.$$c[e].cloneNode(false);
};

/**
 * Fonction de remplacement de document.createDocumentFragment
 * Permet de de cloner l'élément une fois qu'une version a déjà été crée au lieu
 * d'en créer un nouveau, le clonage étant plus rapide que la création.
 * @param {string} e Le tagName de l'élément à créer
 * @public
 * @return {HTMLElement} L'élément créé
 */
document.createDocumentFragment = function()
{
  if ( !DOM.$$c.$df )
  {
		DOM.$$c.$df = document._createDocumentFragment();
	}
	return DOM.$$c.$df.cloneNode(false);
};

/*
---------------------------------------------------------------------------
  HELPERS
---------------------------------------------------------------------------
*/

/**
 * Wrapper pour document.createElement
 * @param[in] e {string}      Le nom HTML de l'élément à créer (required)
 * @param[in] o {object}      Hash des différents attributs à appliquer (id, className, onclick, etc.) (optional)
 * @param[in] s {object}      Hash des styles à appliquer (cssFloat, left, marginBottom, etc.) (optional)
 * @param[in] c {array}       Tableau indexé des childs à ajouter (optional)
 * @param[in] t {string}      Mode de manipulation sur le parametre n,  (ac=AppendChild, ib=InsertBefore, if=InsertFirst, rc=ReplaceChild) (optional)
 * @param[in] n {HTMLElement} Node de référence pour le paramètre t (optional)
 * @public
 * @return {HTMLElement} L'élément créé
 */
function $E(e,o,s,c,t,n)
{
 var
  name = o && o.name ? o.name : null,
  E = $dce(e, name), k, i,
//  var m = AI.utils.isValidArray(c) ? c.length : 0;
  m = c && c.length ? c.length : 0;
  if ( o )
  {
    for ( k in o )
    {
     if ( k == 'disableSelection' && o[k] !== false )
     {
      DOM.disableSelection(E, typeof o[k] == 'boolean' ? 'default' : o[k]);
     }
     else
     {
      E[k] = o[k];
     }
    }
  }
  $style(E, s);
  if ( m > 0 )
  {
    for ( i = 0; i < m; i++ )
    {
      E.appendChild(c[i]);
    }
  }
  if ( AI.utils.isValidString(t) && AI.utils.isValidElement(n) )
  {
    t = t.toLowerCase();
    switch ( t )
    {
      case 'ib':
      case 'before':
      case 'insertbefore':
        n.parentNode.insertBefore(E, n);
      break;
      case 'if':
      case 'first':
      case 'insertfirst':
        DOM.insertFirst(E, n);
      break;
      case 'rc':
      case 'replace':
      case 'replacechild':
        n.parentNode.replaceChild(E, n);
      break;
/*
      case 'ac':
      case 'append':
      case 'appendchild':
*/
      default:
        n.appendChild(E);
      break;
    }
  }
  return E;
}

/**
 * Helper pour manipuler les styles d'un élément
 * @param {string|HTMLElement} e [Element]    Id de l'élément ou directement sa référence
 * @param {object}             P [Properties] Propriétés de style à appliquer
 */
function $styleDEPRECATED(e, P)
{
  var E = $(e), k;
  if ( P && E && E.style )
  {
    for ( k in P )
    {
      if ( k == 'opacity' )
      {
        DOM.setOpacity(E, P[k]);
      }
      else
      {
        E.style[DOM._realCss(k)] = P[k];
      }
    }
  }
}

/*
function $style() améliorée - A TESTER A FOND - ne provoque qu'un seul reflow du document
*/
function $style(e, P)
{
  var E = $(e), A, C, k;
  if ( P && E && E.style )
  {
    C = E.cloneNode(true);
    for ( k in P )
    {
      if ( k == 'opacity' )
      {
        DOM.setOpacity(C, P[k]);
      }
      else
      {
        C.style[DOM._realCss(k)] = P[k];
      }
    }
    DOM.applyStyle(E, C.getAttribute('style'));
  }
}

DOM.centerToParent = function(e)
{
  DOM.centerToParentHorizontal(e);
  DOM.centerToParentVertical(e);
};

DOM.centerToParentHorizontal = function(e)
{
  setTimeout(function()
  {
    var
    E = $(e),
    P = E.parentNode,
    W = P && P.tagName && P.tagName.toUpperCase() === 'BODY' ? DOM.getViewportWidth() : P.offsetWidth;
    E.style.left = intval( (W - E.offsetWidth) / 2 ) + 'px';
  }, 1);
};

DOM.centerToParentVertical = function(e)
{
 setTimeout(function()
 {
  var
   E = $(e),
   P = E.parentNode,
   W = P && P.tagName && P.tagName.toUpperCase() === 'BODY' ? DOM.getViewportHeight() : P.offsetHeight;
  E.style.top = intval( (W - E.offsetHeight) / 2 ) + 'px';
 }, 1);
};


/*
---------------------------------------------------------------------------
  ALIAS
---------------------------------------------------------------------------
*/
function $dce(e) { return document.createElement(e); }
// bug certifié au moins sur IS.IE6
// l'attribut NAME ne peut *PAS* etre attribué dynamiquement sur un document.createElement
// donc on doit le créer immédiatement
// http://msdn.microsoft.com/library/default.asp?url=/workshop/author/dhtml/reference/properties/name_2.asp
if ( IS.IE ) { $dce = function(e, n) { return document.createElement( n ? '<' + e + ' name="' + n + '">' : e ); }; }
function $dct(t) { return document.createTextNode(t); }

/* http://www.davidflanagan.com/blog/2006_10.html#000113 */
/*
DOM.buffer = function(id)
{
  var
  elt = $(id),
  buffer = [];
  this.reset = function() { buffer = []; };
  this.clear = function() { elt.innerHTML = ''; };
  this.add = function() { buffer.push.apply(buffer, arguments); };
  this.flush = function()
  {
    elt.innerHTML += buffer.join('');
    buffer = [];
  };
  return this;
};
// exemple d'utilisation :
var out = new DOM.buffer("placeholder");
out.add('<h2>', section_title, '</h2>');
out.add('<p>', content, '</p>');
out.flush();
*/

/*
---------------------------------------------------------------------------
  DEPRECATED
---------------------------------------------------------------------------
*/
//DOM.findPosX = DOM.getX;
//DOM.findPosY = DOM.getY;

/* A REGARDER, c'est du XPath
if( document.evaluate ) {
  var headings = document.evaluate( '//h2|//h3|//h4', document, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null );
  var oneheading;
  while( oneheading = headings.iterateNext() ) {
    ...
  }
} else {
  var allElements = document.getElementsByTagName('*');
  for( var i = 0; i < allElements.length; i++ ) {
    if( allElements[i].tagName.match(/^h[2-4]$/i) ) {
      ...
    }
  }
}
*/
/*
+-Outer----------------------------------------+
|  Margin                                      |
|  +-Box------------------------------+        |
|  |  Border (+ Scrollbar)            |        |
|  |  +-Area--------------------+     |        |
|  |  |  Padding                |     |        |
|  |  |  +-Inner----------+     |     |        |
|  |  |  |                |     |     |        |
|  |  |  +----------------+     |     |        |
|  |  +-------------------------+     |        |
|  +----------------------------------+        |
+----------------------------------------------+
*/

// OUTER
DOM.getOuterWidth = function(el) { return DOM.getBoxWidth(el)  + DOM.getMarginLeft(el) + DOM.getMarginRight(el); };
DOM.getOuterHeight = function(el) { return DOM.getBoxHeight(el) + DOM.getMarginTop(el) + DOM.getMarginBottom(el); };

// BOX
DOM.getBoxWidth = function(el) { return intval(el.offsetWidth); };
DOM.getBoxHeight = function(el) { return intval(el.offsetHeight); };

DOM.getBoxWidthSafe = function(el)
{
  var h = el.offsetHeight, v, o;
  if ( h === 0 )
  {
    o = el.style.height;
    el.style.height = '1px';
  }
  v = el.offsetWidth;
  if ( h === 0 ) { el.style.height = o; }
  return v;
};

DOM.getBoxHeightSafe = function(el)
{
  var w = el.offsetWidth, v, o;
  if ( w === 0 )
  {
    o = el.style.width;
    el.style.width = "1px";
  }
  v = el.offsetHeight;
  if ( w === 0 ) { el.style.width = o; }
  return v;
};

// AREA
if ( IS.gecko )
{
  DOM.getAreaWidth = function(el)
  {
    // 0 in clientWidth could mean both: That it is really 0 or
    // that the element is not rendered by the browser and
    // therefore it is 0, too

    // In Gecko based browsers there is sometimes another
    // behaviour: The clientHeight is equal to the border
    // sum. This is normally not correct and so we
    // fix this value with a more complex calculation.

    // (Mozilla/5.0 (Windows; U; Windows NT 5.1; de-DE; rv:1.7.6) Gecko/20050223 Firefox/1.0.1)

    if ( el.clientWidth !== 0 && el.clientWidth != (DOM.getBorderLeft(el) + DOM.getBorderRight(el)) )
    {
      return el.clientWidth;
    }
    else
    {
      return DOM.getBoxWidth(el) - DOM.getInsetLeft(el) - DOM.getInsetRight(el);
    }
  };

  DOM.getAreaHeight = function(el)
  {
    // 0 in clientHeight could mean both: That it is really 0 or
    // that the element is not rendered by the browser and
    // therefore it is 0, too

    // In Gecko based browsers there is sometimes another
    // behaviour: The clientHeight is equal to the border
    // sum. This is normally not correct and so we
    // fix this value with a more complex calculation.

    // (Mozilla/5.0 (Windows; U; Windows NT 5.1; de-DE; rv:1.7.6) Gecko/20050223 Firefox/1.0.1)

    if ( el.clientHeight !== 0 && el.clientHeight != (DOM.getBorderTop(el) + DOM.getBorderBottom(el)) )
    {
      return el.clientHeight;
    }
    else
    {
      return DOM.getBoxHeight(el) - DOM.getInsetTop(el) - DOM.getInsetBottom(el);
    }
  };
}
else
{
  DOM.getAreaWidth = function(el)
  {
    // 0 in clientWidth could mean both: That it is really 0 or
    // that the element is not rendered by the browser and
    // therefore it is 0, too

    return el.clientWidth !== 0 ? el.clientWidth : (DOM.getBoxWidth(el) - DOM.getInsetLeft(el) - DOM.getInsetRight(el));
  };

  DOM.getAreaHeight = function(el)
  {
    // 0 in clientHeight could mean both: That it is really 0 or
    // that the element is not rendered by the browser and
    // therefore it is 0, too

    return el.clientHeight !== 0 ? el.clientHeight : (DOM.getBoxHeight(el) - DOM.getInsetTop(el) - DOM.getInsetBottom(el));
  };
}

// INNER
DOM.getInnerWidth = function(el) { return DOM.getAreaWidth(el) - DOM.getPaddingLeft(el) - DOM.getPaddingRight(el); };
DOM.getInnerHeight = function(el) { return DOM.getAreaHeight(el) - DOM.getPaddingTop(el) - DOM.getPaddingBottom(el); };

// INSETS
if ( IS.IE )
{
  DOM.getInsetLeft = function(el) { return el.clientLeft; };
  DOM.getInsetTop = function(el) { return el.clientTop; };
  DOM.getInsetRight = function(el)
  {
    if ( DOM.getStyle(el, "overflowY") == 'hidden' || el.clientWidth === 0 )
    {
      return DOM.getBorderRight(el);
    }
    return Math.max(0, el.offsetWidth - el.clientLeft - el.clientWidth);
  };
  DOM.getInsetBottom = function(el)
  {
    if (DOM.getStyle(el, "overflowX") == 'hidden' || el.clientHeight === 0)
    {
      return DOM.getBorderBottom(el);
    }
    return Math.max(0, el.offsetHeight - el.clientTop - el.clientHeight);
  };
}
else
{
  DOM.getInsetLeft = function(el) { return DOM.getBorderLeft(el); };
  DOM.getInsetTop = function(el) { return DOM.getBorderTop(el); };
  DOM.getInsetRight  = function(el)
  {
    // Alternative method if clientWidth is unavailable
    // clientWidth == 0 could mean both: unavailable or really 0
    if ( el.clientWidth === 0 )
    {
      var
      ov = DOM.getStyle(el, 'overflow'),
      sbv = ov == "scroll" || ov == "-moz-scrollbars-vertical" ? 16 : 0;
      return Math.max(0, DOM.getBorderRight(el) + sbv);
    }
    return Math.max(0, el.offsetWidth - el.clientWidth - DOM.getBorderLeft(el));
  };

  DOM.getInsetBottom = function(el)
  {
    // Alternative method if clientHeight is unavailable
    // clientHeight == 0 could mean both: unavailable or really 0
    if (el.clientHeight === 0)
    {
      var
      ov = DOM.getStyle(el, 'overflow'),
      sbv = ov == "scroll" || ov == "-moz-scrollbars-horizontal" ? 16 : 0;
      return Math.max(0, DOM.getBorderBottom(el) + sbv);
    }
    return Math.max(0, el.offsetHeight - el.clientHeight - DOM.getBorderTop(el));
  };
}

// SCROLLBAR
DOM.SCROLLBAR_SIZE = null;

/**
 * Retourne la taille d'une scrollbar
 * @return {Integer} La taille exprimée en pixel
 * @public
 */
DOM.getScrollBarSize = function()
{
  if ( DOM.SCROLLBAR_SIZE ) { return DOM.SCROLLBAR_SIZE; }

  var
  div = $E('div', {}, {height:'100px', width:'100px', overflow:'scroll'}, [], 'ac', $D.body),
  c = DOM.getScrollBarSizeRight(div);
  if ( c )
  {
    DOM.SCROLLBAR_SIZE = c;
  }
  $D.body.removeChild(div);
  return c;
};
DOM.getScrollBarSizeLeft   = function(el) { return 0; };
DOM.getScrollBarSizeTop    = function(el) { return 0; };
DOM.getScrollBarSizeRight  = function(el) { return DOM.getInsetRight(el)  - DOM.getBorderRight(el); };
DOM.getScrollBarSizeBottom = function(el) { return DOM.getInsetBottom(el) - DOM.getBorderBottom(el); };

DOM.isScrollBarVisibleX = function(el) { return DOM.getScrollBarSizeRight(el)  > 0; };
DOM.isScrollBarVisibleY = function(el) { return DOM.getScrollBarSizeBottom(el) > 0; };

DOM.getScrollLeftSum = function(el)
{
  var sum = 0, p = el.parentNode;
  while ( p && p.nodeType && p.nodeType == $D.ELEMENT_NODE )
  {
    sum += p.scrollLeft;
    p = p.parentNode;
  }
  return sum;
};

DOM.getScrollTopSum = function(el)
{
  var sum = 0, p = el.parentNode;
  while ( p && p.nodeType && p.nodeType == $D.ELEMENT_NODE )
  {
    sum += p.scrollTop;
    p = p.parentNode;
  }
  return sum;
};

if ( IS.IE )
{
  DOM.getClientBoxLeft = function(el) { return el.getBoundingClientRect().left; };
  DOM.getClientBoxTop  = function(el) { return el.getBoundingClientRect().top; };

  DOM.getPageBoxLeft = function(el) { return DOM.getClientBoxLeft(el) + DOM.getScrollLeftSum(el); };
  DOM.getPageBoxTop  = function(el) { return DOM.getClientBoxTop(el)  + DOM.getScrollTopSum(el); };
}
else if ( IS.gecko )
{
  DOM.getClientBoxLeft = function(el) { return DOM.getClientAreaLeft(el) - DOM.getBorderLeft(el); };
  DOM.getClientBoxTop  = function(el) { return DOM.getClientAreaTop(el)  - DOM.getBorderTop(el); };

  DOM.getPageBoxLeft = function(el) { return DOM.getPageAreaLeft(el) - DOM.getBorderLeft(el); };
  DOM.getPageBoxTop  = function(el) { return DOM.getPageAreaTop(el)  - DOM.getBorderTop(el); };
}
else
{
  DOM.getPageBoxLeft = function(el)
  {
    var sum = 0;
    while ( el && el.tagName.toUpperCase() != 'BODY' )
    {
      sum += intval(el.offsetLeft);
      el = el.offsetParent;
    }
    return sum;
  };

  DOM.getPageBoxTop = function(el)
  {
    var sum = 0;
    while ( el && el.tagName.toUpperCase() !== 'BODY' )
    {
      sum += intval(el.offsetTop);
      el = el.offsetParent;
    }
    return sum;
  };

  DOM.getClientBoxLeft = function(el)
  {
    var sum = el.offsetLeft;
    while (el && el.tagName.toUpperCase() !== 'BODY')
    {
      el = el.offsetParent;
      if ( el )
      {
        sum += el.offsetLeft - el.scrollLeft;
      }
    }
    return sum;
  };

  DOM.getClientBoxTop = function(el)
  {
    var sum = el.offsetTop;
    while ( el && el.tagName.toUpperCase() !== 'BODY' )
    {
      el = el.offsetParent;
      if ( el )
      {
        sum += el.offsetTop - el.scrollTop;
      }
    }
    return sum;
  };
}

if ( IS.IE )
{
  DOM.getClientBoxRight  = function(el) { return el.getBoundingClientRect().right; };
  DOM.getClientBoxBottom = function(el) { return el.getBoundingClientRect().bottom; };
  DOM.getPageBoxRight    = function(el) { return DOM.getClientBoxRight(el)  + DOM.getScrollLeftSum(el); };
  DOM.getPageBoxBottom   = function(el) { return DOM.getClientBoxBottom(el) + DOM.getScrollTopSum(el);  };
}
else
{
  DOM.getClientBoxRight  = function(el) { return DOM.getClientBoxLeft(el) + DOM.getBoxWidth(el); };
  DOM.getClientBoxBottom = function(el) { return DOM.getClientBoxTop(el)  + DOM.getBoxHeight(el); };
  DOM.getPageBoxRight    = function(el) { return DOM.getPageBoxLeft(el)   + DOM.getBoxWidth(el); };
  DOM.getPageBoxBottom   = function(el) { return DOM.getPageBoxTop(el)    + DOM.getBoxHeight(el); };
}

if ( IS.gecko )
{
  DOM.getPageAreaLeft = function(el) { return el.ownerDocument.getBoxObjectFor(el).x; };
  DOM.getPageAreaTop = function(el) { return el.ownerDocument.getBoxObjectFor(el).y; };
  DOM.getClientAreaLeft = function(el) { return DOM.getPageAreaLeft(el) - DOM.getScrollLeftSum(el); };
  DOM.getClientAreaTop = function(el) { return DOM.getPageAreaTop(el) - DOM.getScrollTopSum(el); };
}
else
{
  DOM.getPageAreaLeft = function(el) { return DOM.getPageBoxLeft(el) + DOM.getBorderLeft(el); };
  DOM.getPageAreaTop  = function(el) { return DOM.getPageBoxTop(el)  + DOM.getBorderTop(el); };
  DOM.getClientAreaLeft = function(el) { return DOM.getClientBoxLeft(el) + DOM.getBorderLeft(el); };
  DOM.getClientAreaTop  = function(el) { return DOM.getClientBoxTop(el)  + DOM.getBorderTop(el); };
}

DOM.getClientAreaRight  = function(el) { return DOM.getClientAreaLeft(el) + DOM.getAreaWidth(el);  };
DOM.getClientAreaBottom = function(el) { return DOM.getClientAreaTop(el)  + DOM.getAreaHeight(el); };
DOM.getPageAreaRight  = function(el) { return DOM.getPageAreaLeft(el) + DOM.getAreaWidth(el);  };
DOM.getPageAreaBottom = function(el) { return DOM.getPageAreaTop(el)  + DOM.getAreaHeight(el); };


/**
 * Returns the current height of the viewport.
 * @return {Int} The height of the viewable area of the page (excludes scrollbars).
 */
DOM.getViewportHeight = function()
{
  var height = -1;

  // IE, Gecko
  if ( ( $D.compatMode || IS.IE ) && !IS.opera )
  {
    // Standards mode
    if ( IS.strict )
    {
      height = $D.documentElement.clientHeight;
    }
    // Quirks
    else
    {
      height = $D.body.clientHeight;
    }
  }
  // Safari, Opera
  else
  {
    height = self.innerHeight;
  }

  return height;
};

/**
 * Returns the current width of the viewport.
 * @return {Int} The width of the viewable area of the page (excludes scrollbars).
 */
DOM.getViewportWidth = function()
{
  var width = -1;

  // IE, Gecko, Opera
  if ( $D.compatMode || IS.IE )
  {
    // Standards mode
    if ( IS.strict )
    {
      width = $D.documentElement.clientWidth;
    }
    // Quirks
    else
    {
      width = $D.body.clientWidth;
    }
  }
  // Safari
  else
  {
    width = self.innerWidth;
  }
  return width;
};

DOM.windowHeight = AI.utils.returnZero;
DOM.windowWidth = AI.utils.returnZero;

if ( self.innerHeight )
{
  DOM.windowHeight = function() { return self.innerHeight; };
  DOM.windowWidth = function() { return self.innerWidth; };
}
else if ( $D.documentElement )
{
  DOM.windowHeight = function() { return $D.documentElement.clientHeight; };
  DOM.windowWidth = function() { return $D.documentElement.clientWidth; };
}
else if ( $D.body )
{
  DOM.windowHeight = function() { return $D.body.clientHeight; };
  DOM.windowWidth = function() { return $D.body.clientWidth; };
}

// informe le loader principal que le chargement de ce fichier est fini
//AI.setFileState('core/DOM_dimension.js', AI.STATE_LOADED);
/**
 * Tableau des CSS loadées
 * @private
 */
DOM._loaded = {};

/**
 * Identifiant interne
 * on peut pas utiliser le length du tableau puisque on peut les supprimer
 * @private
 */
DOM.$id = 0; 

/**
 * Charge une feuille de style
 * @param {string} U L'url a charger
 * @param {string} I L'identifiant (optionnel)
 * @public
 */
DOM.loadStylesheet = function(U, I)
{
//  if (typeof DOM._loaded[U] === 'undefined')
  if ( !DOM._loaded[U] )
  {
    if ( !AI.utils.isValidString(I) )
    {
      I = U.replace(/[^\w]/g, '-').toCamelCase() + DOM.$id++;
    }
    var H = $tag('head'), L = $E("link", {id:I, rel:'stylesheet', href:U});
    // version forçant la sortie du cache
//    L = $E("link", {id:I, rel:'stylesheet', href:U + '?' + (new Date()).valueOf()});
    H.appendChild(L);
    DOM._loaded[U] = I;
  }
};

/**
 * Supprime la feuille de style
 * @param {string} U L'url a charger
 * @public
 */
DOM.removeStylesheet = function(U)
{
//  if (typeof DOM._loaded[U] !== 'undefined')
  if ( DOM._loaded[U] )
  {
    try
    {
      var H = $tag("head"), L = $(DOM._loaded[U]);
      H.removeChild(L);
    } catch(x) {}
    delete DOM._loaded[U];
  }
};


if ( IS.IE )
{
  DOM.createStyleElement = function(C)
  {
    var S = $D.createStyleSheet();
    if ( C )
    {
      S.cssText = C;
    }
    return S;
  };

  DOM.addCssRule = function(vSheet, vSelector, vStyle)
  {
    vSheet.addRule(vSelector, vStyle);
  };

  DOM.removeCssRule = function(vSheet, vSelector)
  {
    var vRules = vSheet.rules, i;
    for ( i = vRules.length; i--; )
    {
      if ( vRules[i].selectorText == vSelector )
      {
        vSheet.removeRule(i);
      }
    }
  };

  DOM.removeAllCssRules = function(S)
  {
    var R = S.rules, i;
    for ( i = R.length; i--; )
    {
      S.removeRule(i);
    }
  };
}
else
{
  DOM.createStyleElement = function(C)
  {
    var E = $E("style", {type:"text/css"});
    if ( C )
    {
      E.appendChild($dct(C));
    }
    $tag('head').appendChild(E);
    return E.sheet;
  };

  DOM.addCssRule = function(S, r, t)
  {
    S.insertRule(r + "{" + t + "}", S.cssRules.length);
  };

  DOM.removeCssRule = function(S, r)
  {
    var R = S.cssRules, i;
    for ( i = R.length; i--; )
    {
      if ( R[i].selectorText == r)
      {
        S.deleteRule(i);
      }
    }
  };

  DOM.removeAllCssRules = function(S)
  {
    var R = S.cssRules, i;
    for ( i = R.length; i--; )
    {
      S.deleteRule(i);
    }
  };
}
/*
---------------------------------------------------------------------------
  Error Extender
---------------------------------------------------------------------------
*/

/**
 * Méthode toString() appliqué à l'objet Error
 * @public
 */
Error.prototype.toString = function() { return this.message; };

/**
 * Surcharge de la méthode toString() si lineNumber et fileName sont accessibles
 * @public
 */
if ( typeof Error.prototype.lineNumber !== 'undefined' && typeof Error.prototype.fileName !== 'undefined' )
{ 
  Error.prototype.toString = function()
  {
    var X = this.fileName.split('/');
    return '#' + this.lineNumber + ':' + X[X.length - 1] + ' "' + this.message + '"';
  };
}

AI.console = AI.getSimilarParentObject('console') || function()
{
 var
  dragged = false, dragX = 0, dragY = 0,
/*  resizeX = 0, resizeY = 0, resizeW = 0, resizeH = 0, */
  actif = true,
  log = $E('ul', {id:'console_main'}, IS.IE6 ? {'height':'300px', 'overflow':'auto'} : {}),
  label = $E('h1', { "disableSelection":'pointer', title:__("Cliquez pour activer/désactiver les rapports d'erreurs dans la console"), onclick:function() { if ( !dragged ) { setActif(!actif); } return false; } }, {}, [$dct(__('Console activée'))] ),
/*  resizer = $E('img', {"id":'console_resizer', "src":'/css/widget/console_resizer.png', "title":'Resize'}),*/
  container = $E(
   'div', { id:'console_container', className:'cacher' }, {},
   [
    $E(
     'div', { id:'console_boutons', className:'clearfix' }, {},
     [
      $E('span',{"disableSelection":'pointer',title:__('Fermer'),onclick:function (){ DOM.hide(container); return false; }}, {"cssFloat":'right'}, [$dct(__('Fermer'))]),
      $E('span',{"disableSelection":'pointer',title:__('Vider'),onclick:function (){ DOM.flush(log); return false; }}, {"cssFloat":'left'}, [$dct(__('Vider'))]),
      label/*, resizer*/
     ]
    ), log
   ]
 );

 function setActif(state)
 {
  actif = state;
  label.innerHTML = actif ? __('Console activée') : __('Console désactivée');
  return actif;
 }

 function message(mode, msg, force)
 {
  if ( ( actif || force ) && AI.utils.isValid(msg) )
  {
   if ( !AI.utils.isValidString(mode) ) { mode = 'info'; }
   $E('li', {className:'console-' + mode},{},[$dct(msg)], 'if', log);
   if ( mode != 'info' ) { DOM.show(container); }
  }
 }
 
 function messageHTML(html)
 {
  if ( actif && AI.utils.isValid(html) )
  {
   $E('li', {"innerHTML":html, "className":'console-html'},{},[], 'if', log);
  }
 }

 function onLabelMouseDown(evt)
 {
  evt = EVT.fix(evt);
  dragged = false;
  dragX = DOM.getX(container) - EVT.getX(evt);
  dragY = DOM.getY(container) - EVT.getY(evt);
  EVT.add(document, "mousemove", onLabelMouseMove);
  EVT.add(document, "mouseup", onLabelMouseUp);
  EVT.preventDefault(evt);
 }

 function onLabelMouseMove(evt)
 {
  evt = EVT.fix(evt);
  /*@cc_on if ( IS.IE6 ) { DOM.hide(log); } @*/
  $style(container, {top:( EVT.getY(evt) + dragY )  + 'px', left:( EVT.getX(evt) + dragX ) + 'px'});
  dragged = true;
 }

 function onLabelMouseUp(evt)
 {
  evt = EVT.fix(evt);
  /*@cc_on if ( IS.IE6 ) { DOM.show(log); } @*/
  EVT.remove(document, "mousemove", onLabelMouseMove);
  EVT.remove(document, "mouseup", onLabelMouseUp);
 }

/*
 function onResizerMouseDown(evt)
 {
  evt = EVT.fix(evt);
  resizeX = EVT.getX(evt);
  resizeY = EVT.getY(evt);
  resizeW = container.offsetWidth;
  resizeH = container.offsetHeight;
  EVT.add(document, "mousemove", onResizerMouseMove);
  EVT.add(document, "mouseup", onResizerMouseUp);
  EVT.preventDefault(evt);
 }
 function onResizerMouseMove(evt)
 {
  var W, H;
  evt = EVT.fix(evt);
  W = resizeW - (resizeX - EVT.getX(evt));
  H = resizeH - (resizeY - EVT.getY(evt));
  $style(container, {"width":W+'px',"height":H+'px'});
 }
 function onResizerMouseUp(evt)
 {
  evt = EVT.fix(evt);
  EVT.remove(document, "mousemove", onResizerMouseMove);
  EVT.remove(document, "mouseup", onResizerMouseUp);
 }
*/

 function layout()
 {
  if ( document.body && DOM && EVT && AJAX )
  {
   DOM.loadStylesheet('/css/widget/console.css', 'console');
   document.body.appendChild(container);
   label.onmousedown = onLabelMouseDown;
/*   resizer.onmousedown = onResizerMouseDown;*/
   message('info', __('Console activée'));
  }
  else
  {
   setTimeout(layout, 200);
  }
 }
 layout();

 function appendText(obj, tbl)
 {
  tbl.push(AI.utils.escapeHTML(obj));
 }

 function appendNull(obj, tbl)
 {
  tbl.push('<span class="console-null">', AI.utils.escapeHTML(obj), '</span>');
 }

 function appendBoolean(obj, tbl)
 {
  tbl.push('<span class="console-boolean">', AI.utils.escapeHTML(obj), '</span>');
 }

 function appendString(obj, tbl)
 {
  tbl.push('<span class="console-string">&quot;', AI.utils.escapeHTML(obj), '&quot;</span>');
 }

 function appendNumber(obj, tbl)
 {
  tbl.push('<span class="console-number">', AI.utils.escapeHTML(obj), '</span>');
 }

 function appendFunction(obj, tbl)
 {
  var
   r = /function ?(.*?)\(/,
   m = r.exec(obj),
   n = m ? m[1] : "function";
  if ( n.trim() === '' ) { n = 'function'; }
  tbl.push('<span class="console-function">', AI.utils.escapeHTML(n), '()</span>');
 }

 function appendObjectFormatted(obj, tbl)
 {
  var
   r = /\[object (.*?)\]/,
   m = r.exec(obj);
  tbl.push('<span class="console-object">', m ? m[1] : obj, '</span>');
  if ( typeof obj.length === 'number' ) { tbl.push(' [' + obj.length + ']'); }
 }

 function appendSelector(obj, tbl)
 {
  tbl.push('<span class="console-selectorTag">', AI.utils.escapeHTML(obj.nodeName.toLowerCase()), '</span>');
  if ( obj.id ) { tbl.push('<span class="console-selectorId">#', AI.utils.escapeHTML(obj.id), '</span>'); }
  if ( obj.className ) { tbl.push('<span class="console-selectorClass">.', AI.utils.escapeHTML(obj.className), '</span>'); }
 }

 function appendObject(obj, tbl)
 {
  if ( obj === undefined || typeof obj === 'undefined' ) { appendNull("undefined", tbl); }
  else if ( obj === null ) { appendNull("null", tbl); }
  else if ( typeof obj == "string" ) { appendString(obj, tbl); }
  else if ( typeof obj == "boolean" ) { appendBoolean(obj, tbl); }
  else if ( typeof obj == "number" ) { appendNumber(obj, tbl); }
  else if ( typeof obj == "function" ) { appendFunction(obj, tbl); }
  else if ( obj.nodeType == document.ELEMENT_NODE ) { appendSelector(obj, tbl); }
  else if ( typeof obj == "object" ) { appendObjectFormatted(obj, tbl); }
  else { appendText(obj, tbl); }
 }

 function appendNode(node, tbl)
 {
  var i, attr, child, val;
  if ( node.nodeType == document.ELEMENT_NODE )
  {
   tbl.push('<div class="console-element">', '&lt;<span class="console-nodeTag">', node.nodeName.toLowerCase(), '</span>');
   for ( i = 0; i < node.attributes.length; i++ )
   {
    attr = node.attributes[i];
    if ( !attr.specified ) { continue; }
    tbl.push('&nbsp;<span class="console-nodeName">', attr.nodeName.toLowerCase(),
     '</span>=&quot;<span class="console-nodeValue">', AI.utils.escapeHTML(attr.nodeValue),
     '</span>&quot;');
   }
   if ( node.firstChild )
   {
    tbl.push('&gt;</div><div class="console-nodeChildren">');
    for ( child = node.firstChild; child; child = child.nextSibling ) { appendNode(child, tbl); }
    tbl.push('</div><div class="console-element">&lt;/<span class="console-nodeTag">', node.nodeName.toLowerCase(), '&gt;</span></div>');
   }
   else
   {
    tbl.push('/&gt;</div>');
   }
  }
  else if ( node.nodeType == document.TEXT_NODE )
  {
   val = node.nodeValue.trim();
   if ( ! ( val === '' || val == '\n' || val == '\t' || val == '\r' || val == '\r\n' || val == '\n\r' ) )
   {
    tbl.push('<div class="console-nodeText">', AI.utils.escapeHTML(node.nodeValue), '</div>');
   }
  }
 }

 function __setActif__(A) { return setActif(A); }
 function __toggle__(force) { DOM[ force ? 'show' : 'toggle'](container); }
 function __show__() { DOM.show(container); }
 function __hide__() { DOM.hide(container); }
 function __info__(msg, force) { message('info', msg, force); }
 function __warning__(msg, force) { message('warning', msg, force); }
 function __error__(msg, force) { message('error', msg, force); }
 function __html__(html) { messageHTML(html); }
 function __assert__(truth, msg) { if ( !truth ) { message('error', msg); } }
 function __explore__(obj, own, restreint)
 {
  var
   html = [], pairs = [],
   name, value, shortName,
   i, reg;

  function checkOwn(v) { return true; }
  function checkRestreint(v) { return true; }
  function checkFn(v) { return checkOwn(v) && checkRestreint(v); }
  if ( own ) { checkOwn = function(v) { return obj.hasOwnProperty(v); }; }
  if ( restreint )
  {
   reg = typeof restreint == 'string' ? new RegExp(restreint) : restreint;
   checkRestreint = function(v) { try { return reg.test(v); } catch(e) {__error__(e, true); } return false; };
  }

  for ( name in obj ) { if ( checkFn(name) ) { pairs.push([name, obj[name]]); } }
  pairs.sort(function(a, b) { return a[0] < b[0] ? -1 : 1; });

  html.push('<table>');
  for ( i = 0; i < pairs.length; i++ )
  {
   name = pairs[i][0];
   value = pairs[i][1];
   shortName = name.substr(0, 20);
   if ( shortName != name ) { shortName = shortName + '...'; }
   html.push('<tr><td class="console-propertyNameCell" title="', AI.utils.escapeHTML(name), '"><span class="console-propertyName">', AI.utils.escapeHTML(shortName), '</span></td><td>');
   appendObject(value, html);
   html.push('</span></td></tr>');
  }
  html.push('</table>');
  messageHTML(html.join(''));
 }
 function __exploreNode__(node)
 {
  var html = [];
  appendSelector(node, html);
  appendNode(node, html);
  messageHTML(html.join(''));
 }

 return {
  "setActif":__setActif__,
  "toggle":__toggle__, "show":__show__, "hide":__hide__,
  "info":__info__, "warning":__warning__, "error":__error__, "html":__html__,
  "assert":__assert__, "explore":__explore__, "exploreNode":__exploreNode__
 };
}();

/**
 * gestionnaire global onerror (non implémenté dans opera)
 * @param {string}  [M] Le message d'erreur
 * @param {string}  [U] L'url de l'erreur
 * @param {integer} [L] Ligne ou s'est produite l'erreur
 * @private
 */
//window.onerror = function(M, U, L)
//{
//  alertError('Erreur : ' + M + CR + 'ligne : ' + L + CR + 'url : ' + U);
/*
  // @todo : mettre en production le log des erreurs
  var i = new Image();
  i.src = '/js/ai/logerror.php?msg=' + encodeURIComponent(msg) + '&url=' + encodeURIComponent(url) + '&lno=' + encodeURIComponent(lno);
  i = null;
*/
//  return false;
//};

/*
---------------------------------------------------------------------------
  MISCELLEANEOUS
---------------------------------------------------------------------------
*/

/**
 * Déclenche une erreur, méthode surchargée au fur et à mesure que le système se met en place
 * @param {string} [M] Message a afficher
 * @private
 */
AI._throwError = function(M)
{
  AI.console.error(M, true);
  throw new Error(M);
};
/**
 * Bibliothèque de gestion événements (events)
 *
 * La bibliothèque fournie des méthodes pour ajouter et supprimer des listeners d'events.
 * Elle essaye également de supprimer automatiquement sur 'onunload' les listeners qui
 * n'auraient pas été manuellement supprimé du document.
 *  - remplace le mot clé "this" dans son contexte
 *  - découple les fonctions pour éviter les leaks
 *  - permet de fournir un objet optionnel en paramètre à la méthode callback
 *  - permet de changer le scope de "this" sur l'objet personnalisé
 * voir :
 *  - http://www.quirksmode.org/blog/archives/2005/09/addevent_recodi.html
 *  - http://www.quirksmode.org/blog/index.html
 *  - http://developer.yahoo.net/yui/event/index.html
 *  - http://ejohn.org/projects/flexible-javascript-events/
 *  - http://simon.incutio.com/archive/2004/05/26/addLoadEvent
 *  - http://novemberborn.net/javascript/event-cache/follow-up
 *  - http://www.schillmania.com/script/addeventhandler.js
 *  - http://www.dithered.com/javascript/dom2_events/index.html
 *  - http://www.dithered.com/javascript/dom2_events/dom2_events.txt
 *
 * @type object
 * @constructor
 * @public
 */

var EVT =
{
/*
---------------------------------------------------------------------------
  CONSTANTES
---------------------------------------------------------------------------
*/

msg_unload:__('DECHARGEMENT EN COURS'),

/**
 * Correspondances des touches courantes
 *
 * @type object
 * @public
 * @todo rendre plus lisible
 * @todo manque des correspondances
 * @todo _0, _1, .., _9 n'est pas très parlant
 */
touches :
{
  esc: 27, enter: 13, tab: 9, space: 32,
  up: 38, down: 40, left: 37, right: 39,
  shift: 16, ctrl: 17, alt: 18,
  f1: 112, f2: 113, f3: 114, f4: 115, f5: 116, f6: 117, f7: 118, f8: 119, f9: 120, f10: 121, f11: 122, f12: 123,
  del: 46, backspace: 8, insert: 45, home: 36, end: 35,
  pageup: 33, pagedown: 34,
  numlock: 144,
  numpad_0: 96, numpad_1: 97, numpad_2: 98, numpad_3: 99, numpad_4: 100, numpad_5: 101, numpad_6: 102, numpad_7: 103, numpad_8: 104,  numpad_9: 105,
  numpad_divide: 111, numpad_multiply: 106, numpad_minus: 109, numpad_plus: 107,
  _0: 48, _1: 49, _2: 50, _3: 51, _4: 52, _5: 53, _6: 54, _7: 55, _8: 56, _9: 57
},

/*
---------------------------------------------------------------------------
  VARIABLES
---------------------------------------------------------------------------
*/

/**
 * Derniere position X détectée
 * @public
 */
X : 0,
/**
 * Derniere position X détectée
 * @public
 */
Y : 0,

/**
 * Events listeners
 * @private
 */
listeners : [],

/**
 * DOM0 handlers
 * @private
 */
DOM0Handlers : [],

/**
 * DOM0 listeners
 * @private
 */
DOM0Listeners : [],

/**
 * Flag indiquant si le processus de chargement peut continuer
 */
_keep_loading : true,

/**
 * tableau des scripts à lancer sur le onload
 */
$L : [],

/**
 * tableau des scripts à lancer sur le onunload
 */
$U : [],

/*
---------------------------------------------------------------------------
  HANDLERS
---------------------------------------------------------------------------
*/

/**
 * Fixe l'event fournit comme premier paramètre au listener
 * IE ne fournit pas cet objet, il le place dans window.event
 * @param {Event} evt L'event ou null si IE
 * @return {Event} L'event
 * @private
 */
// http://www.outofhanwell.com/blog/index.php?title=cross_window_events&more=1&c=1&tb=1&pb=1
//fix : function(e) { return event || ((this.ownerDocument || this.document || this).parentWindow || window).event;},
fix : function(e) { return e || $W.event; },

/**
 * Ajoute un gestionnaire d'event à un élément
 * @param {string | HTMLElement} source       Id de l'élément ou directement sa référence
 * @param {string | array}       type         Le type d'event à ajouter
 * @param {function}             handler      La fonction que l'event appelle
 * @param {object}               scope        Scope d'exécution
 * @param {object}               arbitraryObj Un objet qui sera passé comme paramètre au gestionnaire
 * @param {boolean|string}       forceDom0    true si le modele DOM0 est imposé
 * @return {boolean} true si l'action a réussi, false si la fonction n'a pas pu être attachée à l'élément
 * @public
 */
add : function(source, type, handler, scope, arbitraryObj, forceDom0)
{
  // manage multiple type with an array
//  if ( typeof type !== 'string' && type.length && type.length > 0 )
  if ( AI.utils.isValidArray(type) )
  {
    var returnValue = true; /* return value */
    for ( var j = 0, m = type.length; j < m; j++ )
    {
      returnValue = EVT.add(source, type[j], handler, scope, arbitraryObj, forceDom0) && returnValue;
    }
    return returnValue;
  }

  var element = $(source);
  if ( !element ) { return false; }

  // determine application scope
  scope = scope ? scope : element;

  var wrapped = function(z) { return handler.call(scope, EVT.fix(z), arbitraryObj); }, /* Wrapped function called */
      listenerIndex = EVT.listeners.length, /* global listener index */
      isDOM0 = false; /* flag the type of event model used */

  // check if DOM0 must be used
  if ( forceDom0 || EVT._useDOM0(element, type) )
  {
    // bind the event to the object with DOM0 model
    var DOM0Index = EVT._getDOM0Index(element, type);
    if ( DOM0Index == -1 )
    {
      DOM0Index = EVT.DOM0Handlers.length;
      // cache the signature for the DOM0 event
      // [element, type, alreadySetHandler]
      EVT.DOM0Handlers[DOM0Index] = [element, type, element['on' + type]];
      EVT.DOM0Listeners[DOM0Index] = [];

      // set our own handler which will be used to fire every listeners
      // for this type of event on this source
      element['on' + type] = function(evt) { return EVT._fireDOM0.call(element, EVT.fix(evt), DOM0Index); };
    }

    // add a reference to the wrapped listener to
    // the custom stack of events
    if ( forceDom0 == 'prepend' && EVT.DOM0Listeners[DOM0Index].length > 0 )
    {
      EVT.DOM0Listeners[DOM0Index].unshift(listenerIndex);
    }
    else
    {
      EVT.DOM0Listeners[DOM0Index].push(listenerIndex);
    }
    isDOM0 = true;
  }
  else
  {
    EVT.bind(element, type, wrapped);
  }

  // cache the listener so we can try to automatically unload
  EVT.listeners[listenerIndex] = [element, type, handler, wrapped, scope, arbitraryObj, isDOM0];

  return true;
},


/**
 * Supprime un gestionnaire d'event
 * @param {string | HTMLElement} source   Id de l'élément ou directement sa référence
 * @param {string | array}       type     Le type d'event à supprimer
 * @param {function}             handler  La fonction que l'event appelle
 * @param {object}               scope    Scope d'exécution
 * @return {boolean} true si la suppression c'est bien passée
 * @public
 */
remove : function(source, type, handler, scope)
{
  var returnValue = true; /* return value */
  // manage multiple type with an array
//  if ( typeof type !== 'string' && type.length && type.length > 0 )
  if ( AI.utils.isValidArray(type) )
  {
    for ( var j = 0, m = type.length; j < m; j++ )
    {
      returnValue = EVT.remove(source, type[j], handler) && returnValue;
    }
    return returnValue;
  }
  var element = $(source);
  if ( !element ) { return false; }

  // determine application scope
  scope = scope ? scope : element;

  var listener = null,
      listenerIndex = EVT._getListenerIndex(element, type, handler, scope);
  if ( listenerIndex >= 0 )
  {
    listener = EVT.listeners[listenerIndex];
  }
  if ( !listener ) { return false; }

  // listener is set like this : [element, type, handler, wrapped, scope, arbitraryObj, isDOM0];

  // remove DOM0 listeners
  if ( listener[6] === true )
  {
    returnValue = EVT.removeDOM0(listenerIndex, element, type);
  }
  // remove DOM2 et IE listeners
  else
  {
    try
    {
      EVT.unbind(element, type, listener[3]);
    }
    catch(x)
    {
      try
      {
        throw('Error removing event : "' + type + '"\nExc: ' + x + '\nElt: ' + element + ( element.tagName ? element.tagName : '' ) + '\nHandler : ' + listener[3]);
      }
      catch (x)
      {
        returnValue = false;
      }
    }
  }

  // removed the wrapped handler
  delete EVT.listeners[listenerIndex][3];
  delete EVT.listeners[listenerIndex][2];
  delete EVT.listeners[listenerIndex];

  return returnValue;
},

/**
 * Annule complètement un event
 *
 * @param {Event} e L'event
 * @public
 */
stop : function(e)
{
  EVT.stopPropagation(e);
  EVT.preventDefault(e);
  return false;
},

/**
 * Find the listener position in the listeners cache
 * listener is set like this : [element, type, handler, wrapped, scope, arbitraryObj, isDOM0];
 * @param {HTMLElement} element  element reference
 * @param {string}      type     Event type to search
 * @param {function}    handler  Event handler to find
 * @param {object}      scope    Execution scope to match (optional) default to the element reference
 * @return {integer} Index in the global listeners array
 * @private
*/
_getListenerIndex : function(element, type, handler, scope)
{
  // determine application scope
  scope = scope ? scope : element;

  for ( var i = 0; i < EVT.listeners.length; i++ )
  {
    var L = EVT.listeners[i];
    if ( L && L[0] == element && L[1] == type && L[2] == handler && L[4] == scope )
    {
      return i;
    }
  }
  return -1;
},

/**
 * Retourne la cible "related" à l'event
 *
 * @param {Event} e L'event
 * @return {HTMLElement} La relatedTarget de l'event
 * @public
 */
getRelatedTarget : function(e)
{
  var t = e.relatedTarget;
  if ( !t )
  {
    if (e.type == "mouseout")
    {
      t = e.toElement;
    }
    else if ( e.type == "mouseover" )
    {
      t = e.fromElement;
    }
  }
  return t;
},

/**
 * Retourne la position X de l'event
 *
 * @param {Event} e L'event
 * @return {int} La position X
 */
getX : function(e)
{
  var x = e.pageX;
  if ( !x && 0 !== x )
  {
    x = e.clientX || 0;
    x += EVT.getScroll('Left');
  }
  EVT.X = x;
  return x;
},

/**
 * Retourne la position Y de l'event
 *
 * @param {Event} e L'event
 * @return {int} La position Y
 */
getY : function(e)
{
  var y = e.pageY;
  if ( !y && 0 !== y )
  {
    y = e.clientY || 0;
    y += EVT.getScroll('Top');
  }
  EVT.Y = y;
  return y;
},


/*
---------------------------------------------------------------------------
  MODELE TRADITIONNEL DOM0
---------------------------------------------------------------------------
*/

/**
 * Routine qui détermine si on doit utiliser le modèle traditionnel ou le model DOM2
 *
 * @param {Object}   E         L'élément html
 * @param {String}   T         Le type d'event
 * @return {boolean} true si le modèle traditionnel doit être utilisé, false si on utilise le modèle DOM2
 * @private
 */
_useDOM0 : function(E, T)
{
  return ( ( !E.addEventListener && !E.attachEvent ) || ( T == "click" && navigator.userAgent.match(/safari/gi) ) );
},

/**
 * Retourne l'index de l'event qui correspond à la signature E + T fournie
 *
 * @param {Object}   E         L'élément html
 * @param {String}   T         Le type d'event
 * @return {int} La position enregistrée
 * @private
 */
_getDOM0Index : function(E, T)
{
  for ( var i = 0, m = EVT._DOM0.length; i < m; i++ )
  {
    var L = EVT._DOM0[i];
    if ( L && L[0] == E && L[1] == T )
    {
      return i;
    }
  }
  return -1;
},

/**
 * Lorsque le modèle traditionnel est utilisé, la routine est routée sur cette
 * fonction qui nous permet de déclencher notrz liste d'events associés
 *
 * @param {Event} evt        L'event généré
 * @param {int}   DOM0Index  La position
 * @return {int} true si l'action a réussi, false si un listener n'a pas pu etre déclenché
 * @private
 */
_fireDOM0 : function(evt, DOM0Index)
{
  var returnValue = true, /* return value */
      DOMListeners = EVT.DOM0Listeners[DOM0Index], /* DOM0 listeners */
      previousListener = EVT.DOM0Handlers[DOM0Index][2], /* previously set DOM0 listener */
      returnListener; /* return value of the listener function */
  for ( var i = 0, m = DOMListeners.length; i < m; i++ )
  {
    var indexListener = DOMListeners[i]; /* listeners index */
    if ( indexListener )
    {
      var listener = EVT.listeners[indexListener]; /* real listener reference */
      if ( listener )
      {
        // listener is set like this : [Element, type, handler, wrapped, scope, arbitraryObj, isDOM0];
        // call the wrapped function
        returnListener = listener[3].call(listener[4], evt, listener[5]);
        returnListener = typeof returnListener == 'boolean' ? returnListener : true;
        returnValue = returnValue && returnListener;
      }
    }
  }
  // if a previous listener was set, call it now
  if ( typeof previousListener == 'function' )
  {
    returnListener = previousListener.call(this, evt);
    returnListener = typeof returnListener == 'boolean' ? returnListener : true;
    returnValue = returnValue && returnListener;
  }
  return returnValue;
},


/**
 * Remove a DOM0 listener
 * @param {integer}     index    Index of the global listener removed
 * @param {HTMLElement} Element  Element reference
 * @param {string}      type     Event type to remove
 * @return {boolean} true if the remove was success, false on the contrary
 * @private
 */
removeDOM0 : function(index, Element, type)
{
  var DOM0Index = EVT._getDOM0Index(Element, type);
  if ( DOM0Index !== -1 )
  {
    for ( var j = 0, m = EVT.DOM0Listeners[DOM0Index].length; j < m; j++ )
    {
      if ( EVT.DOM0Listeners[DOM0Index] == index )
      {
        delete EVT.DOM0Listeners[DOM0Index][j];
        break;
      }
    }
    // check if anymore valid listeners are defined
    for ( j = 0, m = EVT.DOM0Listeners[DOM0Index].length; j < m; j++ )
    {
      // there it is another active listener of this type for the element,
      // since we have removed the needed listener, our job is done and we break here
      if ( EVT.DOM0Listeners[DOM0Index][j] )
      {
        return true;
      }
    }
    // no break, so there is ZERO remaining listener for this type for the element,
    // so we remove our handler
    Element['on' + type] = null;
    delete EVT.DOM0Handlers[DOM0Index];
    delete EVT.DOM0Listeners[DOM0Index];
    return true;
  }
  return false;
},


/*
---------------------------------------------------------------------------
  UTILITIES
---------------------------------------------------------------------------
*/

/**
 * Retourne la valeur scrollTop ou scrollLeft.
 * Utilisé pour calculer getX et getY
 * traitement spécial pour IE
 */
getScroll : AI.utils.returnZero,

/**
 * Retourne le charcode pour un event
 *
 * @param {Event} e L'event
 * @return {int} Le charcode de l'event
 * @public
 */
getCharCode : function(e)
{
  return e.charCode || (e.type == "keypress") ? e.keyCode : 0;
},

/**
 * Trouve la position de l'event enregistré dans le cache
 *
 * @structure var li = [el, evName, fn, wrappedFn, scope];
 * @private
*/
/*
_getCacheIndex : function(E, T, F)
{
  for ( var i=0, m = EVT.listeners.length; i < m; i++ )
  {
    var L = EVT.listeners[i];
    if ( L && L[0] == E && L[1] == T && L[2] == F)
    {
      return i;
    }
  }
  return -1;
},
*/

/**
 * méthode unique appellée sur le onload
 */
_onLoad : function()
{
  var L = EVT.$L, i, m;
  for ( i = 0, m = L.length; i < m; i++ )
  {
    try
    {
      L[i]();
    }
    catch(x)
    {
      var t = 'Erreur lors du chargement' + CR + x;
      try { alertError(t); } catch (y) { $W.alert(t); }
    }
  }
  if ( $W.$$app && typeof $W.$$app != 'undefined' )
  {
    $W.$$app.dispatch('fully_loaded');
  }
},

/**
 * méthode unique appellée sur le onunload
 */
_onUnLoad : function()
{
  // on supprime le DOMLoaded (Fx/O9), onbeforeunload et onunload automatiquement insérés
  EVT.remove($D, 'DOMContentLoaded', EVT.DOMLoaded);
  EVT.remove($W, 'beforeunload', EVT._onBeforeUnLoad);
  EVT.remove($W, 'unload', EVT._onUnLoad);

  var U = EVT.$U;
  for ( var i = 0, m = U.length; i < m; i++ )
  {
    try
    {
      U[i]();
    }
    catch(x)
    {
      /*
      alertError('Erreur lors du déchargement' + CR + x + U[i].toString());
      */
    }
  }
  // Supprime les events qui n'ont pas été correctement effacés par leurs éléments
  if ( EVT.listeners && EVT.listeners.length > 0 )
  {
    for ( var j = 0, n = EVT.listeners.length; j < n; j++ )
    {
      var L = EVT.listeners[j];
      if ( L )
      {
        EVT.remove(L[0], L[1], L[2], L[4]);
      }
    }
  }
},

/**
 * Méthode appelée sur le onbeforeunload
 */
_onBeforeUnLoad : function()
{
  if ( typeof $W.$$app !== 'undefined' )
  {
    var
    B = $D.body,
    // FIX ME : devrait utiliser DOM.getViewport()
    H = DOM.getViewportHeight() + 'px';//DOM.pix(B.offsetHeight, 50);
    
    AI.fixIESelect(true);
    
    B.style.overflow = 'hidden';

    B.appendChild($E(
      'div',
      {},
      {
        zIndex:1e6,
        textAlign:'center',
        position:'absolute',
        backgroundColor:'white',
        paddingTop:'40px',
        top:0,
        left:0,
        opacity:0.5,
        width:DOM.pix(B.offsetWidth, 50),
        height:H,
        backgroundColor:'black'
      },
      [
        $E(
          'span',
          {id:'AIEVT_unloader'},
          {
            color:'white',
            fontFamily:'arial',
            fontWeight:800,
            fontSize:'18px'
          },
          [$dct(EVT.msg_unload)]
        )
      ]
    ));
  }
  return true;
},
/**
 * helper function pour ajouter un script sur le onload
 */
//loader : function(F, onDom)
loader : function(f, o)
{
//  if ( EVT.DOMLoaded.OK && onDom)
  if ( EVT.DOMLoaded.OK )
  {
    f();
  }
  else
  {
    EVT.$L.push(f);
  }
},

/**
 * helper function pour ajouter un script sur le onunload
 */
unloader : function(F) { EVT.$U.push(F); }

};

/*
---------------------------------------------------------------------------
  MODELE W3C DOM2
---------------------------------------------------------------------------
*/

if ( $D.addEventListener )
{
  /**
   * Associe l'élément, l'event et la fonction
   *
   * @param {Object}   E         L'élément html sur lequel associer l'event
   * @param {String}   T         Le type d'event à associer
   * @param {Function} F         La fonction que l'event appelle
   * @private
   */
  EVT.bind = function(E, T, F) { E.addEventListener(T, F, false); };
  
  /**
   * Détache l'élément, l'event et la fonction
   *
   * @param {Object}   E        L'élément html ou son id sur lequel détacher l'event
   * @param {String}   T        Le type d'event à détacher
   * @param {Function} F        La fonction que l'event appelle
   * @private
   */
  EVT.unbind = function(E, T, F) { E.removeEventListener(T, F, false); };


  /**
   * Empêche le comportement par défaut de l'event
   *
   * @param {Event} evt L'event
   * @public
   */
  EVT.preventDefault = function(e) { e.preventDefault(); };
  
  /**
   * Arrête la propagation de l'event
   *
   * @param {Event} evt L'event
   * @public
   */
  EVT.stopPropagation = function(e) { e.stopPropagation(); };
  
  /**
   * Obtient la direction et la distance parcourue par la molette
   *
   * @param {Event} evt L'event
   * @return (int) La direction (positive ou négative) parcourue
   * @public
   */
  EVT.getWheelDelta = function(e) { return -(e.detail || 0); };
  
  /**
   * Place un listener sur le scroll de la molette
   *
   * Utilise l'event DOMMouseScroll pour gecko et mousewheel pour IE
   *
   * @param {Object|String} E         L'élément html ou son id sur lequel assigner l'event
   * @param {Function}      F         La fonction que l'event appelle
   * @param {Object}        S         Scope d'exécution
   * @param {Object}        B         Un objet qui sera passé comme paramètre au gestionnaire
   * @param {boolean}       D         true si le modele DOM0 est imposé
   * @return {boolean}      true si l'action a réussi, false si la fonction n'a pas pu être attachée à l'élément
   * @public
   */
  EVT.addWheel = function(E, F, S, B, D) { return EVT.add(E, "DOMMouseScroll", F, S, B, D); };

  /**
   * Supprime le listener sur le scroll de la molette
   *
   * Utilise l'event DOMMouseScroll pour gecko et mousewheel pour IE
   *
   * @return {boolean}      true si l'action a réussi, false si la fonction n'a pas pu être attachée à l'élément
   * @public
   */
  EVT.removeWheel = function(E, F) { return EVT.remove(E, "DOMMouseScroll", F); };

  /**
   * Correspondance des boutons
   *
   * @type hash
   * @public
   */
  EVT.buttons = { left: 0, right: 2, middle: 1 };
  
  /**
   * Retourne la cible de l'event
   *
   * @param (Event) e L'event
   * @public
   */
  EVT.getTarget = function(e) { return ( e.target.nodeType == $D.TEXT_NODE ) ? e.target.parentNode : e.target; };
}
else if ( $D.attachEvent )
{ 
/*
---------------------------------------------------------------------------
  MODELE IE
---------------------------------------------------------------------------
*/
  EVT.bind = function(E, T, F) { E.attachEvent('on' + T, F); };
  EVT.unbind = function(E, T, F) { E.detachEvent('on' + T, F); };
  EVT.preventDefault = function(e) { e = EVT.fix(e); e.returnValue = false; };
  EVT.stopPropagation = function(e) { e = EVT.fix(e); e.cancelBubble = true; };
  EVT.getWheelDelta = function(e) { return e.wheelDelta ? e.wheelDelta / 40 : 0; };
  EVT.addWheel = function(E, F, S, B, D) { EVT.add(E, 'mousewheel', F, S, B, D); };
  EVT.removeWheel = function(E, F) { return EVT.remove(E, "mousewheel", F); };
  EVT.buttons = { left: 1, right: 2, middle: 4 };
//  EVT.getTarget = function(e) { return e.srcElement; };
  EVT.getTarget = function(e) { return e && e.srcElement ? e.srcElement : null; };
/*
  EVT.getScrollOriginalFromYahoo = function(S)
  {
    var d = document.documentElement;
    var b = document.body;
    if ( d && d['scroll' + S] )
    {
      return d['scroll' + S];
    }
    else if ( b )
    {
      return b['scroll' + S];
    }
    return 0;
  };
*/
  EVT.getScroll = function(S)
  {
    var d = IS.strict ? $D.documentElement : $D.body;
    if ( typeof d['scroll' + S] != 'undefined' )
    {
      return d['scroll' + S];
    }
    return 0;
  };
}

/*
---------------------------------------------------------------------------
  EVENTS DE DECHARGEMENT
---------------------------------------------------------------------------
*/
EVT.add($W, 'unload', EVT._onUnLoad);
EVT.add($W, 'beforeunload', EVT._onBeforeUnLoad);

// backward compatibility, garder les formes courtes et supprimer les longues dès que possible
EVT.stopEvent = EVT.stop;
EVT.addEvent = EVT.add;
EVT.removeEvent = EVT.remove;
EVT.addEventMouseWheel = EVT.addWheel;
EVT.getPageX = EVT.getX;
EVT.getPageY = EVT.getY;
EVT.DOMLoaded = function()
{
  if ( EVT.DOMLoaded.OK ) { return true; }
  EVT.DOMLoaded.OK = true;
  if ( EVT.DOMLoaded.toid ) { $W.clearInterval(EVT.DOMLoaded.toid); }
//  var X = EVT.DOMLoaded;
//  if ( X.OK ) { return true; }
//  X.OK = true;
//  if ( X.toid ) $W.clearInterval(X.toid);
  EVT._onLoad();
  return true;
};

//Mozilla Opera9
if ( $D.addEventListener ) { EVT.add($D, 'DOMContentLoaded', EVT.DOMLoaded); }

//IE
/*@cc_on
$D.write('<script id="ieDOMload" defer src="javascript:void(0)"><\/script>');
var S = $('ieDOMload');
S.onreadystatechange = function(){if(this.readyState=='complete'){EVT.DOMLoaded();}};
@*/
/**
 MAJ plus compacte, censée marcher jusqu'à IE7b3
$D.getElementsByTagName('head')[0].appendChild($E('script',{defer:'defer',src:'//0',onreadystatechange:function(){if(this.readyState=='complete'){EVT.DOMLoaded();}}}));
*/


//Safari
//if ( /WebKit/i.test(navigator.userAgent) )
if ( /khtml|webkit/i.test(navigator.userAgent) )
{
  EVT.DOMLoaded.toid = $W.setInterval(function(){if(/loaded|complete/.test($D.readyState)){EVT.DOMLoaded();}},10);
}

//other browsers
$W.onload = EVT.DOMLoaded;
/**
 * Paramètres d'appel
 *
 * AI.AJAX.send({
 *  // url d'appel
 *  "url"        : [string],         // required
 *  // méthode d'appel (GET ou POST)
 *  "methode"    : [string],         // optionnel, default 'GET'
 *  // encodage
 *  "charset"    : [string],         // optionnel, default 'UTF-8'
 *  // données à transférer
 *  "datas"      : [object],         // optionnel, default {}
 *  // si true, un paramètre est ajouté aux datas pour forcer la sortie du cache
 *  "unique"     : [boolean],        // optionnel, default false.
 *  // contexte d'exécution des handlers
 *  "scope"      : [object],         // optionnel
 *  // object arbitraire transmis aux handlers
 *  "arbitrary"  : [object],         // optionnel, default null
 *  // handlers
 *  "onload"     : [Function],       // optionnel
 *  "onerror"    : [Function],       // optionnel
 *  "onprogress" : [Function]        // optionnel
 * });
 *
 */
AI.AJAX = AI.getSimilarParentObject('AJAX') || function()
{
 var
  requetes = [],
  totalRequetes = 0,
  totalRequetesActives = 0;
  
 /**
  * Création de la requete
  * @private
  */
 function createRequete() { return new XMLHttpRequest(); }
 if ( typeof XMLHttpRequest == 'undefined' )
 {
   createRequete = function()
   {
    var req = null;
    try { req = new ActiveXObject("Microsoft.XMLHTTP");}
    catch(ex)
    {
     try { req = new ActiveXObject("Msxml2.XMLHTTP"); }
     catch(ex) {}
    }
    return req;
   };
 }

 /**
  * Lecture de la configuration
  * @private
  */
 function readConf(params, key, defaut) { return typeof params[key] == 'undefined' ? defaut : params[key]; }

 /**
  * Envoi de la requete
  * @protected
  */
 function __send__(params)
 {
  var
   url = readConf(params, 'url', ''),
   methode = readConf(params, 'methode', '') == 'POST' ? 'POST' : 'GET',
   charset = readConf(params, 'charset', 'UTF-8'),
   datas = readConf(params, 'datas', {}),
   unique = readConf(params, 'unique', false),
   scope = readConf(params, 'scope', this),
   arbitrary = readConf(params, 'arbitrary', null),
   _onload = readConf(params, 'onload', function(){}),
   _onerror = readConf(params, 'onerror', function(){}),
   _onprogress = readConf(params, 'onprogress', function(){}),
   req = createRequete(),
   content = null,
   queryString = '',
   K;

  // TODO : traduction des textes
  if ( '' === url ) { return alertError("L'url de l'appel AJAX est requise");  }
  if ( !req ) { return alertError("Impossible de créer l'objet XMLHttpRequest. Pas de gestion AJAX"); }

  totalRequetes++;
  requetes.push(req);

  if ( unique && 'GET' == methode ) { datas.unique_AI_XHR = (new Date().getTime()) + '' + totalRequetes; }

		for ( K in datas )
  {
			if ( queryString !== '' ) { queryString += '&'; }
			queryString += encodeURIComponent(K) + '=' + encodeURIComponent(datas[K]);
		}
		if ( 'GET' == methode && queryString !== '' ) { url += ( ( url.indexOf('?') > -1 ) ? '&' : '?' ) + queryString; }

  req.open(methode, url, true);
// pour envoyer login/passe, vérifier si ça ça marche
//  req.open(methode, url, true, 'login', 'passe');
  totalRequetesActives++;

//  if ( 'POST' == methode && typeof req.setRequestHeader != 'undefined' )
  if ( 'POST' == methode )
  {
   req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=' + charset);
   content = queryString;
  }

  req.onreadystatechange = function()
  {
   var status = 0;
   if ( req )
   {
    _onprogress.call(scope, req, arbitrary);
    /* 4 : état "complete" */
    if ( req.readyState == 4 )
    {
   		// Les codes d'échecs possibles pour wininet.dll sont
	    // 12002 Server timeout
	    // 12029 dropped connections
	    // 12030 dropped connections
	    // 12031 dropped connections
	    // 12152 Connection closed by server
     try { status = req.status; } catch(e) {}
     if ( status == 200 ) { _onload.call(scope, req, arbitrary); }
     else { _onerror.call(scope, req, arbitrary); }
     req = __dispose__(req);
    }
   }
  };

 // si la requete est réutilisée sans avoir été abort(), mozilla throw une exception
 // ne devrait pas se produire avec l'API mais je note quand même l'info, au cas où
 //Error: uncaught exception: [Exception... "Component returned failure code: 0xc1f30001
 //(NS_ERROR_NOT_INITIALIZED) [nsIXMLHttpRequest.send]"  nsresult: "0xc1f30001 (NS_ERROR_NOT_INITIALIZED)"
  req.send(content);
  
  return true;
 }
 
 /**
  * Destruction de la requete
  * @protected
  */
 function __dispose__(req)
 {
  if ( req )
  {
    if ( typeof req.abort == 'function' ) { try { req.abort(); } catch(e) {} }
    req.onreadystatechange = function(){};
//    var idx = requetes.indexOf(req);
//    if ( idx !== -1 && requetes[index] ) { delete requetes[idx]; }
    requetes.remove(req);
  }
  totalRequetesActives--;
  req = null;
  return null;
 }

 /**
  * Détermine si une requete est encore active
  * @protected
  */
 function __isActive__() { return totalRequetesActives !== 0; }

 /**
  * Parse le résultat JSON d'une requete
  * @protected
  */
 function __parse__(req)
 {
  var
   r = null,
   t = typeof req == 'string' ? req : req.responseText || '';
  try { eval("r = " + t); } catch(e) {}
  return r;
 }
 
 /**
  * Parse le résultat JSON d'une requete en s'assurant que c'est bien du JSON
  * @protected
  */
 function __parseSafe__(req)
 {
  if ( /^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.test(req.responseText) )
  {
   return __parse__(req);
  }
  return null;
 }
 
 // efface toutes les requetes onunload
 EVT.unloader(function () { for ( var i = requetes.length; i--; ) { requetes[i] = __dispose__(requetes[i]); } delete requetes; });

 // retours publics
 return {
  "send"       : __send__,
  "dispose"    : __dispose__,
  "isActive"   : __isActive__,
  "parse"      : __parse__,
  "parseSafe"  : __parseSafe__,
  // TODO : détruire cette méthode
  "DEPRECATED" : function(methode, url, vals, handler_ok, handler_erreur, handler_run, oScope, objAccompagne)
  {
   DEPRECATED('AJAX_' + methode.toLowerCase() + '(' + url + ')');
   var datas = {"methode":methode, "url":url};
   if ( vals ) { datas.datas = vals; }
   if ( handler_ok ) { datas.onload = handler_ok; }
   if ( handler_erreur ) { datas.onerror = handler_erreur; }
   if ( handler_run ) { datas.onprogress = handler_run; }
   if ( oScope ) { datas.scope = oScope; }
   if ( objAccompagne ) { datas.arbitrary = objAccompagne; }
   return __send__(datas);
  }

 };
}();

// DEPRECATED ALIAS
var AJAX = {"parse":AI.AJAX.parse, "dispose":AI.AJAX.dispose};
function AJAX_get(url, handler_ok, handler_erreur, handler_run, oScope, objAccompagne)
{
 return AI.AJAX.DEPRECATED('GET', url, null, handler_ok, handler_erreur, handler_run, oScope, objAccompagne);
}
function AJAX_post(url, datas, handler_ok, handler_erreur, handler_run, oScope, objAccompagne)
{
 return AI.AJAX.DEPRECATED('POST', url, datas, handler_ok, handler_erreur, handler_run, oScope, objAccompagne);
}
function Animation(opt)
{
  var
  me = this,
  subjects = [],
  target = 0,
  state = 0,
  intervalId = null,
  step = opt && opt.step ? opt.step : 10,  // nb frames
  duration = opt && opt.duration ? opt.duration : 400, // length of animation
  transition = opt && opt.transition ? opt.transition : Animation.tx.easeInOut,
  onComplete = opt && opt.onComplete ? opt.onComplete : function(){},
  onStart = opt && opt.onStart ? opt.onStart : function(){},
  onStep = opt && opt.onStep ? opt.onStep : function(){},
  scope = opt && opt.scope ? opt.scope : this,
  linked = opt && opt.linked ? opt.linked : null,
  starting = false,
  interval = duration / step;

	// called once per frame to update the current state
	function onTimerEvent()
  {
//    alerter('ontimerevent');
		var movement = (interval / duration) * (state < target ? 1 : -1);
		if ( starting === true ) { onStart.call(this, state, linked); starting = false; }
		if ( Math.abs(movement) >= Math.abs(state - target) )
    {
			state = target;
		}
    else
    {
			state += movement;
		}
		propagate();
		onStep.call(scope, state, linked);
		if ( target == state )
    {
//      alerter('target==state' + '/' + target + '/' + state);
			window.clearInterval(intervalId);
			intervalId = null;
			onComplete.call(scope, target, linked);
		}
	}

	// forward the current state to the animation subjects
	function propagate()
  {
		var value = transition(state);
		for ( var i = 0; i < subjects.length; i++ )
    {
			if ( subjects[i].setState )
      {
				subjects[i].setState(value);
			}
      else
      {
				subjects[i](value);
			}
		}
	}

	// animate from the current state to provided value
	this.seekTo = function(to) { this.seekFromTo(state, to); };

	// animate from the current state to provided value
	this.seekFromTo = function(from, to)
  {
    starting = true;
		target = Math.max(0, Math.min(1, to));
		state = Math.max(0, Math.min(1, from));
		if ( !intervalId )
    {
			intervalId = $W.setInterval(onTimerEvent, interval);
		}
	};

	// animate from the current state to provided value
	this.jumpTo = function(to)
  {
		target = state = Math.max(0, Math.min(1, to));
		starting = true;
		onTimerEvent();
	};

	// seek to the opposite of the current target
	this.toggle = function() { this.seekTo(1 - target); };

	// add a function or an object with a method setState(state) that will be called with a number
	// between 0 and 1 on each frame of the animation
	this.addSubject = function(subject) { subjects.push(subject); };
	
	// remove an object that was added with addSubject
	this.removeSubject = function(subject) { subjects.remove(subject); };

	// remove all subjects
	this.clearSubjects = function() { subjects = []; };

	// shortcuts pour les fonctions privées
	this.play = function() { this.seekFromTo(0, 1); };
	this.reverse = function() { this.seekFromTo(1, 0); };

  // setter
  this.setInterval = function(I) { interval = I; };
  this.setDuration = function(D) { duration = D; };
  this.setTransition = function(T) { transition = T; };
  this.setOnComplete = function(O) { onComplete = O; };
  this.setScope = function(S) { scope = S; };

  return this;
}

// make a transition function that gradually accelerates. pass a=1 for smooth
// gravitational acceleration, higher values for an exaggerated effect
Animation.makeEaseIn = function(a) {
	return function(state) {
		return Math.pow(state, a*2);
	};
};
// as makeEaseIn but for deceleration
Animation.makeEaseOut = function(a) {
	return function(state) {
		return 1 - Math.pow(1 - state, a*2);
	};
};
Animation.tx =
{
  easeInOut: function(pos) { return ((-Math.cos(pos*Math.PI)/2) + 0.5); },
  linear: function(x) { return x; },
  easeIn: Animation.makeEaseIn(1.5),
  easeOut: Animation.makeEaseOut(1.5)
};

Animation.numericalTransition = function(E, P, F, T, U)
{
  var
  els = forceArray(E),
  property = P == 'opacity' && $W.ActiveXObject ? 'filter' : P,
  from = parseFloat(F),
  to = parseFloat(T),
  units = U || 'px';

	function getStyle(state)
  {
		state = from + ((to - from) * state);
		if ( property == 'filter' ) { return "alpha(opacity=" + Math.round(state*100) + ")"; }
		if ( property == 'opacity' ) { return state; }
		return Math.round(state) + units;
	}

	this.setState = function(state)
  {
		var style = getStyle(state);
//    alerter(state + '/' + style);
//    AI_debug('Animation', 'P = ' + property + ' / style = ' + style, 'error');
//		var visibility = (this.property == 'opacity' && state == 0) ? 'hidden' : '';
		for ( var i = 0; i < els.length; i++ )
    {
			els[i].style[property] = style;
		}
	};

  return this;
};

Animation.widgetNumericalTransition = function(W, prop, T)
{
  var
  els = forceArray(W),
  from = parseFloat(W['get' + prop]()),
  to = parseFloat(T);

	function getStyle(state)
  {
		state = Math.round(from + ((to - from) * state));
		return Math.round(state);
	}

	this.setState = function(state)
  {
		var style = getStyle(state);
//    AI_debug('Animation', 'P = ' + property + ' / style = ' + style, 'error');
//		var visibility = (this.property == 'opacity' && state == 0) ? 'hidden' : '';
		for ( var i = 0; i < els.length; i++ )
    {
			els[i]['set' + prop](style);
		}
	};

  return this;
};

Animation.colorTransition = function(E, P, F, T)
{
  var
  els = forceArray(E),
  property = P == 'opacity' && $W.ActiveXObject ? 'filter' : P,
  from, to;

  function expandColor(color)
  {
    var hexColor, red, green, blue;
    hexColor = Animation.parseColor(color);
    if ( hexColor )
    {
      red = parseInt(hexColor.slice(1, 3), 16);
      green = parseInt(hexColor.slice(3, 5), 16);
      blue = parseInt(hexColor.slice(5, 7), 16);
      return [red,green,blue];
    }
    //AI_debug('Animation.colorTransition', 'Couleur invalide : ' + color, 'error');
    return [255, 255, 255];
  }

  from = expandColor(F);
  to = expandColor(T);
  
	function getStyle(color, state)
  {
		return Animation.toColorPart(Math.round(from[color] + ((to[color] - from[color]) * state)));
	}
	
  this.setState = function(state)
  {
    var color = '#'
      + getStyle(0, state)
      + getStyle(1, state)
      + getStyle(2, state);
    for ( var i = 0; i < els.length; i++ )
    {
      els[i].style[property] = color;
    }
  };
  
  return this;
};

Animation.toColorPart = function(str)
{
  var digits = str.toString(16);
  if ( str < 16) { return '0' + digits; }
  return digits;
};

// return a properly formatted 6-digit hex colour spec, or false
Animation.parseColor = function(string)
{
	var color = '#', match = Animation.rgbRe.exec(string), i;
	if ( match )
  {
		for ( i = 1; i <= 3; i++ )
    {
			var part = Math.max(0, Math.min(255, intval(match[i])));
			color += Animation.toColorPart(part);
		}
		return color;
	}
	match = Animation.hexRe.exec(string);
	if ( match )
  {
		if ( match[1].length == 3 )
    {
			for ( i = 0 ; i < 3; i++ )
      {
				color += match[1].charAt(i) + match[1].charAt(i);
			}
			return color;
		}
		return '#' + match[1];
	}
	return false;
};
Animation.rgbRe = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i;
Animation.hexRe = /^\#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;