/**
 * Copyright (c) 2006-2012, JGraph Holdings Ltd
 */
/**
 * Constructs a new graph editor
 */
Menus = function(editorUi)
{
	this.editorUi = editorUi;
	this.menus = new Object();
	this.pluginMenuItems = {};
	this.init();
};

/**
 * Sets the default font family.
 */
Menus.prototype.defaultFont = 'Helvetica';

/**
 * Sets the default font size.
 */
Menus.prototype.defaultFontSize = '12';

/**
 * Sets the default font size.
 */
Menus.prototype.defaultMenuItems = ['file', 'edit', 'view', 'arrange', 'extras', 'help'];

/**
 * Lookup for menu items and submenus to hide. Set via the
 * hideMenuItems configuration property (array of names).
 */
Menus.prototype.hiddenMenuItems = null;

/**
 * Adds the label menu items to the given menu and parent.
 */
Menus.prototype.defaultFonts = ['Helvetica', 'Verdana', 'Times New Roman', 'Garamond', 'Comic Sans MS',
           		             'Courier New', 'Georgia', 'Lucida Console', 'Tahoma'];

/**
 * Adds the label menu items to the given menu and parent.
 */
Menus.prototype.autoPopup = true;

/**
 * Adds the label menu items to the given menu and parent.
 */
Menus.prototype.init = function()
{
	var ui = this.editorUi;
	var graph = ui.editor.graph;
	var isGraphEnabled = mxUtils.bind(graph, graph.isEnabled);

	this.customFonts = [];
	this.customFontSizes = [];

	this.put('edgeStyle', new Menu(mxUtils.bind(this, function(menu, parent)
	{
		var state = graph.view.getState(graph.getSelectionCell());
		var shape = (state != null) ? mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE) : null;
		
		if (shape != 'arrow')
		{
			Format.processMenuIcon(this.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
				[null, null, null], null, parent, true, Format.straightImage.src)).setAttribute('title', mxResources.get('straight'));
			Format.processMenuIcon(this.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
				['orthogonalEdgeStyle', null, null], null, parent, true, Format.orthogonalImage.src)).setAttribute('title', mxResources.get('orthogonal'));
			Format.processMenuIcon(this.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_ELBOW, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
				['elbowEdgeStyle', 'vertical', null, null], null, parent, true, Format.verticalElbowImage.src)).setAttribute('title', mxResources.get('horizontal'));
			Format.processMenuIcon(this.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_ELBOW, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
				['elbowEdgeStyle', null, null, null], null, parent, true, Format.horizontalElbowImage.src)).setAttribute('title', mxResources.get('vertical'));
			Format.processMenuIcon(this.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_ELBOW, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
				['isometricEdgeStyle', null, null, null], null, parent, true, Format.horizontalIsometricImage.src)).setAttribute('title', mxResources.get('isometric'));
			Format.processMenuIcon(this.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_ELBOW, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
				['isometricEdgeStyle', 'vertical', null, null], null, parent, true, Format.verticalIsometricImage.src)).setAttribute('title', mxResources.get('isometric'));
	
			if (shape == null || shape == 'connector')
			{
				Format.processMenuIcon(this.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
					['orthogonalEdgeStyle', '1', null], null, parent, true, Format.curvedImage.src)).setAttribute('title', mxResources.get('curved'));
			}
			
			Format.processMenuIcon(this.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
				['entityRelationEdgeStyle', null, null], null, parent, true, Format.entityImage.src)).setAttribute('title', mxResources.get('entityRelation'));
		}
	})));
	
	this.put('edgeShape', new Menu(mxUtils.bind(this, function(menu, parent)
	{
		var keys = [mxConstants.STYLE_SHAPE, mxConstants.STYLE_STARTSIZE, mxConstants.STYLE_ENDSIZE, mxConstants.STYLE_DASHED, 'width'];

		Format.processMenuIcon(this.edgeStyleChange(menu, '', keys, [null, null, null, null, null],
			null, parent, true, Format.connectionImage.src)).setAttribute('title', mxResources.get('line'));
		Format.processMenuIcon(this.edgeStyleChange(menu, '', keys, ['link', null, null, null, null],
			null, parent, true, Format.linkEdgeImage.src)).setAttribute('title', mxResources.get('link'));
		Format.processMenuIcon(this.edgeStyleChange(menu, '', keys, ['flexArrow', null, null, null, null],
			null, parent, true, Format.arrowImage.src)).setAttribute('title', mxResources.get('arrow'));
		Format.processMenuIcon(this.edgeStyleChange(menu, '', keys, ['arrow', null, null, null, null],
			null, parent, true, Format.simpleArrowImage.src)).setAttribute('title', mxResources.get('simpleArrow'));
		Format.processMenuIcon(this.edgeStyleChange(menu, '', keys, ['filledEdge', null, null, null, null],
			null, parent, true, Format.filledEdgeImage.src)).setAttribute('title', 'Filled Edge');
		Format.processMenuIcon(this.edgeStyleChange(menu, '', keys, ['pipe', null, null, null, null],
			null, parent, true, Format.pipeEdgeImage.src)).setAttribute('title', 'Pipe');
		Format.processMenuIcon(this.edgeStyleChange(menu, '', keys, ['wire', null, null, '1', null],
			null, parent, true, Format.wireEdgeImage.src)).setAttribute('title', 'Wire');
	})));

	this.put('line', new Menu(mxUtils.bind(this, function(menu, parent)
	{
		this.get('edgeStyle').funct(menu, parent);
		menu.addSeparator(parent);
		this.get('edgeShape').funct(menu, parent);
	})));

	this.put('fontFamily', new Menu(mxUtils.bind(this, function(menu, parent)
	{
		var addItem = mxUtils.bind(this, function(fontFamily)
		{
			var tr = this.styleChange(menu, fontFamily, [mxConstants.STYLE_FONTFAMILY],
				[fontFamily], null, parent, function()
			{
				document.execCommand('fontname', false, fontFamily);
				ui.fireEvent(new mxEventObject('styleChanged',
					'keys', [mxConstants.STYLE_FONTFAMILY],
					'values', [fontFamily],
					'cells', [graph.cellEditor.getEditingCell()]));
			}, function()
			{
				graph.updateLabelElements(graph.getSelectionCells(), function(elt)
				{
					elt.removeAttribute('face');
					elt.style.fontFamily = null;
					
					if (elt.nodeName == 'PRE')
					{
						graph.replaceElement(elt, 'div');
					}
				});
			});

			tr.firstChild.nextSibling.style.fontFamily = fontFamily;
		});
		
		for (var i = 0; i < this.defaultFonts.length; i++)
		{
			addItem(this.defaultFonts[i]);
		}

		menu.addSeparator(parent);
		
		if (this.customFonts.length > 0)
		{
			for (var i = 0; i < this.customFonts.length; i++)
			{
				addItem(this.customFonts[i]);
			}
			
			menu.addSeparator(parent);
			
			menu.addItem(mxResources.get('reset'), null, mxUtils.bind(this, function()
			{
				this.customFonts = [];
				this.editorUi.fireEvent(new mxEventObject('customFontsChanged'));
			}), parent);
			
			menu.addSeparator(parent);
		}
		
		this.promptChange(menu, mxResources.get('custom') + '...', '', mxConstants.DEFAULT_FONTFAMILY, mxConstants.STYLE_FONTFAMILY, parent, true, mxUtils.bind(this, function(newValue)
		{
			if (mxUtils.indexOf(this.customFonts, newValue) < 0)
			{
				this.customFonts.push(newValue);
				this.editorUi.fireEvent(new mxEventObject('customFontsChanged'));
			}
		}));
	})));
	this.put('formatBlock', new Menu(mxUtils.bind(this, function(menu, parent)
	{
		function addItem(label, tag)
		{
			return menu.addItem(label, null, mxUtils.bind(this, function()
			{
				// TODO: Check if visible
				if (graph.cellEditor.textarea != null)
				{
					graph.cellEditor.textarea.focus();
		      		document.execCommand('formatBlock', false, '<' + tag + '>');
				}
			}), parent);
		};
		
		addItem(mxResources.get('normal'), 'p');
		
		addItem('', 'h1').firstChild.nextSibling.innerHTML = '<h1 style="margin:0px;">' + mxResources.get('heading') + ' 1</h1>';
		addItem('', 'h2').firstChild.nextSibling.innerHTML = '<h2 style="margin:0px;">' + mxResources.get('heading') + ' 2</h2>';
		addItem('', 'h3').firstChild.nextSibling.innerHTML = '<h3 style="margin:0px;">' + mxResources.get('heading') + ' 3</h3>';
		addItem('', 'h4').firstChild.nextSibling.innerHTML = '<h4 style="margin:0px;">' + mxResources.get('heading') + ' 4</h4>';
		addItem('', 'h5').firstChild.nextSibling.innerHTML = '<h5 style="margin:0px;">' + mxResources.get('heading') + ' 5</h5>';
		addItem('', 'h6').firstChild.nextSibling.innerHTML = '<h6 style="margin:0px;">' + mxResources.get('heading') + ' 6</h6>';
		
		addItem('', 'pre').firstChild.nextSibling.innerHTML = '<pre style="margin:0px;">' + mxResources.get('formatted') + '</pre>';
		addItem('', 'blockquote').firstChild.nextSibling.innerHTML = '<blockquote style="margin-top:0px;margin-bottom:0px;">' + mxResources.get('blockquote') + '</blockquote>';
	})));
	this.put('fontSize', new Menu(mxUtils.bind(this, function(menu, parent)
	{
		var sizes = [6, 8, 9, 10, 11, 12, 14, 18, 24, 36, 48, 72];

		if (mxUtils.indexOf(sizes, this.defaultFontSize) < 0)
		{
			sizes.push(this.defaultFontSize);
			sizes.sort(function(a, b)
			{
				return a - b;
			});
		}

		var setFontSize = mxUtils.bind(this, function(fontSize)
		{
			if (graph.cellEditor.textarea != null)
			{
				// Creates an element with arbitrary size 3
				document.execCommand('fontSize', false, '3');
				
				// Changes the css font size of the first font element inside the in-place editor with size 3
				// hopefully the above element that we've just created. LATER: Check for new element using
				// previous result of getElementsByTagName (see other actions)
				var elts = graph.cellEditor.textarea.getElementsByTagName('font');
				
				for (var i = 0; i < elts.length; i++)
				{
					if (elts[i].getAttribute('size') == '3')
					{
						elts[i].removeAttribute('size');
						elts[i].style.fontSize = fontSize + 'px';
						
						break;
					}
				}

				ui.fireEvent(new mxEventObject('styleChanged',
					'keys', [mxConstants.STYLE_FONTSIZE],
					'values', [fontSize],
					'cells', [graph.cellEditor.getEditingCell()]));
			}
		});
		
		var addItem = mxUtils.bind(this, function(fontSize)
		{
			this.styleChange(menu, fontSize, [mxConstants.STYLE_FONTSIZE],
				[fontSize], null, parent, function()
			{
				setFontSize(fontSize);
			});
		});
		
		for (var i = 0; i < sizes.length; i++)
		{
			addItem(sizes[i]);
		}

		menu.addSeparator(parent);
		
		if (this.customFontSizes.length > 0)
		{
			var counter = 0;

			for (var i = 0; i < this.customFontSizes.length; i++)
			{
				if (mxUtils.indexOf(sizes, this.customFontSizes[i]) < 0)
				{
					addItem(this.customFontSizes[i]);
					counter++;
				}
			}
			
			if (counter > 0)
			{
				menu.addSeparator(parent);
			}
			
			menu.addItem(mxResources.get('reset'), null, mxUtils.bind(this, function()
			{
				this.customFontSizes = [];
			}), parent);
			
			menu.addSeparator(parent);
		}

		var selState = null;
		
		this.promptChange(menu, mxResources.get('custom') + '...',
			'(' + mxResources.get('points') + ')', this.defaultFontSize,
			mxConstants.STYLE_FONTSIZE, parent, true,
			mxUtils.bind(this, function(newValue)
		{
			if (selState != null && graph.cellEditor.textarea != null)
			{
				graph.cellEditor.textarea.focus();
				graph.cellEditor.restoreSelection(selState);
			}

			if (newValue != null && newValue.length > 0)
			{
				this.customFontSizes.push(newValue);
				setFontSize(newValue);
			}
		}), null, function()
		{
			selState = graph.cellEditor.saveSelection();

			return false;
		});
	})));
	this.put('direction', new Menu(mxUtils.bind(this, function(menu, parent)
	{
		menu.addItem(mxResources.get('flipH'), null, function() { graph.toggleCellStyles(mxConstants.STYLE_FLIPH, false); }, parent);
		menu.addItem(mxResources.get('flipV'), null, function() { graph.toggleCellStyles(mxConstants.STYLE_FLIPV, false); }, parent);
		this.addMenuItems(menu, ['-', 'rotation'], parent);
	})));
	this.put('align', new Menu(mxUtils.bind(this, function(menu, parent)
	{
		var ss = this.editorUi.getSelectionState();
		var enabled = ss.vertices.length > 1;
		menu.addItem(mxResources.get('leftAlign'), null, function() { graph.alignCells(mxConstants.ALIGN_LEFT); }, parent, null, enabled);
		menu.addItem(mxResources.get('center'), null, function() { graph.alignCells(mxConstants.ALIGN_CENTER); }, parent, null, enabled);
		menu.addItem(mxResources.get('rightAlign'), null, function() { graph.alignCells(mxConstants.ALIGN_RIGHT); }, parent, null, enabled);
		menu.addSeparator(parent);
		menu.addItem(mxResources.get('topAlign'), null, function() { graph.alignCells(mxConstants.ALIGN_TOP); }, parent, null, enabled);
		menu.addItem(mxResources.get('middle'), null, function() { graph.alignCells(mxConstants.ALIGN_MIDDLE); }, parent, null, enabled);
		menu.addItem(mxResources.get('bottomAlign'), null, function() { graph.alignCells(mxConstants.ALIGN_BOTTOM); }, parent, null, enabled);
		this.addMenuItems(menu, ['-', 'snapToGrid'], parent);
	})));
	this.put('distribute', new Menu(mxUtils.bind(this, function(menu, parent)
	{
		menu.addItem(mxResources.get('horizontal'), null, function() { graph.distributeCells(true); }, parent);
		menu.addItem(mxResources.get('vertical'), null, function() { graph.distributeCells(false); }, parent);
		menu.addSeparator(parent);
		this.addSubmenu('distributeSpacing', menu, parent, mxResources.get('spacing'));
	})));
	this.put('distributeSpacing', new Menu(mxUtils.bind(this, function(menu, parent)
	{
		menu.addItem(mxResources.get('horizontal'), null, function() { graph.distributeCells(true, null, true); }, parent);
		menu.addItem(mxResources.get('vertical'), null, function() { graph.distributeCells(false, null, true); }, parent);
	})));
	this.put('layout', new Menu(mxUtils.bind(this, function(menu, parent)
	{
		var promptSpacing = mxUtils.bind(this, function(defaultValue, fn)
		{
			this.editorUi.prompt(mxResources.get('spacing'), defaultValue, fn);
		});

		var runTreeLayout = mxUtils.bind(this, function(layout)
		{
			this.editorUi.tryAndHandle(mxUtils.bind(this, function()
			{
				var tmp = graph.getSelectionCell();
				var roots = null;
				
				if (tmp == null || graph.getModel().getChildCount(tmp) == 0)
				{
					if (graph.getModel().getEdgeCount(tmp) == 0)
					{
						roots = graph.findTreeRoots(graph.getDefaultParent());
					}
				}
				else
				{
					roots = graph.findTreeRoots(tmp);
				}
	
				if (roots != null && roots.length > 0)
				{
					tmp = roots[0];
				}
				
				if (tmp != null)
				{
					this.editorUi.executeLayout(function()
					{
						layout.execute(graph.getDefaultParent(), tmp);

						if (!graph.isSelectionEmpty())
						{
							tmp = graph.getModel().getParent(tmp);
							
							if (graph.getModel().isVertex(tmp))
							{
								graph.updateGroupBounds([tmp], graph.gridSize * 2, true);
							}
						}
					}, true);
				}
			}));
		});

		menu.addItem(mxResources.get('horizontalFlow'), null, mxUtils.bind(this, function()
		{
			this.editorUi.tryAndHandle(mxUtils.bind(this, function()
			{
				var layout = new mxHierarchicalLayout(graph, mxConstants.DIRECTION_WEST);

				this.editorUi.executeLayout(function()
				{
					var selectionCells = graph.getSelectionCells();
					layout.execute(graph.getDefaultParent(), selectionCells.length == 0 ? null : selectionCells);
				}, true);
			}));
		}), parent);
		menu.addItem(mxResources.get('verticalFlow'), null, mxUtils.bind(this, function()
		{
			this.editorUi.tryAndHandle(mxUtils.bind(this, function()
			{
				var layout = new mxHierarchicalLayout(graph, mxConstants.DIRECTION_NORTH);
				
				this.editorUi.executeLayout(function()
				{
					var selectionCells = graph.getSelectionCells();
					layout.execute(graph.getDefaultParent(), selectionCells.length == 0 ? null : selectionCells);
				}, true);
			}));
		}), parent);
		menu.addSeparator(parent);
		menu.addItem(mxResources.get('horizontalTree'), null, mxUtils.bind(this, function()
		{
			var layout = new mxCompactTreeLayout(graph, true);
			layout.edgeRouting = false;
			layout.levelDistance = 30;
			layout.sortEdges = true;

			promptSpacing(layout.levelDistance, mxUtils.bind(this, function(spacing)
			{
				if (!isNaN(spacing))
				{
					layout.levelDistance = spacing;
					runTreeLayout(layout);
				}
			}));
		}), parent);
		menu.addItem(mxResources.get('verticalTree'), null, mxUtils.bind(this, function()
		{
			var layout = new mxCompactTreeLayout(graph, false);
			layout.edgeRouting = false;
			layout.levelDistance = 30;
			layout.sortEdges = true;

			promptSpacing(layout.levelDistance, mxUtils.bind(this, function(spacing)
			{
				if (!isNaN(spacing))
				{
					layout.levelDistance = spacing;
					runTreeLayout(layout);
				}
			}));
		}), parent);
		menu.addItem(mxResources.get('radialTree'), null, mxUtils.bind(this, function()
		{
			var layout = new mxRadialTreeLayout(graph);
			layout.levelDistance = 80;
			layout.autoRadius = true;

			promptSpacing(layout.levelDistance, mxUtils.bind(this, function(spacing)
			{
				if (!isNaN(spacing))
				{
					layout.levelDistance = spacing;
					runTreeLayout(layout);
				}
			}));
		}), parent);
		menu.addSeparator(parent);
		menu.addItem(mxResources.get('organic'), null, mxUtils.bind(this, function()
		{
			var layout = new mxFastOrganicLayout(graph);
			
			promptSpacing(layout.forceConstant, mxUtils.bind(this, function(newValue)
			{
				this.editorUi.tryAndHandle(mxUtils.bind(this, function()
				{
					layout.forceConstant = newValue;
					
					this.editorUi.executeLayout(function()
					{
						var tmp = graph.getSelectionCell();
						
						if (tmp == null || graph.getModel().getChildCount(tmp) == 0)
						{
							tmp = graph.getDefaultParent();
						}
						
						layout.execute(tmp);
						
						if (graph.getModel().isVertex(tmp))
						{
							graph.updateGroupBounds([tmp], graph.gridSize * 2, true);
						}
					}, true);
				}));
			}));
		}), parent);
		menu.addItem(mxResources.get('circle'), null, mxUtils.bind(this, function()
		{
			this.editorUi.tryAndHandle(mxUtils.bind(this, function()
			{
				var layout = new mxCircleLayout(graph);
				
				this.editorUi.executeLayout(function()
				{
					var tmp = graph.getSelectionCell();
					
					if (tmp == null || graph.getModel().getChildCount(tmp) == 0)
					{
						tmp = graph.getDefaultParent();
					}
					
					layout.execute(tmp);
					
					if (graph.getModel().isVertex(tmp))
					{
						graph.updateGroupBounds([tmp], graph.gridSize * 2, true);
					}
				}, true);
			}));
		}), parent);
	})));
	this.put('navigation', new Menu(mxUtils.bind(this, function(menu, parent)
	{
		this.addMenuItems(menu, ['home', '-', 'exitGroup', 'enterGroup', '-', 'expand', 'collapse', '-', 'collapsible'], parent);
	})));
	this.put('arrange', new Menu(mxUtils.bind(this, function(menu, parent)
	{
		this.addMenuItems(menu, ['toFront', 'toBack', 'bringForward', 'sendBackward', '-'], parent);
		this.addSubmenu('direction', menu, parent);
		this.addMenuItems(menu, ['turn', '-'], parent);
		this.addSubmenu('align', menu, parent);
		this.addSubmenu('distribute', menu, parent);
		menu.addSeparator(parent);
		this.addSubmenu('navigation', menu, parent);
		this.addSubmenu('insert', menu, parent);
		this.addSubmenu('layout', menu, parent);
		this.addMenuItems(menu, ['-', 'group', 'ungroup', 'removeFromGroup', '-', 'clearWaypoints', 'autosize'], parent);
	}))).isEnabled = isGraphEnabled;
	this.put('insert', new Menu(mxUtils.bind(this, function(menu, parent)
	{
		this.addMenuItems(menu, ['insertLink', 'insertImage'], parent);
	})));
	this.put('view', new Menu(mxUtils.bind(this, function(menu, parent)
	{
		this.addMenuItems(menu, ((this.editorUi.format != null) ? ['format'] : []).
			concat(['outline', 'layers', '-', 'pageView', 'pageScale', '-', 'tooltips',
			        'grid', 'guides', '-', 'connectionArrows', 'connectionPoints', '-',
			        'resetView', 'zoomIn', 'zoomOut'], parent));
	})));
	// Two special dropdowns that are only used in the toolbar
	this.put('viewPanels', new Menu(mxUtils.bind(this, function(menu, parent)
	{
		if (this.editorUi.format != null)
		{
			this.addMenuItems(menu, ['format'], parent);
		}
		
		this.addMenuItems(menu, ['outline', 'layers'], parent);
	})));
	this.put('viewZoom', new Menu(mxUtils.bind(this, function(menu, parent)
	{
		this.addMenuItems(menu, ['smartFit', '-'], parent);
		var scales = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 2, 3, 4];
		
		for (var i = 0; i < scales.length; i++)
		{
			(function(scale)
			{
				menu.addItem((scale * 100) + '%', null, function()
				{
					graph.zoomTo(scale);
				}, parent);
			})(scales[i]);
		}

		this.addMenuItems(menu, ['-', 'fitWindow', 'fitPageWidth', 'fitPage', 'fitTwoPages', '-', 'customZoom'], parent);
	})));
	this.put('file', new Menu(mxUtils.bind(this, function(menu, parent)
	{
		this.addMenuItems(menu, ['new', 'open', '-', 'save', 'saveAs', '-', 'import', 'export', '-', 'pageSetup', 'print'], parent);
	})));
	this.put('edit', new Menu(mxUtils.bind(this, function(menu, parent)
	{
		this.addMenuItems(menu, ['undo', 'redo', '-', 'cut', 'copy', 'paste', 'delete', '-', 'duplicate', '-',
			'editData', 'editTooltip', '-', 'editStyle', '-', 'edit', '-', 'editLink', 'openLink', '-',
			'selectVertices', 'selectEdges', 'selectAll', 'selectNone', '-', 'lockUnlock']);
	})));
	this.put('extras', new Menu(mxUtils.bind(this, function(menu, parent)
	{
		this.addMenuItems(menu, ['copyConnect', 'collapseExpand', '-', 'editDiagram']);
	})));
	this.put('help', new Menu(mxUtils.bind(this, function(menu, parent)
	{
		this.addMenuItems(menu, ['help', '-', 'about']);
	})));
};

/**
 * Adds the label menu items to the given menu and parent.
 */
Menus.prototype.put = function(name, menu)
{
	this.menus[name] = menu;
	
	return menu;
};

/**
 * Adds the label menu items to the given menu and parent.
 */
Menus.prototype.get = function(name)
{
	return this.menus[name];
};

/**
 * Adds the given submenu.
 */
Menus.prototype.addSubmenu = function(name, menu, parent, label)
{
	if (this.hiddenMenuItems != null && this.hiddenMenuItems[name])
	{
		return null;
	}

	var entry = this.get(name);
	var submenu = null;
	
	if (entry != null)
	{
		var enabled = entry.isEnabled();
	
		if (menu.showDisabled || enabled)
		{
			submenu = menu.addItem(label || mxResources.get(name),
				null, null, parent, null, enabled);
			this.addMenu(name, menu, submenu);
		}
	}

	return submenu;
};

/**
 * Adds the label menu items to the given menu and parent.
 */
Menus.prototype.addMenu = function(name, popupMenu, parent)
{
	var menu = this.get(name);

	if (menu != null && (popupMenu.showDisabled || menu.isEnabled()))
	{
		menu.execute(popupMenu, parent);
	}

	// Appends plugin-registered items
	var pluginItems = this.pluginMenuItems[name];

	if (pluginItems != null && pluginItems.length > 0)
	{
		popupMenu.addSeparator(parent);

		for (var i = 0; i < pluginItems.length; i++)
		{
			if (pluginItems[i] == '-')
			{
				popupMenu.addSeparator(parent);
			}
			else
			{
				this.addMenuItem(popupMenu, pluginItems[i], parent);
			}
		}
	}
};

/**
 * Registers additional menu items (action names) to append to a
 * named menu. Items can be action names or '-' for separators.
 */
Menus.prototype.addPluginMenuItems = function(menuName, items)
{
	if (this.pluginMenuItems[menuName] == null)
	{
		this.pluginMenuItems[menuName] = [];
	}

	this.pluginMenuItems[menuName] =
		this.pluginMenuItems[menuName].concat(items);
};

/**
 * Adds a menu item to insert a table cell.
 */
Menus.prototype.addInsertTableCellItem = function(menu, parent)
{
	var graph = this.editorUi.editor.graph;
	var cell = graph.getSelectionCell();
	var style = graph.getCurrentCellStyle(cell);

	if (graph.getSelectionCount() > 1)
	{
		if (graph.isTableCell(cell))
		{
			cell = graph.model.getParent(cell);
		}

		if (graph.isTableRow(cell))
		{
			cell = graph.model.getParent(cell);
		}
	}

	var isTable = graph.isTable(cell) ||
		graph.isTableRow(cell) ||
		graph.isTableCell(cell);
	var isStack = graph.isStack(cell) ||
		graph.isStackChild(cell);

	var showCols = isTable;
	var showRows = isTable;

	if (isStack)
	{
		var style = (graph.isStack(cell)) ? style :
			graph.getCellStyle(graph.model.getParent(cell));

		showRows = style['horizontalStack'] == '0';
		showCols = !showRows;
	}

	if (parent != null || (!isTable && !isStack))
	{
		this.addInsertTableItem(menu, mxUtils.bind(this, function(evt, rows, cols, title, container)
		{
			var table = (container || mxEvent.isControlDown(evt) || mxEvent.isMetaDown(evt)) ?
				graph.createCrossFunctionalSwimlane(rows, cols, null, null,
					(title || mxEvent.isShiftDown(evt)) ? 'Cross-Functional Flowchart' : null) :
				graph.createTable(rows, cols, null, null,
					(title || mxEvent.isShiftDown(evt)) ? 'Table' : null);
			var pt = (mxEvent.isAltDown(evt)) ? graph.getFreeInsertPoint() :
				graph.getCenterInsertPoint(graph.getBoundingBoxFromGeometry([table], true));
			var select = null;

			graph.getModel().beginUpdate();
			try
			{
				select = graph.importCells([table], pt.x, pt.y);
				graph.fireEvent(new mxEventObject('cellsInserted', 'cells',
					graph.model.getDescendants(select[0])));
			}
			finally
			{
				graph.getModel().endUpdate();
			}
			
			if (select != null && select.length > 0)
			{
				graph.scrollCellToVisible(select[0]);
				graph.setSelectionCells(select);
			}
		}), parent);
	}
	else
	{
		if (showCols)
		{
			var elt = menu.addItem(mxResources.get('insertColumnBefore'), Editor.addColumnLeftImage, mxUtils.bind(this, function()
			{
				try
				{
					if (isStack)
					{
						graph.insertLane(cell, true);
					}
					else
					{
						graph.insertTableColumn(cell, true);
					}
				}
				catch (e)
				{
					this.editorUi.handleError(e);
				}
			}));
			elt.setAttribute('title', mxResources.get('insertColumnBefore'));
			
			elt = menu.addItem(mxResources.get('insertColumnAfter'), Editor.addColumnRightImage, mxUtils.bind(this, function()
			{	
				try
				{
					if (isStack)
					{
						graph.insertLane(cell, false);
					}
					else
					{
						graph.insertTableColumn(cell, false);
					}
				}
				catch (e)
				{
					this.editorUi.handleError(e);
				}
			}));
			elt.setAttribute('title', mxResources.get('insertColumnAfter'));

			elt = menu.addItem(mxResources.get('deleteColumn'), Editor.removeColumnImage, mxUtils.bind(this, function()
			{
				if (cell != null)
				{
					try
					{
						if (isStack)
						{
							graph.deleteLane(cell);
						}
						else
						{
							graph.deleteTableColumn(cell);
						}
					}
					catch (e)
					{
						this.editorUi.handleError(e);
					}
				}
			}));
			elt.setAttribute('title', mxResources.get('deleteColumn'));
		}
		
		if (showRows)
		{
			elt = menu.addItem(mxResources.get('insertRowBefore'), Editor.addRowAboveImage, mxUtils.bind(this, function()
			{
				try
				{
					if (isStack)
					{
						graph.insertLane(cell, true);
					}
					else
					{
						graph.insertTableRow(cell, true);
					}
				}
				catch (e)
				{
					this.editorUi.handleError(e);
				}
			}));
			elt.setAttribute('title', mxResources.get('insertRowBefore'));

			elt = menu.addItem(mxResources.get('insertRowAfter'), Editor.addRowBelowImage, mxUtils.bind(this, function()
			{
				try
				{
					if (isStack)
					{
						graph.insertLane(cell, false);
					}
					else
					{
						graph.insertTableRow(cell, false);
					}
				}
				catch (e)
				{
					this.editorUi.handleError(e);
				}
			}));
			elt.setAttribute('title', mxResources.get('insertRowAfter'));

			elt = menu.addItem(mxResources.get('deleteRow'), Editor.removeRowImage, mxUtils.bind(this, function()
			{
				try
				{
					if (isStack)
					{
						graph.deleteLane(cell);
					}
					else
					{
						graph.deleteTableRow(cell);
					}
				}
				catch (e)
				{
					this.editorUi.handleError(e);
				}
			}));
			elt.setAttribute('title', mxResources.get('deleteRow'));
			
			var ss = this.editorUi.getSelectionState();
			
			if (ss.mergeCell != null)
			{
				this.addMenuItem(menu, 'mergeCells');
			}
			else if (ss.style['colspan'] > 1 || ss.style['rowspan'] > 1)
			{
				this.addMenuItem(menu, 'unmergeCells');
			}
		}
	}
};	

/**
 * Adds a menu item to insert a table.
 */
Menus.prototype.addInsertTableItem = function(menu, insertFn, parent, showOptions)
{
	showOptions = (showOptions != null) ? showOptions : true;
	var graph = this.editorUi.editor.graph;
	
	function createTable(rows, cols)
	{
		var html = ['<table>'];
		
		for (var i = 0; i < rows; i++)
		{
			html.push('<tr>');
			
			for (var j = 0; j < cols; j++)
			{
				html.push('<td><br></td>');
			}
			
			html.push('</tr>');
		}
		
		html.push('</table>');
		
		return html.join('');
	};
	
	insertFn = (insertFn != null) ? insertFn : mxUtils.bind(this, function(evt, rows, cols)
	{
		var td = graph.getParentByName(mxEvent.getSource(evt), 'TD');

		if (td != null && graph.cellEditor.textarea != null)
		{
			// To find the new link, we create a list of all existing links first
    		// LATER: Refactor for reuse with code for finding inserted image below
			var tmp = graph.cellEditor.textarea.getElementsByTagName('table');
			var oldTables = [];
			
			for (var i = 0; i < tmp.length; i++)
			{
				oldTables.push(tmp[i]);
			}
			
			// Finding the new table will work with insertHTML, but IE does not support that
			graph.container.focus();
			graph.pasteHtmlAtCaret(createTable(rows, cols));
			
			// Moves cursor to first table cell
			var newTables = graph.cellEditor.textarea.getElementsByTagName('table');
			
			if (newTables.length == oldTables.length + 1)
			{
				// Inverse order in favor of appended tables
				for (var i = newTables.length - 1; i >= 0; i--)
				{
					if (i == 0 || newTables[i] != oldTables[i - 1])
					{
						graph.selectNode(newTables[i].rows[0].cells[0]);
						break;
					}
				}
			}
		}
	});
	
	if (parent == null)
	{
		menu.div.classList.add('geToolbarMenu');
		menu.labels = false;
	}

	var elt2 = menu.addItem('', null, null, parent, null, null, null, true);
	elt2.firstChild.style.fontSize = Menus.prototype.defaultFontSize + 'px';

	// Hide empty rows in menu item
	var tds = elt2.getElementsByTagName('td');

	if (tds.length > 1)
	{
		tds[1].style.display = 'none';
		tds[2].style.display = 'none';
	}
	
	function createPicker(rows, cols)
	{
		var table2 = document.createElement('table');
		table2.className = 'geInsertTablePicker';
		table2.setAttribute('border', '1');
		table2.style.borderCollapse = 'collapse';
		table2.style.borderStyle = 'solid';
		table2.style.marginBottom = '4px';
		table2.style.marginTop = '8px';
		table2.setAttribute('cellPadding', '8');
		
		for (var i = 0; i < rows; i++)
		{
			var row = table2.insertRow(i);
			
			for (var j = 0; j < cols; j++)
			{
				row.insertCell(-1);
			}
		}
		
		return table2;
	};

	function extendPicker(picker, rows, cols)
	{
		for (var i = picker.rows.length; i < rows; i++)
		{
			var row = picker.insertRow(i);
			
			for (var j = 0; j < picker.rows[0].cells.length; j++)
			{
				row.insertCell(-1);
			}
		}
		
		for (var i = 0; i < picker.rows.length; i++)
		{
			var row = picker.rows[i];
			
			for (var j = row.cells.length; j < cols; j++)
			{
				row.insertCell(-1);
			}
		}
	};
	
	elt2.firstChild.innerText = '';
	
	var titleOption = document.createElement('input');
	titleOption.setAttribute('id', 'geTitleOption');
	titleOption.setAttribute('type', 'checkbox');
	titleOption.style.verticalAlign = 'middle';

	var titleLbl = document.createElement('label');
	mxUtils.write(titleLbl, mxResources.get('title'));
	titleLbl.setAttribute('for', 'geTitleOption');
	titleLbl.style.verticalAlign = 'middle';

	mxEvent.addGestureListeners(titleLbl, null, null, mxUtils.bind(this, function(e)
	{
		mxEvent.consume(e);
	}));
	
	mxEvent.addGestureListeners(titleOption, null, null, mxUtils.bind(this, function(e)
	{
		mxEvent.consume(e);
	}));
	
	var containerOption = document.createElement('input');
	containerOption.setAttribute('id', 'geContainerOption');
	containerOption.setAttribute('type', 'checkbox');
	containerOption.style.verticalAlign = 'middle';
	
	var containerLbl = document.createElement('label');
	mxUtils.write(containerLbl, mxResources.get('container'));
	containerLbl.setAttribute('for', 'geContainerOption');
	containerLbl.style.verticalAlign = 'middle';

	mxEvent.addGestureListeners(containerLbl, null, null, mxUtils.bind(this, function(e)
	{
		mxEvent.consume(e);
	}));
	
	mxEvent.addGestureListeners(containerOption, null, null, mxUtils.bind(this, function(e)
	{
		mxEvent.consume(e);
	}));
	
	if (showOptions)
	{
		var titleRow = document.createElement('div');
		titleRow.style.marginBottom = '4px';
		titleRow.appendChild(titleOption);
		titleRow.appendChild(titleLbl);
		elt2.firstChild.appendChild(titleRow);

		var containerRow = document.createElement('div');
		containerRow.style.marginBottom = '6px';
		containerRow.appendChild(containerOption);
		containerRow.appendChild(containerLbl);
		elt2.firstChild.appendChild(containerRow);
	}
	
	var picker = createPicker(5, 5);
	elt2.firstChild.appendChild(picker);
	
	var label = document.createElement('div');
	label.style.textAlign = 'center';
	label.style.padding = '4px';
	label.style.width = '100%';
	label.innerHTML = '1x1';
	elt2.firstChild.appendChild(label);
	
	var row2 = null;
	var td = null;
	
	function mouseover(e)
	{
		td = graph.getParentByName(mxEvent.getSource(e), 'TD');		
		var selected = false;
		
		if (td != null)
		{
			row2 = graph.getParentByName(td, 'TR');
			var ext = (mxEvent.isMouseEvent(e)) ? 2 : 4;
			extendPicker(picker, Math.min(20, row2.sectionRowIndex + ext), Math.min(20, td.cellIndex + ext));
			label.innerHTML = (td.cellIndex + 1) + 'x' + (row2.sectionRowIndex + 1);
			
			for (var i = 0; i < picker.rows.length; i++)
			{
				var r = picker.rows[i];
				
				for (var j = 0; j < r.cells.length; j++)
				{
					var cell = r.cells[j];
					
					if (i == row2.sectionRowIndex &&
						j == td.cellIndex)
					{
						selected = cell.style.backgroundColor == 'blue';
					}
					
					if (i <= row2.sectionRowIndex && j <= td.cellIndex)
					{
						cell.style.backgroundColor = 'blue';
					}
					else
					{
						cell.style.backgroundColor = 'transparent';
					}
				}
			}
		}
		
		mxEvent.consume(e);

		return selected;
	};
	
	mxEvent.addGestureListeners(picker, null, null, mxUtils.bind(this, function(e)
	{
		var selected = mouseover(e);
		
		if (td != null && row2 != null && selected)
		{
			insertFn(e, row2.sectionRowIndex + 1, td.cellIndex + 1,
				titleOption.checked, containerOption.checked);
			
			// Async required to block event for elements under menu
			window.setTimeout(mxUtils.bind(this, function()
			{
				this.editorUi.hideCurrentMenu();
			}), 0);
		}
	}));
	
	mxEvent.addListener(picker, 'mouseover', mouseover);
};

/**
 * Adds a style change item to the given menu.
 */
Menus.prototype.edgeStyleChange = function(menu, label, keys, values, sprite, parent, reset, image)
{
	return this.showIconOnly(menu.addItem(label, image, mxUtils.bind(this, function()
	{
		var graph = this.editorUi.editor.graph;
		graph.stopEditing(false);
		
		graph.getModel().beginUpdate();
		try
		{
			var cells = graph.getSelectionCells();
			var edges = [];
			
			for (var i = 0; i < cells.length; i++)
			{
				var cell = cells[i];

				if (graph.getModel().isEdge(cell))
				{
					if (reset)
					{
						var geo = graph.getCellGeometry(cell);

						// Resets all edge points
						if (geo != null)
						{
							geo = geo.clone();
							geo.points = null;
							graph.getModel().setGeometry(cell, geo);
						}
					}

					for (var j = 0; j < keys.length; j++)
					{
						graph.setCellStyles(keys[j], values[j], [cell]);
					}

					edges.push(cell);
				}
				else if (mxUtils.getValue(graph.getCurrentCellStyle(cell),
					mxConstants.STYLE_SHAPE) == 'mxgraph.basic.arc')
				{
					for (var j = 0; j < keys.length; j++)
					{
						graph.setCellStyles(keys[j], values[j], [cell]);
					}

					edges.push(cell);
				}
			}
			
			this.editorUi.fireEvent(new mxEventObject(
				'styleChanged', 'cells', edges,
				'keys', keys, 'values', values,
				'force', edges.length == 0));
		}
		finally
		{
			graph.getModel().endUpdate();
		}
	}), parent, sprite));
};

/**
 * Adds a style change item to the given menu.
 */
Menus.prototype.showIconOnly = function(elt)
{
	var td = elt.getElementsByTagName('td');

	for (var i = 0; i < td.length; i++)
	{
		if (td[i].getAttribute('class') == 'mxPopupMenuItem')
		{
			td[i].style.display = 'none';
		}
	}
	
	return elt;
};

/**
 * Adds a style change item to the given menu.
 */
Menus.prototype.styleChange = function(menu, label, keys, values, sprite, parent, fn, post, iconOnly, image)
{
	var apply = this.createStyleChangeFunction(keys, values);
	
	var elt = menu.addItem(label, image, mxUtils.bind(this, function()
	{
		var graph = this.editorUi.editor.graph;
		
		if (fn != null && graph.cellEditor.isContentEditing())
		{
			fn();
		}
		else
		{
			apply(post);
		}
	}), parent, sprite);
	
	if (iconOnly)
	{
		this.showIconOnly(elt);
	}
	
	return elt;
};

/**
 * 
 */
Menus.prototype.createStyleChangeFunction = function(keys, values)
{
	return mxUtils.bind(this, function(post)
	{
		var graph = this.editorUi.editor.graph;
		graph.stopEditing(false);
		
		graph.getModel().beginUpdate();
		try
		{
			var cells = graph.getEditableCells(graph.getSelectionCells());
			var autoSizeCells = false;
			
			for (var i = 0; i < keys.length; i++)
			{
				graph.setCellStyles(keys[i], values[i], cells);

				// Removes CSS alignment to produce consistent output
				if (keys[i] == mxConstants.STYLE_ALIGN)
				{
					graph.updateLabelElements(cells, function(elt)
					{
						elt.removeAttribute('align');
						elt.style.textAlign = null;
					});
				}
				
				// Updates autosize after font changes
				if (keys[i] == mxConstants.STYLE_FONTFAMILY ||
					keys[i] == 'fontSource')
				{
					autoSizeCells = true;
				}
			}
			
			if (autoSizeCells)
			{
				for (var j = 0; j < cells.length; j++)
				{
					if (graph.model.getChildCount(cells[j]) == 0)
					{
						graph.autoSizeCell(cells[j], false);
					}
				}
			}
			
			if (post != null)
			{
				post();
			}
			
			this.editorUi.fireEvent(new mxEventObject('styleChanged',
				'keys', keys, 'values', values, 'cells', cells));
		}
		finally
		{
			graph.getModel().endUpdate();
		}
	});
};

/**
 * Adds a style change item with a prompt to the given menu.
 */
Menus.prototype.promptChange = function(menu, label, hint, defaultValue, key, parent, enabled, fn, sprite, beforeFn)
{
	return menu.addItem(label, null, mxUtils.bind(this, function()
	{
		var graph = this.editorUi.editor.graph;
		var value = defaultValue;
    	var state = graph.getView().getState(graph.getSelectionCell());
    	
    	if (state != null)
    	{
    		value = state.style[key] || value;
    	}

		var doStopEditing = (beforeFn != null) ? beforeFn() : true;
    	
		var dlg = new FilenameDialog(this.editorUi, value, mxResources.get('apply'),
			mxUtils.bind(this, function(newValue)
		{
			if (newValue != null && newValue.length > 0)
			{
				if (doStopEditing)
				{
					graph.getModel().beginUpdate();
					try
					{
						graph.stopEditing(false);
						graph.setCellStyles(key, newValue);
					}
					finally
					{
						graph.getModel().endUpdate();
					}
				}
				
				if (fn != null)
				{
					fn(newValue);
				}
			}
		}), mxResources.get('enterValue') + ((hint.length > 0) ? (' ' + hint) : ''),
			null, null, null, null, function()
		{
			if (fn != null && beforeFn != null)
			{
				fn(null);
			}
		});
		this.editorUi.showDialog(dlg.container, 300, 80, true, true);
		dlg.init();
	}), parent, sprite, enabled);
};

/**
 * Adds a handler for showing a menu in the given element.
 */
Menus.prototype.pickColor = function(key, cmd, defaultValue, defaultColor, defaultColorValue, singleColorMode, title)
{
	this.editorUi.tryAndHandle(mxUtils.bind(this, function()
	{
		var graph = this.editorUi.editor.graph;

		if (cmd != null && graph.cellEditor.isContentEditing())
		{
			// Gets current foreground or background color
			var node = graph.getSelectedEditingElement();

			if (node != null && graph.cellEditor.textarea != null)
			{
				var color = graph.getTextColor(node, cmd == 'forecolor');

				this.editorUi.pickColor(color, mxUtils.bind(this, function(color)
				{
					if (graph.cellEditor.textarea != null)
					{
						Graph.setTextColor(graph.cellEditor.textarea, color, cmd == 'forecolor');
						this.editorUi.fireEvent(new mxEventObject('styleChanged',
							'keys', [(cmd == 'forecolor') ? mxConstants.STYLE_FONTCOLOR :
								mxConstants.STYLE_LABEL_BACKGROUNDCOLOR], 'values', [color],
							'cells', [graph.cellEditor.getEditingCell()]));
					}
				}), defaultColor, defaultColorValue, singleColorMode, title);
			}
		}
		else
		{
			var getColorFn = function()
			{
				var style = graph.getCellStyle(graph.getSelectionCell(), false);

				return (style != null) ? (style[key] || mxConstants.NONE) : mxConstants.NONE;
			};

			this.editorUi.pickColor(getColorFn(), ColorDialog.createApplyFunction(
				this.editorUi, key), defaultColor, defaultColorValue,
				singleColorMode, title, getColorFn);
		}
	}));
};

/**
 * Adds a handler for showing a menu in the given element.
 */
Menus.prototype.toggleStyle = function(key, defaultValue)
{
	var graph = this.editorUi.editor.graph;
	var value = graph.toggleCellStyles(key, defaultValue);
	this.editorUi.fireEvent(new mxEventObject('styleChanged', 'keys', [key], 'values', [value],
			'cells', graph.getSelectionCells()));
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
Menus.prototype.addMenuItem = function(menu, key, parent, trigger, sprite, label)
{
	if (this.hiddenMenuItems != null && this.hiddenMenuItems[key])
	{
		return null;
	}

	var action = this.editorUi.actions.get(key);

	if (action != null && (menu.showDisabled || action.isEnabled()) && action.visible)
	{
		var item = menu.addItem(label || action.getTitle(), null, mxUtils.bind(this, function(evt)
		{
			try
			{
				action.funct(trigger, evt);
			}
			catch (e)
			{
				this.editorUi.handleError(e);
			}
		}), parent, sprite, action.isEnabled());
		
		// Adds checkmark image
		if (action.toggleAction && action.isSelected())
		{
			menu.addCheckmark(item, Editor.checkmarkImage);
		}

		this.addShortcut(item, action, menu.hideShortcuts);
		
		return item;
	}
	
	return null;
};

/**
 * Adds a checkmark to the given menuitem.
 */
Menus.prototype.addShortcut = function(item, action, asTooltip)
{
	if (action.shortcut != null)
	{
		if (asTooltip)
		{
			item.setAttribute('title', action.shortcut);
		}
		else
		{
			var td = item.firstChild.nextSibling.nextSibling;	
			var span = document.createElement('span');
			span.style.color = 'gray';
			mxUtils.write(span, action.shortcut);
			td.appendChild(span);
		}
	}
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
Menus.prototype.addMenuItems = function(menu, keys, parent, trigger, sprites)
{
	for (var i = 0; i < keys.length; i++)
	{
		if (keys[i] == '-')
		{
			menu.addSeparator(parent);
		}
		else
		{
			this.addMenuItem(menu, keys[i], parent, trigger, (sprites != null) ? sprites[i] : null);
		}
	}
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
Menus.prototype.createPopupMenu = function(menu, cell, evt)
{
	menu.smartSeparators = true;
	menu.hideShortcuts = true;
	this.addPopupMenuItems(menu, cell, evt);
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
Menus.prototype.addPopupMenuItems = function(menu, cell, evt)
{
	if (this.isShowHistoryItems())
	{
		this.addPopupMenuHistoryItems(menu, cell, evt);	
	}

	this.addPopupMenuEditItems(menu, cell, evt);

	if (this.isShowStyleItems())
	{
		this.addPopupMenuStyleItems(menu, cell, evt);
	}

	if (this.isShowArrangeItems())
	{
		this.addPopupMenuArrangeItems(menu, cell, evt);
	}

	this.addPopupMenuCellItems(menu, cell, evt);
	this.addPopupMenuSelectionItems(menu, cell, evt);
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
Menus.prototype.isShowHistoryItems = function()
{
	return true;
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
Menus.prototype.addPopupMenuHistoryItems = function(menu, cell, evt)
{
	if (this.editorUi.editor.graph.isSelectionEmpty())
	{
		this.addMenuItems(menu, ['undo', 'redo'], null, evt);
	}
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
Menus.prototype.addPopupDeleteItem = function(menu, cell, evt)
{
	var item = this.addMenuItem(menu, 'delete');

	if (item != null && item.firstChild != null &&
		item.firstChild.nextSibling != null)
	{
		item.firstChild.nextSibling.style.color = 'red';
		var graph = this.editorUi.editor.graph;

		if (graph.getSelectionCount() > 1)
		{
			item.firstChild.nextSibling.innerHTML =
				mxUtils.htmlEntities(mxResources.get('delete') +
				' (' + graph.getSelectionCount() + ')');
		}
	}
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
Menus.prototype.addPopupMenuEditItems = function(menu, cell, evt)
{
	if (this.editorUi.editor.graph.isSelectionEmpty())
	{
		this.addMenuItems(menu, ['pasteHere'], null, evt);
	}
	else
	{
		this.addMenuItems(menu, ['cut', 'copy', 'duplicate',
			'-', 'delete', 'lockUnlock'], null, evt);
	}
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
Menus.prototype.isShowStyleItems = function()
{
	return true;
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
Menus.prototype.addPopupMenuStyleItems = function(menu, cell, evt)
{
	if (this.editorUi.editor.graph.getSelectionCount() == 1)
	{
		this.addMenuItems(menu, ['-', 'setAsDefaultStyle'], null, evt);
	}
	else if (this.editorUi.editor.graph.isSelectionEmpty())
	{
		this.addMenuItems(menu, ['-', 'clearDefaultStyle'], null, evt);
	}
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
Menus.prototype.isShowArrangeItems = function()
{
	return true;
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
Menus.prototype.addPopupMenuArrangeItems = function(menu, cell, evt)
{
	var graph = this.editorUi.editor.graph;
	
	// Shows group actions
	if (graph.getSelectionCount() > 1 || (graph.getSelectionCount() > 0 &&
		!graph.getModel().isEdge(cell) && !graph.isSwimlane(cell) &&
		graph.getModel().getChildCount(cell) > 0 && graph.isCellEditable(cell)))
	{
		this.addMenuItems(menu, ['group', 'ungroup'], null, evt);
	}

	var count = graph.getEditableCells(graph.getSelectionCells()).length;

	if (count > 1)
	{
		menu.addSeparator();
		this.addSubmenu('align', menu);
		this.addSubmenu('distribute', menu);
	}

	if (count >= 1)
	{
		this.addMenuItems(menu, ['-', 'toFront', 'toBack'], null, evt);
		
		if (this.isShowCellEditItems() && graph.getSelectionCount() == 1)
		{
			this.addMenuItems(menu, ['bringForward', 'sendBackward'], null, evt);
		}
	}
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
Menus.prototype.addPopupMenuCellItems = function(menu, cell, evt)
{
	var graph = this.editorUi.editor.graph;
	var ss = this.editorUi.getSelectionState();
	var state = graph.view.getState(cell);
	
	if (state != null)
	{
		// Adds reset waypoints option if waypoints exist
		var geo = graph.getModel().getGeometry(cell);
		var hasWaypoints = geo != null && geo.points != null && geo.points.length > 0;
		menu.addSeparator();

		if (this.isShowStyleItems() && ss.vertices.length == 0 && ss.edges.length > 0)
		{
			this.addSubmenu('line', menu);

			if (mxUtils.getValue(state.style, mxConstants.STYLE_EDGE, null) != 'entityRelationEdgeStyle' &&
				mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null) != 'arrow')
			{
				if (this.isShowCellEditItems() && ss.vertices.length == 0 && ss.edges.length > 0)
				{
					this.addMenuItem(menu, 'turn', null, evt, null, mxResources.get('reverse'));
				}
				
				if (graph.getSelectionCount() == 1)
				{	
					var handler = graph.selectionCellsHandler.getHandler(cell);
					var isWaypoint = false;
					
					if (hasWaypoints && handler instanceof mxEdgeHandler &&
						handler.bends != null && handler.bends.length > 2)
					{
						var index = handler.getHandleForEvent(
							graph.updateMouseEvent(new mxMouseEvent(evt)));

						// Ignores ghosted and virtual waypoints
						if (index > 0 && index < handler.bends.length - 1 &&
							(handler.bends[index] == null ||
							handler.bends[index].node == null ||
							handler.bends[index].node.style.opacity == ''))
						{
							// Configures removeWaypoint action before execution
							// Using trigger parameter is cleaner but have to find waypoint here anyway.
							var rmWaypointAction = this.editorUi.actions.get('removeWaypoint');
							rmWaypointAction.handler = handler;
							rmWaypointAction.index = index;

							isWaypoint = true;
						}
					}
					
					this.addMenuItems(menu, [(isWaypoint) ? 'removeWaypoint' : 'addWaypoint'], null, evt);
				}
			}
		}

		if (this.isShowCellEditItems() &&
			((ss.vertices.length == 0 && ss.edges.length > 0) ||
			(hasWaypoints || (graph.getModel().isVertex(cell) &&
			graph.getModel().getEdgeCount(cell) > 0))))
		{
			this.addMenuItems(menu, ['clearWaypoints'], null, evt);
		}
		
		if (this.isShowCellEditItems() &&
			graph.getSelectionCount() == 1 &&
			graph.isCellEditable(cell))
		{
			this.addPopupMenuCellEditItems(menu, cell, evt);
		}
	}
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
Menus.prototype.isShowCellEditItems = function()
{
	return true;
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
Menus.prototype.addPopupMenuCellEditItems = function(menu, cell, evt, parent)
{
	var graph = this.editorUi.editor.graph;
	var state = graph.view.getState(cell);
	this.addMenuItems(menu, ['-', 'editStyle', 'editData', 'editLink'], parent, evt);
	
	// Shows edit image action if there is an image in the style
	if (graph.getModel().isVertex(cell) && mxUtils.getValue(state.style, mxConstants.STYLE_IMAGE, null) != null)
	{
		menu.addSeparator();
		this.addMenuItem(menu, 'image', parent, evt).firstChild.nextSibling.innerHTML = mxResources.get('editImage') + '...';
		this.addMenuItem(menu, 'crop', parent, evt);
	}

	if (graph.getModel().isVertex(cell) && graph.isCellConnectable(cell))
	{
		this.addMenuItem(menu, 'editConnectionPoints', parent, evt);
	}

	if (state != null && mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE) === 'mxgraph.basic.polygon')
	{
		this.addMenuItem(menu, 'editPolygon', parent, evt);
	}
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
Menus.prototype.addPopupMenuSelectionItems = function(menu, cell, evt)
{
	if (this.editorUi.editor.graph.isSelectionEmpty())
	{
		this.addMenuItems(menu, ['-', 'selectAll', 'selectVertices', 'selectEdges'], null, evt);
	}
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
Menus.prototype.createMenubar = function(container)
{
	var menubar = new Menubar(this.editorUi, container);

	var initMenubar = mxUtils.bind(this, function()
	{
		var menus = this.defaultMenuItems;
		
		for (var i = 0; i < menus.length; i++)
		{
			(mxUtils.bind(this, function(menu)
			{
				var elt = menubar.addMenu(mxResources.get(menus[i]), mxUtils.bind(this, function()
				{
					try
					{
						// Allows extensions of menu.funct
						menu.funct.apply(this, arguments);
					}
					catch (e)
					{
						// ignore
					}
				}));
				
				this.menuCreated(menu, elt);
			}))(this.get(menus[i]));
		}
		
		if (Editor.currentTheme == 'kennedy' ||
			Editor.currentTheme == 'atlas')
		{
			container.appendChild(this.editorUi.statusContainer);
		}
	});

	initMenubar();

	// Adds language change listener
	var refresh = mxUtils.bind(this, function()
	{
		container.innerHTML = '';
		initMenubar();
	});

	this.editorUi.addListener('currentThemeChanged', refresh);
	this.editorUi.addListener('languageChanged', refresh);

	return menubar;
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
Menus.prototype.menuCreated = function(menu, elt, className)
{
	if (elt != null)
	{
		className = (className != null) ? className : 'geItem';
		elt.className = className;
		
		menu.addListener('stateChanged', function()
		{
			elt.enabled = menu.enabled;
			
			if (!menu.enabled)
			{
				elt.className = className + ' mxDisabled';
				
				if (document.documentMode == 8)
				{
					elt.style.color = '#c3c3c3';
				}
			}
			else
			{
				elt.className = className;
				
				if (document.documentMode == 8)
				{
					elt.style.color = '';
				}
			}
		});
	}
};

/**
 * Construcs a new menubar for the given editor.
 */
function Menubar(editorUi, container)
{
	this.editorUi = editorUi;
	this.container = container;
};

/**
 * Adds the menubar elements.
 */
Menubar.prototype.hideMenu = function()
{
	this.editorUi.hideCurrentMenu();
};

/**
 * Adds a submenu to this menubar.
 */
Menubar.prototype.addMenu = function(label, fn, before, clickFn)
{
	var elt = this.editorUi.createMenuElement(label, fn, clickFn);
	elt.className = 'geMenubar';	
	
    if (before != null)
    {
    	this.container.insertBefore(elt, before);
    }
    else
    {
    	this.container.appendChild(elt);
    }
	
	return elt;
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
Menubar.prototype.destroy = function()
{
	// do nothing
};

/**
 * Constructs a new action for the given parameters.
 */
function Menu(funct, enabled)
{
	mxEventSource.call(this);
	this.funct = funct;
	this.enabled = (enabled != null) ? enabled : true;
};

// Menu inherits from mxEventSource
mxUtils.extend(Menu, mxEventSource);

/**
 * Sets the enabled state of the action and fires a stateChanged event.
 */
Menu.prototype.isEnabled = function()
{
	return this.enabled;
};

/**
 * Sets the enabled state of the action and fires a stateChanged event.
 */
Menu.prototype.setEnabled = function(value)
{
	if (this.enabled != value)
	{
		this.enabled = value;
		this.fireEvent(new mxEventObject('stateChanged'));
	}
};

/**
 * Sets the enabled state of the action and fires a stateChanged event.
 */
Menu.prototype.execute = function(menu, parent)
{
	this.funct(menu, parent);
};

/**
 * "Installs" menus in EditorUi.
 */
EditorUi.prototype.createMenus = function()
{
	return new Menus(this);
};
