From fc98630dad60839702edf056e0887c2e3f84c73b Mon Sep 17 00:00:00 2001 From: mcyph <20507948+mcyph@users.noreply.github.com> Date: Sat, 20 Mar 2021 19:07:56 +1100 Subject: [PATCH] updates to js syntax --- docs/js-api/files/editor/mxEditor-js.html | 2 +- docs/js-api/files/io/mxObjectCodec-js.html | 4 +- docs/js-api/files/util/mxClipboard-js.html | 2 +- docs/js-api/files/view/mxGraph-js.html | 2 +- docs/js-api/files/view/mxStylesheet-js.html | 2 +- docs/manual.html | 2 +- docs/tutorial.html | 4 +- examples/editing.html | 2 +- examples/editors/diagrameditor.html | 2 +- examples/grapheditor/www/index.html | 4 +- examples/grapheditor/www/js/Actions.js | 2 +- examples/grapheditor/www/js/Editor.js | 2 +- examples/grapheditor/www/js/EditorUi.js | 2 +- examples/grapheditor/www/js/Graph.js | 12 +- examples/grapheditor/www/js/Menus.js | 2 +- examples/grapheditor/www/js/Sidebar.js | 12 +- examples/grapheditor/www/viewer.html | 2 +- examples/images.html | 2 +- examples/map.html | 2 +- examples/ports.html | 8 +- examples/schema.html | 4 +- src/js/editor/mxEditor.js | 2 +- src/js/handler/mxConnectionHandler.js | 4056 +++++++-------- src/js/handler/mxConstraintHandler.js | 904 ++-- src/js/handler/mxEdgeHandler.js | 4490 ++++++++--------- src/js/handler/mxEdgeSegmentHandler.js | 731 ++- src/js/handler/mxElbowEdgeHandler.js | 389 +- src/js/handler/mxGraphHandler.js | 3364 ++++++------ src/js/handler/mxHandle.js | 617 ++- src/js/handler/mxKeyHandler.js | 745 ++- src/js/handler/mxPanningHandler.js | 869 ++-- src/js/handler/mxSelectionCellsHandler.js | 569 +-- src/js/handler/mxTooltipHandler.js | 608 ++- src/js/io/mxCellCodec.js | 74 +- src/js/io/mxChildChangeCodec.js | 6 +- src/js/io/mxCodec.js | 1091 ++-- src/js/io/mxCodecRegistry.js | 65 +- src/js/io/mxDefaultKeyHandlerCodec.js | 4 + src/js/io/mxDefaultPopupMenuCodec.js | 3 + src/js/io/mxDefaultToolbarCodec.js | 4 + src/js/io/mxEditorCodec.js | 109 +- src/js/io/mxGenericChangeCodec.js | 85 +- src/js/io/mxGraphCodec.js | 18 +- src/js/io/mxGraphViewCodec.js | 124 +- src/js/io/mxModelCodec.js | 5 +- src/js/io/mxObjectCodec.js | 24 +- src/js/io/mxRootChangeCodec.js | 1 + src/js/io/mxStylesheetCodec.js | 6 +- src/js/io/mxTerminalChangeCodec.js | 24 +- .../model/mxGraphHierarchyModel.js | 2 +- .../hierarchical/model/mxSwimlaneModel.js | 4 +- .../stage/mxCoordinateAssignment.js | 2 +- .../stage/mxMinimumCycleRemover.js | 4 +- .../hierarchical/stage/mxSwimlaneOrdering.js | 2 +- src/js/layout/mxCompactTreeLayout.js | 10 +- src/js/model/mxGraphModel.js | 6 +- src/js/util/mxClipboard.js | 2 +- src/js/util/mxXmlRequest.js | 805 ++- src/js/view/mxCellRenderer.js | 2 +- src/js/view/mxGraph.js | 8 +- src/js/view/mxStylesheet.js | 10 +- 61 files changed, 9334 insertions(+), 10586 deletions(-) diff --git a/docs/js-api/files/editor/mxEditor-js.html b/docs/js-api/files/editor/mxEditor-js.html index 2407bc44b..a05351486 100644 --- a/docs/js-api/files/editor/mxEditor-js.html +++ b/docs/js-api/files/editor/mxEditor-js.html @@ -40,7 +40,7 @@ echo($xml);
To open a local file, the file should be upload <mxDefaultToolbar> <add as="save" action="save" icon="images/save.gif"/> <add as="Swimlane" template="swimlane" icon="images/swimlane.gif"/> - ...
The format of the configuration is described in mxDefaultToolbarCodec.decode.
For the IDs, there is an implicit behaviour in mxCodec: It moves the Id from the cell to the user object at encoding time and vice versa at decoding time. For example, if the Task node from above has an id attribute, then the mxCell.id of the corresponding cell will have this value. If there is no Id collision in the model, then the cell may be retrieved using this Id with the mxGraphModel.getCell function. If there is a collision, a new Id will be created for the cell using mxGraphModel.createId. At encoding time, this new Id will replace the value previously stored under the id attribute in the Task node.
See mxEditorCodec, mxDefaultToolbarCodec and mxDefaultPopupMenuCodec for information about configuring the editor and user interface.
For inserting a new cell, say, by clicking a button in the document, the following code can be used. This requires an reference to the editor.
var userObject = new Object(); + ...
The format of the configuration is described in mxDefaultToolbarCodec.decode.
For the IDs, there is an implicit behaviour in mxCodec: It moves the Id from the cell to the user object at encoding time and vice versa at decoding time. For example, if the Task node from above has an id attribute, then the mxCell.id of the corresponding cell will have this value. If there is no Id collision in the model, then the cell may be retrieved using this Id with the mxGraphModel.getCell function. If there is a collision, a new Id will be created for the cell using mxGraphModel.createId. At encoding time, this new Id will replace the value previously stored under the id attribute in the Task node.
See mxEditorCodec, mxDefaultToolbarCodec and mxDefaultPopupMenuCodec for information about configuring the editor and user interface.
For inserting a new cell, say, by clicking a button in the document, the following code can be used. This requires an reference to the editor.
var userObject = {}; var parent = editor.graph.getDefaultParent(); var model = editor.graph.model; model.beginUpdate(); diff --git a/docs/js-api/files/io/mxObjectCodec-js.html b/docs/js-api/files/io/mxObjectCodec-js.html index 17b1b4e06..0169bee55 100644 --- a/docs/js-api/files/io/mxObjectCodec-js.html +++ b/docs/js-api/files/io/mxObjectCodec-js.html @@ -11,7 +11,7 @@ if (browserType) {document.write("");if (browserV -mxObjectCodec
Generic codec for JavaScript objects that implements a mapping between JavaScript objects and XML nodes that maps each field or element to an attribute or child node, and vice versa.
Atomic Values
Consider the following example.
var obj = new Object(); +mxObjectCodec
Generic codec for JavaScript objects that implements a mapping between JavaScript objects and XML nodes that maps each field or element to an attribute or child node, and vice versa.
Atomic Values
Consider the following example.
var obj = {}; obj.foo = "Foo"; obj.bar = "Bar";This object is encoded into an XML node using the following.
var enc = new mxCodec(); var node = enc.encode(obj);The output of the encoding may be viewed using mxLog as follows.
mxLog.show(); @@ -29,7 +29,7 @@ obj["foo"] = {bar: "Bar"};This array i
-mxObjectCodec
-
function mxObjectCodec( template, exclude, idrefs, mapping ) Constructs a new codec for the specified template object. The variables in the optional exclude array are ignored by the codec. Variables in the optional idrefs array are turned into references in the XML. The optional mapping may be used to map from variable names to XML attributes. The argument is created as follows:
var mapping = new Object(); +diff --git a/docs/js-api/files/util/mxClipboard-js.html b/docs/js-api/files/util/mxClipboard-js.html index 999dce300..eae8d182b 100644 --- a/docs/js-api/files/util/mxClipboard-js.html +++ b/docs/js-api/files/util/mxClipboard-js.html @@ -17,7 +17,7 @@ mxClipboard.paste(graph2);mxObjectCodec
function mxObjectCodec( template, exclude, idrefs, mapping ) Constructs a new codec for the specified template object. The variables in the optional exclude array are ignored by the codec. Variables in the optional idrefs array are turned into references in the XML. The optional mapping may be used to map from variable names to XML attributes. The argument is created as follows:
var mapping = {}; mapping['variableName'] = 'attribute-name';Parameters
template Prototypical instance of the object to be encoded/decoded. exclude Optional array of fieldnames to be ignored. idrefs Optional array of fieldnames to be converted to/from references. mapping Optional mapping from field- to attributenames. This copies the selection cells cells = cells || graph.getSelectionCells(); var result = graph.getExportableCells(cells); - mxClipboard.parents = new Object(); + mxClipboard.parents = {}; for (var i = 0; i < result.length; i++) { diff --git a/docs/js-api/files/view/mxGraph-js.html b/docs/js-api/files/view/mxGraph-js.html index 1b340ae77..990c2c908 100644 --- a/docs/js-api/files/view/mxGraph-js.html +++ b/docs/js-api/files/view/mxGraph-js.html @@ -27,7 +27,7 @@ style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_IMAGE;
F var label = this.convertValueToString(cell); return 'Tooltip for '+label; }
Shapes & Styles
The implementation of new shapes is demonstrated in the examples. We’ll assume that we have implemented a custom shape with the name BoxShape which we want to use for drawing vertices. To use this shape, it must first be registered in the cell renderer as follows:
mxCellRenderer.registerShape('box', BoxShape);The code registers the BoxShape constructor under the name box in the cell renderer of the graph. The shape can now be referenced using the shape-key in a style definition. (The cell renderer contains a set of additional shapes, namely one for each constant with a SHAPE-prefix in mxConstants.)
Styles are a collection of key, value pairs and a stylesheet is a collection of named styles. The names are referenced by the cellstyle, which is stored in mxCell.style with the following format: [stylename;|key=value;]. The string is resolved to a collection of key, value pairs, where the keys are overridden with the values in the string.
When introducing a new shape, the name under which the shape is registered must be used in the stylesheet. There are three ways of doing this:
- By changing the default style, so that all vertices will use the new shape
- By defining a new style, so that only vertices with the respective cellstyle will use the new shape
- By using shape=box in the cellstyle’s optional list of key, value pairs to be overridden
In the first case, the code to fetch and modify the default style for vertices is as follows:
var style = graph.getStylesheet().getDefaultVertexStyle(); -style[mxConstants.STYLE_SHAPE] = 'box';The code takes the default vertex style, which is used for all vertices that do not have a specific cellstyle, and modifies the value for the shape-key in-place to use the new BoxShape for drawing vertices. This is done by assigning the box value in the second line, which refers to the name of the BoxShape in the cell renderer.
In the second case, a collection of key, value pairs is created and then added to the stylesheet under a new name. In order to distinguish the shapename and the stylename we’ll use boxstyle for the stylename:
var style = new Object(); +style[mxConstants.STYLE_SHAPE] = 'box';The code takes the default vertex style, which is used for all vertices that do not have a specific cellstyle, and modifies the value for the shape-key in-place to use the new BoxShape for drawing vertices. This is done by assigning the box value in the second line, which refers to the name of the BoxShape in the cell renderer.
In the second case, a collection of key, value pairs is created and then added to the stylesheet under a new name. In order to distinguish the shapename and the stylename we’ll use boxstyle for the stylename:
var style = {}; style[mxConstants.STYLE_SHAPE] = 'box'; style[mxConstants.STYLE_STROKECOLOR] = '#000000'; style[mxConstants.STYLE_FONTCOLOR] = '#000000'; diff --git a/docs/js-api/files/view/mxStylesheet-js.html b/docs/js-api/files/view/mxStylesheet-js.html index 01d539e5d..a0a1cd097 100644 --- a/docs/js-api/files/view/mxStylesheet-js.html +++ b/docs/js-api/files/view/mxStylesheet-js.html @@ -34,7 +34,7 @@ edgeStyle[mxConstants.STYLE_EDGE] = mxEdgeStyle.EntityRelation;putCellStyle
mxStylesheet.prototype.putCellStyle = function( name, style ) Stores the given map of key, value pairs under the given name in styles.
Example
The following example adds a new style called ‘rounded’ into an existing stylesheet:
var style = new Object(); +putCellStyle
mxStylesheet.prototype.putCellStyle = function( name, style ) Stores the given map of key, value pairs under the given name in styles.
Example
The following example adds a new style called ‘rounded’ into an existing stylesheet:
var style = {}; style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE; style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter; style[mxConstants.STYLE_ROUNDED] = true; diff --git a/docs/manual.html b/docs/manual.html index 09a4eea1c..b70b6fc41 100644 --- a/docs/manual.html +++ b/docs/manual.html @@ -1141,7 +1141,7 @@ follow this template to create a style and register it with mxStyleSheet:-var style = new Object(); +var style = {}; style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE; style[mxConstants.STYLE_OPACITY] = 50; style[mxConstants.STYLE_FONTCOLOR]= '#774400'; diff --git a/docs/tutorial.html b/docs/tutorial.html index 218ce5800..8bffc1946 100644 --- a/docs/tutorial.html +++ b/docs/tutorial.html @@ -424,9 +424,9 @@ var editor = new mxEditor(config); For example, consider the following JavaScript object definition:-var object = new Object(); +var object = {}; object.myBool = true; -object.myObject = new Object(); +object.myObject = {}; object.myObject.name = 'Test'; object.myArray = ['a', ['b', 'c'], 'd'];diff --git a/examples/editing.html b/examples/editing.html index bcec192b1..e4fc53b7c 100644 --- a/examples/editing.html +++ b/examples/editing.html @@ -127,7 +127,7 @@ }; // Sample user objects with 2 fields - var value = new Object(); + var value = {}; value.first = 'First value'; value.second = 'Second value'; diff --git a/examples/editors/diagrameditor.html b/examples/editors/diagrameditor.html index e53fbb6a3..fd991e282 100644 --- a/examples/editors/diagrameditor.html +++ b/examples/editors/diagrameditor.html @@ -11,7 +11,7 @@ var urlParams = (function(url) { - var result = new Object(); + var result = {}; var params = window.location.search.slice(1).split('&'); for (var i = 0; i < params.length; i++) diff --git a/examples/grapheditor/www/index.html b/examples/grapheditor/www/index.html index eeed42862..54fff46fe 100644 --- a/examples/grapheditor/www/index.html +++ b/examples/grapheditor/www/index.html @@ -14,7 +14,7 @@ // - chrome=0: Chromeless mode. var urlParams = (function(url) { - var result = new Object(); + var result = {}; var idx = url.lastIndexOf('?'); if (idx > 0) @@ -95,7 +95,7 @@ mxResources.parse(xhr[0].getText()); // Configures the default graph theme - var themes = new Object(); + var themes = {}; themes[Graph.prototype.defaultThemeName] = xhr[1].getDocumentElement(); // Main diff --git a/examples/grapheditor/www/js/Actions.js b/examples/grapheditor/www/js/Actions.js index 9edfdb3f8..c1c0228a4 100644 --- a/examples/grapheditor/www/js/Actions.js +++ b/examples/grapheditor/www/js/Actions.js @@ -7,7 +7,7 @@ function Actions(editorUi) { this.editorUi = editorUi; - this.actions = new Object(); + this.actions = {}; this.init(); }; diff --git a/examples/grapheditor/www/js/Editor.js b/examples/grapheditor/www/js/Editor.js index 93a8ccc60..9ab78853d 100644 --- a/examples/grapheditor/www/js/Editor.js +++ b/examples/grapheditor/www/js/Editor.js @@ -1697,7 +1697,7 @@ PageSetupDialog.addPageFormatPanel = function(div, namePostfix, pageFormat, page formatDiv.style.display = 'none'; customDiv.style.display = 'none'; - var pf = new Object(); + var pf = {}; var formats = PageSetupDialog.getFormats(); for (var i = 0; i < formats.length; i++) diff --git a/examples/grapheditor/www/js/EditorUi.js b/examples/grapheditor/www/js/EditorUi.js index c93bdbddb..2cf309a22 100644 --- a/examples/grapheditor/www/js/EditorUi.js +++ b/examples/grapheditor/www/js/EditorUi.js @@ -1626,7 +1626,7 @@ EditorUi.prototype.initClipboard = function() result = result || graph.getSelectionCells(); result = graph.getExportableCells(graph.model.getTopmostCells(result)); - var cloneMap = new Object(); + var cloneMap = {}; var lookup = graph.createCellLookup(result); var clones = graph.cloneCells(result, null, cloneMap); diff --git a/examples/grapheditor/www/js/Graph.js b/examples/grapheditor/www/js/Graph.js index df3f445e3..fd0d924e0 100644 --- a/examples/grapheditor/www/js/Graph.js +++ b/examples/grapheditor/www/js/Graph.js @@ -6356,7 +6356,7 @@ if (typeof mxVertexHandler != 'undefined') */ Graph.prototype.createCellLookup = function(cells, lookup) { - lookup = (lookup != null) ? lookup : new Object(); + lookup = (lookup != null) ? lookup : {}; for (var i = 0; i < cells.length; i++) { @@ -6380,7 +6380,7 @@ if (typeof mxVertexHandler != 'undefined') */ Graph.prototype.createCellMapping = function(mapping, lookup, cellMapping) { - cellMapping = (cellMapping != null) ? cellMapping : new Object(); + cellMapping = (cellMapping != null) ? cellMapping : {}; for (var objectId in mapping) { @@ -6411,8 +6411,8 @@ if (typeof mxVertexHandler != 'undefined') var cells = [] // Clones cells to remove invalid edges - var cloneMap = new Object(); - var cellMapping = new Object(); + var cloneMap = {}; + var cellMapping = {}; var layers = tempModel.getChildren(this.cloneCell(tempModel.root, this.isCloneInvalidEdges(), cloneMap)); @@ -6496,7 +6496,7 @@ if (typeof mxVertexHandler != 'undefined') */ Graph.prototype.encodeCells = function(cells) { - var cloneMap = new Object(); + var cloneMap = {}; var clones = this.cloneCells(cells, null, cloneMap); // Creates a dictionary for fast lookups @@ -6664,7 +6664,7 @@ if (typeof mxVertexHandler != 'undefined') var graphMoveCells = Graph.prototype.moveCells; Graph.prototype.moveCells = function(cells, dx, dy, clone, target, evt, mapping) { - mapping = (mapping != null) ? mapping : new Object(); + mapping = (mapping != null) ? mapping : {}; // Replaces source tables with rows if (this.isTable(target)) diff --git a/examples/grapheditor/www/js/Menus.js b/examples/grapheditor/www/js/Menus.js index d3e88f094..fbef377ce 100644 --- a/examples/grapheditor/www/js/Menus.js +++ b/examples/grapheditor/www/js/Menus.js @@ -7,7 +7,7 @@ Menus = function(editorUi) { this.editorUi = editorUi; - this.menus = new Object(); + this.menus = {}; this.init(); // Pre-fetches checkmark image diff --git a/examples/grapheditor/www/js/Sidebar.js b/examples/grapheditor/www/js/Sidebar.js index d841ae19b..acf921166 100644 --- a/examples/grapheditor/www/js/Sidebar.js +++ b/examples/grapheditor/www/js/Sidebar.js @@ -8,8 +8,8 @@ function Sidebar(editorUi, container) { this.editorUi = editorUi; this.container = container; - this.palettes = new Object(); - this.taglist = new Object(); + this.palettes = {}; + this.taglist = {}; this.showTooltips = true; this.graph = editorUi.createTemporaryGraph(this.editorUi.editor.graph.getStylesheet()); this.graph.cellRenderer.minSvgStrokeWidth = this.minThumbStrokeWidth; @@ -769,7 +769,7 @@ Sidebar.prototype.addSearchPalette = function(expand) var active = false; var complete = false; var page = 0; - var hash = new Object(); + var hash = {}; // Count is dynamically updated below var count = 12; @@ -822,7 +822,7 @@ Sidebar.prototype.addSearchPalette = function(expand) { clearDiv(); searchTerm = input.value; - hash = new Object(); + hash = {}; complete = false; page = 0; } @@ -836,7 +836,7 @@ Sidebar.prototype.addSearchPalette = function(expand) active = true; // Ignores old results - var current = new Object(); + var current = {}; this.currentSearch = current; this.searchEntries(searchTerm, count, page, mxUtils.bind(this, function(results, len, more, terms) @@ -926,7 +926,7 @@ Sidebar.prototype.addSearchPalette = function(expand) clearDiv(); input.value = ''; searchTerm = ''; - hash = new Object(); + hash = {}; button.style.display = 'none'; complete = false; input.focus(); diff --git a/examples/grapheditor/www/viewer.html b/examples/grapheditor/www/viewer.html index beb91b98f..ceaad0dd2 100644 --- a/examples/grapheditor/www/viewer.html +++ b/examples/grapheditor/www/viewer.html @@ -12,7 +12,7 @@ var urlParams = (function(url) { - var result = new Object(); + var result = {}; var idx = url.lastIndexOf('?'); if (idx > 0) diff --git a/examples/images.html b/examples/images.html index 5be29eded..c4bf9fe88 100644 --- a/examples/images.html +++ b/examples/images.html @@ -71,7 +71,7 @@ function configureStylesheet(graph) { - var style = new Object(); + var style = {}; style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_IMAGE; style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter; style[mxConstants.STYLE_IMAGE] = 'images/icons48/keys.png'; diff --git a/examples/map.html b/examples/map.html index d51c177da..73e2968cd 100644 --- a/examples/map.html +++ b/examples/map.html @@ -104,7 +104,7 @@ graph.view.setTranslate(4, 4); // Sets default vertex style - var style = new Object(); + var style = {}; style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_ELLIPSE; style[mxConstants.STYLE_PERIMETER] = mxPerimeter.EllipsePerimeter; style[mxConstants.STYLE_FILLCOLOR] = '#8CCDF5'; diff --git a/examples/ports.html b/examples/ports.html index aaac1d8a1..576f76b90 100644 --- a/examples/ports.html +++ b/examples/ports.html @@ -493,7 +493,7 @@ function configureStylesheet(graph) { - var style = new Object(); + var style = {}; style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE; style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter; style[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_CENTER; @@ -512,7 +512,7 @@ // NOTE: Alternative vertex style for non-HTML labels should be as // follows. This repaces the above style for HTML labels. - /*var style = new Object(); + /*var style = {}; style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_LABEL; style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter; style[mxConstants.STYLE_VERTICAL_ALIGN] = mxConstants.ALIGN_TOP; @@ -533,7 +533,7 @@ style[mxConstants.STYLE_OPACITY] = '80'; graph.getStylesheet().putDefaultVertexStyle(style);*/ - style = new Object(); + style = {}; style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_SWIMLANE; style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter; style[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_CENTER; @@ -549,7 +549,7 @@ style[mxConstants.STYLE_FONTSTYLE] = 1; graph.getStylesheet().putCellStyle('group', style); - style = new Object(); + style = {}; style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_IMAGE; style[mxConstants.STYLE_FONTCOLOR] = '#774400'; style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter; diff --git a/examples/schema.html b/examples/schema.html index 354fffb39..dceb27787 100644 --- a/examples/schema.html +++ b/examples/schema.html @@ -628,7 +628,7 @@ function configureStylesheet(graph) { - var style = new Object(); + var style = {}; style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE; style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter; style[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_LEFT; @@ -641,7 +641,7 @@ style[mxConstants.STYLE_IMAGE_HEIGHT] = '48'; graph.getStylesheet().putDefaultVertexStyle(style); - style = new Object(); + style = {}; style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_SWIMLANE; style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter; style[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_CENTER; diff --git a/src/js/editor/mxEditor.js b/src/js/editor/mxEditor.js index 1a6fbf52f..3e66af47c 100644 --- a/src/js/editor/mxEditor.js +++ b/src/js/editor/mxEditor.js @@ -250,7 +250,7 @@ class mxEditor extends mxEventSource { * the following code can be used. This requires an reference to the editor. * * (code) - * var userObject = new Object(); + * var userObject = {}; * var parent = editor.graph.getDefaultParent(); * var model = editor.graph.model; * model.beginUpdate(); diff --git a/src/js/handler/mxConnectionHandler.js b/src/js/handler/mxConnectionHandler.js index 903127931..1acb286d4 100644 --- a/src/js/handler/mxConnectionHandler.js +++ b/src/js/handler/mxConnectionHandler.js @@ -2,2248 +2,1994 @@ * Copyright (c) 2006-2016, JGraph Ltd * Copyright (c) 2006-2016, Gaudenz Alder */ -/** - * Class: mxConnectionHandler - * - * Graph event handler that creates new connections. Uses- * for finding and highlighting the source and target vertices and - * to create the edge instance. This handler is built-into - * and enabled using . - * - * Example: - * - * (code) - * new mxConnectionHandler(graph, (source, target, style)=> - * { - * edge = new mxCell('', new mxGeometry()); - * edge.setEdge(true); - * edge.setStyle(style); - * edge.geometry.relative = true; - * return edge; - * }); - * (end) - * - * Here is an alternative solution that just sets a specific user object for - * new edges by overriding . - * - * (code) - * mxConnectionHandlerInsertEdge = insertEdge; - * insertEdge = (parent, id, value, source, target, style)=> - * { - * value = 'Test'; - * - * return mxConnectionHandlerInsertEdge.apply(this, arguments); - * }; - * (end) - * - * Using images to trigger connections: - * - * This handler uses mxTerminalMarker to find the source and target cell for - * the new connection and creates a new edge using . The new edge is - * created using which in turn uses or creates a - * new default edge. - * - * The handler uses a "highlight-paradigm" for indicating if a cell is being - * used as a source or target terminal, as seen in other diagramming products. - * In order to allow both, moving and connecting cells at the same time, - * is used in the handler to determine the hotspot - * of a cell, that is, the region of the cell which is used to trigger a new - * connection. The constant is a value between 0 and 1 that specifies the - * amount of the width and height around the center to be used for the hotspot - * of a cell and its default value is 0.5. In addition, - * defines the minimum number of pixels for the - * width and height of the hotspot. - * - * This solution, while standards compliant, may be somewhat confusing because - * there is no visual indicator for the hotspot and the highlight is seen to - * switch on and off while the mouse is being moved in and out. Furthermore, - * this paradigm does not allow to create different connections depending on - * the highlighted hotspot as there is only one hotspot per cell and it - * normally does not allow cells to be moved and connected at the same time as - * there is no clear indication of the connectable area of the cell. - * - * To come across these issues, the handle has an additional hook - * with a default implementation that allows to create one icon to be used to - * trigger new connections. If this icon is specified, then new connections can - * only be created if the image is clicked while the cell is being highlighted. - * The hook may be overridden to create more than one - * for creating new connections, but the default implementation - * supports one image and is used as follows: - * - * In order to display the "connect image" whenever the mouse is over the cell, - * an DEFAULT_HOTSPOT of 1 should be used: - * - * (code) - * mxConstants.DEFAULT_HOTSPOT = 1; - * (end) - * - * In order to avoid confusion with the highlighting, the highlight color - * should not be used with a connect image: - * - * (code) - * mxConstants.HIGHLIGHT_COLOR = null; - * (end) - * - * To install the image, the connectImage field of the mxConnectionHandler must - * be assigned a new instance: - * - * (code) - * connectImage = new mxImage('images/green-dot.gif', 14, 14); - * (end) - * - * This will use the green-dot.gif with a width and height of 14 pixels as the - * image to trigger new connections. In createIcons the icon field of the - * handler will be set in order to remember the icon that has been clicked for - * creating the new connection. This field will be available under selectedIcon - * in the connect method, which may be overridden to take the icon that - * triggered the new connection into account. This is useful if more than one - * icon may be used to create a connection. - * - * Group: Events - * - * Event: mxEvent.START - * - * Fires when a new connection is being created by the user. The state
- * property contains the state of the source cell. - * - * Event: mxEvent.CONNECT - * - * Fires between begin- and endUpdate in. The cell
- * property contains the inserted edge, theevent
andtarget
- * properties contain the respective arguments that were passed to(where - * target corresponds to the dropTarget argument). Finally, the terminal
- * property corresponds to the target argument inor the clone of the source - * terminal if is enabled. - * - * Note that the target is the cell under the mouse where the mouse button was released. - * Depending on the logic in the handler, this doesn't necessarily have to be the target - * of the inserted edge. To print the source, target or any optional ports IDs that the - * edge is connected to, the following code can be used. To get more details about the - * actual connection point, can be used. To resolve - * the port IDs, use . - * - * (code) - * graph.connectionHandler.addListener(mxEvent.CONNECT, (sender, evt)=> - * { - * var edge = evt.getProperty('cell'); - * var source = graph.getModel().getTerminal(edge, true); - * var target = graph.getModel().getTerminal(edge, false); - * - * var style = graph.getCellStyle(edge); - * var sourcePortId = style[mxConstants.STYLE_SOURCE_PORT]; - * var targetPortId = style[mxConstants.STYLE_TARGET_PORT]; - * - * mxLog.show(); - * mxLog.debug('connect', edge, source.id, target.id, sourcePortId, targetPortId); - * }); - * (end) - * - * Event: mxEvent.RESET - * - * Fires when the method is invoked. - * - * Constructor: mxConnectionHandler - * - * Constructs an event handler that connects vertices using the specified - * factory method to create the new edges. Modify - * to setup the region on a cell which triggers - * the creation of a new connection or use connect icons as explained - * above. - * - * Parameters: - * - * graph - Reference to the enclosing . - * factoryMethod - Optional function to create the edge. The function takes - * the source and target as the first and second argument and an - * optional cell style from the preview as the third argument. It returns - * the that represents the new edge. - */ -function mxConnectionHandler(graph, factoryMethod) -{ - mxEventSource.call(this); - - if (graph != null) - { - this.graph = graph; - this.factoryMethod = factoryMethod; - this.init(); - - // Handles escape keystrokes - this.escapeHandler = mxUtils.bind(this, (sender, evt)=> - { + +class mxConnectionHandler extends mxEventSource { + /** + * Variable: graph + * + * Reference to the enclosing . + */ + graph = null; + + /** + * Variable: factoryMethod + * + * Function that is used for creating new edges. The function takes the + * source and target as the first and second argument and returns + * a new that represents the edge. This is used in . + */ + factoryMethod = true; + + /** + * Variable: moveIconFront + * + * Specifies if icons should be displayed inside the graph container instead + * of the overlay pane. This is used for HTML labels on vertices which hide + * the connect icon. This has precendence over when set + * to true. Default is false. + */ + moveIconFront = false; + + /** + * Variable: moveIconBack + * + * Specifies if icons should be moved to the back of the overlay pane. This can + * be set to true if the icons of the connection handler conflict with other + * handles, such as the vertex label move handle. Default is false. + */ + moveIconBack = false; + + /** + * Variable: connectImage + * + * that is used to trigger the creation of a new connection. This + * is used in . Default is null. + */ + connectImage = null; + + /** + * Variable: targetConnectImage + * + * Specifies if the connect icon should be centered on the target state + * while connections are being previewed. Default is false. + */ + targetConnectImage = false; + + /** + * Variable: enabled + * + * Specifies if events are handled. Default is true. + */ + enabled = true; + + /** + * Variable: select + * + * Specifies if new edges should be selected. Default is true. + */ + select = true; + + /** + * Variable: createTarget + * + * Specifies if should be called if no target was under the + * mouse for the new connection. Setting this to true means the connection + * will be drawn as valid if no target is under the mouse, and + * will be called before the connection is created between + * the source cell and the newly created vertex in , which + * can be overridden to create a new target. Default is false. + */ + createTarget = false; + + /** + * Variable: marker + * + * Holds the used for finding source and target cells. + */ + marker = null; + + /** + * Variable: constraintHandler + * + * Holds the used for drawing and highlighting + * constraints. + */ + constraintHandler = null; + + /** + * Variable: error + * + * Holds the current validation error while connections are being created. + */ + error = null; + + /** + * Variable: waypointsEnabled + * + * Specifies if single clicks should add waypoints on the new edge. Default is + * false. + */ + waypointsEnabled = false; + + /** + * Variable: ignoreMouseDown + * + * Specifies if the connection handler should ignore the state of the mouse + * button when highlighting the source. Default is false, that is, the + * handler only highlights the source if no button is being pressed. + */ + ignoreMouseDown = false; + + /** + * Variable: first + * + * Holds the where the mouseDown took place while the handler is + * active. + */ + first = null; + + /** + * Variable: connectIconOffset + * + * Holds the offset for connect icons during connection preview. + * Default is mxPoint(0, ). + * Note that placing the icon under the mouse pointer with an + * offset of (0,0) will affect hit detection. + */ + connectIconOffset = new mxPoint(0, mxConstants.TOOLTIP_VERTICAL_OFFSET); + + /** + * Variable: edgeState + * + * Optional that represents the preview edge while the + * handler is active. This is created in . + */ + edgeState = null; + + /** + * Variable: changeHandler + * + * Holds the change event listener for later removal. + */ + changeHandler = null; + + /** + * Variable: drillHandler + * + * Holds the drill event listener for later removal. + */ + drillHandler = null; + + /** + * Variable: mouseDownCounter + * + * Counts the number of mouseDown events since the start. The initial mouse + * down event counts as 1. + */ + mouseDownCounter = 0; + + /** + * Variable: movePreviewAway + * + * Switch to enable moving the preview away from the mousepointer. This is required in browsers + * where the preview cannot be made transparent to events and if the built-in hit detection on + * the HTML elements in the page should be used. Default is false. + */ + movePreviewAway = false; + + /** + * Variable: outlineConnect + * + * Specifies if connections to the outline of a highlighted target should be + * enabled. This will allow to place the connection point along the outline of + * the highlighted target. Default is false. + */ + outlineConnect = false; + + /** + * Variable: livePreview + * + * Specifies if the actual shape of the edge state should be used for the preview. + * Default is false. (Ignored if no edge state is created in .) + */ + livePreview = false; + + /** + * Variable: cursor + * + * Specifies the cursor to be used while the handler is active. Default is null. + */ + cursor = null; + + /** + * Variable: insertBeforeSource + * + * Specifies if new edges should be inserted before the source vertex in the + * cell hierarchy. Default is false for backwards compatibility. + */ + insertBeforeSource = false; + + /** + * Class: mxConnectionHandler + * + * Graph event handler that creates new connections. Uses + * for finding and highlighting the source and target vertices and + * to create the edge instance. This handler is built-into + * and enabled using . + * + * Example: + * + * (code) + * new mxConnectionHandler(graph, (source, target, style)=> + * { + * edge = new mxCell('', new mxGeometry()); + * edge.setEdge(true); + * edge.setStyle(style); + * edge.geometry.relative = true; + * return edge; + * }); + * (end) + * + * Here is an alternative solution that just sets a specific user object for + * new edges by overriding . + * + * (code) + * mxConnectionHandlerInsertEdge = insertEdge; + * insertEdge = (parent, id, value, source, target, style)=> + * { + * value = 'Test'; + * + * return mxConnectionHandlerInsertEdge.apply(this, arguments); + * }; + * (end) + * + * Using images to trigger connections: + * + * This handler uses mxTerminalMarker to find the source and target cell for + * the new connection and creates a new edge using . The new edge is + * created using which in turn uses or creates a + * new default edge. + * + * The handler uses a "highlight-paradigm" for indicating if a cell is being + * used as a source or target terminal, as seen in other diagramming products. + * In order to allow both, moving and connecting cells at the same time, + * is used in the handler to determine the hotspot + * of a cell, that is, the region of the cell which is used to trigger a new + * connection. The constant is a value between 0 and 1 that specifies the + * amount of the width and height around the center to be used for the hotspot + * of a cell and its default value is 0.5. In addition, + * defines the minimum number of pixels for the + * width and height of the hotspot. + * + * This solution, while standards compliant, may be somewhat confusing because + * there is no visual indicator for the hotspot and the highlight is seen to + * switch on and off while the mouse is being moved in and out. Furthermore, + * this paradigm does not allow to create different connections depending on + * the highlighted hotspot as there is only one hotspot per cell and it + * normally does not allow cells to be moved and connected at the same time as + * there is no clear indication of the connectable area of the cell. + * + * To come across these issues, the handle has an additional hook + * with a default implementation that allows to create one icon to be used to + * trigger new connections. If this icon is specified, then new connections can + * only be created if the image is clicked while the cell is being highlighted. + * The hook may be overridden to create more than one + * for creating new connections, but the default implementation + * supports one image and is used as follows: + * + * In order to display the "connect image" whenever the mouse is over the cell, + * an DEFAULT_HOTSPOT of 1 should be used: + * + * (code) + * mxConstants.DEFAULT_HOTSPOT = 1; + * (end) + * + * In order to avoid confusion with the highlighting, the highlight color + * should not be used with a connect image: + * + * (code) + * mxConstants.HIGHLIGHT_COLOR = null; + * (end) + * + * To install the image, the connectImage field of the mxConnectionHandler must + * be assigned a new instance: + * + * (code) + * connectImage = new mxImage('images/green-dot.gif', 14, 14); + * (end) + * + * This will use the green-dot.gif with a width and height of 14 pixels as the + * image to trigger new connections. In createIcons the icon field of the + * handler will be set in order to remember the icon that has been clicked for + * creating the new connection. This field will be available under selectedIcon + * in the connect method, which may be overridden to take the icon that + * triggered the new connection into account. This is useful if more than one + * icon may be used to create a connection. + * + * Group: Events + * + * Event: mxEvent.START + * + * Fires when a new connection is being created by the user. The state
+ * property contains the state of the source cell. + * + * Event: mxEvent.CONNECT + * + * Fires between begin- and endUpdate in. The cell
+ * property contains the inserted edge, theevent
andtarget
+ * properties contain the respective arguments that were passed to(where + * target corresponds to the dropTarget argument). Finally, the terminal
+ * property corresponds to the target argument inor the clone of the source + * terminal if is enabled. + * + * Note that the target is the cell under the mouse where the mouse button was released. + * Depending on the logic in the handler, this doesn't necessarily have to be the target + * of the inserted edge. To print the source, target or any optional ports IDs that the + * edge is connected to, the following code can be used. To get more details about the + * actual connection point, can be used. To resolve + * the port IDs, use . + * + * (code) + * graph.connectionHandler.addListener(mxEvent.CONNECT, (sender, evt)=> + * { + * var edge = evt.getProperty('cell'); + * var source = graph.getModel().getTerminal(edge, true); + * var target = graph.getModel().getTerminal(edge, false); + * + * var style = graph.getCellStyle(edge); + * var sourcePortId = style[mxConstants.STYLE_SOURCE_PORT]; + * var targetPortId = style[mxConstants.STYLE_TARGET_PORT]; + * + * mxLog.show(); + * mxLog.debug('connect', edge, source.id, target.id, sourcePortId, targetPortId); + * }); + * (end) + * + * Event: mxEvent.RESET + * + * Fires when the method is invoked. + * + * Constructor: mxConnectionHandler + * + * Constructs an event handler that connects vertices using the specified + * factory method to create the new edges. Modify + * to setup the region on a cell which triggers + * the creation of a new connection or use connect icons as explained + * above. + * + * Parameters: + * + * graph - Reference to the enclosing . + * factoryMethod - Optional function to create the edge. The function takes + * the source and target as the first and second argument and an + * optional cell style from the preview as the third argument. It returns + * the that represents the new edge. + */ + constructor(graph, factoryMethod) { + super(); + + if (graph != null) { + this.graph = graph; + this.factoryMethod = factoryMethod; + this.init(); + + // Handles escape keystrokes + this.escapeHandler = mxUtils.bind(this, (sender, evt) => { + this.reset(); + }); + + this.graph.addListener(mxEvent.ESCAPE, this.escapeHandler); + } + }; + + /** + * Function: isEnabled + * + * Returns true if events are handled. This implementation + * returns . + */ + isEnabled = () => { + return this.enabled; + }; + + /** + * Function: setEnabled + * + * Enables or disables event handling. This implementation + * updates . + * + * Parameters: + * + * enabled - Boolean that specifies the new enabled state. + */ + setEnabled = (enabled) => { + this.enabled = enabled; + }; + + /** + * Function: isInsertBefore + * + * Returns for non-loops and false for loops. + * + * Parameters: + * + * edge - that represents the edge to be inserted. + * source - that represents the source terminal. + * target - that represents the target terminal. + * evt - Mousedown event of the connect gesture. + * dropTarget - that represents the cell under the mouse when it was + * released. + */ + isInsertBefore = (edge, source, target, evt, dropTarget) => { + return this.insertBeforeSource && source != target; + }; + + /** + * Function: isCreateTarget + * + * Returns . + * + * Parameters: + * + * evt - Current active native pointer event. + */ + isCreateTarget = (evt) => { + return this.createTarget; + }; + + /** + * Function: setCreateTarget + * + * Sets . + */ + setCreateTarget = (value) => { + this.createTarget = value; + }; + + /** + * Function: createShape + * + * Creates the preview shape for new connections. + */ + createShape = () => { + // Creates the edge preview + var shape = (this.livePreview && this.edgeState != null) ? + this.graph.cellRenderer.createShape(this.edgeState) : + new mxPolyline([], mxConstants.INVALID_COLOR); + shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? + mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG; + shape.scale = this.graph.view.scale; + shape.pointerEvents = false; + shape.isDashed = true; + shape.init(this.graph.getView().getOverlayPane()); + mxEvent.redirectMouseEvents(shape.node, this.graph, null); + + return shape; + }; + + /** + * Function: init + * + * Initializes the shapes required for this connection handler. This should + * be invoked if is assigned after the connection + * handler has been created. + */ + init = () => { + this.graph.addMouseListener(this); + this.marker = this.createMarker(); + this.constraintHandler = new mxConstraintHandler(this.graph); + + // Redraws the icons if the graph changes + this.changeHandler = mxUtils.bind(this, (sender) => { + if (this.iconState != null) { + this.iconState = this.graph.getView().getState(this.iconState.cell); + } + + if (this.iconState != null) { + this.redrawIcons(this.icons, this.iconState); + this.constraintHandler.reset(); + } else if (this.previous != null && this.graph.view.getState(this.previous.cell) == null) { + this.reset(); + } + }); + + this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler); + this.graph.getView().addListener(mxEvent.SCALE, this.changeHandler); + this.graph.getView().addListener(mxEvent.TRANSLATE, this.changeHandler); + this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, this.changeHandler); + + // Removes the icon if we step into/up or start editing + this.drillHandler = mxUtils.bind(this, (sender) => { this.reset(); }); - - this.graph.addListener(mxEvent.ESCAPE, this.escapeHandler); - } -}; -/** - * Extends mxEventSource. - */ -mxUtils.extend(mxConnectionHandler, mxEventSource); + this.graph.addListener(mxEvent.START_EDITING, this.drillHandler); + this.graph.getView().addListener(mxEvent.DOWN, this.drillHandler); + this.graph.getView().addListener(mxEvent.UP, this.drillHandler); + }; -/** - * Variable: graph - * - * Reference to the enclosing . - */ -graph = null; + /** + * Function: isConnectableCell + * + * Returns true if the given cell is connectable. This is a hook to + * disable floating connections. This implementation returns true. + */ + isConnectableCell = (cell) => { + return true; + }; -/** - * Variable: factoryMethod - * - * Function that is used for creating new edges. The function takes the - * source and target as the first and second argument and returns - * a new that represents the edge. This is used in . - */ -factoryMethod = true; + /** + * Function: createMarker + * + * Creates and returns the used in . + */ + createMarker = () => { + var marker = new mxCellMarker(this.graph); + marker.hotspotEnabled = true; -/** - * Variable: moveIconFront - * - * Specifies if icons should be displayed inside the graph container instead - * of the overlay pane. This is used for HTML labels on vertices which hide - * the connect icon. This has precendence over when set - * to true. Default is false. - */ -moveIconFront = false; + // Overrides to return cell at location only if valid (so that + // there is no highlight for invalid cells) + marker.getCell = mxUtils.bind(this, (me) => { + var cell = getCell.apply(marker, arguments); + this.error = null; -/** - * Variable: moveIconBack - * - * Specifies if icons should be moved to the back of the overlay pane. This can - * be set to true if the icons of the connection handler conflict with other - * handles, such as the vertex label move handle. Default is false. - */ -moveIconBack = false; - -/** - * Variable: connectImage - * - * that is used to trigger the creation of a new connection. This - * is used in . Default is null. - */ -connectImage = null; - -/** - * Variable: targetConnectImage - * - * Specifies if the connect icon should be centered on the target state - * while connections are being previewed. Default is false. - */ -targetConnectImage = false; - -/** - * Variable: enabled - * - * Specifies if events are handled. Default is true. - */ -enabled = true; - -/** - * Variable: select - * - * Specifies if new edges should be selected. Default is true. - */ -select = true; - -/** - * Variable: createTarget - * - * Specifies if should be called if no target was under the - * mouse for the new connection. Setting this to true means the connection - * will be drawn as valid if no target is under the mouse, and - * will be called before the connection is created between - * the source cell and the newly created vertex in , which - * can be overridden to create a new target. Default is false. - */ -createTarget = false; - -/** - * Variable: marker - * - * Holds the used for finding source and target cells. - */ -marker = null; - -/** - * Variable: constraintHandler - * - * Holds the used for drawing and highlighting - * constraints. - */ -constraintHandler = null; - -/** - * Variable: error - * - * Holds the current validation error while connections are being created. - */ -error = null; - -/** - * Variable: waypointsEnabled - * - * Specifies if single clicks should add waypoints on the new edge. Default is - * false. - */ -waypointsEnabled = false; - -/** - * Variable: ignoreMouseDown - * - * Specifies if the connection handler should ignore the state of the mouse - * button when highlighting the source. Default is false, that is, the - * handler only highlights the source if no button is being pressed. - */ -ignoreMouseDown = false; - -/** - * Variable: first - * - * Holds the where the mouseDown took place while the handler is - * active. - */ -first = null; - -/** - * Variable: connectIconOffset - * - * Holds the offset for connect icons during connection preview. - * Default is mxPoint(0, ). - * Note that placing the icon under the mouse pointer with an - * offset of (0,0) will affect hit detection. - */ -connectIconOffset = new mxPoint(0, mxConstants.TOOLTIP_VERTICAL_OFFSET); - -/** - * Variable: edgeState - * - * Optional that represents the preview edge while the - * handler is active. This is created in . - */ -edgeState = null; - -/** - * Variable: changeHandler - * - * Holds the change event listener for later removal. - */ -changeHandler = null; - -/** - * Variable: drillHandler - * - * Holds the drill event listener for later removal. - */ -drillHandler = null; - -/** - * Variable: mouseDownCounter - * - * Counts the number of mouseDown events since the start. The initial mouse - * down event counts as 1. - */ -mouseDownCounter = 0; - -/** - * Variable: movePreviewAway - * - * Switch to enable moving the preview away from the mousepointer. This is required in browsers - * where the preview cannot be made transparent to events and if the built-in hit detection on - * the HTML elements in the page should be used. Default is false. - */ -movePreviewAway = false; - -/** - * Variable: outlineConnect - * - * Specifies if connections to the outline of a highlighted target should be - * enabled. This will allow to place the connection point along the outline of - * the highlighted target. Default is false. - */ -outlineConnect = false; - -/** - * Variable: livePreview - * - * Specifies if the actual shape of the edge state should be used for the preview. - * Default is false. (Ignored if no edge state is created in .) - */ -livePreview = false; - -/** - * Variable: cursor - * - * Specifies the cursor to be used while the handler is active. Default is null. - */ -cursor = null; - -/** - * Variable: insertBeforeSource - * - * Specifies if new edges should be inserted before the source vertex in the - * cell hierarchy. Default is false for backwards compatibility. - */ -insertBeforeSource = false; - -/** - * Function: isEnabled - * - * Returns true if events are handled. This implementation - * returns . - */ -isEnabled = ()=> -{ - return this.enabled; -}; - -/** - * Function: setEnabled - * - * Enables or disables event handling. This implementation - * updates . - * - * Parameters: - * - * enabled - Boolean that specifies the new enabled state. - */ -setEnabled = (enabled)=> -{ - this.enabled = enabled; -}; - -/** - * Function: isInsertBefore - * - * Returns for non-loops and false for loops. - * - * Parameters: - * - * edge - that represents the edge to be inserted. - * source - that represents the source terminal. - * target - that represents the target terminal. - * evt - Mousedown event of the connect gesture. - * dropTarget - that represents the cell under the mouse when it was - * released. - */ -isInsertBefore = (edge, source, target, evt, dropTarget)=> -{ - return this.insertBeforeSource && source != target; -}; - -/** - * Function: isCreateTarget - * - * Returns . - * - * Parameters: - * - * evt - Current active native pointer event. - */ -isCreateTarget = (evt)=> -{ - return this.createTarget; -}; - -/** - * Function: setCreateTarget - * - * Sets . - */ -setCreateTarget = (value)=> -{ - this.createTarget = value; -}; - -/** - * Function: createShape - * - * Creates the preview shape for new connections. - */ -createShape = ()=> -{ - // Creates the edge preview - var shape = (this.livePreview && this.edgeState != null) ? - this.graph.cellRenderer.createShape(this.edgeState) : - new mxPolyline([], mxConstants.INVALID_COLOR); - shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? - mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG; - shape.scale = this.graph.view.scale; - shape.pointerEvents = false; - shape.isDashed = true; - shape.init(this.graph.getView().getOverlayPane()); - mxEvent.redirectMouseEvents(shape.node, this.graph, null); - - return shape; -}; - -/** - * Function: init - * - * Initializes the shapes required for this connection handler. This should - * be invoked if is assigned after the connection - * handler has been created. - */ -init = ()=> -{ - this.graph.addMouseListener(this); - this.marker = this.createMarker(); - this.constraintHandler = new mxConstraintHandler(this.graph); - - // Redraws the icons if the graph changes - this.changeHandler = mxUtils.bind(this, (sender)=> - { - if (this.iconState != null) - { - this.iconState = this.graph.getView().getState(this.iconState.cell); - } - - if (this.iconState != null) - { - this.redrawIcons(this.icons, this.iconState); - this.constraintHandler.reset(); - } - else if (this.previous != null && this.graph.view.getState(this.previous.cell) == null) - { - this.reset(); - } - }); - - this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler); - this.graph.getView().addListener(mxEvent.SCALE, this.changeHandler); - this.graph.getView().addListener(mxEvent.TRANSLATE, this.changeHandler); - this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, this.changeHandler); - - // Removes the icon if we step into/up or start editing - this.drillHandler = mxUtils.bind(this, (sender)=> - { - this.reset(); - }); - - this.graph.addListener(mxEvent.START_EDITING, this.drillHandler); - this.graph.getView().addListener(mxEvent.DOWN, this.drillHandler); - this.graph.getView().addListener(mxEvent.UP, this.drillHandler); -}; - -/** - * Function: isConnectableCell - * - * Returns true if the given cell is connectable. This is a hook to - * disable floating connections. This implementation returns true. - */ -isConnectableCell = (cell)=> -{ - return true; -}; - -/** - * Function: createMarker - * - * Creates and returns the used in . - */ -createMarker = ()=> -{ - var marker = new mxCellMarker(this.graph); - marker.hotspotEnabled = true; - - // Overrides to return cell at location only if valid (so that - // there is no highlight for invalid cells) - marker.getCell = mxUtils.bind(this, (me)=> - { - var cell = getCell.apply(marker, arguments); - this.error = null; - - // Checks for cell at preview point (with grid) - if (cell == null && this.currentPoint != null) - { - cell = this.graph.getCellAt(this.currentPoint.x, this.currentPoint.y); - } - - // Uses connectable parent vertex if one exists - if (cell != null && !this.graph.isCellConnectable(cell)) - { - var parent = this.graph.getModel().getParent(cell); - - if (this.graph.getModel().isVertex(parent) && this.graph.isCellConnectable(parent)) - { - cell = parent; + // Checks for cell at preview point (with grid) + if (cell == null && this.currentPoint != null) { + cell = this.graph.getCellAt(this.currentPoint.x, this.currentPoint.y); } - } - - if ((this.graph.isSwimlane(cell) && this.currentPoint != null && - this.graph.hitsSwimlaneContent(cell, this.currentPoint.x, this.currentPoint.y)) || - !this.isConnectableCell(cell)) - { - cell = null; - } - - if (cell != null) - { - if (this.isConnecting()) - { - if (this.previous != null) - { - this.error = this.validateConnection(this.previous.cell, cell); - - if (this.error != null && this.error.length == 0) - { - cell = null; - - // Enables create target inside groups - if (this.isCreateTarget(me.getEvent())) - { - this.error = null; - } - } + + // Uses connectable parent vertex if one exists + if (cell != null && !this.graph.isCellConnectable(cell)) { + var parent = this.graph.getModel().getParent(cell); + + if (this.graph.getModel().isVertex(parent) && this.graph.isCellConnectable(parent)) { + cell = parent; } } - else if (!this.isValidSource(cell, me)) - { + + if ((this.graph.isSwimlane(cell) && this.currentPoint != null && + this.graph.hitsSwimlaneContent(cell, this.currentPoint.x, this.currentPoint.y)) || + !this.isConnectableCell(cell)) { cell = null; } - } - else if (this.isConnecting() && !this.isCreateTarget(me.getEvent()) && - !this.graph.allowDanglingEdges) - { - this.error = ''; + + if (cell != null) { + if (this.isConnecting()) { + if (this.previous != null) { + this.error = this.validateConnection(this.previous.cell, cell); + + if (this.error != null && this.error.length == 0) { + cell = null; + + // Enables create target inside groups + if (this.isCreateTarget(me.getEvent())) { + this.error = null; + } + } + } + } else if (!this.isValidSource(cell, me)) { + cell = null; + } + } else if (this.isConnecting() && !this.isCreateTarget(me.getEvent()) && + !this.graph.allowDanglingEdges) { + this.error = ''; + } + + return cell; + }); + + // Sets the highlight color according to validateConnection + marker.isValidState = mxUtils.bind(this, (state) => { + if (this.isConnecting()) { + return this.error == null; + } else { + return isValidState.apply(marker, arguments); + } + }); + + // Overrides to use marker color only in highlight mode or for + // target selection + marker.getMarkerColor = mxUtils.bind(this, (evt, state, isValid) => { + return (this.connectImage == null || this.isConnecting()) ? + getMarkerColor.apply(marker, arguments) : + null; + }); + + // Overrides to use hotspot only for source selection otherwise + // intersects always returns true when over a cell + marker.intersects = mxUtils.bind(this, (state, evt) => { + if (this.connectImage != null || this.isConnecting()) { + return true; + } + + return intersects.apply(marker, arguments); + }); + + return marker; + }; + + /** + * Function: start + * + * Starts a new connection for the given state and coordinates. + */ + start = (state, x, y, edgeState) => { + this.previous = state; + this.first = new mxPoint(x, y); + this.edgeState = (edgeState != null) ? edgeState : this.createEdgeState(null); + + // Marks the source state + this.marker.currentColor = this.marker.validColor; + this.marker.markedState = state; + this.marker.mark(); + + this.fireEvent(new mxEventObject(mxEvent.START, 'state', this.previous)); + }; + + /** + * Function: isConnecting + * + * Returns true if the source terminal has been clicked and a new + * connection is currently being previewed. + */ + isConnecting = () => { + return this.first != null && this.shape != null; + }; + + /** + * Function: isValidSource + * + * Returns for the given source terminal. + * + * Parameters: + * + * cell - that represents the source terminal. + * me - that is associated with this call. + */ + isValidSource = (cell, me) => { + return this.graph.isValidSource(cell); + }; + + /** + * Function: isValidTarget + * + * Returns true. The call to is implicit by calling + * in . This is an + * additional hook for disabling certain targets in this specific handler. + * + * Parameters: + * + * cell - that represents the target terminal. + */ + isValidTarget = (cell) => { + return true; + }; + + /** + * Function: validateConnection + * + * Returns the error message or an empty string if the connection for the + * given source target pair is not valid. Otherwise it returns null. This + * implementation uses . + * + * Parameters: + * + * source - that represents the source terminal. + * target - that represents the target terminal. + */ + validateConnection = (source, target) => { + if (!this.isValidTarget(target)) { + return ''; } - return cell; - }); + return this.graph.getEdgeValidationError(null, source, target); + }; - // Sets the highlight color according to validateConnection - marker.isValidState = mxUtils.bind(this, (state)=> - { - if (this.isConnecting()) - { - return this.error == null; - } - else - { - return isValidState.apply(marker, arguments); - } - }); + /** + * Function: getConnectImage + * + * Hook to return the used for the connection icon of the given + * . This implementation returns . + * + * Parameters: + * + * state - whose connect image should be returned. + */ + getConnectImage = (state) => { + return this.connectImage; + }; - // Overrides to use marker color only in highlight mode or for - // target selection - marker.getMarkerColor = mxUtils.bind(this, (evt, state, isValid)=> - { - return (this.connectImage == null || this.isConnecting()) ? - getMarkerColor.apply(marker, arguments) : - null; - }); - - // Overrides to use hotspot only for source selection otherwise - // intersects always returns true when over a cell - marker.intersects = mxUtils.bind(this, (state, evt)=> - { - if (this.connectImage != null || this.isConnecting()) - { + /** + * Function: isMoveIconToFrontForState + * + * Returns true if the state has a HTML label in the graph's container, otherwise + * it returns . + * + * Parameters: + * + * state - whose connect icons should be returned. + */ + isMoveIconToFrontForState = (state) => { + if (state.text != null && state.text.node.parentNode == this.graph.container) { return true; } - - return intersects.apply(marker, arguments); - }); - return marker; -}; + return this.moveIconFront; + }; -/** - * Function: start - * - * Starts a new connection for the given state and coordinates. - */ -start = (state, x, y, edgeState)=> -{ - this.previous = state; - this.first = new mxPoint(x, y); - this.edgeState = (edgeState != null) ? edgeState : this.createEdgeState(null); - - // Marks the source state - this.marker.currentColor = this.marker.validColor; - this.marker.markedState = state; - this.marker.mark(); + /** + * Function: createIcons + * + * Creates the array that represent the connect icons for + * the given . + * + * Parameters: + * + * state - whose connect icons should be returned. + */ + createIcons = (state) => { + var image = this.getConnectImage(state); - this.fireEvent(new mxEventObject(mxEvent.START, 'state', this.previous)); -}; + if (image != null && state != null) { + this.iconState = state; + var icons = []; -/** - * Function: isConnecting - * - * Returns true if the source terminal has been clicked and a new - * connection is currently being previewed. - */ -isConnecting = ()=> -{ - return this.first != null && this.shape != null; -}; + // Cannot use HTML for the connect icons because the icon receives all + // mouse move events in IE, must use VML and SVG instead even if the + // connect-icon appears behind the selection border and the selection + // border consumes the events before the icon gets a chance + var bounds = new mxRectangle(0, 0, image.width, image.height); + var icon = new mxImageShape(bounds, image.src, null, null, 0); + icon.preserveImageAspect = false; -/** - * Function: isValidSource - * - * Returns for the given source terminal. - * - * Parameters: - * - * cell - that represents the source terminal. - * me - that is associated with this call. - */ -isValidSource = (cell, me)=> -{ - return this.graph.isValidSource(cell); -}; + if (this.isMoveIconToFrontForState(state)) { + icon.dialect = mxConstants.DIALECT_STRICTHTML; + icon.init(this.graph.container); + } else { + icon.dialect = (this.graph.dialect == mxConstants.DIALECT_SVG) ? + mxConstants.DIALECT_SVG : mxConstants.DIALECT_VML; + icon.init(this.graph.getView().getOverlayPane()); -/** - * Function: isValidTarget - * - * Returns true. The call to is implicit by calling - * in . This is an - * additional hook for disabling certain targets in this specific handler. - * - * Parameters: - * - * cell - that represents the target terminal. - */ -isValidTarget = (cell)=> -{ - return true; -}; - -/** - * Function: validateConnection - * - * Returns the error message or an empty string if the connection for the - * given source target pair is not valid. Otherwise it returns null. This - * implementation uses . - * - * Parameters: - * - * source - that represents the source terminal. - * target - that represents the target terminal. - */ -validateConnection = (source, target)=> -{ - if (!this.isValidTarget(target)) - { - return ''; - } - - return this.graph.getEdgeValidationError(null, source, target); -}; - -/** - * Function: getConnectImage - * - * Hook to return the used for the connection icon of the given - * . This implementation returns . - * - * Parameters: - * - * state - whose connect image should be returned. - */ -getConnectImage = (state)=> -{ - return this.connectImage; -}; - -/** - * Function: isMoveIconToFrontForState - * - * Returns true if the state has a HTML label in the graph's container, otherwise - * it returns . - * - * Parameters: - * - * state - whose connect icons should be returned. - */ -isMoveIconToFrontForState = (state)=> -{ - if (state.text != null && state.text.node.parentNode == this.graph.container) - { - return true; - } - - return this.moveIconFront; -}; - -/** - * Function: createIcons - * - * Creates the array that represent the connect icons for - * the given . - * - * Parameters: - * - * state - whose connect icons should be returned. - */ -createIcons = (state)=> -{ - var image = this.getConnectImage(state); - - if (image != null && state != null) - { - this.iconState = state; - var icons = []; - - // Cannot use HTML for the connect icons because the icon receives all - // mouse move events in IE, must use VML and SVG instead even if the - // connect-icon appears behind the selection border and the selection - // border consumes the events before the icon gets a chance - var bounds = new mxRectangle(0, 0, image.width, image.height); - var icon = new mxImageShape(bounds, image.src, null, null, 0); - icon.preserveImageAspect = false; - - if (this.isMoveIconToFrontForState(state)) - { - icon.dialect = mxConstants.DIALECT_STRICTHTML; - icon.init(this.graph.container); - } - else - { - icon.dialect = (this.graph.dialect == mxConstants.DIALECT_SVG) ? - mxConstants.DIALECT_SVG : mxConstants.DIALECT_VML; - icon.init(this.graph.getView().getOverlayPane()); - - // Move the icon back in the overlay pane - if (this.moveIconBack && icon.node.previousSibling != null) - { - icon.node.parentNode.insertBefore(icon.node, icon.node.parentNode.firstChild); - } - } - - icon.node.style.cursor = mxConstants.CURSOR_CONNECT; - - // Events transparency - var getState = mxUtils.bind(this, ()=> - { - return (this.currentState != null) ? this.currentState : state; - }); - - // Updates the local icon before firing the mouse down event. - var mouseDown = mxUtils.bind(this, (evt)=> - { - if (!mxEvent.isConsumed(evt)) - { - this.icon = icon; - this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN, - new mxMouseEvent(evt, getState())); - } - }); - - mxEvent.redirectMouseEvents(icon.node, this.graph, getState, mouseDown); - - icons.push(icon); - this.redrawIcons(icons, this.iconState); - - return icons; - } - - return null; -}; - -/** - * Function: redrawIcons - * - * Redraws the given array of . - * - * Parameters: - * - * icons - Optional array of to be redrawn. - */ -redrawIcons = (icons, state)=> -{ - if (icons != null && icons[0] != null && state != null) - { - var pos = this.getIconPosition(icons[0], state); - icons[0].bounds.x = pos.x; - icons[0].bounds.y = pos.y; - icons[0].redraw(); - } -}; - -/** - * Function: getIconPosition - * - * Returns the center position of the given icon. - * - * Parameters: - * - * icon - The connect icon of with the mouse. - * state - under the mouse. - */ -getIconPosition = (icon, state)=> -{ - var scale = this.graph.getView().scale; - var cx = state.getCenterX(); - var cy = state.getCenterY(); - - if (this.graph.isSwimlane(state.cell)) - { - var size = this.graph.getStartSize(state.cell); - - cx = (size.width != 0) ? state.x + size.width * scale / 2 : cx; - cy = (size.height != 0) ? state.y + size.height * scale / 2 : cy; - - var alpha = mxUtils.toRadians(mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION) || 0); - - if (alpha != 0) - { - var cos = Math.cos(alpha); - var sin = Math.sin(alpha); - var ct = new mxPoint(state.getCenterX(), state.getCenterY()); - var pt = mxUtils.getRotatedPoint(new mxPoint(cx, cy), cos, sin, ct); - cx = pt.x; - cy = pt.y; - } - } - - return new mxPoint(cx - icon.bounds.width / 2, - cy - icon.bounds.height / 2); -}; - -/** - * Function: destroyIcons - * - * Destroys the connect icons and resets the respective state. - */ -destroyIcons = ()=> -{ - if (this.icons != null) - { - for (var i = 0; i < this.icons.length; i++) - { - this.icons[i].destroy(); - } - - this.icons = null; - this.icon = null; - this.selectedIcon = null; - this.iconState = null; - } -}; - -/** - * Function: isStartEvent - * - * Returns true if the given mouse down event should start this handler. The - * This implementation returns true if the event does not force marquee - * selection, and the currentConstraint and currentFocus of the - * are not null, or and are not null and - * is null or and are not null. - */ -isStartEvent = (me)=> -{ - return ((this.constraintHandler.currentFocus != null && this.constraintHandler.currentConstraint != null) || - (this.previous != null && this.error == null && (this.icons == null || (this.icons != null && - this.icon != null)))); -}; - -/** - * Function: mouseDown - * - * Handles the event by initiating a new connection. - */ -mouseDown = (sender, me)=> -{ - this.mouseDownCounter++; - - if (this.isEnabled() && this.graph.isEnabled() && !me.isConsumed() && - !this.isConnecting() && this.isStartEvent(me)) - { - if (this.constraintHandler.currentConstraint != null && - this.constraintHandler.currentFocus != null && - this.constraintHandler.currentPoint != null) - { - this.sourceConstraint = this.constraintHandler.currentConstraint; - this.previous = this.constraintHandler.currentFocus; - this.first = this.constraintHandler.currentPoint.clone(); - } - else - { - // Stores the location of the initial mousedown - this.first = new mxPoint(me.getGraphX(), me.getGraphY()); - } - - this.edgeState = this.createEdgeState(me); - this.mouseDownCounter = 1; - - if (this.waypointsEnabled && this.shape == null) - { - this.waypoints = null; - this.shape = this.createShape(); - - if (this.edgeState != null) - { - this.shape.apply(this.edgeState); - } - } - - // Stores the starting point in the geometry of the preview - if (this.previous == null && this.edgeState != null) - { - var pt = this.graph.getPointForEvent(me.getEvent()); - this.edgeState.cell.geometry.setTerminalPoint(pt, true); - } - - this.fireEvent(new mxEventObject(mxEvent.START, 'state', this.previous)); - - me.consume(); - } - - this.selectedIcon = this.icon; - this.icon = null; -}; - -/** - * Function: isImmediateConnectSource - * - * Returns true if a tap on the given source state should immediately start - * connecting. This implementation returns true if the state is not movable - * in the graph. - */ -isImmediateConnectSource = (state)=> -{ - return !this.graph.isCellMovable(state.cell); -}; - -/** - * Function: createEdgeState - * - * Hook to return an which may be used during the preview. - * This implementation returns null. - * - * Use the following code to create a preview for an existing edge style: - * - * (code) - * graph.connectionHandler.createEdgeState = (me)=> - * { - * var edge = graph.createEdge(null, null, null, null, null, 'edgeStyle=elbowEdgeStyle'); - * - * return new mxCellState(this.graph.view, edge, this.graph.getCellStyle(edge)); - * }; - * (end) - */ -createEdgeState = (me)=> -{ - return null; -}; - -/** - * Function: isOutlineConnectEvent - * - * Returns true if is true and the source of the event is the outline shape - * or shift is pressed. - */ -isOutlineConnectEvent = (me)=> -{ - var offset = mxUtils.getOffset(this.graph.container); - var evt = me.getEvent(); - - var clientX = mxEvent.getClientX(evt); - var clientY = mxEvent.getClientY(evt); - - var doc = document.documentElement; - var left = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0); - var top = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0); - - var gridX = this.currentPoint.x - this.graph.container.scrollLeft + offset.x - left; - var gridY = this.currentPoint.y - this.graph.container.scrollTop + offset.y - top; - - return this.outlineConnect && !mxEvent.isShiftDown(me.getEvent()) && - (me.isSource(this.marker.highlight.shape) || - (mxEvent.isAltDown(me.getEvent()) && me.getState() != null) || - this.marker.highlight.isHighlightAt(clientX, clientY) || - ((gridX != clientX || gridY != clientY) && me.getState() == null && - this.marker.highlight.isHighlightAt(gridX, gridY))); -}; - -/** - * Function: updateCurrentState - * - * Updates the current state for a given mouse move event by using - * the . - */ -updateCurrentState = (me, point)=> -{ - this.constraintHandler.update(me, this.first == null, false, (this.first == null || - me.isSource(this.marker.highlight.shape)) ? null : point); - - if (this.constraintHandler.currentFocus != null && this.constraintHandler.currentConstraint != null) - { - // Handles special case where grid is large and connection point is at actual point in which - // case the outline is not followed as long as we're < gridSize / 2 away from that point - if (this.marker.highlight != null && this.marker.highlight.state != null && - this.marker.highlight.state.cell == this.constraintHandler.currentFocus.cell) - { - // Direct repaint needed if cell already highlighted - if (this.marker.highlight.shape.stroke != 'transparent') - { - this.marker.highlight.shape.stroke = 'transparent'; - this.marker.highlight.repaint(); - } - } - else - { - this.marker.markCell(this.constraintHandler.currentFocus.cell, 'transparent'); - } - - // Updates validation state - if (this.previous != null) - { - this.error = this.validateConnection(this.previous.cell, this.constraintHandler.currentFocus.cell); - - if (this.error == null) - { - this.currentState = this.constraintHandler.currentFocus; - } - - if (this.error != null || (this.currentState != null && - !this.isCellEnabled(this.currentState.cell))) - { - this.constraintHandler.reset(); - } - } - } - else - { - if (this.graph.isIgnoreTerminalEvent(me.getEvent())) - { - this.marker.reset(); - this.currentState = null; - } - else - { - this.marker.process(me); - this.currentState = this.marker.getValidState(); - } - - if (this.currentState != null && !this.isCellEnabled(this.currentState.cell)) - { - this.constraintHandler.reset(); - this.marker.reset(); - this.currentState = null; - } - - var outline = this.isOutlineConnectEvent(me); - - if (this.currentState != null && outline) - { - // Handles special case where mouse is on outline away from actual end point - // in which case the grid is ignored and mouse point is used instead - if (me.isSource(this.marker.highlight.shape)) - { - point = new mxPoint(me.getGraphX(), me.getGraphY()); - } - - var constraint = this.graph.getOutlineConstraint(point, this.currentState, me); - this.constraintHandler.setFocus(me, this.currentState, false); - this.constraintHandler.currentConstraint = constraint; - this.constraintHandler.currentPoint = point; - } - - if (this.outlineConnect) - { - if (this.marker.highlight != null && this.marker.highlight.shape != null) - { - var s = this.graph.view.scale; - - if (this.constraintHandler.currentConstraint != null && - this.constraintHandler.currentFocus != null) - { - this.marker.highlight.shape.stroke = mxConstants.OUTLINE_HIGHLIGHT_COLOR; - this.marker.highlight.shape.strokewidth = mxConstants.OUTLINE_HIGHLIGHT_STROKEWIDTH / s / s; - this.marker.highlight.repaint(); - } - else if (this.marker.hasValidState()) - { - // Handles special case where actual end point of edge and current mouse point - // are not equal (due to grid snapping) and there is no hit on shape or highlight - // but ignores cases where parent is used for non-connectable child cells - if (this.graph.isCellConnectable(me.getCell()) && - this.marker.getValidState() != me.getState()) - { - this.marker.highlight.shape.stroke = 'transparent'; - this.currentState = null; - } - else - { - this.marker.highlight.shape.stroke = mxConstants.DEFAULT_VALID_COLOR; - } - - this.marker.highlight.shape.strokewidth = mxConstants.HIGHLIGHT_STROKEWIDTH / s / s; - this.marker.highlight.repaint(); + // Move the icon back in the overlay pane + if (this.moveIconBack && icon.node.previousSibling != null) { + icon.node.parentNode.insertBefore(icon.node, icon.node.parentNode.firstChild); } } - } - } -}; -/** - * Function: isCellEnabled - * - * Returns true if the given cell allows new connections to be created. This implementation - * always returns true. - */ -isCellEnabled = (cell)=> -{ - return true; -}; + icon.node.style.cursor = mxConstants.CURSOR_CONNECT; -/** - * Function: convertWaypoint - * - * Converts the given point from screen coordinates to model coordinates. - */ -convertWaypoint = (point)=> -{ - var scale = this.graph.getView().getScale(); - var tr = this.graph.getView().getTranslate(); - - point.x = point.x / scale - tr.x; - point.y = point.y / scale - tr.y; -}; + // Events transparency + var getState = mxUtils.bind(this, () => { + return (this.currentState != null) ? this.currentState : state; + }); -/** - * Function: snapToPreview - * - * Called to snap the given point to the current preview. This snaps to the - * first point of the preview if alt is not pressed. - */ -snapToPreview = (me, point)=> -{ - if (!mxEvent.isAltDown(me.getEvent()) && this.previous != null) - { - var tol = this.graph.gridSize * this.graph.view.scale / 2; - var tmp = (this.sourceConstraint != null) ? this.first : - new mxPoint(this.previous.getCenterX(), this.previous.getCenterY()); + // Updates the local icon before firing the mouse down event. + var mouseDown = mxUtils.bind(this, (evt) => { + if (!mxEvent.isConsumed(evt)) { + this.icon = icon; + this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN, + new mxMouseEvent(evt, getState())); + } + }); - if (Math.abs(tmp.x - me.getGraphX()) < tol) - { - point.x = tmp.x; - } - - if (Math.abs(tmp.y - me.getGraphY()) < tol) - { - point.y = tmp.y; - } - } -}; + mxEvent.redirectMouseEvents(icon.node, this.graph, getState, mouseDown); -/** - * Function: mouseMove - * - * Handles the event by updating the preview edge or by highlighting - * a possible source or target terminal. - */ -mouseMove = (sender, me)=> -{ - if (!me.isConsumed() && (this.ignoreMouseDown || this.first != null || !this.graph.isMouseDown)) - { - // Handles special case when handler is disabled during highlight - if (!this.isEnabled() && this.currentState != null) - { - this.destroyIcons(); - this.currentState = null; + icons.push(icon); + this.redrawIcons(icons, this.iconState); + + return icons; } - var view = this.graph.getView(); - var scale = view.scale; - var tr = view.translate; - var point = new mxPoint(me.getGraphX(), me.getGraphY()); - this.error = null; + return null; + }; - if (this.graph.isGridEnabledEvent(me.getEvent())) - { - point = new mxPoint((this.graph.snap(point.x / scale - tr.x) + tr.x) * scale, - (this.graph.snap(point.y / scale - tr.y) + tr.y) * scale); + /** + * Function: redrawIcons + * + * Redraws the given array of . + * + * Parameters: + * + * icons - Optional array of to be redrawn. + */ + redrawIcons = (icons, state) => { + if (icons != null && icons[0] != null && state != null) { + var pos = this.getIconPosition(icons[0], state); + icons[0].bounds.x = pos.x; + icons[0].bounds.y = pos.y; + icons[0].redraw(); } - - this.snapToPreview(me, point); - this.currentPoint = point; - - if ((this.first != null || (this.isEnabled() && this.graph.isEnabled())) && - (this.shape != null || this.first == null || - Math.abs(me.getGraphX() - this.first.x) > this.graph.tolerance || - Math.abs(me.getGraphY() - this.first.y) > this.graph.tolerance)) - { - this.updateCurrentState(me, point); + }; + + /** + * Function: getIconPosition + * + * Returns the center position of the given icon. + * + * Parameters: + * + * icon - The connect icon of with the mouse. + * state - under the mouse. + */ + getIconPosition = (icon, state) => { + var scale = this.graph.getView().scale; + var cx = state.getCenterX(); + var cy = state.getCenterY(); + + if (this.graph.isSwimlane(state.cell)) { + var size = this.graph.getStartSize(state.cell); + + cx = (size.width != 0) ? state.x + size.width * scale / 2 : cx; + cy = (size.height != 0) ? state.y + size.height * scale / 2 : cy; + + var alpha = mxUtils.toRadians(mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION) || 0); + + if (alpha != 0) { + var cos = Math.cos(alpha); + var sin = Math.sin(alpha); + var ct = new mxPoint(state.getCenterX(), state.getCenterY()); + var pt = mxUtils.getRotatedPoint(new mxPoint(cx, cy), cos, sin, ct); + cx = pt.x; + cy = pt.y; + } } - if (this.first != null) - { - var constraint = null; - var current = point; - - // Uses the current point from the constraint handler if available + return new mxPoint(cx - icon.bounds.width / 2, + cy - icon.bounds.height / 2); + }; + + /** + * Function: destroyIcons + * + * Destroys the connect icons and resets the respective state. + */ + destroyIcons = () => { + if (this.icons != null) { + for (var i = 0; i < this.icons.length; i++) { + this.icons[i].destroy(); + } + + this.icons = null; + this.icon = null; + this.selectedIcon = null; + this.iconState = null; + } + }; + + /** + * Function: isStartEvent + * + * Returns true if the given mouse down event should start this handler. The + * This implementation returns true if the event does not force marquee + * selection, and the currentConstraint and currentFocus of the + * are not null, or and are not null and + * is null or and are not null. + */ + isStartEvent = (me) => { + return ((this.constraintHandler.currentFocus != null && this.constraintHandler.currentConstraint != null) || + (this.previous != null && this.error == null && (this.icons == null || (this.icons != null && + this.icon != null)))); + }; + + /** + * Function: mouseDown + * + * Handles the event by initiating a new connection. + */ + mouseDown = (sender, me) => { + this.mouseDownCounter++; + + if (this.isEnabled() && this.graph.isEnabled() && !me.isConsumed() && + !this.isConnecting() && this.isStartEvent(me)) { if (this.constraintHandler.currentConstraint != null && - this.constraintHandler.currentFocus != null && - this.constraintHandler.currentPoint != null) - { - constraint = this.constraintHandler.currentConstraint; - current = this.constraintHandler.currentPoint.clone(); - } - else if (this.previous != null && !this.graph.isIgnoreTerminalEvent(me.getEvent()) && - mxEvent.isShiftDown(me.getEvent())) - { - if (Math.abs(this.previous.getCenterX() - point.x) < - Math.abs(this.previous.getCenterY() - point.y)) - { - point.x = this.previous.getCenterX(); - } - else - { - point.y = this.previous.getCenterY(); - } - } - - var pt2 = this.first; - - // Moves the connect icon with the mouse - if (this.selectedIcon != null) - { - var w = this.selectedIcon.bounds.width; - var h = this.selectedIcon.bounds.height; - - if (this.currentState != null && this.targetConnectImage) - { - var pos = this.getIconPosition(this.selectedIcon, this.currentState); - this.selectedIcon.bounds.x = pos.x; - this.selectedIcon.bounds.y = pos.y; - } - else - { - var bounds = new mxRectangle(me.getGraphX() + this.connectIconOffset.x, - me.getGraphY() + this.connectIconOffset.y, w, h); - this.selectedIcon.bounds = bounds; - } - - this.selectedIcon.redraw(); + this.constraintHandler.currentFocus != null && + this.constraintHandler.currentPoint != null) { + this.sourceConstraint = this.constraintHandler.currentConstraint; + this.previous = this.constraintHandler.currentFocus; + this.first = this.constraintHandler.currentPoint.clone(); + } else { + // Stores the location of the initial mousedown + this.first = new mxPoint(me.getGraphX(), me.getGraphY()); } - // Uses edge state to compute the terminal points - if (this.edgeState != null) - { - this.updateEdgeState(current, constraint); - current = this.edgeState.absolutePoints[this.edgeState.absolutePoints.length - 1]; - pt2 = this.edgeState.absolutePoints[0]; + this.edgeState = this.createEdgeState(me); + this.mouseDownCounter = 1; + + if (this.waypointsEnabled && this.shape == null) { + this.waypoints = null; + this.shape = this.createShape(); + + if (this.edgeState != null) { + this.shape.apply(this.edgeState); + } } - else - { - if (this.currentState != null) - { - if (this.constraintHandler.currentConstraint == null) - { - var tmp = this.getTargetPerimeterPoint(this.currentState, me); - - if (tmp != null) - { - current = tmp; + + // Stores the starting point in the geometry of the preview + if (this.previous == null && this.edgeState != null) { + var pt = this.graph.getPointForEvent(me.getEvent()); + this.edgeState.cell.geometry.setTerminalPoint(pt, true); + } + + this.fireEvent(new mxEventObject(mxEvent.START, 'state', this.previous)); + + me.consume(); + } + + this.selectedIcon = this.icon; + this.icon = null; + }; + + /** + * Function: isImmediateConnectSource + * + * Returns true if a tap on the given source state should immediately start + * connecting. This implementation returns true if the state is not movable + * in the graph. + */ + isImmediateConnectSource = (state) => { + return !this.graph.isCellMovable(state.cell); + }; + + /** + * Function: createEdgeState + * + * Hook to return an which may be used during the preview. + * This implementation returns null. + * + * Use the following code to create a preview for an existing edge style: + * + * (code) + * graph.connectionHandler.createEdgeState = (me)=> + * { + * var edge = graph.createEdge(null, null, null, null, null, 'edgeStyle=elbowEdgeStyle'); + * + * return new mxCellState(this.graph.view, edge, this.graph.getCellStyle(edge)); + * }; + * (end) + */ + createEdgeState = (me) => { + return null; + }; + + /** + * Function: isOutlineConnectEvent + * + * Returns true if is true and the source of the event is the outline shape + * or shift is pressed. + */ + isOutlineConnectEvent = (me) => { + var offset = mxUtils.getOffset(this.graph.container); + var evt = me.getEvent(); + + var clientX = mxEvent.getClientX(evt); + var clientY = mxEvent.getClientY(evt); + + var doc = document.documentElement; + var left = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0); + var top = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0); + + var gridX = this.currentPoint.x - this.graph.container.scrollLeft + offset.x - left; + var gridY = this.currentPoint.y - this.graph.container.scrollTop + offset.y - top; + + return this.outlineConnect && !mxEvent.isShiftDown(me.getEvent()) && + (me.isSource(this.marker.highlight.shape) || + (mxEvent.isAltDown(me.getEvent()) && me.getState() != null) || + this.marker.highlight.isHighlightAt(clientX, clientY) || + ((gridX != clientX || gridY != clientY) && me.getState() == null && + this.marker.highlight.isHighlightAt(gridX, gridY))); + }; + + /** + * Function: updateCurrentState + * + * Updates the current state for a given mouse move event by using + * the . + */ + updateCurrentState = (me, point) => { + this.constraintHandler.update(me, this.first == null, false, (this.first == null || + me.isSource(this.marker.highlight.shape)) ? null : point); + + if (this.constraintHandler.currentFocus != null && this.constraintHandler.currentConstraint != null) { + // Handles special case where grid is large and connection point is at actual point in which + // case the outline is not followed as long as we're < gridSize / 2 away from that point + if (this.marker.highlight != null && this.marker.highlight.state != null && + this.marker.highlight.state.cell == this.constraintHandler.currentFocus.cell) { + // Direct repaint needed if cell already highlighted + if (this.marker.highlight.shape.stroke != 'transparent') { + this.marker.highlight.shape.stroke = 'transparent'; + this.marker.highlight.repaint(); + } + } else { + this.marker.markCell(this.constraintHandler.currentFocus.cell, 'transparent'); + } + + // Updates validation state + if (this.previous != null) { + this.error = this.validateConnection(this.previous.cell, this.constraintHandler.currentFocus.cell); + + if (this.error == null) { + this.currentState = this.constraintHandler.currentFocus; + } + + if (this.error != null || (this.currentState != null && + !this.isCellEnabled(this.currentState.cell))) { + this.constraintHandler.reset(); + } + } + } else { + if (this.graph.isIgnoreTerminalEvent(me.getEvent())) { + this.marker.reset(); + this.currentState = null; + } else { + this.marker.process(me); + this.currentState = this.marker.getValidState(); + } + + if (this.currentState != null && !this.isCellEnabled(this.currentState.cell)) { + this.constraintHandler.reset(); + this.marker.reset(); + this.currentState = null; + } + + var outline = this.isOutlineConnectEvent(me); + + if (this.currentState != null && outline) { + // Handles special case where mouse is on outline away from actual end point + // in which case the grid is ignored and mouse point is used instead + if (me.isSource(this.marker.highlight.shape)) { + point = new mxPoint(me.getGraphX(), me.getGraphY()); + } + + var constraint = this.graph.getOutlineConstraint(point, this.currentState, me); + this.constraintHandler.setFocus(me, this.currentState, false); + this.constraintHandler.currentConstraint = constraint; + this.constraintHandler.currentPoint = point; + } + + if (this.outlineConnect) { + if (this.marker.highlight != null && this.marker.highlight.shape != null) { + var s = this.graph.view.scale; + + if (this.constraintHandler.currentConstraint != null && + this.constraintHandler.currentFocus != null) { + this.marker.highlight.shape.stroke = mxConstants.OUTLINE_HIGHLIGHT_COLOR; + this.marker.highlight.shape.strokewidth = mxConstants.OUTLINE_HIGHLIGHT_STROKEWIDTH / s / s; + this.marker.highlight.repaint(); + } else if (this.marker.hasValidState()) { + // Handles special case where actual end point of edge and current mouse point + // are not equal (due to grid snapping) and there is no hit on shape or highlight + // but ignores cases where parent is used for non-connectable child cells + if (this.graph.isCellConnectable(me.getCell()) && + this.marker.getValidState() != me.getState()) { + this.marker.highlight.shape.stroke = 'transparent'; + this.currentState = null; + } else { + this.marker.highlight.shape.stroke = mxConstants.DEFAULT_VALID_COLOR; + } + + this.marker.highlight.shape.strokewidth = mxConstants.HIGHLIGHT_STROKEWIDTH / s / s; + this.marker.highlight.repaint(); + } + } + } + } + }; + + /** + * Function: isCellEnabled + * + * Returns true if the given cell allows new connections to be created. This implementation + * always returns true. + */ + isCellEnabled = (cell) => { + return true; + }; + + /** + * Function: convertWaypoint + * + * Converts the given point from screen coordinates to model coordinates. + */ + convertWaypoint = (point) => { + var scale = this.graph.getView().getScale(); + var tr = this.graph.getView().getTranslate(); + + point.x = point.x / scale - tr.x; + point.y = point.y / scale - tr.y; + }; + + /** + * Function: snapToPreview + * + * Called to snap the given point to the current preview. This snaps to the + * first point of the preview if alt is not pressed. + */ + snapToPreview = (me, point) => { + if (!mxEvent.isAltDown(me.getEvent()) && this.previous != null) { + var tol = this.graph.gridSize * this.graph.view.scale / 2; + var tmp = (this.sourceConstraint != null) ? this.first : + new mxPoint(this.previous.getCenterX(), this.previous.getCenterY()); + + if (Math.abs(tmp.x - me.getGraphX()) < tol) { + point.x = tmp.x; + } + + if (Math.abs(tmp.y - me.getGraphY()) < tol) { + point.y = tmp.y; + } + } + }; + + /** + * Function: mouseMove + * + * Handles the event by updating the preview edge or by highlighting + * a possible source or target terminal. + */ + mouseMove = (sender, me) => { + if (!me.isConsumed() && (this.ignoreMouseDown || this.first != null || !this.graph.isMouseDown)) { + // Handles special case when handler is disabled during highlight + if (!this.isEnabled() && this.currentState != null) { + this.destroyIcons(); + this.currentState = null; + } + + var view = this.graph.getView(); + var scale = view.scale; + var tr = view.translate; + var point = new mxPoint(me.getGraphX(), me.getGraphY()); + this.error = null; + + if (this.graph.isGridEnabledEvent(me.getEvent())) { + point = new mxPoint((this.graph.snap(point.x / scale - tr.x) + tr.x) * scale, + (this.graph.snap(point.y / scale - tr.y) + tr.y) * scale); + } + + this.snapToPreview(me, point); + this.currentPoint = point; + + if ((this.first != null || (this.isEnabled() && this.graph.isEnabled())) && + (this.shape != null || this.first == null || + Math.abs(me.getGraphX() - this.first.x) > this.graph.tolerance || + Math.abs(me.getGraphY() - this.first.y) > this.graph.tolerance)) { + this.updateCurrentState(me, point); + } + + if (this.first != null) { + var constraint = null; + var current = point; + + // Uses the current point from the constraint handler if available + if (this.constraintHandler.currentConstraint != null && + this.constraintHandler.currentFocus != null && + this.constraintHandler.currentPoint != null) { + constraint = this.constraintHandler.currentConstraint; + current = this.constraintHandler.currentPoint.clone(); + } else if (this.previous != null && !this.graph.isIgnoreTerminalEvent(me.getEvent()) && + mxEvent.isShiftDown(me.getEvent())) { + if (Math.abs(this.previous.getCenterX() - point.x) < + Math.abs(this.previous.getCenterY() - point.y)) { + point.x = this.previous.getCenterX(); + } else { + point.y = this.previous.getCenterY(); + } + } + + var pt2 = this.first; + + // Moves the connect icon with the mouse + if (this.selectedIcon != null) { + var w = this.selectedIcon.bounds.width; + var h = this.selectedIcon.bounds.height; + + if (this.currentState != null && this.targetConnectImage) { + var pos = this.getIconPosition(this.selectedIcon, this.currentState); + this.selectedIcon.bounds.x = pos.x; + this.selectedIcon.bounds.y = pos.y; + } else { + var bounds = new mxRectangle(me.getGraphX() + this.connectIconOffset.x, + me.getGraphY() + this.connectIconOffset.y, w, h); + this.selectedIcon.bounds = bounds; + } + + this.selectedIcon.redraw(); + } + + // Uses edge state to compute the terminal points + if (this.edgeState != null) { + this.updateEdgeState(current, constraint); + current = this.edgeState.absolutePoints[this.edgeState.absolutePoints.length - 1]; + pt2 = this.edgeState.absolutePoints[0]; + } else { + if (this.currentState != null) { + if (this.constraintHandler.currentConstraint == null) { + var tmp = this.getTargetPerimeterPoint(this.currentState, me); + + if (tmp != null) { + current = tmp; + } + } + } + + // Computes the source perimeter point + if (this.sourceConstraint == null && this.previous != null) { + var next = (this.waypoints != null && this.waypoints.length > 0) ? + this.waypoints[0] : current; + var tmp = this.getSourcePerimeterPoint(this.previous, next, me); + + if (tmp != null) { + pt2 = tmp; } } } - - // Computes the source perimeter point - if (this.sourceConstraint == null && this.previous != null) - { - var next = (this.waypoints != null && this.waypoints.length > 0) ? - this.waypoints[0] : current; - var tmp = this.getSourcePerimeterPoint(this.previous, next, me); - - if (tmp != null) - { - pt2 = tmp; - } - } - } - // Makes sure the cell under the mousepointer can be detected - // by moving the preview shape away from the mouse. This - // makes sure the preview shape does not prevent the detection - // of the cell under the mousepointer even for slow gestures. - if (this.currentState == null && this.movePreviewAway) - { - var tmp = pt2; - - if (this.edgeState != null && this.edgeState.absolutePoints.length >= 2) - { - var tmp2 = this.edgeState.absolutePoints[this.edgeState.absolutePoints.length - 2]; - - if (tmp2 != null) - { - tmp = tmp2; - } - } - - var dx = current.x - tmp.x; - var dy = current.y - tmp.y; - - var len = Math.sqrt(dx * dx + dy * dy); - - if (len == 0) - { - return; - } + // Makes sure the cell under the mousepointer can be detected + // by moving the preview shape away from the mouse. This + // makes sure the preview shape does not prevent the detection + // of the cell under the mousepointer even for slow gestures. + if (this.currentState == null && this.movePreviewAway) { + var tmp = pt2; - // Stores old point to reuse when creating edge - this.originalPoint = current.clone(); - current.x -= dx * 4 / len; - current.y -= dy * 4 / len; - } - else - { - this.originalPoint = null; - } - - // Creates the preview shape (lazy) - if (this.shape == null) - { - var dx = Math.abs(me.getGraphX() - this.first.x); - var dy = Math.abs(me.getGraphY() - this.first.y); + if (this.edgeState != null && this.edgeState.absolutePoints.length >= 2) { + var tmp2 = this.edgeState.absolutePoints[this.edgeState.absolutePoints.length - 2]; - if (dx > this.graph.tolerance || dy > this.graph.tolerance) - { - this.shape = this.createShape(); - - if (this.edgeState != null) - { - this.shape.apply(this.edgeState); - } - - // Revalidates current connection - this.updateCurrentState(me, point); - } - } - - // Updates the points in the preview edge - if (this.shape != null) - { - if (this.edgeState != null) - { - this.shape.points = this.edgeState.absolutePoints; - } - else - { - var pts = [pt2]; - - if (this.waypoints != null) - { - pts = pts.concat(this.waypoints); + if (tmp2 != null) { + tmp = tmp2; + } } - pts.push(current); - this.shape.points = pts; + var dx = current.x - tmp.x; + var dy = current.y - tmp.y; + + var len = Math.sqrt(dx * dx + dy * dy); + + if (len == 0) { + return; + } + + // Stores old point to reuse when creating edge + this.originalPoint = current.clone(); + current.x -= dx * 4 / len; + current.y -= dy * 4 / len; + } else { + this.originalPoint = null; } - - this.drawPreview(); + + // Creates the preview shape (lazy) + if (this.shape == null) { + var dx = Math.abs(me.getGraphX() - this.first.x); + var dy = Math.abs(me.getGraphY() - this.first.y); + + if (dx > this.graph.tolerance || dy > this.graph.tolerance) { + this.shape = this.createShape(); + + if (this.edgeState != null) { + this.shape.apply(this.edgeState); + } + + // Revalidates current connection + this.updateCurrentState(me, point); + } + } + + // Updates the points in the preview edge + if (this.shape != null) { + if (this.edgeState != null) { + this.shape.points = this.edgeState.absolutePoints; + } else { + var pts = [pt2]; + + if (this.waypoints != null) { + pts = pts.concat(this.waypoints); + } + + pts.push(current); + this.shape.points = pts; + } + + this.drawPreview(); + } + + // Makes sure endpoint of edge is visible during connect + if (this.cursor != null) { + this.graph.container.style.cursor = this.cursor; + } + + mxEvent.consume(me.getEvent()); + me.consume(); + } else if (!this.isEnabled() || !this.graph.isEnabled()) { + this.constraintHandler.reset(); + } else if (this.previous != this.currentState && this.edgeState == null) { + this.destroyIcons(); + + // Sets the cursor on the current shape + if (this.currentState != null && this.error == null && this.constraintHandler.currentConstraint == null) { + this.icons = this.createIcons(this.currentState); + + if (this.icons == null) { + this.currentState.setCursor(mxConstants.CURSOR_CONNECT); + me.consume(); + } + } + + this.previous = this.currentState; + } else if (this.previous == this.currentState && this.currentState != null && this.icons == null && + !this.graph.isMouseDown) { + // Makes sure that no cursors are changed + me.consume(); } - - // Makes sure endpoint of edge is visible during connect - if (this.cursor != null) - { - this.graph.container.style.cursor = this.cursor; + + if (!this.graph.isMouseDown && this.currentState != null && this.icons != null) { + var hitsIcon = false; + var target = me.getSource(); + + for (var i = 0; i < this.icons.length && !hitsIcon; i++) { + hitsIcon = target == this.icons[i].node || target.parentNode == this.icons[i].node; + } + + if (!hitsIcon) { + this.updateIcons(this.currentState, this.icons, me); + } } - - mxEvent.consume(me.getEvent()); - me.consume(); - } - else if (!this.isEnabled() || !this.graph.isEnabled()) - { + } else { this.constraintHandler.reset(); } - else if (this.previous != this.currentState && this.edgeState == null) - { - this.destroyIcons(); - - // Sets the cursor on the current shape - if (this.currentState != null && this.error == null && this.constraintHandler.currentConstraint == null) - { - this.icons = this.createIcons(this.currentState); + }; - if (this.icons == null) - { - this.currentState.setCursor(mxConstants.CURSOR_CONNECT); - me.consume(); + /** + * Function: updateEdgeState + * + * Updates . + */ + updateEdgeState = (current, constraint) => { + // TODO: Use generic method for writing constraint to style + if (this.sourceConstraint != null && this.sourceConstraint.point != null) { + this.edgeState.style[mxConstants.STYLE_EXIT_X] = this.sourceConstraint.point.x; + this.edgeState.style[mxConstants.STYLE_EXIT_Y] = this.sourceConstraint.point.y; + } + + if (constraint != null && constraint.point != null) { + this.edgeState.style[mxConstants.STYLE_ENTRY_X] = constraint.point.x; + this.edgeState.style[mxConstants.STYLE_ENTRY_Y] = constraint.point.y; + } else { + delete this.edgeState.style[mxConstants.STYLE_ENTRY_X]; + delete this.edgeState.style[mxConstants.STYLE_ENTRY_Y]; + } + + this.edgeState.absolutePoints = [null, (this.currentState != null) ? null : current]; + this.graph.view.updateFixedTerminalPoint(this.edgeState, this.previous, true, this.sourceConstraint); + + if (this.currentState != null) { + if (constraint == null) { + constraint = this.graph.getConnectionConstraint(this.edgeState, this.previous, false); + } + + this.edgeState.setAbsoluteTerminalPoint(null, false); + this.graph.view.updateFixedTerminalPoint(this.edgeState, this.currentState, false, constraint); + } + + // Scales and translates the waypoints to the model + var realPoints = null; + + if (this.waypoints != null) { + realPoints = []; + + for (var i = 0; i < this.waypoints.length; i++) { + var pt = this.waypoints[i].clone(); + this.convertWaypoint(pt); + realPoints[i] = pt; + } + } + + this.graph.view.updatePoints(this.edgeState, realPoints, this.previous, this.currentState); + this.graph.view.updateFloatingTerminalPoints(this.edgeState, this.previous, this.currentState); + }; + + /** + * Function: getTargetPerimeterPoint + * + * Returns the perimeter point for the given target state. + * + * Parameters: + * + * state - that represents the target cell state. + * me - that represents the mouse move. + */ + getTargetPerimeterPoint = (state, me) => { + var result = null; + var view = state.view; + var targetPerimeter = view.getPerimeterFunction(state); + + if (targetPerimeter != null) { + var next = (this.waypoints != null && this.waypoints.length > 0) ? + this.waypoints[this.waypoints.length - 1] : + new mxPoint(this.previous.getCenterX(), this.previous.getCenterY()); + var tmp = targetPerimeter(view.getPerimeterBounds(state), + this.edgeState, next, false); + + if (tmp != null) { + result = tmp; + } + } else { + result = new mxPoint(state.getCenterX(), state.getCenterY()); + } + + return result; + }; + + /** + * Function: getSourcePerimeterPoint + * + * Hook to update the icon position(s) based on a mouseOver event. This is + * an empty implementation. + * + * Parameters: + * + * state - that represents the target cell state. + * next - that represents the next point along the previewed edge. + * me - that represents the mouse move. + */ + getSourcePerimeterPoint = (state, next, me) => { + var result = null; + var view = state.view; + var sourcePerimeter = view.getPerimeterFunction(state); + var c = new mxPoint(state.getCenterX(), state.getCenterY()); + + if (sourcePerimeter != null) { + var theta = mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0); + var rad = -theta * (Math.PI / 180); + + if (theta != 0) { + next = mxUtils.getRotatedPoint(new mxPoint(next.x, next.y), Math.cos(rad), Math.sin(rad), c); + } + + var tmp = sourcePerimeter(view.getPerimeterBounds(state), state, next, false); + + if (tmp != null) { + if (theta != 0) { + tmp = mxUtils.getRotatedPoint(new mxPoint(tmp.x, tmp.y), Math.cos(-rad), Math.sin(-rad), c); + } + + result = tmp; + } + } else { + result = c; + } + + return result; + }; + + + /** + * Function: updateIcons + * + * Hook to update the icon position(s) based on a mouseOver event. This is + * an empty implementation. + * + * Parameters: + * + * state - under the mouse. + * icons - Array of currently displayed icons. + * me - that contains the mouse event. + */ + updateIcons = (state, icons, me) => { + // empty + }; + + /** + * Function: isStopEvent + * + * Returns true if the given mouse up event should stop this handler. The + * connection will be created if is null. Note that this is only + * called if is true. This implemtation returns true + * if there is a cell state in the given event. + */ + isStopEvent = (me) => { + return me.getState() != null; + }; + + /** + * Function: addWaypoint + * + * Adds the waypoint for the given event to . + */ + addWaypointForEvent = (me) => { + var point = mxUtils.convertPoint(this.graph.container, me.getX(), me.getY()); + var dx = Math.abs(point.x - this.first.x); + var dy = Math.abs(point.y - this.first.y); + var addPoint = this.waypoints != null || (this.mouseDownCounter > 1 && + (dx > this.graph.tolerance || dy > this.graph.tolerance)); + + if (addPoint) { + if (this.waypoints == null) { + this.waypoints = []; + } + + var scale = this.graph.view.scale; + var point = new mxPoint(this.graph.snap(me.getGraphX() / scale) * scale, + this.graph.snap(me.getGraphY() / scale) * scale); + this.waypoints.push(point); + } + }; + + /** + * Function: checkConstraints + * + * Returns true if the connection for the given constraints is valid. This + * implementation returns true if the constraints are not pointing to the + * same fixed connection point. + */ + checkConstraints = (c1, c2) => { + return (c1 == null || c2 == null || c1.point == null || c2.point == null || + !c1.point.equals(c2.point) || c1.dx != c2.dx || c1.dy != c2.dy || + c1.perimeter != c2.perimeter); + }; + + /** + * Function: mouseUp + * + * Handles the event by inserting the new connection. + */ + mouseUp = (sender, me) => { + if (!me.isConsumed() && this.isConnecting()) { + if (this.waypointsEnabled && !this.isStopEvent(me)) { + this.addWaypointForEvent(me); + me.consume(); + + return; + } + + var c1 = this.sourceConstraint; + var c2 = this.constraintHandler.currentConstraint; + + var source = (this.previous != null) ? this.previous.cell : null; + var target = null; + + if (this.constraintHandler.currentConstraint != null && + this.constraintHandler.currentFocus != null) { + target = this.constraintHandler.currentFocus.cell; + } + + if (target == null && this.currentState != null) { + target = this.currentState.cell; + } + + // Inserts the edge if no validation error exists and if constraints differ + if (this.error == null && (source == null || target == null || + source != target || this.checkConstraints(c1, c2))) { + this.connect(source, target, me.getEvent(), me.getCell()); + } else { + // Selects the source terminal for self-references + if (this.previous != null && this.marker.validState != null && + this.previous.cell == this.marker.validState.cell) { + this.graph.selectCellForEvent(this.marker.source, me.getEvent()); + } + + // Displays the error message if it is not an empty string, + // for empty error messages, the event is silently dropped + if (this.error != null && this.error.length > 0) { + this.graph.validationAlert(this.error); } } - this.previous = this.currentState; - } - else if (this.previous == this.currentState && this.currentState != null && this.icons == null && - !this.graph.isMouseDown) - { - // Makes sure that no cursors are changed + // Redraws the connect icons and resets the handler state + this.destroyIcons(); me.consume(); } - if (!this.graph.isMouseDown && this.currentState != null && this.icons != null) - { - var hitsIcon = false; - var target = me.getSource(); - - for (var i = 0; i < this.icons.length && !hitsIcon; i++) - { - hitsIcon = target == this.icons[i].node || target.parentNode == this.icons[i].node; - } - - if (!hitsIcon) - { - this.updateIcons(this.currentState, this.icons, me); - } + if (this.first != null) { + this.reset(); } - } - else - { - this.constraintHandler.reset(); - } -}; + }; -/** - * Function: updateEdgeState - * - * Updates . - */ -updateEdgeState = (current, constraint)=> -{ - // TODO: Use generic method for writing constraint to style - if (this.sourceConstraint != null && this.sourceConstraint.point != null) - { - this.edgeState.style[mxConstants.STYLE_EXIT_X] = this.sourceConstraint.point.x; - this.edgeState.style[mxConstants.STYLE_EXIT_Y] = this.sourceConstraint.point.y; - } - - if (constraint != null && constraint.point != null) - { - this.edgeState.style[mxConstants.STYLE_ENTRY_X] = constraint.point.x; - this.edgeState.style[mxConstants.STYLE_ENTRY_Y] = constraint.point.y; - } - else - { - delete this.edgeState.style[mxConstants.STYLE_ENTRY_X]; - delete this.edgeState.style[mxConstants.STYLE_ENTRY_Y]; - } - - this.edgeState.absolutePoints = [null, (this.currentState != null) ? null : current]; - this.graph.view.updateFixedTerminalPoint(this.edgeState, this.previous, true, this.sourceConstraint); - - if (this.currentState != null) - { - if (constraint == null) - { - constraint = this.graph.getConnectionConstraint(this.edgeState, this.previous, false); + /** + * Function: reset + * + * Resets the state of this handler. + */ + reset = () => { + if (this.shape != null) { + this.shape.destroy(); + this.shape = null; } - - this.edgeState.setAbsoluteTerminalPoint(null, false); - this.graph.view.updateFixedTerminalPoint(this.edgeState, this.currentState, false, constraint); - } - - // Scales and translates the waypoints to the model - var realPoints = null; - - if (this.waypoints != null) - { - realPoints = []; - - for (var i = 0; i < this.waypoints.length; i++) - { - var pt = this.waypoints[i].clone(); - this.convertWaypoint(pt); - realPoints[i] = pt; - } - } - - this.graph.view.updatePoints(this.edgeState, realPoints, this.previous, this.currentState); - this.graph.view.updateFloatingTerminalPoints(this.edgeState, this.previous, this.currentState); -}; -/** - * Function: getTargetPerimeterPoint - * - * Returns the perimeter point for the given target state. - * - * Parameters: - * - * state - that represents the target cell state. - * me - that represents the mouse move. - */ -getTargetPerimeterPoint = (state, me)=> -{ - var result = null; - var view = state.view; - var targetPerimeter = view.getPerimeterFunction(state); - - if (targetPerimeter != null) - { - var next = (this.waypoints != null && this.waypoints.length > 0) ? - this.waypoints[this.waypoints.length - 1] : - new mxPoint(this.previous.getCenterX(), this.previous.getCenterY()); - var tmp = targetPerimeter(view.getPerimeterBounds(state), - this.edgeState, next, false); - - if (tmp != null) - { - result = tmp; + // Resets the cursor on the container + if (this.cursor != null && this.graph.container != null) { + this.graph.container.style.cursor = ''; } - } - else - { - result = new mxPoint(state.getCenterX(), state.getCenterY()); - } - - return result; -}; -/** - * Function: getSourcePerimeterPoint - * - * Hook to update the icon position(s) based on a mouseOver event. This is - * an empty implementation. - * - * Parameters: - * - * state - that represents the target cell state. - * next - that represents the next point along the previewed edge. - * me - that represents the mouse move. - */ -getSourcePerimeterPoint = (state, next, me)=> -{ - var result = null; - var view = state.view; - var sourcePerimeter = view.getPerimeterFunction(state); - var c = new mxPoint(state.getCenterX(), state.getCenterY()); - - if (sourcePerimeter != null) - { - var theta = mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0); - var rad = -theta * (Math.PI / 180); - - if (theta != 0) - { - next = mxUtils.getRotatedPoint(new mxPoint(next.x, next.y), Math.cos(rad), Math.sin(rad), c); - } - - var tmp = sourcePerimeter(view.getPerimeterBounds(state), state, next, false); - - if (tmp != null) - { - if (theta != 0) - { - tmp = mxUtils.getRotatedPoint(new mxPoint(tmp.x, tmp.y), Math.cos(-rad), Math.sin(-rad), c); - } - - result = tmp; - } - } - else - { - result = c; - } - - return result; -}; - - -/** - * Function: updateIcons - * - * Hook to update the icon position(s) based on a mouseOver event. This is - * an empty implementation. - * - * Parameters: - * - * state - under the mouse. - * icons - Array of currently displayed icons. - * me - that contains the mouse event. - */ -updateIcons = (state, icons, me)=> -{ - // empty -}; - -/** - * Function: isStopEvent - * - * Returns true if the given mouse up event should stop this handler. The - * connection will be created if is null. Note that this is only - * called if is true. This implemtation returns true - * if there is a cell state in the given event. - */ -isStopEvent = (me)=> -{ - return me.getState() != null; -}; - -/** - * Function: addWaypoint - * - * Adds the waypoint for the given event to . - */ -addWaypointForEvent = (me)=> -{ - var point = mxUtils.convertPoint(this.graph.container, me.getX(), me.getY()); - var dx = Math.abs(point.x - this.first.x); - var dy = Math.abs(point.y - this.first.y); - var addPoint = this.waypoints != null || (this.mouseDownCounter > 1 && - (dx > this.graph.tolerance || dy > this.graph.tolerance)); - - if (addPoint) - { - if (this.waypoints == null) - { - this.waypoints = []; - } - - var scale = this.graph.view.scale; - var point = new mxPoint(this.graph.snap(me.getGraphX() / scale) * scale, - this.graph.snap(me.getGraphY() / scale) * scale); - this.waypoints.push(point); - } -}; - -/** - * Function: checkConstraints - * - * Returns true if the connection for the given constraints is valid. This - * implementation returns true if the constraints are not pointing to the - * same fixed connection point. - */ -checkConstraints = (c1, c2)=> -{ - return (c1 == null || c2 == null || c1.point == null || c2.point == null || - !c1.point.equals(c2.point) || c1.dx != c2.dx || c1.dy != c2.dy || - c1.perimeter != c2.perimeter); -}; - -/** - * Function: mouseUp - * - * Handles the event by inserting the new connection. - */ -mouseUp = (sender, me)=> -{ - if (!me.isConsumed() && this.isConnecting()) - { - if (this.waypointsEnabled && !this.isStopEvent(me)) - { - this.addWaypointForEvent(me); - me.consume(); - - return; - } - - var c1 = this.sourceConstraint; - var c2 = this.constraintHandler.currentConstraint; - - var source = (this.previous != null) ? this.previous.cell : null; - var target = null; - - if (this.constraintHandler.currentConstraint != null && - this.constraintHandler.currentFocus != null) - { - target = this.constraintHandler.currentFocus.cell; - } - - if (target == null && this.currentState != null) - { - target = this.currentState.cell; - } - - // Inserts the edge if no validation error exists and if constraints differ - if (this.error == null && (source == null || target == null || - source != target || this.checkConstraints(c1, c2))) - { - this.connect(source, target, me.getEvent(), me.getCell()); - } - else - { - // Selects the source terminal for self-references - if (this.previous != null && this.marker.validState != null && - this.previous.cell == this.marker.validState.cell) - { - this.graph.selectCellForEvent(this.marker.source, me.getEvent()); - } - - // Displays the error message if it is not an empty string, - // for empty error messages, the event is silently dropped - if (this.error != null && this.error.length > 0) - { - this.graph.validationAlert(this.error); - } - } - - // Redraws the connect icons and resets the handler state this.destroyIcons(); - me.consume(); - } + this.marker.reset(); + this.constraintHandler.reset(); + this.originalPoint = null; + this.currentPoint = null; + this.edgeState = null; + this.previous = null; + this.error = null; + this.sourceConstraint = null; + this.mouseDownCounter = 0; + this.first = null; - if (this.first != null) - { - this.reset(); - } -}; + this.fireEvent(new mxEventObject(mxEvent.RESET)); + }; -/** - * Function: reset - * - * Resets the state of this handler. - */ -reset = ()=> -{ - if (this.shape != null) - { - this.shape.destroy(); - this.shape = null; - } - - // Resets the cursor on the container - if (this.cursor != null && this.graph.container != null) - { - this.graph.container.style.cursor = ''; - } - - this.destroyIcons(); - this.marker.reset(); - this.constraintHandler.reset(); - this.originalPoint = null; - this.currentPoint = null; - this.edgeState = null; - this.previous = null; - this.error = null; - this.sourceConstraint = null; - this.mouseDownCounter = 0; - this.first = null; + /** + * Function: drawPreview + * + * Redraws the preview edge using the color and width returned by + * and . + */ + drawPreview = () => { + this.updatePreview(this.error == null); + this.shape.redraw(); + }; - this.fireEvent(new mxEventObject(mxEvent.RESET)); -}; + /** + * Function: getEdgeColor + * + * Returns the color used to draw the preview edge. This returns green if + * there is no edge validation error and red otherwise. + * + * Parameters: + * + * valid - Boolean indicating if the color for a valid edge should be + * returned. + */ + updatePreview = (valid) => { + this.shape.strokewidth = this.getEdgeWidth(valid); + this.shape.stroke = this.getEdgeColor(valid); + }; -/** - * Function: drawPreview - * - * Redraws the preview edge using the color and width returned by - * and . - */ -drawPreview = ()=> -{ - this.updatePreview(this.error == null); - this.shape.redraw(); -}; + /** + * Function: getEdgeColor + * + * Returns the color used to draw the preview edge. This returns green if + * there is no edge validation error and red otherwise. + * + * Parameters: + * + * valid - Boolean indicating if the color for a valid edge should be + * returned. + */ + getEdgeColor = (valid) => { + return (valid) ? mxConstants.VALID_COLOR : mxConstants.INVALID_COLOR; + }; -/** - * Function: getEdgeColor - * - * Returns the color used to draw the preview edge. This returns green if - * there is no edge validation error and red otherwise. - * - * Parameters: - * - * valid - Boolean indicating if the color for a valid edge should be - * returned. - */ -updatePreview = (valid)=> -{ - this.shape.strokewidth = this.getEdgeWidth(valid); - this.shape.stroke = this.getEdgeColor(valid); -}; + /** + * Function: getEdgeWidth + * + * Returns the width used to draw the preview edge. This returns 3 if + * there is no edge validation error and 1 otherwise. + * + * Parameters: + * + * valid - Boolean indicating if the width for a valid edge should be + * returned. + */ + getEdgeWidth = (valid) => { + return (valid) ? 3 : 1; + }; -/** - * Function: getEdgeColor - * - * Returns the color used to draw the preview edge. This returns green if - * there is no edge validation error and red otherwise. - * - * Parameters: - * - * valid - Boolean indicating if the color for a valid edge should be - * returned. - */ -getEdgeColor = (valid)=> -{ - return (valid) ? mxConstants.VALID_COLOR : mxConstants.INVALID_COLOR; -}; - -/** - * Function: getEdgeWidth - * - * Returns the width used to draw the preview edge. This returns 3 if - * there is no edge validation error and 1 otherwise. - * - * Parameters: - * - * valid - Boolean indicating if the width for a valid edge should be - * returned. - */ -getEdgeWidth = (valid)=> -{ - return (valid) ? 3 : 1; -}; + /** + * Function: connect + * + * Connects the given source and target using a new edge. This + * implementation uses to create the edge. + * + * Parameters: + * + * source - that represents the source terminal. + * target - that represents the target terminal. + * evt - Mousedown event of the connect gesture. + * dropTarget - that represents the cell under the mouse when it was + * released. + */ + connect = (source, target, evt, dropTarget) => { + if (target != null || this.isCreateTarget(evt) || this.graph.allowDanglingEdges) { + // Uses the common parent of source and target or + // the default parent to insert the edge + var model = this.graph.getModel(); + var terminalInserted = false; + var edge = null; -/** - * Function: connect - * - * Connects the given source and target using a new edge. This - * implementation uses to create the edge. - * - * Parameters: - * - * source - that represents the source terminal. - * target - that represents the target terminal. - * evt - Mousedown event of the connect gesture. - * dropTarget - that represents the cell under the mouse when it was - * released. - */ -connect = (source, target, evt, dropTarget)=> -{ - if (target != null || this.isCreateTarget(evt) || this.graph.allowDanglingEdges) - { - // Uses the common parent of source and target or - // the default parent to insert the edge - var model = this.graph.getModel(); - var terminalInserted = false; + model.beginUpdate(); + try { + if (source != null && target == null && !this.graph.isIgnoreTerminalEvent(evt) && this.isCreateTarget(evt)) { + target = this.createTargetVertex(evt, source); + + if (target != null) { + dropTarget = this.graph.getDropTarget([target], evt, dropTarget); + terminalInserted = true; + + // Disables edges as drop targets if the target cell was created + // FIXME: Should not shift if vertex was aligned (same in Java) + if (dropTarget == null || !this.graph.getModel().isEdge(dropTarget)) { + var pstate = this.graph.getView().getState(dropTarget); + + if (pstate != null) { + var tmp = model.getGeometry(target); + tmp.x -= pstate.origin.x; + tmp.y -= pstate.origin.y; + } + } else { + dropTarget = this.graph.getDefaultParent(); + } + + this.graph.addCell(target, dropTarget); + } + } + + var parent = this.graph.getDefaultParent(); + + if (source != null && target != null && + model.getParent(source) == model.getParent(target) && + model.getParent(model.getParent(source)) != model.getRoot()) { + parent = model.getParent(source); + + if ((source.geometry != null && source.geometry.relative) && + (target.geometry != null && target.geometry.relative)) { + parent = model.getParent(parent); + } + } + + // Uses the value of the preview edge state for inserting + // the new edge into the graph + var value = null; + var style = null; + + if (this.edgeState != null) { + value = this.edgeState.cell.value; + style = this.edgeState.cell.style; + } + + edge = this.insertEdge(parent, null, value, source, target, style); + + if (edge != null) { + // Updates the connection constraints + this.graph.setConnectionConstraint(edge, source, true, this.sourceConstraint); + this.graph.setConnectionConstraint(edge, target, false, this.constraintHandler.currentConstraint); + + // Uses geometry of the preview edge state + if (this.edgeState != null) { + model.setGeometry(edge, this.edgeState.cell.geometry); + } + + var parent = model.getParent(source); + + // Inserts edge before source + if (this.isInsertBefore(edge, source, target, evt, dropTarget)) { + var index = null; + var tmp = source; + + while (tmp.parent != null && tmp.geometry != null && + tmp.geometry.relative && tmp.parent != edge.parent) { + tmp = this.graph.model.getParent(tmp); + } + + if (tmp != null && tmp.parent != null && tmp.parent == edge.parent) { + model.add(parent, edge, tmp.parent.getIndex(tmp)); + } + } + + // Makes sure the edge has a non-null, relative geometry + var geo = model.getGeometry(edge); + + if (geo == null) { + geo = new mxGeometry(); + geo.relative = true; + + model.setGeometry(edge, geo); + } + + // Uses scaled waypoints in geometry + if (this.waypoints != null && this.waypoints.length > 0) { + var s = this.graph.view.scale; + var tr = this.graph.view.translate; + geo.points = []; + + for (var i = 0; i < this.waypoints.length; i++) { + var pt = this.waypoints[i]; + geo.points.push(new mxPoint(pt.x / s - tr.x, pt.y / s - tr.y)); + } + } + + if (target == null) { + var t = this.graph.view.translate; + var s = this.graph.view.scale; + var pt = (this.originalPoint != null) ? + new mxPoint(this.originalPoint.x / s - t.x, this.originalPoint.y / s - t.y) : + new mxPoint(this.currentPoint.x / s - t.x, this.currentPoint.y / s - t.y); + pt.x -= this.graph.panDx / this.graph.view.scale; + pt.y -= this.graph.panDy / this.graph.view.scale; + geo.setTerminalPoint(pt, false); + } + + this.fireEvent(new mxEventObject(mxEvent.CONNECT, 'cell', edge, 'terminal', target, + 'event', evt, 'target', dropTarget, 'terminalInserted', terminalInserted)); + } + } catch (e) { + mxLog.show(); + mxLog.debug(e.message); + } finally { + model.endUpdate(); + } + + if (this.select) { + this.selectCells(edge, (terminalInserted) ? target : null); + } + } + }; + + /** + * Function: selectCells + * + * Selects the given edge after adding a new connection. The target argument + * contains the target vertex if one has been inserted. + */ + selectCells = (edge, target) => { + this.graph.setSelectionCell(edge); + }; + + /** + * Function: insertEdge + * + * Creates, inserts and returns the new edge for the given parameters. This + * implementation does only use if is defined, + * otherwise will be used. + */ + insertEdge = (parent, id, value, source, target, style) => { + if (this.factoryMethod == null) { + return this.graph.insertEdge(parent, id, value, source, target, style); + } else { + var edge = this.createEdge(value, source, target, style); + edge = this.graph.addEdge(edge, parent, source, target); + + return edge; + } + }; + + /** + * Function: createTargetVertex + * + * Hook method for creating new vertices on the fly if no target was + * under the mouse. This is only called if is true and + * returns null. + * + * Parameters: + * + * evt - Mousedown event of the connect gesture. + * source - that represents the source terminal. + */ + createTargetVertex = (evt, source) => { + // Uses the first non-relative source + var geo = this.graph.getCellGeometry(source); + + while (geo != null && geo.relative) { + source = this.graph.getModel().getParent(source); + geo = this.graph.getCellGeometry(source); + } + + var clone = this.graph.cloneCell(source); + var geo = this.graph.getModel().getGeometry(clone); + + if (geo != null) { + var t = this.graph.view.translate; + var s = this.graph.view.scale; + var point = new mxPoint(this.currentPoint.x / s - t.x, this.currentPoint.y / s - t.y); + geo.x = Math.round(point.x - geo.width / 2 - this.graph.panDx / s); + geo.y = Math.round(point.y - geo.height / 2 - this.graph.panDy / s); + + // Aligns with source if within certain tolerance + var tol = this.getAlignmentTolerance(); + + if (tol > 0) { + var sourceState = this.graph.view.getState(source); + + if (sourceState != null) { + var x = sourceState.x / s - t.x; + var y = sourceState.y / s - t.y; + + if (Math.abs(x - geo.x) <= tol) { + geo.x = Math.round(x); + } + + if (Math.abs(y - geo.y) <= tol) { + geo.y = Math.round(y); + } + } + } + } + + return clone; + }; + + /** + * Function: getAlignmentTolerance + * + * Returns the tolerance for aligning new targets to sources. This returns the grid size / 2. + */ + getAlignmentTolerance = (evt) => { + return (this.graph.isGridEnabled()) ? this.graph.gridSize / 2 : this.graph.tolerance; + }; + + /** + * Function: createEdge + * + * Creates and returns a new edge using if one exists. If + * no factory method is defined, then a new default edge is returned. The + * source and target arguments are informal, the actual connection is + * setup later by the caller of this function. + * + * Parameters: + * + * value - Value to be used for creating the edge. + * source - that represents the source terminal. + * target - that represents the target terminal. + * style - Optional style from the preview edge. + */ + createEdge = (value, source, target, style) => { var edge = null; - model.beginUpdate(); - try - { - if (source != null && target == null && !this.graph.isIgnoreTerminalEvent(evt) && this.isCreateTarget(evt)) - { - target = this.createTargetVertex(evt, source); - - if (target != null) - { - dropTarget = this.graph.getDropTarget([target], evt, dropTarget); - terminalInserted = true; - - // Disables edges as drop targets if the target cell was created - // FIXME: Should not shift if vertex was aligned (same in Java) - if (dropTarget == null || !this.graph.getModel().isEdge(dropTarget)) - { - var pstate = this.graph.getView().getState(dropTarget); - - if (pstate != null) - { - var tmp = model.getGeometry(target); - tmp.x -= pstate.origin.x; - tmp.y -= pstate.origin.y; - } - } - else - { - dropTarget = this.graph.getDefaultParent(); - } - - this.graph.addCell(target, dropTarget); - } - } - - var parent = this.graph.getDefaultParent(); - - if (source != null && target != null && - model.getParent(source) == model.getParent(target) && - model.getParent(model.getParent(source)) != model.getRoot()) - { - parent = model.getParent(source); - - if ((source.geometry != null && source.geometry.relative) && - (target.geometry != null && target.geometry.relative)) - { - parent = model.getParent(parent); - } - } - - // Uses the value of the preview edge state for inserting - // the new edge into the graph - var value = null; - var style = null; - - if (this.edgeState != null) - { - value = this.edgeState.cell.value; - style = this.edgeState.cell.style; - } - - edge = this.insertEdge(parent, null, value, source, target, style); - - if (edge != null) - { - // Updates the connection constraints - this.graph.setConnectionConstraint(edge, source, true, this.sourceConstraint); - this.graph.setConnectionConstraint(edge, target, false, this.constraintHandler.currentConstraint); - - // Uses geometry of the preview edge state - if (this.edgeState != null) - { - model.setGeometry(edge, this.edgeState.cell.geometry); - } - - var parent = model.getParent(source); - - // Inserts edge before source - if (this.isInsertBefore(edge, source, target, evt, dropTarget)) - { - var index = null; - var tmp = source; - - while (tmp.parent != null && tmp.geometry != null && - tmp.geometry.relative && tmp.parent != edge.parent) - { - tmp = this.graph.model.getParent(tmp); - } - - if (tmp != null && tmp.parent != null && tmp.parent == edge.parent) - { - model.add(parent, edge, tmp.parent.getIndex(tmp)); - } - } - - // Makes sure the edge has a non-null, relative geometry - var geo = model.getGeometry(edge); - - if (geo == null) - { - geo = new mxGeometry(); - geo.relative = true; - - model.setGeometry(edge, geo); - } - - // Uses scaled waypoints in geometry - if (this.waypoints != null && this.waypoints.length > 0) - { - var s = this.graph.view.scale; - var tr = this.graph.view.translate; - geo.points = []; - - for (var i = 0; i < this.waypoints.length; i++) - { - var pt = this.waypoints[i]; - geo.points.push(new mxPoint(pt.x / s - tr.x, pt.y / s - tr.y)); - } - } - - if (target == null) - { - var t = this.graph.view.translate; - var s = this.graph.view.scale; - var pt = (this.originalPoint != null) ? - new mxPoint(this.originalPoint.x / s - t.x, this.originalPoint.y / s - t.y) : - new mxPoint(this.currentPoint.x / s - t.x, this.currentPoint.y / s - t.y); - pt.x -= this.graph.panDx / this.graph.view.scale; - pt.y -= this.graph.panDy / this.graph.view.scale; - geo.setTerminalPoint(pt, false); - } - - this.fireEvent(new mxEventObject(mxEvent.CONNECT, 'cell', edge, 'terminal', target, - 'event', evt, 'target', dropTarget, 'terminalInserted', terminalInserted)); - } + // Creates a new edge using the factoryMethod + if (this.factoryMethod != null) { + edge = this.factoryMethod(source, target, style); } - catch (e) - { - mxLog.show(); - mxLog.debug(e.message); - } - finally - { - model.endUpdate(); - } - - if (this.select) - { - this.selectCells(edge, (terminalInserted) ? target : null); - } - } -}; -/** - * Function: selectCells - * - * Selects the given edge after adding a new connection. The target argument - * contains the target vertex if one has been inserted. - */ -selectCells = (edge, target)=> -{ - this.graph.setSelectionCell(edge); -}; + if (edge == null) { + edge = new mxCell(value || ''); + edge.setEdge(true); + edge.setStyle(style); + + var geo = new mxGeometry(); + geo.relative = true; + edge.setGeometry(geo); + } -/** - * Function: insertEdge - * - * Creates, inserts and returns the new edge for the given parameters. This - * implementation does only use if is defined, - * otherwise will be used. - */ -insertEdge = (parent, id, value, source, target, style)=> -{ - if (this.factoryMethod == null) - { - return this.graph.insertEdge(parent, id, value, source, target, style); - } - else - { - var edge = this.createEdge(value, source, target, style); - edge = this.graph.addEdge(edge, parent, source, target); - return edge; - } -}; + }; -/** - * Function: createTargetVertex - * - * Hook method for creating new vertices on the fly if no target was - * under the mouse. This is only called if is true and - * returns null. - * - * Parameters: - * - * evt - Mousedown event of the connect gesture. - * source - that represents the source terminal. - */ -createTargetVertex = (evt, source)=> -{ - // Uses the first non-relative source - var geo = this.graph.getCellGeometry(source); - - while (geo != null && geo.relative) - { - source = this.graph.getModel().getParent(source); - geo = this.graph.getCellGeometry(source); - } - - var clone = this.graph.cloneCell(source); - var geo = this.graph.getModel().getGeometry(clone); - - if (geo != null) - { - var t = this.graph.view.translate; - var s = this.graph.view.scale; - var point = new mxPoint(this.currentPoint.x / s - t.x, this.currentPoint.y / s - t.y); - geo.x = Math.round(point.x - geo.width / 2 - this.graph.panDx / s); - geo.y = Math.round(point.y - geo.height / 2 - this.graph.panDy / s); + /** + * Function: destroy + * + * Destroys the handler and all its resources and DOM nodes. This should be + * called on all instances. It is called automatically for the built-in + * instance created for each . + */ + destroy = () => { + this.graph.removeMouseListener(this); - // Aligns with source if within certain tolerance - var tol = this.getAlignmentTolerance(); - - if (tol > 0) - { - var sourceState = this.graph.view.getState(source); - - if (sourceState != null) - { - var x = sourceState.x / s - t.x; - var y = sourceState.y / s - t.y; - - if (Math.abs(x - geo.x) <= tol) - { - geo.x = Math.round(x); - } - - if (Math.abs(y - geo.y) <= tol) - { - geo.y = Math.round(y); - } - } + if (this.shape != null) { + this.shape.destroy(); + this.shape = null; } - } - return clone; -}; + if (this.marker != null) { + this.marker.destroy(); + this.marker = null; + } -/** - * Function: getAlignmentTolerance - * - * Returns the tolerance for aligning new targets to sources. This returns the grid size / 2. - */ -getAlignmentTolerance = (evt)=> -{ - return (this.graph.isGridEnabled()) ? this.graph.gridSize / 2 : this.graph.tolerance; -}; + if (this.constraintHandler != null) { + this.constraintHandler.destroy(); + this.constraintHandler = null; + } -/** - * Function: createEdge - * - * Creates and returns a new edge using if one exists. If - * no factory method is defined, then a new default edge is returned. The - * source and target arguments are informal, the actual connection is - * setup later by the caller of this function. - * - * Parameters: - * - * value - Value to be used for creating the edge. - * source - that represents the source terminal. - * target - that represents the target terminal. - * style - Optional style from the preview edge. - */ -createEdge = (value, source, target, style)=> -{ - var edge = null; - - // Creates a new edge using the factoryMethod - if (this.factoryMethod != null) - { - edge = this.factoryMethod(source, target, style); - } - - if (edge == null) - { - edge = new mxCell(value || ''); - edge.setEdge(true); - edge.setStyle(style); - - var geo = new mxGeometry(); - geo.relative = true; - edge.setGeometry(geo); - } + if (this.changeHandler != null) { + this.graph.getModel().removeListener(this.changeHandler); + this.graph.getView().removeListener(this.changeHandler); + this.changeHandler = null; + } - return edge; -}; + if (this.drillHandler != null) { + this.graph.removeListener(this.drillHandler); + this.graph.getView().removeListener(this.drillHandler); + this.drillHandler = null; + } -/** - * Function: destroy - * - * Destroys the handler and all its resources and DOM nodes. This should be - * called on all instances. It is called automatically for the built-in - * instance created for each . - */ -destroy = ()=> -{ - this.graph.removeMouseListener(this); - - if (this.shape != null) - { - this.shape.destroy(); - this.shape = null; - } - - if (this.marker != null) - { - this.marker.destroy(); - this.marker = null; - } + if (this.escapeHandler != null) { + this.graph.removeListener(this.escapeHandler); + this.escapeHandler = null; + } + }; +} - if (this.constraintHandler != null) - { - this.constraintHandler.destroy(); - this.constraintHandler = null; - } - - if (this.changeHandler != null) - { - this.graph.getModel().removeListener(this.changeHandler); - this.graph.getView().removeListener(this.changeHandler); - this.changeHandler = null; - } - - if (this.drillHandler != null) - { - this.graph.removeListener(this.drillHandler); - this.graph.getView().removeListener(this.drillHandler); - this.drillHandler = null; - } - - if (this.escapeHandler != null) - { - this.graph.removeListener(this.escapeHandler); - this.escapeHandler = null; - } -}; +export default mxConnectionHandler; diff --git a/src/js/handler/mxConstraintHandler.js b/src/js/handler/mxConstraintHandler.js index 4f8ff3637..4ce7159af 100644 --- a/src/js/handler/mxConstraintHandler.js +++ b/src/js/handler/mxConstraintHandler.js @@ -2,505 +2,455 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * Class: mxConstraintHandler - * - * Handles constraints on connection targets. This class is in charge of - * showing fixed points when the mouse is over a vertex and handles constraints - * to establish new connections. - * - * Constructor: mxConstraintHandler - * - * Constructs an new constraint handler. - * - * Parameters: - * - * graph - Reference to the enclosing . - * factoryMethod - Optional function to create the edge. The function takes - * the source and target as the first and second argument and - * returns the that represents the new edge. - */ -function mxConstraintHandler(graph) -{ - this.graph = graph; - - // Adds a graph model listener to update the current focus on changes - this.resetHandler = mxUtils.bind(this, (sender, evt)=> - { - if (this.currentFocus != null && this.graph.view.getState(this.currentFocus.cell) == null) - { - this.reset(); - } - else - { - this.redraw(); - } - }); - - this.graph.model.addListener(mxEvent.CHANGE, this.resetHandler); - this.graph.view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.resetHandler); - this.graph.view.addListener(mxEvent.TRANSLATE, this.resetHandler); - this.graph.view.addListener(mxEvent.SCALE, this.resetHandler); - this.graph.addListener(mxEvent.ROOT, this.resetHandler); -}; -/** - * Variable: pointImage - * - * to be used as the image for fixed connection points. - */ -pointImage = new mxImage(mxClient.imageBasePath + '/point.gif', 5, 5); +class mxConstraintHandler { + /** + * Variable: pointImage + * + * to be used as the image for fixed connection points. + */ + pointImage = new mxImage(mxClient.imageBasePath + '/point.gif', 5, 5); -/** - * Variable: graph - * - * Reference to the enclosing