1/**
2 * Copyright (c) 2006-2012, JGraph Ltd
3 */
4/**
5 * Constructs a new graph editor
6 */
7Menus = function(editorUi)
8{
9	this.editorUi = editorUi;
10	this.menus = new Object();
11	this.init();
12
13	// Pre-fetches checkmark image
14	if (!mxClient.IS_SVG)
15	{
16		new Image().src = this.checkmarkImage;
17	}
18};
19
20/**
21 * Sets the default font family.
22 */
23Menus.prototype.defaultFont = 'Helvetica';
24
25/**
26 * Sets the default font size.
27 */
28Menus.prototype.defaultFontSize = '12';
29
30/**
31 * Sets the default font size.
32 */
33Menus.prototype.defaultMenuItems = ['file', 'edit', 'view', 'arrange', 'extras', 'help'];
34
35/**
36 * Adds the label menu items to the given menu and parent.
37 */
38Menus.prototype.defaultFonts = ['Helvetica', 'Verdana', 'Times New Roman', 'Garamond', 'Comic Sans MS',
39           		             'Courier New', 'Georgia', 'Lucida Console', 'Tahoma'];
40
41/**
42 * Adds the label menu items to the given menu and parent.
43 */
44Menus.prototype.init = function()
45{
46	var ui = this.editorUi;
47	var graph = ui.editor.graph;
48	var isGraphEnabled = mxUtils.bind(graph, graph.isEnabled);
49
50	this.customFonts = [];
51	this.customFontSizes = [];
52
53	this.put('fontFamily', new Menu(mxUtils.bind(this, function(menu, parent)
54	{
55		var addItem = mxUtils.bind(this, function(fontFamily)
56		{
57			var tr = this.styleChange(menu, fontFamily, [mxConstants.STYLE_FONTFAMILY],
58				[fontFamily], null, parent, function()
59			{
60				document.execCommand('fontname', false, fontFamily);
61				ui.fireEvent(new mxEventObject('styleChanged',
62					'keys', [mxConstants.STYLE_FONTFAMILY],
63					'values', [fontFamily],
64					'cells', [graph.cellEditor.getEditingCell()]));
65			}, function()
66			{
67				graph.updateLabelElements(graph.getSelectionCells(), function(elt)
68				{
69					elt.removeAttribute('face');
70					elt.style.fontFamily = null;
71
72					if (elt.nodeName == 'PRE')
73					{
74						graph.replaceElement(elt, 'div');
75					}
76				});
77			});
78
79			tr.firstChild.nextSibling.style.fontFamily = fontFamily;
80		});
81
82		for (var i = 0; i < this.defaultFonts.length; i++)
83		{
84			addItem(this.defaultFonts[i]);
85		}
86
87		menu.addSeparator(parent);
88
89		if (this.customFonts.length > 0)
90		{
91			for (var i = 0; i < this.customFonts.length; i++)
92			{
93				addItem(this.customFonts[i]);
94			}
95
96			menu.addSeparator(parent);
97
98			menu.addItem(mxResources.get('reset'), null, mxUtils.bind(this, function()
99			{
100				this.customFonts = [];
101				this.editorUi.fireEvent(new mxEventObject('customFontsChanged'));
102			}), parent);
103
104			menu.addSeparator(parent);
105		}
106
107		this.promptChange(menu, mxResources.get('custom') + '...', '', mxConstants.DEFAULT_FONTFAMILY, mxConstants.STYLE_FONTFAMILY, parent, true, mxUtils.bind(this, function(newValue)
108		{
109			if (mxUtils.indexOf(this.customFonts, newValue) < 0)
110			{
111				this.customFonts.push(newValue);
112				this.editorUi.fireEvent(new mxEventObject('customFontsChanged'));
113			}
114		}));
115	})));
116	this.put('formatBlock', new Menu(mxUtils.bind(this, function(menu, parent)
117	{
118		function addItem(label, tag)
119		{
120			return menu.addItem(label, null, mxUtils.bind(this, function()
121			{
122				// TODO: Check if visible
123				if (graph.cellEditor.textarea != null)
124				{
125					graph.cellEditor.textarea.focus();
126		      		document.execCommand('formatBlock', false, '<' + tag + '>');
127				}
128			}), parent);
129		};
130
131		addItem(mxResources.get('normal'), 'p');
132
133		addItem('', 'h1').firstChild.nextSibling.innerHTML = '<h1 style="margin:0px;">' + mxResources.get('heading') + ' 1</h1>';
134		addItem('', 'h2').firstChild.nextSibling.innerHTML = '<h2 style="margin:0px;">' + mxResources.get('heading') + ' 2</h2>';
135		addItem('', 'h3').firstChild.nextSibling.innerHTML = '<h3 style="margin:0px;">' + mxResources.get('heading') + ' 3</h3>';
136		addItem('', 'h4').firstChild.nextSibling.innerHTML = '<h4 style="margin:0px;">' + mxResources.get('heading') + ' 4</h4>';
137		addItem('', 'h5').firstChild.nextSibling.innerHTML = '<h5 style="margin:0px;">' + mxResources.get('heading') + ' 5</h5>';
138		addItem('', 'h6').firstChild.nextSibling.innerHTML = '<h6 style="margin:0px;">' + mxResources.get('heading') + ' 6</h6>';
139
140		addItem('', 'pre').firstChild.nextSibling.innerHTML = '<pre style="margin:0px;">' + mxResources.get('formatted') + '</pre>';
141		addItem('', 'blockquote').firstChild.nextSibling.innerHTML = '<blockquote style="margin-top:0px;margin-bottom:0px;">' + mxResources.get('blockquote') + '</blockquote>';
142	})));
143	this.put('fontSize', new Menu(mxUtils.bind(this, function(menu, parent)
144	{
145		var sizes = [6, 8, 9, 10, 11, 12, 14, 18, 24, 36, 48, 72];
146
147		if (mxUtils.indexOf(sizes, this.defaultFontSize) < 0)
148		{
149			sizes.push(this.defaultFontSize);
150			sizes.sort(function(a, b)
151			{
152				return a - b;
153			});
154		}
155
156		var setFontSize = mxUtils.bind(this, function(fontSize)
157		{
158			if (graph.cellEditor.textarea != null)
159			{
160				// Creates an element with arbitrary size 3
161				document.execCommand('fontSize', false, '3');
162
163				// Changes the css font size of the first font element inside the in-place editor with size 3
164				// hopefully the above element that we've just created. LATER: Check for new element using
165				// previous result of getElementsByTagName (see other actions)
166				var elts = graph.cellEditor.textarea.getElementsByTagName('font');
167
168				for (var i = 0; i < elts.length; i++)
169				{
170					if (elts[i].getAttribute('size') == '3')
171					{
172						elts[i].removeAttribute('size');
173						elts[i].style.fontSize = fontSize + 'px';
174
175						break;
176					}
177				}
178
179				ui.fireEvent(new mxEventObject('styleChanged',
180					'keys', [mxConstants.STYLE_FONTSIZE],
181					'values', [fontSize],
182					'cells', [graph.cellEditor.getEditingCell()]));
183			}
184		});
185
186		var addItem = mxUtils.bind(this, function(fontSize)
187		{
188			this.styleChange(menu, fontSize, [mxConstants.STYLE_FONTSIZE],
189				[fontSize], null, parent, function()
190			{
191				setFontSize(fontSize);
192			});
193		});
194
195		for (var i = 0; i < sizes.length; i++)
196		{
197			addItem(sizes[i]);
198		}
199
200		menu.addSeparator(parent);
201
202		if (this.customFontSizes.length > 0)
203		{
204			var counter = 0;
205
206			for (var i = 0; i < this.customFontSizes.length; i++)
207			{
208				if (mxUtils.indexOf(sizes, this.customFontSizes[i]) < 0)
209				{
210					addItem(this.customFontSizes[i]);
211					counter++;
212				}
213			}
214
215			if (counter > 0)
216			{
217				menu.addSeparator(parent);
218			}
219
220			menu.addItem(mxResources.get('reset'), null, mxUtils.bind(this, function()
221			{
222				this.customFontSizes = [];
223			}), parent);
224
225			menu.addSeparator(parent);
226		}
227
228		var selState = null;
229
230		this.promptChange(menu, mxResources.get('custom') + '...',
231			'(' + mxResources.get('points') + ')', this.defaultFontSize,
232			mxConstants.STYLE_FONTSIZE, parent, true,
233			mxUtils.bind(this, function(newValue)
234		{
235			if (selState != null && graph.cellEditor.textarea != null)
236			{
237				graph.cellEditor.textarea.focus();
238				graph.cellEditor.restoreSelection(selState);
239			}
240
241			if (newValue != null && newValue.length > 0)
242			{
243				this.customFontSizes.push(newValue);
244				setFontSize(newValue);
245			}
246		}), null, function()
247		{
248			selState = graph.cellEditor.saveSelection();
249
250			return false;
251		});
252	})));
253	this.put('direction', new Menu(mxUtils.bind(this, function(menu, parent)
254	{
255		menu.addItem(mxResources.get('flipH'), null, function() { graph.toggleCellStyles(mxConstants.STYLE_FLIPH, false); }, parent);
256		menu.addItem(mxResources.get('flipV'), null, function() { graph.toggleCellStyles(mxConstants.STYLE_FLIPV, false); }, parent);
257		this.addMenuItems(menu, ['-', 'rotation'], parent);
258	})));
259	this.put('align', new Menu(mxUtils.bind(this, function(menu, parent)
260	{
261		menu.addItem(mxResources.get('leftAlign'), null, function() { graph.alignCells(mxConstants.ALIGN_LEFT); }, parent);
262		menu.addItem(mxResources.get('center'), null, function() { graph.alignCells(mxConstants.ALIGN_CENTER); }, parent);
263		menu.addItem(mxResources.get('rightAlign'), null, function() { graph.alignCells(mxConstants.ALIGN_RIGHT); }, parent);
264		menu.addSeparator(parent);
265		menu.addItem(mxResources.get('topAlign'), null, function() { graph.alignCells(mxConstants.ALIGN_TOP); }, parent);
266		menu.addItem(mxResources.get('middle'), null, function() { graph.alignCells(mxConstants.ALIGN_MIDDLE); }, parent);
267		menu.addItem(mxResources.get('bottomAlign'), null, function() { graph.alignCells(mxConstants.ALIGN_BOTTOM); }, parent);
268	})));
269	this.put('distribute', new Menu(mxUtils.bind(this, function(menu, parent)
270	{
271		menu.addItem(mxResources.get('horizontal'), null, function() { graph.distributeCells(true); }, parent);
272		menu.addItem(mxResources.get('vertical'), null, function() { graph.distributeCells(false); }, parent);
273	})));
274	this.put('line', new Menu(mxUtils.bind(this, function(menu, parent)
275	{
276		var state = graph.view.getState(graph.getSelectionCell());
277
278		if (state != null)
279		{
280			var shape = mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE);
281
282			if (shape != 'arrow')
283			{
284				this.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE], [null, null, null], 'geIcon geSprite geSprite-straight', parent, true).setAttribute('title', mxResources.get('straight'));
285				this.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE], ['orthogonalEdgeStyle', null, null], 'geIcon geSprite geSprite-orthogonal', parent, true).setAttribute('title', mxResources.get('orthogonal'));
286				this.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_ELBOW, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE], ['elbowEdgeStyle', null, null, null], 'geIcon geSprite geSprite-horizontalelbow', parent, true).setAttribute('title', mxResources.get('simple'));
287				this.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_ELBOW, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE], ['elbowEdgeStyle', 'vertical', null, null], 'geIcon geSprite geSprite-verticalelbow', parent, true).setAttribute('title', mxResources.get('simple'));
288				this.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_ELBOW, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE], ['isometricEdgeStyle', null, null, null], 'geIcon geSprite geSprite-horizontalisometric', parent, true).setAttribute('title', mxResources.get('isometric'));
289				this.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_ELBOW, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE], ['isometricEdgeStyle', 'vertical', null, null], 'geIcon geSprite geSprite-verticalisometric', parent, true).setAttribute('title', mxResources.get('isometric'));
290
291				if (shape == 'connector')
292				{
293					this.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE], ['orthogonalEdgeStyle', '1', null], 'geIcon geSprite geSprite-curved', parent, true).setAttribute('title', mxResources.get('curved'));
294				}
295
296				this.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE], ['entityRelationEdgeStyle', null, null], 'geIcon geSprite geSprite-entity', parent, true).setAttribute('title', mxResources.get('entityRelation'));
297			}
298
299			menu.addSeparator(parent);
300
301			this.styleChange(menu, '', [mxConstants.STYLE_SHAPE, mxConstants.STYLE_STARTSIZE, mxConstants.STYLE_ENDSIZE, 'width'], [null, null, null, null], 'geIcon geSprite geSprite-connection', parent, true, null, true).setAttribute('title', mxResources.get('line'));
302			this.styleChange(menu, '', [mxConstants.STYLE_SHAPE, mxConstants.STYLE_STARTSIZE, mxConstants.STYLE_ENDSIZE, 'width'], ['link', null, null, null], 'geIcon geSprite geSprite-linkedge', parent, true, null, true).setAttribute('title', mxResources.get('link'));
303			this.styleChange(menu, '', [mxConstants.STYLE_SHAPE, mxConstants.STYLE_STARTSIZE, mxConstants.STYLE_ENDSIZE, 'width'], ['flexArrow', null, null, null], 'geIcon geSprite geSprite-arrow', parent, true, null, true).setAttribute('title', mxResources.get('arrow'));
304			this.styleChange(menu, '', [mxConstants.STYLE_SHAPE, mxConstants.STYLE_STARTSIZE, mxConstants.STYLE_ENDSIZE, 'width'], ['arrow', null, null, null], 'geIcon geSprite geSprite-simplearrow', parent, true, null, true).setAttribute('title', mxResources.get('simpleArrow'));
305		}
306	})));
307	this.put('layout', new Menu(mxUtils.bind(this, function(menu, parent)
308	{
309		var promptSpacing = mxUtils.bind(this, function(defaultValue, fn)
310		{
311			var dlg = new FilenameDialog(this.editorUi, defaultValue, mxResources.get('apply'), function(newValue)
312			{
313				fn(parseFloat(newValue));
314			}, mxResources.get('spacing'));
315			this.editorUi.showDialog(dlg.container, 300, 80, true, true);
316			dlg.init();
317		});
318
319		menu.addItem(mxResources.get('horizontalFlow'), null, mxUtils.bind(this, function()
320		{
321			var layout = new mxHierarchicalLayout(graph, mxConstants.DIRECTION_WEST);
322
323    		this.editorUi.executeLayout(function()
324    		{
325    			var selectionCells = graph.getSelectionCells();
326    			layout.execute(graph.getDefaultParent(), selectionCells.length == 0 ? null : selectionCells);
327    		}, true);
328		}), parent);
329		menu.addItem(mxResources.get('verticalFlow'), null, mxUtils.bind(this, function()
330		{
331			var layout = new mxHierarchicalLayout(graph, mxConstants.DIRECTION_NORTH);
332
333    		this.editorUi.executeLayout(function()
334    		{
335    			var selectionCells = graph.getSelectionCells();
336    			layout.execute(graph.getDefaultParent(), selectionCells.length == 0 ? null : selectionCells);
337    		}, true);
338		}), parent);
339		menu.addSeparator(parent);
340		menu.addItem(mxResources.get('horizontalTree'), null, mxUtils.bind(this, function()
341		{
342			var tmp = graph.getSelectionCell();
343			var roots = null;
344
345			if (tmp == null || graph.getModel().getChildCount(tmp) == 0)
346			{
347				if (graph.getModel().getEdgeCount(tmp) == 0)
348				{
349					roots = graph.findTreeRoots(graph.getDefaultParent());
350				}
351			}
352			else
353			{
354				roots = graph.findTreeRoots(tmp);
355			}
356
357			if (roots != null && roots.length > 0)
358			{
359				tmp = roots[0];
360			}
361
362			if (tmp != null)
363			{
364				var layout = new mxCompactTreeLayout(graph, true);
365				layout.edgeRouting = false;
366				layout.levelDistance = 30;
367
368				promptSpacing(layout.levelDistance, mxUtils.bind(this, function(newValue)
369				{
370					layout.levelDistance = newValue;
371
372					this.editorUi.executeLayout(function()
373		    		{
374						layout.execute(graph.getDefaultParent(), tmp);
375		    		}, true);
376				}));
377			}
378		}), parent);
379		menu.addItem(mxResources.get('verticalTree'), null, mxUtils.bind(this, function()
380		{
381			var tmp = graph.getSelectionCell();
382			var roots = null;
383
384			if (tmp == null || graph.getModel().getChildCount(tmp) == 0)
385			{
386				if (graph.getModel().getEdgeCount(tmp) == 0)
387				{
388					roots = graph.findTreeRoots(graph.getDefaultParent());
389				}
390			}
391			else
392			{
393				roots = graph.findTreeRoots(tmp);
394			}
395
396			if (roots != null && roots.length > 0)
397			{
398				tmp = roots[0];
399			}
400
401			if (tmp != null)
402			{
403				var layout = new mxCompactTreeLayout(graph, false);
404				layout.edgeRouting = false;
405				layout.levelDistance = 30;
406
407				promptSpacing(layout.levelDistance, mxUtils.bind(this, function(newValue)
408				{
409					layout.levelDistance = newValue;
410
411					this.editorUi.executeLayout(function()
412		    		{
413						layout.execute(graph.getDefaultParent(), tmp);
414		    		}, true);
415				}));
416			}
417		}), parent);
418		menu.addItem(mxResources.get('radialTree'), null, mxUtils.bind(this, function()
419		{
420			var tmp = graph.getSelectionCell();
421			var roots = null;
422
423			if (tmp == null || graph.getModel().getChildCount(tmp) == 0)
424			{
425				if (graph.getModel().getEdgeCount(tmp) == 0)
426				{
427					roots = graph.findTreeRoots(graph.getDefaultParent());
428				}
429			}
430			else
431			{
432				roots = graph.findTreeRoots(tmp);
433			}
434
435			if (roots != null && roots.length > 0)
436			{
437				tmp = roots[0];
438			}
439
440			if (tmp != null)
441			{
442				var layout = new mxRadialTreeLayout(graph, false);
443				layout.levelDistance = 80;
444				layout.autoRadius = true;
445
446				promptSpacing(layout.levelDistance, mxUtils.bind(this, function(newValue)
447				{
448					layout.levelDistance = newValue;
449
450					this.editorUi.executeLayout(function()
451		    		{
452		    			layout.execute(graph.getDefaultParent(), tmp);
453
454		    			if (!graph.isSelectionEmpty())
455		    			{
456			    			tmp = graph.getModel().getParent(tmp);
457
458			    			if (graph.getModel().isVertex(tmp))
459			    			{
460			    				graph.updateGroupBounds([tmp], graph.gridSize * 2, true);
461			    			}
462		    			}
463		    		}, true);
464				}));
465			}
466		}), parent);
467		menu.addSeparator(parent);
468		menu.addItem(mxResources.get('organic'), null, mxUtils.bind(this, function()
469		{
470			var layout = new mxFastOrganicLayout(graph);
471
472			promptSpacing(layout.forceConstant, mxUtils.bind(this, function(newValue)
473			{
474				layout.forceConstant = newValue;
475
476	    		this.editorUi.executeLayout(function()
477	    		{
478	    			var tmp = graph.getSelectionCell();
479
480	    			if (tmp == null || graph.getModel().getChildCount(tmp) == 0)
481	    			{
482	    				tmp = graph.getDefaultParent();
483	    			}
484
485	    			layout.execute(tmp);
486
487	    			if (graph.getModel().isVertex(tmp))
488	    			{
489	    				graph.updateGroupBounds([tmp], graph.gridSize * 2, true);
490	    			}
491	    		}, true);
492			}));
493		}), parent);
494		menu.addItem(mxResources.get('circle'), null, mxUtils.bind(this, function()
495		{
496			var layout = new mxCircleLayout(graph);
497
498    		this.editorUi.executeLayout(function()
499    		{
500    			var tmp = graph.getSelectionCell();
501
502    			if (tmp == null || graph.getModel().getChildCount(tmp) == 0)
503    			{
504    				tmp = graph.getDefaultParent();
505    			}
506
507    			layout.execute(tmp);
508
509    			if (graph.getModel().isVertex(tmp))
510    			{
511    				graph.updateGroupBounds([tmp], graph.gridSize * 2, true);
512    			}
513    		}, true);
514		}), parent);
515	})));
516	this.put('navigation', new Menu(mxUtils.bind(this, function(menu, parent)
517	{
518		this.addMenuItems(menu, ['home', '-', 'exitGroup', 'enterGroup', '-', 'expand', 'collapse', '-', 'collapsible'], parent);
519	})));
520	this.put('arrange', new Menu(mxUtils.bind(this, function(menu, parent)
521	{
522		this.addMenuItems(menu, ['toFront', 'toBack', 'bringForward', 'sendBackward', '-'], parent);
523		this.addSubmenu('direction', menu, parent);
524		this.addMenuItems(menu, ['turn', '-'], parent);
525		this.addSubmenu('align', menu, parent);
526		this.addSubmenu('distribute', menu, parent);
527		menu.addSeparator(parent);
528		this.addSubmenu('navigation', menu, parent);
529		this.addSubmenu('insert', menu, parent);
530		this.addSubmenu('layout', menu, parent);
531		this.addMenuItems(menu, ['-', 'group', 'ungroup', 'removeFromGroup', '-', 'clearWaypoints', 'autosize'], parent);
532	}))).isEnabled = isGraphEnabled;
533	this.put('insert', new Menu(mxUtils.bind(this, function(menu, parent)
534	{
535		this.addMenuItems(menu, ['insertLink', 'insertImage'], parent);
536	})));
537	this.put('view', new Menu(mxUtils.bind(this, function(menu, parent)
538	{
539		this.addMenuItems(menu, ((this.editorUi.format != null) ? ['formatPanel'] : []).
540			concat(['outline', 'layers', '-', 'pageView', 'pageScale', '-', 'scrollbars', 'tooltips', '-',
541			        'grid', 'guides', '-', 'connectionArrows', 'connectionPoints', '-',
542			        'resetView', 'zoomIn', 'zoomOut'], parent));
543	})));
544	// Two special dropdowns that are only used in the toolbar
545	this.put('viewPanels', new Menu(mxUtils.bind(this, function(menu, parent)
546	{
547		if (this.editorUi.format != null)
548		{
549			this.addMenuItems(menu, ['formatPanel'], parent);
550		}
551
552		this.addMenuItems(menu, ['outline', 'layers'], parent);
553	})));
554	this.put('viewZoom', new Menu(mxUtils.bind(this, function(menu, parent)
555	{
556		this.addMenuItems(menu, ['resetView', '-'], parent);
557		var scales = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 2, 3, 4];
558
559		for (var i = 0; i < scales.length; i++)
560		{
561			(function(scale)
562			{
563				menu.addItem((scale * 100) + '%', null, function()
564				{
565					graph.zoomTo(scale);
566				}, parent);
567			})(scales[i]);
568		}
569
570		this.addMenuItems(menu, ['-', 'fitWindow', 'fitPageWidth', 'fitPage', 'fitTwoPages', '-', 'customZoom'], parent);
571	})));
572	this.put('file', new Menu(mxUtils.bind(this, function(menu, parent)
573	{
574		this.addMenuItems(menu, ['new', 'open', '-', 'save', 'saveAs', '-', 'import', 'export', '-', 'pageSetup', 'print'], parent);
575	})));
576	this.put('edit', new Menu(mxUtils.bind(this, function(menu, parent)
577	{
578		this.addMenuItems(menu, ['undo', 'redo', '-', 'cut', 'copy', 'paste', 'delete', '-', 'duplicate', '-',
579			'editData', 'editTooltip', '-', 'editStyle', '-', 'edit', '-', 'editLink', 'openLink', '-',
580			'selectVertices', 'selectEdges', 'selectAll', 'selectNone', '-', 'lockUnlock']);
581	})));
582	this.put('extras', new Menu(mxUtils.bind(this, function(menu, parent)
583	{
584		this.addMenuItems(menu, ['copyConnect', 'collapseExpand', '-', 'editDiagram']);
585	})));
586	this.put('help', new Menu(mxUtils.bind(this, function(menu, parent)
587	{
588		this.addMenuItems(menu, ['help', '-', 'about']);
589	})));
590};
591
592/**
593 * Adds the label menu items to the given menu and parent.
594 */
595Menus.prototype.put = function(name, menu)
596{
597	this.menus[name] = menu;
598
599	return menu;
600};
601
602/**
603 * Adds the label menu items to the given menu and parent.
604 */
605Menus.prototype.get = function(name)
606{
607	return this.menus[name];
608};
609
610/**
611 * Adds the given submenu.
612 */
613Menus.prototype.addSubmenu = function(name, menu, parent, label)
614{
615	var entry = this.get(name);
616
617	if (entry != null)
618	{
619		var enabled = entry.isEnabled();
620
621		if (menu.showDisabled || enabled)
622		{
623			var submenu = menu.addItem(label || mxResources.get(name), null, null, parent, null, enabled);
624			this.addMenu(name, menu, submenu);
625		}
626	}
627};
628
629/**
630 * Adds the label menu items to the given menu and parent.
631 */
632Menus.prototype.addMenu = function(name, popupMenu, parent)
633{
634	var menu = this.get(name);
635
636	if (menu != null && (popupMenu.showDisabled || menu.isEnabled()))
637	{
638		this.get(name).execute(popupMenu, parent);
639	}
640};
641
642/**
643 * Adds a menu item to insert a table cell.
644 */
645Menus.prototype.addInsertTableCellItem = function(menu, parent)
646{
647	var graph = this.editorUi.editor.graph;
648	var cell = graph.getSelectionCell();
649	var style = graph.getCurrentCellStyle(cell);
650
651	var isTable = graph.isTable(cell) ||
652		graph.isTableRow(cell) ||
653		graph.isTableCell(cell);
654	var isStack = graph.isStack(cell) ||
655		graph.isStackChild(cell);
656
657	var showCols = isTable;
658	var showRows = isTable;
659
660	if (isStack)
661	{
662		var style = (graph.isStack(cell)) ? style :
663			graph.getCellStyle(graph.model.getParent(cell));
664
665		showRows = style['horizontalStack'] == '0';
666		showCols = !showRows;
667	}
668
669	if (parent != null || (!isTable && !isStack))
670	{
671		this.addInsertTableItem(menu, mxUtils.bind(this, function(evt, rows, cols, title, container)
672		{
673			var table = (container || mxEvent.isControlDown(evt) || mxEvent.isMetaDown(evt)) ?
674				graph.createCrossFunctionalSwimlane(rows, cols, null, null,
675					(title || mxEvent.isShiftDown(evt)) ? 'Cross-Functional Flowchart' : null) :
676				graph.createTable(rows, cols, null, null,
677					(title || mxEvent.isShiftDown(evt)) ? 'Table' : null);
678			var pt = (mxEvent.isAltDown(evt)) ? graph.getFreeInsertPoint() :
679				graph.getCenterInsertPoint(graph.getBoundingBoxFromGeometry([table], true));
680			var select = null;
681
682			graph.getModel().beginUpdate();
683			try
684			{
685				select = graph.importCells([table], pt.x, pt.y);
686				graph.fireEvent(new mxEventObject('cellsInserted', 'cells',
687					graph.model.getDescendants(select[0])));
688			}
689			finally
690			{
691				graph.getModel().endUpdate();
692			}
693
694			if (select != null && select.length > 0)
695			{
696				graph.scrollCellToVisible(select[0]);
697				graph.setSelectionCells(select);
698			}
699		}), parent);
700	}
701	else
702	{
703		if (showCols)
704		{
705			var elt = menu.addItem(mxResources.get('insertColumnBefore'), null, mxUtils.bind(this, function()
706			{
707				try
708				{
709					if (isStack)
710					{
711						graph.insertLane(cell, true);
712					}
713					else
714					{
715						graph.insertTableColumn(cell, true);
716					}
717				}
718				catch (e)
719				{
720					this.editorUi.handleError(e);
721				}
722			}), null, 'geIcon geSprite geSprite-insertcolumnbefore');
723			elt.setAttribute('title', mxResources.get('insertColumnBefore'));
724
725			elt = menu.addItem(mxResources.get('insertColumnAfter'), null, mxUtils.bind(this, function()
726			{
727				try
728				{
729					if (isStack)
730					{
731						graph.insertLane(cell, false);
732					}
733					else
734					{
735						graph.insertTableColumn(cell, false);
736					}
737				}
738				catch (e)
739				{
740					this.editorUi.handleError(e);
741				}
742			}), null, 'geIcon geSprite geSprite-insertcolumnafter');
743			elt.setAttribute('title', mxResources.get('insertColumnAfter'));
744
745			elt = menu.addItem(mxResources.get('deleteColumn'), null, mxUtils.bind(this, function()
746			{
747				if (cell != null)
748				{
749					try
750					{
751						if (isStack)
752						{
753							graph.deleteLane(cell);
754						}
755						else
756						{
757							graph.deleteTableColumn(cell);
758						}
759					}
760					catch (e)
761					{
762						this.editorUi.handleError(e);
763					}
764				}
765			}), null, 'geIcon geSprite geSprite-deletecolumn');
766			elt.setAttribute('title', mxResources.get('deleteColumn'));
767		}
768
769		if (showRows)
770		{
771			elt = menu.addItem(mxResources.get('insertRowBefore'), null, mxUtils.bind(this, function()
772			{
773				try
774				{
775					if (isStack)
776					{
777						graph.insertLane(cell, true);
778					}
779					else
780					{
781						graph.insertTableRow(cell, true);
782					}
783				}
784				catch (e)
785				{
786					this.editorUi.handleError(e);
787				}
788			}), null, 'geIcon geSprite geSprite-insertrowbefore');
789			elt.setAttribute('title', mxResources.get('insertRowBefore'));
790
791			elt = menu.addItem(mxResources.get('insertRowAfter'), null, mxUtils.bind(this, function()
792			{
793				try
794				{
795					if (isStack)
796					{
797						graph.insertLane(cell, false);
798					}
799					else
800					{
801						graph.insertTableRow(cell, false);
802					}
803				}
804				catch (e)
805				{
806					this.editorUi.handleError(e);
807				}
808			}), null, 'geIcon geSprite geSprite-insertrowafter');
809			elt.setAttribute('title', mxResources.get('insertRowAfter'));
810
811			elt = menu.addItem(mxResources.get('deleteRow'), null, mxUtils.bind(this, function()
812			{
813				try
814				{
815					if (isStack)
816					{
817						graph.deleteLane(cell);
818					}
819					else
820					{
821						graph.deleteTableRow(cell);
822					}
823				}
824				catch (e)
825				{
826					this.editorUi.handleError(e);
827				}
828			}), null, 'geIcon geSprite geSprite-deleterow');
829			elt.setAttribute('title', mxResources.get('deleteRow'));
830		}
831	}
832};
833
834/**
835 * Adds a menu item to insert a table.
836 */
837Menus.prototype.addInsertTableItem = function(menu, insertFn, parent, showOptions)
838{
839	showOptions = (showOptions != null) ? showOptions : true;
840
841	insertFn = (insertFn != null) ? insertFn : mxUtils.bind(this, function(evt, rows, cols)
842	{
843		var graph = this.editorUi.editor.graph;
844		var td = graph.getParentByName(mxEvent.getSource(evt), 'TD');
845
846		if (td != null && graph.cellEditor.textarea != null)
847		{
848			var row2 = graph.getParentByName(td, 'TR');
849
850			// To find the new link, we create a list of all existing links first
851    		// LATER: Refactor for reuse with code for finding inserted image below
852			var tmp = graph.cellEditor.textarea.getElementsByTagName('table');
853			var oldTables = [];
854
855			for (var i = 0; i < tmp.length; i++)
856			{
857				oldTables.push(tmp[i]);
858			}
859
860			// Finding the new table will work with insertHTML, but IE does not support that
861			graph.container.focus();
862			graph.pasteHtmlAtCaret(createTable(rows, cols));
863
864			// Moves cursor to first table cell
865			var newTables = graph.cellEditor.textarea.getElementsByTagName('table');
866
867			if (newTables.length == oldTables.length + 1)
868			{
869				// Inverse order in favor of appended tables
870				for (var i = newTables.length - 1; i >= 0; i--)
871				{
872					if (i == 0 || newTables[i] != oldTables[i - 1])
873					{
874						graph.selectNode(newTables[i].rows[0].cells[0]);
875						break;
876					}
877				}
878			}
879		}
880	});
881
882	// KNOWN: Does not work in IE8 standards and quirks
883	var graph = this.editorUi.editor.graph;
884	var row2 = null;
885	var td = null;
886
887	function createTable(rows, cols)
888	{
889		var html = ['<table>'];
890
891		for (var i = 0; i < rows; i++)
892		{
893			html.push('<tr>');
894
895			for (var j = 0; j < cols; j++)
896			{
897				html.push('<td><br></td>');
898			}
899
900			html.push('</tr>');
901		}
902
903		html.push('</table>');
904
905		return html.join('');
906	};
907
908	if (parent == null)
909	{
910		menu.div.className += ' geToolbarMenu';
911		menu.labels = false;
912	}
913
914	var elt2 = menu.addItem('', null, null, parent, null, null, null, true);
915	elt2.firstChild.style.fontSize = Menus.prototype.defaultFontSize + 'px';
916
917	function createPicker(rows, cols)
918	{
919		var table2 = document.createElement('table');
920		table2.setAttribute('border', '1');
921		table2.style.borderCollapse = 'collapse';
922		table2.style.borderStyle = 'solid';
923		table2.setAttribute('cellPadding', '8');
924
925		for (var i = 0; i < rows; i++)
926		{
927			var row = table2.insertRow(i);
928
929			for (var j = 0; j < cols; j++)
930			{
931				var cell = row.insertCell(-1);
932			}
933		}
934
935		return table2;
936	};
937
938	function extendPicker(picker, rows, cols)
939	{
940		for (var i = picker.rows.length; i < rows; i++)
941		{
942			var row = picker.insertRow(i);
943
944			for (var j = 0; j < picker.rows[0].cells.length; j++)
945			{
946				var cell = row.insertCell(-1);
947			}
948		}
949
950		for (var i = 0; i < picker.rows.length; i++)
951		{
952			var row = picker.rows[i];
953
954			for (var j = row.cells.length; j < cols; j++)
955			{
956				var cell = row.insertCell(-1);
957			}
958		}
959	};
960
961	elt2.firstChild.innerHTML = '';
962
963	var titleOption = document.createElement('input');
964	titleOption.setAttribute('id', 'geTitleOption');
965	titleOption.setAttribute('type', 'checkbox');
966
967	var titleLbl = document.createElement('label');
968	mxUtils.write(titleLbl, mxResources.get('title'));
969	titleLbl.setAttribute('for', 'geTitleOption');
970
971	mxEvent.addGestureListeners(titleLbl, null, null, mxUtils.bind(this, function(e)
972	{
973		mxEvent.consume(e);
974	}));
975
976	mxEvent.addGestureListeners(titleOption, null, null, mxUtils.bind(this, function(e)
977	{
978		mxEvent.consume(e);
979	}));
980
981	var containerOption = document.createElement('input');
982	containerOption.setAttribute('id', 'geContainerOption');
983	containerOption.setAttribute('type', 'checkbox');
984
985	var containerLbl = document.createElement('label');
986	mxUtils.write(containerLbl, mxResources.get('container'));
987	containerLbl.setAttribute('for', 'geContainerOption');
988
989	mxEvent.addGestureListeners(containerLbl, null, null, mxUtils.bind(this, function(e)
990	{
991		mxEvent.consume(e);
992	}));
993
994	mxEvent.addGestureListeners(containerOption, null, null, mxUtils.bind(this, function(e)
995	{
996		mxEvent.consume(e);
997	}));
998
999	if (showOptions)
1000	{
1001		elt2.firstChild.appendChild(titleOption);
1002		elt2.firstChild.appendChild(titleLbl);
1003		mxUtils.br(elt2.firstChild);
1004		elt2.firstChild.appendChild(containerOption);
1005		elt2.firstChild.appendChild(containerLbl);
1006		mxUtils.br(elt2.firstChild);
1007		mxUtils.br(elt2.firstChild);
1008	}
1009
1010	var picker = createPicker(5, 5);
1011	elt2.firstChild.appendChild(picker);
1012
1013	var label = document.createElement('div');
1014	label.style.padding = '4px';
1015	label.innerHTML = '1x1';
1016	elt2.firstChild.appendChild(label);
1017
1018	function mouseover(e)
1019	{
1020		td = graph.getParentByName(mxEvent.getSource(e), 'TD');
1021		var selected = false;
1022
1023		if (td != null)
1024		{
1025			row2 = graph.getParentByName(td, 'TR');
1026			var ext = (mxEvent.isMouseEvent(e)) ? 2 : 4;
1027			extendPicker(picker, Math.min(20, row2.sectionRowIndex + ext), Math.min(20, td.cellIndex + ext));
1028			label.innerHTML = (td.cellIndex + 1) + 'x' + (row2.sectionRowIndex + 1);
1029
1030			for (var i = 0; i < picker.rows.length; i++)
1031			{
1032				var r = picker.rows[i];
1033
1034				for (var j = 0; j < r.cells.length; j++)
1035				{
1036					var cell = r.cells[j];
1037
1038					if (i == row2.sectionRowIndex &&
1039						j == td.cellIndex)
1040					{
1041						selected = cell.style.backgroundColor == 'blue';
1042					}
1043
1044					if (i <= row2.sectionRowIndex && j <= td.cellIndex)
1045					{
1046						cell.style.backgroundColor = 'blue';
1047					}
1048					else
1049					{
1050						cell.style.backgroundColor = 'transparent';
1051					}
1052				}
1053			}
1054		}
1055
1056		mxEvent.consume(e);
1057
1058		return selected;
1059	};
1060
1061	mxEvent.addGestureListeners(picker, null, null, mxUtils.bind(this, function(e)
1062	{
1063		var selected = mouseover(e);
1064
1065		if (td != null && row2 != null && selected)
1066		{
1067			insertFn(e, row2.sectionRowIndex + 1, td.cellIndex + 1,
1068				titleOption.checked, containerOption.checked);
1069
1070			// Async required to block event for elements under menu
1071			window.setTimeout(mxUtils.bind(this, function()
1072			{
1073				this.editorUi.hideCurrentMenu();
1074			}), 0);
1075		}
1076	}));
1077
1078	mxEvent.addListener(picker, 'mouseover', mouseover);
1079};
1080
1081/**
1082 * Adds a style change item to the given menu.
1083 */
1084Menus.prototype.edgeStyleChange = function(menu, label, keys, values, sprite, parent, reset, image)
1085{
1086	return this.showIconOnly(menu.addItem(label, image, mxUtils.bind(this, function()
1087	{
1088		var graph = this.editorUi.editor.graph;
1089		graph.stopEditing(false);
1090
1091		graph.getModel().beginUpdate();
1092		try
1093		{
1094			var cells = graph.getSelectionCells();
1095			var edges = [];
1096
1097			for (var i = 0; i < cells.length; i++)
1098			{
1099				var cell = cells[i];
1100
1101				if (graph.getModel().isEdge(cell))
1102				{
1103					if (reset)
1104					{
1105						var geo = graph.getCellGeometry(cell);
1106
1107						// Resets all edge points
1108						if (geo != null)
1109						{
1110							geo = geo.clone();
1111							geo.points = null;
1112							graph.getModel().setGeometry(cell, geo);
1113						}
1114					}
1115
1116					for (var j = 0; j < keys.length; j++)
1117					{
1118						graph.setCellStyles(keys[j], values[j], [cell]);
1119					}
1120
1121					edges.push(cell);
1122				}
1123			}
1124
1125			this.editorUi.fireEvent(new mxEventObject(
1126				'styleChanged', 'keys', keys,
1127				'values', values, 'cells', edges));
1128		}
1129		finally
1130		{
1131			graph.getModel().endUpdate();
1132		}
1133	}), parent, sprite));
1134};
1135
1136/**
1137 * Adds a style change item to the given menu.
1138 */
1139Menus.prototype.showIconOnly = function(elt)
1140{
1141	var td = elt.getElementsByTagName('td');
1142
1143	for (i = 0; i < td.length; i++)
1144	{
1145		if (td[i].getAttribute('class') == 'mxPopupMenuItem')
1146		{
1147			td[i].style.display = 'none';
1148		}
1149	}
1150
1151	return elt;
1152};
1153
1154/**
1155 * Adds a style change item to the given menu.
1156 */
1157Menus.prototype.styleChange = function(menu, label, keys, values, sprite, parent, fn, post, iconOnly)
1158{
1159	var apply = this.createStyleChangeFunction(keys, values);
1160
1161	var elt = menu.addItem(label, null, mxUtils.bind(this, function()
1162	{
1163		var graph = this.editorUi.editor.graph;
1164
1165		if (fn != null && graph.cellEditor.isContentEditing())
1166		{
1167			fn();
1168		}
1169		else
1170		{
1171			apply(post);
1172		}
1173	}), parent, sprite);
1174
1175	if (iconOnly)
1176	{
1177		this.showIconOnly(elt);
1178	}
1179
1180	return elt;
1181};
1182
1183/**
1184 *
1185 */
1186Menus.prototype.createStyleChangeFunction = function(keys, values)
1187{
1188	return mxUtils.bind(this, function(post)
1189	{
1190		var graph = this.editorUi.editor.graph;
1191		graph.stopEditing(false);
1192
1193		graph.getModel().beginUpdate();
1194		try
1195		{
1196			var cells = graph.getEditableCells(graph.getSelectionCells());
1197			var autoSizeCells = false;
1198
1199			for (var i = 0; i < keys.length; i++)
1200			{
1201				graph.setCellStyles(keys[i], values[i], cells);
1202
1203				// Removes CSS alignment to produce consistent output
1204				if (keys[i] == mxConstants.STYLE_ALIGN)
1205				{
1206					graph.updateLabelElements(cells, function(elt)
1207					{
1208						elt.removeAttribute('align');
1209						elt.style.textAlign = null;
1210					});
1211				}
1212
1213				// Updates autosize after font changes
1214				if (keys[i] == mxConstants.STYLE_FONTFAMILY ||
1215					keys[i] == 'fontSource')
1216				{
1217					autoSizeCells = true;
1218				}
1219			}
1220
1221			if (autoSizeCells)
1222			{
1223				for (var j = 0; j < cells.length; j++)
1224				{
1225					if (graph.model.getChildCount(cells[j]) == 0)
1226					{
1227						graph.autoSizeCell(cells[j], false);
1228					}
1229				}
1230			}
1231
1232			if (post != null)
1233			{
1234				post();
1235			}
1236
1237			this.editorUi.fireEvent(new mxEventObject('styleChanged',
1238				'keys', keys, 'values', values, 'cells', cells));
1239		}
1240		finally
1241		{
1242			graph.getModel().endUpdate();
1243		}
1244	});
1245};
1246
1247/**
1248 * Adds a style change item with a prompt to the given menu.
1249 */
1250Menus.prototype.promptChange = function(menu, label, hint, defaultValue, key, parent, enabled, fn, sprite, beforeFn)
1251{
1252	return menu.addItem(label, null, mxUtils.bind(this, function()
1253	{
1254		var graph = this.editorUi.editor.graph;
1255		var value = defaultValue;
1256    	var state = graph.getView().getState(graph.getSelectionCell());
1257
1258    	if (state != null)
1259    	{
1260    		value = state.style[key] || value;
1261    	}
1262
1263		var doStopEditing = (beforeFn != null) ? beforeFn() : true;
1264
1265		var dlg = new FilenameDialog(this.editorUi, value, mxResources.get('apply'), mxUtils.bind(this, function(newValue)
1266		{
1267			if (newValue != null && newValue.length > 0)
1268			{
1269				if (doStopEditing)
1270				{
1271					graph.getModel().beginUpdate();
1272					try
1273					{
1274						graph.stopEditing(false);
1275						graph.setCellStyles(key, newValue);
1276					}
1277					finally
1278					{
1279						graph.getModel().endUpdate();
1280					}
1281				}
1282
1283				if (fn != null)
1284				{
1285					fn(newValue);
1286				}
1287			}
1288		}), mxResources.get('enterValue') + ((hint.length > 0) ? (' ' + hint) : ''),
1289			null, null, null, null, function()
1290		{
1291			if (fn != null && beforeFn != null)
1292			{
1293				fn(null);
1294			}
1295		});
1296		this.editorUi.showDialog(dlg.container, 300, 80, true, true);
1297		dlg.init();
1298	}), parent, sprite, enabled);
1299};
1300
1301/**
1302 * Adds a handler for showing a menu in the given element.
1303 */
1304Menus.prototype.pickColor = function(key, cmd, defaultValue)
1305{
1306	var ui = this.editorUi;
1307	var graph = ui.editor.graph;
1308	var h = 226 + ((Math.ceil(ColorDialog.prototype.presetColors.length / 12) +
1309			Math.ceil(ColorDialog.prototype.defaultColors.length / 12)) * 17);
1310
1311	if (cmd != null && graph.cellEditor.isContentEditing())
1312	{
1313		// Saves and restores text selection for in-place editor
1314		var selState = graph.cellEditor.saveSelection();
1315
1316		var dlg = new ColorDialog(this.editorUi, defaultValue || '000000', mxUtils.bind(this, function(color)
1317		{
1318			graph.cellEditor.restoreSelection(selState);
1319			document.execCommand(cmd, false, (color != mxConstants.NONE) ? color : 'transparent');
1320
1321			var cmdMapping = {
1322				'forecolor': mxConstants.STYLE_FONTCOLOR,
1323				'backcolor': mxConstants.STYLE_LABEL_BACKGROUNDCOLOR
1324			};
1325
1326			var style = cmdMapping[cmd];
1327
1328			if (style != null)
1329			{
1330				ui.fireEvent(new mxEventObject('styleChanged',
1331					'keys', [style], 'values', [color],
1332					'cells', [graph.cellEditor.getEditingCell()]));
1333			}
1334		}), function()
1335		{
1336			graph.cellEditor.restoreSelection(selState);
1337		});
1338		this.editorUi.showDialog(dlg.container, 230, h, true, true);
1339		dlg.init();
1340	}
1341	else
1342	{
1343		if (this.colorDialog == null)
1344		{
1345			this.colorDialog = new ColorDialog(this.editorUi);
1346		}
1347
1348		this.colorDialog.currentColorKey = key;
1349		var state = graph.getView().getState(graph.getSelectionCell());
1350		var color = 'none';
1351
1352		if (state != null)
1353		{
1354			color = state.style[key] || color;
1355		}
1356
1357		if (color == 'none')
1358		{
1359			color = 'ffffff';
1360			this.colorDialog.picker.fromString('ffffff');
1361			this.colorDialog.colorInput.value = 'none';
1362		}
1363		else
1364		{
1365			this.colorDialog.picker.fromString(color);
1366		}
1367
1368		this.editorUi.showDialog(this.colorDialog.container, 230, h, true, true);
1369		this.colorDialog.init();
1370	}
1371};
1372
1373/**
1374 * Adds a handler for showing a menu in the given element.
1375 */
1376Menus.prototype.toggleStyle = function(key, defaultValue)
1377{
1378	var graph = this.editorUi.editor.graph;
1379	var value = graph.toggleCellStyles(key, defaultValue);
1380	this.editorUi.fireEvent(new mxEventObject('styleChanged', 'keys', [key], 'values', [value],
1381			'cells', graph.getSelectionCells()));
1382};
1383
1384/**
1385 * Creates the keyboard event handler for the current graph and history.
1386 */
1387Menus.prototype.addMenuItem = function(menu, key, parent, trigger, sprite, label)
1388{
1389	var action = this.editorUi.actions.get(key);
1390
1391	if (action != null && (menu.showDisabled || action.isEnabled()) && action.visible)
1392	{
1393		var item = menu.addItem(label || action.label, null, function(evt)
1394		{
1395			action.funct(trigger, evt);
1396		}, parent, sprite, action.isEnabled());
1397
1398		// Adds checkmark image
1399		if (action.toggleAction && action.isSelected())
1400		{
1401			menu.addCheckmark(item, Editor.checkmarkImage);
1402		}
1403
1404		this.addShortcut(item, action);
1405
1406		return item;
1407	}
1408
1409	return null;
1410};
1411
1412/**
1413 * Adds a checkmark to the given menuitem.
1414 */
1415Menus.prototype.addShortcut = function(item, action)
1416{
1417	if (action.shortcut != null)
1418	{
1419		var td = item.firstChild.nextSibling.nextSibling;
1420		var span = document.createElement('span');
1421		span.style.color = 'gray';
1422		mxUtils.write(span, action.shortcut);
1423		td.appendChild(span);
1424	}
1425};
1426
1427/**
1428 * Creates the keyboard event handler for the current graph and history.
1429 */
1430Menus.prototype.addMenuItems = function(menu, keys, parent, trigger, sprites)
1431{
1432	for (var i = 0; i < keys.length; i++)
1433	{
1434		if (keys[i] == '-')
1435		{
1436			menu.addSeparator(parent);
1437		}
1438		else
1439		{
1440			this.addMenuItem(menu, keys[i], parent, trigger, (sprites != null) ? sprites[i] : null);
1441		}
1442	}
1443};
1444
1445/**
1446 * Creates the keyboard event handler for the current graph and history.
1447 */
1448Menus.prototype.createPopupMenu = function(menu, cell, evt)
1449{
1450	menu.smartSeparators = true;
1451
1452	this.addPopupMenuHistoryItems(menu, cell, evt);
1453	this.addPopupMenuEditItems(menu, cell, evt);
1454	this.addPopupMenuStyleItems(menu, cell, evt);
1455	this.addPopupMenuArrangeItems(menu, cell, evt);
1456	this.addPopupMenuCellItems(menu, cell, evt);
1457	this.addPopupMenuSelectionItems(menu, cell, evt);
1458};
1459
1460/**
1461 * Creates the keyboard event handler for the current graph and history.
1462 */
1463Menus.prototype.addPopupMenuHistoryItems = function(menu, cell, evt)
1464{
1465	if (this.editorUi.editor.graph.isSelectionEmpty())
1466	{
1467		this.addMenuItems(menu, ['undo', 'redo'], null, evt);
1468	}
1469};
1470
1471/**
1472 * Creates the keyboard event handler for the current graph and history.
1473 */
1474Menus.prototype.addPopupMenuEditItems = function(menu, cell, evt)
1475{
1476	if (this.editorUi.editor.graph.isSelectionEmpty())
1477	{
1478		this.addMenuItems(menu, ['pasteHere'], null, evt);
1479	}
1480	else
1481	{
1482		this.addMenuItems(menu, ['delete', '-', 'cut', 'copy', '-', 'duplicate'], null, evt);
1483	}
1484};
1485
1486/**
1487 * Creates the keyboard event handler for the current graph and history.
1488 */
1489Menus.prototype.addPopupMenuStyleItems = function(menu, cell, evt)
1490{
1491	if (this.editorUi.editor.graph.getSelectionCount() == 1)
1492	{
1493		this.addMenuItems(menu, ['-', 'setAsDefaultStyle'], null, evt);
1494	}
1495	else if (this.editorUi.editor.graph.isSelectionEmpty())
1496	{
1497		this.addMenuItems(menu, ['-', 'clearDefaultStyle'], null, evt);
1498	}
1499};
1500
1501/**
1502 * Creates the keyboard event handler for the current graph and history.
1503 */
1504Menus.prototype.addPopupMenuArrangeItems = function(menu, cell, evt)
1505{
1506	var graph = this.editorUi.editor.graph;
1507
1508	if (graph.getEditableCells(graph.getSelectionCells()).length > 0)
1509	{
1510		this.addMenuItems(menu, ['-', 'toFront', 'toBack'], null, evt);
1511
1512		if (graph.getSelectionCount() == 1)
1513		{
1514			this.addMenuItems(menu, ['bringForward', 'sendBackward'], null, evt);
1515		}
1516	}
1517
1518	if (graph.getSelectionCount() > 1)
1519	{
1520		this.addMenuItems(menu, ['-', 'group'], null, evt);
1521	}
1522	else if (graph.getSelectionCount() == 1 && !graph.getModel().isEdge(cell) &&
1523		!graph.isSwimlane(cell) && graph.getModel().getChildCount(cell) > 0 &&
1524		graph.isCellEditable(cell))
1525	{
1526		this.addMenuItems(menu, ['-', 'ungroup'], null, evt);
1527	}
1528};
1529
1530/**
1531 * Creates the keyboard event handler for the current graph and history.
1532 */
1533Menus.prototype.addPopupMenuCellItems = function(menu, cell, evt)
1534{
1535	var graph = this.editorUi.editor.graph;
1536	var state = graph.view.getState(cell);
1537	menu.addSeparator();
1538
1539	if (state != null)
1540	{
1541		var hasWaypoints = false;
1542
1543		if (graph.getSelectionCount() == 1 && graph.getModel().isEdge(cell))
1544		{
1545			menu.addSeparator();
1546			this.addSubmenu('line', menu);
1547		}
1548
1549		if (graph.getModel().isEdge(cell) && mxUtils.getValue(state.style, mxConstants.STYLE_EDGE, null) != 'entityRelationEdgeStyle' &&
1550			mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null) != 'arrow')
1551		{
1552			var handler = graph.selectionCellsHandler.getHandler(cell);
1553			var isWaypoint = false;
1554
1555			if (handler instanceof mxEdgeHandler && handler.bends != null && handler.bends.length > 2)
1556			{
1557				var index = handler.getHandleForEvent(graph.updateMouseEvent(new mxMouseEvent(evt)));
1558
1559				// Ignores ghosted and virtual waypoints
1560				if (index > 0 && index < handler.bends.length - 1 &&
1561					(handler.bends[index] == null ||
1562					handler.bends[index].node == null ||
1563					handler.bends[index].node.style.opacity == ''))
1564				{
1565					// Configures removeWaypoint action before execution
1566					// Using trigger parameter is cleaner but have to find waypoint here anyway.
1567					var rmWaypointAction = this.editorUi.actions.get('removeWaypoint');
1568					rmWaypointAction.handler = handler;
1569					rmWaypointAction.index = index;
1570
1571					isWaypoint = true;
1572				}
1573			}
1574
1575			menu.addSeparator();
1576			this.addMenuItem(menu, 'turn', null, evt, null, mxResources.get('reverse'));
1577			this.addMenuItems(menu, [(isWaypoint) ? 'removeWaypoint' : 'addWaypoint'], null, evt);
1578
1579			// Adds reset waypoints option if waypoints exist
1580			var geo = graph.getModel().getGeometry(cell);
1581			hasWaypoints = geo != null && geo.points != null && geo.points.length > 0;
1582		}
1583
1584		if (graph.getSelectionCount() == 1 && (hasWaypoints || (graph.getModel().isVertex(cell) &&
1585			graph.getModel().getEdgeCount(cell) > 0)))
1586		{
1587			this.addMenuItems(menu, ['-', 'clearWaypoints'], null, evt);
1588		}
1589
1590		if (graph.getSelectionCount() == 1 && graph.isCellEditable(cell))
1591		{
1592			this.addPopupMenuCellEditItems(menu, cell, evt);
1593		}
1594	}
1595};
1596
1597/**
1598 * Creates the keyboard event handler for the current graph and history.
1599 */
1600Menus.prototype.addPopupMenuCellEditItems = function(menu, cell, evt, parent)
1601{
1602	var graph = this.editorUi.editor.graph;
1603	var state = graph.view.getState(cell);
1604	this.addMenuItems(menu, ['-', 'editStyle', 'editData', 'editLink'], parent, evt);
1605
1606	// Shows edit image action if there is an image in the style
1607	if (this.editorUi.editor.graph.getModel().isVertex(cell) && mxUtils.getValue(state.style, mxConstants.STYLE_IMAGE, null) != null)
1608	{
1609		menu.addSeparator();
1610		this.addMenuItem(menu, 'image', parent, evt).firstChild.nextSibling.innerHTML = mxResources.get('editImage') + '...';
1611	}
1612};
1613
1614/**
1615 * Creates the keyboard event handler for the current graph and history.
1616 */
1617Menus.prototype.addPopupMenuSelectionItems = function(menu, cell, evt)
1618{
1619	if (this.editorUi.editor.graph.isSelectionEmpty())
1620	{
1621		this.addMenuItems(menu, ['-', 'selectVertices', 'selectEdges', 'selectAll'], null, evt);
1622	}
1623};
1624
1625/**
1626 * Creates the keyboard event handler for the current graph and history.
1627 */
1628Menus.prototype.createMenubar = function(container)
1629{
1630	var menubar = new Menubar(this.editorUi, container);
1631	var menus = this.defaultMenuItems;
1632
1633	for (var i = 0; i < menus.length; i++)
1634	{
1635		(mxUtils.bind(this, function(menu)
1636		{
1637			var elt = menubar.addMenu(mxResources.get(menus[i]), mxUtils.bind(this, function()
1638			{
1639				// Allows extensions of menu.funct
1640				menu.funct.apply(this, arguments);
1641			}));
1642
1643			this.menuCreated(menu, elt);
1644		}))(this.get(menus[i]));
1645	}
1646
1647	return menubar;
1648};
1649
1650/**
1651 * Creates the keyboard event handler for the current graph and history.
1652 */
1653Menus.prototype.menuCreated = function(menu, elt, className)
1654{
1655	if (elt != null)
1656	{
1657		className = (className != null) ? className : 'geItem';
1658
1659		menu.addListener('stateChanged', function()
1660		{
1661			elt.enabled = menu.enabled;
1662
1663			if (!menu.enabled)
1664			{
1665				elt.className = className + ' mxDisabled';
1666
1667				if (document.documentMode == 8)
1668				{
1669					elt.style.color = '#c3c3c3';
1670				}
1671			}
1672			else
1673			{
1674				elt.className = className;
1675
1676				if (document.documentMode == 8)
1677				{
1678					elt.style.color = '';
1679				}
1680			}
1681		});
1682	}
1683};
1684
1685/**
1686 * Construcs a new menubar for the given editor.
1687 */
1688function Menubar(editorUi, container)
1689{
1690	this.editorUi = editorUi;
1691	this.container = container;
1692};
1693
1694/**
1695 * Adds the menubar elements.
1696 */
1697Menubar.prototype.hideMenu = function()
1698{
1699	this.editorUi.hideCurrentMenu();
1700};
1701
1702/**
1703 * Adds a submenu to this menubar.
1704 */
1705Menubar.prototype.addMenu = function(label, funct, before)
1706{
1707	var elt = document.createElement('a');
1708	elt.className = 'geItem';
1709	mxUtils.write(elt, label);
1710	this.addMenuHandler(elt, funct);
1711
1712    if (before != null)
1713    {
1714    	this.container.insertBefore(elt, before);
1715    }
1716    else
1717    {
1718    	this.container.appendChild(elt);
1719    }
1720
1721	return elt;
1722};
1723
1724/**
1725 * Adds a handler for showing a menu in the given element.
1726 */
1727Menubar.prototype.addMenuHandler = function(elt, funct)
1728{
1729	if (funct != null)
1730	{
1731		var show = true;
1732
1733		var clickHandler = mxUtils.bind(this, function(evt)
1734		{
1735			if (show && elt.enabled == null || elt.enabled)
1736			{
1737				this.editorUi.editor.graph.popupMenuHandler.hideMenu();
1738				var menu = new mxPopupMenu(funct);
1739				menu.div.className += ' geMenubarMenu';
1740				menu.smartSeparators = true;
1741				menu.showDisabled = true;
1742				menu.autoExpand = true;
1743
1744				// Disables autoexpand and destroys menu when hidden
1745				menu.hideMenu = mxUtils.bind(this, function()
1746				{
1747					mxPopupMenu.prototype.hideMenu.apply(menu, arguments);
1748					this.editorUi.resetCurrentMenu();
1749					menu.destroy();
1750				});
1751
1752				var offset = mxUtils.getOffset(elt);
1753				menu.popup(offset.x, offset.y + elt.offsetHeight, null, evt);
1754				this.editorUi.setCurrentMenu(menu, elt);
1755			}
1756
1757			mxEvent.consume(evt);
1758		});
1759
1760		// Shows menu automatically while in expanded state
1761		mxEvent.addListener(elt, 'mousemove', mxUtils.bind(this, function(evt)
1762		{
1763			if (this.editorUi.currentMenu != null && this.editorUi.currentMenuElt != elt)
1764			{
1765				this.editorUi.hideCurrentMenu();
1766				clickHandler(evt);
1767			}
1768		}));
1769
1770		// Hides menu if already showing and prevents focus
1771        mxEvent.addListener(elt, (mxClient.IS_POINTER) ? 'pointerdown' : 'mousedown',
1772        	mxUtils.bind(this, function(evt)
1773		{
1774			show = this.currentElt != elt;
1775			evt.preventDefault();
1776		}));
1777
1778		mxEvent.addListener(elt, 'click', mxUtils.bind(this, function(evt)
1779		{
1780			clickHandler(evt);
1781			show = true;
1782		}));
1783	}
1784};
1785
1786/**
1787 * Creates the keyboard event handler for the current graph and history.
1788 */
1789Menubar.prototype.destroy = function()
1790{
1791	// do nothing
1792};
1793
1794/**
1795 * Constructs a new action for the given parameters.
1796 */
1797function Menu(funct, enabled)
1798{
1799	mxEventSource.call(this);
1800	this.funct = funct;
1801	this.enabled = (enabled != null) ? enabled : true;
1802};
1803
1804// Menu inherits from mxEventSource
1805mxUtils.extend(Menu, mxEventSource);
1806
1807/**
1808 * Sets the enabled state of the action and fires a stateChanged event.
1809 */
1810Menu.prototype.isEnabled = function()
1811{
1812	return this.enabled;
1813};
1814
1815/**
1816 * Sets the enabled state of the action and fires a stateChanged event.
1817 */
1818Menu.prototype.setEnabled = function(value)
1819{
1820	if (this.enabled != value)
1821	{
1822		this.enabled = value;
1823		this.fireEvent(new mxEventObject('stateChanged'));
1824	}
1825};
1826
1827/**
1828 * Sets the enabled state of the action and fires a stateChanged event.
1829 */
1830Menu.prototype.execute = function(menu, parent)
1831{
1832	this.funct(menu, parent);
1833};
1834
1835/**
1836 * "Installs" menus in EditorUi.
1837 */
1838EditorUi.prototype.createMenus = function()
1839{
1840	return new Menus(this);
1841};
1842