3 * $Id: Actions.js,v 1.7 2013-02-14 07:48:01 gaudenz Exp $
4 * Copyright (c) 2006-2012, JGraph Ltd
7 * Constructs the actions object for the given UI.
9 function Actions(editorUi) {
10 this.editorUi = editorUi;
11 this.actions = new Object();
16 * Adds the default actions.
18 Actions.prototype.init = function() {
19 var ui = this.editorUi;
20 var editor = ui.editor;
21 var graph = editor.graph;
24 this.addAction('new', function() {
25 ui.showDialog(new NewDialog(ui).container, 300, 180, true, true);
27 this.addAction('open', function() {
28 ui.showDialog(new OpenDialog(ui).container, 300, 180, true, true);
30 this.addAction('save', function() {
32 }, null, null, 'Ctrl+S');
33 this.addAction('saveAs', function() {
35 }, null, null, 'Ctrl+Shift-S');
36 this.addAction('publish', function() {
37 ui.publishFile(false);
38 }, null, null, 'Ctrl+Shift+P');
39 this.addAction('variable', function() {
40 ui.showDialog(new VariableDialog(ui).container, 300, 180, true, true);
42 this.addAction('import', function() {
43 ui.showDialog(new ImportDialog(ui).container, 300, 200, true, true);
45 this.addAction('export', function() {
46 ui.showDialog(new ExportDialog(ui).container, 300, 200, true, true);
47 }, null, null, 'Ctrl+E');
48 this.put('editFile', new Action(mxResources.get('edit'), mxUtils.bind(this,
50 this.editorUi.showDialog(new EditFileDialog(ui).container, 620, 420,
53 this.addAction('print', function() {
54 mxUtils.printScreen(graph);
55 }, null, 'sprite-print', 'Ctrl+P');
56 this.addAction('preview', function() {
57 mxUtils.show(graph, null, 10, 10);
61 this.addAction('undo', function() {
62 editor.undoManager.undo();
63 }, null, 'sprite-undo', 'Ctrl+Z');
64 this.addAction('redo', function() {
65 editor.undoManager.redo();
66 }, null, 'sprite-redo', 'Ctrl+Y');
67 this.addAction('cut', function() {
68 mxClipboard.cut(graph);
69 }, null, 'sprite-cut', 'Ctrl+X');
70 this.addAction('copy', function() {
71 mxClipboard.copy(graph);
72 }, null, 'sprite-copy', 'Ctrl+C');
73 this.addAction('paste', function() {
74 mxClipboard.paste(graph);
75 }, false, 'sprite-paste', 'Ctrl+V');
76 this.addAction('delete', function() {
78 }, null, null, 'Delete');
79 this.addAction('duplicate', function() {
80 var s = graph.gridSize;
81 graph.setSelectionCells(graph.moveCells(graph.getSelectionCells(), s, s,
83 }, null, null, 'Ctrl+D');
84 this.addAction('selectVertices', function() {
85 graph.selectVertices();
86 }, null, null, 'Ctrl+Shift+V');
87 this.addAction('selectEdges', function() {
89 }, null, null, 'Ctrl+Shift+E');
90 this.addAction('selectAll', function() {
92 }, null, null, 'Ctrl+A');
95 this.addAction('home', function() {
97 }, null, null, 'Home');
98 this.addAction('exitGroup', function() {
100 }, null, null, 'Page Up');
101 this.addAction('enterGroup', function() {
103 }, null, null, 'Page Down');
104 this.addAction('expand', function() {
105 graph.foldCells(false);
106 }, null, null, 'Enter');
107 this.addAction('collapse', function() {
108 graph.foldCells(true);
109 }, null, null, 'Backspace');
112 this.addAction('toFront', function() {
113 graph.orderCells(false);
114 }, null, null, 'Ctrl+F');
115 this.addAction('toBack', function() {
116 graph.orderCells(true);
117 }, null, null, 'Ctrl+B');
118 this.addAction('group', function() {
119 graph.setSelectionCell(graph.groupCells(null, 0));
120 }, null, null, 'Ctrl+G');
121 this.addAction('ungroup', function() {
122 graph.setSelectionCells(graph.ungroupCells());
123 }, null, null, 'Ctrl+U');
124 this.addAction('removeFromGroup', function() {
125 graph.removeCellsFromParent();
127 this.addAction('editLink', function() {
128 var cell = graph.getSelectionCell();
129 var link = graph.getLinkForCell(cell);
135 link = mxUtils.prompt(mxResources.get('enterValue'), link);
138 graph.setLinkForCell(cell, link);
141 this.addAction('openLink', function() {
142 var cell = graph.getSelectionCell();
143 var link = graph.getLinkForCell(cell);
149 this.addAction('autosize', function() {
150 var cells = graph.getSelectionCells();
153 graph.getModel().beginUpdate();
155 for (var i = 0; i < cells.length; i++) {
158 if (graph.getModel().getChildCount(cell)) {
159 graph.updateGroupBounds([cell], 20);
161 graph.updateCellSize(cell);
165 graph.getModel().endUpdate();
169 this.addAction('wordWrap', function() {
170 var state = graph.getView().getState(graph.getSelectionCell());
173 if (state != null && state.style[mxConstants.STYLE_WHITE_SPACE] == 'wrap') {
177 graph.setCellStyles(mxConstants.STYLE_WHITE_SPACE, value);
179 this.addAction('rotation', function() {
181 var state = graph.getView().getState(graph.getSelectionCell());
184 value = state.style[mxConstants.STYLE_ROTATION] || value;
187 value = mxUtils.prompt(mxResources.get('enterValue') + ' (' +
188 mxResources.get('rotation') + ' 0-360)', value);
191 graph.setCellStyles(mxConstants.STYLE_ROTATION, value);
194 this.addAction('tilt', function() {
195 var cells = graph.getSelectionCells();
198 graph.getModel().beginUpdate();
200 for (var i = 0; i < cells.length; i++) {
203 if (graph.getModel().isVertex(cell) && graph.getModel().getChildCount(
205 var geo = graph.getCellGeometry(cell);
208 // Rotates the size and position in the geometry
210 geo.x += geo.width / 2 - geo.height / 2;
211 geo.y += geo.height / 2 - geo.width / 2;
213 geo.width = geo.height;
215 graph.getModel().setGeometry(cell, geo);
217 // Reads the current direction and advances by 90 degrees
218 var state = graph.view.getState(cell);
221 var dir = state.style[mxConstants.STYLE_DIRECTION] || 'east' /*default*/ ;
225 } else if (dir == 'south') {
227 } else if (dir == 'west') {
229 } else if (dir == 'north') {
233 graph.setCellStyles(mxConstants.STYLE_DIRECTION, dir, [cell]);
239 graph.getModel().endUpdate();
242 }, null, null, 'Ctrl+R');
245 this.addAction('actualSize', function() {
248 this.addAction('zoomIn', function() {
250 }, null, null, 'Add');
251 this.addAction('zoomOut', function() {
253 }, null, null, 'Subtract');
254 this.addAction('fitWindow', function() {
258 this.addAction('fitPage', mxUtils.bind(this, function() {
259 if (!graph.pageVisible) {
260 this.get('pageView').funct();
263 var fmt = graph.pageFormat;
264 var ps = graph.pageScale;
265 var cw = graph.container.clientWidth - 20;
266 var ch = graph.container.clientHeight - 20;
268 var scale = Math.floor(100 * Math.min(cw / fmt.width / ps, ch / fmt.height /
272 graph.container.scrollLeft = Math.round(graph.view.translate.x * scale -
273 Math.max(10, (graph.container.clientWidth - fmt.width * ps * scale) / 2)
275 graph.container.scrollTop = Math.round(graph.view.translate.y * scale -
276 Math.max(10, (graph.container.clientHeight - fmt.height * ps * scale) /
279 this.addAction('fitPageWidth', mxUtils.bind(this, function() {
280 if (!graph.pageVisible) {
281 this.get('pageView').funct();
284 var fmt = graph.pageFormat;
285 var ps = graph.pageScale;
286 var cw = graph.container.clientWidth - 20;
288 var scale = Math.floor(100 * cw / fmt.width / ps) / 100;
291 graph.container.scrollLeft = Math.round(graph.view.translate.x * scale -
292 Math.max(10, (graph.container.clientWidth - fmt.width * ps * scale) / 2)
294 graph.container.scrollTop = Math.round(graph.view.translate.y * scale -
295 Math.max(10, (graph.container.clientHeight - fmt.height * ps * scale) /
298 this.put('customZoom', new Action(mxResources.get('custom'), function() {
299 var value = mxUtils.prompt(mxResources.get('enterValue') + ' (%)',
300 parseInt(graph.getView().getScale() * 100));
302 if (value != null && value.length > 0 && !isNaN(parseInt(value))) {
303 graph.zoomTo(parseInt(value) / 100);
309 action = this.addAction('grid', function() {
310 graph.setGridEnabled(!graph.isGridEnabled());
311 editor.updateGraphComponents();
312 }, null, null, 'Ctrl+Shift+G');
313 action.setToggleAction(true);
314 action.setSelectedCallback(function() {
315 return graph.isGridEnabled();
317 action = this.addAction('guides', function() {
318 graph.graphHandler.guidesEnabled = !graph.graphHandler.guidesEnabled;
320 action.setToggleAction(true);
321 action.setSelectedCallback(function() {
322 return graph.graphHandler.guidesEnabled;
324 action = this.addAction('tooltips', function() {
325 graph.tooltipHandler.setEnabled(!graph.tooltipHandler.isEnabled());
327 action.setToggleAction(true);
328 action.setSelectedCallback(function() {
329 return graph.tooltipHandler.isEnabled();
331 action = this.addAction('navigation', function() {
332 graph.foldingEnabled = !graph.foldingEnabled;
333 graph.view.revalidate();
335 action.setToggleAction(true);
336 action.setSelectedCallback(function() {
337 return graph.foldingEnabled;
339 action = this.addAction('scrollbars', function() {
340 graph.scrollbars = !graph.scrollbars;
341 editor.updateGraphComponents();
343 if (!graph.scrollbars) {
344 var t = graph.view.translate;
345 graph.view.setTranslate(t.x - graph.container.scrollLeft / graph.view.scale,
346 t.y - graph.container.scrollTop / graph.view.scale);
347 graph.container.scrollLeft = 0;
348 graph.container.scrollTop = 0;
349 graph.sizeDidChange();
351 var dx = graph.view.translate.x;
352 var dy = graph.view.translate.y;
354 graph.view.translate.x = 0;
355 graph.view.translate.y = 0;
356 graph.sizeDidChange();
357 graph.container.scrollLeft -= Math.round(dx * graph.view.scale);
358 graph.container.scrollTop -= Math.round(dy * graph.view.scale);
360 }, !mxClient.IS_TOUCH);
361 action.setToggleAction(true);
362 action.setSelectedCallback(function() {
363 return graph.container.style.overflow == 'auto';
365 action = this.addAction('pageView', mxUtils.bind(this, function() {
366 graph.pageVisible = !graph.pageVisible;
367 graph.pageBreaksVisible = graph.pageVisible;
368 graph.preferPageSize = graph.pageBreaksVisible;
369 graph.view.validate();
370 graph.sizeDidChange();
372 editor.updateGraphComponents();
373 editor.outline.update();
375 if (mxUtils.hasScrollbars(graph.container)) {
376 if (graph.pageVisible) {
377 graph.container.scrollLeft -= 20;
378 graph.container.scrollTop -= 20;
380 graph.container.scrollLeft += 20;
381 graph.container.scrollTop += 20;
385 action.setToggleAction(true);
386 action.setSelectedCallback(function() {
387 return graph.pageVisible;
389 action = this.addAction('connect', function() {
390 graph.setConnectable(!graph.connectionHandler.isEnabled());
391 }, null, null, 'Ctrl+Q');
392 action.setToggleAction(true);
393 action.setSelectedCallback(function() {
394 return graph.connectionHandler.isEnabled();
396 action = this.addAction('copyConnect', function() {
397 graph.connectionHandler.setCreateTarget(!graph.connectionHandler.isCreateTarget());
399 action.setToggleAction(true);
400 action.setSelectedCallback(function() {
401 return graph.connectionHandler.isCreateTarget();
405 this.addAction('help', function() {
408 if (mxResources.isLanguageSupported(mxClient.language)) {
409 ext = '_' + mxClient.language;
412 window.open(RESOURCES_PATH + '/help' + ext + '.html');
414 this.put('about', new Action(mxResources.get('about') + ' Cally Square',
416 ui.showDialog(new AboutDialog(ui).container, 320, 280, true, true);
417 }, null, null, 'F1'));
419 // Font style actions
420 var toggleFontStyle = mxUtils.bind(this, function(key, style) {
421 this.addAction(key, function() {
422 graph.toggleCellStyleFlags(mxConstants.STYLE_FONTSTYLE, style);
426 toggleFontStyle('bold', mxConstants.FONT_BOLD);
427 toggleFontStyle('italic', mxConstants.FONT_ITALIC);
428 toggleFontStyle('underline', mxConstants.FONT_UNDERLINE);
431 this.addAction('shadow', function() {
432 graph.toggleCellStyles(mxConstants.STYLE_SHADOW);
434 this.addAction('dashed', function() {
435 graph.toggleCellStyles(mxConstants.STYLE_DASHED);
437 this.addAction('rounded', function() {
438 graph.toggleCellStyles(mxConstants.STYLE_ROUNDED);
440 this.addAction('curved', function() {
441 graph.toggleCellStyles(mxConstants.STYLE_CURVED);
443 this.addAction('style', function() {
444 var cells = graph.getSelectionCells();
446 if (cells != null && cells.length > 0) {
447 var model = graph.getModel();
448 var style = mxUtils.prompt(mxResources.get('enterValue') + ' (' +
449 mxResources.get('style') + ')',
450 model.getStyle(cells[0]) || '');
453 graph.setCellStyle(style, cells);
457 this.addAction('setAsDefaultEdge', function() {
458 graph.setDefaultEdge(graph.getSelectionCell());
460 this.addAction('addWaypoint', function() {
461 var cell = graph.getSelectionCell();
463 if (cell != null && graph.getModel().isEdge(cell)) {
464 var handler = editor.graph.selectionCellsHandler.getHandler(cell);
466 if (handler instanceof mxEdgeHandler) {
467 var t = graph.view.translate;
468 var s = graph.view.scale;
472 var parent = graph.getModel().getParent(cell);
473 var pgeo = graph.getCellGeometry(parent);
475 if (graph.getModel().isVertex(parent) && pgeo != null) {
480 handler.addPointAt(handler.state, graph.panningHandler.triggerX / s - dx,
481 graph.panningHandler.triggerY / s - dy);
485 this.addAction('removeWaypoint', function() {
486 // TODO: Action should run with "this" set to action
487 var rmWaypointAction = ui.actions.get('removeWaypoint');
489 if (rmWaypointAction.handler != null) {
490 // NOTE: Popupevent handled and action updated in Menus.createPopupMenu
491 rmWaypointAction.handler.removePoint(rmWaypointAction.handler.state,
492 rmWaypointAction.index);
495 this.addAction('image', function() {
496 function updateImage(value, w, h) {
498 var cells = graph.getSelectionCells();
500 graph.getModel().beginUpdate();
502 // Inserts new cell if no cell is selected
503 if (cells.length == 0) {
504 var gs = graph.getGridSize();
505 cells = [graph.insertVertex(graph.getDefaultParent(), null, '', gs, gs,
510 graph.setCellStyles(mxConstants.STYLE_IMAGE, value, cells);
511 graph.setCellStyles(mxConstants.STYLE_SHAPE, 'image', cells);
513 if (graph.getSelectionCount() == 1) {
514 if (w != null && h != null) {
516 var geo = graph.getModel().getGeometry(cell);
522 graph.getModel().setGeometry(cell, geo);
527 graph.getModel().endUpdate();
530 if (select != null) {
531 graph.setSelectionCells(select);
532 graph.scrollCellToVisible(select[0]);
537 var state = graph.getView().getState(graph.getSelectionCell());
540 value = state.style[mxConstants.STYLE_IMAGE] || value;
543 value = mxUtils.prompt(mxResources.get('enterValue') + ' (' + mxResources.get(
544 'url') + ')', value);
547 if (value.length > 0) {
548 var img = new Image();
550 img.onload = function() {
551 updateImage(value, img.width, img.height);
553 img.onerror = function() {
554 mxUtils.alert(mxResources.get('fileNotFound'));
564 * Registers the given action under the given name.
566 Actions.prototype.addAction = function(key, funct, enabled, iconCls, shortcut) {
567 return this.put(key, new Action(mxResources.get(key), funct, enabled, iconCls,
572 * Registers the given action under the given name.
574 Actions.prototype.put = function(name, action) {
575 this.actions[name] = action;
581 * Returns the action for the given name or null if no such action exists.
583 Actions.prototype.get = function(name) {
584 return this.actions[name];
588 * Constructs a new action for the given parameters.
590 function Action(label, funct, enabled, iconCls, shortcut) {
591 mxEventSource.call(this);
594 this.enabled = (enabled != null) ? enabled : true;
595 this.iconCls = iconCls;
596 this.shortcut = shortcut;
599 // Action inherits from mxEventSource
600 mxUtils.extend(Action, mxEventSource);
603 * Sets the enabled state of the action and fires a stateChanged event.
605 Action.prototype.setEnabled = function(value) {
606 if (this.enabled != value) {
607 this.enabled = value;
608 this.fireEvent(new mxEventObject('stateChanged'));
613 * Sets the enabled state of the action and fires a stateChanged event.
615 Action.prototype.setToggleAction = function(value) {
616 this.toggleAction = value;
620 * Sets the enabled state of the action and fires a stateChanged event.
622 Action.prototype.setSelectedCallback = function(funct) {
623 this.selectedCallback = funct;
627 * Sets the enabled state of the action and fires a stateChanged event.
629 Action.prototype.isSelected = function() {
630 return this.selectedCallback();