UNPKG

mxgraph-map-fix

Version:

mxGraph is a fully client side JavaScript diagramming library that uses SVG and HTML for rendering.

1,822 lines (1,556 loc) 111 kB
/** * Copyright (c) 2006-2012, JGraph Ltd */ /** * Constructs a new graph editor */ EditorUi = function(editor, container, lightbox) { mxEventSource.call(this); this.destroyFunctions = []; this.editor = editor || new Editor(); this.container = container || document.body; var graph = this.editor.graph; graph.lightbox = lightbox; // Pre-fetches submenu image or replaces with embedded image if supported if (mxClient.IS_SVG) { mxPopupMenu.prototype.submenuImage = 'data:image/gif;base64,R0lGODlhCQAJAIAAAP///zMzMyH5BAEAAAAALAAAAAAJAAkAAAIPhI8WebHsHopSOVgb26AAADs='; } else { new Image().src = mxPopupMenu.prototype.submenuImage; } // Pre-fetches connect image if (!mxClient.IS_SVG && mxConnectionHandler.prototype.connectImage != null) { new Image().src = mxConnectionHandler.prototype.connectImage.src; } // Disables graph and forced panning in chromeless mode if (this.editor.chromeless && !this.editor.editable) { this.footerHeight = 0; graph.isEnabled = function() { return false; }; graph.panningHandler.isForcePanningEvent = function(me) { return !mxEvent.isPopupTrigger(me.getEvent()); }; } // Creates the user interface this.actions = new Actions(this); this.menus = this.createMenus(); this.createDivs(); this.createUi(); this.refresh(); // Disables HTML and text selection var textEditing = mxUtils.bind(this, function(evt) { if (evt == null) { evt = window.event; } return (this.isSelectionAllowed(evt) || graph.isEditing()); }); // Disables text selection while not editing and no dialog visible if (this.container == document.body) { this.menubarContainer.onselectstart = textEditing; this.menubarContainer.onmousedown = textEditing; this.toolbarContainer.onselectstart = textEditing; this.toolbarContainer.onmousedown = textEditing; this.diagramContainer.onselectstart = textEditing; this.diagramContainer.onmousedown = textEditing; this.sidebarContainer.onselectstart = textEditing; this.sidebarContainer.onmousedown = textEditing; this.formatContainer.onselectstart = textEditing; this.formatContainer.onmousedown = textEditing; this.footerContainer.onselectstart = textEditing; this.footerContainer.onmousedown = textEditing; if (this.tabContainer != null) { // Mouse down is needed for drag and drop this.tabContainer.onselectstart = textEditing; } } // And uses built-in context menu while editing if (!this.editor.chromeless || this.editor.editable) { // Allows context menu for links in hints var linkHandler = function(evt) { var source = mxEvent.getSource(evt); if (source.nodeName == 'A') { while (source != null) { if (source.className == 'geHint') { return true; } source = source.parentNode; } } return textEditing(evt); }; if (mxClient.IS_IE && (typeof(document.documentMode) === 'undefined' || document.documentMode < 9)) { mxEvent.addListener(this.diagramContainer, 'contextmenu', linkHandler); } else { // Allows browser context menu outside of diagram and sidebar this.diagramContainer.oncontextmenu = linkHandler; } } else { graph.panningHandler.usePopupTrigger = false; } // Contains the main graph instance inside the given panel graph.init(this.diagramContainer); // Creates hover icons this.hoverIcons = this.createHoverIcons(); // Adds tooltip when mouse is over scrollbars to show space-drag panning option mxEvent.addListener(this.diagramContainer, 'mousemove', mxUtils.bind(this, function(evt) { var off = mxUtils.getOffset(this.diagramContainer); if (mxEvent.getClientX(evt) - off.x - this.diagramContainer.clientWidth > 0 || mxEvent.getClientY(evt) - off.y - this.diagramContainer.clientHeight > 0) { this.diagramContainer.setAttribute('title', mxResources.get('panTooltip')); } else { this.diagramContainer.removeAttribute('title'); } })); // Escape key hides dialogs, adds space+drag panning var spaceKeyPressed = false; // Overrides hovericons to disable while space key is pressed var hoverIconsIsResetEvent = this.hoverIcons.isResetEvent; this.hoverIcons.isResetEvent = function(evt, allowShift) { return spaceKeyPressed || hoverIconsIsResetEvent.apply(this, arguments); }; this.keydownHandler = mxUtils.bind(this, function(evt) { if (evt.which == 32 /* Space */) { spaceKeyPressed = true; this.hoverIcons.reset(); graph.container.style.cursor = 'move'; // Disables scroll after space keystroke with scrollbars if (!graph.isEditing() && mxEvent.getSource(evt) == graph.container) { mxEvent.consume(evt); } } else if (!mxEvent.isConsumed(evt) && evt.keyCode == 27 /* Escape */) { this.hideDialog(); } }); mxEvent.addListener(document, 'keydown', this.keydownHandler); this.keyupHandler = mxUtils.bind(this, function(evt) { graph.container.style.cursor = ''; spaceKeyPressed = false; }); mxEvent.addListener(document, 'keyup', this.keyupHandler); // Forces panning for middle and right mouse buttons var panningHandlerIsForcePanningEvent = graph.panningHandler.isForcePanningEvent; graph.panningHandler.isForcePanningEvent = function(me) { // Ctrl+left button is reported as right button in FF on Mac return panningHandlerIsForcePanningEvent.apply(this, arguments) || spaceKeyPressed || (mxEvent.isMouseEvent(me.getEvent()) && (this.usePopupTrigger || !mxEvent.isPopupTrigger(me.getEvent())) && ((!mxEvent.isControlDown(me.getEvent()) && mxEvent.isRightMouseButton(me.getEvent())) || mxEvent.isMiddleMouseButton(me.getEvent()))); }; // Ctrl/Cmd+Enter applies editing value except in Safari where Ctrl+Enter creates // a new line (while Enter creates a new paragraph and Shift+Enter stops) var cellEditorIsStopEditingEvent = graph.cellEditor.isStopEditingEvent; graph.cellEditor.isStopEditingEvent = function(evt) { return cellEditorIsStopEditingEvent.apply(this, arguments) || (evt.keyCode == 13 && ((!mxClient.IS_SF && mxEvent.isControlDown(evt)) || (mxClient.IS_MAC && mxEvent.isMetaDown(evt)) || (mxClient.IS_SF && mxEvent.isShiftDown(evt)))); }; // Switches toolbar for text editing var textMode = false; var fontMenu = null; var sizeMenu = null; var nodes = null; var updateToolbar = mxUtils.bind(this, function() { if (this.toolbar != null && textMode != graph.cellEditor.isContentEditing()) { var node = this.toolbar.container.firstChild; var newNodes = []; while (node != null) { var tmp = node.nextSibling; if (mxUtils.indexOf(this.toolbar.staticElements, node) < 0) { node.parentNode.removeChild(node); newNodes.push(node); } node = tmp; } // Saves references to special items var tmp1 = this.toolbar.fontMenu; var tmp2 = this.toolbar.sizeMenu; if (nodes == null) { this.toolbar.createTextToolbar(); } else { for (var i = 0; i < nodes.length; i++) { this.toolbar.container.appendChild(nodes[i]); } // Restores references to special items this.toolbar.fontMenu = fontMenu; this.toolbar.sizeMenu = sizeMenu; } textMode = graph.cellEditor.isContentEditing(); fontMenu = tmp1; sizeMenu = tmp2; nodes = newNodes; } }); var ui = this; // Overrides cell editor to update toolbar var cellEditorStartEditing = graph.cellEditor.startEditing; graph.cellEditor.startEditing = function() { cellEditorStartEditing.apply(this, arguments); updateToolbar(); if (graph.cellEditor.isContentEditing()) { var updating = false; var updateCssHandler = function() { if (!updating) { updating = true; window.setTimeout(function() { var selectedElement = graph.getSelectedElement(); var node = selectedElement; while (node != null && node.nodeType != mxConstants.NODETYPE_ELEMENT) { node = node.parentNode; } if (node != null) { var css = mxUtils.getCurrentStyle(node); if (css != null && ui.toolbar != null) { // Strips leading and trailing quotes var ff = css.fontFamily; if (ff.charAt(0) == '\'') { ff = ff.substring(1); } if (ff.charAt(ff.length - 1) == '\'') { ff = ff.substring(0, ff.length - 1); } ui.toolbar.setFontName(ff); ui.toolbar.setFontSize(parseInt(css.fontSize)); } } updating = false; }, 0); } }; mxEvent.addListener(graph.cellEditor.textarea, 'input', updateCssHandler) mxEvent.addListener(graph.cellEditor.textarea, 'touchend', updateCssHandler); mxEvent.addListener(graph.cellEditor.textarea, 'mouseup', updateCssHandler); mxEvent.addListener(graph.cellEditor.textarea, 'keyup', updateCssHandler); updateCssHandler(); } }; var cellEditorStopEditing = graph.cellEditor.stopEditing; graph.cellEditor.stopEditing = function(cell, trigger) { cellEditorStopEditing.apply(this, arguments); updateToolbar(); }; // Enables scrollbars and sets cursor style for the container graph.container.setAttribute('tabindex', '0'); graph.container.style.cursor = 'default'; // Workaround for page scroll if embedded via iframe if (window.self === window.top && graph.container.parentNode != null) { try { graph.container.focus(); } catch (e) { // ignores error in old versions of IE } } // Keeps graph container focused on mouse down var graphFireMouseEvent = graph.fireMouseEvent; graph.fireMouseEvent = function(evtName, me, sender) { if (evtName == mxEvent.MOUSE_DOWN) { this.container.focus(); } graphFireMouseEvent.apply(this, arguments); }; // Configures automatic expand on mouseover graph.popupMenuHandler.autoExpand = true; // Installs context menu if (this.menus != null) { graph.popupMenuHandler.factoryMethod = mxUtils.bind(this, function(menu, cell, evt) { this.menus.createPopupMenu(menu, cell, evt); }); } // Hides context menu mxEvent.addGestureListeners(document, mxUtils.bind(this, function(evt) { graph.popupMenuHandler.hideMenu(); })); // Create handler for key events this.keyHandler = this.createKeyHandler(editor); // Getter for key handler this.getKeyHandler = function() { return keyHandler; }; // Stores the current style and assigns it to new cells var styles = ['rounded', 'shadow', 'glass', 'dashed', 'dashPattern', 'comic', 'labelBackgroundColor']; var connectStyles = ['shape', 'edgeStyle', 'curved', 'rounded', 'elbow', 'comic', 'jumpStyle', 'jumpSize']; // Note: Everything that is not in styles is ignored (styles is augmented below) this.setDefaultStyle = function(cell) { var state = graph.view.getState(cell); if (state != null) { // Ignores default styles var clone = cell.clone(); clone.style = '' var defaultStyle = graph.getCellStyle(clone); var values = []; var keys = []; for (var key in state.style) { if (defaultStyle[key] != state.style[key]) { values.push(state.style[key]); keys.push(key); } } // Handles special case for value "none" var cellStyle = graph.getModel().getStyle(state.cell); var tokens = (cellStyle != null) ? cellStyle.split(';') : []; for (var i = 0; i < tokens.length; i++) { var tmp = tokens[i]; var pos = tmp.indexOf('='); if (pos >= 0) { var key = tmp.substring(0, pos); var value = tmp.substring(pos + 1); if (defaultStyle[key] != null && value == 'none') { values.push(value); keys.push(key); } } } // Resets current style if (graph.getModel().isEdge(state.cell)) { graph.currentEdgeStyle = {}; } else { graph.currentVertexStyle = {} } this.fireEvent(new mxEventObject('styleChanged', 'keys', keys, 'values', values, 'cells', [state.cell])); } }; this.clearDefaultStyle = function() { graph.currentEdgeStyle = mxUtils.clone(graph.defaultEdgeStyle); graph.currentVertexStyle = mxUtils.clone(graph.defaultVertexStyle); // Updates UI this.fireEvent(new mxEventObject('styleChanged', 'keys', [], 'values', [], 'cells', [])); }; // Keys that should be ignored if the cell has a value (known: new default for all cells is html=1 so // for the html key this effecticely only works for edges inserted via the connection handler) var valueStyles = ['fontFamily', 'fontSize', 'fontColor']; // Keys that always update the current edge style regardless of selection var alwaysEdgeStyles = ['edgeStyle', 'startArrow', 'startFill', 'startSize', 'endArrow', 'endFill', 'endSize', 'jettySize', 'orthogonalLoop']; // Keys that are ignored together (if one appears all are ignored) var keyGroups = [['startArrow', 'startFill', 'startSize', 'endArrow', 'endFill', 'endSize', 'jettySize', 'orthogonalLoop'], ['strokeColor', 'strokeWidth'], ['fillColor', 'gradientColor'], valueStyles, ['align'], ['html']]; // Adds all keys used above to the styles array for (var i = 0; i < keyGroups.length; i++) { for (var j = 0; j < keyGroups[i].length; j++) { styles.push(keyGroups[i][j]); } } for (var i = 0; i < connectStyles.length; i++) { if (mxUtils.indexOf(styles, connectStyles[i]) < 0) { styles.push(connectStyles[i]); } } // Implements a global current style for edges and vertices that is applied to new cells var insertHandler = function(cells, asText) { graph.getModel().beginUpdate(); try { // Applies only basic text styles if (asText) { var edge = graph.getModel().isEdge(cell); var current = (edge) ? graph.currentEdgeStyle : graph.currentVertexStyle; var textStyles = ['fontSize', 'fontFamily', 'fontColor']; for (var j = 0; j < textStyles.length; j++) { var value = current[textStyles[j]]; if (value != null) { graph.setCellStyles(textStyles[j], value, cells); } } } else { for (var i = 0; i < cells.length; i++) { var cell = cells[i]; // Removes styles defined in the cell style from the styles to be applied var cellStyle = graph.getModel().getStyle(cell); var tokens = (cellStyle != null) ? cellStyle.split(';') : []; var appliedStyles = styles.slice(); for (var j = 0; j < tokens.length; j++) { var tmp = tokens[j]; var pos = tmp.indexOf('='); if (pos >= 0) { var key = tmp.substring(0, pos); var index = mxUtils.indexOf(appliedStyles, key); if (index >= 0) { appliedStyles.splice(index, 1); } // Handles special cases where one defined style ignores other styles for (var k = 0; k < keyGroups.length; k++) { var group = keyGroups[k]; if (mxUtils.indexOf(group, key) >= 0) { for (var l = 0; l < group.length; l++) { var index2 = mxUtils.indexOf(appliedStyles, group[l]); if (index2 >= 0) { appliedStyles.splice(index2, 1); } } } } } } // Applies the current style to the cell var edge = graph.getModel().isEdge(cell); var current = (edge) ? graph.currentEdgeStyle : graph.currentVertexStyle; for (var j = 0; j < appliedStyles.length; j++) { var key = appliedStyles[j]; var styleValue = current[key]; if (styleValue != null && (key != 'shape' || edge)) { // Special case: Connect styles are not applied here but in the connection handler if (!edge || mxUtils.indexOf(connectStyles, key) < 0) { graph.setCellStyles(key, styleValue, [cell]); } } } } } } finally { graph.getModel().endUpdate(); } }; graph.addListener('cellsInserted', function(sender, evt) { insertHandler(evt.getProperty('cells')); }); graph.addListener('textInserted', function(sender, evt) { insertHandler(evt.getProperty('cells'), true); }); graph.connectionHandler.addListener(mxEvent.CONNECT, function(sender, evt) { var cells = [evt.getProperty('cell')]; if (evt.getProperty('terminalInserted')) { cells.push(evt.getProperty('terminal')); } insertHandler(cells); }); this.addListener('styleChanged', mxUtils.bind(this, function(sender, evt) { // Checks if edges and/or vertices were modified var cells = evt.getProperty('cells'); var vertex = false; var edge = false; if (cells.length > 0) { for (var i = 0; i < cells.length; i++) { vertex = graph.getModel().isVertex(cells[i]) || vertex; edge = graph.getModel().isEdge(cells[i]) || edge; if (edge && vertex) { break; } } } else { vertex = true; edge = true; } var keys = evt.getProperty('keys'); var values = evt.getProperty('values'); for (var i = 0; i < keys.length; i++) { var common = mxUtils.indexOf(valueStyles, keys[i]) >= 0; // Ignores transparent stroke colors if (keys[i] != 'strokeColor' || (values[i] != null && values[i] != 'none')) { // Special case: Edge style and shape if (mxUtils.indexOf(connectStyles, keys[i]) >= 0) { if (edge || mxUtils.indexOf(alwaysEdgeStyles, keys[i]) >= 0) { if (values[i] == null) { delete graph.currentEdgeStyle[keys[i]]; } else { graph.currentEdgeStyle[keys[i]] = values[i]; } } // Uses style for vertex if defined in styles else if (vertex && mxUtils.indexOf(styles, keys[i]) >= 0) { if (values[i] == null) { delete graph.currentVertexStyle[keys[i]]; } else { graph.currentVertexStyle[keys[i]] = values[i]; } } } else if (mxUtils.indexOf(styles, keys[i]) >= 0) { if (vertex || common) { if (values[i] == null) { delete graph.currentVertexStyle[keys[i]]; } else { graph.currentVertexStyle[keys[i]] = values[i]; } } if (edge || common || mxUtils.indexOf(alwaysEdgeStyles, keys[i]) >= 0) { if (values[i] == null) { delete graph.currentEdgeStyle[keys[i]]; } else { graph.currentEdgeStyle[keys[i]] = values[i]; } } } } } if (this.toolbar != null) { this.toolbar.setFontName(graph.currentVertexStyle['fontFamily'] || Menus.prototype.defaultFont); this.toolbar.setFontSize(graph.currentVertexStyle['fontSize'] || Menus.prototype.defaultFontSize); if (this.toolbar.edgeStyleMenu != null) { // Updates toolbar icon for edge style var edgeStyleDiv = this.toolbar.edgeStyleMenu.getElementsByTagName('div')[0]; if (graph.currentEdgeStyle['edgeStyle'] == 'orthogonalEdgeStyle' && graph.currentEdgeStyle['curved'] == '1') { edgeStyleDiv.className = 'geSprite geSprite-curved'; } else if (graph.currentEdgeStyle['edgeStyle'] == 'straight' || graph.currentEdgeStyle['edgeStyle'] == 'none' || graph.currentEdgeStyle['edgeStyle'] == null) { edgeStyleDiv.className = 'geSprite geSprite-straight'; } else if (graph.currentEdgeStyle['edgeStyle'] == 'entityRelationEdgeStyle') { edgeStyleDiv.className = 'geSprite geSprite-entity'; } else if (graph.currentEdgeStyle['edgeStyle'] == 'elbowEdgeStyle') { edgeStyleDiv.className = 'geSprite geSprite-' + ((graph.currentEdgeStyle['elbow'] == 'vertical') ? 'verticalelbow' : 'horizontalelbow'); } else if (graph.currentEdgeStyle['edgeStyle'] == 'isometricEdgeStyle') { edgeStyleDiv.className = 'geSprite geSprite-' + ((graph.currentEdgeStyle['elbow'] == 'vertical') ? 'verticalisometric' : 'horizontalisometric'); } else { edgeStyleDiv.className = 'geSprite geSprite-orthogonal'; } } if (this.toolbar.edgeShapeMenu != null) { // Updates icon for edge shape var edgeShapeDiv = this.toolbar.edgeShapeMenu.getElementsByTagName('div')[0]; if (graph.currentEdgeStyle['shape'] == 'link') { edgeShapeDiv.className = 'geSprite geSprite-linkedge'; } else if (graph.currentEdgeStyle['shape'] == 'flexArrow') { edgeShapeDiv.className = 'geSprite geSprite-arrow'; } else if (graph.currentEdgeStyle['shape'] == 'arrow') { edgeShapeDiv.className = 'geSprite geSprite-simplearrow'; } else { edgeShapeDiv.className = 'geSprite geSprite-connection'; } } // Updates icon for optinal line start shape if (this.toolbar.lineStartMenu != null) { var lineStartDiv = this.toolbar.lineStartMenu.getElementsByTagName('div')[0]; lineStartDiv.className = this.getCssClassForMarker('start', graph.currentEdgeStyle['shape'], graph.currentEdgeStyle[mxConstants.STYLE_STARTARROW], mxUtils.getValue(graph.currentEdgeStyle, 'startFill', '1')); } // Updates icon for optinal line end shape if (this.toolbar.lineEndMenu != null) { var lineEndDiv = this.toolbar.lineEndMenu.getElementsByTagName('div')[0]; lineEndDiv.className = this.getCssClassForMarker('end', graph.currentEdgeStyle['shape'], graph.currentEdgeStyle[mxConstants.STYLE_ENDARROW], mxUtils.getValue(graph.currentEdgeStyle, 'endFill', '1')); } } })); // Update font size and font family labels if (this.toolbar != null) { var update = mxUtils.bind(this, function() { var ff = graph.currentVertexStyle['fontFamily'] || 'Helvetica'; var fs = String(graph.currentVertexStyle['fontSize'] || '12'); var state = graph.getView().getState(graph.getSelectionCell()); if (state != null) { ff = state.style[mxConstants.STYLE_FONTFAMILY] || ff; fs = state.style[mxConstants.STYLE_FONTSIZE] || fs; if (ff.length > 10) { ff = ff.substring(0, 8) + '...'; } } this.toolbar.setFontName(ff); this.toolbar.setFontSize(fs); }); graph.getSelectionModel().addListener(mxEvent.CHANGE, update); graph.getModel().addListener(mxEvent.CHANGE, update); } // Makes sure the current layer is visible when cells are added graph.addListener(mxEvent.CELLS_ADDED, function(sender, evt) { var cells = evt.getProperty('cells'); var parent = evt.getProperty('parent'); if (graph.getModel().isLayer(parent) && !graph.isCellVisible(parent) && cells != null && cells.length > 0) { graph.getModel().setVisible(parent, true); } }); // Global handler to hide the current menu this.gestureHandler = mxUtils.bind(this, function(evt) { if (this.currentMenu != null && mxEvent.getSource(evt) != this.currentMenu.div) { this.hideCurrentMenu(); } }); mxEvent.addGestureListeners(document, this.gestureHandler); // Updates the editor UI after the window has been resized or the orientation changes // Timeout is workaround for old IE versions which have a delay for DOM client sizes. // Should not use delay > 0 to avoid handle multiple repaints during window resize this.resizeHandler = mxUtils.bind(this, function() { window.setTimeout(mxUtils.bind(this, function() { this.refresh(); }), 0); }); mxEvent.addListener(window, 'resize', this.resizeHandler); this.orientationChangeHandler = mxUtils.bind(this, function() { this.refresh(); }); mxEvent.addListener(window, 'orientationchange', this.orientationChangeHandler); // Workaround for bug on iOS see // http://stackoverflow.com/questions/19012135/ios-7-ipad-safari-landscape-innerheight-outerheight-layout-issue if (mxClient.IS_IOS && !window.navigator.standalone) { this.scrollHandler = mxUtils.bind(this, function() { window.scrollTo(0, 0); }); mxEvent.addListener(window, 'scroll', this.scrollHandler); } /** * Sets the initial scrollbar locations after a file was loaded. */ this.editor.addListener('resetGraphView', mxUtils.bind(this, function() { this.resetScrollbars(); })); /** * Repaints the grid. */ this.addListener('gridEnabledChanged', mxUtils.bind(this, function() { graph.view.validateBackground(); })); this.addListener('backgroundColorChanged', mxUtils.bind(this, function() { graph.view.validateBackground(); })); /** * Repaints the grid. */ graph.addListener('gridSizeChanged', mxUtils.bind(this, function() { if (graph.isGridEnabled()) { graph.view.validateBackground(); } })); // Resets UI, updates action and menu states this.editor.resetGraph(); this.init(); this.open(); }; // Extends mxEventSource mxUtils.extend(EditorUi, mxEventSource); /** * Global config that specifies if the compact UI elements should be used. */ EditorUi.compactUi = true; /** * Specifies the size of the split bar. */ EditorUi.prototype.splitSize = (mxClient.IS_TOUCH || mxClient.IS_POINTER) ? 12 : 8; /** * Specifies the height of the menubar. Default is 34. */ EditorUi.prototype.menubarHeight = 30; /** * Specifies the width of the format panel should be enabled. Default is true. */ EditorUi.prototype.formatEnabled = true; /** * Specifies the width of the format panel. Default is 240. */ EditorUi.prototype.formatWidth = 240; /** * Specifies the height of the toolbar. Default is 36. */ EditorUi.prototype.toolbarHeight = 34; /** * Specifies the height of the footer. Default is 28. */ EditorUi.prototype.footerHeight = 28; /** * Specifies the height of the optional sidebarFooterContainer. Default is 34. */ EditorUi.prototype.sidebarFooterHeight = 34; /** * Specifies the link for the edit button in chromeless mode. */ EditorUi.prototype.editButtonLink = null; /** * Specifies the position of the horizontal split bar. Default is 208 or 118 for * screen widths <= 640px. */ EditorUi.prototype.hsplitPosition = (screen.width <= 640) ? 118 : 208; /** * Specifies if animations are allowed in <executeLayout>. Default is true. */ EditorUi.prototype.allowAnimation = true; /** * Specifies if animations are allowed in <executeLayout>. Default is true. */ EditorUi.prototype.lightboxMaxFitScale = 2; /** * Specifies if animations are allowed in <executeLayout>. Default is true. */ EditorUi.prototype.lightboxVerticalDivider = 4; /** * Installs the listeners to update the action states. */ EditorUi.prototype.init = function() { /** * Keypress starts immediate editing on selection cell */ var graph = this.editor.graph; mxEvent.addListener(graph.container, 'keydown', mxUtils.bind(this, function(evt) { this.onKeyDown(evt); })); mxEvent.addListener(graph.container, 'keypress', mxUtils.bind(this, function(evt) { this.onKeyPress(evt); })); // Updates action states this.addUndoListener(); this.addBeforeUnloadListener(); graph.getSelectionModel().addListener(mxEvent.CHANGE, mxUtils.bind(this, function() { this.updateActionStates(); })); graph.getModel().addListener(mxEvent.CHANGE, mxUtils.bind(this, function() { this.updateActionStates(); })); // Changes action states after change of default parent var graphSetDefaultParent = graph.setDefaultParent; var ui = this; this.editor.graph.setDefaultParent = function() { graphSetDefaultParent.apply(this, arguments); ui.updateActionStates(); }; // Hack to make editLink available in vertex handler graph.editLink = ui.actions.get('editLink').funct; this.updateActionStates(); this.initClipboard(); this.initCanvas(); if (this.format != null) { this.format.init(); } }; /** * Returns true if the given event should start editing. This implementation returns true. */ EditorUi.prototype.onKeyDown = function(evt) { var graph = this.editor.graph; // Tab selects next cell if (evt.which == 9 && graph.isEnabled() && !mxEvent.isAltDown(evt)) { if (graph.isEditing()) { graph.stopEditing(false); } else { graph.selectCell(!mxEvent.isShiftDown(evt)); } mxEvent.consume(evt); } }; /** * Returns true if the given event should start editing. This implementation returns true. */ EditorUi.prototype.onKeyPress = function(evt) { var graph = this.editor.graph; // KNOWN: Focus does not work if label is empty in quirks mode if (this.isImmediateEditingEvent(evt) && !graph.isEditing() && !graph.isSelectionEmpty() && evt.which !== 0 && !mxEvent.isAltDown(evt) && !mxEvent.isControlDown(evt) && !mxEvent.isMetaDown(evt)) { graph.escape(); graph.startEditing(); // Workaround for FF where char is lost if cursor is placed before char if (mxClient.IS_FF) { var ce = graph.cellEditor; ce.textarea.innerHTML = String.fromCharCode(evt.which); // Moves cursor to end of textarea var range = document.createRange(); range.selectNodeContents(ce.textarea); range.collapse(false); var sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range); } } }; /** * Returns true if the given event should start editing. This implementation returns true. */ EditorUi.prototype.isImmediateEditingEvent = function(evt) { return true; }; /** * Private helper method. */ EditorUi.prototype.getCssClassForMarker = function(prefix, shape, marker, fill) { var result = ''; if (shape == 'flexArrow') { result = (marker != null && marker != mxConstants.NONE) ? 'geSprite geSprite-' + prefix + 'blocktrans' : 'geSprite geSprite-noarrow'; } else { if (marker == mxConstants.ARROW_CLASSIC) { result = (fill == '1') ? 'geSprite geSprite-' + prefix + 'classic' : 'geSprite geSprite-' + prefix + 'classictrans'; } else if (marker == mxConstants.ARROW_CLASSIC_THIN) { result = (fill == '1') ? 'geSprite geSprite-' + prefix + 'classicthin' : 'geSprite geSprite-' + prefix + 'classicthintrans'; } else if (marker == mxConstants.ARROW_OPEN) { result = 'geSprite geSprite-' + prefix + 'open'; } else if (marker == mxConstants.ARROW_OPEN_THIN) { result = 'geSprite geSprite-' + prefix + 'openthin'; } else if (marker == mxConstants.ARROW_BLOCK) { result = (fill == '1') ? 'geSprite geSprite-' + prefix + 'block' : 'geSprite geSprite-' + prefix + 'blocktrans'; } else if (marker == mxConstants.ARROW_BLOCK_THIN) { result = (fill == '1') ? 'geSprite geSprite-' + prefix + 'blockthin' : 'geSprite geSprite-' + prefix + 'blockthintrans'; } else if (marker == mxConstants.ARROW_OVAL) { result = (fill == '1') ? 'geSprite geSprite-' + prefix + 'oval' : 'geSprite geSprite-' + prefix + 'ovaltrans'; } else if (marker == mxConstants.ARROW_DIAMOND) { result = (fill == '1') ? 'geSprite geSprite-' + prefix + 'diamond' : 'geSprite geSprite-' + prefix + 'diamondtrans'; } else if (marker == mxConstants.ARROW_DIAMOND_THIN) { result = (fill == '1') ? 'geSprite geSprite-' + prefix + 'thindiamond' : 'geSprite geSprite-' + prefix + 'thindiamondtrans'; } else if (marker == 'openAsync') { result = 'geSprite geSprite-' + prefix + 'openasync'; } else if (marker == 'dash') { result = 'geSprite geSprite-' + prefix + 'dash'; } else if (marker == 'cross') { result = 'geSprite geSprite-' + prefix + 'cross'; } else if (marker == 'async') { result = (fill == '1') ? 'geSprite geSprite-' + prefix + 'async' : 'geSprite geSprite-' + prefix + 'asynctrans'; } else if (marker == 'circle' || marker == 'circlePlus') { result = (fill == '1' || marker == 'circle') ? 'geSprite geSprite-' + prefix + 'circle' : 'geSprite geSprite-' + prefix + 'circleplus'; } else if (marker == 'ERone') { result = 'geSprite geSprite-' + prefix + 'erone'; } else if (marker == 'ERmandOne') { result = 'geSprite geSprite-' + prefix + 'eronetoone'; } else if (marker == 'ERmany') { result = 'geSprite geSprite-' + prefix + 'ermany'; } else if (marker == 'ERoneToMany') { result = 'geSprite geSprite-' + prefix + 'eronetomany'; } else if (marker == 'ERzeroToOne') { result = 'geSprite geSprite-' + prefix + 'eroneopt'; } else if (marker == 'ERzeroToMany') { result = 'geSprite geSprite-' + prefix + 'ermanyopt'; } else { result = 'geSprite geSprite-noarrow'; } } return result; }; /** * Overridden in Menus.js */ EditorUi.prototype.createMenus = function() { return null; }; /** * Hook for allowing selection and context menu for certain events. */ EditorUi.prototype.updatePasteActionStates = function() { var graph = this.editor.graph; var paste = this.actions.get('paste'); var pasteHere = this.actions.get('pasteHere'); paste.setEnabled(this.editor.graph.cellEditor.isContentEditing() || (!mxClipboard.isEmpty() && graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent()))); pasteHere.setEnabled(paste.isEnabled()); }; /** * Hook for allowing selection and context menu for certain events. */ EditorUi.prototype.initClipboard = function() { var ui = this; var mxClipboardCut = mxClipboard.cut; mxClipboard.cut = function(graph) { if (graph.cellEditor.isContentEditing()) { document.execCommand('cut', false, null); } else { mxClipboardCut.apply(this, arguments); } ui.updatePasteActionStates(); }; var mxClipboardCopy = mxClipboard.copy; mxClipboard.copy = function(graph) { if (graph.cellEditor.isContentEditing()) { document.execCommand('copy', false, null); } else { mxClipboardCopy.apply(this, arguments); } ui.updatePasteActionStates(); }; var mxClipboardPaste = mxClipboard.paste; mxClipboard.paste = function(graph) { var result = null; if (graph.cellEditor.isContentEditing()) { document.execCommand('paste', false, null); } else { result = mxClipboardPaste.apply(this, arguments); } ui.updatePasteActionStates(); return result; }; // Overrides cell editor to update paste action state var cellEditorStartEditing = this.editor.graph.cellEditor.startEditing; this.editor.graph.cellEditor.startEditing = function() { cellEditorStartEditing.apply(this, arguments); ui.updatePasteActionStates(); }; var cellEditorStopEditing = this.editor.graph.cellEditor.stopEditing; this.editor.graph.cellEditor.stopEditing = function(cell, trigger) { cellEditorStopEditing.apply(this, arguments); ui.updatePasteActionStates(); }; this.updatePasteActionStates(); }; /** * Initializes the infinite canvas. */ EditorUi.prototype.initCanvas = function() { var graph = this.editor.graph; // Initial page layout view, scrollBuffer and timer-based scrolling var graph = this.editor.graph; graph.timerAutoScroll = true; /** * Returns the padding for pages in page view with scrollbars. */ graph.getPagePadding = function() { return new mxPoint(Math.max(0, Math.round((graph.container.offsetWidth - 34) / graph.view.scale)), Math.max(0, Math.round((graph.container.offsetHeight - 34) / graph.view.scale))); }; // Fits the number of background pages to the graph graph.view.getBackgroundPageBounds = function() { var layout = this.graph.getPageLayout(); var page = this.graph.getPageSize(); return new mxRectangle(this.scale * (this.translate.x + layout.x * page.width), this.scale * (this.translate.y + layout.y * page.height), this.scale * layout.width * page.width, this.scale * layout.height * page.height); }; graph.getPreferredPageSize = function(bounds, width, height) { var pages = this.getPageLayout(); var size = this.getPageSize(); return new mxRectangle(0, 0, pages.width * size.width, pages.height * size.height); }; // Scales pages/graph to fit available size var resize = null; var ui = this; if (this.editor.chromeless) { resize = mxUtils.bind(this, function(autoscale, maxScale, cx, cy) { if (graph.container != null) { cx = (cx != null) ? cx : 0; cy = (cy != null) ? cy : 0; var bds = (graph.pageVisible) ? graph.view.getBackgroundPageBounds() : graph.getGraphBounds(); var scroll = mxUtils.hasScrollbars(graph.container); var tr = graph.view.translate; var s = graph.view.scale; // Normalizes the bounds var b = mxRectangle.fromRectangle(bds); b.x = b.x / s - tr.x; b.y = b.y / s - tr.y; b.width /= s; b.height /= s; var st = graph.container.scrollTop; var sl = graph.container.scrollLeft; var sb = (mxClient.IS_QUIRKS || document.documentMode >= 8) ? 20 : 14; if (document.documentMode == 8 || document.documentMode == 9) { sb += 3; } var cw = graph.container.offsetWidth - sb; var ch = graph.container.offsetHeight - sb; var ns = (autoscale) ? Math.max(0.3, Math.min(maxScale || 1, cw / b.width)) : s; var dx = ((cw - ns * b.width) / 2) / ns; var dy = (this.lightboxVerticalDivider == 0) ? 0 : ((ch - ns * b.height) / this.lightboxVerticalDivider) / ns; if (scroll) { dx = Math.max(dx, 0); dy = Math.max(dy, 0); } if (scroll || bds.width < cw || bds.height < ch) { graph.view.scaleAndTranslate(ns, Math.floor(dx - b.x), Math.floor(dy - b.y)); graph.container.scrollTop = st * ns / s; graph.container.scrollLeft = sl * ns / s; } else if (cx != 0 || cy != 0) { var t = graph.view.translate; graph.view.setTranslate(Math.floor(t.x + cx / s), Math.floor(t.y + cy / s)); } } }); // Hack to make function available to subclassers this.chromelessResize = resize; // Hook for subclassers for override this.chromelessWindowResize = mxUtils.bind(this, function() { this.chromelessResize(false); }); // Removable resize listener var autoscaleResize = mxUtils.bind(this, function() { this.chromelessWindowResize(false); }); mxEvent.addListener(window, 'resize', autoscaleResize); this.destroyFunctions.push(function() { mxEvent.removeListener(window, 'resize', autoscaleResize); }); this.editor.addListener('resetGraphView', mxUtils.bind(this, function() { this.chromelessResize(true); })); this.actions.get('zoomIn').funct = mxUtils.bind(this, function(evt) { graph.zoomIn(); this.chromelessResize(false); }); this.actions.get('zoomOut').funct = mxUtils.bind(this, function(evt) { graph.zoomOut(); this.chromelessResize(false); }); // Creates toolbar for viewer - do not use CSS here // as this may be used in a viewer that has no CSS if (urlParams['toolbar'] != '0') { this.chromelessToolbar = document.createElement('div'); this.chromelessToolbar.style.position = 'fixed'; this.chromelessToolbar.style.overflow = 'hidden'; this.chromelessToolbar.style.boxSizing = 'border-box'; this.chromelessToolbar.style.whiteSpace = 'nowrap'; this.chromelessToolbar.style.backgroundColor = '#000000'; this.chromelessToolbar.style.padding = '10px 10px 8px 10px'; this.chromelessToolbar.style.left = '50%'; if (!mxClient.IS_VML) { mxUtils.setPrefixedStyle(this.chromelessToolbar.style, 'borderRadius', '20px'); mxUtils.setPrefixedStyle(this.chromelessToolbar.style, 'transition', 'opacity 600ms ease-in-out'); } var updateChromelessToolbarPosition = mxUtils.bind(this, function() { var css = mxUtils.getCurrentStyle(graph.container); this.chromelessToolbar.style.bottom = ((css != null) ? parseInt(css['margin-bottom'] || 0) : 0) + ((this.tabContainer != null) ? (20 + parseInt(this.tabContainer.style.height)) : 20) + 'px'; }); this.editor.addListener('resetGraphView', updateChromelessToolbarPosition); updateChromelessToolbarPosition(); var btnCount = 0; var addButton = mxUtils.bind(this, function(fn, imgSrc, tip) { btnCount++; var a = document.createElement('span'); a.style.paddingLeft = '8px'; a.style.paddingRight = '8px'; a.style.cursor = 'pointer'; mxEvent.addListener(a, 'click', fn); if (tip != null) { a.setAttribute('title', tip); } var img = document.createElement('img'); img.setAttribute('border', '0'); img.setAttribute('src', imgSrc); a.appendChild(img); this.chromelessToolbar.appendChild(a); return a; }); var prevButton = addButton(mxUtils.bind(this, function(evt) { this.actions.get('previousPage').funct(); mxEvent.consume(evt); }), Editor.previousLargeImage, mxResources.get('previousPage')); var pageInfo = document.createElement('div'); pageInfo.style.display = 'inline-block'; pageInfo.style.verticalAlign = 'top'; pageInfo.style.fontFamily = 'Helvetica,Arial'; pageInfo.style.marginTop = '8px'; pageInfo.style.color = '#ffffff'; this.chromelessToolbar.appendChild(pageInfo); var nextButton = addButton(mxUtils.bind(this, function(evt) { this.actions.get('nextPage').funct(); mxEvent.consume(evt); }), Editor.nextLargeImage, mxResources.get('nextPage')); var updatePageInfo = mxUtils.bind(this, function() { if (this.pages != null && this.pages.length > 1 && this.currentPage != null) { pageInfo.innerHTML = ''; mxUtils.write(pageInfo, (mxUtils.indexOf(this.pages, this.currentPage) + 1) + ' / ' + this.pages.length); } }); prevButton.style.paddingLeft = '0px'; prevButton.style.paddingRight = '4px'; nextButton.style.paddingLeft = '4px'; nextButton.style.paddingRight = '0px'; var updatePageButtons = mxUtils.bind(this, function() { if (this.pages != null && this.pages.length > 1 && this.currentPage != null) { nextButton.style.display = ''; prevButton.style.display = ''; pageInfo.style.display = 'inline-block'; } else { nextButton.style.display = 'none'; prevButton.style.display = 'none'; pageInfo.style.display = 'none'; } updatePageInfo(); }); this.editor.addListener('resetGraphView', updatePageButtons); this.editor.addListener('pageSelected', updatePageInfo); addButton(mxUtils.bind(this, function(evt) { this.actions.get('zoomOut').funct(); mxEvent.consume(evt); }), Editor.zoomOutLargeImage, mxResources.get('zoomOut') + ' (Alt+Mousewheel)'); addButton(mxUtils.bind(this, function(evt) { this.actions.get('zoomIn').funct(); mxEvent.consume(evt); }), Editor.zoomInLargeImage, mxResources.get('zoomIn') + ' (Alt+Mousewheel)'); addButton(mxUtils.bind(this, function(evt) { if (graph.lightbox) { if (graph.view.scale == 1) { this.lightboxFit(); } else { graph.zoomTo(1); } this.chromelessResize(false); } else { this.chromelessResize(true); } mxEvent.consume(evt); }), Editor.actualSizeLargeImage, mxResources.get('fit')); // Changes toolbar opacity on hover var fadeThread = null; var fadeThread2 = null; var fadeOut = mxUtils.bind(this, function(delay) { if (fadeThread != null) { window.clearTimeout(fadeThread); fadeThead = null; } if (fadeThread2 != null) { window.clearTimeout(fadeThread2); fadeThead2 = null; } fadeThread = window.setTimeout(mxUtils.bind(this, function() { mxUtils.setOpacity(this.chromelessToolbar, 0); fadeThread = null; fadeThread2 = window.setTimeout(mxUtils.bind(this, function() { this.chromelessToolbar.style.display = 'none'; fadeThread2 = null; }), 600); }), delay || 200); }); var fadeIn = mxUtils.bind(this, function(opacity) { if (fadeThread != null) { window.clearTimeout(fadeThread); fadeThead = null; } if (fadeThread2 != null) { window.clearTimeout(fadeThread2); fadeThead2 = null; } this.chromelessToolbar.style.display = ''; mxUtils.setOpacity(this.chromelessToolbar, opacity || 30); }); if (urlParams['layers'] == '1') { this.layersDialog = null; var layersButton = addButton(mxUtils.bind(this, function(evt) { if (this.layersDialog != null) { this.layersDialog.parentNode.removeChild(this.layersDialog); this.layersDialog = null; } else { this.layersDialog = graph.createLayersDialog(); mxEvent.addListener(this.layersDialog, 'mouseleave', mxUtils.bind(this, function() { this.layersDialog.parentNode.removeChild(this.layersDialog); this.layersDialog = null; })); var r = layersButton.getBoundingClientRect(); mxUtils.setPrefixedStyle(this.layersDialog.style, 'borderRadius', '5px'); this.layersDialog.style.position = 'fixed'; this.layersDialog.style.fontFamily = 'Helvetica,Arial'; this.layersDialog.style.backgroundColor = '#000000'; this.layersDialog.style.width = '160px'; this.layersDialog.style.padding = '4px 2px 4px 2px'; this.layersDialog.style.color = '#ffffff'; mxUtils.setOpacity(this.layersDialog, 70); this.layersDialog.style.left = r.left + 'px'; this.layersDialog.style.bottom = parseInt(this.chromelessToolbar.style.bottom) + this.chromelessToolbar.offsetHeight + 4 + 'px'; // Puts the dialog on top of the container z-index var style = mxUtils.getCurrentStyle(this.editor.graph.container); this.layersDialog.style.zIndex = style.zIndex; document.body.appendChild(this.layersDialog); } mxEvent.consume(evt); }), Editor.layersLargeImage, mxResources.get('layers')); // Shows/hides layers button depending on content var model = graph.getModel(); model.addListener(mxEvent.CHANGE, function() { layersButton.style.display = (model.getChildCount(model.root) > 1) ? '' : 'none'; }); } this.addChromelessToolbarItems(addButton); if (this.editor.editButtonLink != null) { addButton(mxUtils.bind(this, function(evt) { if (this.editor.editButtonLink == '_blank') { this.editor.editAsNew(this.getEditBlankXml()); } else { window.open(this.editor.editButtonLink, 'editWindow'); } mxEvent.consume(evt); }), Editor.editLargeImage, mxResources.get('openInNewWindow')); } if (graph.lightbox && (urlParams['close'] == '1' || this.container != document.body)) { addButton(mxUtils.bind(this, function(evt) { if (urlParams['close'] == '1') { window.close(); } else { this.destroy(); mxEvent.consume(evt); } }), Editor.closeLargeImage, mxResources.get('close') + ' (Escape)'); } // Initial state invisible this.chromelessToolbar.style.display = 'none'; mxUtils.setPrefixedStyle(this.chromelessToolbar.style, 'transform', 'translate(-50%,0)'); graph.container.appendChild(this.chromelessToolbar); mxEvent.addListener(graph.container, (mxClient.IS_POINTER) ? 'pointermove' : 'mousemove', mxUtils.bind(this, function(evt) { if (!mxEvent.isTouchEvent(evt)) { if (!mxEvent.isShiftDown(evt)) { fadeIn(30); } fadeOut(); } })); mxEvent.addListener(this.chromelessToolbar, (mxClient.IS_POINTER) ? 'pointermove' : 'mousemove', function(evt) { mxEvent.consume(evt); }); mxEvent.addListener(this.chromelessToolbar, 'mouseenter', mxUtils.bind(this, function(evt) { if (!mxEvent.isShiftDown(evt)) { fadeIn(100); } else { fadeOut(); } })); mxEvent.addListener(this.chromelessToolbar, 'mousemove', mxUtils.bind(this, function(evt) { if (!mxEvent.isShiftDown(evt)) { fadeIn(100); } else { fadeOut(); } mxEvent.consume(evt); })); mxEvent.addListener(this.chromelessToolbar, 'mouseleave', mxUtils.bind(this, function(evt) { if (!mxEvent.isTouchEvent(evt)) { fadeIn(30); } })); // Shows/hides toolbar for touch devices var tol = graph.getTolerance(); graph.addMouseListener( { startX: 0, startY: 0, scrollLeft: 0, scrollTop: 0, mouseDown: function(sender, me) { this.startX = me.getGraphX(); this.startY = me.getGraphY(); this.scrollLeft = graph.container.scrollLeft; this.scrollTop = graph.container.scrollTop; }, mouseMove: function(sender, me) {}, mouseUp: function(sender, me) { if (mxEvent.isTouchEvent(me.getEvent())) { if ((Math.abs(this.scrollLeft - graph.container.scrollLeft) < tol && Math.abs(this.scrollTop - graph.container.scrollTop) < tol) && (Math.abs(this.startX - me.getGraphX()) < tol && Math.abs(this.startY - me.getGraphY()) < tol)) { if (parseFloat(ui.chromelessToolbar.style.opacity || 0) > 0) { fadeOut(); } else { fadeIn(30); } } } } });