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.
95 this.createMultiplicities(graph.multiplicities, ['start', 'end', 'input',
114 // Processes a doubleclick on an optional cell and fires a <dblclick> event.
115 // The event is fired initially.
116 // If the graph is enabled and the event has not been consumed, then <edit> is called with the given cell.
117 // The event is ignored if no cell was specified.
118 graph.dblClick = function(evt, cell) {
119 var edges = ['input', 'question'];
120 var mxe = new mxEventObject(mxEvent.DOUBLE_CLICK, 'event', evt, 'cell',
124 if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed()) {
125 // Override dblClick Vertices and Edges
126 if (this.getModel().isEdge(cell)) {
127 if (edges.indexOf(cell.source.value.tagName) >= 0)
128 this.startEditingAtCell(cell);
129 } else if (this.getModel().isVertex(cell)) {
132 self.showDialog(new GeneralDialog(self, cell).container, 320, 280, true,
139 // Configures automatic expand on mouseover
140 graph.panningHandler.autoExpand = true;
142 // Installs context menu
143 graph.panningHandler.factoryMethod = mxUtils.bind(this, function(menu, cell,
145 this.menus.createPopupMenu(menu, cell, evt);
148 // Initializes the outline
149 editor.outline.init(this.outlineContainer);
151 // Hides context menu
152 mxEvent.addGestureListeners(document, mxUtils.bind(this, function(evt) {
153 graph.panningHandler.hideMenu();
156 // Adds gesture handling (pinch to zoom)
157 if (mxClient.IS_TOUCH) {
158 mxEvent.addListener(graph.container, 'gesturechange',
159 mxUtils.bind(this, function(evt) {
160 graph.view.getDrawPane().setAttribute('transform', 'scale(' + evt.scale +
162 graph.view.getOverlayPane().style.visibility = 'hidden';
166 mxEvent.addListener(graph.container, 'gestureend',
167 mxUtils.bind(this, function(evt) {
168 graph.view.getDrawPane().removeAttribute('transform');
169 graph.zoomToCenter = true;
170 graph.zoom(evt.scale);
171 graph.view.getOverlayPane().style.visibility = 'visible';
176 // Create handler for key events
177 var keyHandler = this.createKeyHandler(editor);
179 // Getter for key handler
180 this.getKeyHandler = function() {
184 // Updates the editor UI after the window has been resized
185 mxEvent.addListener(window, 'resize', mxUtils.bind(this, function() {
187 graph.sizeDidChange();
188 this.editor.outline.update(false);
189 this.editor.outline.outline.sizeDidChange();
192 // Updates action and menu states
198 * Specifies the size of the split bar.
200 EditorUi.prototype.splitSize = (mxClient.IS_TOUCH) ? 16 : 8;
203 * Specifies the height of the menubar. Default is 34.
205 EditorUi.prototype.menubarHeight = 33;
208 * Specifies the height of the toolbar. Default is 36.
210 EditorUi.prototype.toolbarHeight = 36;
213 * Specifies the height of the footer. Default is 28.
215 EditorUi.prototype.footerHeight = 28;
218 * Specifies the position of the horizontal split bar. Default is 212.
220 EditorUi.prototype.hsplitPosition = 204;
223 * Specifies the position of the vertical split bar. Default is 190.
225 EditorUi.prototype.vsplitPosition = 190;
228 * Installs the listeners to update the action states.
230 EditorUi.prototype.init = function() {
231 // Updates action states
232 this.addUndoListener();
233 this.addSelectionListener();
235 // Overrides clipboard to update paste action state
236 var paste = this.actions.get('paste');
238 var updatePaste = function() {
239 paste.setEnabled(!mxClipboard.isEmpty());
242 var mxClipboardCut = mxClipboard.cut;
243 mxClipboard.cut = function() {
244 mxClipboardCut.apply(this, arguments);
248 var mxClipboardCopy = mxClipboard.copy;
249 mxClipboard.copy = function() {
250 mxClipboardCopy.apply(this, arguments);
256 * Hook for allowing selection and context menu for certain events.
258 EditorUi.prototype.isSelectionAllowed = function(evt) {
263 * Opens the current diagram via the window.opener if one exists.
265 EditorUi.prototype.open = function() {
266 // Cross-domain window access is not allowed in FF, so if we
267 // were opened from another domain then this will fail.
269 if (window.opener != null && window.opener.openFile != null) {
270 window.opener.openFile.setConsumer(mxUtils.bind(this, function(xml,
273 var doc = mxUtils.parseXml(xml);
274 this.editor.setGraphXml(doc.documentElement);
275 this.editor.modified = false;
276 this.editor.undoManager.clear();
278 if (filename != null) {
279 this.editor.filename = filename;
282 mxUtils.alert(mxResources.get('invalidOrMissingFile') + ': ' + e.message);
293 * Opens the current diagram via string.
295 EditorUi.prototype.openString = function(xml, filename, data) {
297 var doc = mxUtils.parseXml(xml);
298 this.editor.setGraphXml(doc.documentElement);
299 this.editor.modified = false;
300 this.editor.undoManager.clear();
302 if (filename != null) {
303 this.editor.filename = filename;
307 this.editor.data = data;
310 mxUtils.alert(mxResources.get('invalidOrMissingFile') + ': ' + e.message);
315 * Saves As the current graph under the given project name.
317 EditorUi.prototype.new = function(name) {
318 var editor = this.editor;
321 var xml = mxUtils.getPrettyXml(this.editor.getGraphXml());
323 if (useLocalStorage) {
324 if (localStorage.getItem(name) != null &&
325 !mxUtils.confirm(mxResources.get('replace', [name]))) {
329 localStorage.setItem(name, xml);
330 this.editor.setStatus(mxResources.get('saved') + ' ' + new Date());
332 console.log(xml.length);
333 console.log(MAX_REQUEST_SIZE);
334 if (xml.length < MAX_REQUEST_SIZE) {
335 xml = encodeURIComponent(xml);
336 name = encodeURIComponent(name);
338 var xhr = new XMLHttpRequest();
339 xhr.open("POST", SAVE_URL, true);
340 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
341 xhr.setRequestHeader('Authorization', 'Bearer ' + editor.data.token);
342 xhr.onload = function(e) {
343 if (xhr.readyState === 4) {
344 if (xhr.status === 201) {
345 editor.setStatus('Project ' + JSON.parse(xhr.response).name +
346 ' is opening in new window!');
347 setTimeout(function() {
348 window.open('jscripty/project/' + JSON.parse(xhr.response).id,
352 editor.setStatus('Error creating project: ' +
353 JSON.parse(xhr.response).errors[0].message);
357 xhr.onerror = function(e) {
358 mxUtils.alert(xhr.statusText);
360 xhr.send('description=project_new&name=' + name);
362 mxUtils.alert(mxResources.get('drawingTooLarge'));
369 this.editor.filename = name;
370 this.editor.modified = false;
372 this.editor.setStatus('Error creating project');
375 this.editor.setStatus('Error creating project');
381 * Saves the current graph under the given project name.
383 EditorUi.prototype.save = function(name) {
384 var editor = this.editor;
387 var xml = mxUtils.getPrettyXml(this.editor.getGraphXml());
389 if (useLocalStorage) {
390 if (localStorage.getItem(name) != null &&
391 !mxUtils.confirm(mxResources.get('replace', [name]))) {
395 localStorage.setItem(name, xml);
396 this.editor.setStatus(mxResources.get('saved') + ' ' + new Date());
398 console.log(xml.length);
399 console.log(MAX_REQUEST_SIZE);
400 if (xml.length < MAX_REQUEST_SIZE) {
401 xml = encodeURIComponent(xml);
403 var xhr = new XMLHttpRequest();
404 xhr.open("PUT", SAVE_URL + this.editor.data.id, true);
405 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
406 xhr.setRequestHeader('Authorization', 'Bearer ' + editor.data.token);
407 xhr.onload = function(e) {
408 if (xhr.readyState === 4) {
409 if (xhr.status === 200) {
410 editor.setStatus('Updated at ' + JSON.parse(xhr.response).updatedAt +
411 ' (' + xhr.status + ' ' + xhr.statusText + ')');
413 editor.setStatus('Error saving project: ' +
414 JSON.parse(xhr.response).errors[0].message);
418 xhr.onerror = function(e) {
419 mxUtils.alert(xhr.statusText);
421 xhr.send('draft=' + xml);
423 mxUtils.alert(mxResources.get('drawingTooLarge'));
430 this.editor.filename = name;
431 this.editor.modified = false;
433 this.editor.setStatus('Error saving file');
436 this.editor.setStatus('Error saving file');
441 * Saves As the current graph under the given project name.
443 EditorUi.prototype.saveAs = function(name) {
444 var editor = this.editor;
447 var xml = mxUtils.getPrettyXml(this.editor.getGraphXml());
449 if (useLocalStorage) {
450 if (localStorage.getItem(name) != null &&
451 !mxUtils.confirm(mxResources.get('replace', [name]))) {
455 localStorage.setItem(name, xml);
456 this.editor.setStatus(mxResources.get('saved') + ' ' + new Date());
458 console.log(xml.length);
459 console.log(MAX_REQUEST_SIZE);
460 if (xml.length < MAX_REQUEST_SIZE) {
461 xml = encodeURIComponent(xml);
462 name = encodeURIComponent(name);
464 var xhr = new XMLHttpRequest();
465 xhr.open("POST", SAVE_URL, true);
466 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
467 xhr.setRequestHeader('Authorization', 'Bearer ' + editor.data.token);
468 xhr.onload = function(e) {
469 if (xhr.readyState === 4) {
470 if (xhr.status === 201) {
471 editor.setStatus('Project ' + JSON.parse(xhr.response).name +
472 ' is opening in new window!');
473 setTimeout(function() {
474 window.open('jscripty/project/' + JSON.parse(xhr.response).id,
478 editor.setStatus('Error cloning project: ' +
479 JSON.parse(xhr.response).errors[0].message);
483 xhr.onerror = function(e) {
484 mxUtils.alert(xhr.statusText);
486 xhr.send('description=project_cloned&name=' + name +
487 '&draft=' + xml + '&production=' + xml);
489 mxUtils.alert(mxResources.get('drawingTooLarge'));
496 this.editor.filename = name;
497 this.editor.modified = false;
499 this.editor.setStatus('Error saving file');
502 this.editor.setStatus('Error saving file');
507 * Plush the current graph under the given project name.
509 EditorUi.prototype.validate = function(a, b) {
510 var graph = this.editor.graph;
511 a = null != a ? a : graph.model.getRoot();
512 b = null != b ? b : {};
513 for (var c = !0, d = graph.model.getChildCount(a), e = 0; e < d; e++) {
514 var f = graph.model.getChildAt(a, e),
516 graph.isValidRoot(f) && (g = {});
517 g = this.validate(f, g);
518 null != g ? graph.setCellWarning(f, g.replace(/\n/g, "\x3cbr\x3e")) : graph.setCellWarning(f, null);
522 if (graph.model.isVertex(a)) {
523 switch (a.value.nodeName) {
525 d = (a.value.getAttribute('question') !== "") ? "" :
526 'Content is empty' + "\n";
529 d = (a.value.getAttribute('question') !== "") ? "" :
530 'Content is empty' + "\n";
533 d = this.validateBlock('start') ? '' : "Only one Start block is allowed" + "\n";
538 if (graph.model.isEdge(a)) {
539 var z = graph.model.getCell(a.source.getId());
540 switch (z.value.nodeName) {
542 var patt = /^[a-zA-Z0-9][a-zA-Z0-9\s]*$/;
543 var str = String(a.getValue());
544 if ((str == 'undefined') || (str == 'null')) {
547 var res = str.split(",");
548 res.forEach(function(entry) {
549 d = d + ((patt.test(entry.trim())) ? "" : 'Can not be empty' + "\n");
556 graph.isCellCollapsed(a) && !c && (d += (mxResources.get(graph.containsValidationErrorsResource) || graph.containsValidationErrorsResource) + "\n");
557 d = graph.model.isEdge(a) ? d + (graph.getEdgeValidationError(a, graph.model.getTerminal(a, !0), graph.model.getTerminal(a, !1)) || "") : d + (graph.getCellValidationError(a) || "");
558 e = graph.validateCell(a, b);
559 null != e && (d += e);
560 null == graph.model.getParent(a) && graph.view.validate();
561 return 0 < d.length || !c ? d : null
564 EditorUi.prototype.validateBlock = function(blockName) {
565 var graph = this.editor.graph;
566 var parent = graph.getDefaultParent();
568 // var model = graph.getModel();
569 //We can also use model.getChildren(parent)
570 parent.children.forEach(function(n) {
571 if (graph.model.isVertex(n) && n.value.nodeName == blockName) {
575 return count > 1 ? false : true;
578 EditorUi.prototype.publish = function(name) {
580 var editor = this.editor;
584 var response = this.validate();
585 if (response === null) {
586 var xml = mxUtils.getPrettyXml(this.editor.getGraphXml());
588 if (useLocalStorage) {
589 if (localStorage.getItem(name) != null &&
590 !mxUtils.confirm(mxResources.get('replace', [name]))) {
594 localStorage.setItem(name, xml);
595 this.editor.setStatus(mxResources.get('saved') + ' ' + new Date());
597 console.log(xml.length);
598 console.log(MAX_REQUEST_SIZE);
599 if (xml.length < MAX_REQUEST_SIZE) {
600 xml = encodeURIComponent(xml);
602 var xhr = new XMLHttpRequest();
603 xhr.open("PUT", SAVE_URL + this.editor.data.id, true);
604 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
605 xhr.setRequestHeader('Authorization', 'Bearer ' + editor.data.token);
606 xhr.onload = function(e) {
607 if (xhr.readyState === 4) {
608 if (xhr.status === 200) {
609 editor.setStatus('Published at ' + JSON.parse(xhr.response).updatedAt +
610 ' (' + xhr.status + ' ' + xhr.statusText + ')');
612 editor.setStatus('Error saving project: ' +
613 JSON.parse(xhr.response).errors[0].message);
617 xhr.onerror = function(e) {
618 mxUtils.alert(xhr.statusText);
620 xhr.send('draft=' + xml + '&production=' + xml);
622 mxUtils.alert(mxResources.get('drawingTooLarge'));
629 this.editor.filename = name;
630 this.editor.modified = false;
632 this.editor.setStatus('Error publishing file');
635 editor.setStatus('Error publishing project: ');
638 this.editor.setStatus('Error publishing file');
649 EditorUi.prototype.variable = function(name) {
650 var editor = this.editor;
655 var xhr = new XMLHttpRequest();
656 xhr.open("POST", VARIABLE_URL, true);
657 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
658 xhr.setRequestHeader('Authorization', 'Bearer ' + editor.data.token);
659 xhr.onload = function(e) {
660 if (xhr.readyState === 4) {
661 if (xhr.status === 201) {
662 editor.setStatus('Variable created at ' + JSON.parse(xhr.response).createdAt +
663 ' (' + xhr.status + ' ' + xhr.statusText + ')');
665 editor.setStatus('Error cloning project: ' +
666 JSON.parse(xhr.response).errors[0].message);
670 xhr.onerror = function(e) {
671 mxUtils.alert(xhr.statusText);
673 xhr.send('name=' + name);
675 this.editor.setStatus('Error creating variable');
678 this.editor.setStatus('Error creating variable');
683 * Returns the URL for a copy of this editor with no state.
685 EditorUi.prototype.getUrl = function(pathname) {
686 var href = (pathname != null) ? pathname : window.location.pathname;
687 var parms = (href.indexOf('?') > 0) ? 1 : 0;
689 // Removes template URL parameter for new blank diagram
690 for (var key in urlParams) {
697 href += key + '=' + urlParams[key];
705 * Updates the states of the given undo/redo items.
707 EditorUi.prototype.addUndoListener = function() {
708 var undo = this.actions.get('undo');
709 var redo = this.actions.get('redo');
711 var undoMgr = this.editor.undoManager;
713 var undoListener = function() {
714 undo.setEnabled(undoMgr.canUndo());
715 redo.setEnabled(undoMgr.canRedo());
718 undoMgr.addListener(mxEvent.ADD, undoListener);
719 undoMgr.addListener(mxEvent.UNDO, undoListener);
720 undoMgr.addListener(mxEvent.REDO, undoListener);
721 undoMgr.addListener(mxEvent.CLEAR, undoListener);
723 // Updates the button states once
728 * Updates the states of the given toolbar items based on the selection.
730 EditorUi.prototype.addSelectionListener = function() {
731 var selectionListener = mxUtils.bind(this, function() {
732 var graph = this.editor.graph;
733 var selected = !graph.isSelectionEmpty();
734 var vertexSelected = false;
735 var edgeSelected = false;
737 var cells = graph.getSelectionCells();
740 for (var i = 0; i < cells.length; i++) {
743 if (graph.getModel().isEdge(cell)) {
747 if (graph.getModel().isVertex(cell)) {
748 vertexSelected = true;
751 if (edgeSelected && vertexSelected) {
757 // Updates action states
758 var actions = ['cut', 'copy', 'delete', 'duplicate', 'bold', 'italic',
759 'style', 'underline', 'toFront', 'toBack', 'dashed', 'rounded',
764 for (var i = 0; i < actions.length; i++) {
765 this.actions.get(actions[i]).setEnabled(selected);
768 this.actions.get('curved').setEnabled(edgeSelected);
769 this.actions.get('rotation').setEnabled(vertexSelected);
770 this.actions.get('wordWrap').setEnabled(vertexSelected);
771 this.actions.get('group').setEnabled(graph.getSelectionCount() > 1);
772 this.actions.get('ungroup').setEnabled(graph.getSelectionCount() == 1 &&
773 graph.getModel().getChildCount(graph.getSelectionCell()) > 0);
774 var oneVertexSelected = vertexSelected && graph.getSelectionCount() == 1;
775 this.actions.get('removeFromGroup').setEnabled(oneVertexSelected &&
776 graph.getModel().isVertex(graph.getModel().getParent(graph.getSelectionCell()))
779 // Updates menu states
780 var menus = ['fontFamily', 'fontSize', 'alignment', 'position', 'text',
782 'arrange', 'linewidth', 'spacing'
785 for (var i = 0; i < menus.length; i++) {
786 this.menus.get(menus[i]).setEnabled(selected);
789 menus = ['line', 'lineend', 'linestart'];
791 for (var i = 0; i < menus.length; i++) {
792 this.menus.get(menus[i]).setEnabled(edgeSelected);
795 this.actions.get('setAsDefaultEdge').setEnabled(edgeSelected);
797 this.menus.get('align').setEnabled(graph.getSelectionCount() > 1);
798 this.menus.get('direction').setEnabled(vertexSelected || (edgeSelected &&
799 graph.isLoop(graph.view.getState(graph.getSelectionCell()))));
800 this.menus.get('navigation').setEnabled(graph.foldingEnabled && ((graph.view
801 .currentRoot != null) ||
802 (graph.getSelectionCount() == 1 && graph.isValidRoot(graph.getSelectionCell()))
804 this.actions.get('home').setEnabled(graph.view.currentRoot != null);
805 this.actions.get('exitGroup').setEnabled(graph.view.currentRoot != null);
806 var groupEnabled = graph.getSelectionCount() == 1 && graph.isValidRoot(
807 graph.getSelectionCell());
808 this.actions.get('enterGroup').setEnabled(groupEnabled);
809 this.actions.get('expand').setEnabled(groupEnabled);
810 this.actions.get('collapse').setEnabled(groupEnabled);
811 this.actions.get('editLink').setEnabled(graph.getSelectionCount() == 1);
812 this.actions.get('openLink').setEnabled(graph.getSelectionCount() == 1 &&
813 graph.getLinkForCell(graph.getSelectionCell()) != null);
816 this.editor.graph.getSelectionModel().addListener(mxEvent.CHANGE,
822 * Refreshes the viewport.
824 EditorUi.prototype.refresh = function() {
825 var quirks = mxClient.IS_IE && (document.documentMode == null || document.documentMode ==
827 var w = this.container.clientWidth;
828 var h = this.container.clientHeight;
830 if (this.container == document.body) {
831 w = document.body.clientWidth || document.documentElement.clientWidth;
832 h = (quirks) ? document.body.clientHeight || document.documentElement.clientHeight :
833 document.documentElement.clientHeight;
836 var effHsplitPosition = Math.max(0, Math.min(this.hsplitPosition, w - this.splitSize -
838 var effVsplitPosition = Math.max(0, Math.min(this.vsplitPosition, h - this.menubarHeight -
839 this.toolbarHeight - this.footerHeight - this.splitSize - 1));
841 this.menubarContainer.style.height = this.menubarHeight + 'px';
842 this.toolbarContainer.style.top = this.menubarHeight + 'px';
843 this.toolbarContainer.style.height = this.toolbarHeight + 'px';
845 var tmp = this.menubarHeight + this.toolbarHeight;
847 if (!mxClient.IS_QUIRKS) {
851 this.sidebarContainer.style.top = tmp + 'px';
852 this.sidebarContainer.style.width = effHsplitPosition + 'px';
853 this.outlineContainer.style.width = effHsplitPosition + 'px';
854 this.outlineContainer.style.height = effVsplitPosition + 'px';
855 this.outlineContainer.style.bottom = this.footerHeight + 'px';
856 this.diagramContainer.style.left = (effHsplitPosition + this.splitSize) +
858 this.diagramContainer.style.top = this.sidebarContainer.style.top;
859 this.footerContainer.style.height = this.footerHeight + 'px';
860 this.footerContainer.style.display = 'none';
861 this.hsplit.style.top = this.sidebarContainer.style.top;
862 this.hsplit.style.bottom = this.outlineContainer.style.bottom;
863 this.hsplit.style.left = effHsplitPosition + 'px';
864 this.vsplit.style.width = this.sidebarContainer.style.width;
865 this.vsplit.style.bottom = (effVsplitPosition + this.footerHeight) + 'px';
868 this.menubarContainer.style.width = w + 'px';
869 this.toolbarContainer.style.width = this.menubarContainer.style.width;
870 var sidebarHeight = (h - effVsplitPosition - this.splitSize - this.footerHeight -
871 this.menubarHeight - this.toolbarHeight);
872 this.sidebarContainer.style.height = sidebarHeight + 'px';
873 this.diagramContainer.style.width = (w - effHsplitPosition - this.splitSize) +
875 var diagramHeight = (h - this.footerHeight - this.menubarHeight - this.toolbarHeight);
876 this.diagramContainer.style.height = diagramHeight + 'px';
877 this.footerContainer.style.width = this.menubarContainer.style.width;
878 this.hsplit.style.height = diagramHeight + 'px';
880 this.sidebarContainer.style.bottom = (effVsplitPosition + this.splitSize +
881 this.footerHeight) + 'px';
882 this.diagramContainer.style.bottom = this.outlineContainer.style.bottom;
887 * Creates the required containers.
889 EditorUi.prototype.createDivs = function() {
890 this.menubarContainer = this.createDiv('geMenubarContainer');
891 this.toolbarContainer = this.createDiv('geToolbarContainer');
892 this.sidebarContainer = this.createDiv('geSidebarContainer');
893 this.outlineContainer = this.createDiv('geOutlineContainer');
894 this.diagramContainer = this.createDiv('geDiagramContainer');
895 this.footerContainer = this.createDiv('geFooterContainer');
896 this.hsplit = this.createDiv('geHsplit');
897 this.vsplit = this.createDiv('geVsplit');
899 // Sets static style for containers
900 this.menubarContainer.style.top = '0px';
901 this.menubarContainer.style.left = '0px';
902 this.menubarContainer.style.right = '0px';
903 this.toolbarContainer.style.left = '0px';
904 this.toolbarContainer.style.right = '0px';
905 this.sidebarContainer.style.left = '0px';
906 this.outlineContainer.style.left = '0px';
907 this.diagramContainer.style.right = '0px';
908 this.footerContainer.style.left = '0px';
909 this.footerContainer.style.right = '0px';
910 this.footerContainer.style.bottom = '0px';
911 this.vsplit.style.left = '0px';
912 this.vsplit.style.height = this.splitSize + 'px';
913 this.hsplit.style.width = this.splitSize + 'px';
917 * Creates the required containers.
919 EditorUi.prototype.createUi = function() {
921 this.menubar = this.menus.createMenubar(this.createDiv('geMenubar'));
922 this.menubarContainer.appendChild(this.menubar.container);
925 this.toolbar = this.createToolbar(this.createDiv('geToolbar'));
926 this.toolbarContainer.appendChild(this.toolbar.container);
928 // Creates the sidebar
929 this.sidebar = this.createSidebar(this.sidebarContainer);
931 // Creates the footer
932 this.footerContainer.appendChild(this.createFooter());
934 // Adds status bar in menubar
935 this.statusContainer = this.createStatusContainer();
937 // Connects the status bar to the editor status
938 this.editor.addListener('statusChanged', mxUtils.bind(this, function() {
939 this.setStatusText(this.editor.getStatus());
942 this.setStatusText(this.editor.getStatus());
943 this.menubar.container.appendChild(this.statusContainer);
946 this.container.appendChild(this.menubarContainer);
947 this.container.appendChild(this.toolbarContainer);
948 this.container.appendChild(this.sidebarContainer);
949 this.container.appendChild(this.outlineContainer);
950 this.container.appendChild(this.diagramContainer);
951 this.container.appendChild(this.footerContainer);
952 this.container.appendChild(this.hsplit);
953 this.container.appendChild(this.vsplit);
956 this.addSplitHandler(this.hsplit, true, 0, mxUtils.bind(this, function(
958 this.hsplitPosition = value;
960 this.editor.graph.sizeDidChange();
961 this.editor.outline.update(false);
962 this.editor.outline.outline.sizeDidChange();
966 this.addSplitHandler(this.vsplit, false, this.footerHeight, mxUtils.bind(
969 this.vsplitPosition = value;
971 this.editor.outline.update(false);
972 this.editor.outline.outline.sizeDidChange();
977 * Creates a new toolbar for the given container.
979 EditorUi.prototype.createStatusContainer = function() {
980 var container = document.createElement('a');
981 container.className = 'geItem geStatus';
987 * Creates a new toolbar for the given container.
989 EditorUi.prototype.createStatusContainer = function() {
990 var container = document.createElement('a');
991 container.className = 'geItem geStatus';
997 * Creates a new toolbar for the given container.
999 EditorUi.prototype.setStatusText = function(value) {
1000 this.statusContainer.innerHTML = value;
1004 * Creates a new toolbar for the given container.
1006 EditorUi.prototype.createToolbar = function(container) {
1007 return new Toolbar(this, container);
1011 * Creates a new sidebar for the given container.
1013 EditorUi.prototype.createSidebar = function(container) {
1014 return new Sidebar(this, container);
1018 * Creates and returns a new footer.
1020 EditorUi.prototype.createFooter = function() {
1021 return this.createDiv('geFooter');
1025 * Creates the actual toolbar for the toolbar container.
1027 EditorUi.prototype.createDiv = function(classname) {
1028 var elt = document.createElement('div');
1029 elt.className = classname;
1035 * Creates the custom header.
1037 EditorUi.prototype.createHeader = function(classname) {
1038 var elt = document.createElement(classname);
1043 * Creates the custom header.
1045 EditorUi.prototype.createForm = function(classname) {
1046 var elt = document.createElement('form');
1047 elt.className = classname;
1053 * Updates the states of the given undo/redo items.
1055 EditorUi.prototype.addSplitHandler = function(elt, horizontal, dx, onChange) {
1059 function getValue() {
1060 return parseInt(((horizontal) ? elt.style.left : elt.style.bottom));
1063 function moveHandler(evt) {
1064 if (start != null) {
1065 var pt = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
1066 onChange(Math.max(0, initial + ((horizontal) ? (pt.x - start.x) : (start.y -
1068 mxEvent.consume(evt);
1072 function dropHandler(evt) {
1078 mxEvent.addGestureListeners(elt, function(evt) {
1079 start = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
1080 initial = getValue();
1081 mxEvent.consume(evt);
1084 mxEvent.addListener(document, 'mousemove', moveHandler);
1085 mxEvent.addListener(document, 'touchmove', moveHandler);
1086 mxEvent.addListener(document, 'mouseup', dropHandler);
1087 mxEvent.addListener(document, 'touchend', dropHandler);
1091 * Displays a print dialog.
1093 EditorUi.prototype.showDialog = function(elt, w, h, modal, closable, onClose) {
1095 this.dialog = new Dialog(this, elt, w, (mxClient.IS_VML) ? h - 12 : h,
1101 * Displays a print dialog.
1103 EditorUi.prototype.hideDialog = function() {
1104 if (this.dialog != null) {
1105 this.dialog.close();
1107 this.editor.graph.container.focus();
1112 * Adds the label menu items to the given menu and parent.
1114 EditorUi.prototype.saveFile = function(forceDialog) {
1115 if (!forceDialog && this.editor.filename != null) {
1116 this.save(this.editor.getOrCreateFilename());
1118 this.showDialog(new SaveDialog(this).container, 300, 100, true, true);
1123 * Adds the label menu items to the given menu and parent.
1125 EditorUi.prototype.publishFile = function(forceDialog) {
1126 if (!forceDialog && this.editor.filename != null) {
1127 this.publish(this.editor.getOrCreateFilename());
1129 //this.showDialog(new SaveDialog(this).container, 300, 100, true, true);
1134 * Executes the given layout.
1136 EditorUi.prototype.executeLayout = function(layout, animate, ignoreChildCount) {
1137 var graph = this.editor.graph;
1138 var cell = graph.getSelectionCell();
1140 // Allow global overridding of animation
1141 animate = this.animate != null ? this.animate : animate;
1143 graph.getModel().beginUpdate();
1145 layout.execute(graph.getDefaultParent(), cell);
1149 // Animates the changes in the graph model except
1150 // for Camino, where animation is too slow
1151 if (animate && navigator.userAgent.indexOf('Camino') < 0) {
1152 // New API for animating graph layout results asynchronously
1153 var morph = new mxMorphing(graph);
1154 morph.addListener(mxEvent.DONE, mxUtils.bind(this, function() {
1155 graph.getModel().endUpdate();
1158 morph.startAnimation();
1160 graph.getModel().endUpdate();
1166 * Creates the keyboard event handler for the current graph and history.
1168 EditorUi.prototype.createKeyHandler = function(editor) {
1169 var graph = this.editor.graph;
1170 var keyHandler = new mxKeyHandler(graph);
1172 // Routes command-key to control-key on Mac
1173 keyHandler.isControlDown = function(evt) {
1174 return mxEvent.isControlDown(evt) || (mxClient.IS_MAC && evt.metaKey);
1177 // Helper function to move cells with the cursor keys
1178 function nudge(keyCode) {
1179 if (!graph.isSelectionEmpty()) {
1183 if (keyCode == 37) {
1185 } else if (keyCode == 38) {
1187 } else if (keyCode == 39) {
1189 } else if (keyCode == 40) {
1193 graph.moveCells(graph.getSelectionCells(), dx, dy);
1194 graph.scrollCellToVisible(graph.getSelectionCell());
1198 // Binds keystrokes to actions
1199 var bindAction = mxUtils.bind(this, function(code, control, key, shift) {
1200 var action = this.actions.get(key);
1202 if (action != null) {
1203 var f = function() {
1204 if (action.enabled) {
1211 keyHandler.bindControlShiftKey(code, f);
1213 keyHandler.bindControlKey(code, f);
1217 keyHandler.bindShiftKey(code, f);
1219 keyHandler.bindKey(code, f);
1226 var keyHandleEscape = keyHandler.escape;
1227 keyHandler.escape = function(evt) {
1229 keyHandleEscape.apply(this, arguments);
1232 // Ignores enter keystroke. Remove this line if you want the
1233 // enter keystroke to stop editing.
1234 keyHandler.enter = function() {};
1235 keyHandler.bindKey(8, function() {
1236 graph.foldCells(true);
1238 keyHandler.bindKey(13, function() {
1239 graph.foldCells(false);
1241 keyHandler.bindKey(33, function() {
1244 keyHandler.bindKey(34, function() {
1247 keyHandler.bindKey(36, function() {
1250 keyHandler.bindKey(35, function() {
1253 keyHandler.bindKey(37, function() {
1256 keyHandler.bindKey(38, function() {
1259 keyHandler.bindKey(39, function() {
1262 keyHandler.bindKey(40, function() {
1265 keyHandler.bindKey(113, function() {
1266 graph.startEditingAtCell();
1268 bindAction(46, false, 'delete'); // Delete
1269 bindAction(82, true, 'tilt'); // Ctrl+R
1270 bindAction(83, true, 'save'); // Ctrl+S
1271 bindAction(83, true, 'saveAs', true); // Ctrl+Shift+S
1272 bindAction(107, false, 'zoomIn'); // Add
1273 bindAction(109, false, 'zoomOut'); // Subtract
1274 bindAction(65, true, 'selectAll'); // Ctrl+A
1275 bindAction(86, true, 'selectVertices', true); // Ctrl+Shift+V
1276 bindAction(69, true, 'selectEdges', true); // Ctrl+Shift+E
1277 bindAction(69, true, 'export'); // Ctrl+Shift+E
1278 bindAction(66, true, 'toBack'); // Ctrl+B
1279 bindAction(70, true, 'toFront'); // Ctrl+F
1280 bindAction(68, true, 'duplicate'); // Ctrl+D
1281 bindAction(90, true, 'undo'); // Ctrl+Z
1282 bindAction(89, true, 'redo'); // Ctrl+Y
1283 bindAction(88, true, 'cut'); // Ctrl+X
1284 bindAction(67, true, 'copy'); // Ctrl+C
1285 bindAction(81, true, 'connect'); // Ctrl+Q
1286 bindAction(86, true, 'paste'); // Ctrl+V
1287 bindAction(71, true, 'group'); // Ctrl+G
1288 bindAction(71, true, 'grid', true); // Ctrl+Shift+G
1289 bindAction(85, true, 'ungroup'); // Ctrl+U
1290 bindAction(112, false, 'about'); // F1
1291 bindAction(80, true, 'publish', true); // Ctrl+Shift+P
1298 * Creates a new toolbar for the given container.
1300 EditorUi.prototype.createMultiplicities = function(graph, cells, source,
1302 for (var i = 0; i < cells.length; i++) {
1303 graph.push(new mxMultiplicity(true, cells[i], null, null, source[i][0],
1304 source[i][1], null, mxResources.get(cells[i]) + ' must have ' + source[i][0] + ' outgoing edge.', null));
1305 graph.push(new mxMultiplicity(false, cells[i], null, null, target[i][0],
1306 target[i][1], null, mxResources.get(cells[i]) + ' must have ' + target[i][0] + ' incoming edge.', null));