1/**
2 * Copyright (c) 2006-2020, JGraph Ltd
3 * Copyright (c) 2006-2020, draw.io AG
4 *
5 * Constructs the actions object for the given UI.
6 */
7function Actions(editorUi)
8{
9	this.editorUi = editorUi;
10	this.actions = new Object();
11	this.init();
12};
13
14/**
15 * Adds the default actions.
16 */
17Actions.prototype.init = function()
18{
19	var ui = this.editorUi;
20	var editor = ui.editor;
21	var graph = editor.graph;
22	var isGraphEnabled = function()
23	{
24		return Action.prototype.isEnabled.apply(this, arguments) && graph.isEnabled();
25	};
26
27	// File actions
28	this.addAction('new...', function() { graph.openLink(ui.getUrl()); });
29	this.addAction('open...', function()
30	{
31		window.openNew = true;
32		window.openKey = 'open';
33
34		ui.openFile();
35	});
36	this.addAction('smartFit', function()
37	{
38		graph.popupMenuHandler.hideMenu();
39
40		var scale = graph.view.scale;
41        var tx = graph.view.translate.x;
42        var ty = graph.view.translate.y;
43
44    	ui.actions.get('resetView').funct();
45
46        // Toggle scale if nothing has changed
47        if (Math.abs(scale - graph.view.scale) < 0.00001 && tx == graph.view.translate.x && ty == graph.view.translate.y)
48        {
49        	ui.actions.get((graph.pageVisible) ? 'fitPage' : 'fitWindow').funct();
50        }
51	});
52	this.addAction('keyPressEnter', function()
53	{
54		if (graph.isEnabled())
55		{
56			if (graph.isSelectionEmpty())
57			{
58				ui.actions.get('smartFit').funct();
59			}
60			else
61			{
62				graph.startEditingAtCell();
63			}
64		}
65	});
66	this.addAction('import...', function()
67	{
68		window.openNew = false;
69		window.openKey = 'import';
70
71		// Closes dialog after open
72		window.openFile = new OpenFile(mxUtils.bind(this, function()
73		{
74			ui.hideDialog();
75		}));
76
77		window.openFile.setConsumer(mxUtils.bind(this, function(xml, filename)
78		{
79			try
80			{
81				var doc = mxUtils.parseXml(xml);
82				editor.graph.setSelectionCells(editor.graph.importGraphModel(doc.documentElement));
83			}
84			catch (e)
85			{
86				mxUtils.alert(mxResources.get('invalidOrMissingFile') + ': ' + e.message);
87			}
88		}));
89
90		// Removes openFile if dialog is closed
91		ui.showDialog(new OpenDialog(this).container, 320, 220, true, true, function()
92		{
93			window.openFile = null;
94		});
95	}).isEnabled = isGraphEnabled;
96	this.addAction('save', function() { ui.saveFile(false); }, null, null, Editor.ctrlKey + '+S').isEnabled = isGraphEnabled;
97	this.addAction('saveAs...', function() { ui.saveFile(true); }, null, null, Editor.ctrlKey + '+Shift+S').isEnabled = isGraphEnabled;
98	this.addAction('export...', function() { ui.showDialog(new ExportDialog(ui).container, 300, 340, true, true); });
99	this.addAction('editDiagram...', function()
100	{
101		var dlg = new EditDiagramDialog(ui);
102		ui.showDialog(dlg.container, 620, 420, true, false);
103		dlg.init();
104	});
105	this.addAction('pageSetup...', function() { ui.showDialog(new PageSetupDialog(ui).container, 320, 240, true, true); }).isEnabled = isGraphEnabled;
106	this.addAction('print...', function() { ui.showDialog(new PrintDialog(ui).container, 300, 180, true, true); }, null, 'sprite-print', Editor.ctrlKey + '+P');
107	this.addAction('preview', function() { mxUtils.show(graph, null, 10, 10); });
108
109	// Edit actions
110	this.addAction('undo', function() { ui.undo(); }, null, 'sprite-undo', Editor.ctrlKey + '+Z');
111	this.addAction('redo', function() { ui.redo(); }, null, 'sprite-redo', (!mxClient.IS_WIN) ? Editor.ctrlKey + '+Shift+Z' : Editor.ctrlKey + '+Y');
112	this.addAction('cut', function()
113	{
114		var cells = null;
115
116		try
117		{
118			cells = ui.copyXml();
119
120			if (cells != null)
121			{
122				graph.removeCells(cells, false);
123			}
124		}
125		catch (e)
126		{
127			// ignore
128		}
129
130		if (cells == null)
131		{
132			mxClipboard.cut(graph);
133		}
134	}, null, 'sprite-cut', Editor.ctrlKey + '+X');
135	this.addAction('copy', function()
136	{
137		try
138		{
139			ui.copyXml();
140		}
141		catch (e)
142		{
143			// ignore
144		}
145
146		try
147		{
148			mxClipboard.copy(graph);
149		}
150		catch (e)
151		{
152			ui.handleError(e);
153		}
154	}, null, 'sprite-copy', Editor.ctrlKey + '+C');
155	this.addAction('paste', function()
156	{
157		if (graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent()))
158		{
159			var done = false;
160
161			try
162			{
163				if (Editor.enableNativeCipboard)
164				{
165					ui.readGraphModelFromClipboard(function(xml)
166					{
167						if (xml != null)
168						{
169							graph.getModel().beginUpdate();
170							try
171							{
172								ui.pasteXml(xml, true);
173							}
174							finally
175							{
176								graph.getModel().endUpdate();
177							}
178						}
179						else
180						{
181							mxClipboard.paste(graph);
182						}
183					})
184
185					done = true;
186				}
187			}
188			catch (e)
189			{
190				// ignore
191			}
192
193			if (!done)
194			{
195				mxClipboard.paste(graph);
196			}
197		}
198	}, false, 'sprite-paste', Editor.ctrlKey + '+V');
199	this.addAction('pasteHere', function(evt)
200	{
201		function pasteCellsHere(cells)
202		{
203			if (cells != null)
204			{
205				var includeEdges = true;
206
207				for (var i = 0; i < cells.length && includeEdges; i++)
208				{
209					includeEdges = includeEdges && graph.model.isEdge(cells[i]);
210				}
211
212				var t = graph.view.translate;
213				var s = graph.view.scale;
214				var dx = t.x;
215				var dy = t.y;
216				var bb = null;
217
218				if (cells.length == 1 && includeEdges)
219				{
220					var geo = graph.getCellGeometry(cells[0]);
221
222					if (geo != null)
223					{
224						bb = geo.getTerminalPoint(true);
225					}
226				}
227
228				bb = (bb != null) ? bb : graph.getBoundingBoxFromGeometry(cells, includeEdges);
229
230				if (bb != null)
231				{
232					var x = Math.round(graph.snap(graph.popupMenuHandler.triggerX / s - dx));
233					var y = Math.round(graph.snap(graph.popupMenuHandler.triggerY / s - dy));
234
235					graph.cellsMoved(cells, x - bb.x, y - bb.y);
236				}
237			}
238		};
239
240		function fallback()
241		{
242			graph.getModel().beginUpdate();
243			try
244			{
245				pasteCellsHere(mxClipboard.paste(graph));
246			}
247			finally
248			{
249				graph.getModel().endUpdate();
250			}
251		};
252
253		if (graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent()))
254		{
255			var done = false;
256
257			try
258			{
259				if (Editor.enableNativeCipboard)
260				{
261					ui.readGraphModelFromClipboard(function(xml)
262					{
263						if (xml != null)
264						{
265							graph.getModel().beginUpdate();
266							try
267							{
268								pasteCellsHere(ui.pasteXml(xml, true));
269							}
270							finally
271							{
272								graph.getModel().endUpdate();
273							}
274						}
275						else
276						{
277							fallback();
278						}
279					})
280
281					done = true;
282				}
283			}
284			catch (e)
285			{
286				// ignore
287			}
288
289			if (!done)
290			{
291				fallback();
292			}
293		}
294	});
295
296	this.addAction('copySize', function()
297	{
298		var cell = graph.getSelectionCell();
299
300		if (graph.isEnabled() && cell != null && graph.getModel().isVertex(cell))
301		{
302			var geo = graph.getCellGeometry(cell);
303
304			if (geo != null)
305			{
306				ui.copiedSize = new mxRectangle(geo.x, geo.y, geo.width, geo.height);
307			}
308		}
309	}, null, null, 'Alt+Shift+X');
310
311	this.addAction('pasteSize', function()
312	{
313		if (graph.isEnabled() && !graph.isSelectionEmpty() && ui.copiedSize != null)
314		{
315			graph.getModel().beginUpdate();
316
317			try
318			{
319				var cells = graph.getResizableCells(graph.getSelectionCells());
320
321				for (var i = 0; i < cells.length; i++)
322				{
323					if (graph.getModel().isVertex(cells[i]))
324					{
325						var geo = graph.getCellGeometry(cells[i]);
326
327						if (geo != null)
328						{
329							geo = geo.clone();
330							geo.width = ui.copiedSize.width;
331							geo.height = ui.copiedSize.height;
332
333							graph.getModel().setGeometry(cells[i], geo);
334						}
335					}
336				}
337			}
338			finally
339			{
340				graph.getModel().endUpdate();
341			}
342		}
343	}, null, null, 'Alt+Shift+V');
344
345	this.addAction('copyData', function()
346	{
347		var cell = graph.getSelectionCell() || graph.getModel().getRoot();
348
349		if (graph.isEnabled() && cell != null)
350		{
351			var value = cell.cloneValue();
352
353			if (value != null && !isNaN(value.nodeType))
354			{
355				ui.copiedValue = value;
356			}
357		}
358	}, null, null, 'Alt+Shift+B');
359
360	this.addAction('pasteData', function(evt, trigger)
361	{
362		// Context menu click uses trigger, toolbar menu click uses evt
363		var evt = (trigger != null) ? trigger : evt;
364		var model = graph.getModel();
365
366		function applyValue(cell, value)
367		{
368			var old = model.getValue(cell);
369			value = cell.cloneValue(value);
370			value.removeAttribute('placeholders');
371
372			// Carries over placeholders and label properties
373			if (old != null && !isNaN(old.nodeType))
374			{
375				value.setAttribute('placeholders', old.getAttribute('placeholders'));
376			}
377
378			if (evt == null || !mxEvent.isShiftDown(evt))
379			{
380				value.setAttribute('label', graph.convertValueToString(cell));
381			}
382
383			model.setValue(cell, value);
384		};
385
386		if (graph.isEnabled() && !graph.isSelectionEmpty() && ui.copiedValue != null)
387		{
388			model.beginUpdate();
389
390			try
391			{
392				var cells = graph.getEditableCells(graph.getSelectionCells());
393
394				if (cells.length == 0)
395				{
396					applyValue(model.getRoot(), ui.copiedValue);
397				}
398				else
399				{
400					for (var i = 0; i < cells.length; i++)
401					{
402						applyValue(cells[i], ui.copiedValue);
403					}
404				}
405			}
406			finally
407			{
408				model.endUpdate();
409			}
410		}
411	}, null, null, 'Alt+Shift+E');
412
413	function deleteCells(includeEdges)
414	{
415		// Cancels interactive operations
416		graph.escape();
417		var select = graph.deleteCells(graph.getDeletableCells(graph.getSelectionCells()), includeEdges);
418
419		if (select != null)
420		{
421			graph.setSelectionCells(select);
422		}
423	};
424
425	function deleteLabels()
426	{
427		if (!graph.isSelectionEmpty())
428		{
429			graph.getModel().beginUpdate();
430			try
431			{
432				var cells = graph.getSelectionCells();
433
434				for (var i = 0; i < cells.length; i++)
435				{
436					graph.cellLabelChanged(cells[i], '');
437				}
438			}
439			finally
440			{
441				graph.getModel().endUpdate();
442			}
443		}
444	};
445
446	this.addAction('delete', function(evt, trigger)
447	{
448		// Context menu click uses trigger, toolbar menu click uses evt
449		var evt = (trigger != null) ? trigger : evt;
450
451		if (evt != null && mxEvent.isShiftDown(evt))
452		{
453			deleteLabels();
454		}
455		else
456		{
457			deleteCells(evt != null && (mxEvent.isControlDown(evt) ||
458				mxEvent.isMetaDown(evt) || mxEvent.isAltDown(evt)));
459		}
460	}, null, null, 'Delete');
461	this.addAction('deleteAll', function()
462	{
463		deleteCells(true);
464	});
465	this.addAction('deleteLabels', function()
466	{
467		deleteLabels();
468	}, null, null, Editor.ctrlKey + '+Delete');
469	this.addAction('duplicate', function()
470	{
471		try
472		{
473			graph.setSelectionCells(graph.duplicateCells());
474			graph.scrollCellToVisible(graph.getSelectionCell());
475		}
476		catch (e)
477		{
478			ui.handleError(e);
479		}
480	}, null, null, Editor.ctrlKey + '+D');
481	this.put('turn', new Action(mxResources.get('turn') + ' / ' + mxResources.get('reverse'), function(evt, trigger)
482	{
483		// Context menu click uses trigger, toolbar menu click uses evt
484		var evt = (trigger != null) ? trigger : evt;
485
486		graph.turnShapes(graph.getResizableCells(graph.getSelectionCells()),
487			(evt != null) ? mxEvent.isShiftDown(evt) : false);
488	}, null, null, Editor.ctrlKey + '+R'));
489	this.put('selectConnections', new Action(mxResources.get('selectEdges'), function(evt)
490	{
491		var cell = graph.getSelectionCell();
492
493		if (graph.isEnabled() && cell != null)
494		{
495			graph.addSelectionCells(graph.getEdges(cell));
496		}
497	}));
498	this.addAction('selectVertices', function() { graph.selectVertices(null, true); }, null, null, Editor.ctrlKey + '+Shift+I');
499	this.addAction('selectEdges', function() { graph.selectEdges(); }, null, null, Editor.ctrlKey + '+Shift+E');
500	this.addAction('selectAll', function() { graph.selectAll(null, true); }, null, null, Editor.ctrlKey + '+A');
501	this.addAction('selectNone', function() { graph.clearSelection(); }, null, null, Editor.ctrlKey + '+Shift+A');
502	this.addAction('lockUnlock', function()
503	{
504		if (!graph.isSelectionEmpty())
505		{
506			graph.getModel().beginUpdate();
507			try
508			{
509				var cells = graph.getSelectionCells();
510				var style = graph.getCurrentCellStyle(graph.getSelectionCell());
511				var value = (mxUtils.getValue(style, mxConstants.STYLE_EDITABLE, 1)) == 1 ? 0 : 1;
512				graph.setCellStyles(mxConstants.STYLE_MOVABLE, value, cells);
513				graph.setCellStyles(mxConstants.STYLE_RESIZABLE, value, cells);
514				graph.setCellStyles(mxConstants.STYLE_ROTATABLE, value, cells);
515				graph.setCellStyles(mxConstants.STYLE_DELETABLE, value, cells);
516				graph.setCellStyles(mxConstants.STYLE_EDITABLE, value, cells);
517				graph.setCellStyles('connectable', value, cells);
518			}
519			finally
520			{
521				graph.getModel().endUpdate();
522			}
523		}
524	}, null, null, Editor.ctrlKey + '+L');
525
526	// Navigation actions
527	this.addAction('home', function() { graph.home(); }, null, null, 'Shift+Home');
528	this.addAction('exitGroup', function() { graph.exitGroup(); }, null, null, Editor.ctrlKey + '+Shift+Home');
529	this.addAction('enterGroup', function() { graph.enterGroup(); }, null, null, Editor.ctrlKey + '+Shift+End');
530	this.addAction('collapse', function() { graph.foldCells(true); }, null, null, Editor.ctrlKey + '+Home');
531	this.addAction('expand', function() { graph.foldCells(false); }, null, null, Editor.ctrlKey + '+End');
532
533	// Arrange actions
534	this.addAction('toFront', function()
535	{
536		graph.orderCells(false);
537	}, null, null, Editor.ctrlKey + '+Shift+F');
538	this.addAction('toBack', function()
539	{
540		graph.orderCells(true);
541	}, null, null, Editor.ctrlKey + '+Shift+B');
542	this.addAction('bringForward', function(evt)
543	{
544		graph.orderCells(false, null, true);
545	});
546	this.addAction('sendBackward', function(evt)
547	{
548		graph.orderCells(true, null, true);
549	});
550	this.addAction('group', function()
551	{
552		if (graph.isEnabled())
553		{
554			var cells = mxUtils.sortCells(graph.getSelectionCells(), true);
555
556			if (cells.length == 1 && !graph.isTable(cells[0]) && !graph.isTableRow(cells[0]))
557			{
558				graph.setCellStyles('container', '1');
559			}
560			else
561			{
562				cells = graph.getCellsForGroup(cells);
563
564				if (cells.length > 1)
565				{
566					graph.setSelectionCell(graph.groupCells(null, 0, cells));
567				}
568			}
569		}
570	}, null, null, Editor.ctrlKey + '+G');
571	this.addAction('ungroup', function()
572	{
573		if (graph.isEnabled())
574		{
575			var cells = graph.getEditableCells(graph.getSelectionCells());
576
577	        graph.model.beginUpdate();
578			try
579			{
580				var temp = graph.ungroupCells();
581
582				// Clears container flag for remaining cells
583				if (cells != null)
584				{
585					for (var i = 0; i < cells.length; i++)
586			    	{
587						if (graph.model.contains(cells[i]))
588						{
589							if (graph.model.getChildCount(cells[i]) == 0 &&
590								graph.model.isVertex(cells[i]))
591							{
592								graph.setCellStyles('container', '0', [cells[i]]);
593							}
594
595							temp.push(cells[i]);
596						}
597			    	}
598				}
599		    }
600			finally
601			{
602				graph.model.endUpdate();
603			}
604
605			if (temp.length > 0)
606			{
607				graph.setSelectionCells(temp);
608			}
609		}
610	}, null, null, Editor.ctrlKey + '+Shift+U');
611	this.addAction('removeFromGroup', function()
612	{
613		if (graph.isEnabled())
614		{
615			var cells = graph.getSelectionCells();
616
617			// Removes table rows and cells
618			if (cells != null)
619			{
620				var temp = [];
621
622				for (var i = 0; i < cells.length; i++)
623		    	{
624					if (!graph.isTableRow(cells[i]) &&
625						!graph.isTableCell(cells[i]))
626					{
627						temp.push(cells[i]);
628					}
629		    	}
630
631				graph.removeCellsFromParent(temp);
632			}
633		}
634	});
635	// Adds action
636	this.addAction('edit', function()
637	{
638		if (graph.isEnabled())
639		{
640			graph.startEditingAtCell();
641		}
642	}, null, null, 'F2/Enter');
643	this.addAction('editData...', function()
644	{
645		var cell = graph.getSelectionCell() || graph.getModel().getRoot();
646		ui.showDataDialog(cell);
647	}, null, null, Editor.ctrlKey + '+M');
648	this.addAction('editTooltip...', function()
649	{
650		var cell = graph.getSelectionCell();
651
652		if (graph.isEnabled() && cell != null && graph.isCellEditable(cell))
653		{
654			var tooltip = '';
655
656			if (mxUtils.isNode(cell.value))
657			{
658				var tmp = null;
659
660				if (Graph.translateDiagram && Graph.diagramLanguage != null &&
661					cell.value.hasAttribute('tooltip_' + Graph.diagramLanguage))
662				{
663					tmp = cell.value.getAttribute('tooltip_' + Graph.diagramLanguage);
664				}
665
666				if (tmp == null)
667				{
668					tmp = cell.value.getAttribute('tooltip');
669				}
670
671				if (tmp != null)
672				{
673					tooltip = tmp;
674				}
675			}
676
677	    	var dlg = new TextareaDialog(ui, mxResources.get('editTooltip') + ':', tooltip, function(newValue)
678			{
679				graph.setTooltipForCell(cell, newValue);
680			});
681			ui.showDialog(dlg.container, 320, 200, true, true);
682			dlg.init();
683		}
684	}, null, null, 'Alt+Shift+T');
685	this.addAction('openLink', function()
686	{
687		var link = graph.getLinkForCell(graph.getSelectionCell());
688
689		if (link != null)
690		{
691			graph.openLink(link);
692		}
693	});
694	this.addAction('editLink...', function()
695	{
696		var cell = graph.getSelectionCell();
697
698		if (graph.isEnabled() && cell != null && graph.isCellEditable(cell))
699		{
700			var value = graph.getLinkForCell(cell) || '';
701
702			ui.showLinkDialog(value, mxResources.get('apply'), function(link, docs, linkTarget)
703			{
704				link = mxUtils.trim(link);
705    			graph.setLinkForCell(cell, (link.length > 0) ? link : null);
706				graph.setAttributeForCell(cell, 'linkTarget', linkTarget);
707			}, true, graph.getLinkTargetForCell(cell));
708		}
709	}, null, null, 'Alt+Shift+L');
710	this.put('insertImage', new Action(mxResources.get('image') + '...', function()
711	{
712		if (graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent()))
713		{
714			graph.clearSelection();
715			ui.actions.get('image').funct();
716		}
717	})).isEnabled = isGraphEnabled;
718	this.put('insertLink', new Action(mxResources.get('link') + '...', function()
719	{
720		if (graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent()))
721		{
722			ui.showLinkDialog('', mxResources.get('insert'), function(link, docs, linkTarget)
723			{
724				link = mxUtils.trim(link);
725
726				if (link.length > 0)
727				{
728					var icon = null;
729					var title = graph.getLinkTitle(link);
730
731					if (docs != null && docs.length > 0)
732					{
733						icon = docs[0].iconUrl;
734						title = docs[0].name || docs[0].type;
735						title = title.charAt(0).toUpperCase() + title.substring(1);
736
737						if (title.length > 30)
738						{
739							title = title.substring(0, 30) + '...';
740						}
741					}
742
743            		var linkCell = new mxCell(title, new mxGeometry(0, 0, 100, 40),
744	            	    	'fontColor=#0000EE;fontStyle=4;rounded=1;overflow=hidden;' + ((icon != null) ?
745	            	    	'shape=label;imageWidth=16;imageHeight=16;spacingLeft=26;align=left;image=' + icon :
746	            	    	'spacing=10;'));
747            	    linkCell.vertex = true;
748
749            	    var pt = graph.getCenterInsertPoint(graph.getBoundingBoxFromGeometry([linkCell], true));
750					linkCell.geometry.x = pt.x;
751            	    linkCell.geometry.y = pt.y;
752
753					graph.setAttributeForCell(linkCell, 'linkTarget', linkTarget);
754            	    graph.setLinkForCell(linkCell, link);
755            	    graph.cellSizeUpdated(linkCell, true);
756
757            		graph.getModel().beginUpdate();
758            		try
759            		{
760        	    		linkCell = graph.addCell(linkCell);
761        	    		graph.fireEvent(new mxEventObject('cellsInserted', 'cells', [linkCell]));
762            	    }
763            		finally
764            		{
765            			graph.getModel().endUpdate();
766            		}
767
768            	    graph.setSelectionCell(linkCell);
769            	    graph.scrollCellToVisible(graph.getSelectionCell());
770				}
771			}, true);
772		}
773	})).isEnabled = isGraphEnabled;
774	this.addAction('link...', mxUtils.bind(this, function()
775	{
776		if (graph.isEnabled())
777		{
778			if (graph.cellEditor.isContentEditing())
779			{
780				var elt = graph.getSelectedElement();
781				var link = graph.getParentByName(elt, 'A', graph.cellEditor.textarea);
782				var oldValue = '';
783
784				// Workaround for FF returning the outermost selected element after double
785				// click on a DOM hierarchy with a link inside (but not as topmost element)
786				if (link == null && elt != null && elt.getElementsByTagName != null)
787				{
788					// Finds all links in the selected DOM and uses the link
789					// where the selection text matches its text content
790					var links = elt.getElementsByTagName('a');
791
792					for (var i = 0; i < links.length && link == null; i++)
793					{
794						if (links[i].textContent == elt.textContent)
795						{
796							link = links[i];
797						}
798					}
799				}
800
801				if (link != null && link.nodeName == 'A')
802				{
803					oldValue = link.getAttribute('href') || '';
804					graph.selectNode(link);
805				}
806
807				var selState = graph.cellEditor.saveSelection();
808
809				ui.showLinkDialog(oldValue, mxResources.get('apply'), mxUtils.bind(this, function(value)
810				{
811		    		graph.cellEditor.restoreSelection(selState);
812
813		    		if (value != null)
814		    		{
815		    			graph.insertLink(value);
816					}
817				}));
818			}
819			else if (graph.isSelectionEmpty())
820			{
821				this.get('insertLink').funct();
822			}
823			else
824			{
825				this.get('editLink').funct();
826			}
827		}
828	})).isEnabled = isGraphEnabled;
829	this.addAction('autosize', function()
830	{
831		var cells = graph.getSelectionCells();
832
833		if (cells != null)
834		{
835			graph.getModel().beginUpdate();
836			try
837			{
838				for (var i = 0; i < cells.length; i++)
839				{
840					var cell = cells[i];
841
842					if (graph.getModel().getChildCount(cell) > 0)
843					{
844						graph.updateGroupBounds([cell], 0, true);
845					}
846					else
847					{
848						graph.updateCellSize(cell);
849					}
850				}
851			}
852			finally
853			{
854				graph.getModel().endUpdate();
855			}
856		}
857	}, null, null, Editor.ctrlKey + '+Shift+Y');
858	this.addAction('formattedText', function()
859	{
860    	graph.stopEditing();
861
862		var style = graph.getCommonStyle(graph.getSelectionCells());
863		var value = (mxUtils.getValue(style, 'html', '0') == '1') ? null : '1';
864
865		graph.getModel().beginUpdate();
866		try
867		{
868			var cells = graph.getEditableCells(graph.getSelectionCells());
869
870			for (var i = 0; i < cells.length; i++)
871			{
872				state = graph.getView().getState(cells[i]);
873
874				if (state != null)
875				{
876					var html = mxUtils.getValue(state.style, 'html', '0');
877
878					if (html == '1' && value == null)
879			    	{
880			    		var label = graph.convertValueToString(state.cell);
881
882			    		if (mxUtils.getValue(state.style, 'nl2Br', '1') != '0')
883						{
884							// Removes newlines from HTML and converts breaks to newlines
885							// to match the HTML output in plain text
886							label = label.replace(/\n/g, '').replace(/<br\s*.?>/g, '\n');
887						}
888
889			    		// Removes HTML tags
890		    			var temp = document.createElement('div');
891		    			temp.innerHTML = graph.sanitizeHtml(label);
892		    			label = mxUtils.extractTextWithWhitespace(temp.childNodes);
893
894						graph.cellLabelChanged(state.cell, label);
895						graph.setCellStyles('html', value, [cells[i]]);
896			    	}
897					else if (html == '0' && value == '1')
898			    	{
899			    		// Converts HTML tags to text
900			    		var label = mxUtils.htmlEntities(graph.convertValueToString(state.cell), false);
901
902			    		if (mxUtils.getValue(state.style, 'nl2Br', '1') != '0')
903						{
904							// Converts newlines in plain text to breaks in HTML
905							// to match the plain text output
906			    			label = label.replace(/\n/g, '<br/>');
907						}
908
909			    		graph.cellLabelChanged(state.cell, graph.sanitizeHtml(label));
910			    		graph.setCellStyles('html', value, [cells[i]]);
911			    	}
912				}
913			}
914
915			ui.fireEvent(new mxEventObject('styleChanged', 'keys', ['html'],
916				'values', [(value != null) ? value : '0'], 'cells', cells));
917		}
918		finally
919		{
920			graph.getModel().endUpdate();
921		}
922	});
923	this.addAction('wordWrap', function()
924	{
925    	var state = graph.getView().getState(graph.getSelectionCell());
926    	var value = 'wrap';
927
928		graph.stopEditing();
929
930    	if (state != null && state.style[mxConstants.STYLE_WHITE_SPACE] == 'wrap')
931    	{
932    		value = null;
933    	}
934
935       	graph.setCellStyles(mxConstants.STYLE_WHITE_SPACE, value);
936	});
937	this.addAction('rotation', function()
938	{
939		var value = '0';
940    	var state = graph.getView().getState(graph.getSelectionCell());
941
942    	if (state != null)
943    	{
944    		value = state.style[mxConstants.STYLE_ROTATION] || value;
945    	}
946
947		var dlg = new FilenameDialog(ui, value, mxResources.get('apply'), function(newValue)
948		{
949			if (newValue != null && newValue.length > 0)
950			{
951				graph.setCellStyles(mxConstants.STYLE_ROTATION, newValue);
952			}
953		}, mxResources.get('enterValue') + ' (' + mxResources.get('rotation') + ' 0-360)');
954
955		ui.showDialog(dlg.container, 375, 80, true, true);
956		dlg.init();
957	});
958	// View actions
959	this.addAction('resetView', function()
960	{
961		graph.zoomTo(1);
962		ui.resetScrollbars();
963	}, null, null, 'Enter/Home');
964	this.addAction('zoomIn', function(evt)
965	{
966		if (graph.isFastZoomEnabled())
967		{
968			graph.lazyZoom(true, true, ui.buttonZoomDelay);
969		}
970		else
971		{
972			graph.zoomIn();
973		}
974	}, null, null, Editor.ctrlKey + ' + (Numpad) / Alt+Mousewheel');
975	this.addAction('zoomOut', function(evt)
976	{
977		if (graph.isFastZoomEnabled())
978		{
979			graph.lazyZoom(false, true, ui.buttonZoomDelay);
980		}
981		else
982		{
983			graph.zoomOut();
984		}
985	}, null, null, Editor.ctrlKey + ' - (Numpad) / Alt+Mousewheel');
986	this.addAction('fitWindow', function()
987	{
988		var bounds = (graph.isSelectionEmpty()) ? graph.getGraphBounds() :
989			graph.getBoundingBox(graph.getSelectionCells())
990		var t = graph.view.translate;
991		var s = graph.view.scale;
992
993		bounds.x = bounds.x / s - t.x;
994		bounds.y = bounds.y / s - t.y;
995		bounds.width /= s;
996		bounds.height /= s;
997
998		if (graph.backgroundImage != null)
999		{
1000			bounds = mxRectangle.fromRectangle(bounds);
1001			bounds.add(new mxRectangle(0, 0,
1002				graph.backgroundImage.width,
1003				graph.backgroundImage.height));
1004		}
1005
1006		if (bounds.width == 0 || bounds.height == 0)
1007		{
1008			graph.zoomTo(1);
1009			ui.resetScrollbars();
1010		}
1011		else
1012		{
1013			var b = Editor.fitWindowBorders;
1014
1015			if (b != null)
1016			{
1017				bounds.x -= b.x;
1018				bounds.y -= b.y;
1019				bounds.width += b.width + b.x;
1020				bounds.height += b.height + b.y;
1021			}
1022
1023			graph.fitWindow(bounds);
1024		}
1025	}, null, null, Editor.ctrlKey + '+Shift+H');
1026	this.addAction('fitPage', mxUtils.bind(this, function()
1027	{
1028		if (!graph.pageVisible)
1029		{
1030			this.get('pageView').funct();
1031		}
1032
1033		var fmt = graph.pageFormat;
1034		var ps = graph.pageScale;
1035		var cw = graph.container.clientWidth - 10;
1036		var ch = graph.container.clientHeight - 10;
1037		var scale = Math.floor(20 * Math.min(cw / fmt.width / ps, ch / fmt.height / ps)) / 20;
1038		graph.zoomTo(scale);
1039
1040		if (mxUtils.hasScrollbars(graph.container))
1041		{
1042			var pad = graph.getPagePadding();
1043			graph.container.scrollTop = pad.y * graph.view.scale - 1;
1044			graph.container.scrollLeft = Math.min(pad.x * graph.view.scale, (graph.container.scrollWidth - graph.container.clientWidth) / 2) - 1;
1045		}
1046	}), null, null, Editor.ctrlKey + '+J');
1047	this.addAction('fitTwoPages', mxUtils.bind(this, function()
1048	{
1049		if (!graph.pageVisible)
1050		{
1051			this.get('pageView').funct();
1052		}
1053
1054		var fmt = graph.pageFormat;
1055		var ps = graph.pageScale;
1056		var cw = graph.container.clientWidth - 10;
1057		var ch = graph.container.clientHeight - 10;
1058
1059		var scale = Math.floor(20 * Math.min(cw / (2 * fmt.width) / ps, ch / fmt.height / ps)) / 20;
1060		graph.zoomTo(scale);
1061
1062		if (mxUtils.hasScrollbars(graph.container))
1063		{
1064			var pad = graph.getPagePadding();
1065			graph.container.scrollTop = Math.min(pad.y, (graph.container.scrollHeight - graph.container.clientHeight) / 2);
1066			graph.container.scrollLeft = Math.min(pad.x, (graph.container.scrollWidth - graph.container.clientWidth) / 2);
1067		}
1068	}), null, null, Editor.ctrlKey + '+Shift+J');
1069	this.addAction('fitPageWidth', mxUtils.bind(this, function()
1070	{
1071		if (!graph.pageVisible)
1072		{
1073			this.get('pageView').funct();
1074		}
1075
1076		var fmt = graph.pageFormat;
1077		var ps = graph.pageScale;
1078		var cw = graph.container.clientWidth - 10;
1079
1080		var scale = Math.floor(20 * cw / fmt.width / ps) / 20;
1081		graph.zoomTo(scale);
1082
1083		if (mxUtils.hasScrollbars(graph.container))
1084		{
1085			var pad = graph.getPagePadding();
1086			graph.container.scrollLeft = Math.min(pad.x * graph.view.scale,
1087				(graph.container.scrollWidth - graph.container.clientWidth) / 2);
1088		}
1089	}));
1090	this.put('customZoom', new Action(mxResources.get('custom') + '...', mxUtils.bind(this, function()
1091	{
1092		var dlg = new FilenameDialog(this.editorUi, parseInt(graph.getView().getScale() * 100), mxResources.get('apply'), mxUtils.bind(this, function(newValue)
1093		{
1094			var val = parseInt(newValue);
1095
1096			if (!isNaN(val) && val > 0)
1097			{
1098				graph.zoomTo(val / 100);
1099			}
1100		}), mxResources.get('zoom') + ' (%)');
1101		this.editorUi.showDialog(dlg.container, 300, 80, true, true);
1102		dlg.init();
1103	}), null, null, Editor.ctrlKey + '+0'));
1104	this.addAction('pageScale...', mxUtils.bind(this, function()
1105	{
1106		var dlg = new FilenameDialog(this.editorUi, parseInt(graph.pageScale * 100), mxResources.get('apply'), mxUtils.bind(this, function(newValue)
1107		{
1108			var val = parseInt(newValue);
1109
1110			if (!isNaN(val) && val > 0)
1111			{
1112				var change = new ChangePageSetup(ui, null, null, null, val / 100);
1113				change.ignoreColor = true;
1114				change.ignoreImage = true;
1115
1116				graph.model.execute(change);
1117			}
1118		}), mxResources.get('pageScale') + ' (%)');
1119		this.editorUi.showDialog(dlg.container, 300, 80, true, true);
1120		dlg.init();
1121	}));
1122
1123	// Option actions
1124	var action = null;
1125	action = this.addAction('grid', function()
1126	{
1127		graph.setGridEnabled(!graph.isGridEnabled());
1128		graph.defaultGridEnabled = graph.isGridEnabled();
1129		ui.fireEvent(new mxEventObject('gridEnabledChanged'));
1130	}, null, null, Editor.ctrlKey + '+Shift+G');
1131	action.setToggleAction(true);
1132	action.setSelectedCallback(function() { return graph.isGridEnabled(); });
1133	action.setEnabled(false);
1134
1135	action = this.addAction('guides', function()
1136	{
1137		graph.graphHandler.guidesEnabled = !graph.graphHandler.guidesEnabled;
1138		ui.fireEvent(new mxEventObject('guidesEnabledChanged'));
1139	});
1140	action.setToggleAction(true);
1141	action.setSelectedCallback(function() { return graph.graphHandler.guidesEnabled; });
1142	action.setEnabled(false);
1143
1144	action = this.addAction('tooltips', function()
1145	{
1146		graph.tooltipHandler.setEnabled(!graph.tooltipHandler.isEnabled());
1147		ui.fireEvent(new mxEventObject('tooltipsEnabledChanged'));
1148	});
1149	action.setToggleAction(true);
1150	action.setSelectedCallback(function() { return graph.tooltipHandler.isEnabled(); });
1151
1152	action = this.addAction('collapseExpand', function()
1153	{
1154		var change = new ChangePageSetup(ui);
1155		change.ignoreColor = true;
1156		change.ignoreImage = true;
1157		change.foldingEnabled = !graph.foldingEnabled;
1158
1159		graph.model.execute(change);
1160	});
1161	action.setToggleAction(true);
1162	action.setSelectedCallback(function() { return graph.foldingEnabled; });
1163	action.isEnabled = isGraphEnabled;
1164	action = this.addAction('scrollbars', function()
1165	{
1166		ui.setScrollbars(!ui.hasScrollbars());
1167	});
1168	action.setToggleAction(true);
1169	action.setSelectedCallback(function() { return graph.scrollbars; });
1170	action = this.addAction('pageView', mxUtils.bind(this, function()
1171	{
1172		ui.setPageVisible(!graph.pageVisible);
1173	}));
1174	action.setToggleAction(true);
1175	action.setSelectedCallback(function() { return graph.pageVisible; });
1176	action = this.addAction('connectionArrows', function()
1177	{
1178		graph.connectionArrowsEnabled = !graph.connectionArrowsEnabled;
1179		ui.fireEvent(new mxEventObject('connectionArrowsChanged'));
1180	}, null, null, 'Alt+Shift+A');
1181	action.setToggleAction(true);
1182	action.setSelectedCallback(function() { return graph.connectionArrowsEnabled; });
1183	action = this.addAction('connectionPoints', function()
1184	{
1185		graph.setConnectable(!graph.connectionHandler.isEnabled());
1186		ui.fireEvent(new mxEventObject('connectionPointsChanged'));
1187	}, null, null, 'Alt+Shift+P');
1188	action.setToggleAction(true);
1189	action.setSelectedCallback(function() { return graph.connectionHandler.isEnabled(); });
1190	action = this.addAction('copyConnect', function()
1191	{
1192		graph.connectionHandler.setCreateTarget(!graph.connectionHandler.isCreateTarget());
1193		ui.fireEvent(new mxEventObject('copyConnectChanged'));
1194	});
1195	action.setToggleAction(true);
1196	action.setSelectedCallback(function() { return graph.connectionHandler.isCreateTarget(); });
1197	action.isEnabled = isGraphEnabled;
1198	action = this.addAction('autosave', function()
1199	{
1200		ui.editor.setAutosave(!ui.editor.autosave);
1201	});
1202	action.setToggleAction(true);
1203	action.setSelectedCallback(function() { return ui.editor.autosave; });
1204	action.isEnabled = isGraphEnabled;
1205	action.visible = false;
1206
1207	// Help actions
1208	this.addAction('help', function()
1209	{
1210		var ext = '';
1211
1212		if (mxResources.isLanguageSupported(mxClient.language))
1213		{
1214			ext = '_' + mxClient.language;
1215		}
1216
1217		graph.openLink(RESOURCES_PATH + '/help' + ext + '.html');
1218	});
1219
1220	var showingAbout = false;
1221
1222	this.put('about', new Action(mxResources.get('about') + ' Graph Editor...', function()
1223	{
1224		if (!showingAbout)
1225		{
1226			ui.showDialog(new AboutDialog(ui).container, 320, 280, true, true, function()
1227			{
1228				showingAbout = false;
1229			});
1230
1231			showingAbout = true;
1232		}
1233	}));
1234
1235	// Font style actions
1236	var toggleFontStyle = mxUtils.bind(this, function(key, style, fn, shortcut)
1237	{
1238		return this.addAction(key, function()
1239		{
1240			if (fn != null && graph.cellEditor.isContentEditing())
1241			{
1242				fn();
1243			}
1244			else
1245			{
1246				graph.stopEditing(false);
1247
1248				graph.getModel().beginUpdate();
1249				try
1250				{
1251					var cells = graph.getEditableCells(graph.getSelectionCells());
1252					graph.toggleCellStyleFlags(mxConstants.STYLE_FONTSTYLE, style, cells);
1253
1254					// Removes bold and italic tags and CSS styles inside labels
1255					if ((style & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
1256					{
1257						graph.updateLabelElements(cells, function(elt)
1258						{
1259							elt.style.fontWeight = null;
1260
1261							if (elt.nodeName == 'B')
1262							{
1263								graph.replaceElement(elt);
1264							}
1265						});
1266					}
1267					else if ((style & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
1268					{
1269						graph.updateLabelElements(cells, function(elt)
1270						{
1271							elt.style.fontStyle = null;
1272
1273							if (elt.nodeName == 'I')
1274							{
1275								graph.replaceElement(elt);
1276							}
1277						});
1278					}
1279					else if ((style & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
1280					{
1281						graph.updateLabelElements(cells, function(elt)
1282						{
1283							elt.style.textDecoration = null;
1284
1285							if (elt.nodeName == 'U')
1286							{
1287								graph.replaceElement(elt);
1288							}
1289						});
1290					}
1291
1292					for (var i = 0; i < cells.length; i++)
1293					{
1294						if (graph.model.getChildCount(cells[i]) == 0)
1295						{
1296							graph.autoSizeCell(cells[i], false);
1297						}
1298					}
1299				}
1300				finally
1301				{
1302					graph.getModel().endUpdate();
1303				}
1304			}
1305		}, null, null, shortcut);
1306	});
1307
1308	toggleFontStyle('bold', mxConstants.FONT_BOLD, function() { document.execCommand('bold', false, null); }, Editor.ctrlKey + '+B');
1309	toggleFontStyle('italic', mxConstants.FONT_ITALIC, function() { document.execCommand('italic', false, null); }, Editor.ctrlKey + '+I');
1310	toggleFontStyle('underline', mxConstants.FONT_UNDERLINE, function() { document.execCommand('underline', false, null); }, Editor.ctrlKey + '+U');
1311
1312	// Color actions
1313	this.addAction('fontColor...', function() { ui.menus.pickColor(mxConstants.STYLE_FONTCOLOR, 'forecolor', '000000'); });
1314	this.addAction('strokeColor...', function() { ui.menus.pickColor(mxConstants.STYLE_STROKECOLOR); });
1315	this.addAction('fillColor...', function() { ui.menus.pickColor(mxConstants.STYLE_FILLCOLOR); });
1316	this.addAction('gradientColor...', function() { ui.menus.pickColor(mxConstants.STYLE_GRADIENTCOLOR); });
1317	this.addAction('backgroundColor...', function() { ui.menus.pickColor(mxConstants.STYLE_LABEL_BACKGROUNDCOLOR, 'backcolor'); });
1318	this.addAction('borderColor...', function() { ui.menus.pickColor(mxConstants.STYLE_LABEL_BORDERCOLOR); });
1319
1320	// Format actions
1321	this.addAction('vertical', function() { ui.menus.toggleStyle(mxConstants.STYLE_HORIZONTAL, true); });
1322	this.addAction('shadow', function() { ui.menus.toggleStyle(mxConstants.STYLE_SHADOW); });
1323	this.addAction('solid', function()
1324	{
1325		graph.getModel().beginUpdate();
1326		try
1327		{
1328			graph.setCellStyles(mxConstants.STYLE_DASHED, null);
1329			graph.setCellStyles(mxConstants.STYLE_DASH_PATTERN, null);
1330			ui.fireEvent(new mxEventObject('styleChanged', 'keys', [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN],
1331				'values', [null, null], 'cells', graph.getSelectionCells()));
1332		}
1333		finally
1334		{
1335			graph.getModel().endUpdate();
1336		}
1337	});
1338	this.addAction('dashed', function()
1339	{
1340		graph.getModel().beginUpdate();
1341		try
1342		{
1343			graph.setCellStyles(mxConstants.STYLE_DASHED, '1');
1344			graph.setCellStyles(mxConstants.STYLE_DASH_PATTERN, null);
1345			ui.fireEvent(new mxEventObject('styleChanged', 'keys', [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN],
1346				'values', ['1', null], 'cells', graph.getSelectionCells()));
1347		}
1348		finally
1349		{
1350			graph.getModel().endUpdate();
1351		}
1352	});
1353	this.addAction('dotted', function()
1354	{
1355		graph.getModel().beginUpdate();
1356		try
1357		{
1358			graph.setCellStyles(mxConstants.STYLE_DASHED, '1');
1359			graph.setCellStyles(mxConstants.STYLE_DASH_PATTERN, '1 4');
1360			ui.fireEvent(new mxEventObject('styleChanged', 'keys', [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN],
1361				'values', ['1', '1 4'], 'cells', graph.getSelectionCells()));
1362		}
1363		finally
1364		{
1365			graph.getModel().endUpdate();
1366		}
1367	});
1368	this.addAction('sharp', function()
1369	{
1370		graph.getModel().beginUpdate();
1371		try
1372		{
1373			graph.setCellStyles(mxConstants.STYLE_ROUNDED, '0');
1374			graph.setCellStyles(mxConstants.STYLE_CURVED, '0');
1375			ui.fireEvent(new mxEventObject('styleChanged', 'keys', [mxConstants.STYLE_ROUNDED, mxConstants.STYLE_CURVED],
1376					'values', ['0', '0'], 'cells', graph.getSelectionCells()));
1377		}
1378		finally
1379		{
1380			graph.getModel().endUpdate();
1381		}
1382	});
1383	this.addAction('rounded', function()
1384	{
1385		graph.getModel().beginUpdate();
1386		try
1387		{
1388			graph.setCellStyles(mxConstants.STYLE_ROUNDED, '1');
1389			graph.setCellStyles(mxConstants.STYLE_CURVED, '0');
1390			ui.fireEvent(new mxEventObject('styleChanged', 'keys', [mxConstants.STYLE_ROUNDED, mxConstants.STYLE_CURVED],
1391					'values', ['1', '0'], 'cells', graph.getSelectionCells()));
1392		}
1393		finally
1394		{
1395			graph.getModel().endUpdate();
1396		}
1397	});
1398	this.addAction('toggleRounded', function()
1399	{
1400		if (!graph.isSelectionEmpty() && graph.isEnabled())
1401		{
1402			graph.getModel().beginUpdate();
1403			try
1404			{
1405				var cells = graph.getSelectionCells();
1406	    		var style = graph.getCurrentCellStyle(cells[0]);
1407	    		var value = (mxUtils.getValue(style, mxConstants.STYLE_ROUNDED, '0') == '1') ? '0' : '1';
1408
1409				graph.setCellStyles(mxConstants.STYLE_ROUNDED, value);
1410				graph.setCellStyles(mxConstants.STYLE_CURVED, null);
1411				ui.fireEvent(new mxEventObject('styleChanged', 'keys', [mxConstants.STYLE_ROUNDED, mxConstants.STYLE_CURVED],
1412						'values', [value, '0'], 'cells', graph.getSelectionCells()));
1413			}
1414			finally
1415			{
1416				graph.getModel().endUpdate();
1417			}
1418		}
1419	});
1420	this.addAction('curved', function()
1421	{
1422		graph.getModel().beginUpdate();
1423		try
1424		{
1425			graph.setCellStyles(mxConstants.STYLE_ROUNDED, '0');
1426			graph.setCellStyles(mxConstants.STYLE_CURVED, '1');
1427			ui.fireEvent(new mxEventObject('styleChanged', 'keys', [mxConstants.STYLE_ROUNDED, mxConstants.STYLE_CURVED],
1428					'values', ['0', '1'], 'cells', graph.getSelectionCells()));
1429		}
1430		finally
1431		{
1432			graph.getModel().endUpdate();
1433		}
1434	});
1435	this.addAction('collapsible', function()
1436	{
1437		var state = graph.view.getState(graph.getSelectionCell());
1438		var value = '1';
1439
1440		if (state != null && graph.getFoldingImage(state) != null)
1441		{
1442			value = '0';
1443		}
1444
1445		graph.setCellStyles('collapsible', value);
1446		ui.fireEvent(new mxEventObject('styleChanged', 'keys', ['collapsible'],
1447				'values', [value], 'cells', graph.getSelectionCells()));
1448	});
1449	this.addAction('editStyle...', mxUtils.bind(this, function()
1450	{
1451		var cells = graph.getEditableCells(graph.getSelectionCells());
1452
1453		if (cells != null && cells.length > 0)
1454		{
1455			var model = graph.getModel();
1456
1457	    	var dlg = new TextareaDialog(this.editorUi, mxResources.get('editStyle') + ':',
1458	    		model.getStyle(cells[0]) || '', function(newValue)
1459			{
1460	    		if (newValue != null)
1461				{
1462					graph.setCellStyle(mxUtils.trim(newValue), cells);
1463				}
1464			}, null, null, 400, 220);
1465			this.editorUi.showDialog(dlg.container, 420, 300, true, true);
1466			dlg.init();
1467		}
1468	}), null, null, Editor.ctrlKey + '+E');
1469	this.addAction('setAsDefaultStyle', function()
1470	{
1471		if (graph.isEnabled() && !graph.isSelectionEmpty())
1472		{
1473			ui.setDefaultStyle(graph.getSelectionCell());
1474		}
1475	}, null, null, Editor.ctrlKey + '+Shift+D');
1476	this.addAction('clearDefaultStyle', function()
1477	{
1478		if (graph.isEnabled())
1479		{
1480			ui.clearDefaultStyle();
1481		}
1482	}, null, null, Editor.ctrlKey + '+Shift+R');
1483	this.addAction('addWaypoint', function()
1484	{
1485		var cell = graph.getSelectionCell();
1486
1487		if (cell != null && graph.getModel().isEdge(cell))
1488		{
1489			var handler = editor.graph.selectionCellsHandler.getHandler(cell);
1490
1491			if (handler instanceof mxEdgeHandler)
1492			{
1493				var t = graph.view.translate;
1494				var s = graph.view.scale;
1495				var dx = t.x;
1496				var dy = t.y;
1497
1498				var parent = graph.getModel().getParent(cell);
1499				var pgeo = graph.getCellGeometry(parent);
1500
1501				while (graph.getModel().isVertex(parent) && pgeo != null)
1502				{
1503					dx += pgeo.x;
1504					dy += pgeo.y;
1505
1506					parent = graph.getModel().getParent(parent);
1507					pgeo = graph.getCellGeometry(parent);
1508				}
1509
1510				var x = Math.round(graph.snap(graph.popupMenuHandler.triggerX / s - dx));
1511				var y = Math.round(graph.snap(graph.popupMenuHandler.triggerY / s - dy));
1512
1513				handler.addPointAt(handler.state, x, y);
1514			}
1515		}
1516	});
1517	this.addAction('removeWaypoint', function()
1518	{
1519		// TODO: Action should run with "this" set to action
1520		var rmWaypointAction = ui.actions.get('removeWaypoint');
1521
1522		if (rmWaypointAction.handler != null)
1523		{
1524			// NOTE: Popupevent handled and action updated in Menus.createPopupMenu
1525			rmWaypointAction.handler.removePoint(rmWaypointAction.handler.state, rmWaypointAction.index);
1526		}
1527	});
1528	this.addAction('clearWaypoints', function(evt, trigger)
1529	{
1530		// Context menu click uses trigger, toolbar menu click uses evt
1531		var evt = (trigger != null) ? trigger : evt;
1532		var cells = graph.getSelectionCells();
1533
1534		if (cells != null)
1535		{
1536			cells = graph.getEditableCells(graph.addAllEdges(cells));
1537
1538			graph.getModel().beginUpdate();
1539			try
1540			{
1541				for (var i = 0; i < cells.length; i++)
1542				{
1543					var cell = cells[i];
1544
1545					if (graph.getModel().isEdge(cell))
1546					{
1547						var geo = graph.getCellGeometry(cell);
1548
1549						// Resets fixed connection point
1550						if (mxEvent.isShiftDown(evt))
1551						{
1552							graph.setCellStyles(mxConstants.STYLE_EXIT_X, null, [cell]);
1553							graph.setCellStyles(mxConstants.STYLE_EXIT_Y, null, [cell]);
1554							graph.setCellStyles(mxConstants.STYLE_ENTRY_X, null, [cell]);
1555							graph.setCellStyles(mxConstants.STYLE_ENTRY_Y, null, [cell]);
1556						}
1557						else if (geo != null)
1558						{
1559							geo = geo.clone();
1560							geo.points = null;
1561							geo.x = 0;
1562							geo.y = 0;
1563							geo.offset = null;
1564							graph.getModel().setGeometry(cell, geo);
1565						}
1566					}
1567				}
1568			}
1569			finally
1570			{
1571				graph.getModel().endUpdate();
1572			}
1573		}
1574	}, null, null, 'Alt+Shift+C');
1575	action = this.addAction('subscript', mxUtils.bind(this, function()
1576	{
1577	    if (graph.cellEditor.isContentEditing())
1578	    {
1579			document.execCommand('subscript', false, null);
1580		}
1581	}), null, null, Editor.ctrlKey + '+,');
1582	action = this.addAction('superscript', mxUtils.bind(this, function()
1583	{
1584	    if (graph.cellEditor.isContentEditing())
1585	    {
1586			document.execCommand('superscript', false, null);
1587		}
1588	}), null, null, Editor.ctrlKey + '+.');
1589	this.addAction('image...', function()
1590	{
1591		if (graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent()))
1592		{
1593			var title = mxResources.get('image') + ' (' + mxResources.get('url') + '):';
1594	    	var state = graph.getView().getState(graph.getSelectionCell());
1595	    	var value = '';
1596
1597	    	if (state != null)
1598	    	{
1599	    		value = state.style[mxConstants.STYLE_IMAGE] || value;
1600	    	}
1601
1602	    	var selectionState = graph.cellEditor.saveSelection();
1603
1604	    	ui.showImageDialog(title, value, function(newValue, w, h)
1605			{
1606	    		// Inserts image into HTML text
1607	    		if (graph.cellEditor.isContentEditing())
1608	    		{
1609	    			graph.cellEditor.restoreSelection(selectionState);
1610	    			graph.insertImage(newValue, w, h);
1611	    		}
1612	    		else
1613	    		{
1614					var cells = graph.getSelectionCells();
1615
1616					if (newValue != null && (newValue.length > 0 || cells.length > 0))
1617					{
1618						var select = null;
1619
1620						graph.getModel().beginUpdate();
1621			        	try
1622			        	{
1623			        		// Inserts new cell if no cell is selected
1624			    			if (cells.length == 0)
1625			    			{
1626			    				cells = [graph.insertVertex(graph.getDefaultParent(), null, '', 0, 0, w, h,
1627			    					'shape=image;imageAspect=0;aspect=fixed;verticalLabelPosition=bottom;verticalAlign=top;')];
1628			    				var pt = graph.getCenterInsertPoint(graph.getBoundingBoxFromGeometry(cells, true));
1629								cells[0].geometry.x = pt.x;
1630			            	    cells[0].geometry.y = pt.y;
1631
1632			    				select = cells;
1633		            	    	graph.fireEvent(new mxEventObject('cellsInserted', 'cells', select));
1634			    			}
1635
1636			        		graph.setCellStyles(mxConstants.STYLE_IMAGE, (newValue.length > 0) ? newValue : null, cells);
1637
1638			        		// Sets shape only if not already shape with image (label or image)
1639			        		var style = graph.getCurrentCellStyle(cells[0]);
1640
1641			        		if (style[mxConstants.STYLE_SHAPE] != 'image' && style[mxConstants.STYLE_SHAPE] != 'label')
1642			        		{
1643			        			graph.setCellStyles(mxConstants.STYLE_SHAPE, 'image', cells);
1644			        		}
1645			        		else if (newValue.length == 0)
1646			        		{
1647			        			graph.setCellStyles(mxConstants.STYLE_SHAPE, null, cells);
1648			        		}
1649
1650				        	if (graph.getSelectionCount() == 1)
1651				        	{
1652					        	if (w != null && h != null)
1653					        	{
1654					        		var cell = cells[0];
1655					        		var geo = graph.getModel().getGeometry(cell);
1656
1657					        		if (geo != null)
1658					        		{
1659					        			geo = geo.clone();
1660						        		geo.width = w;
1661						        		geo.height = h;
1662						        		graph.getModel().setGeometry(cell, geo);
1663					        		}
1664					        	}
1665				        	}
1666			        	}
1667			        	finally
1668			        	{
1669			        		graph.getModel().endUpdate();
1670			        	}
1671
1672			        	if (select != null)
1673			        	{
1674			        		graph.setSelectionCells(select);
1675			        		graph.scrollCellToVisible(select[0]);
1676			        	}
1677					}
1678		    	}
1679			}, graph.cellEditor.isContentEditing(), !graph.cellEditor.isContentEditing());
1680		}
1681	}).isEnabled = isGraphEnabled;
1682	action = this.addAction('layers', mxUtils.bind(this, function()
1683	{
1684		if (this.layersWindow == null)
1685		{
1686			// LATER: Check outline window for initial placement
1687			this.layersWindow = new LayersWindow(ui, document.body.offsetWidth - 280, 120, 212, 200);
1688			this.layersWindow.window.addListener('show', mxUtils.bind(this, function()
1689			{
1690				ui.fireEvent(new mxEventObject('layers'));
1691				this.layersWindow.window.fit();
1692			}));
1693			this.layersWindow.window.addListener('hide', function()
1694			{
1695				ui.fireEvent(new mxEventObject('layers'));
1696			});
1697			this.layersWindow.window.setVisible(true);
1698			ui.fireEvent(new mxEventObject('layers'));
1699
1700			this.layersWindow.init();
1701		}
1702		else
1703		{
1704			this.layersWindow.window.setVisible(!this.layersWindow.window.isVisible());
1705		}
1706	}), null, null, Editor.ctrlKey + '+Shift+L');
1707	action.setToggleAction(true);
1708	action.setSelectedCallback(mxUtils.bind(this, function() { return this.layersWindow != null && this.layersWindow.window.isVisible(); }));
1709	action = this.addAction('formatPanel', mxUtils.bind(this, function()
1710	{
1711		ui.toggleFormatPanel();
1712	}), null, null, Editor.ctrlKey + '+Shift+P');
1713	action.setToggleAction(true);
1714	action.setSelectedCallback(mxUtils.bind(this, function() { return ui.formatWidth > 0; }));
1715	action = this.addAction('outline', mxUtils.bind(this, function()
1716	{
1717		if (this.outlineWindow == null)
1718		{
1719			// LATER: Check layers window for initial placement
1720			this.outlineWindow = new OutlineWindow(ui, document.body.offsetWidth - 260, 100, 180, 180);
1721			this.outlineWindow.window.addListener('show', mxUtils.bind(this, function()
1722			{
1723				ui.fireEvent(new mxEventObject('outline'));
1724				this.outlineWindow.window.fit();
1725			}));
1726			this.outlineWindow.window.addListener('hide', function()
1727			{
1728				ui.fireEvent(new mxEventObject('outline'));
1729			});
1730			this.outlineWindow.window.setVisible(true);
1731			ui.fireEvent(new mxEventObject('outline'));
1732		}
1733		else
1734		{
1735			this.outlineWindow.window.setVisible(!this.outlineWindow.window.isVisible());
1736		}
1737	}), null, null, Editor.ctrlKey + '+Shift+O');
1738
1739	action.setToggleAction(true);
1740	action.setSelectedCallback(mxUtils.bind(this, function() { return this.outlineWindow != null && this.outlineWindow.window.isVisible(); }));
1741};
1742
1743/**
1744 * Registers the given action under the given name.
1745 */
1746Actions.prototype.addAction = function(key, funct, enabled, iconCls, shortcut)
1747{
1748	var title;
1749
1750	if (key.substring(key.length - 3) == '...')
1751	{
1752		key = key.substring(0, key.length - 3);
1753		title = mxResources.get(key) + '...';
1754	}
1755	else
1756	{
1757		title = mxResources.get(key);
1758	}
1759
1760	return this.put(key, new Action(title, funct, enabled, iconCls, shortcut));
1761};
1762
1763/**
1764 * Registers the given action under the given name.
1765 */
1766Actions.prototype.put = function(name, action)
1767{
1768	this.actions[name] = action;
1769
1770	return action;
1771};
1772
1773/**
1774 * Returns the action for the given name or null if no such action exists.
1775 */
1776Actions.prototype.get = function(name)
1777{
1778	return this.actions[name];
1779};
1780
1781/**
1782 * Constructs a new action for the given parameters.
1783 */
1784function Action(label, funct, enabled, iconCls, shortcut)
1785{
1786	mxEventSource.call(this);
1787	this.label = label;
1788	this.funct = this.createFunction(funct);
1789	this.enabled = (enabled != null) ? enabled : true;
1790	this.iconCls = iconCls;
1791	this.shortcut = shortcut;
1792	this.visible = true;
1793};
1794
1795// Action inherits from mxEventSource
1796mxUtils.extend(Action, mxEventSource);
1797
1798/**
1799 * Sets the enabled state of the action and fires a stateChanged event.
1800 */
1801Action.prototype.createFunction = function(funct)
1802{
1803	return funct;
1804};
1805
1806/**
1807 * Sets the enabled state of the action and fires a stateChanged event.
1808 */
1809Action.prototype.setEnabled = function(value)
1810{
1811	if (this.enabled != value)
1812	{
1813		this.enabled = value;
1814		this.fireEvent(new mxEventObject('stateChanged'));
1815	}
1816};
1817
1818/**
1819 * Sets the enabled state of the action and fires a stateChanged event.
1820 */
1821Action.prototype.isEnabled = function()
1822{
1823	return this.enabled;
1824};
1825
1826/**
1827 * Sets the enabled state of the action and fires a stateChanged event.
1828 */
1829Action.prototype.setToggleAction = function(value)
1830{
1831	this.toggleAction = value;
1832};
1833
1834/**
1835 * Sets the enabled state of the action and fires a stateChanged event.
1836 */
1837Action.prototype.setSelectedCallback = function(funct)
1838{
1839	this.selectedCallback = funct;
1840};
1841
1842/**
1843 * Sets the enabled state of the action and fires a stateChanged event.
1844 */
1845Action.prototype.isSelected = function()
1846{
1847	return this.selectedCallback();
1848};
1849