text: /**
   * DropMenu.js
   *
   * Copyright 2009, Moxiecode Systems AB
   * Released under LGPL License.
   *
   * License: http://tinymce.moxiecode.com/license
   * Contributing: http://tinymce.moxiecode.com/contributing
   */
  
  (function(tinymce) {
  	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
  
  	/**
  	 * This class is used to create drop menus, a drop menu can be a
  	 * context menu, or a menu for a list box or a menu bar.
  	 *
  	 * @class tinymce.ui.DropMenu
  	 * @extends tinymce.ui.Menu
  	 */
  	tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
  		/**
  		 * Constructs a new drop menu control instance.
  		 *
  		 * @constructor
  		 * @method DropMenu
  		 * @param {String} id Button control id for the button.
  		 * @param {Object} s Optional name/value settings object.
  		 */
  		DropMenu : function(id, s) {
  			s = s || {};
  			s.container = s.container || DOM.doc.body;
  			s.offset_x = s.offset_x || 0;
  			s.offset_y = s.offset_y || 0;
  			s.vp_offset_x = s.vp_offset_x || 0;
  			s.vp_offset_y = s.vp_offset_y || 0;
  
  			if (is(s.icons) && !s.icons)
  				s['class'] += ' mceNoIcons';
  
  			this.parent(id, s);
  			this.onShowMenu = new tinymce.util.Dispatcher(this);
  			this.onHideMenu = new tinymce.util.Dispatcher(this);
  			this.classPrefix = 'mceMenu';
  		},
  
  		/**
  		 * Created a new sub menu for the drop menu control.
  		 *
  		 * @method createMenu
  		 * @param {Object} s Optional name/value settings object.
  		 * @return {tinymce.ui.DropMenu} New drop menu instance.
  		 */
  		createMenu : function(s) {
  			var t = this, cs = t.settings, m;
  
  			s.container = s.container || cs.container;
  			s.parent = t;
  			s.constrain = s.constrain || cs.constrain;
  			s['class'] = s['class'] || cs['class'];
  			s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
  			s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
  			m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
  
  			m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
  
  			return m;
  		},
  
  		/**
  		 * Repaints the menu after new items have been added dynamically.
  		 *
  		 * @method update
  		 */
  		update : function() {
  			var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
  
  			tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth;
  			th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight;
  
  			if (!DOM.boxModel)
  				t.element.setStyles({width : tw + 2, height : th + 2});
  			else
  				t.element.setStyles({width : tw, height : th});
  
  			if (s.max_width)
  				DOM.setStyle(co, 'width', tw);
  
  			if (s.max_height) {
  				DOM.setStyle(co, 'height', th);
  
  				if (tb.clientHeight < s.max_height)
  					DOM.setStyle(co, 'overflow', 'hidden');
  			}
  		},
  
  		/**
  		 * Displays the menu at the specified cordinate.
  		 *
  		 * @method showMenu
  		 * @param {Number} x Horizontal position of the menu.
  		 * @param {Number} y Vertical position of the menu.
  		 * @param {Numner} px Optional parent X position used when menus are cascading.
  		 */
  		showMenu : function(x, y, px) {
  			var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
  
  			t.collapse(1);
  
  			if (t.isMenuVisible)
  				return;
  
  			if (!t.rendered) {
  				co = DOM.add(t.settings.container, t.renderNode());
  
  				each(t.items, function(o) {
  					o.postRender();
  				});
  
  				t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
  			} else
  				co = DOM.get('menu_' + t.id);
  
  			// Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
  			if (!tinymce.isOpera)
  				DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
  
  			DOM.show(co);
  			t.update();
  
  			x += s.offset_x || 0;
  			y += s.offset_y || 0;
  			vp.w -= 4;
  			vp.h -= 4;
  
  			// Move inside viewport if not submenu
  			if (s.constrain) {
  				w = co.clientWidth - ot;
  				h = co.clientHeight - ot;
  				mx = vp.x + vp.w;
  				my = vp.y + vp.h;
  
  				if ((x + s.vp_offset_x + w) > mx)
  					x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
  
  				if ((y + s.vp_offset_y + h) > my)
  					y = Math.max(0, (my - s.vp_offset_y) - h);
  			}
  
  			DOM.setStyles(co, {left : x , top : y});
  			t.element.update();
  
  			t.isMenuVisible = 1;
  			t.mouseClickFunc = Event.add(co, 'click', function(e) {
  				var m;
  
  				e = e.target;
  
  				if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
  					m = t.items[e.id];
  
  					if (m.isDisabled())
  						return;
  
  					dm = t;
  
  					while (dm) {
  						if (dm.hideMenu)
  							dm.hideMenu();
  
  						dm = dm.settings.parent;
  					}
  
  					if (m.settings.onclick)
  						m.settings.onclick(e);
  
  					return Event.cancel(e); // Cancel to fix onbeforeunload problem
  				}
  			});
  
  			if (t.hasMenus()) {
  				t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
  					var m, r, mi;
  
  					e = e.target;
  					if (e && (e = DOM.getParent(e, 'tr'))) {
  						m = t.items[e.id];
  
  						if (t.lastMenu)
  							t.lastMenu.collapse(1);
  
  						if (m.isDisabled())
  							return;
  
  						if (e && DOM.hasClass(e, cp + 'ItemSub')) {
  							//p = DOM.getPos(s.container);
  							r = DOM.getRect(e);
  							m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
  							t.lastMenu = m;
  							DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
  						}
  					}
  				});
  			}
  
  			t.onShowMenu.dispatch(t);
  
  			if (s.keyboard_focus) {
  				Event.add(co, 'keydown', t._keyHandler, t);
  				DOM.select('a', 'menu_' + t.id)[0].focus(); // Select first link
  				t._focusIdx = 0;
  			}
  		},
  
  		/**
  		 * Hides the displayed menu.
  		 *
  		 * @method hideMenu
  		 */
  		hideMenu : function(c) {
  			var t = this, co = DOM.get('menu_' + t.id), e;
  
  			if (!t.isMenuVisible)
  				return;
  
  			Event.remove(co, 'mouseover', t.mouseOverFunc);
  			Event.remove(co, 'click', t.mouseClickFunc);
  			Event.remove(co, 'keydown', t._keyHandler);
  			DOM.hide(co);
  			t.isMenuVisible = 0;
  
  			if (!c)
  				t.collapse(1);
  
  			if (t.element)
  				t.element.hide();
  
  			if (e = DOM.get(t.id))
  				DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
  
  			t.onHideMenu.dispatch(t);
  		},
  
  		/**
  		 * Adds a new menu, menu item or sub classes of them to the drop menu.
  		 *
  		 * @method add
  		 * @param {tinymce.ui.Control} o Menu or menu item to add to the drop menu.
  		 * @return {tinymce.ui.Control} Same as the input control, the menu or menu item.
  		 */
  		add : function(o) {
  			var t = this, co;
  
  			o = t.parent(o);
  
  			if (t.isRendered && (co = DOM.get('menu_' + t.id)))
  				t._add(DOM.select('tbody', co)[0], o);
  
  			return o;
  		},
  
  		/**
  		 * Collapses the menu, this will hide the menu and all menu items.
  		 *
  		 * @method collapse
  		 * @param {Boolean} d Optional deep state. If this is set to true all children will be collapsed as well.
  		 */
  		collapse : function(d) {
  			this.parent(d);
  			this.hideMenu(1);
  		},
  
  		/**
  		 * Removes a specific sub menu or menu item from the drop menu.
  		 *
  		 * @method remove
  		 * @param {tinymce.ui.Control} o Menu item or menu to remove from drop menu.
  		 * @return {tinymce.ui.Control} Control instance or null if it wasn't found.
  		 */
  		remove : function(o) {
  			DOM.remove(o.id);
  			this.destroy();
  
  			return this.parent(o);
  		},
  
  		/**
  		 * Destroys the menu. This will remove the menu from the DOM and any events added to it etc.
  		 *
  		 * @method destroy
  		 */
  		destroy : function() {
  			var t = this, co = DOM.get('menu_' + t.id);
  
  			Event.remove(co, 'mouseover', t.mouseOverFunc);
  			Event.remove(co, 'click', t.mouseClickFunc);
  
  			if (t.element)
  				t.element.remove();
  
  			DOM.remove(co);
  		},
  
  		/**
  		 * Renders the specified menu node to the dom.
  		 *
  		 * @method renderNode
  		 * @return {Element} Container element for the drop menu.
  		 */
  		renderNode : function() {
  			var t = this, s = t.settings, n, tb, co, w;
  
  			w = DOM.create('div', {id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000'});
  			co = DOM.add(w, 'div', {id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
  			t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
  
  			if (s.menu_line)
  				DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
  
  //			n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
  			n = DOM.add(co, 'table', {id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
  			tb = DOM.add(n, 'tbody');
  
  			each(t.items, function(o) {
  				t._add(tb, o);
  			});
  
  			t.rendered = true;
  
  			return w;
  		},
  
  		// Internal functions
  
  		_keyHandler : function(e) {
  			var t = this, kc = e.keyCode;
  
  			function focus(d) {
  				var i = t._focusIdx + d, e = DOM.select('a', 'menu_' + t.id)[i];
  
  				if (e) {
  					t._focusIdx = i;
  					e.focus();
  				}
  			};
  
  			switch (kc) {
  				case 38:
  					focus(-1); // Select first link
  					return;
  
  				case 40:
  					focus(1);
  					return;
  
  				case 13:
  					return;
  
  				case 27:
  					return this.hideMenu();
  			}
  		},
  
  		_add : function(tb, o) {
  			var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
  
  			if (s.separator) {
  				ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
  				DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
  
  				if (n = ro.previousSibling)
  					DOM.addClass(n, 'mceLast');
  
  				return;
  			}
  
  			n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
  			n = it = DOM.add(n, 'td');
  			n = a = DOM.add(n, 'a', {href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
  
  			DOM.addClass(it, s['class']);
  //			n = DOM.add(n, 'span', {'class' : 'item'});
  
  			ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
  
  			if (s.icon_src)
  				DOM.add(ic, 'img', {src : s.icon_src});
  
  			n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
  
  			if (o.settings.style)
  				DOM.setAttrib(n, 'style', o.settings.style);
  
  			if (tb.childNodes.length == 1)
  				DOM.addClass(ro, 'mceFirst');
  
  			if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
  				DOM.addClass(ro, 'mceFirst');
  
  			if (o.collapse)
  				DOM.addClass(ro, cp + 'ItemSub');
  
  			if (n = ro.previousSibling)
  				DOM.removeClass(n, 'mceLast');
  
  			DOM.addClass(ro, 'mceLast');
  		}
  	});
  })(tinymce);