2 * $Id: EditorUi.js,v 1.21 2013/03/14 20:46:36 david Exp $
3 * Copyright (c) 2006-2012, JGraph Ltd
6 * Constructs a new graph editor
8 EditorUi = function(editor, container) {
9 this.editor = editor || new Editor();
10 this.container = container || document.getElementById('geEditor');
11 var graph = editor.graph;
14 // Disables scrollbars
15 this.container.style.overflow = 'hidden';
17 // Pre-fetches submenu image
18 new Image().src = mxPopupMenu.prototype.submenuImage;
20 // Pre-fetches connect image
21 if (mxConnectionHandler.prototype.connectImage != null) {
22 new Image().src = mxConnectionHandler.prototype.connectImage.src;
25 // Creates the user interface
26 this.actions = new Actions(this);
27 this.menus = new Menus(this);
32 // Disables HTML and text selection
33 var textEditing = mxUtils.bind(this, function(evt) {
38 if (this.isSelectionAllowed(evt)) {
42 return graph.isEditing();
45 // Disables text selection while not editing and no dialog visible
46 if (this.container == document.body) {
47 this.menubarContainer.onselectstart = textEditing;
48 this.menubarContainer.onmousedown = textEditing;
49 this.toolbarContainer.onselectstart = textEditing;
50 this.toolbarContainer.onmousedown = textEditing;
51 this.diagramContainer.onselectstart = textEditing;
52 this.diagramContainer.onmousedown = textEditing;
53 this.sidebarContainer.onselectstart = textEditing;
54 this.sidebarContainer.onmousedown = textEditing;
55 this.footerContainer.onselectstart = textEditing;
56 this.footerContainer.onmousedown = textEditing;
59 // And uses built-in context menu while editing
60 if (mxClient.IS_IE && (typeof(document.documentMode) === 'undefined' ||
61 document.documentMode < 9)) {
62 mxEvent.addListener(this.diagramContainer, 'contextmenu', textEditing);
63 mxEvent.addListener(this.sidebarContainer, 'contextmenu', textEditing);
65 // Allows browser context menu outside of diagram and sidebar
66 this.diagramContainer.oncontextmenu = textEditing;
67 this.sidebarContainer.oncontextmenu = textEditing;
70 // Contains the main graph instance inside the given panel
71 graph.init(this.diagramContainer);
74 // Enables scrollbars and sets cursor style for the container
75 graph.container.setAttribute('tabindex', '0');
76 // graph.container.style.overflow = (touchStyle) ? 'hidden' : 'auto';
77 graph.container.style.cursor = 'default';
78 graph.container.style.backgroundImage = 'url(' + editor.gridImage + ')';
79 graph.container.style.backgroundPosition = '-1px -1px';
80 graph.container.focus();
82 // Keeps graph container focused on mouse down
83 var graphFireMouseEvent = graph.fireMouseEvent;
84 graph.fireMouseEvent = function(evtName, me, sender) {
85 if (evtName == mxEvent.MOUSE_DOWN) {
86 this.container.focus();
89 graphFireMouseEvent.apply(this, arguments);
93 // Defines invalid connections along with the error messages that they produce.
94 this.createMultiplicities(graph.multiplicities, ['start', 'finally', 'end'], [
103 this.createMultiplicities(graph.multiplicities, ['answer', 'hangup', 'dial',
104 'ext_dial', 'queue', 'voicemail', 'callback'
122 this.createMultiplicities(graph.multiplicities, ['set', 'math'], [
129 this.createMultiplicities(graph.multiplicities, ['background', 'playback',
130 'menu', 'saydigits', 'saynumber', 'sayphonetic', 'tts', 'ispeechtts', 'getdigits'
152 this.createMultiplicities(graph.multiplicities, ['record'], [
157 this.createMultiplicities(graph.multiplicities, ['database', 'ispeechasr'], [
164 this.createMultiplicities(graph.multiplicities, ['gotoc', 'gotoif',
165 'gotoiftime', 'gotoifmultitime', 'vswitch'
179 this.createMultiplicities(graph.multiplicities, ['queuelog', 'goal'], [
186 this.createMultiplicities(graph.multiplicities, ['noop', 'system', 'agi',
187 'subproject', 'custom_app'
203 // Processes a doubleclick on an optional cell and fires a <dblclick> event.
204 // The event is fired initially.
205 // If the graph is enabled and the event has not been consumed, then <edit> is called with the given cell.
206 // The event is ignored if no cell was specified.
207 graph.dblClick = function(evt, cell) {
208 var edges = ['background', 'gotoif', 'gotoiftime', 'menu', 'getdigits', 'vswitch'];
209 var mxe = new mxEventObject(mxEvent.DOUBLE_CLICK, 'event', evt, 'cell',
213 if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed()) {
214 // Override dblClick Vertices and Edges
215 if (this.getModel().isEdge(cell)) {
216 if (edges.indexOf(cell.source.value.tagName) >= 0)
217 this.startEditingAtCell(cell);
218 } else if (this.getModel().isVertex(cell)) {
221 self.showDialog(new GeneralDialog(self, cell).container, 320, 280, true,
228 // Configures automatic expand on mouseover
229 graph.panningHandler.autoExpand = true;
231 // Installs context menu
232 graph.panningHandler.factoryMethod = mxUtils.bind(this, function(menu, cell,
234 this.menus.createPopupMenu(menu, cell, evt);
237 // Initializes the outline
238 editor.outline.init(this.outlineContainer);
240 // Hides context menu
241 mxEvent.addGestureListeners(document, mxUtils.bind(this, function(evt) {
242 graph.panningHandler.hideMenu();
245 // Adds gesture handling (pinch to zoom)
246 if (mxClient.IS_TOUCH) {
247 mxEvent.addListener(graph.container, 'gesturechange',
248 mxUtils.bind(this, function(evt) {
249 graph.view.getDrawPane().setAttribute('transform', 'scale(' + evt.scale +
251 graph.view.getOverlayPane().style.visibility = 'hidden';
255 mxEvent.addListener(graph.container, 'gestureend',
256 mxUtils.bind(this, function(evt) {
257 graph.view.getDrawPane().removeAttribute('transform');
258 graph.zoomToCenter = true;
259 graph.zoom(evt.scale);
260 graph.view.getOverlayPane().style.visibility = 'visible';
265 // Create handler for key events
266 var keyHandler = this.createKeyHandler(editor);
268 // Getter for key handler
269 this.getKeyHandler = function() {
273 // Updates the editor UI after the window has been resized
274 mxEvent.addListener(window, 'resize', mxUtils.bind(this, function() {
276 graph.sizeDidChange();
277 this.editor.outline.update(false);
278 this.editor.outline.outline.sizeDidChange();
281 // Updates action and menu states
287 * Specifies the size of the split bar.
289 EditorUi.prototype.splitSize = (mxClient.IS_TOUCH) ? 16 : 8;
292 * Specifies the height of the menubar. Default is 34.
294 EditorUi.prototype.menubarHeight = 33;
297 * Specifies the height of the toolbar. Default is 36.
299 EditorUi.prototype.toolbarHeight = 36;
302 * Specifies the height of the footer. Default is 28.
304 EditorUi.prototype.footerHeight = 28;
307 * Specifies the position of the horizontal split bar. Default is 212.
309 EditorUi.prototype.hsplitPosition = 204;
312 * Specifies the position of the vertical split bar. Default is 190.
314 EditorUi.prototype.vsplitPosition = 190;
317 * Installs the listeners to update the action states.
319 EditorUi.prototype.init = function() {
320 // Updates action states
321 this.addUndoListener();
322 this.addSelectionListener();
324 // Overrides clipboard to update paste action state
325 var paste = this.actions.get('paste');
327 var updatePaste = function() {
328 paste.setEnabled(!mxClipboard.isEmpty());
331 var mxClipboardCut = mxClipboard.cut;
332 mxClipboard.cut = function() {
333 mxClipboardCut.apply(this, arguments);
337 var mxClipboardCopy = mxClipboard.copy;
338 mxClipboard.copy = function() {
339 mxClipboardCopy.apply(this, arguments);
345 * Hook for allowing selection and context menu for certain events.
347 EditorUi.prototype.isSelectionAllowed = function(evt) {
352 * Opens the current diagram via the window.opener if one exists.
354 EditorUi.prototype.open = function() {
355 // Cross-domain window access is not allowed in FF, so if we
356 // were opened from another domain then this will fail.
358 if (window.opener != null && window.opener.openFile != null) {
359 window.opener.openFile.setConsumer(mxUtils.bind(this, function(xml,
362 var doc = mxUtils.parseXml(xml);
363 this.editor.setGraphXml(doc.documentElement);
364 this.editor.modified = false;
365 this.editor.undoManager.clear();
367 if (filename != null) {
368 this.editor.filename = filename;
371 mxUtils.alert(mxResources.get('invalidOrMissingFile') + ': ' + e.message);
382 * Opens the current diagram via string.
384 EditorUi.prototype.openString = function(xml, filename, data) {
386 var doc = mxUtils.parseXml(xml);
387 this.editor.setGraphXml(doc.documentElement);
388 this.editor.modified = false;
389 this.editor.undoManager.clear();
391 if (filename != null) {
392 this.editor.filename = filename;
396 this.editor.data = data;
399 mxUtils.alert(mxResources.get('invalidOrMissingFile') + ': ' + e.message);
404 * Saves As the current graph under the given project name.
406 EditorUi.prototype.new = function(name) {
407 var editor = this.editor;
410 var xml = mxUtils.getPrettyXml(this.editor.getGraphXml());
412 if (useLocalStorage) {
413 if (localStorage.getItem(name) != null &&
414 !mxUtils.confirm(mxResources.get('replace', [name]))) {
418 localStorage.setItem(name, xml);
419 this.editor.setStatus(mxResources.get('saved') + ' ' + new Date());
421 console.log(xml.length);
422 console.log(MAX_REQUEST_SIZE);
423 if (xml.length < MAX_REQUEST_SIZE) {
424 xml = encodeURIComponent(xml);
425 name = encodeURIComponent(name);
427 var xhr = new XMLHttpRequest();
428 xhr.open("POST", SAVE_URL, true);
429 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
430 xhr.setRequestHeader('Authorization', 'Bearer ' + editor.data.token);
431 xhr.onload = function(e) {
432 if (xhr.readyState === 4) {
433 if (xhr.status === 201) {
434 editor.setStatus('Project ' + JSON.parse(xhr.response).name +
435 ' is opening in new window!');
436 setTimeout(function() {
437 window.open('square/project/' + JSON.parse(xhr.response).id,
441 editor.setStatus('Error creating project: ' +
442 JSON.parse(xhr.response).errors[0].message);
446 xhr.onerror = function(e) {
447 mxUtils.alert(xhr.statusText);
449 xhr.send('description=project_new&name=' + name);
451 mxUtils.alert(mxResources.get('drawingTooLarge'));
458 this.editor.filename = name;
459 this.editor.modified = false;
461 this.editor.setStatus('Error creating project');
464 this.editor.setStatus('Error creating project');
470 * Saves the current graph under the given project name.
472 EditorUi.prototype.save = function(name) {
473 var editor = this.editor;
476 var xml = mxUtils.getPrettyXml(this.editor.getGraphXml());
478 if (useLocalStorage) {
479 if (localStorage.getItem(name) != null &&
480 !mxUtils.confirm(mxResources.get('replace', [name]))) {
484 localStorage.setItem(name, xml);
485 this.editor.setStatus(mxResources.get('saved') + ' ' + new Date());
487 console.log(xml.length);
488 console.log(MAX_REQUEST_SIZE);
489 if (xml.length < MAX_REQUEST_SIZE) {
490 xml = encodeURIComponent(xml);
492 var xhr = new XMLHttpRequest();
493 xhr.open("PUT", SAVE_URL + this.editor.data.id, true);
494 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
495 xhr.setRequestHeader('Authorization', 'Bearer ' + editor.data.token);
496 xhr.onload = function(e) {
497 if (xhr.readyState === 4) {
498 if (xhr.status === 200) {
499 editor.setStatus('Updated at ' + JSON.parse(xhr.response).updatedAt +
500 ' (' + xhr.status + ' ' + xhr.statusText + ')');
502 editor.setStatus('Error saving project: ' +
503 JSON.parse(xhr.response).errors[0].message);
507 xhr.onerror = function(e) {
508 mxUtils.alert(xhr.statusText);
510 xhr.send('preproduction=' + xml);
512 mxUtils.alert(mxResources.get('drawingTooLarge'));
519 this.editor.filename = name;
520 this.editor.modified = false;
522 this.editor.setStatus('Error saving file');
525 this.editor.setStatus('Error saving file');
530 * Saves As the current graph under the given project name.
532 EditorUi.prototype.saveAs = function(name) {
533 var editor = this.editor;
536 var xml = mxUtils.getPrettyXml(this.editor.getGraphXml());
538 if (useLocalStorage) {
539 if (localStorage.getItem(name) != null &&
540 !mxUtils.confirm(mxResources.get('replace', [name]))) {
544 localStorage.setItem(name, xml);
545 this.editor.setStatus(mxResources.get('saved') + ' ' + new Date());
547 console.log(xml.length);
548 console.log(MAX_REQUEST_SIZE);
549 if (xml.length < MAX_REQUEST_SIZE) {
550 xml = encodeURIComponent(xml);
551 name = encodeURIComponent(name);
553 var xhr = new XMLHttpRequest();
554 xhr.open("POST", SAVE_URL, true);
555 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
556 xhr.setRequestHeader('Authorization', 'Bearer ' + editor.data.token);
557 xhr.onload = function(e) {
558 if (xhr.readyState === 4) {
559 if (xhr.status === 201) {
560 editor.setStatus('Project ' + JSON.parse(xhr.response).name +
561 ' is opening in new window!');
562 setTimeout(function() {
563 window.open('square/project/' + JSON.parse(xhr.response).id,
567 editor.setStatus('Error cloning project: ' +
568 JSON.parse(xhr.response).errors[0].message);
572 xhr.onerror = function(e) {
573 mxUtils.alert(xhr.statusText);
575 xhr.send('description=project_cloned&name=' + name +
576 '&preproduction=' + xml + '&production=' + xml);
578 mxUtils.alert(mxResources.get('drawingTooLarge'));
585 this.editor.filename = name;
586 this.editor.modified = false;
588 this.editor.setStatus('Error saving file');
591 this.editor.setStatus('Error saving file');
596 * Plush the current graph under the given project name.
598 EditorUi.prototype.validate = function(a, b) {
599 var graph = this.editor.graph;
600 for (var a = a != null ? a : graph.model.getRoot(), b = b != null ? b : {}, c = true, d = graph.model.getChildCount(a), e = 0; e < d; e++) {
601 var f = graph.model.getChildAt(a, e),
603 graph.isValidRoot(f) && (g = {});
604 g = this.validate(f, g);
605 g != null ? graph.setCellWarning(f, g.replace(/\n/g, "<br>")) : graph.setCellWarning(f, null);
611 //--- Giuseppe Careri - Modify 12/12/2013 - giuseppe.careri@gmail.com
612 if (graph.model.isVertex(a)) {
613 switch (a.value.nodeName) {
615 d = this.validateBlock('start') ? '' : "Only one Start block is allowed" + "\n";
618 d = this.validateBlock('finally') ? '' : "Only one Finally block is allowed" + "\n";
623 d = (a.value.getAttribute('file_id') > 0) ? "" : mxResources.get('noAudioSelected') + "\n";
626 d = (a.value.getAttribute('file_id') > 0) ? "" : mxResources.get('noAudioSelected') + "\n";
627 d += (a.value.getAttribute('variable_id') > 0) ? "" : mxResources.get('noVariableSelected') + "\n";
630 d = (a.value.getAttribute('variable') !== "") ? "" : mxResources.get('noVariableSelected') + "\n";
633 d = (a.value.getAttribute('database_id') > 0) ? "" : mxResources.get('noDatabaseSelected') + "\n";
637 d = (a.value.getAttribute('variable_id') > 0) ? "" : mxResources.get('noVariableSelected') + "\n";
640 d = (a.value.getAttribute('extension') !== "") ? "" : mxResources.get('noExtensionSelected') + "\n";
644 d = (a.value.getAttribute('command') !== "") ? "" : mxResources.get('noCommandSelected') + "\n";
647 d = (a.value.getAttribute('condition') !== "") ? "" : mxResources.get('noConditionSelected') + "\n";
650 d = (a.value.getAttribute('digits') !== "") ? "" : mxResources.get('noDigitsSelected') + "\n";
653 d = (a.value.getAttribute('number') !== "") ? "" : mxResources.get('noNumberSelected') + "\n";
656 d = (a.value.getAttribute('text') !== "") ? "" : mxResources.get('noTextSelected') + "\n";
659 d = (a.value.getAttribute('filename') !== "") ? "" : mxResources.get('noFileSelected') + "\n";
662 d = (a.value.getAttribute('goalname') !== "") ? "" : mxResources.get('noGoalSelected') + "\n";
665 d = (a.value.getAttribute('phone') !== "") ? "" : mxResources.get('noIdentifierSelected') + "\n";
668 d = (a.value.getAttribute('sip_id') > 0) ? "" : mxResources.get('noIdentifierSelected') + "\n";
671 d = (a.value.getAttribute('queue_id') !== "") ? "" : mxResources.get('noQueueSelected') + "\n";
674 d = (a.value.getAttribute('list_id') > 0) ? "" : mxResources.get('noListSelected') + "\n";
677 d = (a.value.getAttribute('interval_id') > 0) ? "" : mxResources.get('noIntervalSelected') + "\n";
679 case 'gotoifmultitime':
680 d = (a.value.getAttribute('interval_id') != "") ? "" : mxResources.get('noIntervalSelected') + "\n";
683 d = (a.value.getAttribute('context') !== "") ? "" : mxResources.get('noContextSelected') + "\n";
684 d += (a.value.getAttribute('boxnumber') !== "") ? "" : mxResources.get('noBoxNumberSelected') + "\n";
687 d = (a.value.getAttribute('project_id') > 0) ? "" : mxResources.get('noProjectSelected') + "\n";
690 d = (a.value.getAttribute('text') !== "") ? "" : mxResources.get('noTextSelected') + "\n";
693 d = (a.value.getAttribute('text') !== "") ? "" : mxResources.get('noTextSelected') + "\n";
694 d += (a.value.getAttribute('key') !== "") ? "" : mxResources.get('noKeySelected') + "\n";
697 d = (a.value.getAttribute('key') !== "") ? "" : mxResources.get('noKeySelected') + "\n";
702 if (graph.model.isEdge(a)) {
703 var z = graph.model.getCell(a.source.getId());
704 switch (z.value.nodeName) {
706 var patt = /^[0-9]{1,45}$|^[i]{1}$|^[t]{1}|^[#]{1}|^[*]{1}$/;
707 d = d + ((patt.test(String(a.getValue()))) ? "" : mxResources.get('numberOrInvalidOrTimeout') + "\n");
710 var patt = /^[0-9]{1,45}$|^[-]{1}$|^[i]{1}$|^[t]{1}|^[#]{1}|^[*]{1}$/;
711 var str = String(a.getValue());
712 var res = str.split(",");
713 res.forEach(function(entry) {
714 d = d + ((patt.test(entry)) ? "" : mxResources.get('numberOrAll') + "\n");
718 var patt = /^(x|i|-)$/;
719 var str = String(a.getValue());
720 d = d + ((patt.test(str)) ? "" : mxResources.get('numberOrInvalidOrDefault') + "\n");
724 case 'gotoifmultitime':
725 var patt = /^(true){1}$|^(false){1}$/;
726 d = d + ((patt.test(String(a.getValue()))) ? "" : mxResources.get('trueOrFalse') + "\n");
731 // -----------------------------------------------------------------
734 graph.isCellCollapsed(a) && !c && (d = d + ((mxResources.get(graph.containsValidationErrorsResource) || graph.containsValidationErrorsResource) + "\n"));
735 d = graph.model.isEdge(a) ?
736 d + (graph.getEdgeValidationError(a, graph.model.getTerminal(a, true), graph.model.getTerminal(a, false)) || "") : d + (graph.getCellValidationError(a) || "");
737 e = graph.validateCell(a, b);
738 e != null && (d = d + e);
739 graph.model.getParent(a) == null && graph.view.validate();
740 return d.length > 0 || !c ? d : null
742 EditorUi.prototype.validateBlock = function(blockName) {
743 var graph = this.editor.graph;
744 var parent = graph.getDefaultParent();
746 // var model = graph.getModel();
747 //We can also use model.getChildren(parent)
748 parent.children.forEach(function(n) {
749 if (graph.model.isVertex(n) && n.value.nodeName == blockName) {
753 return count > 1 ? false : true;
755 EditorUi.prototype.publish = function(name) {
756 var editor = this.editor;
759 var response = this.validate();
760 if (response === null) {
762 var xml = mxUtils.getPrettyXml(this.editor.getGraphXml());
764 if (useLocalStorage) {
765 if (localStorage.getItem(name) != null &&
766 !mxUtils.confirm(mxResources.get('replace', [name]))) {
770 localStorage.setItem(name, xml);
771 this.editor.setStatus(mxResources.get('saved') + ' ' + new Date());
773 console.log(xml.length);
774 console.log(MAX_REQUEST_SIZE);
775 if (xml.length < MAX_REQUEST_SIZE) {
776 xml = encodeURIComponent(xml);
778 var xhr = new XMLHttpRequest();
779 xhr.open("PUT", SAVE_URL + this.editor.data.id, true);
780 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
781 xhr.setRequestHeader('Authorization', 'Bearer ' + editor.data.token);
782 xhr.onload = function(e) {
783 if (xhr.readyState === 4) {
784 if (xhr.status === 200) {
785 editor.setStatus('Published at ' + JSON.parse(xhr.response).updatedAt +
786 ' (' + xhr.status + ' ' + xhr.statusText + ')');
788 editor.setStatus('Error saving project: ' +
789 JSON.parse(xhr.response).errors[0].message);
793 xhr.onerror = function(e) {
794 mxUtils.alert(xhr.statusText);
796 xhr.send('preproduction=' + xml + '&production=' + xml);
798 mxUtils.alert(mxResources.get('drawingTooLarge'));
805 this.editor.filename = name;
806 this.editor.modified = false;
808 this.editor.setStatus('Error publishing file');
811 editor.setStatus('Error publishing project: ');
814 this.editor.setStatus('Error publishing file');
822 EditorUi.prototype.variable = function(name) {
823 var editor = this.editor;
828 var xhr = new XMLHttpRequest();
829 xhr.open("POST", VARIABLE_URL, true);
830 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
831 xhr.setRequestHeader('Authorization', 'Bearer ' + editor.data.token);
832 xhr.onload = function(e) {
833 if (xhr.readyState === 4) {
834 if (xhr.status === 201) {
835 editor.setStatus('Variable created at ' + JSON.parse(xhr.response).createdAt +
836 ' (' + xhr.status + ' ' + xhr.statusText + ')');
838 editor.setStatus('Error cloning project: ' +
839 JSON.parse(xhr.response).errors[0].message);
843 xhr.onerror = function(e) {
844 mxUtils.alert(xhr.statusText);
846 xhr.send('name=' + name);
848 this.editor.setStatus('Error creating variable');
851 this.editor.setStatus('Error creating variable');
856 * Returns the URL for a copy of this editor with no state.
858 EditorUi.prototype.getUrl = function(pathname) {
859 var href = (pathname != null) ? pathname : window.location.pathname;
860 var parms = (href.indexOf('?') > 0) ? 1 : 0;
862 // Removes template URL parameter for new blank diagram
863 for (var key in urlParams) {
870 href += key + '=' + urlParams[key];
878 * Updates the states of the given undo/redo items.
880 EditorUi.prototype.addUndoListener = function() {
881 var undo = this.actions.get('undo');
882 var redo = this.actions.get('redo');
884 var undoMgr = this.editor.undoManager;
886 var undoListener = function() {
887 undo.setEnabled(undoMgr.canUndo());
888 redo.setEnabled(undoMgr.canRedo());
891 undoMgr.addListener(mxEvent.ADD, undoListener);
892 undoMgr.addListener(mxEvent.UNDO, undoListener);
893 undoMgr.addListener(mxEvent.REDO, undoListener);
894 undoMgr.addListener(mxEvent.CLEAR, undoListener);
896 // Updates the button states once
901 * Updates the states of the given toolbar items based on the selection.
903 EditorUi.prototype.addSelectionListener = function() {
904 var selectionListener = mxUtils.bind(this, function() {
905 var graph = this.editor.graph;
906 var selected = !graph.isSelectionEmpty();
907 var vertexSelected = false;
908 var edgeSelected = false;
910 var cells = graph.getSelectionCells();
913 for (var i = 0; i < cells.length; i++) {
916 if (graph.getModel().isEdge(cell)) {
920 if (graph.getModel().isVertex(cell)) {
921 vertexSelected = true;
924 if (edgeSelected && vertexSelected) {
930 // Updates action states
931 var actions = ['cut', 'copy', 'delete', 'duplicate', 'bold', 'italic',
932 'style', 'underline', 'toFront', 'toBack', 'dashed', 'rounded',
937 for (var i = 0; i < actions.length; i++) {
938 this.actions.get(actions[i]).setEnabled(selected);
941 this.actions.get('curved').setEnabled(edgeSelected);
942 this.actions.get('rotation').setEnabled(vertexSelected);
943 this.actions.get('wordWrap').setEnabled(vertexSelected);
944 this.actions.get('group').setEnabled(graph.getSelectionCount() > 1);
945 this.actions.get('ungroup').setEnabled(graph.getSelectionCount() == 1 &&
946 graph.getModel().getChildCount(graph.getSelectionCell()) > 0);
947 var oneVertexSelected = vertexSelected && graph.getSelectionCount() == 1;
948 this.actions.get('removeFromGroup').setEnabled(oneVertexSelected &&
949 graph.getModel().isVertex(graph.getModel().getParent(graph.getSelectionCell()))
952 // Updates menu states
953 var menus = ['fontFamily', 'fontSize', 'alignment', 'position', 'text',
955 'arrange', 'linewidth', 'spacing'
958 for (var i = 0; i < menus.length; i++) {
959 this.menus.get(menus[i]).setEnabled(selected);
962 menus = ['line', 'lineend', 'linestart'];
964 for (var i = 0; i < menus.length; i++) {
965 this.menus.get(menus[i]).setEnabled(edgeSelected);
968 this.actions.get('setAsDefaultEdge').setEnabled(edgeSelected);
970 this.menus.get('align').setEnabled(graph.getSelectionCount() > 1);
971 this.menus.get('direction').setEnabled(vertexSelected || (edgeSelected &&
972 graph.isLoop(graph.view.getState(graph.getSelectionCell()))));
973 this.menus.get('navigation').setEnabled(graph.foldingEnabled && ((graph.view
974 .currentRoot != null) ||
975 (graph.getSelectionCount() == 1 && graph.isValidRoot(graph.getSelectionCell()))
977 this.actions.get('home').setEnabled(graph.view.currentRoot != null);
978 this.actions.get('exitGroup').setEnabled(graph.view.currentRoot != null);
979 var groupEnabled = graph.getSelectionCount() == 1 && graph.isValidRoot(
980 graph.getSelectionCell());
981 this.actions.get('enterGroup').setEnabled(groupEnabled);
982 this.actions.get('expand').setEnabled(groupEnabled);
983 this.actions.get('collapse').setEnabled(groupEnabled);
984 this.actions.get('editLink').setEnabled(graph.getSelectionCount() == 1);
985 this.actions.get('openLink').setEnabled(graph.getSelectionCount() == 1 &&
986 graph.getLinkForCell(graph.getSelectionCell()) != null);
989 this.editor.graph.getSelectionModel().addListener(mxEvent.CHANGE,
995 * Refreshes the viewport.
997 EditorUi.prototype.refresh = function() {
998 var quirks = mxClient.IS_IE && (document.documentMode == null || document.documentMode ==
1000 var w = this.container.clientWidth;
1001 var h = this.container.clientHeight;
1003 if (this.container == document.body) {
1004 w = document.body.clientWidth || document.documentElement.clientWidth;
1005 h = (quirks) ? document.body.clientHeight || document.documentElement.clientHeight :
1006 document.documentElement.clientHeight;
1009 var effHsplitPosition = Math.max(0, Math.min(this.hsplitPosition, w - this.splitSize -
1011 var effVsplitPosition = Math.max(0, Math.min(this.vsplitPosition, h - this.menubarHeight -
1012 this.toolbarHeight - this.footerHeight - this.splitSize - 1));
1014 this.menubarContainer.style.height = this.menubarHeight + 'px';
1015 this.toolbarContainer.style.top = this.menubarHeight + 'px';
1016 this.toolbarContainer.style.height = this.toolbarHeight + 'px';
1018 var tmp = this.menubarHeight + this.toolbarHeight;
1020 if (!mxClient.IS_QUIRKS) {
1024 this.sidebarContainer.style.top = tmp + 'px';
1025 this.sidebarContainer.style.width = effHsplitPosition + 'px';
1026 this.outlineContainer.style.width = effHsplitPosition + 'px';
1027 this.outlineContainer.style.height = effVsplitPosition + 'px';
1028 this.outlineContainer.style.bottom = this.footerHeight + 'px';
1029 this.diagramContainer.style.left = (effHsplitPosition + this.splitSize) +
1031 this.diagramContainer.style.top = this.sidebarContainer.style.top;
1032 this.footerContainer.style.height = this.footerHeight + 'px';
1033 this.footerContainer.style.display = 'none';
1034 this.hsplit.style.top = this.sidebarContainer.style.top;
1035 this.hsplit.style.bottom = this.outlineContainer.style.bottom;
1036 this.hsplit.style.left = effHsplitPosition + 'px';
1037 this.vsplit.style.width = this.sidebarContainer.style.width;
1038 this.vsplit.style.bottom = (effVsplitPosition + this.footerHeight) + 'px';
1041 this.menubarContainer.style.width = w + 'px';
1042 this.toolbarContainer.style.width = this.menubarContainer.style.width;
1043 var sidebarHeight = (h - effVsplitPosition - this.splitSize - this.footerHeight -
1044 this.menubarHeight - this.toolbarHeight);
1045 this.sidebarContainer.style.height = sidebarHeight + 'px';
1046 this.diagramContainer.style.width = (w - effHsplitPosition - this.splitSize) +
1048 var diagramHeight = (h - this.footerHeight - this.menubarHeight - this.toolbarHeight);
1049 this.diagramContainer.style.height = diagramHeight + 'px';
1050 this.footerContainer.style.width = this.menubarContainer.style.width;
1051 this.hsplit.style.height = diagramHeight + 'px';
1053 this.sidebarContainer.style.bottom = (effVsplitPosition + this.splitSize +
1054 this.footerHeight) + 'px';
1055 this.diagramContainer.style.bottom = this.outlineContainer.style.bottom;
1060 * Creates the required containers.
1062 EditorUi.prototype.createDivs = function() {
1063 this.menubarContainer = this.createDiv('geMenubarContainer');
1064 this.toolbarContainer = this.createDiv('geToolbarContainer');
1065 this.sidebarContainer = this.createDiv('geSidebarContainer');
1066 this.outlineContainer = this.createDiv('geOutlineContainer');
1067 this.diagramContainer = this.createDiv('geDiagramContainer');
1068 this.footerContainer = this.createDiv('geFooterContainer');
1069 this.hsplit = this.createDiv('geHsplit');
1070 this.vsplit = this.createDiv('geVsplit');
1072 // Sets static style for containers
1073 this.menubarContainer.style.top = '0px';
1074 this.menubarContainer.style.left = '0px';
1075 this.menubarContainer.style.right = '0px';
1076 this.toolbarContainer.style.left = '0px';
1077 this.toolbarContainer.style.right = '0px';
1078 this.sidebarContainer.style.left = '0px';
1079 this.outlineContainer.style.left = '0px';
1080 this.diagramContainer.style.right = '0px';
1081 this.footerContainer.style.left = '0px';
1082 this.footerContainer.style.right = '0px';
1083 this.footerContainer.style.bottom = '0px';
1084 this.vsplit.style.left = '0px';
1085 this.vsplit.style.height = this.splitSize + 'px';
1086 this.hsplit.style.width = this.splitSize + 'px';
1090 * Creates the required containers.
1092 EditorUi.prototype.createUi = function() {
1094 this.menubar = this.menus.createMenubar(this.createDiv('geMenubar'));
1095 this.menubarContainer.appendChild(this.menubar.container);
1098 this.toolbar = this.createToolbar(this.createDiv('geToolbar'));
1099 this.toolbarContainer.appendChild(this.toolbar.container);
1101 // Creates the sidebar
1102 this.sidebar = this.createSidebar(this.sidebarContainer);
1104 // Creates the footer
1105 this.footerContainer.appendChild(this.createFooter());
1107 // Adds status bar in menubar
1108 this.statusContainer = this.createStatusContainer();
1110 // Connects the status bar to the editor status
1111 this.editor.addListener('statusChanged', mxUtils.bind(this, function() {
1112 this.setStatusText(this.editor.getStatus());
1115 this.setStatusText(this.editor.getStatus());
1116 this.menubar.container.appendChild(this.statusContainer);
1119 this.container.appendChild(this.menubarContainer);
1120 this.container.appendChild(this.toolbarContainer);
1121 this.container.appendChild(this.sidebarContainer);
1122 this.container.appendChild(this.outlineContainer);
1123 this.container.appendChild(this.diagramContainer);
1124 this.container.appendChild(this.footerContainer);
1125 this.container.appendChild(this.hsplit);
1126 this.container.appendChild(this.vsplit);
1129 this.addSplitHandler(this.hsplit, true, 0, mxUtils.bind(this, function(
1131 this.hsplitPosition = value;
1133 this.editor.graph.sizeDidChange();
1134 this.editor.outline.update(false);
1135 this.editor.outline.outline.sizeDidChange();
1139 this.addSplitHandler(this.vsplit, false, this.footerHeight, mxUtils.bind(
1142 this.vsplitPosition = value;
1144 this.editor.outline.update(false);
1145 this.editor.outline.outline.sizeDidChange();
1150 * Creates a new toolbar for the given container.
1152 EditorUi.prototype.createStatusContainer = function() {
1153 var container = document.createElement('a');
1154 container.className = 'geItem geStatus';
1160 * Creates a new toolbar for the given container.
1162 EditorUi.prototype.createStatusContainer = function() {
1163 var container = document.createElement('a');
1164 container.className = 'geItem geStatus';
1170 * Creates a new toolbar for the given container.
1172 EditorUi.prototype.setStatusText = function(value) {
1173 this.statusContainer.innerHTML = value;
1177 * Creates a new toolbar for the given container.
1179 EditorUi.prototype.createToolbar = function(container) {
1180 return new Toolbar(this, container);
1184 * Creates a new sidebar for the given container.
1186 EditorUi.prototype.createSidebar = function(container) {
1187 return new Sidebar(this, container);
1191 * Creates and returns a new footer.
1193 EditorUi.prototype.createFooter = function() {
1194 return this.createDiv('geFooter');
1198 * Creates the actual toolbar for the toolbar container.
1200 EditorUi.prototype.createDiv = function(classname) {
1201 var elt = document.createElement('div');
1202 elt.className = classname;
1208 * Creates the custom header.
1210 EditorUi.prototype.createHeader = function(classname) {
1211 var elt = document.createElement(classname);
1216 * Creates the custom header.
1218 EditorUi.prototype.createForm = function(classname) {
1219 var elt = document.createElement('form');
1220 elt.className = classname;
1226 * Updates the states of the given undo/redo items.
1228 EditorUi.prototype.addSplitHandler = function(elt, horizontal, dx, onChange) {
1232 function getValue() {
1233 return parseInt(((horizontal) ? elt.style.left : elt.style.bottom));
1236 function moveHandler(evt) {
1237 if (start != null) {
1238 var pt = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
1239 onChange(Math.max(0, initial + ((horizontal) ? (pt.x - start.x) : (start.y -
1241 mxEvent.consume(evt);
1245 function dropHandler(evt) {
1251 mxEvent.addGestureListeners(elt, function(evt) {
1252 start = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
1253 initial = getValue();
1254 mxEvent.consume(evt);
1257 mxEvent.addListener(document, 'mousemove', moveHandler);
1258 mxEvent.addListener(document, 'touchmove', moveHandler);
1259 mxEvent.addListener(document, 'mouseup', dropHandler);
1260 mxEvent.addListener(document, 'touchend', dropHandler);
1264 * Displays a print dialog.
1266 EditorUi.prototype.showDialog = function(elt, w, h, modal, closable, onClose) {
1268 this.dialog = new Dialog(this, elt, w, (mxClient.IS_VML) ? h - 12 : h,
1274 * Displays a print dialog.
1276 EditorUi.prototype.hideDialog = function() {
1277 if (this.dialog != null) {
1278 this.dialog.close();
1280 this.editor.graph.container.focus();
1285 * Adds the label menu items to the given menu and parent.
1287 EditorUi.prototype.saveFile = function(forceDialog) {
1288 if (!forceDialog && this.editor.filename != null) {
1289 this.save(this.editor.getOrCreateFilename());
1291 this.showDialog(new SaveDialog(this).container, 300, 100, true, true);
1296 * Adds the label menu items to the given menu and parent.
1298 EditorUi.prototype.publishFile = function(forceDialog) {
1299 if (!forceDialog && this.editor.filename != null) {
1300 this.publish(this.editor.getOrCreateFilename());
1302 //this.showDialog(new SaveDialog(this).container, 300, 100, true, true);
1307 * Executes the given layout.
1309 EditorUi.prototype.executeLayout = function(layout, animate, ignoreChildCount) {
1310 var graph = this.editor.graph;
1311 var cell = graph.getSelectionCell();
1313 // Allow global overridding of animation
1314 animate = this.animate != null ? this.animate : animate;
1316 graph.getModel().beginUpdate();
1318 layout.execute(graph.getDefaultParent(), cell);
1322 // Animates the changes in the graph model except
1323 // for Camino, where animation is too slow
1324 if (animate && navigator.userAgent.indexOf('Camino') < 0) {
1325 // New API for animating graph layout results asynchronously
1326 var morph = new mxMorphing(graph);
1327 morph.addListener(mxEvent.DONE, mxUtils.bind(this, function() {
1328 graph.getModel().endUpdate();
1331 morph.startAnimation();
1333 graph.getModel().endUpdate();
1339 * Creates the keyboard event handler for the current graph and history.
1341 EditorUi.prototype.createKeyHandler = function(editor) {
1342 var graph = this.editor.graph;
1343 var keyHandler = new mxKeyHandler(graph);
1345 // Routes command-key to control-key on Mac
1346 keyHandler.isControlDown = function(evt) {
1347 return mxEvent.isControlDown(evt) || (mxClient.IS_MAC && evt.metaKey);
1350 // Helper function to move cells with the cursor keys
1351 function nudge(keyCode) {
1352 if (!graph.isSelectionEmpty()) {
1356 if (keyCode == 37) {
1358 } else if (keyCode == 38) {
1360 } else if (keyCode == 39) {
1362 } else if (keyCode == 40) {
1366 graph.moveCells(graph.getSelectionCells(), dx, dy);
1367 graph.scrollCellToVisible(graph.getSelectionCell());
1371 // Binds keystrokes to actions
1372 var bindAction = mxUtils.bind(this, function(code, control, key, shift) {
1373 var action = this.actions.get(key);
1375 if (action != null) {
1376 var f = function() {
1377 if (action.enabled) {
1384 keyHandler.bindControlShiftKey(code, f);
1386 keyHandler.bindControlKey(code, f);
1390 keyHandler.bindShiftKey(code, f);
1392 keyHandler.bindKey(code, f);
1399 var keyHandleEscape = keyHandler.escape;
1400 keyHandler.escape = function(evt) {
1402 keyHandleEscape.apply(this, arguments);
1405 // Ignores enter keystroke. Remove this line if you want the
1406 // enter keystroke to stop editing.
1407 keyHandler.enter = function() {};
1408 keyHandler.bindKey(8, function() {
1409 graph.foldCells(true);
1411 keyHandler.bindKey(13, function() {
1412 graph.foldCells(false);
1414 keyHandler.bindKey(33, function() {
1417 keyHandler.bindKey(34, function() {
1420 keyHandler.bindKey(36, function() {
1423 keyHandler.bindKey(35, function() {
1426 keyHandler.bindKey(37, function() {
1429 keyHandler.bindKey(38, function() {
1432 keyHandler.bindKey(39, function() {
1435 keyHandler.bindKey(40, function() {
1438 keyHandler.bindKey(113, function() {
1439 graph.startEditingAtCell();
1441 bindAction(46, false, 'delete'); // Delete
1442 bindAction(82, true, 'tilt'); // Ctrl+R
1443 bindAction(83, true, 'save'); // Ctrl+S
1444 bindAction(83, true, 'saveAs', true); // Ctrl+Shift+S
1445 bindAction(107, false, 'zoomIn'); // Add
1446 bindAction(109, false, 'zoomOut'); // Subtract
1447 bindAction(65, true, 'selectAll'); // Ctrl+A
1448 bindAction(86, true, 'selectVertices', true); // Ctrl+Shift+V
1449 bindAction(69, true, 'selectEdges', true); // Ctrl+Shift+E
1450 bindAction(69, true, 'export'); // Ctrl+Shift+E
1451 bindAction(66, true, 'toBack'); // Ctrl+B
1452 bindAction(70, true, 'toFront'); // Ctrl+F
1453 bindAction(68, true, 'duplicate'); // Ctrl+D
1454 bindAction(90, true, 'undo'); // Ctrl+Z
1455 bindAction(89, true, 'redo'); // Ctrl+Y
1456 bindAction(88, true, 'cut'); // Ctrl+X
1457 bindAction(67, true, 'copy'); // Ctrl+C
1458 bindAction(81, true, 'connect'); // Ctrl+Q
1459 bindAction(86, true, 'paste'); // Ctrl+V
1460 bindAction(71, true, 'group'); // Ctrl+G
1461 bindAction(71, true, 'grid', true); // Ctrl+Shift+G
1462 bindAction(85, true, 'ungroup'); // Ctrl+U
1463 bindAction(112, false, 'about'); // F1
1464 bindAction(80, true, 'publish', true); // Ctrl+Shift+P
1471 * Creates a new toolbar for the given container.
1473 EditorUi.prototype.createMultiplicities = function(graph, cells, source,
1475 for (var i = 0; i < cells.length; i++) {
1476 graph.push(new mxMultiplicity(true, cells[i], null, null, source[i][0],
1477 source[i][1], null, mxResources.get(cells[i]) + ' must have ' + source[i][0] + ' outcoming edge.', null));
1478 graph.push(new mxMultiplicity(false, cells[i], null, null, target[i][0],
1479 target[i][1], null, mxResources.get(cells[i]) + ' must have ' + target[i][0] + ' incoming edge.', null));