window.$Menu = { }; // Dépend de common-1, event-1.

$Menu.ItemDISABLED  = 1;
$Menu.ItemSELECTED  = 2;
$Menu.ItemSEPARATED = 4;
$Menu.ItemDISSEL    = 3;
$Menu.ItemDISSEP    = 5;
$Menu.ItemSELSEP    = 6;
$Menu.ItemDISSELSEP = 7;

$Menu.RIGHT_ARROW_CHAR = '\u25BA'; //String.fromCharCode(8594);
$Menu.CHECK_MARK_CHAR = String.fromCharCode(8226);
$Menu.POPUP_OFFSET_X = -10;
$Menu.POPUP_OFFSET_Y = -10;

$Menu.CHECK_IDX = 0;
$Menu.LABEL_IDX = 1;
$Menu.MORE_IDX = 2;

$Menu.TIMEOUT = 75; // ms. Délai avant hilite d'un item.
$Menu.TIMER = null;


$Menu.Close = function(MENU) {
//----=====-----------------

/*
	Ferme le menu donné et ses descendants éventuels.
*/

	var
		ITEMS,
		I;

	if (MENU && MENU.style.display != 'none') {
		ITEMS = $Menu.GetItems(MENU);
		for (I = 0; I < ITEMS.length; I++)
			$Menu.LOLITE(ITEMS[I]);
		if (MENU.style.position == 'absolute')
			$Menu.HIDE(MENU);
		if (MENU == $Menu.ROOT_MENU) {
			$Menu.ROOT_MENU = null;
			$Event.Release(MENU);
			MENU.parentNode.removeChild(MENU);
		}
	}
};


$Menu.Create = function(HORIZONTAL) {
//----======-----------------------

/*
	Crée et renvoie un élément table, qui est le conteneur principal de tout
	MENU. Si le MENU est horizontal, alors la table ne contiendra jamais qu'une
	seule ligne et les items du MENU correspondront aux colonnes de cette ligne;
	s'il est vertical, alors chaque ITEM correspondra à une ligne de la table.
*/

	var
		TABLE = document.createElement('table');

	$Acn(TABLE, 'cM_');
	TABLE.HORIZONTAL = (HORIZONTAL ? 1 : 0);
	TABLE.defautDisplayStyle = TABLE.style.display;
	TABLE.style.display = 'none';
	TABLE.style.position = 'absolute';
	TABLE.style.left = '0px';
	TABLE.style.top = '0px';
	TABLE.style.borderCollapse = 'collapse';
	TABLE.style.textAlign = 'left';
	if (HORIZONTAL)
		TABLE.insertRow(0);

	return TABLE;
};


$Menu.addItem = function(MENU, BEHAVIOR, ATTRIBUTES, LABEL, KBD_EQUIV, EXTRA) {
//----=======----------------------------------------------------------------

/*
	Ajoute un item au MENU donné, …
*/

	var
		DIV = $Ce('div'),
		ITEM,
		CELL;

	DIV.style.position = 'relative';
	if (MENU.HORIZONTAL) {
		ITEM = MENU.rows[0].insertCell(-1); // -1 : voir portabilité, Safari en particulier
		ITEM.style.margin = '0 0.5em 0 0.5em';
		ITEM.style.padding = '0 0.5em 0 0.5em';
		ITEM.style.whiteSpace = 'nowrap';
		ITEM.appendChild(DIV);
		ITEM.appendChild($Ct(LABEL ? LABEL : (typeof BEHAVIOR == 'string' ? BEHAVIOR : '')));
		DIV.style.top = '1.25em';
	}
	else {
		ITEM = MENU.insertRow(-1); // -1 : voir portabilité, Safari en particulier

		CELL = ITEM.insertCell($Menu.CHECK_IDX);
		CELL.appendChild($Ct());

		CELL = ITEM.insertCell($Menu.LABEL_IDX);
		CELL.style.paddingLeft = '0.2em';
		CELL.style.whiteSpace = 'nowrap';
		CELL.appendChild($Ct(LABEL ? LABEL : (typeof BEHAVIOR == 'string' ? BEHAVIOR : '')));

		CELL = ITEM.insertCell($Menu.MORE_IDX);
		CELL.className = 'cM_MORE';
		CELL.style.textAlign = 'center';
		CELL.style.verticalAlign = 'top';
		CELL.style.paddingLeft = '0.5em';
		CELL.style.paddingRight = '0.5em';
		CELL.appendChild(DIV);
		CELL.appendChild($Ct());
		DIV.style.left = '1.25em';

	}
	if (typeof BEHAVIOR == 'object')
		$Menu.ItemSubmenu(ITEM, BEHAVIOR);
	else // typeof BEHAVIOUR == string ou function
		ITEM.CODE = BEHAVIOR;
	ITEM.EXTRA = EXTRA;
	$Acn(ITEM, 'cM_ITEM');
	if (ATTRIBUTES == null)
		ATTRIBUTES = 0;
	$Menu.ItemDisabled(ITEM, ATTRIBUTES & $Menu.ItemDISABLED);
	$Menu.ItemSelected(ITEM, ATTRIBUTES & $Menu.ItemSELECTED);
	$Menu.ItemSeparatorBefore(ITEM, ATTRIBUTES & $Menu.ItemSEPARATED);
	if (KBD_EQUIV)
		$Menu.ItemKbdEquiv(ITEM, KBD_EQUIV);
	return ITEM;
};


$Menu.GetItems = function(MENU) {
//----========-----------------

/*
	Renvoie la "liste" des items du menu donné.
*/

	return (MENU.HORIZONTAL ? MENU.rows[0].cells : MENU.rows);
};


$Menu.GetItemLabel = function(ITEM) {
//----============-----------------

/*
	Renvoie le texte de l'item donné.
*/

	return ($Tn(ITEM) == 'td' ? ITEM.childNodes[LABEL_IDX].data : ITEM.cells[1].firstChild.data);
};


// Dans toutes les fonctions suivantes du type $Menu.ItemXXX(MENU, ARGUMENT),
// si ARGUMENT n'est pas donné (ou vaut null), la fonction renvoie la valeur de
// la propriété correspondante; dans tous les autres cas, la fonction initialise
// la propriété correspondante avec l'argument donné. Si on souhaite supprimer
// la valeur d'une propriété (comme une action ou un sous-menu), il faut alors
// renseigner une chaîne vide comme second argument.


$Menu.ItemDisabled = function(ITEM, DISABLED) {
//----============---------------------------

/*
	(R)envoie l'état d'activation de l'ITEM donné.
*/

	var
		CURRENT = $Hcn(ITEM, 'cDISABLED');

	if (DISABLED != null && CURRENT != (CURRENT = DISABLED))
		if (DISABLED)
			$Acn(ITEM, 'cDISABLED');
		else
			$Rcn(ITEM, 'cDISABLED');

	return CURRENT;
};


$Menu.ItemKbdEquiv = function(ITEM, KBD_EQUIV) {
//----============----------------------------

/*
	(R)envoie l'équivalent-clavier de l'ITEM donné.
	À n'utiliser que pour un menu vertical sans sous-menu.
*/

	var
		CURRENT = $Menu.ItemSubmenu(ITEM) ?
			null  :
			ITEM.cells[$Menu.MORE_IDX].childNodes[1].data;

	if (KBD_EQUIV != null && CURRENT != (CURRENT = KBD_EQUIV)) {
		ITEM.cells[$Menu.MORE_IDX].childNodes[1].data = KBD_EQUIV;
		ITEM.cells[$Menu.MORE_IDX].style.color = 'rgb(33, 33, 66)';
	}
	return CURRENT;
};


$Menu.ItemExtra = function(ITEM, EXTRA) {
//----=========------------------------

/*
	(R)envoie l'information complémentaire de l'ITEM donné.
*/

	var
		CURRENT = (ITEM.EXTRA == undefined ? null : ITEM.EXTRA);

	if (EXTRA != null && CURRENT != EXTRA)
		ITEM.EXTRA = EXTRA;
	return CURRENT;
};


$Menu.ItemSelected = function(ITEM, SELECTED) {
//----============---------------------------

/*
	(R)envoie l'état de sélection de l'ITEM donné.
*/

	if ($Tn(ITEM) == 'td')
		return false;
	var
		CURRENT = (ITEM.cells[$Menu.CHECK_IDX].firstChild.data != '');

	if (SELECTED != null && CURRENT != (CURRENT = SELECTED))
		ITEM.cells[$Menu.CHECK_IDX].firstChild.data = (SELECTED ? $Menu.CHECK_MARK_CHAR : '');

	return CURRENT;
};


$Menu.ItemSeparatorBefore = function(ITEM, SEP_ABOVE) {
//----===================----------------------------

/*
	(R)envoie l'état de séparation antérieure de l'ITEM donné.
*/

	if ($Tn(ITEM) == 'td')
		return false;
	var
		CURRENT = $Hcn(ITEM, 'cSEPARATORBEFORE'),
		i;

	if (SEP_ABOVE != null && CURRENT != (CURRENT = SEP_ABOVE))
		for (i = 0; i < ITEM.cells.length; i++)
			if (SEP_ABOVE)
				$Acn(ITEM.cells[i], 'cSEPARATORBEFORE');
			else
				$Rcn(ITEM.cells[i], 'cSEPARATORBEFORE');

	return CURRENT;
};


$Menu.ItemSubmenu = function(ITEM, MENU) {
//----===========-----------------------

/*
	(R)envoie le sous-MENU de l'ITEM donné.
*/

	var
		horiz = ($Tn(ITEM) == 'td'),
		DIV = (horiz ? ITEM.firstChild : ITEM.cells[$Menu.MORE_IDX].firstChild),
		CURRENT = (DIV.childNodes.length == 0 ? null : DIV.firstChild);

	if (MENU != null && CURRENT != (CURRENT = MENU)) {
		if (DIV.childNodes.length > 0)
			DIV.removeChild(DIV.firstChild);
		if (MENU != '') {
			DIV.appendChild(MENU);
			if (! horiz)
				ITEM.cells[$Menu.MORE_IDX].childNodes[1].data = $Menu.RIGHT_ARROW_CHAR;
		}
		else if (! horiz)
			ITEM.cells[$Menu.MORE_IDX].childNodes[1].data = '';
	}
	return CURRENT;
};


$Menu.open = function(MENU, EVT) {
//----====----------------------

/*
	Ouvre le MENU donné à proximité de l'endroit de l'évènement donné.
*/

	$Menu.openAt(
		MENU,
		EVT.Left,
		EVT.Top
	);
};


$Menu.openAt = function(MENU, X, Y) {
//----======-----------------------

/*
	Ouvre le MENU donné à l'endroit donné.
*/

	$Menu.ROOT_MENU = MENU;
	MENU.ProcessEvent = $Menu.PROCESS_EVENT;
	$Event.Capture(MENU);
	$Menu.SHOW_AT(MENU, X + $Menu.POPUP_OFFSET_X, Y + $Menu.POPUP_OFFSET_Y);
};


$Menu.PROCESS_EVENT = function(EVT) {
//----=============----------------

/*
	Traite tout évènement en rapport avec la gestion des menus.

	(La fermeture d'un menu entraîne le lolite de tous ses items)
	L'ouverture d'un menu (fermé) est déclenchée
		- soit par la commande explicite Open
		- soit par le hilite de l'item parent
	La fermeture d'un menu (ouvert) est déclenchée
		- soit par la commande explicite Close
		- soit par le lolite de l'item-parent

	Un item est hilité
		- soit par l'entrée du pointeur alors qu'aucun menu neveu n'est ouvert
		- soit par la présence du pointeur non en direction d'un menu-neveu
		  ouvert
	Un item est lolité
		- dès qu'un item-frère est hilité
*/

	var
		ITEM,
		SUPMENU,
		SUBMENU,
		NODE,
		PARENT_ITEM,
		ITEMS,
		I;

	if (EVT.Target == $Event.Shield) {
		if (EVT.Type == 'mousedown' || EVT.Type == 'Key')
			$Menu.Close($Menu.ROOT_MENU);
		if (EVT.Type == 'Key')
			document.ProcessEvent(EVT);
	}
	else if ((ITEM = $Nca(EVT.Target, 'cM_ITEM'))) { // Vérifier la pertinence de cette condition …
		SUPMENU = $Nta(ITEM, 'table');
		SWITCH:
		switch (EVT.Type) {
			case 'Key' :
				$Menu.Close($Menu.ROOT_MENU);
				break;
			case 'mouseover' :
				ITEMS = $Menu.GetItems(SUPMENU);
				for (I = 0; I < ITEMS.length; I++)
					if (ITEMS[I] != ITEM && (SUBMENU = $Menu.ItemSubmenu(ITEMS[I])) && SUBMENU.style.display != 'none') {
						$Menu.WAIT(ITEM);
						break SWITCH;
					}
				$Menu.HILITE(ITEM);
				break;
			case 'mousemove' :
				if ($Menu.TIMER)
					$Menu.WAIT(ITEM);
				break;
			case 'click' :
				if (true /*! $Menu.ItemDisabled(ITEM)  && ! $Menu.ItemSubmenu(ITEM) && $Hcn(ITEM, 'cHILITED')*/) {
					$Menu.Close($Menu.ROOT_MENU);
					if (typeof ITEM.CODE == 'function')
						ITEM.CODE();
					else if (typeof $Menu.ItemClicked == 'function')
						$Menu.ItemClicked(ITEM, SUPMENU, EVT);
				}
				break;
		}
	}
};


// -- Fonctions privées --------------------------------------------------------


$Menu.HIDE = function(MENU) {
//----====-----------------

/*
	Supprime l'affichage du MENU donné et celui de ses sous-menus éventuellement
	ouverts.
*/

	MENU.style.display = 'none';
};


$Menu.WAIT = function(ITEM) {
//----====-----------------

/*
	Annule tout processus différé.
	Diffère du TIMEOUT convenu le hilite de l'item donné.
*/

	clearTimeout($Menu.TIMER);
	$Menu.TIMER = setTimeout(function() {$Menu.HILITE(ITEM); $Menu.TIMER = null;}, $Menu.TIMEOUT);
};


$Menu.HILITE = function(ITEM) {
//----======-----------------

/*
	Si l'item donné est désactivé, ne fait rien.

	Sinon :

		- Annule tout processus différé.
		- Lolite tous les items frères.
		- Met l'item donné en état de surbrillance.
		- Affiche le sous-menu éventuellement attaché à l'item donné.
*/

	if ($Menu.ItemDisabled(ITEM))
		return;

	var
		SUBMENU = $Menu.ItemSubmenu(ITEM),
		SIBLING_ITEMS = $Menu.GetItems($Nta(ITEM, 'table')),
		I;

	clearTimeout($Menu.TIMER);
	for (I = 0; I < SIBLING_ITEMS.length; I++)
		if (SIBLING_ITEMS[I] != ITEM)
			$Menu.LOLITE(SIBLING_ITEMS[I]);
	$Acn(ITEM, 'cHILITED');
	if (SUBMENU)
		$Menu.TIMER = setTimeout(function() {$Menu.SHOW_AT(SUBMENU, 0, 0);}, 1);
};


$Menu.LOLITE = function(ITEM) {
//----======-----------------

/*
	Ramène l'item donné dans l'état de repos, et ferme son sous-menu éventueL.
*/

	var
		SUBMENU = $Menu.ItemSubmenu(ITEM);

	if (SUBMENU && SUBMENU != $Menu.ROOT_MENU)
		$Menu.Close(SUBMENU);
	$Rcn(ITEM, 'cHILITED');
};


$Menu.SHOW_AT = function(MENU, X, Y) {
//----=======-----------------------

/*
	Entraîne l'affichage du MENU donné.
*/

	MENU.style.left = X + 'px';
	MENU.style.top  = Y + 'px';
	MENU.style.display = MENU.defautDisplayStyle;
	// Ajustement éventuel de la position pour éviter un affichage hors viewport.
	MENU.style.left = X - Math.max(0, $DomLeft(MENU) + MENU.offsetWidth + 0 - ($Viewport.InnerWidth() + $Viewport.ScrollLeft())) + 'px';
	MENU.style.top  = Y - Math.max(0, $DomTop(MENU) + MENU.offsetHeight + 0 - ($Viewport.InnerHeight() + $Viewport.ScrollTop() )) + 'px';
};

