| /** |
| * $Id: Graph.js,v 1.40 2014/02/17 13:46:29 gaudenz Exp $ |
| * Copyright (c) 2006-2012, JGraph Ltd |
| */ |
| /** |
| * Constructs a new graph instance. Note that the constructor does not take a |
| * container because the graph instance is needed for creating the UI, which |
| * in turn will create the container for the graph. Hence, the container is |
| * assigned later in EditorUi. |
| */ |
| Graph = function(container, model, renderHint, stylesheet) |
| { |
| mxGraph.call(this, container, model, renderHint, stylesheet); |
| |
| this.setConnectable(true); |
| this.setDropEnabled(true); |
| this.setPanning(true); |
| this.setTooltips(true); |
| this.setAllowLoops(true); |
| this.allowAutoPanning = true; |
| this.resetEdgesOnConnect = false; |
| this.constrainChildren = false; |
| |
| // Enables cloning of connection sources by default |
| this.connectionHandler.setCreateTarget(true); |
| |
| // Disables built-in connection starts |
| this.connectionHandler.isValidSource = function() |
| { |
| return mxConnectionHandler.prototype.isValidSource.apply(this, arguments) && urlParams['connect'] != '2' && urlParams['connect'] != null; |
| }; |
| |
| // Sets the style to be used when an elbow edge is double clicked |
| this.alternateEdgeStyle = 'vertical'; |
| |
| if (stylesheet == null) |
| { |
| this.loadStylesheet(); |
| } |
| |
| // Creates rubberband selection |
| var rubberband = new mxRubberband(this); |
| |
| this.getRubberband = function() |
| { |
| return rubberband; |
| }; |
| |
| // Shows hand cursor while panning |
| this.panningHandler.addListener(mxEvent.PAN_START, mxUtils.bind(this, function() |
| { |
| this.container.style.cursor = 'pointer'; |
| })); |
| |
| this.panningHandler.addListener(mxEvent.PAN_END, mxUtils.bind(this, function() |
| { |
| this.container.style.cursor = 'default'; |
| })); |
| |
| // Forces panning for middle mouse button |
| this.panningHandler.isForcePanningEvent = function(me) |
| { |
| return mxEvent.isMiddleMouseButton(me.getEvent()); |
| }; |
| |
| this.popupMenuHandler.autoExpand = true; |
| |
| this.popupMenuHandler.isSelectOnPopup = function(me) |
| { |
| return mxEvent.isMouseEvent(me.getEvent()); |
| }; |
| |
| // Adds support for HTML labels via style. Note: Currently, only the Java |
| // backend supports HTML labels but CSS support is limited to the following: |
| // http://docs.oracle.com/javase/6/docs/api/index.html?javax/swing/text/html/CSS.html |
| this.isHtmlLabel = function(cell) |
| { |
| var state = this.view.getState(cell); |
| var style = (state != null) ? state.style : this.getCellStyle(cell); |
| var href = this.getLinkForCell(cell); |
| |
| return style['html'] == '1' || style['whiteSpace'] == 'wrap' || href != null; |
| }; |
| |
| // HTML entities are displayed as plain text in wrapped plain text labels |
| this.cellRenderer.getLabelValue = function(state) |
| { |
| var result = mxCellRenderer.prototype.getLabelValue.apply(this, arguments); |
| |
| if (state.view.graph.isHtmlLabel(state.cell)) |
| { |
| if (state.style['html'] != 1) |
| { |
| result = mxUtils.htmlEntities(result, false); |
| } |
| |
| var href = state.view.graph.getLinkForCell(state.cell); |
| |
| if (href != null) |
| { |
| result = '<a style="color:inherit;text-decoration:inherit;" href="' + href + '" target="_blank">' + result + '</a>'; |
| } |
| } |
| |
| return result; |
| }; |
| |
| // Unlocks all cells |
| this.isCellLocked = function(cell) |
| { |
| return false; |
| }; |
| |
| // Tap and hold on background starts rubberband for multiple selected |
| // cells the cell associated with the event is deselected |
| this.addListener(mxEvent.TAP_AND_HOLD, mxUtils.bind(this, function(sender, evt) |
| { |
| var me = evt.getProperty('event'); |
| var cell = evt.getProperty('cell'); |
| |
| if (cell == null) |
| { |
| var pt = mxUtils.convertPoint(this.container, |
| mxEvent.getClientX(me), mxEvent.getClientY(me)); |
| rubberband.start(pt.x, pt.y); |
| } |
| else if (this.getSelectionCount() > 1 && this.isCellSelected(cell)) |
| { |
| this.removeSelectionCell(cell); |
| } |
| |
| // Blocks further processing of the event |
| evt.consume(); |
| })); |
| |
| // On connect the target is selected and we clone the cell of the preview edge for insert |
| this.connectionHandler.selectCells = function(edge, target) |
| { |
| this.graph.setSelectionCell(target || edge); |
| }; |
| |
| // Shows connection points only if cell not selected |
| this.connectionHandler.constraintHandler.isStateIgnored = function(state, source) |
| { |
| return source && state.view.graph.isCellSelected(state.cell); |
| }; |
| |
| // Updates constraint handler if the selection changes |
| this.selectionModel.addListener(mxEvent.CHANGE, mxUtils.bind(this, function() |
| { |
| var ch = this.connectionHandler.constraintHandler; |
| |
| if (ch.currentFocus != null && ch.isStateIgnored(ch.currentFocus, true)) |
| { |
| ch.currentFocus = null; |
| ch.constraints = null; |
| ch.destroyIcons(); |
| } |
| |
| ch.destroyFocusHighlight(); |
| })); |
| |
| if (touchStyle) |
| { |
| this.initTouch(); |
| } |
| }; |
| |
| // Graph inherits from mxGraph |
| mxUtils.extend(Graph, mxGraph); |
| |
| /** |
| * Allows to all values in fit. |
| */ |
| Graph.prototype.minFitScale = null; |
| |
| /** |
| * Allows to all values in fit. |
| */ |
| Graph.prototype.maxFitScale = null; |
| |
| /** |
| * Loads the stylesheet for this graph. |
| */ |
| Graph.prototype.loadStylesheet = function() |
| { |
| var node = mxUtils.load(STYLE_PATH + '/default.xml').getDocumentElement(); |
| var dec = new mxCodec(node.ownerDocument); |
| dec.decode(node, this.getStylesheet()); |
| }; |
| |
| /** |
| * Inverts the elbow edge style without removing existing styles. |
| */ |
| Graph.prototype.flipEdge = function(edge) |
| { |
| if (edge != null) |
| { |
| var state = this.view.getState(edge); |
| var style = (state != null) ? state.style : this.getCellStyle(edge); |
| |
| if (style != null) |
| { |
| var elbow = mxUtils.getValue(style, mxConstants.STYLE_ELBOW, |
| mxConstants.ELBOW_HORIZONTAL); |
| var value = (elbow == mxConstants.ELBOW_HORIZONTAL) ? |
| mxConstants.ELBOW_VERTICAL : mxConstants.ELBOW_HORIZONTAL; |
| this.setCellStyles(mxConstants.STYLE_ELBOW, value, [edge]); |
| } |
| } |
| }; |
| |
| /** |
| * Sets the default edge for future connections. |
| */ |
| Graph.prototype.setDefaultEdge = function(cell) |
| { |
| if (cell != null && this.getModel().isEdge(cell)) |
| { |
| // Take a snapshot of the cell at the moment of calling |
| var proto = this.getModel().cloneCell(cell); |
| |
| // Delete existing points |
| if (proto.geometry != null) |
| { |
| proto.geometry.points = null; |
| } |
| |
| // Delete entry-/exitXY styles |
| var style = proto.getStyle(); |
| style = mxUtils.setStyle(style, mxConstants.STYLE_ENTRY_X, null); |
| style = mxUtils.setStyle(style, mxConstants.STYLE_ENTRY_Y, null); |
| style = mxUtils.setStyle(style, mxConstants.STYLE_EXIT_X, null); |
| style = mxUtils.setStyle(style, mxConstants.STYLE_EXIT_Y, null); |
| proto.setStyle(style); |
| |
| // Uses edge template for connect preview |
| this.connectionHandler.createEdgeState = function(me) |
| { |
| return this.graph.view.createState(proto); |
| }; |
| |
| // Creates new connections from edge template |
| this.connectionHandler.factoryMethod = function() |
| { |
| return this.graph.cloneCells([proto])[0]; |
| }; |
| } |
| }; |
| |
| /** |
| * Disables folding for non-swimlanes. |
| */ |
| Graph.prototype.isCellFoldable = function(cell) |
| { |
| var state = this.view.getState(cell); |
| var style = (state != null) ? state.style : this.getCellStyle(cell); |
| |
| return this.foldingEnabled && this.isContainer(cell) && style['collapsible'] != '0'; |
| }; |
| |
| /** |
| * Disables drill-down for non-swimlanes. |
| */ |
| Graph.prototype.isValidRoot = function(cell) |
| { |
| return this.isContainer(cell); |
| }; |
| |
| /** |
| * Disables drill-down for non-swimlanes. |
| */ |
| Graph.prototype.isValidDropTarget = function(cell) |
| { |
| return this.isContainer(cell) || mxGraph.prototype.isValidDropTarget.apply(this, arguments); |
| }; |
| |
| /** |
| * Disables drill-down for non-swimlanes. |
| */ |
| Graph.prototype.isContainer = function(cell) |
| { |
| if (this.isSwimlane(cell)) |
| { |
| return true; |
| } |
| else |
| { |
| var state = this.view.getState(cell); |
| var style = (state != null) ? state.style : this.getCellStyle(cell); |
| |
| return style['container'] == '1'; |
| } |
| }; |
| |
| /** |
| * Overrides createGroupCell to set the group style for new groups to 'group'. |
| */ |
| Graph.prototype.createGroupCell = function() |
| { |
| var group = mxGraph.prototype.createGroupCell.apply(this, arguments); |
| group.setStyle('group'); |
| |
| return group; |
| }; |
| |
| /** |
| * Overrides tooltips to show position and size |
| */ |
| Graph.prototype.getTooltipForCell = function(cell) |
| { |
| var tip = ''; |
| |
| if (this.getModel().isVertex(cell)) |
| { |
| var geo = this.getCellGeometry(cell); |
| |
| var f2 = function(x) |
| { |
| return Math.round(parseFloat(x) * 100) / 100; |
| }; |
| |
| if (geo != null) |
| { |
| if (tip == null) |
| { |
| tip = ''; |
| } |
| else if (tip.length > 0) |
| { |
| tip += '\n'; |
| } |
| |
| tip += 'X/Y: ' + f2(geo.x) + '/' + f2(geo.y) + '\nWxH: ' + f2(geo.width) + 'x' + f2(geo.height); |
| } |
| } |
| else if (this.getModel().isEdge(cell)) |
| { |
| tip = mxGraph.prototype.getTooltipForCell.apply(this, arguments); |
| } |
| |
| // Adds metadata |
| if (mxUtils.isNode(cell.value)) |
| { |
| var attrs = cell.value.attributes; |
| |
| for (var i = 0; i < attrs.length; i++) |
| { |
| if (attrs[i].nodeName != 'label' && attrs[i].nodeValue.length > 0) |
| { |
| tip += '\n' + attrs[i].nodeName + ': ' + attrs[i].nodeValue; |
| } |
| } |
| } |
| |
| return tip; |
| }; |
| |
| /** |
| * Returns the label for the given cell. |
| */ |
| Graph.prototype.convertValueToString = function(cell) |
| { |
| if (cell.value != null && typeof(cell.value) == 'object') |
| { |
| return cell.value.getAttribute('label'); |
| } |
| |
| return mxGraph.prototype.convertValueToString.apply(this, arguments); |
| }; |
| |
| /** |
| * Removes all illegal control characters with ASCII code <32 except TAB, LF |
| * and CR. |
| */ |
| Graph.prototype.zapGremlins = function(text) |
| { |
| var checked = []; |
| |
| for (var i = 0; i < text.length; i++) |
| { |
| var code = text.charCodeAt(i); |
| |
| // Removes all control chars except TAB, LF and CR |
| if (code >= 32 || code == 9 || code == 10 || code == 13) |
| { |
| checked.push(text.charAt(i)); |
| } |
| } |
| |
| return checked.join(''); |
| }; |
| |
| /** |
| * Handles label changes for XML user objects. |
| */ |
| Graph.prototype.cellLabelChanged = function(cell, value, autoSize) |
| { |
| // Removes all illegal control characters in user input |
| value = this.zapGremlins(value); |
| |
| if (cell.value != null && typeof(cell.value) == 'object') |
| { |
| var tmp = cell.value.cloneNode(true); |
| tmp.setAttribute('label', value); |
| value = tmp; |
| } |
| |
| mxGraph.prototype.cellLabelChanged.apply(this, arguments); |
| }; |
| |
| /** |
| * Sets the link for the given cell. |
| */ |
| Graph.prototype.setLinkForCell = function(cell, link) |
| { |
| var value = null; |
| |
| if (cell.value != null && typeof(cell.value) == 'object') |
| { |
| value = cell.value.cloneNode(true); |
| } |
| else |
| { |
| var doc = mxUtils.createXmlDocument(); |
| |
| value = doc.createElement('UserObject'); |
| value.setAttribute('label', cell.value); |
| } |
| |
| if (link != null && link.length > 0) |
| { |
| value.setAttribute('link', link); |
| } |
| else |
| { |
| value.removeAttribute('link'); |
| } |
| |
| this.model.setValue(cell, value); |
| }; |
| |
| /** |
| * Returns the link for the given cell. |
| */ |
| Graph.prototype.getLinkForCell = function(cell) |
| { |
| if (cell.value != null && typeof(cell.value) == 'object') |
| { |
| return cell.value.getAttribute('link'); |
| } |
| |
| return null; |
| }; |
| |
| /** |
| * Customized graph for touch devices. |
| */ |
| Graph.prototype.initTouch = function() |
| { |
| // Disables new connections via "hotspot" |
| this.connectionHandler.marker.isEnabled = function() |
| { |
| return this.graph.connectionHandler.first != null; |
| }; |
| |
| // Hides menu when editing starts |
| this.addListener(mxEvent.START_EDITING, function(sender, evt) |
| { |
| this.popupMenuHandler.hideMenu(); |
| }); |
| |
| // Adds custom hit detection if native hit detection found no cell |
| this.updateMouseEvent = function(me) |
| { |
| var me = mxGraph.prototype.updateMouseEvent.apply(this, arguments); |
| |
| if (me.getState() == null) |
| { |
| var cell = this.getCellAt(me.graphX, me.graphY); |
| |
| if (cell != null && this.isSwimlane(cell) && this.hitsSwimlaneContent(cell, me.graphX, me.graphY)) |
| { |
| cell = null; |
| } |
| else |
| { |
| me.state = this.view.getState(cell); |
| |
| if (me.state != null && me.state.shape != null) |
| { |
| this.container.style.cursor = me.state.shape.node.style.cursor; |
| } |
| } |
| } |
| |
| if (me.getState() == null) |
| { |
| this.container.style.cursor = 'default'; |
| } |
| |
| return me; |
| }; |
| |
| // Context menu trigger implementation depending on current selection state |
| // combined with support for normal popup trigger. |
| var cellSelected = false; |
| var selectionEmpty = false; |
| var menuShowing = false; |
| |
| this.fireMouseEvent = function(evtName, me, sender) |
| { |
| if (evtName == mxEvent.MOUSE_DOWN) |
| { |
| // For hit detection on edges |
| me = this.updateMouseEvent(me); |
| |
| cellSelected = this.isCellSelected(me.getCell()); |
| selectionEmpty = this.isSelectionEmpty(); |
| menuShowing = this.popupMenuHandler.isMenuShowing(); |
| } |
| |
| mxGraph.prototype.fireMouseEvent.apply(this, arguments); |
| }; |
| |
| // Shows popup menu if cell was selected or selection was empty and background was clicked |
| // FIXME: Conflicts with mxPopupMenuHandler.prototype.getCellForPopupEvent in Editor.js by |
| // selecting parent for selected children in groups before this check can be made. |
| this.popupMenuHandler.mouseUp = mxUtils.bind(this, function(sender, me) |
| { |
| this.popupMenuHandler.popupTrigger = !this.isEditing() && (this.popupMenuHandler.popupTrigger || |
| (!menuShowing && !mxEvent.isMouseEvent(me.getEvent()) && |
| ((selectionEmpty && me.getCell() == null && this.isSelectionEmpty()) || |
| (cellSelected && this.isCellSelected(me.getCell()))))); |
| mxPopupMenuHandler.prototype.mouseUp.apply(this.popupMenuHandler, arguments); |
| }); |
| }; |
| |
| (function() |
| { |
| /** |
| * HTML in-place editor |
| */ |
| mxCellEditor.prototype.isContentEditing = function() |
| { |
| return this.text2 != null && this.text2.style.display != 'none'; |
| }; |
| |
| /** |
| * HTML in-place editor |
| */ |
| mxCellEditor.prototype.toggleViewMode = function() |
| { |
| if (this.text2 != null) |
| { |
| var tmp = this.saveSelection(); |
| |
| if (this.textarea.style.display == 'none') |
| { |
| var content = this.text2.innerHTML.replace(/\n/g, ''); |
| |
| if (this.textarea.value != content) |
| { |
| this.textarea.value = content; |
| this.setModified(true); |
| } |
| |
| this.textarea.style.display = 'block'; |
| this.text2.style.display = 'none'; |
| this.textarea.focus(); |
| } |
| else |
| { |
| var content = this.textarea.value.replace(/\n/g, '<br/>'); |
| |
| if (this.text2.innerHTML != content) |
| { |
| this.text2.innerHTML = content; |
| this.setModified(true); |
| } |
| |
| this.text2.style.display = ''; |
| this.textarea.style.display = 'none'; |
| this.text2.focus(); |
| } |
| |
| if (this.switchSelectionState != null) |
| { |
| this.restoreSelection(this.switchSelectionState); |
| } |
| |
| this.switchSelectionState = tmp; |
| } |
| }; |
| |
| /** |
| * Creates the keyboard event handler for the current graph and history. |
| */ |
| mxCellEditor.prototype.saveSelection = function() |
| { |
| if (window.getSelection) |
| { |
| sel = window.getSelection(); |
| |
| if (sel.getRangeAt && sel.rangeCount) |
| { |
| var ranges = []; |
| |
| for (var i = 0, len = sel.rangeCount; i < len; ++i) |
| { |
| ranges.push(sel.getRangeAt(i)); |
| } |
| |
| return ranges; |
| } |
| } |
| else if (document.selection && document.selection.createRange) |
| { |
| return document.selection.createRange(); |
| } |
| |
| return null; |
| }; |
| |
| /** |
| * Creates the keyboard event handler for the current graph and history. |
| */ |
| mxCellEditor.prototype.restoreSelection = function(savedSel) |
| { |
| if (savedSel) |
| { |
| if (window.getSelection) |
| { |
| sel = window.getSelection(); |
| sel.removeAllRanges(); |
| |
| for (var i = 0, len = savedSel.length; i < len; ++i) |
| { |
| sel.addRange(savedSel[i]); |
| } |
| } |
| else if (document.selection && savedSel.select) |
| { |
| savedSel.select(); |
| } |
| } |
| }; |
| |
| if ('contentEditable' in document.documentElement) |
| { |
| /** |
| * HTML in-place editor |
| */ |
| var mxCellEditorStartEditing = mxCellEditor.prototype.startEditing; |
| mxCellEditor.prototype.startEditing = function(cell, trigger) |
| { |
| this.switchSelectionState = null; |
| |
| // First run cannot set display before supercall because textarea is lazy created |
| // Lazy instantiates textarea to save memory in IE |
| if (this.textarea == null) |
| { |
| this.init(); |
| } |
| |
| var state = this.graph.view.getState(cell); |
| |
| if (state != null && state.style['html'] == 1) |
| { |
| this.textarea.style.display = 'none'; |
| } |
| else |
| { |
| this.textarea.style.display = 'block'; |
| } |
| |
| mxCellEditorStartEditing.apply(this, arguments); |
| |
| if (this.textarea.style.display == 'none') |
| { |
| this.text2 = document.createElement('div'); |
| this.text2.className = 'geContentEditable'; |
| this.text2.innerHTML = this.textarea.value.replace(/\n/g, '<br/>'); |
| var style = this.text2.style; |
| |
| // Required to catch all events on the background in IE |
| style.backgroundImage = 'url(\'' + mxClient.imageBasePath + '/transparent.gif\')'; |
| |
| style.cursor = 'text'; |
| style.outline = 'none'; |
| style.position = 'absolute'; |
| style.width = parseInt(this.textarea.style.width) + 'px'; |
| style.height = (parseInt(this.textarea.style.height) - 4) + 'px'; |
| style.left = parseInt(this.textarea.style.left) + 'px'; |
| style.top = parseInt(this.textarea.style.top) + 'px'; |
| style.fontFamily = this.textarea.style.fontFamily; |
| style.fontWeight = this.textarea.style.fontWeight; |
| style.textAlign = this.textarea.style.textAlign; |
| style.fontSize = this.textarea.style.fontSize; |
| style.color = this.textarea.style.color; |
| |
| // Matches line height correctionFactor in embedded HTML output |
| if (state.text != null && state.text.node != null && state.text.node.ownerSVGElement != null) |
| { |
| var lh = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? Math.round(parseInt(this.textarea.style.fontSize) * mxConstants.LINE_HEIGHT) + 'px' : |
| (mxConstants.LINE_HEIGHT * mxSvgCanvas2D.prototype.lineHeightCorrection); |
| style.lineHeight = lh; |
| } |
| else |
| { |
| style.lineHeight = this.textarea.style.lineHeight; |
| } |
| |
| this.graph.container.appendChild(this.text2); |
| this.text2.contentEditable = true; |
| this.text2.focus(); |
| |
| document.execCommand('selectall'); |
| } |
| else |
| { |
| this.textarea.focus(); |
| this.textarea.select(); |
| } |
| }; |
| |
| var mxCellEditorStopEditing = mxCellEditor.prototype.stopEditing; |
| mxCellEditor.prototype.stopEditing = function(cancel) |
| { |
| if (this.text2 != null) |
| { |
| var content = this.text2.innerHTML; |
| |
| // Modified state also updated in code view action |
| if (this.text2.style.display != 'none' && this.textarea.value != content) |
| { |
| this.textarea.value = content.replace(/\r\n/g, '').replace(/\n/g, ''); |
| this.setModified(true); |
| } |
| |
| this.text2.parentNode.removeChild(this.text2); |
| this.text2 = null; |
| } |
| |
| mxCellEditorStopEditing.apply(this, arguments); |
| }; |
| |
| // Workaround for focusLost calls stopEditing when in HTML view |
| var mxCellEditorFocusLost = mxCellEditor.prototype.focusLost; |
| mxCellEditor.prototype.focusLost = function(evt) |
| { |
| if (this.text2 == null) |
| { |
| mxCellEditorFocusLost.apply(this, arguments); |
| } |
| }; |
| } |
| |
| /** |
| * Implements touch style |
| */ |
| if (touchStyle) |
| { |
| // Sets constants for touch style |
| mxConstants.HANDLE_SIZE = 16; |
| mxConstants.LABEL_HANDLE_SIZE = 7; |
| |
| // Larger tolerance and grid for real touch devices |
| if (mxClient.IS_TOUCH || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0) |
| { |
| mxShape.prototype.svgStrokeTolerance = 18; |
| mxVertexHandler.prototype.tolerance = 12; |
| mxEdgeHandler.prototype.tolerance = 12; |
| Graph.prototype.tolerance = 12; |
| } |
| |
| // One finger pans (no rubberband selection) must start regardless of mouse button |
| mxPanningHandler.prototype.isPanningTrigger = function(me) |
| { |
| var evt = me.getEvent(); |
| |
| return (me.getState() == null && !mxEvent.isMouseEvent(evt)) || |
| (mxEvent.isPopupTrigger(evt) && (me.getState() == null || |
| mxEvent.isControlDown(evt) || mxEvent.isShiftDown(evt))); |
| }; |
| |
| // Don't clear selection if multiple cells selected |
| var graphHandlerMouseDown = mxGraphHandler.prototype.mouseDown; |
| mxGraphHandler.prototype.mouseDown = function(sender, me) |
| { |
| graphHandlerMouseDown.apply(this, arguments); |
| |
| if (this.graph.isCellSelected(me.getCell()) && this.graph.getSelectionCount() > 1) |
| { |
| this.delayedSelection = false; |
| } |
| }; |
| |
| // Rounded edge and vertex handles |
| var touchHandle = new mxImage(IMAGE_PATH + '/touch-handle.png', 16, 16); |
| var rotationHandle = new mxImage(IMAGE_PATH + '/touch-handle-orange.png', 16, 16); |
| var edgeHandle = new mxImage(IMAGE_PATH + '/touch-handle.png', 16, 16); |
| mxVertexHandler.prototype.handleImage = touchHandle; |
| mxEdgeHandler.prototype.handleImage = edgeHandle; |
| mxOutline.prototype.sizerImage = touchHandle; |
| |
| // Pre-fetches touch handle |
| new Image().src = touchHandle.src; |
| |
| var vertexHandlerCreateSizerShape = mxVertexHandler.prototype.createSizerShape; |
| mxVertexHandler.prototype.createSizerShape = function(bounds, index, fillColor) |
| { |
| this.handleImage = (index == mxEvent.ROTATION_HANDLE) ? rotationHandle : mxVertexHandler.prototype.handleImage; |
| return vertexHandlerCreateSizerShape.apply(this, arguments); |
| }; |
| |
| // Installs locked and connect handles |
| // Problem is condition for source and target in segment handler before creating bends array |
| /*var edgeHandlerCreateHandleShape = mxEdgeHandler.prototype.createHandleShape; |
| mxEdgeHandler.prototype.createHandleShape = function(index) |
| { |
| if (index == 0 || index == this.abspoints.length - 1) |
| { |
| this.handleImage = connectHandle; |
| } |
| else |
| { |
| this.handleImage = touchHandle; |
| } |
| |
| return edgeHandlerCreateHandleShape.apply(this, arguments); |
| };*/ |
| |
| // Adds connect icon to selected vertices |
| var connectorSrc = IMAGE_PATH + '/touch-connector.png'; |
| |
| // TODO: Merge with code below |
| var vertexHandlerInit = mxVertexHandler.prototype.init; |
| mxVertexHandler.prototype.init = function() |
| { |
| vertexHandlerInit.apply(this, arguments); |
| |
| // Only show connector image on one cell and do not show on containers |
| if (showConnectorImg && this.graph.connectionHandler.isEnabled() && |
| this.graph.isCellConnectable(this.state.cell) && |
| !this.graph.isValidRoot(this.state.cell) && |
| this.graph.getSelectionCount() == 1) |
| { |
| this.connectorImg = mxUtils.createImage(connectorSrc); |
| this.connectorImg.style.cursor = 'pointer'; |
| this.connectorImg.style.width = '29px'; |
| this.connectorImg.style.height = '29px'; |
| this.connectorImg.style.position = 'absolute'; |
| |
| if (!(mxClient.IS_TOUCH || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0)) |
| { |
| this.connectorImg.setAttribute('title', mxResources.get('connect')); |
| mxEvent.redirectMouseEvents(this.connectorImg, this.graph, this.state); |
| } |
| |
| // Starts connecting on touch/mouse down |
| mxEvent.addGestureListeners(this.connectorImg, |
| mxUtils.bind(this, function(evt) |
| { |
| this.graph.popupMenuHandler.hideMenu(); |
| this.graph.stopEditing(false); |
| |
| var pt = mxUtils.convertPoint(this.graph.container, |
| mxEvent.getClientX(evt), mxEvent.getClientY(evt)); |
| this.graph.connectionHandler.start(this.state, pt.x, pt.y); |
| this.graph.isMouseTrigger = mxEvent.isMouseEvent(evt); |
| this.graph.isMouseDown = true; |
| mxEvent.consume(evt); |
| }) |
| ); |
| |
| this.graph.container.appendChild(this.connectorImg); |
| } |
| |
| this.redrawHandles(); |
| }; |
| |
| // Pre-fetches touch connector |
| new Image().src = connectorSrc; |
| } |
| else // not touchStyle |
| { |
| var img = new mxImage(IMAGE_PATH + '/connector.png', 15, 15); |
| mxConnectionHandler.prototype.connectImage = img; |
| |
| // Pre-fetches img |
| new Image().src = img.src; |
| |
| if (urlParams['connect'] == null || urlParams['connect'] == '2') |
| { |
| var img = new mxImage(IMAGE_PATH + '/connector.png', 15, 15); |
| |
| var vertexHandlerInit = mxVertexHandler.prototype.init; |
| mxVertexHandler.prototype.init = function() |
| { |
| vertexHandlerInit.apply(this, arguments); |
| |
| // Only show connector image on one cell and do not show on containers |
| if (showConnectorImg && this.graph.connectionHandler.isEnabled() && |
| this.graph.isCellConnectable(this.state.cell) && |
| !this.graph.isValidRoot(this.state.cell) && |
| this.graph.getSelectionCount() == 1) |
| { |
| // Workaround for event redirection via image tag in quirks and IE8 |
| if (mxClient.IS_IE && !mxClient.IS_SVG) |
| { |
| this.connectorImg = document.createElement('div'); |
| this.connectorImg.style.backgroundImage = 'url(' + img.src + ')'; |
| this.connectorImg.style.backgroundPosition = 'center'; |
| this.connectorImg.style.backgroundRepeat = 'no-repeat'; |
| this.connectorImg.style.width = (img.width + 4) + 'px'; |
| this.connectorImg.style.height = (img.height + 4) + 'px'; |
| this.connectorImg.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block'; |
| } |
| else |
| { |
| this.connectorImg = mxUtils.createImage(img.src); |
| this.connectorImg.style.width = img.width + 'px'; |
| this.connectorImg.style.height = img.height + 'px'; |
| } |
| |
| this.connectorImg.style.cursor = 'pointer'; |
| this.connectorImg.style.position = 'absolute'; |
| this.connectorImg.setAttribute('title', mxResources.get('connect')); |
| mxEvent.redirectMouseEvents(this.connectorImg, this.graph, this.state); |
| |
| // Starts connecting on touch/mouse down |
| // Starts connecting on touch/mouse down |
| mxEvent.addGestureListeners(this.connectorImg, |
| mxUtils.bind(this, function(evt) |
| { |
| this.graph.popupMenuHandler.hideMenu(); |
| this.graph.stopEditing(false); |
| |
| var pt = mxUtils.convertPoint(this.graph.container, |
| mxEvent.getClientX(evt), mxEvent.getClientY(evt)); |
| this.graph.connectionHandler.start(this.state, pt.x, pt.y); |
| this.graph.isMouseTrigger = mxEvent.isMouseEvent(evt); |
| this.graph.isMouseDown = true; |
| mxEvent.consume(evt); |
| }) |
| ); |
| |
| this.graph.container.appendChild(this.connectorImg); |
| this.redrawHandles(); |
| } |
| }; |
| } |
| } |
| |
| var vertexHandlerRedrawHandles = mxVertexHandler.prototype.redrawHandles; |
| mxVertexHandler.prototype.redrawHandles = function() |
| { |
| vertexHandlerRedrawHandles.apply(this); |
| |
| if (this.state != null && this.connectorImg != null) |
| { |
| var pt = new mxPoint(); |
| var s = this.state; |
| |
| // Top right for single-sizer |
| if (mxVertexHandler.prototype.singleSizer) |
| { |
| pt.x = s.x + s.width - this.connectorImg.offsetWidth / 2; |
| pt.y = s.y - this.connectorImg.offsetHeight / 2; |
| } |
| else |
| { |
| pt.x = s.x + s.width + mxConstants.HANDLE_SIZE / 2 + 4 + this.connectorImg.offsetWidth / 2; |
| pt.y = s.y + s.height / 2; |
| } |
| |
| var alpha = mxUtils.toRadians(mxUtils.getValue(s.style, mxConstants.STYLE_ROTATION, 0)); |
| |
| if (alpha != 0) |
| { |
| var cos = Math.cos(alpha); |
| var sin = Math.sin(alpha); |
| |
| var ct = new mxPoint(s.getCenterX(), s.getCenterY()); |
| pt = mxUtils.getRotatedPoint(pt, cos, sin, ct); |
| } |
| |
| this.connectorImg.style.left = (pt.x - this.connectorImg.offsetWidth / 2) + 'px'; |
| this.connectorImg.style.top = (pt.y - this.connectorImg.offsetHeight / 2) + 'px'; |
| } |
| }; |
| |
| var vertexHandlerHideSizers = mxVertexHandler.prototype.hideSizers; |
| mxVertexHandler.prototype.hideSizers = function() |
| { |
| vertexHandlerHideSizers.apply(this, arguments); |
| |
| if (this.connectorImg != null) |
| { |
| this.connectorImg.style.visibility = 'hidden'; |
| } |
| }; |
| |
| var vertexHandlerReset = mxVertexHandler.prototype.reset; |
| mxVertexHandler.prototype.reset = function() |
| { |
| vertexHandlerReset.apply(this, arguments); |
| |
| if (this.connectorImg != null) |
| { |
| this.connectorImg.style.visibility = ''; |
| } |
| }; |
| |
| var vertexHandlerDestroy = mxVertexHandler.prototype.destroy; |
| mxVertexHandler.prototype.destroy = function(sender, me) |
| { |
| vertexHandlerDestroy.apply(this, arguments); |
| |
| if (this.connectorImg != null) |
| { |
| this.connectorImg.parentNode.removeChild(this.connectorImg); |
| this.connectorImg = null; |
| } |
| }; |
| })(); |