



		var idMap = Array();
		var lastEntry = new Array();
		var leftEntry = new Array();
		var killTimer = new Array();

		/* ----------------------------------------------------------------------------------------
		 * tools
		 */
		function isChild(child, father) {
			return father && child && (child.parent == father);
		}

		function isFather(father, child) {
			return !father || !child || (child.parent == father);
		}

		function isBrother(brother, sister) {
			return brother && sister && (brother.parent == sister.parent);
		}

		function isLocked(actual) {
			/* assume we don't need time for this */
			var leaved  = actual.root.leaved;
			var entered = actual.root.entered;

			/* we entered a non-valid object
			 * for sure not locked!
			 */
			if (!entered)
				return false;

			/* if we a parent, we are locked! */
			do {
				if (actual == entered)
					return true;
			} while((entered = entered.parent));

			return false;
		}

		/* ----------------------------------------------------------------------------------------
		 */
		function applyEnterEffects(actual, levelsdown, up, down) {
			var flow = BREAK_CONTINUE;

			/* we have the ability to go down */
			if (actual.parent != null) {
				var parent = actual.parent;

				/* look after parents */
				for (var key in parent.entries) {
					var child = parent.entries[key];
					var local = (child.id == actual.id ?
						(levelsdown == 0 ? HOOK_HOVERED      : HOOK_HOVERPARENT) :
						(levelsdown == 0 ? HOOK_HOVERBROTHER : HOOK_HOVERPARENTBROTHER));

					flow = Math.max(flow,
					setEnterEffect(child, local, levelsdown));
				}

				/* recurse down further */
				if (down)
					flow = Math.max(flow,
					applyEnterEffects(parent, levelsdown + 1, false, true));
			}

			/* look after childs */
			if (up)
				for (var key in actual.entries)
				if (getObj(actual.entries[key].id))
					flow = Math.max(flow,
					setEnterEffect(actual.entries[key], HOOK_HOVERCHILD, levelsdown - 1));

			/* continue processing? how? */
			return flow;
		}

		function applyLeaveEffects(actual, levelsdown, up, down) {
			var flow = BREAK_CONTINUE;

			/* we have the ability to go down */
			if (actual.parent != null) {
				var parent = actual.parent;

				/* look after parents */
				for (var key in parent.entries) {
					var child = parent.entries[key];
					var clsfy = (child == actual ?
						(levelsdown == 0 ? HOOK_HOVERED      : HOOK_HOVERPARENT) :
						(levelsdown == 0 ? HOOK_HOVERBROTHER : HOOK_HOVERPARENTBROTHER));

					flow = Math.max(flow,
					setLeaveEffect(child, clsfy, levelsdown));
				}

				/* recurse down further */
				if (down)
					flow = Math.max(flow,
					applyLeaveEffects(parent, levelsdown + 1, false, true));
			}

			/* look after childs */
			if (up)
				for (var key in actual.entries)
				if (getObj(actual.entries[key].id))
					flow = Math.max(flow,
					setLeaveEffect(actual.entries[key], HOOK_HOVERCHILD, levelsdown - 1));

			/* continue processing? how? */
			return flow;
		}

		function enableClickedState(entry) {
			if (entry != null) {
				entry.classname = entry.classname + '_clicked';
				entry.obj.className = entry.classname;

				switch (entry.hovereffect) {
					case EFFECT_HOOK:
						entry.hoverparams(entry, HOOK_CLICKED, 0);
						break;
				}
			}
		}

		function disableClickedState(entry) {
			if (entry != null) {
				entry.classname = entry.classname.replace(/_clicked/, '');
				entry.obj.className = entry.classname;
			}
		}

		/* ----------------------------------------------------------------------------------------
		 * commands and actions
		 */
		function hoverEntry(obj, id, event) {
			var entr = idMap[id];
			var root = entr.root;

			/* there is some similar running, kill it! */
			if (entr.kill) {
				clearTimeout(entr.kill);
				entr.kill = null
			}

			/* remember where to go */
			if ((entr.mvfr = getFrom(event)))
				/* it's only possible for one element to have the focus */
				root.leaved = idMap[entr.mvfr = entr.mvfr.id];

			/* setup the timer (for aborting-ability) */
			entr.kill = setTimeout('createEntry(\'' + id + '\');', createDelay);
		}

		function createEntry(id) {
			var root = idMap[id].root;

			/* setup the action */
			var createEntry = idMap[id];
			var leaveEntry  = idMap[createEntry.mvfr];

			createEntry.kill = null;

			/* prepare all vars for a submenu */
			var offsX = 0, offsY = 0, floating = '';
			var expander = null, container = null;

			/* setup the action */
			expander     = createEntry.expander;
			container    = createEntry.container;

			/* no submenu */
			if (!createEntry.entries ||
				!createEntry.entries.length) {
				/* process only parallel, nothing else changes */
				applyEnterEffects(createEntry, 0, false, false);
				return;
			}

			/* from submenu to father */
			if (leaveEntry && isChild(leaveEntry, createEntry)) {
				/* level-change process everything */
				applyEnterEffects(createEntry, 0, true, true);
				return;
			}

			/* clear the expander */
			expander.style.visibility = 'hidden';
			expander.innerHTML = '';

			/* base offsets */
			offsX = getLeft(createEntry);
			offsY = getTop (createEntry);

			/* switch where/how to start expanding (getComputedStyle is also possible) */
			switch(createEntry.expand) {
				case EXPAND_RIGHT:	offsX = getLeft(createEntry) + getWidth (createEntry); break;
				case EXPAND_BOTTOM:	offsY = getTop (createEntry) + getHeight(createEntry); break;
			}

			/* adjust position of expander */
			expander.style.width  = 'auto';
			expander.style.height = 'auto';
			expander.style.left   = offsX + 'px';
			expander.style.top    = offsY + 'px';

			showMenuBlock(expander, createEntry, 'sub');

			/* get the informations */
			var maxwidth  = createEntry.maxWidth;
			var maxheight = createEntry.maxHeight;
			var sumwidth  = createEntry.sumWidth;
			var sumheight = createEntry.sumHeight;

			switch(createEntry.expand) {
				case EXPAND_LEFT:
					switch(createEntry.align) {
						case MENU_VERTICAL_BOTTOM:  offsX = getLeft(createEntry) - maxwidth; break;
						case MENU_VERTICAL_TOP:		offsX = getLeft(createEntry) - maxwidth;
													offsY = getTop (createEntry) - sumheight + getHeight(createEntry); break;
						case MENU_HORIZONTAL_LEFT:	offsX = getLeft(createEntry) - sumwidth; break;
						case MENU_HORIZONTAL_RIGHT:	alert('forbidden!!! right left'); break;
					}
					break;
				case EXPAND_TOP:
					switch(createEntry.align) {
						case MENU_VERTICAL_BOTTOM:	alert('forbidden!!! top bottom'); break;
						case MENU_VERTICAL_TOP:		offsY = getTop (createEntry) - sumheight; break;
						case MENU_HORIZONTAL_LEFT:	offsY = getTop (createEntry) - maxheight;
													offsX = getLeft(createEntry) - sumwidth + getWidth(createEntry); break;
						case MENU_HORIZONTAL_RIGHT: offsY = getTop (createEntry) - maxheight; break;
					}
					break;
				case EXPAND_RIGHT:
					switch(createEntry.align) {
						case MENU_VERTICAL_BOTTOM:  break;
						case MENU_VERTICAL_TOP:		offsX = getLeft(createEntry) + getWidth (createEntry);
													offsY = getTop (createEntry) + getHeight(createEntry) - sumheight; break;
						case MENU_HORIZONTAL_LEFT:	alert('forbidden!!! right left'); break;
						case MENU_HORIZONTAL_RIGHT:	break;
					}
					break;
				case EXPAND_BOTTOM:
					switch(createEntry.align) {
						case MENU_VERTICAL_BOTTOM:	break;
						case MENU_VERTICAL_TOP:		alert('forbidden!!! bottom top'); break;
						case MENU_HORIZONTAL_LEFT:	offsY = getTop (createEntry) + getHeight(createEntry);
													offsX = getLeft(createEntry) + getWidth (createEntry) - sumwidth; break;
						case MENU_HORIZONTAL_RIGHT: offsY = getTop (createEntry) + getHeight(createEntry); break;
					}
					break;
			}

			/* manual correction of expander */
			offsX += createEntry.expOffsX;
			offsY += createEntry.expOffsY;

			/* adjust position of expander */
			expander.style.width  = sumwidth  + 'px';
			expander.style.height = sumheight + 'px';
			expander.style.left   = offsX + 'px';
			expander.style.top    = offsY + 'px';
			expander.style.visibility = '';

			/* process only parallel, and up nothing else changes */
			applyEnterEffects(createEntry, 0, true, false);
		}

		function leaveEntry(obj, id, event) {
			var leav = idMap[id];
			var root = leav.root;

			/* there is some similar running, kill it! */
			if (leav.kill) {
				clearTimeout(leav.kill);
				leav.kill = null
			}

			/* remember where to go */
			if ((leav.mvto = getTo(event)))
				/* it's only possible for one element to be entered */
				root.entered = idMap[leav.mvto = leav.mvto.id];

			/* setup the timer (for aborting-ability) */
			leav.kill = setTimeout('killEntry(\'' + id + '\');', leaveDelay);
		}

		function killEntry(id) {
			var root = idMap[id].root;

			/* setup the action */
			var removeEntry = idMap[id];
			var enterEntry  = idMap[removeEntry.mvto];

			removeEntry.kill = null;

			do {
				var parentEntry = removeEntry.parent;

				/* there are other kill-instances running through
				 * children, so delay untill those have finished
				 */
				for (var key in removeEntry.entries) {
					/* hard break! */
					if (getObj(removeEntry.entries[key].id + '.container'))
						return;
				}

				/* are we locked? */
				if (isLocked(removeEntry)) {
					//applyLeaveEffects(removeEntry, 0, true, true);
					setLeaveEffect(removeEntry, HOOK_HOVERED, 0);
					break;
				}

				/* collapse the menu somehow */
				if (removeEntry.leavemode != MODE_STAY) {
					/* leave-effect-hook before remove */
					var flow = //applyLeaveEffects(removeEntry, 0, true, true);
						setLeaveEffect(removeEntry, HOOK_HOVERED, 0);

					/* abort */
					if (flow == BREAK_ABORT)
						break;

					/* delay the down-level collapse for entries */
					if (flow == BREAK_ONENTRY) {
						leaveEntry(removeEntry.obj, removeEntry.id);
						break;
					}

					if (parentEntry != null) {
						/* get away with the block */
						hideMenuBlock(removeEntry.expander, removeEntry, 'sub');

						/* delay the down-level collapse for block */
						if (flow == BREAK_ONBLOCK) {
							leaveEntry(parentEntry.obj, parentEntry.id);
							break;
						}
					}
				}

			} while ((removeEntry = parentEntry) != null);
		}

		function clickEntry(obj, id, event) {
			var root = idMap[id].root;
			var clickedEntry;

			disableClickedState(root.clicked);

			/* setup the action */
			clickedEntry      = root.clicked = idMap[id];
			clickedEntry.obj  = obj;

			enableClickedState(root.clicked);

			leaveEntry(obj, id, event);
		}

		/* ----------------------------------------------------------------------------------------
		 * completion
		 */
		function completeMenuEntry(entry, parent, level, pfx) {
			entry.level  = level;
			entry.parent = parent;
			entry.root   = parent.root;
			entry.id     = parent.id + '.' + makeValidID(entry.name) + pfx;

			var num = 0; for (var key in entry.entries)
				completeMenuEntry(entry.entries[key], entry, level + 1, num++);

			idMap[entry.id] = entry;
		}

		function completeMenuBlock(block) {
			block.level  = -1;
			block.parent = null;
			block.root   = block;
			block.id     = makeValidID(block.name);

			var num = 0; for (var key in block.entries)
				completeMenuEntry(block.entries[key], block, 0, num++);
		}

		/* ----------------------------------------------------------------------------------------
		 * construction
		 */
		function constructMenuEntry(entry) {
			entry.html = entry.name;

			for (var key in entry.entries)
				constructMenuEntry(entry.entries[key]);
		}

		function constructMenuBlock(block) {
			for (var key in block.entries)
				constructMenuEntry(block.entries[key]);
		}

		/* ----------------------------------------------------------------------------------------
		 * display
		 */
		function showMenuExpander(baseid) {
			return '' +
			'<div' +
				' id="' + baseid + '.absolute"' +
				' style="' +
					'position: absolute;">' +
			'<div' +
				' id="' + baseid + '.expander"' +
				' style="' +
					'position: absolute; ' +
					'z-index: 0; ' +
					'width: auto; ' +
					'height: auto; ' +
					'left: 0px; ' +
					'top: 0px;">' +
			'</div>' +
			'</div>';
		}

		function showMenuEntry(entry, classname) {
			var floating = '';

			switch (entry.parent.align) {
				case MENU_HORIZONTAL_RIGHT:	floating = ' float: left;'; break;
				case MENU_VERTICAL_BOTTOM:	floating = ' float: none;'; break;
				case MENU_HORIZONTAL_LEFT:	floating = ' float: left;'; break;
				case MENU_VERTICAL_TOP:		floating = ' float: none;'; break;
			}

			if (entry.classname != null)
				classname = entry.classname;

			return '' +
			'<div' +
				' id="' + entry.id + '"' +
				' class="' + classname + '"' +
				' style="cursor: pointer; white-space: nowrap;' + floating + '"' +
//				' ondragstart="undockEntry(this, this.id);"' +
//				' ondrag="moveEntry(this, this.id);"' +
				' onmouseover="hoverEntry(this, this.id, event);"' +
				' onmouseout="leaveEntry(this, this.id, event);"' +
//				' onmousedown="pressEntry(this, this.id);"' +
//				' onmouseup="releaseEntry(this, this.id);"' +
//				' ondblclick="dblclkEntry(this, this.id);"' +
				' onclick="clickEntry(this, this.id, event);">' + entry.html +
			'</div>';
		}

		/* ---------------------------------------------------------------------
		 * space	-	the element that surrounds the content (super-container)
		 * block	-	the element having the entry-definitions
		 */
		function hideMenuBlock(space, block, type) {
			/* kill the whole expander (useless now) */
			space.style.visibility = 'hidden';

			/* clear the space */
			while (space.lastChild)
				space.removeChild(space.lastChild);

			/* readjust position of expander */
			space.style.width  = 'auto';
			space.style.height = 'auto';
			space.style.left   = '0px';
			space.style.top    = '0px';

			/* store the informations */
			block.hasBuild = false;
		}

		function showMenuBlock(space, block, type) {
			/* backup the visibility and display settings */
			var bvis   = space.style.visibility;
//			var bdisp  = space.style.display;

			var exps   = '';
			var wrap   = null;
			var eobj   = null,
				maxwidth = 0, maxheight = 0,
				sumwidth = 0, sumheight = 0;

			/* look for breaking */
			switch (block.align) {
				case MENU_HORIZONTAL_RIGHT:
				case MENU_HORIZONTAL_LEFT:	wrap = 'white-space: nowrap;'; break;
				case MENU_VERTICAL_BOTTOM:
				case MENU_VERTICAL_TOP:		wrap = 'white-space: wrap;'; break;
			}

			/* create all the individual expanders */
			for (var key in block.entries)
				exps += showMenuExpander(block.entries[key].id);

			/* because we want a the values, we have to force a hidden CSS-render!!! */
			space.style.visibility = 'hidden';
//			space.style.display    = null;
			/* create the block contents */
			space.innerHTML = '' +
			'<div' +
				' id="' + block.id + '.relation"' +
				' style="' +
					'position: relative;">' + exps +
			'<div' +
				' id="' + block.id + '.container"' +
				' style="' +
					'position: relative;"' +
					'width: auto; ' +
					'height: auto; ' + wrap + '">' +
			'</div>' +
			'</div>';

			/* recalculate block contents */
			var relation  = getObj(block.id + '.relation');
			var container = getObj(block.id + '.container');

			/* build block contents */
			for (var key in block.entries) {
				eobj = block.entries[key];

				/* create another entry:
				 *
				 * update after every new addition (for delayed/transition effects,
				 * as well as getting the new elements dimensions)
				 */
				container.innerHTML +=
					showMenuEntry(eobj, type + 'entry');

				/* get the object */
				eobj.obj       = getObj(eobj.id);

				/* measure the object */
				maxwidth   = Math.max(maxwidth , getWidth (eobj));
				maxheight  = Math.max(maxheight, getHeight(eobj));
			}

			/* correct the object */
			switch(block.align) {
				case MENU_VERTICAL_BOTTOM:
				case MENU_VERTICAL_TOP:
					sumwidth  = maxwidth;
					break;
				case MENU_HORIZONTAL_LEFT:
				case MENU_HORIZONTAL_RIGHT:
					sumheight = maxheight;
					break;
			}

			for (var key in block.entries) {
				eobj = block.entries[key];

				/* get the object */
				eobj.obj       = getObj(eobj.id);
				eobj.expander  = getObj(eobj.id + '.expander');

				/* store the infos */
				eobj.container = container;
				eobj.parent    = block;

				/* correct the object */
				switch(block.align) {
					case MENU_VERTICAL_BOTTOM:
					case MENU_VERTICAL_TOP:
						eobj.obj.style.width  = maxwidth  + 'px'; var coX = maxwidth  - getWidth (eobj);
						eobj.obj.style.width  = maxwidth  + coX + 'px'; sumheight += getHeight(eobj);
						break;
					case MENU_HORIZONTAL_LEFT:
					case MENU_HORIZONTAL_RIGHT:
						eobj.obj.style.height = maxheight + 'px'; var coY = maxheight - getHeight(eobj);
						eobj.obj.style.height = maxheight + coY + 'px'; sumwidth  += getWidth (eobj);
						break;
				}

				/* define infos */
				eobj.visible = true;
				eobj.posX = getLeft  (eobj);
				eobj.posY = getTop   (eobj);
				eobj.szeX = getWidth (eobj);
				eobj.szeY = getHeight(eobj);
			}

			/* resize the the super-container, the relation and the container
			 * NOTE: super-container and relation don't have margin/outline/padding/border
			 */
			sumwidth += 1;
			
			space.style.width  = relation.style.width  = container.style.width  = sumwidth  + 'px';
			space.style.height = relation.style.height = container.style.height = sumheight + 'px';

			/* store the informations */
			block.maxWidth  = maxwidth;
			block.maxHeight = maxheight;
			block.sumWidth  = sumwidth;
			block.sumHeight = sumheight;
			block.hasBuild  = true;

			/* reset the visibility and display settings */
			space.style.visibility = bvis;
//			space.style.display    = bdisp;
		}

		/* ----------------------------------------------------------------------------------------
		 * onetime-construction
		 */
		function processMenuBlocks(blocks) {
			for (var key in blocks)
				completeMenuBlock(blocks[key]);
			for (var key in blocks)
				constructMenuBlock(blocks[key]);

			for (var key in blocks)
				showMenuBlock(getObj(blocks[key].id), blocks[key], 'block');
		}
