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.

Ids

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.

Programmatically inserting cells

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.

Ids

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.

Programmatically inserting cells

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

Functions

-

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();
+

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

templatePrototypical instance of the object to be encoded/decoded.
excludeOptional array of fieldnames to be ignored.
idrefsOptional array of fieldnames to be converted to/from references.
mappingOptional mapping from field- to attributenames.

Variables

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);

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:

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;

getDefaultEdgeStyle

mxStylesheet.prototype.getDefaultEdgeStyle = function()

Sets the default style for edges.

-

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, the event and target - * 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 in or 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, the event and target + * 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 in or 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 . - */ -graph = null; + /** + * Variable: graph + * + * Reference to the enclosing . + */ + graph = null; -/** - * Variable: enabled - * - * Specifies if events are handled. Default is true. - */ -enabled = true; + /** + * Variable: enabled + * + * Specifies if events are handled. Default is true. + */ + enabled = true; -/** - * Variable: highlightColor - * - * Specifies the color for the highlight. Default is . - */ -highlightColor = mxConstants.DEFAULT_VALID_COLOR; + /** + * Variable: highlightColor + * + * Specifies the color for the highlight. Default is . + */ + highlightColor = mxConstants.DEFAULT_VALID_COLOR; -/** - * 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; -}; + /** + * 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. + */ + constructor(graph) { + this.graph = graph; -/** - * Function: reset - * - * Resets the state of this handler. - */ -reset = ()=> -{ - if (this.focusIcons != null) - { - for (var i = 0; i < this.focusIcons.length; i++) - { - this.focusIcons[i].destroy(); - } - - this.focusIcons = null; - } - - if (this.focusHighlight != null) - { - this.focusHighlight.destroy(); - this.focusHighlight = null; - } - - this.currentConstraint = null; - this.currentFocusArea = null; - this.currentPoint = null; - this.currentFocus = null; - this.focusPoints = null; -}; - -/** - * Function: getTolerance - * - * Returns the tolerance to be used for intersecting connection points. This - * implementation returns . - * - * Parameters: - * - * me - whose tolerance should be returned. - */ -getTolerance = (me)=> -{ - return this.graph.getTolerance(); -}; - -/** - * Function: getImageForConstraint - * - * Returns the tolerance to be used for intersecting connection points. - */ -getImageForConstraint = (state, constraint, point)=> -{ - return this.pointImage; -}; - -/** - * Function: isEventIgnored - * - * Returns true if the given should be ignored in . This - * implementation always returns false. - */ -isEventIgnored = (me, source)=> -{ - return false; -}; - -/** - * Function: isStateIgnored - * - * Returns true if the given state should be ignored. This always returns false. - */ -isStateIgnored = (state, source)=> -{ - return false; -}; - -/** - * Function: destroyIcons - * - * Destroys the if they exist. - */ -destroyIcons = ()=> -{ - if (this.focusIcons != null) - { - for (var i = 0; i < this.focusIcons.length; i++) - { - this.focusIcons[i].destroy(); - } - - this.focusIcons = null; - this.focusPoints = null; - } -}; - -/** - * Function: destroyFocusHighlight - * - * Destroys the if one exists. - */ -destroyFocusHighlight = ()=> -{ - if (this.focusHighlight != null) - { - this.focusHighlight.destroy(); - this.focusHighlight = null; - } -}; - -/** - * Function: isKeepFocusEvent - * - * Returns true if the current focused state should not be changed for the given event. - * This returns true if shift and alt are pressed. - */ -isKeepFocusEvent = (me)=> -{ - return mxEvent.isShiftDown(me.getEvent()); -}; - -/** - * Function: getCellForEvent - * - * Returns the cell for the given event. - */ -getCellForEvent = (me, point)=> -{ - var cell = me.getCell(); - - // Gets cell under actual point if different from event location - if (cell == null && point != null && (me.getGraphX() != point.x || me.getGraphY() != point.y)) - { - cell = this.graph.getCellAt(point.x, point.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; - } - } - - return (this.graph.isCellLocked(cell)) ? null : cell; -}; - -/** - * Function: update - * - * Updates the state of this handler based on the given . - * Source is a boolean indicating if the cell is a source or target. - */ -update = (me, source, existingEdge, point)=> -{ - if (this.isEnabled() && !this.isEventIgnored(me)) - { - // Lazy installation of mouseleave handler - if (this.mouseleaveHandler == null && this.graph.container != null) - { - this.mouseleaveHandler = mxUtils.bind(this, ()=> - { + // 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(); - }); - - mxEvent.addListener(this.graph.container, 'mouseleave', this.resetHandler); - } - - var tol = this.getTolerance(me); - var x = (point != null) ? point.x : me.getGraphX(); - var y = (point != null) ? point.y : me.getGraphY(); - var grid = new mxRectangle(x - tol, y - tol, 2 * tol, 2 * tol); - var mouse = new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol); - var state = this.graph.view.getState(this.getCellForEvent(me, point)); - - // Keeps focus icons visible while over vertex bounds and no other cell under mouse or shift is pressed - if (!this.isKeepFocusEvent(me) && (this.currentFocusArea == null || this.currentFocus == null || - (state != null) || !this.graph.getModel().isVertex(this.currentFocus.cell) || - !mxUtils.intersects(this.currentFocusArea, mouse)) && (state != this.currentFocus)) - { - this.currentFocusArea = null; - this.currentFocus = null; - this.setFocus(me, state, source); - } - - this.currentConstraint = null; - this.currentPoint = null; - var minDistSq = null; - - if (this.focusIcons != null && this.constraints != null && - (state == null || this.currentFocus == state)) - { - var cx = mouse.getCenterX(); - var cy = mouse.getCenterY(); - - for (var i = 0; i < this.focusIcons.length; i++) - { - var dx = cx - this.focusIcons[i].bounds.getCenterX(); - var dy = cy - this.focusIcons[i].bounds.getCenterY(); - var tmp = dx * dx + dy * dy; - - if ((this.intersects(this.focusIcons[i], mouse, source, existingEdge) || (point != null && - this.intersects(this.focusIcons[i], grid, source, existingEdge))) && - (minDistSq == null || tmp < minDistSq)) - { - this.currentConstraint = this.constraints[i]; - this.currentPoint = this.focusPoints[i]; - minDistSq = tmp; - - var tmp = this.focusIcons[i].bounds.clone(); - tmp.grow(mxConstants.HIGHLIGHT_SIZE + 1); - tmp.width -= 1; - tmp.height -= 1; - - if (this.focusHighlight == null) - { - var hl = this.createHighlightShape(); - hl.dialect = (this.graph.dialect == mxConstants.DIALECT_SVG) ? - mxConstants.DIALECT_SVG : mxConstants.DIALECT_VML; - hl.pointerEvents = false; - - hl.init(this.graph.getView().getOverlayPane()); - this.focusHighlight = hl; - - var getState = mxUtils.bind(this, ()=> - { - return (this.currentFocus != null) ? this.currentFocus : state; - }); - - mxEvent.redirectMouseEvents(hl.node, this.graph, getState); - } - - this.focusHighlight.bounds = tmp; - this.focusHighlight.redraw(); - } + } else { + this.redraw(); } - } - - if (this.currentConstraint == null) - { - this.destroyFocusHighlight(); - } - } - else - { - this.currentConstraint = null; - this.currentFocus = null; - this.currentPoint = null; - } -}; + }); -/** - * Function: redraw - * - * Transfers the focus to the given state as a source or target terminal. If - * the handler is not enabled then the outline is painted, but the constraints - * are ignored. - */ -redraw = ()=> -{ - if (this.currentFocus != null && this.constraints != null && this.focusIcons != null) - { - var state = this.graph.view.getState(this.currentFocus.cell); - this.currentFocus = state; - this.currentFocusArea = new mxRectangle(state.x, state.y, state.width, state.height); - - for (var i = 0; i < this.constraints.length; i++) - { - var cp = this.graph.getConnectionPoint(state, this.constraints[i]); - var img = this.getImageForConstraint(state, this.constraints[i], cp); + 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); + }; - var bounds = new mxRectangle(Math.round(cp.x - img.width / 2), - Math.round(cp.y - img.height / 2), img.width, img.height); - this.focusIcons[i].bounds = bounds; - this.focusIcons[i].redraw(); - this.currentFocusArea.add(this.focusIcons[i].bounds); - this.focusPoints[i] = cp; - } - } -}; + /** + * Function: isEnabled + * + * Returns true if events are handled. This implementation + * returns . + */ + isEnabled = () => { + return this.enabled; + }; -/** - * Function: setFocus - * - * Transfers the focus to the given state as a source or target terminal. If - * the handler is not enabled then the outline is painted, but the constraints - * are ignored. - */ -setFocus = (me, state, source)=> -{ - this.constraints = (state != null && !this.isStateIgnored(state, source) && - this.graph.isCellConnectable(state.cell)) ? ((this.isEnabled()) ? - (this.graph.getAllConnectionConstraints(state, source) || []) : []) : null; + /** + * Function: setEnabled + * + * Enables or disables event handling. This implementation + * updates . + * + * Parameters: + * + * enabled - Boolean that specifies the new enabled state. + */ + setEnabled = (enabled) => { + this.enabled = enabled; + }; - // Only uses cells which have constraints - if (this.constraints != null) - { - this.currentFocus = state; - this.currentFocusArea = new mxRectangle(state.x, state.y, state.width, state.height); - - if (this.focusIcons != null) - { - for (var i = 0; i < this.focusIcons.length; i++) - { + /** + * Function: reset + * + * Resets the state of this handler. + */ + reset = () => { + if (this.focusIcons != null) { + for (var i = 0; i < this.focusIcons.length; i++) { this.focusIcons[i].destroy(); } - + + this.focusIcons = null; + } + + if (this.focusHighlight != null) { + this.focusHighlight.destroy(); + this.focusHighlight = null; + } + + this.currentConstraint = null; + this.currentFocusArea = null; + this.currentPoint = null; + this.currentFocus = null; + this.focusPoints = null; + }; + + /** + * Function: getTolerance + * + * Returns the tolerance to be used for intersecting connection points. This + * implementation returns . + * + * Parameters: + * + * me - whose tolerance should be returned. + */ + getTolerance = (me) => { + return this.graph.getTolerance(); + }; + + /** + * Function: getImageForConstraint + * + * Returns the tolerance to be used for intersecting connection points. + */ + getImageForConstraint = (state, constraint, point) => { + return this.pointImage; + }; + + /** + * Function: isEventIgnored + * + * Returns true if the given should be ignored in . This + * implementation always returns false. + */ + isEventIgnored = (me, source) => { + return false; + }; + + /** + * Function: isStateIgnored + * + * Returns true if the given state should be ignored. This always returns false. + */ + isStateIgnored = (state, source) => { + return false; + }; + + /** + * Function: destroyIcons + * + * Destroys the if they exist. + */ + destroyIcons = () => { + if (this.focusIcons != null) { + for (var i = 0; i < this.focusIcons.length; i++) { + this.focusIcons[i].destroy(); + } + this.focusIcons = null; this.focusPoints = null; } - - this.focusPoints = []; - this.focusIcons = []; - - for (var i = 0; i < this.constraints.length; i++) - { - var cp = this.graph.getConnectionPoint(state, this.constraints[i]); - var img = this.getImageForConstraint(state, this.constraints[i], cp); + }; - var src = img.src; - var bounds = new mxRectangle(Math.round(cp.x - img.width / 2), - Math.round(cp.y - img.height / 2), img.width, img.height); - var icon = new mxImageShape(bounds, src); - icon.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? - mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG; - icon.preserveImageAspect = false; - icon.init(this.graph.getView().getDecoratorPane()); - - // Move the icon behind all other overlays - if (icon.node.previousSibling != null) - { - icon.node.parentNode.insertBefore(icon.node, icon.node.parentNode.firstChild); + /** + * Function: destroyFocusHighlight + * + * Destroys the if one exists. + */ + destroyFocusHighlight = () => { + if (this.focusHighlight != null) { + this.focusHighlight.destroy(); + this.focusHighlight = null; + } + }; + + /** + * Function: isKeepFocusEvent + * + * Returns true if the current focused state should not be changed for the given event. + * This returns true if shift and alt are pressed. + */ + isKeepFocusEvent = (me) => { + return mxEvent.isShiftDown(me.getEvent()); + }; + + /** + * Function: getCellForEvent + * + * Returns the cell for the given event. + */ + getCellForEvent = (me, point) => { + var cell = me.getCell(); + + // Gets cell under actual point if different from event location + if (cell == null && point != null && (me.getGraphX() != point.x || me.getGraphY() != point.y)) { + cell = this.graph.getCellAt(point.x, point.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; + } + } + + return (this.graph.isCellLocked(cell)) ? null : cell; + }; + + /** + * Function: update + * + * Updates the state of this handler based on the given . + * Source is a boolean indicating if the cell is a source or target. + */ + update = (me, source, existingEdge, point) => { + if (this.isEnabled() && !this.isEventIgnored(me)) { + // Lazy installation of mouseleave handler + if (this.mouseleaveHandler == null && this.graph.container != null) { + this.mouseleaveHandler = mxUtils.bind(this, () => { + this.reset(); + }); + + mxEvent.addListener(this.graph.container, 'mouseleave', this.resetHandler); } - var getState = mxUtils.bind(this, ()=> - { - return (this.currentFocus != null) ? this.currentFocus : state; - }); - - icon.redraw(); + var tol = this.getTolerance(me); + var x = (point != null) ? point.x : me.getGraphX(); + var y = (point != null) ? point.y : me.getGraphY(); + var grid = new mxRectangle(x - tol, y - tol, 2 * tol, 2 * tol); + var mouse = new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol); + var state = this.graph.view.getState(this.getCellForEvent(me, point)); - mxEvent.redirectMouseEvents(icon.node, this.graph, getState); - this.currentFocusArea.add(icon.bounds); - this.focusIcons.push(icon); - this.focusPoints.push(cp); + // Keeps focus icons visible while over vertex bounds and no other cell under mouse or shift is pressed + if (!this.isKeepFocusEvent(me) && (this.currentFocusArea == null || this.currentFocus == null || + (state != null) || !this.graph.getModel().isVertex(this.currentFocus.cell) || + !mxUtils.intersects(this.currentFocusArea, mouse)) && (state != this.currentFocus)) { + this.currentFocusArea = null; + this.currentFocus = null; + this.setFocus(me, state, source); + } + + this.currentConstraint = null; + this.currentPoint = null; + var minDistSq = null; + + if (this.focusIcons != null && this.constraints != null && + (state == null || this.currentFocus == state)) { + var cx = mouse.getCenterX(); + var cy = mouse.getCenterY(); + + for (var i = 0; i < this.focusIcons.length; i++) { + var dx = cx - this.focusIcons[i].bounds.getCenterX(); + var dy = cy - this.focusIcons[i].bounds.getCenterY(); + var tmp = dx * dx + dy * dy; + + if ((this.intersects(this.focusIcons[i], mouse, source, existingEdge) || (point != null && + this.intersects(this.focusIcons[i], grid, source, existingEdge))) && + (minDistSq == null || tmp < minDistSq)) { + this.currentConstraint = this.constraints[i]; + this.currentPoint = this.focusPoints[i]; + minDistSq = tmp; + + var tmp = this.focusIcons[i].bounds.clone(); + tmp.grow(mxConstants.HIGHLIGHT_SIZE + 1); + tmp.width -= 1; + tmp.height -= 1; + + if (this.focusHighlight == null) { + var hl = this.createHighlightShape(); + hl.dialect = (this.graph.dialect == mxConstants.DIALECT_SVG) ? + mxConstants.DIALECT_SVG : mxConstants.DIALECT_VML; + hl.pointerEvents = false; + + hl.init(this.graph.getView().getOverlayPane()); + this.focusHighlight = hl; + + var getState = mxUtils.bind(this, () => { + return (this.currentFocus != null) ? this.currentFocus : state; + }); + + mxEvent.redirectMouseEvents(hl.node, this.graph, getState); + } + + this.focusHighlight.bounds = tmp; + this.focusHighlight.redraw(); + } + } + } + + if (this.currentConstraint == null) { + this.destroyFocusHighlight(); + } + } else { + this.currentConstraint = null; + this.currentFocus = null; + this.currentPoint = null; } - - this.currentFocusArea.grow(this.getTolerance(me)); - } - else - { - this.destroyIcons(); - this.destroyFocusHighlight(); - } -}; + }; -/** - * Function: createHighlightShape - * - * Create the shape used to paint the highlight. - * - * Returns true if the given icon intersects the given point. - */ -createHighlightShape = ()=> -{ - var hl = new mxRectangleShape(null, this.highlightColor, this.highlightColor, mxConstants.HIGHLIGHT_STROKEWIDTH); - hl.opacity = mxConstants.HIGHLIGHT_OPACITY; - - return hl; -}; + /** + * Function: redraw + * + * Transfers the focus to the given state as a source or target terminal. If + * the handler is not enabled then the outline is painted, but the constraints + * are ignored. + */ + redraw = () => { + if (this.currentFocus != null && this.constraints != null && this.focusIcons != null) { + var state = this.graph.view.getState(this.currentFocus.cell); + this.currentFocus = state; + this.currentFocusArea = new mxRectangle(state.x, state.y, state.width, state.height); -/** - * Function: intersects - * - * Returns true if the given icon intersects the given rectangle. - */ -intersects = (icon, mouse, source, existingEdge)=> -{ - return mxUtils.intersects(icon.bounds, mouse); -}; + for (var i = 0; i < this.constraints.length; i++) { + var cp = this.graph.getConnectionPoint(state, this.constraints[i]); + var img = this.getImageForConstraint(state, this.constraints[i], cp); -/** - * Function: destroy - * - * Destroy this handler. - */ -destroy = ()=> -{ - this.reset(); - - if (this.resetHandler != null) - { - this.graph.model.removeListener(this.resetHandler); - this.graph.view.removeListener(this.resetHandler); - this.graph.removeListener(this.resetHandler); - this.resetHandler = null; - } - - if (this.mouseleaveHandler != null && this.graph.container != null) - { - mxEvent.removeListener(this.graph.container, 'mouseleave', this.mouseleaveHandler); - this.mouseleaveHandler = null; - } -}; + var bounds = new mxRectangle(Math.round(cp.x - img.width / 2), + Math.round(cp.y - img.height / 2), img.width, img.height); + this.focusIcons[i].bounds = bounds; + this.focusIcons[i].redraw(); + this.currentFocusArea.add(this.focusIcons[i].bounds); + this.focusPoints[i] = cp; + } + } + }; + + /** + * Function: setFocus + * + * Transfers the focus to the given state as a source or target terminal. If + * the handler is not enabled then the outline is painted, but the constraints + * are ignored. + */ + setFocus = (me, state, source) => { + this.constraints = (state != null && !this.isStateIgnored(state, source) && + this.graph.isCellConnectable(state.cell)) ? ((this.isEnabled()) ? + (this.graph.getAllConnectionConstraints(state, source) || []) : []) : null; + + // Only uses cells which have constraints + if (this.constraints != null) { + this.currentFocus = state; + this.currentFocusArea = new mxRectangle(state.x, state.y, state.width, state.height); + + if (this.focusIcons != null) { + for (var i = 0; i < this.focusIcons.length; i++) { + this.focusIcons[i].destroy(); + } + + this.focusIcons = null; + this.focusPoints = null; + } + + this.focusPoints = []; + this.focusIcons = []; + + for (var i = 0; i < this.constraints.length; i++) { + var cp = this.graph.getConnectionPoint(state, this.constraints[i]); + var img = this.getImageForConstraint(state, this.constraints[i], cp); + + var src = img.src; + var bounds = new mxRectangle(Math.round(cp.x - img.width / 2), + Math.round(cp.y - img.height / 2), img.width, img.height); + var icon = new mxImageShape(bounds, src); + icon.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? + mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG; + icon.preserveImageAspect = false; + icon.init(this.graph.getView().getDecoratorPane()); + + // Move the icon behind all other overlays + if (icon.node.previousSibling != null) { + icon.node.parentNode.insertBefore(icon.node, icon.node.parentNode.firstChild); + } + + var getState = mxUtils.bind(this, () => { + return (this.currentFocus != null) ? this.currentFocus : state; + }); + + icon.redraw(); + + mxEvent.redirectMouseEvents(icon.node, this.graph, getState); + this.currentFocusArea.add(icon.bounds); + this.focusIcons.push(icon); + this.focusPoints.push(cp); + } + + this.currentFocusArea.grow(this.getTolerance(me)); + } else { + this.destroyIcons(); + this.destroyFocusHighlight(); + } + }; + + /** + * Function: createHighlightShape + * + * Create the shape used to paint the highlight. + * + * Returns true if the given icon intersects the given point. + */ + createHighlightShape = () => { + var hl = new mxRectangleShape(null, this.highlightColor, this.highlightColor, mxConstants.HIGHLIGHT_STROKEWIDTH); + hl.opacity = mxConstants.HIGHLIGHT_OPACITY; + + return hl; + }; + + /** + * Function: intersects + * + * Returns true if the given icon intersects the given rectangle. + */ + intersects = (icon, mouse, source, existingEdge) => { + return mxUtils.intersects(icon.bounds, mouse); + }; + + /** + * Function: destroy + * + * Destroy this handler. + */ + destroy = () => { + this.reset(); + + if (this.resetHandler != null) { + this.graph.model.removeListener(this.resetHandler); + this.graph.view.removeListener(this.resetHandler); + this.graph.removeListener(this.resetHandler); + this.resetHandler = null; + } + + if (this.mouseleaveHandler != null && this.graph.container != null) { + mxEvent.removeListener(this.graph.container, 'mouseleave', this.mouseleaveHandler); + this.mouseleaveHandler = null; + } + }; +} + +export default mxConstraintHandler; diff --git a/src/js/handler/mxEdgeHandler.js b/src/js/handler/mxEdgeHandler.js index 46cfe5e87..5ac093f6e 100644 --- a/src/js/handler/mxEdgeHandler.js +++ b/src/js/handler/mxEdgeHandler.js @@ -2,2527 +2,2185 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * Class: mxEdgeHandler - * - * Graph event handler that reconnects edges and modifies control points and - * the edge label location. Uses for finding and - * highlighting new source and target vertices. This handler is automatically - * created in for each selected edge. - * - * To enable adding/removing control points, the following code can be used: - * - * (code) - * addEnabled = true; - * removeEnabled = true; - * (end) - * - * Note: This experimental feature is not recommended for production use. - * - * Constructor: mxEdgeHandler - * - * Constructs an edge handler for the specified . - * - * Parameters: - * - * state - of the cell to be handled. - */ -function mxEdgeHandler(state) -{ - if (state != null && state.shape != null) - { - this.state = state; - this.init(); - - // Handles escape keystrokes - this.escapeHandler = mxUtils.bind(this, (sender, evt)=> - { - var dirty = this.index != null; - this.reset(); - - if (dirty) - { - this.graph.cellRenderer.redraw(this.state, false, state.view.isRendering()); - } - }); - - this.state.view.graph.addListener(mxEvent.ESCAPE, this.escapeHandler); - } -}; -/** - * Variable: graph - * - * Reference to the enclosing . - */ -graph = null; +class mxEdgeHandler { + /** + * Variable: graph + * + * Reference to the enclosing . + */ + graph = null; -/** - * Variable: state - * - * Reference to the being modified. - */ -state = null; + /** + * Variable: state + * + * Reference to the being modified. + */ + state = null; -/** - * Variable: marker - * - * Holds the which is used for highlighting terminals. - */ -marker = null; + /** + * Variable: marker + * + * Holds the which is used for highlighting terminals. + */ + marker = null; -/** - * Variable: constraintHandler - * - * Holds the used for drawing and highlighting - * constraints. - */ -constraintHandler = null; + /** + * Variable: constraintHandler + * + * Holds the used for drawing and highlighting + * constraints. + */ + constraintHandler = null; -/** - * Variable: error - * - * Holds the current validation error while a connection is being changed. - */ -error = null; + /** + * Variable: error + * + * Holds the current validation error while a connection is being changed. + */ + error = null; -/** - * Variable: shape - * - * Holds the that represents the preview edge. - */ -shape = null; + /** + * Variable: shape + * + * Holds the that represents the preview edge. + */ + shape = null; -/** - * Variable: bends - * - * Holds the that represent the points. - */ -bends = null; + /** + * Variable: bends + * + * Holds the that represent the points. + */ + bends = null; -/** - * Variable: labelShape - * - * Holds the that represents the label position. - */ -labelShape = null; + /** + * Variable: labelShape + * + * Holds the that represents the label position. + */ + labelShape = null; -/** - * Variable: cloneEnabled - * - * Specifies if cloning by control-drag is enabled. Default is true. - */ -cloneEnabled = true; + /** + * Variable: cloneEnabled + * + * Specifies if cloning by control-drag is enabled. Default is true. + */ + cloneEnabled = true; -/** - * Variable: addEnabled - * - * Specifies if adding bends by shift-click is enabled. Default is false. - * Note: This experimental feature is not recommended for production use. - */ -addEnabled = false; + /** + * Variable: addEnabled + * + * Specifies if adding bends by shift-click is enabled. Default is false. + * Note: This experimental feature is not recommended for production use. + */ + addEnabled = false; -/** - * Variable: removeEnabled - * - * Specifies if removing bends by shift-click is enabled. Default is false. - * Note: This experimental feature is not recommended for production use. - */ -removeEnabled = false; + /** + * Variable: removeEnabled + * + * Specifies if removing bends by shift-click is enabled. Default is false. + * Note: This experimental feature is not recommended for production use. + */ + removeEnabled = false; -/** - * Variable: dblClickRemoveEnabled - * - * Specifies if removing bends by double click is enabled. Default is false. - */ -dblClickRemoveEnabled = false; + /** + * Variable: dblClickRemoveEnabled + * + * Specifies if removing bends by double click is enabled. Default is false. + */ + dblClickRemoveEnabled = false; -/** - * Variable: mergeRemoveEnabled - * - * Specifies if removing bends by dropping them on other bends is enabled. - * Default is false. - */ -mergeRemoveEnabled = false; + /** + * Variable: mergeRemoveEnabled + * + * Specifies if removing bends by dropping them on other bends is enabled. + * Default is false. + */ + mergeRemoveEnabled = false; -/** - * Variable: straightRemoveEnabled - * - * Specifies if removing bends by creating straight segments should be enabled. - * If enabled, this can be overridden by holding down the alt key while moving. - * Default is false. - */ -straightRemoveEnabled = false; + /** + * Variable: straightRemoveEnabled + * + * Specifies if removing bends by creating straight segments should be enabled. + * If enabled, this can be overridden by holding down the alt key while moving. + * Default is false. + */ + straightRemoveEnabled = false; -/** - * Variable: virtualBendsEnabled - * - * Specifies if virtual bends should be added in the center of each - * segments. These bends can then be used to add new waypoints. - * Default is false. - */ -virtualBendsEnabled = false; + /** + * Variable: virtualBendsEnabled + * + * Specifies if virtual bends should be added in the center of each + * segments. These bends can then be used to add new waypoints. + * Default is false. + */ + virtualBendsEnabled = false; -/** - * Variable: virtualBendOpacity - * - * Opacity to be used for virtual bends (see ). - * Default is 20. - */ -virtualBendOpacity = 20; + /** + * Variable: virtualBendOpacity + * + * Opacity to be used for virtual bends (see ). + * Default is 20. + */ + virtualBendOpacity = 20; -/** - * Variable: parentHighlightEnabled - * - * Specifies if the parent should be highlighted if a child cell is selected. - * Default is false. - */ -parentHighlightEnabled = false; + /** + * Variable: parentHighlightEnabled + * + * Specifies if the parent should be highlighted if a child cell is selected. + * Default is false. + */ + parentHighlightEnabled = false; -/** - * Variable: preferHtml - * - * Specifies if bends should be added to the graph container. This is updated - * in based on whether the edge or one of its terminals has an HTML - * label in the container. - */ -preferHtml = false; + /** + * Variable: preferHtml + * + * Specifies if bends should be added to the graph container. This is updated + * in based on whether the edge or one of its terminals has an HTML + * label in the container. + */ + preferHtml = false; -/** - * Variable: allowHandleBoundsCheck - * - * Specifies if the bounds of handles should be used for hit-detection in IE - * Default is true. - */ -allowHandleBoundsCheck = true; + /** + * Variable: allowHandleBoundsCheck + * + * Specifies if the bounds of handles should be used for hit-detection in IE + * Default is true. + */ + allowHandleBoundsCheck = true; -/** - * Variable: snapToTerminals - * - * Specifies if waypoints should snap to the routing centers of terminals. - * Default is false. - */ -snapToTerminals = false; + /** + * Variable: snapToTerminals + * + * Specifies if waypoints should snap to the routing centers of terminals. + * Default is false. + */ + snapToTerminals = false; -/** - * Variable: handleImage - * - * Optional to be used as handles. Default is null. - */ -handleImage = null; + /** + * Variable: handleImage + * + * Optional to be used as handles. Default is null. + */ + handleImage = null; -/** - * Variable: tolerance - * - * Optional tolerance for hit-detection in . Default is 0. - */ -tolerance = 0; + /** + * Variable: tolerance + * + * Optional tolerance for hit-detection in . Default is 0. + */ + tolerance = 0; -/** - * 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: 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: manageLabelHandle - * - * Specifies if the label handle should be moved if it intersects with another - * handle. Uses for checking and moving. Default is false. - */ -manageLabelHandle = false; + /** + * Variable: manageLabelHandle + * + * Specifies if the label handle should be moved if it intersects with another + * handle. Uses for checking and moving. Default is false. + */ + manageLabelHandle = false; -/** - * Function: init - * - * Initializes the shapes required for this edge handler. - */ -init = ()=> -{ - this.graph = this.state.view.graph; - this.marker = this.createMarker(); - this.constraintHandler = new mxConstraintHandler(this.graph); - - // Clones the original points from the cell - // and makes sure at least one point exists - this.points = []; - - // Uses the absolute points of the state - // for the initial configuration and preview - this.abspoints = this.getSelectionPoints(this.state); - this.shape = this.createSelectionShape(this.abspoints); - this.shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? - mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG; - this.shape.init(this.graph.getView().getOverlayPane()); - this.shape.pointerEvents = false; - this.shape.setCursor(mxConstants.CURSOR_MOVABLE_EDGE); - mxEvent.redirectMouseEvents(this.shape.node, this.graph, this.state); + /** + * Class: mxEdgeHandler + * + * Graph event handler that reconnects edges and modifies control points and + * the edge label location. Uses for finding and + * highlighting new source and target vertices. This handler is automatically + * created in for each selected edge. + * + * To enable adding/removing control points, the following code can be used: + * + * (code) + * addEnabled = true; + * removeEnabled = true; + * (end) + * + * Note: This experimental feature is not recommended for production use. + * + * Constructor: mxEdgeHandler + * + * Constructs an edge handler for the specified . + * + * Parameters: + * + * state - of the cell to be handled. + */ + constructor(state) { + if (state != null && state.shape != null) { + this.state = state; + this.init(); - // Updates preferHtml - this.preferHtml = this.state.text != null && - this.state.text.node.parentNode == this.graph.container; - - if (!this.preferHtml) - { - // Checks source terminal - var sourceState = this.state.getVisibleTerminalState(true); - - if (sourceState != null) - { - this.preferHtml = sourceState.text != null && - sourceState.text.node.parentNode == this.graph.container; + // Handles escape keystrokes + this.escapeHandler = mxUtils.bind(this, (sender, evt) => { + var dirty = this.index != null; + this.reset(); + + if (dirty) { + this.graph.cellRenderer.redraw(this.state, false, state.view.isRendering()); + } + }); + + this.state.view.graph.addListener(mxEvent.ESCAPE, this.escapeHandler); } - - if (!this.preferHtml) - { - // Checks target terminal - var targetState = this.state.getVisibleTerminalState(false); - - if (targetState != null) - { - this.preferHtml = targetState.text != null && - targetState.text.node.parentNode == this.graph.container; - } - } - } - - // Creates bends for the non-routed absolute points - // or bends that don't correspond to points - if (this.graph.getSelectionCount() < maxCells || - maxCells <= 0) - { - this.bends = this.createBends(); - - if (this.isVirtualBendsEnabled()) - { - this.virtualBends = this.createVirtualBends(); - } - } - - // Adds a rectangular handle for the label position - this.label = new mxPoint(this.state.absoluteOffset.x, this.state.absoluteOffset.y); - this.labelShape = this.createLabelHandleShape(); - this.initBend(this.labelShape); - this.labelShape.setCursor(mxConstants.CURSOR_LABEL_HANDLE); - - this.customHandles = this.createCustomHandles(); - - this.updateParentHighlight(); - this.redraw(); -}; - - -/** - * Function: isParentHighlightVisible - * - * Returns true if the parent highlight should be visible. This implementation - * always returns true. - */ -isParentHighlightVisible = isParentHighlightVisible; - -/** - * Function: updateParentHighlight - * - * Updates the highlight of the parent if is true. - */ -updateParentHighlight = updateParentHighlight; - -/** - * Function: createCustomHandles - * - * Returns an array of custom handles. This implementation returns null. - */ -createCustomHandles = ()=> -{ - return null; -}; - -/** - * Function: isVirtualBendsEnabled - * - * Returns true if virtual bends should be added. This returns true if - * is true and the current style allows and - * renders custom waypoints. - */ -isVirtualBendsEnabled = (evt)=> -{ - return this.virtualBendsEnabled && (this.state.style[mxConstants.STYLE_EDGE] == null || - this.state.style[mxConstants.STYLE_EDGE] == mxConstants.NONE || - this.state.style[mxConstants.STYLE_NOEDGESTYLE] == 1) && - mxUtils.getValue(this.state.style, mxConstants.STYLE_SHAPE, null) != 'arrow'; -}; - -/** - * Function: isCellEnabled - * - * Returns true if the given cell allows new connections to be created. This implementation - * always returns true. - */ -isCellEnabled = (cell)=> -{ - return true; -}; - -/** - * Function: isAddPointEvent - * - * Returns true if the given event is a trigger to add a new point. This - * implementation returns true if shift is pressed. - */ -isAddPointEvent = (evt)=> -{ - return mxEvent.isShiftDown(evt); -}; - -/** - * Function: isRemovePointEvent - * - * Returns true if the given event is a trigger to remove a point. This - * implementation returns true if shift is pressed. - */ -isRemovePointEvent = (evt)=> -{ - return mxEvent.isShiftDown(evt); -}; - -/** - * Function: getSelectionPoints - * - * Returns the list of points that defines the selection stroke. - */ -getSelectionPoints = (state)=> -{ - return state.absolutePoints; -}; - -/** - * Function: createParentHighlightShape - * - * Creates the shape used to draw the selection border. - */ -createParentHighlightShape = (bounds)=> -{ - var shape = new mxRectangleShape(mxRectangle.fromRectangle(bounds), - null, this.getSelectionColor()); - shape.strokewidth = this.getSelectionStrokeWidth(); - shape.isDashed = this.isSelectionDashed(); - - return shape; -}; - -/** - * Function: createSelectionShape - * - * Creates the shape used to draw the selection border. - */ -createSelectionShape = (points)=> -{ - var shape = new this.state.shape.constructor(); - shape.outline = true; - shape.apply(this.state); - - shape.isDashed = this.isSelectionDashed(); - shape.stroke = this.getSelectionColor(); - shape.isShadow = false; - - return shape; -}; - -/** - * Function: getSelectionColor - * - * Returns . - */ -getSelectionColor = ()=> -{ - return mxConstants.EDGE_SELECTION_COLOR; -}; - -/** - * Function: getSelectionStrokeWidth - * - * Returns . - */ -getSelectionStrokeWidth = ()=> -{ - return mxConstants.EDGE_SELECTION_STROKEWIDTH; -}; - -/** - * Function: isSelectionDashed - * - * Returns . - */ -isSelectionDashed = ()=> -{ - return mxConstants.EDGE_SELECTION_DASHED; -}; - -/** - * 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: getCellAt - * - * Creates and returns the used in . - */ -getCellAt = (x, y)=> -{ - return (!this.outlineConnect) ? this.graph.getCellAt(x, y) : null; -}; - -/** - * Function: createMarker - * - * Creates and returns the used in . - */ -createMarker = ()=> -{ - var marker = new mxCellMarker(this.graph); - var self = this; // closure - - // Only returns edges if they are connectable and never returns - // the edge that is currently being modified - marker.getCell = (me)=> - { - var cell = getCell.apply(this, arguments); - - // Checks for cell at preview point (with grid) - if ((cell == self.state.cell || cell == null) && self.currentPoint != null) - { - cell = self.graph.getCellAt(self.currentPoint.x, self.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; - } - } - - var model = self.graph.getModel(); - - if ((this.graph.isSwimlane(cell) && self.currentPoint != null && - this.graph.hitsSwimlaneContent(cell, self.currentPoint.x, self.currentPoint.y)) || - (!self.isConnectableCell(cell)) || (cell == self.state.cell || - (cell != null && !self.graph.connectableEdges && model.isEdge(cell))) || - model.isAncestor(self.state.cell, cell)) - { - cell = null; - } - - if (!this.graph.isCellConnectable(cell)) - { - cell = null; - } - - return cell; }; - // Sets the highlight color according to validateConnection - marker.isValidState = (state)=> - { - var model = self.graph.getModel(); - var other = self.graph.view.getTerminalPort(state, - self.graph.view.getState(model.getTerminal(self.state.cell, - !self.isSource)), !self.isSource); - var otherCell = (other != null) ? other.cell : null; - var source = (self.isSource) ? state.cell : otherCell; - var target = (self.isSource) ? otherCell : state.cell; - - // Updates the error message of the handler - self.error = self.validateConnection(source, target); + /** + * Function: init + * + * Initializes the shapes required for this edge handler. + */ + init = () => { + this.graph = this.state.view.graph; + this.marker = this.createMarker(); + this.constraintHandler = new mxConstraintHandler(this.graph); - return self.error == null; + // Clones the original points from the cell + // and makes sure at least one point exists + this.points = []; + + // Uses the absolute points of the state + // for the initial configuration and preview + this.abspoints = this.getSelectionPoints(this.state); + this.shape = this.createSelectionShape(this.abspoints); + this.shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? + mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG; + this.shape.init(this.graph.getView().getOverlayPane()); + this.shape.pointerEvents = false; + this.shape.setCursor(mxConstants.CURSOR_MOVABLE_EDGE); + mxEvent.redirectMouseEvents(this.shape.node, this.graph, this.state); + + // Updates preferHtml + this.preferHtml = this.state.text != null && + this.state.text.node.parentNode == this.graph.container; + + if (!this.preferHtml) { + // Checks source terminal + var sourceState = this.state.getVisibleTerminalState(true); + + if (sourceState != null) { + this.preferHtml = sourceState.text != null && + sourceState.text.node.parentNode == this.graph.container; + } + + if (!this.preferHtml) { + // Checks target terminal + var targetState = this.state.getVisibleTerminalState(false); + + if (targetState != null) { + this.preferHtml = targetState.text != null && + targetState.text.node.parentNode == this.graph.container; + } + } + } + + // Creates bends for the non-routed absolute points + // or bends that don't correspond to points + if (this.graph.getSelectionCount() < maxCells || + maxCells <= 0) { + this.bends = this.createBends(); + + if (this.isVirtualBendsEnabled()) { + this.virtualBends = this.createVirtualBends(); + } + } + + // Adds a rectangular handle for the label position + this.label = new mxPoint(this.state.absoluteOffset.x, this.state.absoluteOffset.y); + this.labelShape = this.createLabelHandleShape(); + this.initBend(this.labelShape); + this.labelShape.setCursor(mxConstants.CURSOR_LABEL_HANDLE); + + this.customHandles = this.createCustomHandles(); + + this.updateParentHighlight(); + this.redraw(); }; - - return marker; -}; -/** - * 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)=> -{ - return this.graph.getEdgeValidationError(this.state.cell, source, target); -}; -/** - * Function: createBends - * - * Creates and returns the bends used for modifying the edge. This is - * typically an array of . - */ - createBends = ()=> - { - var cell = this.state.cell; - var bends = []; + /** + * Function: isParentHighlightVisible + * + * Returns true if the parent highlight should be visible. This implementation + * always returns true. + */ + isParentHighlightVisible = isParentHighlightVisible; - for (var i = 0; i < this.abspoints.length; i++) - { - if (this.isHandleVisible(i)) - { - var source = i == 0; - var target = i == this.abspoints.length - 1; - var terminal = source || target; + /** + * Function: updateParentHighlight + * + * Updates the highlight of the parent if is true. + */ + updateParentHighlight = updateParentHighlight; - if (terminal || this.graph.isCellBendable(cell)) - { - (mxUtils.bind(this, (index)=> - { - var bend = this.createHandleShape(index); - this.initBend(bend, mxUtils.bind(this, mxUtils.bind(this, ()=> - { - if (this.dblClickRemoveEnabled) - { - this.removePoint(this.state, index); - } - }))); - - if (this.isHandleEnabled(i)) - { - bend.setCursor((terminal) ? mxConstants.CURSOR_TERMINAL_HANDLE : mxConstants.CURSOR_BEND_HANDLE); - } - - bends.push(bend); - - if (!terminal) - { - this.points.push(new mxPoint(0,0)); - bend.node.style.visibility = 'hidden'; - } - }))(i); - } - } - } + /** + * Function: createCustomHandles + * + * Returns an array of custom handles. This implementation returns null. + */ + createCustomHandles = () => { + return null; + }; - return bends; -}; + /** + * Function: isVirtualBendsEnabled + * + * Returns true if virtual bends should be added. This returns true if + * is true and the current style allows and + * renders custom waypoints. + */ + isVirtualBendsEnabled = (evt) => { + return this.virtualBendsEnabled && (this.state.style[mxConstants.STYLE_EDGE] == null || + this.state.style[mxConstants.STYLE_EDGE] == mxConstants.NONE || + this.state.style[mxConstants.STYLE_NOEDGESTYLE] == 1) && + mxUtils.getValue(this.state.style, mxConstants.STYLE_SHAPE, null) != 'arrow'; + }; -/** - * Function: createVirtualBends - * - * Creates and returns the bends used for modifying the edge. This is - * typically an array of . - */ - createVirtualBends = ()=> - { - var cell = this.state.cell; - var last = this.abspoints[0]; - var bends = []; + /** + * Function: isCellEnabled + * + * Returns true if the given cell allows new connections to be created. This implementation + * always returns true. + */ + isCellEnabled = (cell) => { + return true; + }; - if (this.graph.isCellBendable(cell)) - { - for (var i = 1; i < this.abspoints.length; i++) - { - (mxUtils.bind(this, (bend)=> - { - this.initBend(bend); - bend.setCursor(mxConstants.CURSOR_VIRTUAL_BEND_HANDLE); - bends.push(bend); - }))(this.createHandleShape()); - } - } + /** + * Function: isAddPointEvent + * + * Returns true if the given event is a trigger to add a new point. This + * implementation returns true if shift is pressed. + */ + isAddPointEvent = (evt) => { + return mxEvent.isShiftDown(evt); + }; - return bends; -}; + /** + * Function: isRemovePointEvent + * + * Returns true if the given event is a trigger to remove a point. This + * implementation returns true if shift is pressed. + */ + isRemovePointEvent = (evt) => { + return mxEvent.isShiftDown(evt); + }; -/** - * Function: isHandleEnabled - * - * Creates the shape used to display the given bend. - */ -isHandleEnabled = (index)=> -{ - return true; -}; + /** + * Function: getSelectionPoints + * + * Returns the list of points that defines the selection stroke. + */ + getSelectionPoints = (state) => { + return state.absolutePoints; + }; -/** - * Function: isHandleVisible - * - * Returns true if the handle at the given index is visible. - */ -isHandleVisible = (index)=> -{ - var source = this.state.getVisibleTerminalState(true); - var target = this.state.getVisibleTerminalState(false); - var geo = this.graph.getCellGeometry(this.state.cell); - var edgeStyle = (geo != null) ? this.graph.view.getEdgeStyle(this.state, geo.points, source, target) : null; - - return edgeStyle != mxEdgeStyle.EntityRelation || index == 0 || index == this.abspoints.length - 1; -}; - -/** - * Function: createHandleShape - * - * Creates the shape used to display the given bend. Note that the index may be - * null for special cases, such as when called from - * . Only images and rectangles should be - * returned if support for HTML labels with not foreign objects is required. - * Index if null for virtual handles. - */ -createHandleShape = (index)=> -{ - if (this.handleImage != null) - { - var shape = new mxImageShape(new mxRectangle(0, 0, this.handleImage.width, this.handleImage.height), this.handleImage.src); - - // Allows HTML rendering of the images - shape.preserveImageAspect = false; + /** + * Function: createParentHighlightShape + * + * Creates the shape used to draw the selection border. + */ + createParentHighlightShape = (bounds) => { + var shape = new mxRectangleShape(mxRectangle.fromRectangle(bounds), + null, this.getSelectionColor()); + shape.strokewidth = this.getSelectionStrokeWidth(); + shape.isDashed = this.isSelectionDashed(); return shape; - } - else - { - var s = mxConstants.HANDLE_SIZE; - - if (this.preferHtml) - { - s -= 1; - } - - return new mxRectangleShape(new mxRectangle(0, 0, s, s), mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR); - } -}; + }; -/** - * Function: createLabelHandleShape - * - * Creates the shape used to display the the label handle. - */ -createLabelHandleShape = ()=> -{ - if (this.labelHandleImage != null) - { - var shape = new mxImageShape(new mxRectangle(0, 0, this.labelHandleImage.width, this.labelHandleImage.height), this.labelHandleImage.src); - - // Allows HTML rendering of the images - shape.preserveImageAspect = false; + /** + * Function: createSelectionShape + * + * Creates the shape used to draw the selection border. + */ + createSelectionShape = (points) => { + var shape = new this.state.shape.constructor(); + shape.outline = true; + shape.apply(this.state); + + shape.isDashed = this.isSelectionDashed(); + shape.stroke = this.getSelectionColor(); + shape.isShadow = false; return shape; - } - else - { - var s = mxConstants.LABEL_HANDLE_SIZE; - return new mxRectangleShape(new mxRectangle(0, 0, s, s), mxConstants.LABEL_HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR); - } -}; + }; -/** - * Function: initBend - * - * Helper method to initialize the given bend. - * - * Parameters: - * - * bend - that represents the bend to be initialized. - */ -initBend = (bend, dblClick)=> -{ - if (this.preferHtml) - { - bend.dialect = mxConstants.DIALECT_STRICTHTML; - bend.init(this.graph.container); - } - else - { - bend.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? - mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG; - bend.init(this.graph.getView().getOverlayPane()); - } + /** + * Function: getSelectionColor + * + * Returns . + */ + getSelectionColor = () => { + return mxConstants.EDGE_SELECTION_COLOR; + }; - mxEvent.redirectMouseEvents(bend.node, this.graph, this.state, - null, null, null, dblClick); - - if (mxClient.IS_TOUCH) - { - bend.node.setAttribute('pointer-events', 'none'); - } -}; + /** + * Function: getSelectionStrokeWidth + * + * Returns . + */ + getSelectionStrokeWidth = () => { + return mxConstants.EDGE_SELECTION_STROKEWIDTH; + }; -/** - * Function: getHandleForEvent - * - * Returns the index of the handle for the given event. - */ -getHandleForEvent = (me)=> -{ - var result = null; - - if (this.state != null) - { - // Connection highlight may consume events before they reach sizer handle - var tol = (!mxEvent.isMouseEvent(me.getEvent())) ? this.tolerance : 1; - var hit = (this.allowHandleBoundsCheck && tol > 0) ? - new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol) : null; - var minDistSq = null; - - function checkShape(shape) - { - if (shape != null && shape.node != null && shape.node.style.display != 'none' && - shape.node.style.visibility != 'hidden' && - (me.isSource(shape) || (hit != null && mxUtils.intersects(shape.bounds, hit)))) - { - var dx = me.getGraphX() - shape.bounds.getCenterX(); - var dy = me.getGraphY() - shape.bounds.getCenterY(); - var tmp = dx * dx + dy * dy; - - if (minDistSq == null || tmp <= minDistSq) - { - minDistSq = tmp; - - return true; + /** + * Function: isSelectionDashed + * + * Returns . + */ + isSelectionDashed = () => { + return mxConstants.EDGE_SELECTION_DASHED; + }; + + /** + * 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: getCellAt + * + * Creates and returns the used in . + */ + getCellAt = (x, y) => { + return (!this.outlineConnect) ? this.graph.getCellAt(x, y) : null; + }; + + /** + * Function: createMarker + * + * Creates and returns the used in . + */ + createMarker = () => { + var marker = new mxCellMarker(this.graph); + var self = this; // closure + + // Only returns edges if they are connectable and never returns + // the edge that is currently being modified + marker.getCell = (me) => { + var cell = getCell.apply(this, arguments); + + // Checks for cell at preview point (with grid) + if ((cell == self.state.cell || cell == null) && self.currentPoint != null) { + cell = self.graph.getCellAt(self.currentPoint.x, self.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; } } - - return false; - } - - if (this.customHandles != null && this.isCustomHandleEvent(me)) - { - // Inverse loop order to match display order - for (var i = this.customHandles.length - 1; i >= 0; i--) - { - if (checkShape(this.customHandles[i].shape)) - { - // LATER: Return reference to active shape - return mxEvent.CUSTOM_HANDLE - i; - } + + var model = self.graph.getModel(); + + if ((this.graph.isSwimlane(cell) && self.currentPoint != null && + this.graph.hitsSwimlaneContent(cell, self.currentPoint.x, self.currentPoint.y)) || + (!self.isConnectableCell(cell)) || (cell == self.state.cell || + (cell != null && !self.graph.connectableEdges && model.isEdge(cell))) || + model.isAncestor(self.state.cell, cell)) { + cell = null; } - } - - if (me.isSource(this.state.text) || checkShape(this.labelShape)) - { - result = mxEvent.LABEL_HANDLE; - } - - if (this.bends != null) - { - for (var i = 0; i < this.bends.length; i++) - { - if (checkShape(this.bends[i])) - { - result = i; - } + + if (!this.graph.isCellConnectable(cell)) { + cell = null; } - } - - if (this.virtualBends != null && this.isAddVirtualBendEvent(me)) - { - for (var i = 0; i < this.virtualBends.length; i++) - { - if (checkShape(this.virtualBends[i])) - { - result = mxEvent.VIRTUAL_HANDLE - i; - } - } - } - } - return result; -}; - -/** - * Function: isAddVirtualBendEvent - * - * Returns true if the given event allows virtual bends to be added. This - * implementation returns true. - */ -isAddVirtualBendEvent = (me)=> -{ - return true; -}; - -/** - * Function: isCustomHandleEvent - * - * Returns true if the given event allows custom handles to be changed. This - * implementation returns true. - */ -isCustomHandleEvent = (me)=> -{ - return true; -}; - -/** - * Function: mouseDown - * - * Handles the event by checking if a special element of the handler - * was clicked, in which case the index parameter is non-null. The - * indices may be one of or the number of the respective - * control point. The source and target points are used for reconnecting - * the edge. - */ -mouseDown = (sender, me)=> -{ - var handle = this.getHandleForEvent(me); - - if (this.bends != null && this.bends[handle] != null) - { - var b = this.bends[handle].bounds; - this.snapPoint = new mxPoint(b.getCenterX(), b.getCenterY()); - } - - if (this.addEnabled && handle == null && this.isAddPointEvent(me.getEvent())) - { - this.addPoint(this.state, me.getEvent()); - me.consume(); - } - else if (handle != null && !me.isConsumed() && this.graph.isEnabled()) - { - if (this.removeEnabled && this.isRemovePointEvent(me.getEvent())) - { - this.removePoint(this.state, handle); - } - else if (handle != mxEvent.LABEL_HANDLE || this.graph.isLabelMovable(me.getCell())) - { - if (handle <= mxEvent.VIRTUAL_HANDLE) - { - mxUtils.setOpacity(this.virtualBends[mxEvent.VIRTUAL_HANDLE - handle].node, 100); - } - - this.start(me.getX(), me.getY(), handle); - } - - me.consume(); - } -}; - -/** - * Function: start - * - * Starts the handling of the mouse gesture. - */ -start = (x, y, index)=> -{ - this.startX = x; - this.startY = y; - - this.isSource = (this.bends == null) ? false : index == 0; - this.isTarget = (this.bends == null) ? false : index == this.bends.length - 1; - this.isLabel = index == mxEvent.LABEL_HANDLE; - - if (this.isSource || this.isTarget) - { - var cell = this.state.cell; - var terminal = this.graph.model.getTerminal(cell, this.isSource); - - if ((terminal == null && this.graph.isTerminalPointMovable(cell, this.isSource)) || - (terminal != null && this.graph.isCellDisconnectable(cell, terminal, this.isSource))) - { - this.index = index; - } - } - else - { - this.index = index; - } - - // Hides other custom handles - if (this.index <= mxEvent.CUSTOM_HANDLE && this.index > mxEvent.VIRTUAL_HANDLE) - { - if (this.customHandles != null) - { - for (var i = 0; i < this.customHandles.length; i++) - { - if (i != mxEvent.CUSTOM_HANDLE - this.index) - { - this.customHandles[i].setVisible(false); - } - } - } - } -}; - -/** - * Function: clonePreviewState - * - * Returns a clone of the current preview state for the given point and terminal. - */ -clonePreviewState = (point, terminal)=> -{ - return this.state.clone(); -}; - -/** - * Function: getSnapToTerminalTolerance - * - * Returns the tolerance for the guides. Default value is - * gridSize * scale / 2. - */ -getSnapToTerminalTolerance = ()=> -{ - return this.graph.gridSize * this.graph.view.scale / 2; -}; - -/** - * Function: updateHint - * - * Hook for subclassers do show details while the handler is active. - */ -updateHint = (me, point)=> { }; - -/** - * Function: removeHint - * - * Hooks for subclassers to hide details when the handler gets inactive. - */ -removeHint = ()=> { }; - -/** - * Function: roundLength - * - * Hook for rounding the unscaled width or height. This uses Math.round. - */ -roundLength = (length)=> -{ - return Math.round(length); -}; - -/** - * Function: isSnapToTerminalsEvent - * - * Returns true if is true and if alt is not pressed. - */ -isSnapToTerminalsEvent = (me)=> -{ - return this.snapToTerminals && !mxEvent.isAltDown(me.getEvent()); -}; - -/** - * Function: getPointForEvent - * - * Returns the point for the given event. - */ -getPointForEvent = (me)=> -{ - var view = this.graph.getView(); - var scale = view.scale; - var point = new mxPoint(this.roundLength(me.getGraphX() / scale) * scale, - this.roundLength(me.getGraphY() / scale) * scale); - - var tt = this.getSnapToTerminalTolerance(); - var overrideX = false; - var overrideY = false; - - if (tt > 0 && this.isSnapToTerminalsEvent(me)) - { - function snapToPoint(pt) - { - if (pt != null) - { - var x = pt.x; - - if (Math.abs(point.x - x) < tt) - { - point.x = x; - overrideX = true; - } - - var y = pt.y; - - if (Math.abs(point.y - y) < tt) - { - point.y = y; - overrideY = true; - } - } - } - - // Temporary function - function snapToTerminal(terminal) - { - if (terminal != null) - { - snapToPoint.call(this, new mxPoint(view.getRoutingCenterX(terminal), - view.getRoutingCenterY(terminal))); - } + return cell; }; - snapToTerminal.call(this, this.state.getVisibleTerminalState(true)); - snapToTerminal.call(this, this.state.getVisibleTerminalState(false)); + // Sets the highlight color according to validateConnection + marker.isValidState = (state) => { + var model = self.graph.getModel(); + var other = self.graph.view.getTerminalPort(state, + self.graph.view.getState(model.getTerminal(self.state.cell, + !self.isSource)), !self.isSource); + var otherCell = (other != null) ? other.cell : null; + var source = (self.isSource) ? state.cell : otherCell; + var target = (self.isSource) ? otherCell : state.cell; - if (this.state.absolutePoints != null) - { - for (var i = 0; i < this.state.absolutePoints.length; i++) - { - snapToPoint.call(this, this.state.absolutePoints[i]); + // Updates the error message of the handler + self.error = self.validateConnection(source, target); + + return self.error == null; + }; + + return marker; + }; + + /** + * 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) => { + return this.graph.getEdgeValidationError(this.state.cell, source, target); + }; + + /** + * Function: createBends + * + * Creates and returns the bends used for modifying the edge. This is + * typically an array of . + */ + createBends = () => { + var cell = this.state.cell; + var bends = []; + + for (var i = 0; i < this.abspoints.length; i++) { + if (this.isHandleVisible(i)) { + var source = i == 0; + var target = i == this.abspoints.length - 1; + var terminal = source || target; + + if (terminal || this.graph.isCellBendable(cell)) { + (mxUtils.bind(this, (index) => { + var bend = this.createHandleShape(index); + this.initBend(bend, mxUtils.bind(this, mxUtils.bind(this, () => { + if (this.dblClickRemoveEnabled) { + this.removePoint(this.state, index); + } + }))); + + if (this.isHandleEnabled(i)) { + bend.setCursor((terminal) ? mxConstants.CURSOR_TERMINAL_HANDLE : mxConstants.CURSOR_BEND_HANDLE); + } + + bends.push(bend); + + if (!terminal) { + this.points.push(new mxPoint(0, 0)); + bend.node.style.visibility = 'hidden'; + } + }))(i); + } } } - } - if (this.graph.isGridEnabledEvent(me.getEvent())) - { - var tr = view.translate; - - if (!overrideX) - { - point.x = (this.graph.snap(point.x / scale - tr.x) + tr.x) * scale; - } - - if (!overrideY) - { - point.y = (this.graph.snap(point.y / scale - tr.y) + tr.y) * scale; - } - } - - return point; -}; + return bends; + }; -/** - * Function: getPreviewTerminalState - * - * Updates the given preview state taking into account the state of the constraint handler. - */ -getPreviewTerminalState = (me)=> -{ - this.constraintHandler.update(me, this.isSource, true, me.isSource(this.marker.highlight.shape) ? null : this.currentPoint); - - 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'; + /** + * Function: createVirtualBends + * + * Creates and returns the bends used for modifying the edge. This is + * typically an array of . + */ + createVirtualBends = () => { + var cell = this.state.cell; + var last = this.abspoints[0]; + var bends = []; + + if (this.graph.isCellBendable(cell)) { + for (var i = 1; i < this.abspoints.length; i++) { + (mxUtils.bind(this, (bend) => { + this.initBend(bend); + bend.setCursor(mxConstants.CURSOR_VIRTUAL_BEND_HANDLE); + bends.push(bend); + }))(this.createHandleShape()); + } + } + + return bends; + }; + + /** + * Function: isHandleEnabled + * + * Creates the shape used to display the given bend. + */ + isHandleEnabled = (index) => { + return true; + }; + + /** + * Function: isHandleVisible + * + * Returns true if the handle at the given index is visible. + */ + isHandleVisible = (index) => { + var source = this.state.getVisibleTerminalState(true); + var target = this.state.getVisibleTerminalState(false); + var geo = this.graph.getCellGeometry(this.state.cell); + var edgeStyle = (geo != null) ? this.graph.view.getEdgeStyle(this.state, geo.points, source, target) : null; + + return edgeStyle != mxEdgeStyle.EntityRelation || index == 0 || index == this.abspoints.length - 1; + }; + + /** + * Function: createHandleShape + * + * Creates the shape used to display the given bend. Note that the index may be + * null for special cases, such as when called from + * . Only images and rectangles should be + * returned if support for HTML labels with not foreign objects is required. + * Index if null for virtual handles. + */ + createHandleShape = (index) => { + if (this.handleImage != null) { + var shape = new mxImageShape(new mxRectangle(0, 0, this.handleImage.width, this.handleImage.height), this.handleImage.src); + + // Allows HTML rendering of the images + shape.preserveImageAspect = false; + + return shape; + } else { + var s = mxConstants.HANDLE_SIZE; + + if (this.preferHtml) { + s -= 1; + } + + return new mxRectangleShape(new mxRectangle(0, 0, s, s), mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR); + } + }; + + /** + * Function: createLabelHandleShape + * + * Creates the shape used to display the the label handle. + */ + createLabelHandleShape = () => { + if (this.labelHandleImage != null) { + var shape = new mxImageShape(new mxRectangle(0, 0, this.labelHandleImage.width, this.labelHandleImage.height), this.labelHandleImage.src); + + // Allows HTML rendering of the images + shape.preserveImageAspect = false; + + return shape; + } else { + var s = mxConstants.LABEL_HANDLE_SIZE; + return new mxRectangleShape(new mxRectangle(0, 0, s, s), mxConstants.LABEL_HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR); + } + }; + + /** + * Function: initBend + * + * Helper method to initialize the given bend. + * + * Parameters: + * + * bend - that represents the bend to be initialized. + */ + initBend = (bend, dblClick) => { + if (this.preferHtml) { + bend.dialect = mxConstants.DIALECT_STRICTHTML; + bend.init(this.graph.container); + } else { + bend.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? + mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG; + bend.init(this.graph.getView().getOverlayPane()); + } + + mxEvent.redirectMouseEvents(bend.node, this.graph, this.state, + null, null, null, dblClick); + + if (mxClient.IS_TOUCH) { + bend.node.setAttribute('pointer-events', 'none'); + } + }; + + /** + * Function: getHandleForEvent + * + * Returns the index of the handle for the given event. + */ + getHandleForEvent = (me) => { + var result = null; + + if (this.state != null) { + // Connection highlight may consume events before they reach sizer handle + var tol = (!mxEvent.isMouseEvent(me.getEvent())) ? this.tolerance : 1; + var hit = (this.allowHandleBoundsCheck && tol > 0) ? + new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol) : null; + var minDistSq = null; + + function checkShape(shape) { + if (shape != null && shape.node != null && shape.node.style.display != 'none' && + shape.node.style.visibility != 'hidden' && + (me.isSource(shape) || (hit != null && mxUtils.intersects(shape.bounds, hit)))) { + var dx = me.getGraphX() - shape.bounds.getCenterX(); + var dy = me.getGraphY() - shape.bounds.getCenterY(); + var tmp = dx * dx + dy * dy; + + if (minDistSq == null || tmp <= minDistSq) { + minDistSq = tmp; + + return true; + } + } + + return false; + } + + if (this.customHandles != null && this.isCustomHandleEvent(me)) { + // Inverse loop order to match display order + for (var i = this.customHandles.length - 1; i >= 0; i--) { + if (checkShape(this.customHandles[i].shape)) { + // LATER: Return reference to active shape + return mxEvent.CUSTOM_HANDLE - i; + } + } + } + + if (me.isSource(this.state.text) || checkShape(this.labelShape)) { + result = mxEvent.LABEL_HANDLE; + } + + if (this.bends != null) { + for (var i = 0; i < this.bends.length; i++) { + if (checkShape(this.bends[i])) { + result = i; + } + } + } + + if (this.virtualBends != null && this.isAddVirtualBendEvent(me)) { + for (var i = 0; i < this.virtualBends.length; i++) { + if (checkShape(this.virtualBends[i])) { + result = mxEvent.VIRTUAL_HANDLE - i; + } + } + } + } + + return result; + }; + + /** + * Function: isAddVirtualBendEvent + * + * Returns true if the given event allows virtual bends to be added. This + * implementation returns true. + */ + isAddVirtualBendEvent = (me) => { + return true; + }; + + /** + * Function: isCustomHandleEvent + * + * Returns true if the given event allows custom handles to be changed. This + * implementation returns true. + */ + isCustomHandleEvent = (me) => { + return true; + }; + + /** + * Function: mouseDown + * + * Handles the event by checking if a special element of the handler + * was clicked, in which case the index parameter is non-null. The + * indices may be one of or the number of the respective + * control point. The source and target points are used for reconnecting + * the edge. + */ + mouseDown = (sender, me) => { + var handle = this.getHandleForEvent(me); + + if (this.bends != null && this.bends[handle] != null) { + var b = this.bends[handle].bounds; + this.snapPoint = new mxPoint(b.getCenterX(), b.getCenterY()); + } + + if (this.addEnabled && handle == null && this.isAddPointEvent(me.getEvent())) { + this.addPoint(this.state, me.getEvent()); + me.consume(); + } else if (handle != null && !me.isConsumed() && this.graph.isEnabled()) { + if (this.removeEnabled && this.isRemovePointEvent(me.getEvent())) { + this.removePoint(this.state, handle); + } else if (handle != mxEvent.LABEL_HANDLE || this.graph.isLabelMovable(me.getCell())) { + if (handle <= mxEvent.VIRTUAL_HANDLE) { + mxUtils.setOpacity(this.virtualBends[mxEvent.VIRTUAL_HANDLE - handle].node, 100); + } + + this.start(me.getX(), me.getY(), handle); + } + + me.consume(); + } + }; + + /** + * Function: start + * + * Starts the handling of the mouse gesture. + */ + start = (x, y, index) => { + this.startX = x; + this.startY = y; + + this.isSource = (this.bends == null) ? false : index == 0; + this.isTarget = (this.bends == null) ? false : index == this.bends.length - 1; + this.isLabel = index == mxEvent.LABEL_HANDLE; + + if (this.isSource || this.isTarget) { + var cell = this.state.cell; + var terminal = this.graph.model.getTerminal(cell, this.isSource); + + if ((terminal == null && this.graph.isTerminalPointMovable(cell, this.isSource)) || + (terminal != null && this.graph.isCellDisconnectable(cell, terminal, this.isSource))) { + this.index = index; + } + } else { + this.index = index; + } + + // Hides other custom handles + if (this.index <= mxEvent.CUSTOM_HANDLE && this.index > mxEvent.VIRTUAL_HANDLE) { + if (this.customHandles != null) { + for (var i = 0; i < this.customHandles.length; i++) { + if (i != mxEvent.CUSTOM_HANDLE - this.index) { + this.customHandles[i].setVisible(false); + } + } + } + } + }; + + /** + * Function: clonePreviewState + * + * Returns a clone of the current preview state for the given point and terminal. + */ + clonePreviewState = (point, terminal) => { + return this.state.clone(); + }; + + /** + * Function: getSnapToTerminalTolerance + * + * Returns the tolerance for the guides. Default value is + * gridSize * scale / 2. + */ + getSnapToTerminalTolerance = () => { + return this.graph.gridSize * this.graph.view.scale / 2; + }; + + /** + * Function: updateHint + * + * Hook for subclassers do show details while the handler is active. + */ + updateHint = (me, point) => { + }; + + /** + * Function: removeHint + * + * Hooks for subclassers to hide details when the handler gets inactive. + */ + removeHint = () => { + }; + + /** + * Function: roundLength + * + * Hook for rounding the unscaled width or height. This uses Math.round. + */ + roundLength = (length) => { + return Math.round(length); + }; + + /** + * Function: isSnapToTerminalsEvent + * + * Returns true if is true and if alt is not pressed. + */ + isSnapToTerminalsEvent = (me) => { + return this.snapToTerminals && !mxEvent.isAltDown(me.getEvent()); + }; + + /** + * Function: getPointForEvent + * + * Returns the point for the given event. + */ + getPointForEvent = (me) => { + var view = this.graph.getView(); + var scale = view.scale; + var point = new mxPoint(this.roundLength(me.getGraphX() / scale) * scale, + this.roundLength(me.getGraphY() / scale) * scale); + + var tt = this.getSnapToTerminalTolerance(); + var overrideX = false; + var overrideY = false; + + if (tt > 0 && this.isSnapToTerminalsEvent(me)) { + function snapToPoint(pt) { + if (pt != null) { + var x = pt.x; + + if (Math.abs(point.x - x) < tt) { + point.x = x; + overrideX = true; + } + + var y = pt.y; + + if (Math.abs(point.y - y) < tt) { + point.y = y; + overrideY = true; + } + } + } + + // Temporary function + function snapToTerminal(terminal) { + if (terminal != null) { + snapToPoint.call(this, new mxPoint(view.getRoutingCenterX(terminal), + view.getRoutingCenterY(terminal))); + } + }; + + snapToTerminal.call(this, this.state.getVisibleTerminalState(true)); + snapToTerminal.call(this, this.state.getVisibleTerminalState(false)); + + if (this.state.absolutePoints != null) { + for (var i = 0; i < this.state.absolutePoints.length; i++) { + snapToPoint.call(this, this.state.absolutePoints[i]); + } + } + } + + if (this.graph.isGridEnabledEvent(me.getEvent())) { + var tr = view.translate; + + if (!overrideX) { + point.x = (this.graph.snap(point.x / scale - tr.x) + tr.x) * scale; + } + + if (!overrideY) { + point.y = (this.graph.snap(point.y / scale - tr.y) + tr.y) * scale; + } + } + + return point; + }; + + /** + * Function: getPreviewTerminalState + * + * Updates the given preview state taking into account the state of the constraint handler. + */ + getPreviewTerminalState = (me) => { + this.constraintHandler.update(me, this.isSource, true, me.isSource(this.marker.highlight.shape) ? null : this.currentPoint); + + 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'); + } + + var model = this.graph.getModel(); + var other = this.graph.view.getTerminalPort(this.state, + this.graph.view.getState(model.getTerminal(this.state.cell, + !this.isSource)), !this.isSource); + var otherCell = (other != null) ? other.cell : null; + var source = (this.isSource) ? this.constraintHandler.currentFocus.cell : otherCell; + var target = (this.isSource) ? otherCell : this.constraintHandler.currentFocus.cell; + + // Updates the error message of the handler + this.error = this.validateConnection(source, target); + var result = null; + + if (this.error == null) { + result = this.constraintHandler.currentFocus; + } + + if (this.error != null || (result != null && + !this.isCellEnabled(result.cell))) { + this.constraintHandler.reset(); + } + + return result; + } else if (!this.graph.isIgnoreTerminalEvent(me.getEvent())) { + this.marker.process(me); + var state = this.marker.getValidState(); + + if (state != null && !this.isCellEnabled(state.cell)) { + this.constraintHandler.reset(); + this.marker.reset(); + } + + return this.marker.getValidState(); + } else { + this.marker.reset(); + + return null; + } + }; + + /** + * Function: getPreviewPoints + * + * Updates the given preview state taking into account the state of the constraint handler. + * + * Parameters: + * + * pt - that contains the current pointer position. + * me - Optional that contains the current event. + */ + getPreviewPoints = (pt, me) => { + var geometry = this.graph.getCellGeometry(this.state.cell); + var points = (geometry.points != null) ? geometry.points.slice() : null; + var point = new mxPoint(pt.x, pt.y); + var result = null; + + if (!this.isSource && !this.isTarget) { + this.convertPoint(point, false); + + if (points == null) { + points = [point]; + } else { + // Adds point from virtual bend + if (this.index <= mxEvent.VIRTUAL_HANDLE) { + points.splice(mxEvent.VIRTUAL_HANDLE - this.index, 0, point); + } + + // Removes point if dragged on terminal point + if (!this.isSource && !this.isTarget) { + for (var i = 0; i < this.bends.length; i++) { + if (i != this.index) { + var bend = this.bends[i]; + + if (bend != null && mxUtils.contains(bend.bounds, pt.x, pt.y)) { + if (this.index <= mxEvent.VIRTUAL_HANDLE) { + points.splice(mxEvent.VIRTUAL_HANDLE - this.index, 1); + } else { + points.splice(this.index - 1, 1); + } + + result = points; + } + } + } + + // Removes point if user tries to straighten a segment + if (result == null && this.straightRemoveEnabled && (me == null || !mxEvent.isAltDown(me.getEvent()))) { + var tol = this.graph.tolerance * this.graph.tolerance; + var abs = this.state.absolutePoints.slice(); + abs[this.index] = pt; + + // Handes special case where removing waypoint affects tolerance (flickering) + var src = this.state.getVisibleTerminalState(true); + + if (src != null) { + var c = this.graph.getConnectionConstraint(this.state, src, true); + + // Checks if point is not fixed + if (c == null || this.graph.getConnectionPoint(src, c) == null) { + abs[0] = new mxPoint(src.view.getRoutingCenterX(src), src.view.getRoutingCenterY(src)); + } + } + + var trg = this.state.getVisibleTerminalState(false); + + if (trg != null) { + var c = this.graph.getConnectionConstraint(this.state, trg, false); + + // Checks if point is not fixed + if (c == null || this.graph.getConnectionPoint(trg, c) == null) { + abs[abs.length - 1] = new mxPoint(trg.view.getRoutingCenterX(trg), trg.view.getRoutingCenterY(trg)); + } + } + + function checkRemove(idx, tmp) { + if (idx > 0 && idx < abs.length - 1 && + mxUtils.ptSegDistSq(abs[idx - 1].x, abs[idx - 1].y, + abs[idx + 1].x, abs[idx + 1].y, tmp.x, tmp.y) < tol) { + points.splice(idx - 1, 1); + result = points; + } + }; + + // LATER: Check if other points can be removed if a segment is made straight + checkRemove(this.index, pt); + } + } + + // Updates existing point + if (result == null && this.index > mxEvent.VIRTUAL_HANDLE) { + points[this.index - 1] = point; + } + } + } else if (this.graph.resetEdgesOnConnect) { + points = null; + } + + return (result != null) ? result : points; + }; + + /** + * 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: updatePreviewState + * + * Updates the given preview state taking into account the state of the constraint handler. + */ + updatePreviewState = (edge, point, terminalState, me, outline) => { + // Computes the points for the edge style and terminals + var sourceState = (this.isSource) ? terminalState : this.state.getVisibleTerminalState(true); + var targetState = (this.isTarget) ? terminalState : this.state.getVisibleTerminalState(false); + + var sourceConstraint = this.graph.getConnectionConstraint(edge, sourceState, true); + var targetConstraint = this.graph.getConnectionConstraint(edge, targetState, false); + + var constraint = this.constraintHandler.currentConstraint; + + if (constraint == null && outline) { + if (terminalState != null) { + // 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()); + } + + constraint = this.graph.getOutlineConstraint(point, terminalState, me); + this.constraintHandler.setFocus(me, terminalState, this.isSource); + this.constraintHandler.currentConstraint = constraint; + this.constraintHandler.currentPoint = point; + } else { + constraint = new mxConnectionConstraint(); + } + } + + if (this.outlineConnect && 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 = (outline) ? mxConstants.OUTLINE_HIGHLIGHT_COLOR : 'transparent'; + this.marker.highlight.shape.strokewidth = mxConstants.OUTLINE_HIGHLIGHT_STROKEWIDTH / s / s; + this.marker.highlight.repaint(); + } else if (this.marker.hasValidState()) { + this.marker.highlight.shape.stroke = (this.graph.isCellConnectable(me.getCell()) && + this.marker.getValidState() != me.getState()) ? + 'transparent' : mxConstants.DEFAULT_VALID_COLOR; + this.marker.highlight.shape.strokewidth = mxConstants.HIGHLIGHT_STROKEWIDTH / s / s; this.marker.highlight.repaint(); } } - else - { - this.marker.markCell(this.constraintHandler.currentFocus.cell, 'transparent'); + + if (this.isSource) { + sourceConstraint = constraint; + } else if (this.isTarget) { + targetConstraint = constraint; } - - var model = this.graph.getModel(); - var other = this.graph.view.getTerminalPort(this.state, - this.graph.view.getState(model.getTerminal(this.state.cell, - !this.isSource)), !this.isSource); - var otherCell = (other != null) ? other.cell : null; - var source = (this.isSource) ? this.constraintHandler.currentFocus.cell : otherCell; - var target = (this.isSource) ? otherCell : this.constraintHandler.currentFocus.cell; - - // Updates the error message of the handler - this.error = this.validateConnection(source, target); - var result = null; - - if (this.error == null) - { - result = this.constraintHandler.currentFocus; + + if (this.isSource || this.isTarget) { + if (constraint != null && constraint.point != null) { + edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_X : mxConstants.STYLE_ENTRY_X] = constraint.point.x; + edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_Y : mxConstants.STYLE_ENTRY_Y] = constraint.point.y; + } else { + delete edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_X : mxConstants.STYLE_ENTRY_X]; + delete edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_Y : mxConstants.STYLE_ENTRY_Y]; + } } - - if (this.error != null || (result != null && - !this.isCellEnabled(result.cell))) - { - this.constraintHandler.reset(); + + edge.setVisibleTerminalState(sourceState, true); + edge.setVisibleTerminalState(targetState, false); + + if (!this.isSource || sourceState != null) { + edge.view.updateFixedTerminalPoint(edge, sourceState, true, sourceConstraint); } - - return result; - } - else if (!this.graph.isIgnoreTerminalEvent(me.getEvent())) - { - this.marker.process(me); - var state = this.marker.getValidState(); - - if (state != null && !this.isCellEnabled(state.cell)) - { - this.constraintHandler.reset(); + + if (!this.isTarget || targetState != null) { + edge.view.updateFixedTerminalPoint(edge, targetState, false, targetConstraint); + } + + if ((this.isSource || this.isTarget) && terminalState == null) { + edge.setAbsoluteTerminalPoint(point, this.isSource); + + if (this.marker.getMarkedState() == null) { + this.error = (this.graph.allowDanglingEdges) ? null : ''; + } + } + + edge.view.updatePoints(edge, this.points, sourceState, targetState); + edge.view.updateFloatingTerminalPoints(edge, sourceState, targetState); + }; + + /** + * Function: mouseMove + * + * Handles the event by updating the preview. + */ + mouseMove = (sender, me) => { + if (this.index != null && this.marker != null) { + this.currentPoint = this.getPointForEvent(me); + this.error = null; + + // Uses the current point from the constraint handler if available + if (!this.graph.isIgnoreTerminalEvent(me.getEvent()) && mxEvent.isShiftDown(me.getEvent()) && this.snapPoint != null) { + if (Math.abs(this.snapPoint.x - this.currentPoint.x) < Math.abs(this.snapPoint.y - this.currentPoint.y)) { + this.currentPoint.x = this.snapPoint.x; + } else { + this.currentPoint.y = this.snapPoint.y; + } + } + + if (this.index <= mxEvent.CUSTOM_HANDLE && this.index > mxEvent.VIRTUAL_HANDLE) { + if (this.customHandles != null) { + this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].processEvent(me); + this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].positionChanged(); + + if (this.shape != null && this.shape.node != null) { + this.shape.node.style.display = 'none'; + } + } + } else if (this.isLabel) { + this.label.x = this.currentPoint.x; + this.label.y = this.currentPoint.y; + } else { + this.points = this.getPreviewPoints(this.currentPoint, me); + var terminalState = (this.isSource || this.isTarget) ? this.getPreviewTerminalState(me) : null; + + if (this.constraintHandler.currentConstraint != null && + this.constraintHandler.currentFocus != null && + this.constraintHandler.currentPoint != null) { + this.currentPoint = this.constraintHandler.currentPoint.clone(); + } else if (this.outlineConnect) { + // Need to check outline before cloning terminal state + var outline = (this.isSource || this.isTarget) ? this.isOutlineConnectEvent(me) : false + + if (outline) { + terminalState = this.marker.highlight.state; + } else if (terminalState != null && terminalState != me.getState() && + this.graph.isCellConnectable(me.getCell()) && + this.marker.highlight.shape != null) { + this.marker.highlight.shape.stroke = 'transparent'; + this.marker.highlight.repaint(); + terminalState = null; + } + } + + if (terminalState != null && !this.isCellEnabled(terminalState.cell)) { + terminalState = null; + this.marker.reset(); + } + + var clone = this.clonePreviewState(this.currentPoint, (terminalState != null) ? terminalState.cell : null); + this.updatePreviewState(clone, this.currentPoint, terminalState, me, outline); + + // Sets the color of the preview to valid or invalid, updates the + // points of the preview and redraws + var color = (this.error == null) ? this.marker.validColor : this.marker.invalidColor; + this.setPreviewColor(color); + this.abspoints = clone.absolutePoints; + this.active = true; + this.updateHint(me, this.currentPoint); + } + + // This should go before calling isOutlineConnectEvent above. As a workaround + // we add an offset of gridSize to the hint to avoid problem with hit detection + // in highlight.isHighlightAt (which uses comonentFromPoint) + this.drawPreview(); + mxEvent.consume(me.getEvent()); + me.consume(); + } + }; + + /** + * Function: mouseUp + * + * Handles the event to applying the previewed changes on the edge by + * using , or . + */ + mouseUp = (sender, me) => { + // Workaround for wrong event source in Webkit + if (this.index != null && this.marker != null) { + if (this.shape != null && this.shape.node != null) { + this.shape.node.style.display = ''; + } + + var edge = this.state.cell; + var index = this.index; + this.index = null; + + // Ignores event if mouse has not been moved + if (me.getX() != this.startX || me.getY() != this.startY) { + var clone = !this.graph.isIgnoreTerminalEvent(me.getEvent()) && this.graph.isCloneEvent(me.getEvent()) && + this.cloneEnabled && this.graph.isCellsCloneable(); + + // Displays the reason for not carriying out the change + // if there is an error message with non-zero length + if (this.error != null) { + if (this.error.length > 0) { + this.graph.validationAlert(this.error); + } + } else if (index <= mxEvent.CUSTOM_HANDLE && index > mxEvent.VIRTUAL_HANDLE) { + if (this.customHandles != null) { + var model = this.graph.getModel(); + + model.beginUpdate(); + try { + this.customHandles[mxEvent.CUSTOM_HANDLE - index].execute(me); + + if (this.shape != null && this.shape.node != null) { + this.shape.apply(this.state); + this.shape.redraw(); + } + } finally { + model.endUpdate(); + } + } + } else if (this.isLabel) { + this.moveLabel(this.state, this.label.x, this.label.y); + } else if (this.isSource || this.isTarget) { + var terminal = null; + + if (this.constraintHandler.currentConstraint != null && + this.constraintHandler.currentFocus != null) { + terminal = this.constraintHandler.currentFocus.cell; + } + + if (terminal == null && this.marker.hasValidState() && this.marker.highlight != null && + this.marker.highlight.shape != null && + this.marker.highlight.shape.stroke != 'transparent' && + this.marker.highlight.shape.stroke != 'white') { + terminal = this.marker.validState.cell; + } + + if (terminal != null) { + var model = this.graph.getModel(); + var parent = model.getParent(edge); + + model.beginUpdate(); + try { + // Clones and adds the cell + if (clone) { + var geo = model.getGeometry(edge); + var clone = this.graph.cloneCell(edge); + model.add(parent, clone, model.getChildCount(parent)); + + if (geo != null) { + geo = geo.clone(); + model.setGeometry(clone, geo); + } + + var other = model.getTerminal(edge, !this.isSource); + this.graph.connectCell(clone, other, !this.isSource); + + edge = clone; + } + + edge = this.connect(edge, terminal, this.isSource, clone, me); + } finally { + model.endUpdate(); + } + } else if (this.graph.isAllowDanglingEdges()) { + var pt = this.abspoints[(this.isSource) ? 0 : this.abspoints.length - 1]; + pt.x = this.roundLength(pt.x / this.graph.view.scale - this.graph.view.translate.x); + pt.y = this.roundLength(pt.y / this.graph.view.scale - this.graph.view.translate.y); + + var pstate = this.graph.getView().getState( + this.graph.getModel().getParent(edge)); + + if (pstate != null) { + pt.x -= pstate.origin.x; + pt.y -= pstate.origin.y; + } + + pt.x -= this.graph.panDx / this.graph.view.scale; + pt.y -= this.graph.panDy / this.graph.view.scale; + + // Destroys and recreates this handler + edge = this.changeTerminalPoint(edge, pt, this.isSource, clone); + } + } else if (this.active) { + edge = this.changePoints(edge, this.points, clone); + } else { + this.graph.getView().invalidate(this.state.cell); + this.graph.getView().validate(this.state.cell); + } + } else if (this.graph.isToggleEvent(me.getEvent())) { + this.graph.selectCellForEvent(this.state.cell, me.getEvent()); + } + + // Resets the preview color the state of the handler if this + // handler has not been recreated + if (this.marker != null) { + this.reset(); + + // Updates the selection if the edge has been cloned + if (edge != this.state.cell) { + this.graph.setSelectionCell(edge); + } + } + + me.consume(); + } + }; + + /** + * Function: reset + * + * Resets the state of this handler. + */ + reset = () => { + if (this.active) { + this.refresh(); + } + + this.error = null; + this.index = null; + this.label = null; + this.points = null; + this.snapPoint = null; + this.isLabel = false; + this.isSource = false; + this.isTarget = false; + this.active = false; + + if (this.livePreview && this.sizers != null) { + for (var i = 0; i < this.sizers.length; i++) { + if (this.sizers[i] != null) { + this.sizers[i].node.style.display = ''; + } + } + } + + if (this.marker != null) { this.marker.reset(); } - - return this.marker.getValidState(); - } - else - { - this.marker.reset(); - - return null; - } -}; -/** - * Function: getPreviewPoints - * - * Updates the given preview state taking into account the state of the constraint handler. - * - * Parameters: - * - * pt - that contains the current pointer position. - * me - Optional that contains the current event. - */ -getPreviewPoints = (pt, me)=> -{ - var geometry = this.graph.getCellGeometry(this.state.cell); - var points = (geometry.points != null) ? geometry.points.slice() : null; - var point = new mxPoint(pt.x, pt.y); - var result = null; - - if (!this.isSource && !this.isTarget) - { - this.convertPoint(point, false); - - if (points == null) - { - points = [point]; - } - else - { - // Adds point from virtual bend - if (this.index <= mxEvent.VIRTUAL_HANDLE) - { - points.splice(mxEvent.VIRTUAL_HANDLE - this.index, 0, point); - } - - // Removes point if dragged on terminal point - if (!this.isSource && !this.isTarget) - { - for (var i = 0; i < this.bends.length; i++) - { - if (i != this.index) - { - var bend = this.bends[i]; - - if (bend != null && mxUtils.contains(bend.bounds, pt.x, pt.y)) - { - if (this.index <= mxEvent.VIRTUAL_HANDLE) - { - points.splice(mxEvent.VIRTUAL_HANDLE - this.index, 1); - } - else - { - points.splice(this.index - 1, 1); - } - - result = points; - } - } - } - - // Removes point if user tries to straighten a segment - if (result == null && this.straightRemoveEnabled && (me == null || !mxEvent.isAltDown(me.getEvent()))) - { - var tol = this.graph.tolerance * this.graph.tolerance; - var abs = this.state.absolutePoints.slice(); - abs[this.index] = pt; - - // Handes special case where removing waypoint affects tolerance (flickering) - var src = this.state.getVisibleTerminalState(true); - - if (src != null) - { - var c = this.graph.getConnectionConstraint(this.state, src, true); - - // Checks if point is not fixed - if (c == null || this.graph.getConnectionPoint(src, c) == null) - { - abs[0] = new mxPoint(src.view.getRoutingCenterX(src), src.view.getRoutingCenterY(src)); - } - } - - var trg = this.state.getVisibleTerminalState(false); - - if (trg != null) - { - var c = this.graph.getConnectionConstraint(this.state, trg, false); - - // Checks if point is not fixed - if (c == null || this.graph.getConnectionPoint(trg, c) == null) - { - abs[abs.length - 1] = new mxPoint(trg.view.getRoutingCenterX(trg), trg.view.getRoutingCenterY(trg)); - } - } - - function checkRemove(idx, tmp) - { - if (idx > 0 && idx < abs.length - 1 && - mxUtils.ptSegDistSq(abs[idx - 1].x, abs[idx - 1].y, - abs[idx + 1].x, abs[idx + 1].y, tmp.x, tmp.y) < tol) - { - points.splice(idx - 1, 1); - result = points; - } - }; - - // LATER: Check if other points can be removed if a segment is made straight - checkRemove(this.index, pt); - } - } - - // Updates existing point - if (result == null && this.index > mxEvent.VIRTUAL_HANDLE) - { - points[this.index - 1] = point; - } - } - } - else if (this.graph.resetEdgesOnConnect) - { - points = null; - } - - return (result != null) ? result : points; -}; - -/** - * 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: updatePreviewState - * - * Updates the given preview state taking into account the state of the constraint handler. - */ -updatePreviewState = (edge, point, terminalState, me, outline)=> -{ - // Computes the points for the edge style and terminals - var sourceState = (this.isSource) ? terminalState : this.state.getVisibleTerminalState(true); - var targetState = (this.isTarget) ? terminalState : this.state.getVisibleTerminalState(false); - - var sourceConstraint = this.graph.getConnectionConstraint(edge, sourceState, true); - var targetConstraint = this.graph.getConnectionConstraint(edge, targetState, false); - - var constraint = this.constraintHandler.currentConstraint; - - if (constraint == null && outline) - { - if (terminalState != null) - { - // 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()); - } - - constraint = this.graph.getOutlineConstraint(point, terminalState, me); - this.constraintHandler.setFocus(me, terminalState, this.isSource); - this.constraintHandler.currentConstraint = constraint; - this.constraintHandler.currentPoint = point; - } - else - { - constraint = new mxConnectionConstraint(); - } - } - - if (this.outlineConnect && 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 = (outline) ? mxConstants.OUTLINE_HIGHLIGHT_COLOR : 'transparent'; - this.marker.highlight.shape.strokewidth = mxConstants.OUTLINE_HIGHLIGHT_STROKEWIDTH / s / s; - this.marker.highlight.repaint(); - } - else if (this.marker.hasValidState()) - { - this.marker.highlight.shape.stroke = (this.graph.isCellConnectable(me.getCell()) && - this.marker.getValidState() != me.getState()) ? - 'transparent' : mxConstants.DEFAULT_VALID_COLOR; - this.marker.highlight.shape.strokewidth = mxConstants.HIGHLIGHT_STROKEWIDTH / s / s; - this.marker.highlight.repaint(); - } - } - - if (this.isSource) - { - sourceConstraint = constraint; - } - else if (this.isTarget) - { - targetConstraint = constraint; - } - - if (this.isSource || this.isTarget) - { - if (constraint != null && constraint.point != null) - { - edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_X : mxConstants.STYLE_ENTRY_X] = constraint.point.x; - edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_Y : mxConstants.STYLE_ENTRY_Y] = constraint.point.y; - } - else - { - delete edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_X : mxConstants.STYLE_ENTRY_X]; - delete edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_Y : mxConstants.STYLE_ENTRY_Y]; - } - } - - edge.setVisibleTerminalState(sourceState, true); - edge.setVisibleTerminalState(targetState, false); - - if (!this.isSource || sourceState != null) - { - edge.view.updateFixedTerminalPoint(edge, sourceState, true, sourceConstraint); - } - - if (!this.isTarget || targetState != null) - { - edge.view.updateFixedTerminalPoint(edge, targetState, false, targetConstraint); - } - - if ((this.isSource || this.isTarget) && terminalState == null) - { - edge.setAbsoluteTerminalPoint(point, this.isSource); - - if (this.marker.getMarkedState() == null) - { - this.error = (this.graph.allowDanglingEdges) ? null : ''; - } - } - - edge.view.updatePoints(edge, this.points, sourceState, targetState); - edge.view.updateFloatingTerminalPoints(edge, sourceState, targetState); -}; - -/** - * Function: mouseMove - * - * Handles the event by updating the preview. - */ -mouseMove = (sender, me)=> -{ - if (this.index != null && this.marker != null) - { - this.currentPoint = this.getPointForEvent(me); - this.error = null; - - // Uses the current point from the constraint handler if available - if (!this.graph.isIgnoreTerminalEvent(me.getEvent()) && mxEvent.isShiftDown(me.getEvent()) && this.snapPoint != null) - { - if (Math.abs(this.snapPoint.x - this.currentPoint.x) < Math.abs(this.snapPoint.y - this.currentPoint.y)) - { - this.currentPoint.x = this.snapPoint.x; - } - else - { - this.currentPoint.y = this.snapPoint.y; - } - } - - if (this.index <= mxEvent.CUSTOM_HANDLE && this.index > mxEvent.VIRTUAL_HANDLE) - { - if (this.customHandles != null) - { - this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].processEvent(me); - this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].positionChanged(); - - if (this.shape != null && this.shape.node != null) - { - this.shape.node.style.display = 'none'; - } - } - } - else if (this.isLabel) - { - this.label.x = this.currentPoint.x; - this.label.y = this.currentPoint.y; - } - else - { - this.points = this.getPreviewPoints(this.currentPoint, me); - var terminalState = (this.isSource || this.isTarget) ? this.getPreviewTerminalState(me) : null; - - if (this.constraintHandler.currentConstraint != null && - this.constraintHandler.currentFocus != null && - this.constraintHandler.currentPoint != null) - { - this.currentPoint = this.constraintHandler.currentPoint.clone(); - } - else if (this.outlineConnect) - { - // Need to check outline before cloning terminal state - var outline = (this.isSource || this.isTarget) ? this.isOutlineConnectEvent(me) : false - - if (outline) - { - terminalState = this.marker.highlight.state; - } - else if (terminalState != null && terminalState != me.getState() && - this.graph.isCellConnectable(me.getCell()) && - this.marker.highlight.shape != null) - { - this.marker.highlight.shape.stroke = 'transparent'; - this.marker.highlight.repaint(); - terminalState = null; - } - } - - if (terminalState != null && !this.isCellEnabled(terminalState.cell)) - { - terminalState = null; - this.marker.reset(); - } - - var clone = this.clonePreviewState(this.currentPoint, (terminalState != null) ? terminalState.cell : null); - this.updatePreviewState(clone, this.currentPoint, terminalState, me, outline); - - // Sets the color of the preview to valid or invalid, updates the - // points of the preview and redraws - var color = (this.error == null) ? this.marker.validColor : this.marker.invalidColor; - this.setPreviewColor(color); - this.abspoints = clone.absolutePoints; - this.active = true; - this.updateHint(me, this.currentPoint); + if (this.constraintHandler != null) { + this.constraintHandler.reset(); } - // This should go before calling isOutlineConnectEvent above. As a workaround - // we add an offset of gridSize to the hint to avoid problem with hit detection - // in highlight.isHighlightAt (which uses comonentFromPoint) - this.drawPreview(); - mxEvent.consume(me.getEvent()); - me.consume(); - } -}; - -/** - * Function: mouseUp - * - * Handles the event to applying the previewed changes on the edge by - * using , or . - */ -mouseUp = (sender, me)=> -{ - // Workaround for wrong event source in Webkit - if (this.index != null && this.marker != null) - { - if (this.shape != null && this.shape.node != null) - { - this.shape.node.style.display = ''; - } - - var edge = this.state.cell; - var index = this.index; - this.index = null; - - // Ignores event if mouse has not been moved - if (me.getX() != this.startX || me.getY() != this.startY) - { - var clone = !this.graph.isIgnoreTerminalEvent(me.getEvent()) && this.graph.isCloneEvent(me.getEvent()) && - this.cloneEnabled && this.graph.isCellsCloneable(); - - // Displays the reason for not carriying out the change - // if there is an error message with non-zero length - if (this.error != null) - { - if (this.error.length > 0) - { - this.graph.validationAlert(this.error); - } - } - else if (index <= mxEvent.CUSTOM_HANDLE && index > mxEvent.VIRTUAL_HANDLE) - { - if (this.customHandles != null) - { - var model = this.graph.getModel(); - - model.beginUpdate(); - try - { - this.customHandles[mxEvent.CUSTOM_HANDLE - index].execute(me); - - if (this.shape != null && this.shape.node != null) - { - this.shape.apply(this.state); - this.shape.redraw(); - } - } - finally - { - model.endUpdate(); - } - } - } - else if (this.isLabel) - { - this.moveLabel(this.state, this.label.x, this.label.y); - } - else if (this.isSource || this.isTarget) - { - var terminal = null; - - if (this.constraintHandler.currentConstraint != null && - this.constraintHandler.currentFocus != null) - { - terminal = this.constraintHandler.currentFocus.cell; - } - - if (terminal == null && this.marker.hasValidState() && this.marker.highlight != null && - this.marker.highlight.shape != null && - this.marker.highlight.shape.stroke != 'transparent' && - this.marker.highlight.shape.stroke != 'white') - { - terminal = this.marker.validState.cell; - } - - if (terminal != null) - { - var model = this.graph.getModel(); - var parent = model.getParent(edge); - - model.beginUpdate(); - try - { - // Clones and adds the cell - if (clone) - { - var geo = model.getGeometry(edge); - var clone = this.graph.cloneCell(edge); - model.add(parent, clone, model.getChildCount(parent)); - - if (geo != null) - { - geo = geo.clone(); - model.setGeometry(clone, geo); - } - - var other = model.getTerminal(edge, !this.isSource); - this.graph.connectCell(clone, other, !this.isSource); - - edge = clone; - } - - edge = this.connect(edge, terminal, this.isSource, clone, me); - } - finally - { - model.endUpdate(); - } - } - else if (this.graph.isAllowDanglingEdges()) - { - var pt = this.abspoints[(this.isSource) ? 0 : this.abspoints.length - 1]; - pt.x = this.roundLength(pt.x / this.graph.view.scale - this.graph.view.translate.x); - pt.y = this.roundLength(pt.y / this.graph.view.scale - this.graph.view.translate.y); - - var pstate = this.graph.getView().getState( - this.graph.getModel().getParent(edge)); - - if (pstate != null) - { - pt.x -= pstate.origin.x; - pt.y -= pstate.origin.y; - } - - pt.x -= this.graph.panDx / this.graph.view.scale; - pt.y -= this.graph.panDy / this.graph.view.scale; - - // Destroys and recreates this handler - edge = this.changeTerminalPoint(edge, pt, this.isSource, clone); - } - } - else if (this.active) - { - edge = this.changePoints(edge, this.points, clone); - } - else - { - this.graph.getView().invalidate(this.state.cell); - this.graph.getView().validate(this.state.cell); - } - } - else if (this.graph.isToggleEvent(me.getEvent())) - { - this.graph.selectCellForEvent(this.state.cell, me.getEvent()); - } - - // Resets the preview color the state of the handler if this - // handler has not been recreated - if (this.marker != null) - { - this.reset(); - - // Updates the selection if the edge has been cloned - if (edge != this.state.cell) - { - this.graph.setSelectionCell(edge); + if (this.customHandles != null) { + for (var i = 0; i < this.customHandles.length; i++) { + this.customHandles[i].reset(); } } - me.consume(); - } -}; - -/** - * Function: reset - * - * Resets the state of this handler. - */ -reset = ()=> -{ - if (this.active) - { - this.refresh(); - } - - this.error = null; - this.index = null; - this.label = null; - this.points = null; - this.snapPoint = null; - this.isLabel = false; - this.isSource = false; - this.isTarget = false; - this.active = false; - - if (this.livePreview && this.sizers != null) - { - for (var i = 0; i < this.sizers.length; i++) - { - if (this.sizers[i] != null) - { - this.sizers[i].node.style.display = ''; - } - } - } - - if (this.marker != null) - { - this.marker.reset(); - } - - if (this.constraintHandler != null) - { - this.constraintHandler.reset(); - } - - if (this.customHandles != null) - { - for (var i = 0; i < this.customHandles.length; i++) - { - this.customHandles[i].reset(); - } - } - - this.setPreviewColor(mxConstants.EDGE_SELECTION_COLOR); - this.removeHint(); - this.redraw(); -}; - -/** - * Function: setPreviewColor - * - * Sets the color of the preview to the given value. - */ -setPreviewColor = (color)=> -{ - if (this.shape != null) - { - this.shape.stroke = color; - } -}; - - -/** - * Function: convertPoint - * - * Converts the given point in-place from screen to unscaled, untranslated - * graph coordinates and applies the grid. Returns the given, modified - * point instance. - * - * Parameters: - * - * point - to be converted. - * gridEnabled - Boolean that specifies if the grid should be applied. - */ -convertPoint = (point, gridEnabled)=> -{ - var scale = this.graph.getView().getScale(); - var tr = this.graph.getView().getTranslate(); - - if (gridEnabled) - { - point.x = this.graph.snap(point.x); - point.y = this.graph.snap(point.y); - } - - point.x = Math.round(point.x / scale - tr.x); - point.y = Math.round(point.y / scale - tr.y); - - var pstate = this.graph.getView().getState( - this.graph.getModel().getParent(this.state.cell)); - - if (pstate != null) - { - point.x -= pstate.origin.x; - point.y -= pstate.origin.y; - } - - return point; -}; - -/** - * Function: moveLabel - * - * Changes the coordinates for the label of the given edge. - * - * Parameters: - * - * edge - that represents the edge. - * x - Integer that specifies the x-coordinate of the new location. - * y - Integer that specifies the y-coordinate of the new location. - */ -moveLabel = (edgeState, x, y)=> -{ - var model = this.graph.getModel(); - var geometry = model.getGeometry(edgeState.cell); - - if (geometry != null) - { - var scale = this.graph.getView().scale; - geometry = geometry.clone(); - - if (geometry.relative) - { - // Resets the relative location stored inside the geometry - var pt = this.graph.getView().getRelativePoint(edgeState, x, y); - geometry.x = Math.round(pt.x * 10000) / 10000; - geometry.y = Math.round(pt.y); - - // Resets the offset inside the geometry to find the offset - // from the resulting point - geometry.offset = new mxPoint(0, 0); - var pt = this.graph.view.getPoint(edgeState, geometry); - geometry.offset = new mxPoint(Math.round((x - pt.x) / scale), Math.round((y - pt.y) / scale)); - } - else - { - var points = edgeState.absolutePoints; - var p0 = points[0]; - var pe = points[points.length - 1]; - - if (p0 != null && pe != null) - { - var cx = p0.x + (pe.x - p0.x) / 2; - var cy = p0.y + (pe.y - p0.y) / 2; - - geometry.offset = new mxPoint(Math.round((x - cx) / scale), Math.round((y - cy) / scale)); - geometry.x = 0; - geometry.y = 0; - } - } - - model.setGeometry(edgeState.cell, geometry); - } -}; - -/** - * Function: connect - * - * Changes the terminal or terminal point of the given edge in the graph - * model. - * - * Parameters: - * - * edge - that represents the edge to be reconnected. - * terminal - that represents the new terminal. - * isSource - Boolean indicating if the new terminal is the source or - * target terminal. - * isClone - Boolean indicating if the new connection should be a clone of - * the old edge. - * me - that contains the mouse up event. - */ -connect = (edge, terminal, isSource, isClone, me)=> -{ - var model = this.graph.getModel(); - var parent = model.getParent(edge); - - model.beginUpdate(); - try - { - var constraint = this.constraintHandler.currentConstraint; - - if (constraint == null) - { - constraint = new mxConnectionConstraint(); - } - - this.graph.connectCell(edge, terminal, isSource, constraint); - } - finally - { - model.endUpdate(); - } - - return edge; -}; - -/** - * Function: changeTerminalPoint - * - * Changes the terminal point of the given edge. - */ -changeTerminalPoint = (edge, point, isSource, clone)=> -{ - var model = this.graph.getModel(); - - model.beginUpdate(); - try - { - if (clone) - { - var parent = model.getParent(edge); - var terminal = model.getTerminal(edge, !isSource); - edge = this.graph.cloneCell(edge); - model.add(parent, edge, model.getChildCount(parent)); - model.setTerminal(edge, terminal, !isSource); - } - - var geo = model.getGeometry(edge); - - if (geo != null) - { - geo = geo.clone(); - geo.setTerminalPoint(point, isSource); - model.setGeometry(edge, geo); - this.graph.connectCell(edge, null, isSource, new mxConnectionConstraint()); - } - } - finally - { - model.endUpdate(); - } - - return edge; -}; - -/** - * Function: changePoints - * - * Changes the control points of the given edge in the graph model. - */ -changePoints = (edge, points, clone)=> -{ - var model = this.graph.getModel(); - model.beginUpdate(); - try - { - if (clone) - { - var parent = model.getParent(edge); - var source = model.getTerminal(edge, true); - var target = model.getTerminal(edge, false); - edge = this.graph.cloneCell(edge); - model.add(parent, edge, model.getChildCount(parent)); - model.setTerminal(edge, source, true); - model.setTerminal(edge, target, false); - } - - var geo = model.getGeometry(edge); - - if (geo != null) - { - geo = geo.clone(); - geo.points = points; - - model.setGeometry(edge, geo); - } - } - finally - { - model.endUpdate(); - } - - return edge; -}; - -/** - * Function: addPoint - * - * Adds a control point for the given state and event. - */ -addPoint = (state, evt)=> -{ - var pt = mxUtils.convertPoint(this.graph.container, mxEvent.getClientX(evt), - mxEvent.getClientY(evt)); - var gridEnabled = this.graph.isGridEnabledEvent(evt); - this.convertPoint(pt, gridEnabled); - this.addPointAt(state, pt.x, pt.y); - mxEvent.consume(evt); -}; - -/** - * Function: addPointAt - * - * Adds a control point at the given point. - */ -addPointAt = (state, x, y)=> -{ - var geo = this.graph.getCellGeometry(state.cell); - var pt = new mxPoint(x, y); - - if (geo != null) - { - geo = geo.clone(); - var t = this.graph.view.translate; - var s = this.graph.view.scale; - var offset = new mxPoint(t.x * s, t.y * s); - - var parent = this.graph.model.getParent(this.state.cell); - - if (this.graph.model.isVertex(parent)) - { - var pState = this.graph.view.getState(parent); - offset = new mxPoint(pState.x, pState.y); - } - - var index = mxUtils.findNearestSegment(state, pt.x * s + offset.x, pt.y * s + offset.y); - - if (geo.points == null) - { - geo.points = [pt]; - } - else - { - geo.points.splice(index, 0, pt); - } - - this.graph.getModel().setGeometry(state.cell, geo); - this.refresh(); + this.setPreviewColor(mxConstants.EDGE_SELECTION_COLOR); + this.removeHint(); this.redraw(); - } -}; + }; -/** - * Function: removePoint - * - * Removes the control point at the given index from the given state. - */ -removePoint = (state, index)=> -{ - if (index > 0 && index < this.abspoints.length - 1) - { - var geo = this.graph.getCellGeometry(this.state.cell); - - if (geo != null && geo.points != null) - { + /** + * Function: setPreviewColor + * + * Sets the color of the preview to the given value. + */ + setPreviewColor = (color) => { + if (this.shape != null) { + this.shape.stroke = color; + } + }; + + + /** + * Function: convertPoint + * + * Converts the given point in-place from screen to unscaled, untranslated + * graph coordinates and applies the grid. Returns the given, modified + * point instance. + * + * Parameters: + * + * point - to be converted. + * gridEnabled - Boolean that specifies if the grid should be applied. + */ + convertPoint = (point, gridEnabled) => { + var scale = this.graph.getView().getScale(); + var tr = this.graph.getView().getTranslate(); + + if (gridEnabled) { + point.x = this.graph.snap(point.x); + point.y = this.graph.snap(point.y); + } + + point.x = Math.round(point.x / scale - tr.x); + point.y = Math.round(point.y / scale - tr.y); + + var pstate = this.graph.getView().getState( + this.graph.getModel().getParent(this.state.cell)); + + if (pstate != null) { + point.x -= pstate.origin.x; + point.y -= pstate.origin.y; + } + + return point; + }; + + /** + * Function: moveLabel + * + * Changes the coordinates for the label of the given edge. + * + * Parameters: + * + * edge - that represents the edge. + * x - Integer that specifies the x-coordinate of the new location. + * y - Integer that specifies the y-coordinate of the new location. + */ + moveLabel = (edgeState, x, y) => { + var model = this.graph.getModel(); + var geometry = model.getGeometry(edgeState.cell); + + if (geometry != null) { + var scale = this.graph.getView().scale; + geometry = geometry.clone(); + + if (geometry.relative) { + // Resets the relative location stored inside the geometry + var pt = this.graph.getView().getRelativePoint(edgeState, x, y); + geometry.x = Math.round(pt.x * 10000) / 10000; + geometry.y = Math.round(pt.y); + + // Resets the offset inside the geometry to find the offset + // from the resulting point + geometry.offset = new mxPoint(0, 0); + var pt = this.graph.view.getPoint(edgeState, geometry); + geometry.offset = new mxPoint(Math.round((x - pt.x) / scale), Math.round((y - pt.y) / scale)); + } else { + var points = edgeState.absolutePoints; + var p0 = points[0]; + var pe = points[points.length - 1]; + + if (p0 != null && pe != null) { + var cx = p0.x + (pe.x - p0.x) / 2; + var cy = p0.y + (pe.y - p0.y) / 2; + + geometry.offset = new mxPoint(Math.round((x - cx) / scale), Math.round((y - cy) / scale)); + geometry.x = 0; + geometry.y = 0; + } + } + + model.setGeometry(edgeState.cell, geometry); + } + }; + + /** + * Function: connect + * + * Changes the terminal or terminal point of the given edge in the graph + * model. + * + * Parameters: + * + * edge - that represents the edge to be reconnected. + * terminal - that represents the new terminal. + * isSource - Boolean indicating if the new terminal is the source or + * target terminal. + * isClone - Boolean indicating if the new connection should be a clone of + * the old edge. + * me - that contains the mouse up event. + */ + connect = (edge, terminal, isSource, isClone, me) => { + var model = this.graph.getModel(); + var parent = model.getParent(edge); + + model.beginUpdate(); + try { + var constraint = this.constraintHandler.currentConstraint; + + if (constraint == null) { + constraint = new mxConnectionConstraint(); + } + + this.graph.connectCell(edge, terminal, isSource, constraint); + } finally { + model.endUpdate(); + } + + return edge; + }; + + /** + * Function: changeTerminalPoint + * + * Changes the terminal point of the given edge. + */ + changeTerminalPoint = (edge, point, isSource, clone) => { + var model = this.graph.getModel(); + + model.beginUpdate(); + try { + if (clone) { + var parent = model.getParent(edge); + var terminal = model.getTerminal(edge, !isSource); + edge = this.graph.cloneCell(edge); + model.add(parent, edge, model.getChildCount(parent)); + model.setTerminal(edge, terminal, !isSource); + } + + var geo = model.getGeometry(edge); + + if (geo != null) { + geo = geo.clone(); + geo.setTerminalPoint(point, isSource); + model.setGeometry(edge, geo); + this.graph.connectCell(edge, null, isSource, new mxConnectionConstraint()); + } + } finally { + model.endUpdate(); + } + + return edge; + }; + + /** + * Function: changePoints + * + * Changes the control points of the given edge in the graph model. + */ + changePoints = (edge, points, clone) => { + var model = this.graph.getModel(); + model.beginUpdate(); + try { + if (clone) { + var parent = model.getParent(edge); + var source = model.getTerminal(edge, true); + var target = model.getTerminal(edge, false); + edge = this.graph.cloneCell(edge); + model.add(parent, edge, model.getChildCount(parent)); + model.setTerminal(edge, source, true); + model.setTerminal(edge, target, false); + } + + var geo = model.getGeometry(edge); + + if (geo != null) { + geo = geo.clone(); + geo.points = points; + + model.setGeometry(edge, geo); + } + } finally { + model.endUpdate(); + } + + return edge; + }; + + /** + * Function: addPoint + * + * Adds a control point for the given state and event. + */ + addPoint = (state, evt) => { + var pt = mxUtils.convertPoint(this.graph.container, mxEvent.getClientX(evt), + mxEvent.getClientY(evt)); + var gridEnabled = this.graph.isGridEnabledEvent(evt); + this.convertPoint(pt, gridEnabled); + this.addPointAt(state, pt.x, pt.y); + mxEvent.consume(evt); + }; + + /** + * Function: addPointAt + * + * Adds a control point at the given point. + */ + addPointAt = (state, x, y) => { + var geo = this.graph.getCellGeometry(state.cell); + var pt = new mxPoint(x, y); + + if (geo != null) { geo = geo.clone(); - geo.points.splice(index - 1, 1); + var t = this.graph.view.translate; + var s = this.graph.view.scale; + var offset = new mxPoint(t.x * s, t.y * s); + + var parent = this.graph.model.getParent(this.state.cell); + + if (this.graph.model.isVertex(parent)) { + var pState = this.graph.view.getState(parent); + offset = new mxPoint(pState.x, pState.y); + } + + var index = mxUtils.findNearestSegment(state, pt.x * s + offset.x, pt.y * s + offset.y); + + if (geo.points == null) { + geo.points = [pt]; + } else { + geo.points.splice(index, 0, pt); + } + this.graph.getModel().setGeometry(state.cell, geo); this.refresh(); this.redraw(); } - } -}; + }; -/** - * Function: getHandleFillColor - * - * Returns the fillcolor for the handle at the given index. - */ -getHandleFillColor = (index)=> -{ - var isSource = index == 0; - var cell = this.state.cell; - var terminal = this.graph.getModel().getTerminal(cell, isSource); - var color = mxConstants.HANDLE_FILLCOLOR; - - if ((terminal != null && !this.graph.isCellDisconnectable(cell, terminal, isSource)) || - (terminal == null && !this.graph.isTerminalPointMovable(cell, isSource))) - { - color = mxConstants.LOCKED_HANDLE_FILLCOLOR; - } - else if (terminal != null && this.graph.isCellDisconnectable(cell, terminal, isSource)) - { - color = mxConstants.CONNECT_HANDLE_FILLCOLOR; - } - - return color; -}; + /** + * Function: removePoint + * + * Removes the control point at the given index from the given state. + */ + removePoint = (state, index) => { + if (index > 0 && index < this.abspoints.length - 1) { + var geo = this.graph.getCellGeometry(this.state.cell); -/** - * Function: redraw - * - * Redraws the preview, and the bends- and label control points. - */ -redraw = (ignoreHandles)=> -{ - if (this.state != null) - { - this.abspoints = this.state.absolutePoints.slice(); - var g = this.graph.getModel().getGeometry(this.state.cell); - - if (g != null) - { - var pts = g.points; - - if (this.bends != null && this.bends.length > 0) - { - if (pts != null) - { - if (this.points == null) - { - this.points = []; - } - - for (var i = 1; i < this.bends.length - 1; i++) - { - if (this.bends[i] != null && this.abspoints[i] != null) - { - this.points[i - 1] = pts[i - 1]; + if (geo != null && geo.points != null) { + geo = geo.clone(); + geo.points.splice(index - 1, 1); + this.graph.getModel().setGeometry(state.cell, geo); + this.refresh(); + this.redraw(); + } + } + }; + + /** + * Function: getHandleFillColor + * + * Returns the fillcolor for the handle at the given index. + */ + getHandleFillColor = (index) => { + var isSource = index == 0; + var cell = this.state.cell; + var terminal = this.graph.getModel().getTerminal(cell, isSource); + var color = mxConstants.HANDLE_FILLCOLOR; + + if ((terminal != null && !this.graph.isCellDisconnectable(cell, terminal, isSource)) || + (terminal == null && !this.graph.isTerminalPointMovable(cell, isSource))) { + color = mxConstants.LOCKED_HANDLE_FILLCOLOR; + } else if (terminal != null && this.graph.isCellDisconnectable(cell, terminal, isSource)) { + color = mxConstants.CONNECT_HANDLE_FILLCOLOR; + } + + return color; + }; + + /** + * Function: redraw + * + * Redraws the preview, and the bends- and label control points. + */ + redraw = (ignoreHandles) => { + if (this.state != null) { + this.abspoints = this.state.absolutePoints.slice(); + var g = this.graph.getModel().getGeometry(this.state.cell); + + if (g != null) { + var pts = g.points; + + if (this.bends != null && this.bends.length > 0) { + if (pts != null) { + if (this.points == null) { + this.points = []; + } + + for (var i = 1; i < this.bends.length - 1; i++) { + if (this.bends[i] != null && this.abspoints[i] != null) { + this.points[i - 1] = pts[i - 1]; + } } } } } - } - - this.drawPreview(); - - if (!ignoreHandles) - { - this.redrawHandles(); - } - } -}; -/** - * Function: redrawHandles - * - * Redraws the handles. - */ -redrawHandles = ()=> -{ - var cell = this.state.cell; + this.drawPreview(); - // Updates the handle for the label position - var b = this.labelShape.bounds; - this.label = new mxPoint(this.state.absoluteOffset.x, this.state.absoluteOffset.y); - this.labelShape.bounds = new mxRectangle(Math.round(this.label.x - b.width / 2), - Math.round(this.label.y - b.height / 2), b.width, b.height); - - // Shows or hides the label handle depending on the label - var lab = this.graph.getLabel(cell); - this.labelShape.visible = (lab != null && lab.length > 0 && this.graph.isLabelMovable(cell)); - - if (this.bends != null && this.bends.length > 0) - { - var n = this.abspoints.length - 1; - - var p0 = this.abspoints[0]; - var x0 = p0.x; - var y0 = p0.y; - - b = this.bends[0].bounds; - this.bends[0].bounds = new mxRectangle(Math.floor(x0 - b.width / 2), - Math.floor(y0 - b.height / 2), b.width, b.height); - this.bends[0].fill = this.getHandleFillColor(0); - this.bends[0].redraw(); - - if (this.manageLabelHandle) - { - this.checkLabelHandle(this.bends[0].bounds); - } - - var pe = this.abspoints[n]; - var xn = pe.x; - var yn = pe.y; - - var bn = this.bends.length - 1; - b = this.bends[bn].bounds; - this.bends[bn].bounds = new mxRectangle(Math.floor(xn - b.width / 2), - Math.floor(yn - b.height / 2), b.width, b.height); - this.bends[bn].fill = this.getHandleFillColor(bn); - this.bends[bn].redraw(); - - if (this.manageLabelHandle) - { - this.checkLabelHandle(this.bends[bn].bounds); - } - - this.redrawInnerBends(p0, pe); - } - - if (this.abspoints != null && this.virtualBends != null && this.virtualBends.length > 0) - { - var last = this.abspoints[0]; - - for (var i = 0; i < this.virtualBends.length; i++) - { - if (this.virtualBends[i] != null && this.abspoints[i + 1] != null) - { - var pt = this.abspoints[i + 1]; - var b = this.virtualBends[i]; - var x = last.x + (pt.x - last.x) / 2; - var y = last.y + (pt.y - last.y) / 2; - b.bounds = new mxRectangle(Math.floor(x - b.bounds.width / 2), - Math.floor(y - b.bounds.height / 2), b.bounds.width, b.bounds.height); - b.redraw(); - mxUtils.setOpacity(b.node, this.virtualBendOpacity); - last = pt; - - if (this.manageLabelHandle) - { - this.checkLabelHandle(b.bounds); - } + if (!ignoreHandles) { + this.redrawHandles(); } } - } - - if (this.labelShape != null) - { - this.labelShape.redraw(); - } - - if (this.customHandles != null) - { - for (var i = 0; i < this.customHandles.length; i++) - { - var temp = this.customHandles[i].shape.node.style.display; - this.customHandles[i].redraw(); - this.customHandles[i].shape.node.style.display = temp; + }; - // Hides custom handles during text editing - this.customHandles[i].shape.node.style.visibility = - (this.isCustomHandleVisible(this.customHandles[i])) ? - '' : 'hidden'; - } - } -}; + /** + * Function: redrawHandles + * + * Redraws the handles. + */ + redrawHandles = () => { + var cell = this.state.cell; -/** - * Function: isCustomHandleVisible - * - * Returns true if the given custom handle is visible. - */ -isCustomHandleVisible = (handle)=> -{ - return !this.graph.isEditing() && this.state.view.graph.getSelectionCount() == 1; -}; - -/** - * Function: hideHandles - * - * Shortcut to . - */ -setHandlesVisible = (visible)=> -{ - if (this.bends != null) - { - for (var i = 0; i < this.bends.length; i++) - { - this.bends[i].node.style.display = (visible) ? '' : 'none'; - } - } - - if (this.virtualBends != null) - { - for (var i = 0; i < this.virtualBends.length; i++) - { - this.virtualBends[i].node.style.display = (visible) ? '' : 'none'; - } - } - - if (this.labelShape != null) - { - this.labelShape.node.style.display = (visible) ? '' : 'none'; - } - - if (this.customHandles != null) - { - for (var i = 0; i < this.customHandles.length; i++) - { - this.customHandles[i].setVisible(visible); - } - } -}; - -/** - * Function: redrawInnerBends - * - * Updates and redraws the inner bends. - * - * Parameters: - * - * p0 - that represents the location of the first point. - * pe - that represents the location of the last point. - */ -redrawInnerBends = (p0, pe)=> -{ - for (var i = 1; i < this.bends.length - 1; i++) - { - if (this.bends[i] != null) - { - if (this.abspoints[i] != null) - { - var x = this.abspoints[i].x; - var y = this.abspoints[i].y; - - var b = this.bends[i].bounds; - this.bends[i].node.style.visibility = 'visible'; - this.bends[i].bounds = new mxRectangle(Math.round(x - b.width / 2), - Math.round(y - b.height / 2), b.width, b.height); - - if (this.manageLabelHandle) - { - this.checkLabelHandle(this.bends[i].bounds); - } - else if (this.handleImage == null && this.labelShape.visible && mxUtils.intersects(this.bends[i].bounds, this.labelShape.bounds)) - { - w = mxConstants.HANDLE_SIZE + 3; - h = mxConstants.HANDLE_SIZE + 3; - this.bends[i].bounds = new mxRectangle(Math.round(x - w / 2), Math.round(y - h / 2), w, h); - } - - this.bends[i].redraw(); - } - else - { - this.bends[i].destroy(); - this.bends[i] = null; - } - } - } -}; - -/** - * Function: checkLabelHandle - * - * Checks if the label handle intersects the given bounds and moves it if it - * intersects. - */ -checkLabelHandle = (b)=> -{ - if (this.labelShape != null) - { - var b2 = this.labelShape.bounds; - - if (mxUtils.intersects(b, b2)) - { - if (b.getCenterY() < b2.getCenterY()) - { - b2.y = b.y + b.height; - } - else - { - b2.y = b.y - b2.height; - } - } - } -}; - -/** - * Function: drawPreview - * - * Redraws the preview. - */ -drawPreview = ()=> -{ - try - { - if (this.isLabel) - { - var b = this.labelShape.bounds; - var bounds = new mxRectangle(Math.round(this.label.x - b.width / 2), + // Updates the handle for the label position + var b = this.labelShape.bounds; + this.label = new mxPoint(this.state.absoluteOffset.x, this.state.absoluteOffset.y); + this.labelShape.bounds = new mxRectangle(Math.round(this.label.x - b.width / 2), Math.round(this.label.y - b.height / 2), b.width, b.height); - - if (!this.labelShape.bounds.equals(bounds)) - { - this.labelShape.bounds = bounds; - this.labelShape.redraw(); + + // Shows or hides the label handle depending on the label + var lab = this.graph.getLabel(cell); + this.labelShape.visible = (lab != null && lab.length > 0 && this.graph.isLabelMovable(cell)); + + if (this.bends != null && this.bends.length > 0) { + var n = this.abspoints.length - 1; + + var p0 = this.abspoints[0]; + var x0 = p0.x; + var y0 = p0.y; + + b = this.bends[0].bounds; + this.bends[0].bounds = new mxRectangle(Math.floor(x0 - b.width / 2), + Math.floor(y0 - b.height / 2), b.width, b.height); + this.bends[0].fill = this.getHandleFillColor(0); + this.bends[0].redraw(); + + if (this.manageLabelHandle) { + this.checkLabelHandle(this.bends[0].bounds); + } + + var pe = this.abspoints[n]; + var xn = pe.x; + var yn = pe.y; + + var bn = this.bends.length - 1; + b = this.bends[bn].bounds; + this.bends[bn].bounds = new mxRectangle(Math.floor(xn - b.width / 2), + Math.floor(yn - b.height / 2), b.width, b.height); + this.bends[bn].fill = this.getHandleFillColor(bn); + this.bends[bn].redraw(); + + if (this.manageLabelHandle) { + this.checkLabelHandle(this.bends[bn].bounds); + } + + this.redrawInnerBends(p0, pe); + } + + if (this.abspoints != null && this.virtualBends != null && this.virtualBends.length > 0) { + var last = this.abspoints[0]; + + for (var i = 0; i < this.virtualBends.length; i++) { + if (this.virtualBends[i] != null && this.abspoints[i + 1] != null) { + var pt = this.abspoints[i + 1]; + var b = this.virtualBends[i]; + var x = last.x + (pt.x - last.x) / 2; + var y = last.y + (pt.y - last.y) / 2; + b.bounds = new mxRectangle(Math.floor(x - b.bounds.width / 2), + Math.floor(y - b.bounds.height / 2), b.bounds.width, b.bounds.height); + b.redraw(); + mxUtils.setOpacity(b.node, this.virtualBendOpacity); + last = pt; + + if (this.manageLabelHandle) { + this.checkLabelHandle(b.bounds); + } + } } } - - if (this.shape != null && !mxUtils.equalPoints(this.shape.points, this.abspoints)) - { - this.shape.apply(this.state); - this.shape.points = this.abspoints.slice(); - this.shape.scale = this.state.view.scale; - this.shape.isDashed = this.isSelectionDashed(); - this.shape.stroke = this.getSelectionColor(); - this.shape.strokewidth = this.getSelectionStrokeWidth() / this.shape.scale / this.shape.scale; - this.shape.isShadow = false; - this.shape.redraw(); - } - - this.updateParentHighlight(); - } - catch (e) - { - // ignore - } -}; -/** - * Function: refresh - * - * Refreshes the bends of this handler. - */ -refresh = ()=> -{ - if (this.state != null) - { - this.abspoints = this.getSelectionPoints(this.state); - this.points = []; - - if (this.bends != null) - { - this.destroyBends(this.bends); - this.bends = this.createBends(); + if (this.labelShape != null) { + this.labelShape.redraw(); } - - if (this.virtualBends != null) - { - this.destroyBends(this.virtualBends); - this.virtualBends = this.createVirtualBends(); - } - - if (this.customHandles != null) - { - this.destroyBends(this.customHandles); - this.customHandles = this.createCustomHandles(); - } - - // Puts label node on top of bends - if (this.labelShape != null && this.labelShape.node != null && this.labelShape.node.parentNode != null) - { - this.labelShape.node.parentNode.appendChild(this.labelShape.node); - } - } -}; -/** - * Function: isDestroyed - * - * Returns true if was called. - */ -isDestroyed = ()=> -{ - return this.shape == null; -}; + if (this.customHandles != null) { + for (var i = 0; i < this.customHandles.length; i++) { + var temp = this.customHandles[i].shape.node.style.display; + this.customHandles[i].redraw(); + this.customHandles[i].shape.node.style.display = temp; -/** - * Function: destroyBends - * - * Destroys all elements in . - */ -destroyBends = (bends)=> -{ - if (bends != null) - { - for (var i = 0; i < bends.length; i++) - { - if (bends[i] != null) - { - bends[i].destroy(); + // Hides custom handles during text editing + this.customHandles[i].shape.node.style.visibility = + (this.isCustomHandleVisible(this.customHandles[i])) ? + '' : 'hidden'; } } - } -}; + }; -/** - * Function: destroy - * - * Destroys the handler and all its resources and DOM nodes. This does - * normally not need to be called as handlers are destroyed automatically - * when the corresponding cell is deselected. - */ -destroy = ()=> -{ - if (this.escapeHandler != null) - { - this.state.view.graph.removeListener(this.escapeHandler); - this.escapeHandler = null; - } - - if (this.marker != null) - { - this.marker.destroy(); - this.marker = null; - } - - if (this.shape != null) - { - this.shape.destroy(); - this.shape = null; - } - - if (this.parentHighlight != null) - { - var parent = this.graph.model.getParent(this.state.cell); - var pstate = this.graph.view.getState(parent); + /** + * Function: isCustomHandleVisible + * + * Returns true if the given custom handle is visible. + */ + isCustomHandleVisible = (handle) => { + return !this.graph.isEditing() && this.state.view.graph.getSelectionCount() == 1; + }; - if (pstate != null && pstate.parentHighlight == this.parentHighlight) - { - pstate.parentHighlight = null; + /** + * Function: hideHandles + * + * Shortcut to . + */ + setHandlesVisible = (visible) => { + if (this.bends != null) { + for (var i = 0; i < this.bends.length; i++) { + this.bends[i].node.style.display = (visible) ? '' : 'none'; + } } - - this.parentHighlight.destroy(); - this.parentHighlight = null; - } - - if (this.labelShape != null) - { - this.labelShape.destroy(); - this.labelShape = null; - } - if (this.constraintHandler != null) - { - this.constraintHandler.destroy(); - this.constraintHandler = null; - } - - this.destroyBends(this.virtualBends); - this.virtualBends = null; - - this.destroyBends(this.customHandles); - this.customHandles = null; + if (this.virtualBends != null) { + for (var i = 0; i < this.virtualBends.length; i++) { + this.virtualBends[i].node.style.display = (visible) ? '' : 'none'; + } + } - this.destroyBends(this.bends); - this.bends = null; - - this.removeHint(); -}; + if (this.labelShape != null) { + this.labelShape.node.style.display = (visible) ? '' : 'none'; + } + + if (this.customHandles != null) { + for (var i = 0; i < this.customHandles.length; i++) { + this.customHandles[i].setVisible(visible); + } + } + }; + + /** + * Function: redrawInnerBends + * + * Updates and redraws the inner bends. + * + * Parameters: + * + * p0 - that represents the location of the first point. + * pe - that represents the location of the last point. + */ + redrawInnerBends = (p0, pe) => { + for (var i = 1; i < this.bends.length - 1; i++) { + if (this.bends[i] != null) { + if (this.abspoints[i] != null) { + var x = this.abspoints[i].x; + var y = this.abspoints[i].y; + + var b = this.bends[i].bounds; + this.bends[i].node.style.visibility = 'visible'; + this.bends[i].bounds = new mxRectangle(Math.round(x - b.width / 2), + Math.round(y - b.height / 2), b.width, b.height); + + if (this.manageLabelHandle) { + this.checkLabelHandle(this.bends[i].bounds); + } else if (this.handleImage == null && this.labelShape.visible && mxUtils.intersects(this.bends[i].bounds, this.labelShape.bounds)) { + w = mxConstants.HANDLE_SIZE + 3; + h = mxConstants.HANDLE_SIZE + 3; + this.bends[i].bounds = new mxRectangle(Math.round(x - w / 2), Math.round(y - h / 2), w, h); + } + + this.bends[i].redraw(); + } else { + this.bends[i].destroy(); + this.bends[i] = null; + } + } + } + }; + + /** + * Function: checkLabelHandle + * + * Checks if the label handle intersects the given bounds and moves it if it + * intersects. + */ + checkLabelHandle = (b) => { + if (this.labelShape != null) { + var b2 = this.labelShape.bounds; + + if (mxUtils.intersects(b, b2)) { + if (b.getCenterY() < b2.getCenterY()) { + b2.y = b.y + b.height; + } else { + b2.y = b.y - b2.height; + } + } + } + }; + + /** + * Function: drawPreview + * + * Redraws the preview. + */ + drawPreview = () => { + try { + if (this.isLabel) { + var b = this.labelShape.bounds; + var bounds = new mxRectangle(Math.round(this.label.x - b.width / 2), + Math.round(this.label.y - b.height / 2), b.width, b.height); + + if (!this.labelShape.bounds.equals(bounds)) { + this.labelShape.bounds = bounds; + this.labelShape.redraw(); + } + } + + if (this.shape != null && !mxUtils.equalPoints(this.shape.points, this.abspoints)) { + this.shape.apply(this.state); + this.shape.points = this.abspoints.slice(); + this.shape.scale = this.state.view.scale; + this.shape.isDashed = this.isSelectionDashed(); + this.shape.stroke = this.getSelectionColor(); + this.shape.strokewidth = this.getSelectionStrokeWidth() / this.shape.scale / this.shape.scale; + this.shape.isShadow = false; + this.shape.redraw(); + } + + this.updateParentHighlight(); + } catch (e) { + // ignore + } + }; + + /** + * Function: refresh + * + * Refreshes the bends of this handler. + */ + refresh = () => { + if (this.state != null) { + this.abspoints = this.getSelectionPoints(this.state); + this.points = []; + + if (this.bends != null) { + this.destroyBends(this.bends); + this.bends = this.createBends(); + } + + if (this.virtualBends != null) { + this.destroyBends(this.virtualBends); + this.virtualBends = this.createVirtualBends(); + } + + if (this.customHandles != null) { + this.destroyBends(this.customHandles); + this.customHandles = this.createCustomHandles(); + } + + // Puts label node on top of bends + if (this.labelShape != null && this.labelShape.node != null && this.labelShape.node.parentNode != null) { + this.labelShape.node.parentNode.appendChild(this.labelShape.node); + } + } + }; + + /** + * Function: isDestroyed + * + * Returns true if was called. + */ + isDestroyed = () => { + return this.shape == null; + }; + + /** + * Function: destroyBends + * + * Destroys all elements in . + */ + destroyBends = (bends) => { + if (bends != null) { + for (var i = 0; i < bends.length; i++) { + if (bends[i] != null) { + bends[i].destroy(); + } + } + } + }; + + /** + * Function: destroy + * + * Destroys the handler and all its resources and DOM nodes. This does + * normally not need to be called as handlers are destroyed automatically + * when the corresponding cell is deselected. + */ + destroy = () => { + if (this.escapeHandler != null) { + this.state.view.graph.removeListener(this.escapeHandler); + this.escapeHandler = null; + } + + if (this.marker != null) { + this.marker.destroy(); + this.marker = null; + } + + if (this.shape != null) { + this.shape.destroy(); + this.shape = null; + } + + if (this.parentHighlight != null) { + var parent = this.graph.model.getParent(this.state.cell); + var pstate = this.graph.view.getState(parent); + + if (pstate != null && pstate.parentHighlight == this.parentHighlight) { + pstate.parentHighlight = null; + } + + this.parentHighlight.destroy(); + this.parentHighlight = null; + } + + if (this.labelShape != null) { + this.labelShape.destroy(); + this.labelShape = null; + } + + if (this.constraintHandler != null) { + this.constraintHandler.destroy(); + this.constraintHandler = null; + } + + this.destroyBends(this.virtualBends); + this.virtualBends = null; + + this.destroyBends(this.customHandles); + this.customHandles = null; + + this.destroyBends(this.bends); + this.bends = null; + + this.removeHint(); + }; +} + +export default mxEdgeHandler; diff --git a/src/js/handler/mxEdgeSegmentHandler.js b/src/js/handler/mxEdgeSegmentHandler.js index 52bb1f1c8..44529b0e1 100644 --- a/src/js/handler/mxEdgeSegmentHandler.js +++ b/src/js/handler/mxEdgeSegmentHandler.js @@ -2,412 +2,359 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -function mxEdgeSegmentHandler(state) -{ - mxEdgeHandler.call(this, state); -}; -/** - * Extends mxEdgeHandler. - */ -mxUtils.extend(mxEdgeSegmentHandler, mxElbowEdgeHandler); +class mxEdgeSegmentHandler extends mxElbowEdgeHandler { + constructor(state) { + // WARNING: should be super of mxEdgeHandler! + super(state); + }; -/** - * Function: getCurrentPoints - * - * Returns the current absolute points. - */ -getCurrentPoints = ()=> -{ - var pts = this.state.absolutePoints; - - if (pts != null) - { - // Special case for straight edges where we add a virtual middle handle for moving the edge - var tol = Math.max(1, this.graph.view.scale); - - if (pts.length == 2 || (pts.length == 3 && - (Math.abs(pts[0].x - pts[1].x) < tol && Math.abs(pts[1].x - pts[2].x) < tol || - Math.abs(pts[0].y - pts[1].y) < tol && Math.abs(pts[1].y - pts[2].y) < tol))) - { - var cx = pts[0].x + (pts[pts.length - 1].x - pts[0].x) / 2; - var cy = pts[0].y + (pts[pts.length - 1].y - pts[0].y) / 2; - - pts = [pts[0], new mxPoint(cx, cy), new mxPoint(cx, cy), pts[pts.length - 1]]; - } - } + /** + * Function: getCurrentPoints + * + * Returns the current absolute points. + */ + getCurrentPoints = () => { + var pts = this.state.absolutePoints; - return pts; -}; + if (pts != null) { + // Special case for straight edges where we add a virtual middle handle for moving the edge + var tol = Math.max(1, this.graph.view.scale); -/** - * Function: getPreviewPoints - * - * Updates the given preview state taking into account the state of the constraint handler. - */ -getPreviewPoints = (point)=> -{ - if (this.isSource || this.isTarget) - { - return getPreviewPoints.apply(this, arguments); - } - else - { - var pts = this.getCurrentPoints(); - var last = this.convertPoint(pts[0].clone(), false); - point = this.convertPoint(point.clone(), false); - var result = []; + if (pts.length == 2 || (pts.length == 3 && + (Math.abs(pts[0].x - pts[1].x) < tol && Math.abs(pts[1].x - pts[2].x) < tol || + Math.abs(pts[0].y - pts[1].y) < tol && Math.abs(pts[1].y - pts[2].y) < tol))) { + var cx = pts[0].x + (pts[pts.length - 1].x - pts[0].x) / 2; + var cy = pts[0].y + (pts[pts.length - 1].y - pts[0].y) / 2; - for (var i = 1; i < pts.length; i++) - { - var pt = this.convertPoint(pts[i].clone(), false); - - if (i == this.index) - { - if (Math.round(last.x - pt.x) == 0) - { - last.x = point.x; - pt.x = point.x; - } - - if (Math.round(last.y - pt.y) == 0) - { - last.y = point.y; - pt.y = point.y; - } - } - - if (i < pts.length - 1) - { - result.push(pt); - } - - last = pt; - } - - // Replaces single point that intersects with source or target - if (result.length == 1) - { - var source = this.state.getVisibleTerminalState(true); - var target = this.state.getVisibleTerminalState(false); - var scale = this.state.view.getScale(); - var tr = this.state.view.getTranslate(); - - var x = result[0].x * scale + tr.x; - var y = result[0].y * scale + tr.y; - - if ((source != null && mxUtils.contains(source, x, y)) || - (target != null && mxUtils.contains(target, x, y))) - { - result = [point, point]; + pts = [pts[0], new mxPoint(cx, cy), new mxPoint(cx, cy), pts[pts.length - 1]]; } } - return result; - } -}; + return pts; + }; -/** - * Function: updatePreviewState - * - * Overridden to perform optimization of the edge style result. - */ -updatePreviewState = (edge, point, terminalState, me)=> -{ - updatePreviewState.apply(this, arguments); + /** + * Function: getPreviewPoints + * + * Updates the given preview state taking into account the state of the constraint handler. + */ + getPreviewPoints = (point) => { + if (this.isSource || this.isTarget) { + return getPreviewPoints.apply(this, arguments); + } else { + var pts = this.getCurrentPoints(); + var last = this.convertPoint(pts[0].clone(), false); + point = this.convertPoint(point.clone(), false); + var result = []; - // Checks and corrects preview by running edge style again - if (!this.isSource && !this.isTarget) - { - point = this.convertPoint(point.clone(), false); - var pts = edge.absolutePoints; - var pt0 = pts[0]; - var pt1 = pts[1]; + for (var i = 1; i < pts.length; i++) { + var pt = this.convertPoint(pts[i].clone(), false); - var result = []; - - for (var i = 2; i < pts.length; i++) - { - var pt2 = pts[i]; - - // Merges adjacent segments only if more than 2 to allow for straight edges - if ((Math.round(pt0.x - pt1.x) != 0 || Math.round(pt1.x - pt2.x) != 0) && - (Math.round(pt0.y - pt1.y) != 0 || Math.round(pt1.y - pt2.y) != 0)) - { - result.push(this.convertPoint(pt1.clone(), false)); - } + if (i == this.index) { + if (Math.round(last.x - pt.x) == 0) { + last.x = point.x; + pt.x = point.x; + } - pt0 = pt1; - pt1 = pt2; - } - - var source = this.state.getVisibleTerminalState(true); - var target = this.state.getVisibleTerminalState(false); - var rpts = this.state.absolutePoints; - - // A straight line is represented by 3 handles - if (result.length == 0 && (Math.round(pts[0].x - pts[pts.length - 1].x) == 0 || - Math.round(pts[0].y - pts[pts.length - 1].y) == 0)) - { - result = [point, point]; - } - // Handles special case of transitions from straight vertical to routed - else if (pts.length == 5 && result.length == 2 && source != null && target != null && - rpts != null && Math.round(rpts[0].x - rpts[rpts.length - 1].x) == 0) - { - var view = this.graph.getView(); - var scale = view.getScale(); - var tr = view.getTranslate(); - - var y0 = view.getRoutingCenterY(source) / scale - tr.y; - - // Use fixed connection point y-coordinate if one exists - var sc = this.graph.getConnectionConstraint(edge, source, true); - - if (sc != null) - { - var pt = this.graph.getConnectionPoint(source, sc); - - if (pt != null) - { - this.convertPoint(pt, false); - y0 = pt.y; - } - } - - var ye = view.getRoutingCenterY(target) / scale - tr.y; - - // Use fixed connection point y-coordinate if one exists - var tc = this.graph.getConnectionConstraint(edge, target, false); - - if (tc) - { - var pt = this.graph.getConnectionPoint(target, tc); - - if (pt != null) - { - this.convertPoint(pt, false); - ye = pt.y; - } - } - - result = [new mxPoint(point.x, y0), new mxPoint(point.x, ye)]; - } - - this.points = result; - - // LATER: Check if points and result are different - edge.view.updateFixedTerminalPoints(edge, source, target); - edge.view.updatePoints(edge, this.points, source, target); - edge.view.updateFloatingTerminalPoints(edge, source, target); - } -}; - -/** - * Overriden to merge edge segments. - */ -connect = (edge, terminal, isSource, isClone, me)=> -{ - var model = this.graph.getModel(); - var geo = model.getGeometry(edge); - var result = null; - - // Merges adjacent edge segments - if (geo != null && geo.points != null && geo.points.length > 0) - { - var pts = this.abspoints; - var pt0 = pts[0]; - var pt1 = pts[1]; - result = []; - - for (var i = 2; i < pts.length; i++) - { - var pt2 = pts[i]; - - // Merges adjacent segments only if more than 2 to allow for straight edges - if ((Math.round(pt0.x - pt1.x) != 0 || Math.round(pt1.x - pt2.x) != 0) && - (Math.round(pt0.y - pt1.y) != 0 || Math.round(pt1.y - pt2.y) != 0)) - { - result.push(this.convertPoint(pt1.clone(), false)); - } - - pt0 = pt1; - pt1 = pt2; - } - } - - model.beginUpdate(); - try - { - if (result != null) - { - var geo = model.getGeometry(edge); - - if (geo != null) - { - geo = geo.clone(); - geo.points = result; - - model.setGeometry(edge, geo); - } - } - - edge = connect.apply(this, arguments); - } - finally - { - model.endUpdate(); - } - - return edge; -}; - -/** - * Function: getTooltipForNode - * - * Returns no tooltips. - */ -getTooltipForNode = (node)=> -{ - return null; -}; - -/** - * Function: start - * - * Starts the handling of the mouse gesture. - */ -start = (x, y, index)=> -{ - start.apply(this, arguments); - - if (this.bends != null && this.bends[index] != null && - !this.isSource && !this.isTarget) - { - mxUtils.setOpacity(this.bends[index].node, 100); - } -}; - -/** - * Function: createBends - * - * Adds custom bends for the center of each segment. - */ -createBends = ()=> -{ - var bends = []; - - // Source - var bend = this.createHandleShape(0); - this.initBend(bend); - bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE); - bends.push(bend); - - var pts = this.getCurrentPoints(); - - // Waypoints (segment handles) - if (this.graph.isCellBendable(this.state.cell)) - { - if (this.points == null) - { - this.points = []; - } - - for (var i = 0; i < pts.length - 1; i++) - { - bend = this.createVirtualBend(); - bends.push(bend); - var horizontal = Math.round(pts[i].x - pts[i + 1].x) == 0; - - // Special case where dy is 0 as well - if (Math.round(pts[i].y - pts[i + 1].y) == 0 && i < pts.length - 2) - { - horizontal = Math.round(pts[i].x - pts[i + 2].x) == 0; - } - - bend.setCursor((horizontal) ? 'col-resize' : 'row-resize'); - this.points.push(new mxPoint(0,0)); - } - } - - // Target - var bend = this.createHandleShape(pts.length); - this.initBend(bend); - bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE); - bends.push(bend); - - return bends; -}; - -/** - * Function: redraw - * - * Overridden to invoke before the redraw. - */ -redraw = ()=> -{ - this.refresh(); - redraw.apply(this, arguments); -}; - -/** - * Function: redrawInnerBends - * - * Updates the position of the custom bends. - */ -redrawInnerBends = (p0, pe)=> -{ - if (this.graph.isCellBendable(this.state.cell)) - { - var pts = this.getCurrentPoints(); - - if (pts != null && pts.length > 1) - { - var straight = false; - - // Puts handle in the center of straight edges - if (pts.length == 4 && Math.round(pts[1].x - pts[2].x) == 0 && Math.round(pts[1].y - pts[2].y) == 0) - { - straight = true; - - if (Math.round(pts[0].y - pts[pts.length - 1].y) == 0) - { - var cx = pts[0].x + (pts[pts.length - 1].x - pts[0].x) / 2; - pts[1] = new mxPoint(cx, pts[1].y); - pts[2] = new mxPoint(cx, pts[2].y); - } - else - { - var cy = pts[0].y + (pts[pts.length - 1].y - pts[0].y) / 2; - pts[1] = new mxPoint(pts[1].x, cy); - pts[2] = new mxPoint(pts[2].x, cy); - } - } - - for (var i = 0; i < pts.length - 1; i++) - { - if (this.bends[i + 1] != null) - { - var p0 = pts[i]; - var pe = pts[i + 1]; - var pt = new mxPoint(p0.x + (pe.x - p0.x) / 2, p0.y + (pe.y - p0.y) / 2); - var b = this.bends[i + 1].bounds; - this.bends[i + 1].bounds = new mxRectangle(Math.floor(pt.x - b.width / 2), - Math.floor(pt.y - b.height / 2), b.width, b.height); - this.bends[i + 1].redraw(); - - if (this.manageLabelHandle) - { - this.checkLabelHandle(this.bends[i + 1].bounds); + if (Math.round(last.y - pt.y) == 0) { + last.y = point.y; + pt.y = point.y; } } + + if (i < pts.length - 1) { + result.push(pt); + } + + last = pt; } - - if (straight) - { - mxUtils.setOpacity(this.bends[1].node, this.virtualBendOpacity); - mxUtils.setOpacity(this.bends[3].node, this.virtualBendOpacity); + + // Replaces single point that intersects with source or target + if (result.length == 1) { + var source = this.state.getVisibleTerminalState(true); + var target = this.state.getVisibleTerminalState(false); + var scale = this.state.view.getScale(); + var tr = this.state.view.getTranslate(); + + var x = result[0].x * scale + tr.x; + var y = result[0].y * scale + tr.y; + + if ((source != null && mxUtils.contains(source, x, y)) || + (target != null && mxUtils.contains(target, x, y))) { + result = [point, point]; + } + } + + return result; + } + }; + + /** + * Function: updatePreviewState + * + * Overridden to perform optimization of the edge style result. + */ + updatePreviewState = (edge, point, terminalState, me) => { + updatePreviewState.apply(this, arguments); + + // Checks and corrects preview by running edge style again + if (!this.isSource && !this.isTarget) { + point = this.convertPoint(point.clone(), false); + var pts = edge.absolutePoints; + var pt0 = pts[0]; + var pt1 = pts[1]; + + var result = []; + + for (var i = 2; i < pts.length; i++) { + var pt2 = pts[i]; + + // Merges adjacent segments only if more than 2 to allow for straight edges + if ((Math.round(pt0.x - pt1.x) != 0 || Math.round(pt1.x - pt2.x) != 0) && + (Math.round(pt0.y - pt1.y) != 0 || Math.round(pt1.y - pt2.y) != 0)) { + result.push(this.convertPoint(pt1.clone(), false)); + } + + pt0 = pt1; + pt1 = pt2; + } + + var source = this.state.getVisibleTerminalState(true); + var target = this.state.getVisibleTerminalState(false); + var rpts = this.state.absolutePoints; + + // A straight line is represented by 3 handles + if (result.length == 0 && (Math.round(pts[0].x - pts[pts.length - 1].x) == 0 || + Math.round(pts[0].y - pts[pts.length - 1].y) == 0)) { + result = [point, point]; + } + // Handles special case of transitions from straight vertical to routed + else if (pts.length == 5 && result.length == 2 && source != null && target != null && + rpts != null && Math.round(rpts[0].x - rpts[rpts.length - 1].x) == 0) { + var view = this.graph.getView(); + var scale = view.getScale(); + var tr = view.getTranslate(); + + var y0 = view.getRoutingCenterY(source) / scale - tr.y; + + // Use fixed connection point y-coordinate if one exists + var sc = this.graph.getConnectionConstraint(edge, source, true); + + if (sc != null) { + var pt = this.graph.getConnectionPoint(source, sc); + + if (pt != null) { + this.convertPoint(pt, false); + y0 = pt.y; + } + } + + var ye = view.getRoutingCenterY(target) / scale - tr.y; + + // Use fixed connection point y-coordinate if one exists + var tc = this.graph.getConnectionConstraint(edge, target, false); + + if (tc) { + var pt = this.graph.getConnectionPoint(target, tc); + + if (pt != null) { + this.convertPoint(pt, false); + ye = pt.y; + } + } + + result = [new mxPoint(point.x, y0), new mxPoint(point.x, ye)]; + } + + this.points = result; + + // LATER: Check if points and result are different + edge.view.updateFixedTerminalPoints(edge, source, target); + edge.view.updatePoints(edge, this.points, source, target); + edge.view.updateFloatingTerminalPoints(edge, source, target); + } + }; + + /** + * Overriden to merge edge segments. + */ + connect = (edge, terminal, isSource, isClone, me) => { + var model = this.graph.getModel(); + var geo = model.getGeometry(edge); + var result = null; + + // Merges adjacent edge segments + if (geo != null && geo.points != null && geo.points.length > 0) { + var pts = this.abspoints; + var pt0 = pts[0]; + var pt1 = pts[1]; + result = []; + + for (var i = 2; i < pts.length; i++) { + var pt2 = pts[i]; + + // Merges adjacent segments only if more than 2 to allow for straight edges + if ((Math.round(pt0.x - pt1.x) != 0 || Math.round(pt1.x - pt2.x) != 0) && + (Math.round(pt0.y - pt1.y) != 0 || Math.round(pt1.y - pt2.y) != 0)) { + result.push(this.convertPoint(pt1.clone(), false)); + } + + pt0 = pt1; + pt1 = pt2; } } - } -}; + + model.beginUpdate(); + try { + if (result != null) { + var geo = model.getGeometry(edge); + + if (geo != null) { + geo = geo.clone(); + geo.points = result; + + model.setGeometry(edge, geo); + } + } + + edge = connect.apply(this, arguments); + } finally { + model.endUpdate(); + } + + return edge; + }; + + /** + * Function: getTooltipForNode + * + * Returns no tooltips. + */ + getTooltipForNode = (node) => { + return null; + }; + + /** + * Function: start + * + * Starts the handling of the mouse gesture. + */ + start = (x, y, index) => { + start.apply(this, arguments); + + if (this.bends != null && this.bends[index] != null && + !this.isSource && !this.isTarget) { + mxUtils.setOpacity(this.bends[index].node, 100); + } + }; + + /** + * Function: createBends + * + * Adds custom bends for the center of each segment. + */ + createBends = () => { + var bends = []; + + // Source + var bend = this.createHandleShape(0); + this.initBend(bend); + bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE); + bends.push(bend); + + var pts = this.getCurrentPoints(); + + // Waypoints (segment handles) + if (this.graph.isCellBendable(this.state.cell)) { + if (this.points == null) { + this.points = []; + } + + for (var i = 0; i < pts.length - 1; i++) { + bend = this.createVirtualBend(); + bends.push(bend); + var horizontal = Math.round(pts[i].x - pts[i + 1].x) == 0; + + // Special case where dy is 0 as well + if (Math.round(pts[i].y - pts[i + 1].y) == 0 && i < pts.length - 2) { + horizontal = Math.round(pts[i].x - pts[i + 2].x) == 0; + } + + bend.setCursor((horizontal) ? 'col-resize' : 'row-resize'); + this.points.push(new mxPoint(0, 0)); + } + } + + // Target + var bend = this.createHandleShape(pts.length); + this.initBend(bend); + bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE); + bends.push(bend); + + return bends; + }; + + /** + * Function: redraw + * + * Overridden to invoke before the redraw. + */ + redraw = () => { + this.refresh(); + redraw.apply(this, arguments); + }; + + /** + * Function: redrawInnerBends + * + * Updates the position of the custom bends. + */ + redrawInnerBends = (p0, pe) => { + if (this.graph.isCellBendable(this.state.cell)) { + var pts = this.getCurrentPoints(); + + if (pts != null && pts.length > 1) { + var straight = false; + + // Puts handle in the center of straight edges + if (pts.length == 4 && Math.round(pts[1].x - pts[2].x) == 0 && Math.round(pts[1].y - pts[2].y) == 0) { + straight = true; + + if (Math.round(pts[0].y - pts[pts.length - 1].y) == 0) { + var cx = pts[0].x + (pts[pts.length - 1].x - pts[0].x) / 2; + pts[1] = new mxPoint(cx, pts[1].y); + pts[2] = new mxPoint(cx, pts[2].y); + } else { + var cy = pts[0].y + (pts[pts.length - 1].y - pts[0].y) / 2; + pts[1] = new mxPoint(pts[1].x, cy); + pts[2] = new mxPoint(pts[2].x, cy); + } + } + + for (var i = 0; i < pts.length - 1; i++) { + if (this.bends[i + 1] != null) { + var p0 = pts[i]; + var pe = pts[i + 1]; + var pt = new mxPoint(p0.x + (pe.x - p0.x) / 2, p0.y + (pe.y - p0.y) / 2); + var b = this.bends[i + 1].bounds; + this.bends[i + 1].bounds = new mxRectangle(Math.floor(pt.x - b.width / 2), + Math.floor(pt.y - b.height / 2), b.width, b.height); + this.bends[i + 1].redraw(); + + if (this.manageLabelHandle) { + this.checkLabelHandle(this.bends[i + 1].bounds); + } + } + } + + if (straight) { + mxUtils.setOpacity(this.bends[1].node, this.virtualBendOpacity); + mxUtils.setOpacity(this.bends[3].node, this.virtualBendOpacity); + } + } + } + }; +} + +export default mxEdgeSegmentHandler; diff --git a/src/js/handler/mxElbowEdgeHandler.js b/src/js/handler/mxElbowEdgeHandler.js index 3a4a2adcd..fe981cc3d 100644 --- a/src/js/handler/mxElbowEdgeHandler.js +++ b/src/js/handler/mxElbowEdgeHandler.js @@ -2,229 +2,206 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * Class: mxElbowEdgeHandler - * - * Graph event handler that reconnects edges and modifies control points and - * the edge label location. Uses for finding and - * highlighting new source and target vertices. This handler is automatically - * created in . It extends . - * - * Constructor: mxEdgeHandler - * - * Constructs an edge handler for the specified . - * - * Parameters: - * - * state - of the cell to be modified. - */ -function mxElbowEdgeHandler(state) -{ - mxEdgeHandler.call(this, state); -}; -/** - * Extends mxEdgeHandler. - */ -mxUtils.extend(mxElbowEdgeHandler, mxEdgeHandler); +class mxElbowEdgeHandler extends mxEdgeHandler { + /** + * Specifies if a double click on the middle handle should call + * . Default is true. + */ + flipEnabled = true; -/** - * Specifies if a double click on the middle handle should call - * . Default is true. - */ -flipEnabled = true; + /** + * Variable: doubleClickOrientationResource + * + * Specifies the resource key for the tooltip to be displayed on the single + * control point for routed edges. If the resource for this key does not + * exist then the value is used as the error message. Default is + * 'doubleClickOrientation'. + */ + doubleClickOrientationResource = (mxClient.language !== 'none') ? 'doubleClickOrientation' : ''; -/** - * Variable: doubleClickOrientationResource - * - * Specifies the resource key for the tooltip to be displayed on the single - * control point for routed edges. If the resource for this key does not - * exist then the value is used as the error message. Default is - * 'doubleClickOrientation'. - */ -doubleClickOrientationResource = - (mxClient.language != 'none') ? 'doubleClickOrientation' : ''; + /** + * Class: mxElbowEdgeHandler + * + * Graph event handler that reconnects edges and modifies control points and + * the edge label location. Uses for finding and + * highlighting new source and target vertices. This handler is automatically + * created in . It extends . + * + * Constructor: mxEdgeHandler + * + * Constructs an edge handler for the specified . + * + * Parameters: + * + * state - of the cell to be modified. + */ + constructor(state) { + super(state); + }; -/** - * Function: createBends - * - * Overrides to create custom bends. - */ - createBends = ()=> - { - var bends = []; - - // Source - var bend = this.createHandleShape(0); - this.initBend(bend); - bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE); - bends.push(bend); + /** + * Function: createBends + * + * Overrides to create custom bends. + */ + createBends = () => { + var bends = []; - // Virtual - bends.push(this.createVirtualBend(mxUtils.bind(this, (evt)=> - { - if (!mxEvent.isConsumed(evt) && this.flipEnabled) - { - this.graph.flipEdge(this.state.cell, evt); - mxEvent.consume(evt); + // Source + var bend = this.createHandleShape(0); + this.initBend(bend); + bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE); + bends.push(bend); + + // Virtual + bends.push(this.createVirtualBend(mxUtils.bind(this, (evt) => { + if (!mxEvent.isConsumed(evt) && this.flipEnabled) { + this.graph.flipEdge(this.state.cell, evt); + mxEvent.consume(evt); + } + }))); + + this.points.push(new mxPoint(0, 0)); + + // Target + bend = this.createHandleShape(2); + this.initBend(bend); + bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE); + bends.push(bend); + + return bends; + }; + + /** + * Function: createVirtualBend + * + * Creates a virtual bend that supports double clicking and calls + * . + */ + createVirtualBend = (dblClickHandler) => { + var bend = this.createHandleShape(); + this.initBend(bend, dblClickHandler); + + bend.setCursor(this.getCursorForBend()); + + if (!this.graph.isCellBendable(this.state.cell)) { + bend.node.style.display = 'none'; } - }))); - - this.points.push(new mxPoint(0,0)); - // Target - bend = this.createHandleShape(2); - this.initBend(bend); - bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE); - bends.push(bend); - - return bends; - }; + return bend; + }; -/** - * Function: createVirtualBend - * - * Creates a virtual bend that supports double clicking and calls - * . - */ -createVirtualBend = (dblClickHandler)=> -{ - var bend = this.createHandleShape(); - this.initBend(bend, dblClickHandler); + /** + * Function: getCursorForBend + * + * Returns the cursor to be used for the bend. + */ + getCursorForBend = () => { + return (this.state.style[mxConstants.STYLE_EDGE] === mxEdgeStyle.TopToBottom || + this.state.style[mxConstants.STYLE_EDGE] === mxConstants.EDGESTYLE_TOPTOBOTTOM || + ((this.state.style[mxConstants.STYLE_EDGE] === mxEdgeStyle.ElbowConnector || + this.state.style[mxConstants.STYLE_EDGE] === mxConstants.EDGESTYLE_ELBOW) && + this.state.style[mxConstants.STYLE_ELBOW] === mxConstants.ELBOW_VERTICAL)) ? + 'row-resize' : 'col-resize'; + }; - bend.setCursor(this.getCursorForBend()); + /** + * Function: getTooltipForNode + * + * Returns the tooltip for the given node. + */ + getTooltipForNode = (node) => { + var tip = null; - if (!this.graph.isCellBendable(this.state.cell)) - { - bend.node.style.display = 'none'; - } + if (this.bends != null && this.bends[1] != null && (node === this.bends[1].node || + node.parentNode === this.bends[1].node)) { + tip = this.doubleClickOrientationResource; + tip = mxResources.get(tip) || tip; // translate + } - return bend; -}; + return tip; + }; -/** - * Function: getCursorForBend - * - * Returns the cursor to be used for the bend. - */ -getCursorForBend = ()=> -{ - return (this.state.style[mxConstants.STYLE_EDGE] == mxEdgeStyle.TopToBottom || - this.state.style[mxConstants.STYLE_EDGE] == mxConstants.EDGESTYLE_TOPTOBOTTOM || - ((this.state.style[mxConstants.STYLE_EDGE] == mxEdgeStyle.ElbowConnector || - this.state.style[mxConstants.STYLE_EDGE] == mxConstants.EDGESTYLE_ELBOW)&& - this.state.style[mxConstants.STYLE_ELBOW] == mxConstants.ELBOW_VERTICAL)) ? - 'row-resize' : 'col-resize'; -}; + /** + * Function: convertPoint + * + * Converts the given point in-place from screen to unscaled, untranslated + * graph coordinates and applies the grid. + * + * Parameters: + * + * point - to be converted. + * gridEnabled - Boolean that specifies if the grid should be applied. + */ + convertPoint = (point, gridEnabled) => { + var scale = this.graph.getView().getScale(); + var tr = this.graph.getView().getTranslate(); + var origin = this.state.origin; -/** - * Function: getTooltipForNode - * - * Returns the tooltip for the given node. - */ -getTooltipForNode = (node)=> -{ - var tip = null; - - if (this.bends != null && this.bends[1] != null && (node == this.bends[1].node || - node.parentNode == this.bends[1].node)) - { - tip = this.doubleClickOrientationResource; - tip = mxResources.get(tip) || tip; // translate - } + if (gridEnabled) { + point.x = this.graph.snap(point.x); + point.y = this.graph.snap(point.y); + } - return tip; -}; + point.x = Math.round(point.x / scale - tr.x - origin.x); + point.y = Math.round(point.y / scale - tr.y - origin.y); -/** - * Function: convertPoint - * - * Converts the given point in-place from screen to unscaled, untranslated - * graph coordinates and applies the grid. - * - * Parameters: - * - * point - to be converted. - * gridEnabled - Boolean that specifies if the grid should be applied. - */ -convertPoint = (point, gridEnabled)=> -{ - var scale = this.graph.getView().getScale(); - var tr = this.graph.getView().getTranslate(); - var origin = this.state.origin; - - if (gridEnabled) - { - point.x = this.graph.snap(point.x); - point.y = this.graph.snap(point.y); - } - - point.x = Math.round(point.x / scale - tr.x - origin.x); - point.y = Math.round(point.y / scale - tr.y - origin.y); - - return point; -}; + return point; + }; -/** - * Function: redrawInnerBends - * - * Updates and redraws the inner bends. - * - * Parameters: - * - * p0 - that represents the location of the first point. - * pe - that represents the location of the last point. - */ -redrawInnerBends = (p0, pe)=> -{ - var g = this.graph.getModel().getGeometry(this.state.cell); - var pts = this.state.absolutePoints; - var pt = null; + /** + * Function: redrawInnerBends + * + * Updates and redraws the inner bends. + * + * Parameters: + * + * p0 - that represents the location of the first point. + * pe - that represents the location of the last point. + */ + redrawInnerBends = (p0, pe) => { + var g = this.graph.getModel().getGeometry(this.state.cell); + var pts = this.state.absolutePoints; + var pt = null; - // Keeps the virtual bend on the edge shape - if (pts.length > 1) - { - p0 = pts[1]; - pe = pts[pts.length - 2]; - } - else if (g.points != null && g.points.length > 0) - { - pt = pts[0]; - } - - if (pt == null) - { - pt = new mxPoint(p0.x + (pe.x - p0.x) / 2, p0.y + (pe.y - p0.y) / 2); - } - else - { - pt = new mxPoint(this.graph.getView().scale * (pt.x + this.graph.getView().translate.x + this.state.origin.x), - this.graph.getView().scale * (pt.y + this.graph.getView().translate.y + this.state.origin.y)); - } + // Keeps the virtual bend on the edge shape + if (pts.length > 1) { + p0 = pts[1]; + pe = pts[pts.length - 2]; + } else if (g.points != null && g.points.length > 0) { + pt = pts[0]; + } - // Makes handle slightly bigger if the yellow label handle - // exists and intersects this green handle - var b = this.bends[1].bounds; - var w = b.width; - var h = b.height; - var bounds = new mxRectangle(Math.round(pt.x - w / 2), Math.round(pt.y - h / 2), w, h); + if (pt == null) { + pt = new mxPoint(p0.x + (pe.x - p0.x) / 2, p0.y + (pe.y - p0.y) / 2); + } else { + pt = new mxPoint(this.graph.getView().scale * (pt.x + this.graph.getView().translate.x + this.state.origin.x), + this.graph.getView().scale * (pt.y + this.graph.getView().translate.y + this.state.origin.y)); + } - if (this.manageLabelHandle) - { - this.checkLabelHandle(bounds); - } - else if (this.handleImage == null && this.labelShape.visible && mxUtils.intersects(bounds, this.labelShape.bounds)) - { - w = mxConstants.HANDLE_SIZE + 3; - h = mxConstants.HANDLE_SIZE + 3; - bounds = new mxRectangle(Math.floor(pt.x - w / 2), Math.floor(pt.y - h / 2), w, h); - } + // Makes handle slightly bigger if the yellow label handle + // exists and intersects this green handle + var b = this.bends[1].bounds; + var w = b.width; + var h = b.height; + var bounds = new mxRectangle(Math.round(pt.x - w / 2), Math.round(pt.y - h / 2), w, h); - this.bends[1].bounds = bounds; - this.bends[1].redraw(); - - if (this.manageLabelHandle) - { - this.checkLabelHandle(this.bends[1].bounds); - } -}; + if (this.manageLabelHandle) { + this.checkLabelHandle(bounds); + } else if (this.handleImage == null && this.labelShape.visible && mxUtils.intersects(bounds, this.labelShape.bounds)) { + w = mxConstants.HANDLE_SIZE + 3; + h = mxConstants.HANDLE_SIZE + 3; + bounds = new mxRectangle(Math.floor(pt.x - w / 2), Math.floor(pt.y - h / 2), w, h); + } + + this.bends[1].bounds = bounds; + this.bends[1].redraw(); + + if (this.manageLabelHandle) { + this.checkLabelHandle(this.bends[1].bounds); + } + }; +} + +export default mxElbowEdgeHandler; diff --git a/src/js/handler/mxGraphHandler.js b/src/js/handler/mxGraphHandler.js index 69be2cd2f..627cdec92 100644 --- a/src/js/handler/mxGraphHandler.js +++ b/src/js/handler/mxGraphHandler.js @@ -2,1864 +2,1620 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * Class: mxGraphHandler - * - * Graph event handler that handles selection. Individual cells are handled - * separately using or one of the edge handlers. These - * handlers are created using in - * . - * - * To avoid the container to scroll a moved cell into view, set - * to false. - * - * Constructor: mxGraphHandler - * - * Constructs an event handler that creates handles for the - * selection cells. - * - * Parameters: - * - * graph - Reference to the enclosing . - */ -function mxGraphHandler(graph) -{ - this.graph = graph; - this.graph.addMouseListener(this); - - // Repaints the handler after autoscroll - this.panHandler = mxUtils.bind(this, ()=> - { - if (!this.suspended) - { - this.updatePreview(); - this.updateHint(); - } - }); - - this.graph.addListener(mxEvent.PAN, this.panHandler); - - // Handles escape keystrokes - this.escapeHandler = mxUtils.bind(this, (sender, evt)=> - { - this.reset(); - }); - - this.graph.addListener(mxEvent.ESCAPE, this.escapeHandler); - - // Updates the preview box for remote changes - this.refreshHandler = mxUtils.bind(this, (sender, evt)=> - { - // Merges multiple pending calls - if (this.refreshThread) - { - window.clearTimeout(this.refreshThread); - } - // Waits for the states and handlers to be updated - this.refreshThread = window.setTimeout(mxUtils.bind(this, ()=> - { - this.refreshThread = null; - - if (this.first != null && !this.suspended) - { - // Updates preview with no translate to compute bounding box - var dx = this.currentDx; - var dy = this.currentDy; - this.currentDx = 0; - this.currentDy = 0; +class mxGraphHandler { + /** + * Variable: graph + * + * Reference to the enclosing . + */ + graph = null; + + /** + * Variable: maxCells + * + * Defines the maximum number of cells to paint subhandles + * for. Default is 50 for Firefox and 20 for IE. Set this + * to 0 if you want an unlimited number of handles to be + * displayed. This is only recommended if the number of + * cells in the graph is limited to a small number, eg. + * 500. + */ + maxCells = 50; + + /** + * Variable: enabled + * + * Specifies if events are handled. Default is true. + */ + enabled = true; + + /** + * Variable: highlightEnabled + * + * Specifies if drop targets under the mouse should be enabled. Default is + * true. + */ + highlightEnabled = true; + + /** + * Variable: cloneEnabled + * + * Specifies if cloning by control-drag is enabled. Default is true. + */ + cloneEnabled = true; + + /** + * Variable: moveEnabled + * + * Specifies if moving is enabled. Default is true. + */ + moveEnabled = true; + + /** + * Variable: guidesEnabled + * + * Specifies if other cells should be used for snapping the right, center or + * left side of the current selection. Default is false. + */ + guidesEnabled = false; + + /** + * Variable: handlesVisible + * + * Whether the handles of the selection are currently visible. + */ + handlesVisible = true; + + /** + * Variable: guide + * + * Holds the instance that is used for alignment. + */ + guide = null; + + /** + * Variable: currentDx + * + * Stores the x-coordinate of the current mouse move. + */ + currentDx = null; + + /** + * Variable: currentDy + * + * Stores the y-coordinate of the current mouse move. + */ + currentDy = null; + + /** + * Variable: updateCursor + * + * Specifies if a move cursor should be shown if the mouse is over a movable + * cell. Default is true. + */ + updateCursor = true; + + /** + * Variable: selectEnabled + * + * Specifies if selecting is enabled. Default is true. + */ + selectEnabled = true; + + /** + * Variable: removeCellsFromParent + * + * Specifies if cells may be moved out of their parents. Default is true. + */ + removeCellsFromParent = true; + + /** + * Variable: removeEmptyParents + * + * If empty parents should be removed from the model after all child cells + * have been moved out. Default is true. + */ + removeEmptyParents = false; + + /** + * Variable: connectOnDrop + * + * Specifies if drop events are interpreted as new connections if no other + * drop action is defined. Default is false. + */ + connectOnDrop = false; + + /** + * Variable: scrollOnMove + * + * Specifies if the view should be scrolled so that a moved cell is + * visible. Default is true. + */ + scrollOnMove = true; + + /** + * Variable: minimumSize + * + * Specifies the minimum number of pixels for the width and height of a + * selection border. Default is 6. + */ + minimumSize = 6; + + /** + * Variable: previewColor + * + * Specifies the color of the preview shape. Default is black. + */ + previewColor = 'black'; + + /** + * Variable: htmlPreview + * + * Specifies if the graph container should be used for preview. If this is used + * then drop target detection relies entirely on because + * the HTML preview does not "let events through". Default is false. + */ + htmlPreview = false; + + /** + * Variable: shape + * + * Reference to the that represents the preview. + */ + shape = null; + + /** + * Variable: scaleGrid + * + * Specifies if the grid should be scaled. Default is false. + */ + scaleGrid = false; + + /** + * Variable: rotationEnabled + * + * Specifies if the bounding box should allow for rotation. Default is true. + */ + rotationEnabled = true; + + /** + * Variable: maxLivePreview + * + * Maximum number of cells for which live preview should be used. Default is 0 + * which means no live preview. + */ + maxLivePreview = 0; + + /** + * Variable: allowLivePreview + * + * If live preview is allowed on this system. Default is true for systems with + * SVG support. + */ + allowLivePreview = mxClient.IS_SVG; + + /** + * Class: mxGraphHandler + * + * Graph event handler that handles selection. Individual cells are handled + * separately using or one of the edge handlers. These + * handlers are created using in + * . + * + * To avoid the container to scroll a moved cell into view, set + * to false. + * + * Constructor: mxGraphHandler + * + * Constructs an event handler that creates handles for the + * selection cells. + * + * Parameters: + * + * graph - Reference to the enclosing . + */ + constructor(graph) { + this.graph = graph; + this.graph.addMouseListener(this); + + // Repaints the handler after autoscroll + this.panHandler = mxUtils.bind(this, () => { + if (!this.suspended) { this.updatePreview(); - this.bounds = this.graph.getView().getBounds(this.cells); - this.pBounds = this.getPreviewBounds(this.cells); - - if (this.pBounds == null && !this.livePreviewUsed) - { - this.reset(); - } - else - { - // Restores translate and updates preview - this.currentDx = dx; - this.currentDy = dy; - this.updatePreview(); - this.updateHint(); - - if (this.livePreviewUsed) - { - // Forces update to ignore last visible state - this.setHandlesVisibleForCells( - this.graph.selectionCellsHandler. - getHandledSelectionCells(), false, true); - this.updatePreview(); - } - } + this.updateHint(); } - }), 0); - }); - - this.graph.getModel().addListener(mxEvent.CHANGE, this.refreshHandler); - this.graph.addListener(mxEvent.REFRESH, this.refreshHandler); - - this.keyHandler = mxUtils.bind(this, (e)=> - { - if (this.graph.container != null && this.graph.container.style.visibility != 'hidden' && - this.first != null && !this.suspended) - { - var clone = this.graph.isCloneEvent(e) && - this.graph.isCellsCloneable() && - this.isCloneEnabled(); - - if (clone != this.cloning) - { - this.cloning = clone; - this.checkPreview(); - this.updatePreview(); - } - } - }); - - mxEvent.addListener(document, 'keydown', this.keyHandler); - mxEvent.addListener(document, 'keyup', this.keyHandler); -}; - -/** - * Variable: graph - * - * Reference to the enclosing . - */ -graph = null; - -/** - * Variable: maxCells - * - * Defines the maximum number of cells to paint subhandles - * for. Default is 50 for Firefox and 20 for IE. Set this - * to 0 if you want an unlimited number of handles to be - * displayed. This is only recommended if the number of - * cells in the graph is limited to a small number, eg. - * 500. - */ -maxCells = 50; - -/** - * Variable: enabled - * - * Specifies if events are handled. Default is true. - */ -enabled = true; - -/** - * Variable: highlightEnabled - * - * Specifies if drop targets under the mouse should be enabled. Default is - * true. - */ -highlightEnabled = true; - -/** - * Variable: cloneEnabled - * - * Specifies if cloning by control-drag is enabled. Default is true. - */ -cloneEnabled = true; - -/** - * Variable: moveEnabled - * - * Specifies if moving is enabled. Default is true. - */ -moveEnabled = true; - -/** - * Variable: guidesEnabled - * - * Specifies if other cells should be used for snapping the right, center or - * left side of the current selection. Default is false. - */ -guidesEnabled = false; - -/** - * Variable: handlesVisible - * - * Whether the handles of the selection are currently visible. - */ -handlesVisible = true; - -/** - * Variable: guide - * - * Holds the instance that is used for alignment. - */ -guide = null; - -/** - * Variable: currentDx - * - * Stores the x-coordinate of the current mouse move. - */ -currentDx = null; - -/** - * Variable: currentDy - * - * Stores the y-coordinate of the current mouse move. - */ -currentDy = null; - -/** - * Variable: updateCursor - * - * Specifies if a move cursor should be shown if the mouse is over a movable - * cell. Default is true. - */ -updateCursor = true; - -/** - * Variable: selectEnabled - * - * Specifies if selecting is enabled. Default is true. - */ -selectEnabled = true; - -/** - * Variable: removeCellsFromParent - * - * Specifies if cells may be moved out of their parents. Default is true. - */ -removeCellsFromParent = true; - -/** - * Variable: removeEmptyParents - * - * If empty parents should be removed from the model after all child cells - * have been moved out. Default is true. - */ -removeEmptyParents = false; - -/** - * Variable: connectOnDrop - * - * Specifies if drop events are interpreted as new connections if no other - * drop action is defined. Default is false. - */ -connectOnDrop = false; - -/** - * Variable: scrollOnMove - * - * Specifies if the view should be scrolled so that a moved cell is - * visible. Default is true. - */ -scrollOnMove = true; - -/** - * Variable: minimumSize - * - * Specifies the minimum number of pixels for the width and height of a - * selection border. Default is 6. - */ -minimumSize = 6; - -/** - * Variable: previewColor - * - * Specifies the color of the preview shape. Default is black. - */ -previewColor = 'black'; - -/** - * Variable: htmlPreview - * - * Specifies if the graph container should be used for preview. If this is used - * then drop target detection relies entirely on because - * the HTML preview does not "let events through". Default is false. - */ -htmlPreview = false; - -/** - * Variable: shape - * - * Reference to the that represents the preview. - */ -shape = null; - -/** - * Variable: scaleGrid - * - * Specifies if the grid should be scaled. Default is false. - */ -scaleGrid = false; - -/** - * Variable: rotationEnabled - * - * Specifies if the bounding box should allow for rotation. Default is true. - */ -rotationEnabled = true; - -/** - * Variable: maxLivePreview - * - * Maximum number of cells for which live preview should be used. Default is 0 - * which means no live preview. - */ -maxLivePreview = 0; - -/** - * Variable: allowLivePreview - * - * If live preview is allowed on this system. Default is true for systems with - * SVG support. - */ -allowLivePreview = mxClient.IS_SVG; - -/** - * Function: isEnabled - * - * Returns . - */ -isEnabled = ()=> -{ - return this.enabled; -}; - -/** - * Function: setEnabled - * - * Sets . - */ -setEnabled = (value)=> -{ - this.enabled = value; -}; - -/** - * Function: isCloneEnabled - * - * Returns . - */ -isCloneEnabled = ()=> -{ - return this.cloneEnabled; -}; - -/** - * Function: setCloneEnabled - * - * Sets . - * - * Parameters: - * - * value - Boolean that specifies the new clone enabled state. - */ -setCloneEnabled = (value)=> -{ - this.cloneEnabled = value; -}; - -/** - * Function: isMoveEnabled - * - * Returns . - */ -isMoveEnabled = ()=> -{ - return this.moveEnabled; -}; - -/** - * Function: setMoveEnabled - * - * Sets . - */ -setMoveEnabled = (value)=> -{ - this.moveEnabled = value; -}; - -/** - * Function: isSelectEnabled - * - * Returns . - */ -isSelectEnabled = ()=> -{ - return this.selectEnabled; -}; - -/** - * Function: setSelectEnabled - * - * Sets . - */ -setSelectEnabled = (value)=> -{ - this.selectEnabled = value; -}; - -/** - * Function: isRemoveCellsFromParent - * - * Returns . - */ -isRemoveCellsFromParent = ()=> -{ - return this.removeCellsFromParent; -}; - -/** - * Function: setRemoveCellsFromParent - * - * Sets . - */ -setRemoveCellsFromParent = (value)=> -{ - this.removeCellsFromParent = value; -}; - -/** - * Function: isPropagateSelectionCell - * - * Returns true if the given cell and parent should propagate - * selection state to the parent. - */ -isPropagateSelectionCell = (cell, immediate, me)=> -{ - var parent = this.graph.model.getParent(cell); - - if (immediate) - { - var geo = (this.graph.model.isEdge(cell)) ? null : - this.graph.getCellGeometry(cell); - - return !this.graph.isSiblingSelected(cell) && - ((geo != null && geo.relative) || - !this.graph.isSwimlane(parent)); - } - else - { - return (!this.graph.isToggleEvent(me.getEvent()) || - (!this.graph.isSiblingSelected(cell) && - !this.graph.isCellSelected(cell) && - (!this.graph.isSwimlane(parent)) || - this.graph.isCellSelected(parent))) && - (this.graph.isToggleEvent(me.getEvent()) || - !this.graph.isCellSelected(parent)); - } -}; - -/** - * Function: getInitialCellForEvent - * - * Hook to return initial cell for the given event. This returns - * the topmost cell that is not a swimlane or is selected. - */ -getInitialCellForEvent = (me)=> -{ - var state = me.getState(); - - if ((!this.graph.isToggleEvent(me.getEvent()) || !mxEvent.isAltDown(me.getEvent())) && - state != null && !this.graph.isCellSelected(state.cell)) - { - var model = this.graph.model; - var next = this.graph.view.getState(model.getParent(state.cell)); - - while (next != null && !this.graph.isCellSelected(next.cell) && - (model.isVertex(next.cell) || model.isEdge(next.cell)) && - this.isPropagateSelectionCell(state.cell, true, me)) - { - state = next; - next = this.graph.view.getState(this.graph.getModel().getParent(state.cell)); - } - } - - return (state != null) ? state.cell : null; -}; - -/** - * Function: isDelayedSelection - * - * Returns true if the cell or one of its ancestors is selected. - */ -isDelayedSelection = (cell, me)=> -{ - if (!this.graph.isToggleEvent(me.getEvent()) || !mxEvent.isAltDown(me.getEvent())) - { - while (cell != null) - { - if (this.graph.selectionCellsHandler.isHandled(cell)) - { - return this.graph.cellEditor.getEditingCell() != cell; - } - - cell = this.graph.model.getParent(cell); - } - } - - return this.graph.isToggleEvent(me.getEvent()) && !mxEvent.isAltDown(me.getEvent()); -}; - -/** - * Function: selectDelayed - * - * Implements the delayed selection for the given mouse event. - */ -selectDelayed = (me)=> -{ - if (!this.graph.popupMenuHandler.isPopupTrigger(me)) - { - var cell = me.getCell(); - - if (cell == null) - { - cell = this.cell; - } - - this.selectCellForEvent(cell, me); - } -}; - -/** - * Function: selectCellForEvent - * - * Selects the given cell for the given . - */ -selectCellForEvent = (cell, me)=> -{ - var state = this.graph.view.getState(cell); - - if (state != null) - { - if (me.isSource(state.control)) - { - this.graph.selectCellForEvent(cell, me.getEvent()); - } - else - { - if (!this.graph.isToggleEvent(me.getEvent()) || - !mxEvent.isAltDown(me.getEvent())) - { - var model = this.graph.getModel(); - var parent = model.getParent(cell); - - while (this.graph.view.getState(parent) != null && - (model.isVertex(parent) || model.isEdge(parent)) && - this.isPropagateSelectionCell(cell, false, me)) - { - cell = parent; - parent = model.getParent(cell); - } - } - - this.graph.selectCellForEvent(cell, me.getEvent()); - } - } - - return cell; -}; - -/** - * Function: consumeMouseEvent - * - * Consumes the given mouse event. NOTE: This may be used to enable click - * events for links in labels on iOS as follows as consuming the initial - * touchStart disables firing the subsequent click event on the link. - * - * - * consumeMouseEvent = (evtName, me)=> - * { - * var source = mxEvent.getSource(me.getEvent()); - * - * if (!mxEvent.isTouchEvent(me.getEvent()) || source.nodeName != 'A') - * { - * me.consume(); - * } - * } - * - */ -consumeMouseEvent = (evtName, me)=> -{ - me.consume(); -}; - -/** - * Function: mouseDown - * - * Handles the event by selecing the given cell and creating a handle for - * it. By consuming the event all subsequent events of the gesture are - * redirected to this handler. - */ -mouseDown = (sender, me)=> -{ - if (!me.isConsumed() && this.isEnabled() && this.graph.isEnabled() && - me.getState() != null && !mxEvent.isMultiTouchEvent(me.getEvent())) - { - var cell = this.getInitialCellForEvent(me); - this.delayedSelection = this.isDelayedSelection(cell, me); - this.cell = null; - - if (this.isSelectEnabled() && !this.delayedSelection) - { - this.graph.selectCellForEvent(cell, me.getEvent()); - } - - if (this.isMoveEnabled()) - { - var model = this.graph.model; - var geo = model.getGeometry(cell); - - if (this.graph.isCellMovable(cell) && ((!model.isEdge(cell) || this.graph.getSelectionCount() > 1 || - (geo.points != null && geo.points.length > 0) || model.getTerminal(cell, true) == null || - model.getTerminal(cell, false) == null) || this.graph.allowDanglingEdges || - (this.graph.isCloneEvent(me.getEvent()) && this.graph.isCellsCloneable()))) - { - this.start(cell, me.getX(), me.getY()); - } - else if (this.delayedSelection) - { - this.cell = cell; - } - - this.cellWasClicked = true; - this.consumeMouseEvent(mxEvent.MOUSE_DOWN, me); - } - } -}; - -/** - * Function: getGuideStates - * - * Creates an array of cell states which should be used as guides. - */ -getGuideStates = ()=> -{ - var parent = this.graph.getDefaultParent(); - var model = this.graph.getModel(); - - var filter = mxUtils.bind(this, (cell)=> - { - return this.graph.view.getState(cell) != null && - model.isVertex(cell) && - model.getGeometry(cell) != null && - !model.getGeometry(cell).relative; - }); - - return this.graph.view.getCellStates(model.filterDescendants(filter, parent)); -}; - -/** - * Function: getCells - * - * Returns the cells to be modified by this handler. This implementation - * returns all selection cells that are movable, or the given initial cell if - * the given cell is not selected and movable. This handles the case of moving - * unselectable or unselected cells. - * - * Parameters: - * - * initialCell - that triggered this handler. - */ -getCells = (initialCell)=> -{ - if (!this.delayedSelection && this.graph.isCellMovable(initialCell)) - { - return [initialCell]; - } - else - { - return this.graph.getMovableCells(this.graph.getSelectionCells()); - } -}; - -/** - * Function: getPreviewBounds - * - * Returns the used as the preview bounds for - * moving the given cells. - */ -getPreviewBounds = (cells)=> -{ - var bounds = this.getBoundingBox(cells); - - if (bounds != null) - { - // Corrects width and height - bounds.width = Math.max(0, bounds.width - 1); - bounds.height = Math.max(0, bounds.height - 1); - - if (bounds.width < this.minimumSize) - { - var dx = this.minimumSize - bounds.width; - bounds.x -= dx / 2; - bounds.width = this.minimumSize; - } - else - { - bounds.x = Math.round(bounds.x); - bounds.width = Math.ceil(bounds.width); - } - - var tr = this.graph.view.translate; - var s = this.graph.view.scale; - - if (bounds.height < this.minimumSize) - { - var dy = this.minimumSize - bounds.height; - bounds.y -= dy / 2; - bounds.height = this.minimumSize; - } - else - { - bounds.y = Math.round(bounds.y); - bounds.height = Math.ceil(bounds.height); - } - } - - return bounds; -}; - -/** - * Function: getBoundingBox - * - * Returns the union of the for the given array of . - * For vertices, this method uses the bounding box of the corresponding shape - * if one exists. The bounding box of the corresponding text label and all - * controls and overlays are ignored. See also: and - * . - * - * Parameters: - * - * cells - Array of whose bounding box should be returned. - */ -getBoundingBox = (cells)=> -{ - var result = null; - - if (cells != null && cells.length > 0) - { - var model = this.graph.getModel(); - - for (var i = 0; i < cells.length; i++) - { - if (model.isVertex(cells[i]) || model.isEdge(cells[i])) - { - var state = this.graph.view.getState(cells[i]); - - if (state != null) - { - var bbox = state; - - if (model.isVertex(cells[i]) && state.shape != null && state.shape.boundingBox != null) - { - bbox = state.shape.boundingBox; - } - - if (result == null) - { - result = mxRectangle.fromRectangle(bbox); - } - else - { - result.add(bbox); - } - } - } - } - } - - return result; -}; - -/** - * Function: createPreviewShape - * - * Creates the shape used to draw the preview for the given bounds. - */ -createPreviewShape = (bounds)=> -{ - var shape = new mxRectangleShape(bounds, null, this.previewColor); - shape.isDashed = true; - - if (this.htmlPreview) - { - shape.dialect = mxConstants.DIALECT_STRICTHTML; - shape.init(this.graph.container); - } - else - { - // Makes sure to use either VML or SVG shapes in order to implement - // event-transparency on the background area of the rectangle since - // HTML shapes do not let mouseevents through even when transparent - shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? - mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG; - shape.init(this.graph.getView().getOverlayPane()); - shape.pointerEvents = false; - - // Workaround for artifacts on iOS - if (mxClient.IS_IOS) - { - shape.getSvgScreenOffset = ()=> - { - return 0; - }; - } - } - - return shape; -}; - -/** - * Function: start - * - * Starts the handling of the mouse gesture. - */ -start = (cell, x, y, cells)=> -{ - this.cell = cell; - this.first = mxUtils.convertPoint(this.graph.container, x, y); - this.cells = (cells != null) ? cells : this.getCells(this.cell); - this.bounds = this.graph.getView().getBounds(this.cells); - this.pBounds = this.getPreviewBounds(this.cells); - this.allCells = new mxDictionary(); - this.cloning = false; - this.cellCount = 0; - - for (var i = 0; i < this.cells.length; i++) - { - this.cellCount += this.addStates(this.cells[i], this.allCells); - } - - if (this.guidesEnabled) - { - this.guide = new mxGuide(this.graph, this.getGuideStates()); - var parent = this.graph.model.getParent(cell); - var ignore = this.graph.model.getChildCount(parent) < 2; - - // Uses connected states as guides - var connected = new mxDictionary(); - var opps = this.graph.getOpposites(this.graph.getEdges(this.cell), this.cell); - - for (var i = 0; i < opps.length; i++) - { - var state = this.graph.view.getState(opps[i]); - - if (state != null && !connected.get(state)) - { - connected.put(state, true); - } - } - - this.guide.isStateIgnored = mxUtils.bind(this, (state)=> - { - var p = this.graph.model.getParent(state.cell); - - return state.cell != null && ((!this.cloning && - this.isCellMoving(state.cell)) || - (state.cell != (this.target || parent) && !ignore && - !connected.get(state) && - (this.target == null || this.graph.model.getChildCount( - this.target) >= 2) && p != (this.target || parent))); }); - } -}; -/** - * Function: addStates - * - * Adds the states for the given cell recursively to the given dictionary. - */ -addStates = (cell, dict)=> -{ - var state = this.graph.view.getState(cell); - var count = 0; - - if (state != null && dict.get(cell) == null) - { - dict.put(cell, state); - count++; - - var childCount = this.graph.model.getChildCount(cell); - - for (var i = 0; i < childCount; i++) - { - count += this.addStates(this.graph.model.getChildAt(cell, i), dict); - } - } - - return count; -}; + this.graph.addListener(mxEvent.PAN, this.panHandler); -/** - * Function: isCellMoving - * - * Returns true if the given cell is currently being moved. - */ -isCellMoving = (cell)=> -{ - return this.allCells.get(cell) != null; -}; - -/** - * Function: useGuidesForEvent - * - * Returns true if the guides should be used for the given . - * This implementation returns . - */ -useGuidesForEvent = (me)=> -{ - return (this.guide != null) ? this.guide.isEnabledForEvent(me.getEvent()) && - !this.graph.isConstrainedEvent(me.getEvent()) : true; -}; - - -/** - * Function: snap - * - * Snaps the given vector to the grid and returns the given mxPoint instance. - */ -snap = (vector)=> -{ - var scale = (this.scaleGrid) ? this.graph.view.scale : 1; - - vector.x = this.graph.snap(vector.x / scale) * scale; - vector.y = this.graph.snap(vector.y / scale) * scale; - - return vector; -}; - -/** - * Function: getDelta - * - * Returns an that represents the vector for moving the cells - * for the given . - */ -getDelta = (me)=> -{ - var point = mxUtils.convertPoint(this.graph.container, me.getX(), me.getY()); - - return new mxPoint(point.x - this.first.x - this.graph.panDx, - point.y - this.first.y - this.graph.panDy); -}; - -/** - * Function: updateHint - * - * Hook for subclassers do show details while the handler is active. - */ -updateHint = (me)=> { }; - -/** - * Function: removeHint - * - * Hooks for subclassers to hide details when the handler gets inactive. - */ -removeHint = ()=> { }; - -/** - * Function: roundLength - * - * Hook for rounding the unscaled vector. Allows for half steps in the raster so - * numbers coming in should be rounded if no half steps are allowed (ie for non - * aligned standard moving where pixel steps should be preferred). - */ -roundLength = (length)=> -{ - return Math.round(length * 100) / 100; -}; - -/** - * Function: isValidDropTarget - * - * Returns true if the given cell is a valid drop target. - */ -isValidDropTarget = (target, me)=> -{ - return this.graph.model.getParent(this.cell) != target; -}; - -/** - * Function: checkPreview - * - * Updates the preview if cloning state has changed. - */ -checkPreview = ()=> -{ - if (this.livePreviewActive && this.cloning) - { - this.resetLivePreview(); - this.livePreviewActive = false; - } - else if (this.maxLivePreview >= this.cellCount && !this.livePreviewActive && this.allowLivePreview) - { - if (!this.cloning || !this.livePreviewActive) - { - this.livePreviewActive = true; - this.livePreviewUsed = true; - } - } - else if (!this.livePreviewUsed && this.shape == null) - { - this.shape = this.createPreviewShape(this.bounds); - } -}; - -/** - * Function: mouseMove - * - * Handles the event by highlighting possible drop targets and updating the - * preview. - */ -mouseMove = (sender, me)=> -{ - var graph = this.graph; - - if (!me.isConsumed() && graph.isMouseDown && this.cell != null && - this.first != null && this.bounds != null && !this.suspended) - { - // Stops moving if a multi touch event is received - if (mxEvent.isMultiTouchEvent(me.getEvent())) - { + // Handles escape keystrokes + this.escapeHandler = mxUtils.bind(this, (sender, evt) => { this.reset(); - return; + }); + + this.graph.addListener(mxEvent.ESCAPE, this.escapeHandler); + + // Updates the preview box for remote changes + this.refreshHandler = mxUtils.bind(this, (sender, evt) => { + // Merges multiple pending calls + if (this.refreshThread) { + window.clearTimeout(this.refreshThread); + } + + // Waits for the states and handlers to be updated + this.refreshThread = window.setTimeout(mxUtils.bind(this, () => { + this.refreshThread = null; + + if (this.first != null && !this.suspended) { + // Updates preview with no translate to compute bounding box + var dx = this.currentDx; + var dy = this.currentDy; + this.currentDx = 0; + this.currentDy = 0; + this.updatePreview(); + this.bounds = this.graph.getView().getBounds(this.cells); + this.pBounds = this.getPreviewBounds(this.cells); + + if (this.pBounds == null && !this.livePreviewUsed) { + this.reset(); + } else { + // Restores translate and updates preview + this.currentDx = dx; + this.currentDy = dy; + this.updatePreview(); + this.updateHint(); + + if (this.livePreviewUsed) { + // Forces update to ignore last visible state + this.setHandlesVisibleForCells( + this.graph.selectionCellsHandler.getHandledSelectionCells(), false, true); + this.updatePreview(); + } + } + } + }), 0); + }); + + this.graph.getModel().addListener(mxEvent.CHANGE, this.refreshHandler); + this.graph.addListener(mxEvent.REFRESH, this.refreshHandler); + + this.keyHandler = mxUtils.bind(this, (e) => { + if (this.graph.container != null && this.graph.container.style.visibility != 'hidden' && + this.first != null && !this.suspended) { + var clone = this.graph.isCloneEvent(e) && + this.graph.isCellsCloneable() && + this.isCloneEnabled(); + + if (clone != this.cloning) { + this.cloning = clone; + this.checkPreview(); + this.updatePreview(); + } + } + }); + + mxEvent.addListener(document, 'keydown', this.keyHandler); + mxEvent.addListener(document, 'keyup', this.keyHandler); + }; + + /** + * Function: isEnabled + * + * Returns . + */ + isEnabled = () => { + return this.enabled; + }; + + /** + * Function: setEnabled + * + * Sets . + */ + setEnabled = (value) => { + this.enabled = value; + }; + + /** + * Function: isCloneEnabled + * + * Returns . + */ + isCloneEnabled = () => { + return this.cloneEnabled; + }; + + /** + * Function: setCloneEnabled + * + * Sets . + * + * Parameters: + * + * value - Boolean that specifies the new clone enabled state. + */ + setCloneEnabled = (value) => { + this.cloneEnabled = value; + }; + + /** + * Function: isMoveEnabled + * + * Returns . + */ + isMoveEnabled = () => { + return this.moveEnabled; + }; + + /** + * Function: setMoveEnabled + * + * Sets . + */ + setMoveEnabled = (value) => { + this.moveEnabled = value; + }; + + /** + * Function: isSelectEnabled + * + * Returns . + */ + isSelectEnabled = () => { + return this.selectEnabled; + }; + + /** + * Function: setSelectEnabled + * + * Sets . + */ + setSelectEnabled = (value) => { + this.selectEnabled = value; + }; + + /** + * Function: isRemoveCellsFromParent + * + * Returns . + */ + isRemoveCellsFromParent = () => { + return this.removeCellsFromParent; + }; + + /** + * Function: setRemoveCellsFromParent + * + * Sets . + */ + setRemoveCellsFromParent = (value) => { + this.removeCellsFromParent = value; + }; + + /** + * Function: isPropagateSelectionCell + * + * Returns true if the given cell and parent should propagate + * selection state to the parent. + */ + isPropagateSelectionCell = (cell, immediate, me) => { + var parent = this.graph.model.getParent(cell); + + if (immediate) { + var geo = (this.graph.model.isEdge(cell)) ? null : + this.graph.getCellGeometry(cell); + + return !this.graph.isSiblingSelected(cell) && + ((geo != null && geo.relative) || + !this.graph.isSwimlane(parent)); + } else { + return (!this.graph.isToggleEvent(me.getEvent()) || + (!this.graph.isSiblingSelected(cell) && + !this.graph.isCellSelected(cell) && + (!this.graph.isSwimlane(parent)) || + this.graph.isCellSelected(parent))) && + (this.graph.isToggleEvent(me.getEvent()) || + !this.graph.isCellSelected(parent)); } - - var delta = this.getDelta(me); - var tol = graph.tolerance; + }; - if (this.shape != null || this.livePreviewActive || Math.abs(delta.x) > tol || Math.abs(delta.y) > tol) - { - // Highlight is used for highlighting drop targets - if (this.highlight == null) - { - this.highlight = new mxCellHighlight(this.graph, - mxConstants.DROP_TARGET_COLOR, 3); + /** + * Function: getInitialCellForEvent + * + * Hook to return initial cell for the given event. This returns + * the topmost cell that is not a swimlane or is selected. + */ + getInitialCellForEvent = (me) => { + var state = me.getState(); + + if ((!this.graph.isToggleEvent(me.getEvent()) || !mxEvent.isAltDown(me.getEvent())) && + state != null && !this.graph.isCellSelected(state.cell)) { + var model = this.graph.model; + var next = this.graph.view.getState(model.getParent(state.cell)); + + while (next != null && !this.graph.isCellSelected(next.cell) && + (model.isVertex(next.cell) || model.isEdge(next.cell)) && + this.isPropagateSelectionCell(state.cell, true, me)) { + state = next; + next = this.graph.view.getState(this.graph.getModel().getParent(state.cell)); } + } - var clone = graph.isCloneEvent(me.getEvent()) && graph.isCellsCloneable() && this.isCloneEnabled(); - var gridEnabled = graph.isGridEnabledEvent(me.getEvent()); + return (state != null) ? state.cell : null; + }; + + /** + * Function: isDelayedSelection + * + * Returns true if the cell or one of its ancestors is selected. + */ + isDelayedSelection = (cell, me) => { + if (!this.graph.isToggleEvent(me.getEvent()) || !mxEvent.isAltDown(me.getEvent())) { + while (cell != null) { + if (this.graph.selectionCellsHandler.isHandled(cell)) { + return this.graph.cellEditor.getEditingCell() != cell; + } + + cell = this.graph.model.getParent(cell); + } + } + + return this.graph.isToggleEvent(me.getEvent()) && !mxEvent.isAltDown(me.getEvent()); + }; + + /** + * Function: selectDelayed + * + * Implements the delayed selection for the given mouse event. + */ + selectDelayed = (me) => { + if (!this.graph.popupMenuHandler.isPopupTrigger(me)) { var cell = me.getCell(); - var hideGuide = true; - var target = null; - this.cloning = clone; - - if (graph.isDropEnabled() && this.highlightEnabled) - { - // Contains a call to getCellAt to find the cell under the mouse - target = graph.getDropTarget(this.cells, me.getEvent(), cell, clone); + + if (cell == null) { + cell = this.cell; } - var state = graph.getView().getState(target); - var highlight = false; - - if (state != null && (clone || this.isValidDropTarget(target, me))) - { - if (this.target != target) - { + this.selectCellForEvent(cell, me); + } + }; + + /** + * Function: selectCellForEvent + * + * Selects the given cell for the given . + */ + selectCellForEvent = (cell, me) => { + var state = this.graph.view.getState(cell); + + if (state != null) { + if (me.isSource(state.control)) { + this.graph.selectCellForEvent(cell, me.getEvent()); + } else { + if (!this.graph.isToggleEvent(me.getEvent()) || + !mxEvent.isAltDown(me.getEvent())) { + var model = this.graph.getModel(); + var parent = model.getParent(cell); + + while (this.graph.view.getState(parent) != null && + (model.isVertex(parent) || model.isEdge(parent)) && + this.isPropagateSelectionCell(cell, false, me)) { + cell = parent; + parent = model.getParent(cell); + } + } + + this.graph.selectCellForEvent(cell, me.getEvent()); + } + } + + return cell; + }; + + /** + * Function: consumeMouseEvent + * + * Consumes the given mouse event. NOTE: This may be used to enable click + * events for links in labels on iOS as follows as consuming the initial + * touchStart disables firing the subsequent click event on the link. + * + * + * consumeMouseEvent = (evtName, me)=> + * { + * var source = mxEvent.getSource(me.getEvent()); + * + * if (!mxEvent.isTouchEvent(me.getEvent()) || source.nodeName != 'A') + * { + * me.consume(); + * } + * } + * + */ + consumeMouseEvent = (evtName, me) => { + me.consume(); + }; + + /** + * Function: mouseDown + * + * Handles the event by selecing the given cell and creating a handle for + * it. By consuming the event all subsequent events of the gesture are + * redirected to this handler. + */ + mouseDown = (sender, me) => { + if (!me.isConsumed() && this.isEnabled() && this.graph.isEnabled() && + me.getState() != null && !mxEvent.isMultiTouchEvent(me.getEvent())) { + var cell = this.getInitialCellForEvent(me); + this.delayedSelection = this.isDelayedSelection(cell, me); + this.cell = null; + + if (this.isSelectEnabled() && !this.delayedSelection) { + this.graph.selectCellForEvent(cell, me.getEvent()); + } + + if (this.isMoveEnabled()) { + var model = this.graph.model; + var geo = model.getGeometry(cell); + + if (this.graph.isCellMovable(cell) && ((!model.isEdge(cell) || this.graph.getSelectionCount() > 1 || + (geo.points != null && geo.points.length > 0) || model.getTerminal(cell, true) == null || + model.getTerminal(cell, false) == null) || this.graph.allowDanglingEdges || + (this.graph.isCloneEvent(me.getEvent()) && this.graph.isCellsCloneable()))) { + this.start(cell, me.getX(), me.getY()); + } else if (this.delayedSelection) { + this.cell = cell; + } + + this.cellWasClicked = true; + this.consumeMouseEvent(mxEvent.MOUSE_DOWN, me); + } + } + }; + + /** + * Function: getGuideStates + * + * Creates an array of cell states which should be used as guides. + */ + getGuideStates = () => { + var parent = this.graph.getDefaultParent(); + var model = this.graph.getModel(); + + var filter = mxUtils.bind(this, (cell) => { + return this.graph.view.getState(cell) != null && + model.isVertex(cell) && + model.getGeometry(cell) != null && + !model.getGeometry(cell).relative; + }); + + return this.graph.view.getCellStates(model.filterDescendants(filter, parent)); + }; + + /** + * Function: getCells + * + * Returns the cells to be modified by this handler. This implementation + * returns all selection cells that are movable, or the given initial cell if + * the given cell is not selected and movable. This handles the case of moving + * unselectable or unselected cells. + * + * Parameters: + * + * initialCell - that triggered this handler. + */ + getCells = (initialCell) => { + if (!this.delayedSelection && this.graph.isCellMovable(initialCell)) { + return [initialCell]; + } else { + return this.graph.getMovableCells(this.graph.getSelectionCells()); + } + }; + + /** + * Function: getPreviewBounds + * + * Returns the used as the preview bounds for + * moving the given cells. + */ + getPreviewBounds = (cells) => { + var bounds = this.getBoundingBox(cells); + + if (bounds != null) { + // Corrects width and height + bounds.width = Math.max(0, bounds.width - 1); + bounds.height = Math.max(0, bounds.height - 1); + + if (bounds.width < this.minimumSize) { + var dx = this.minimumSize - bounds.width; + bounds.x -= dx / 2; + bounds.width = this.minimumSize; + } else { + bounds.x = Math.round(bounds.x); + bounds.width = Math.ceil(bounds.width); + } + + var tr = this.graph.view.translate; + var s = this.graph.view.scale; + + if (bounds.height < this.minimumSize) { + var dy = this.minimumSize - bounds.height; + bounds.y -= dy / 2; + bounds.height = this.minimumSize; + } else { + bounds.y = Math.round(bounds.y); + bounds.height = Math.ceil(bounds.height); + } + } + + return bounds; + }; + + /** + * Function: getBoundingBox + * + * Returns the union of the for the given array of . + * For vertices, this method uses the bounding box of the corresponding shape + * if one exists. The bounding box of the corresponding text label and all + * controls and overlays are ignored. See also: and + * . + * + * Parameters: + * + * cells - Array of whose bounding box should be returned. + */ + getBoundingBox = (cells) => { + var result = null; + + if (cells != null && cells.length > 0) { + var model = this.graph.getModel(); + + for (var i = 0; i < cells.length; i++) { + if (model.isVertex(cells[i]) || model.isEdge(cells[i])) { + var state = this.graph.view.getState(cells[i]); + + if (state != null) { + var bbox = state; + + if (model.isVertex(cells[i]) && state.shape != null && state.shape.boundingBox != null) { + bbox = state.shape.boundingBox; + } + + if (result == null) { + result = mxRectangle.fromRectangle(bbox); + } else { + result.add(bbox); + } + } + } + } + } + + return result; + }; + + /** + * Function: createPreviewShape + * + * Creates the shape used to draw the preview for the given bounds. + */ + createPreviewShape = (bounds) => { + var shape = new mxRectangleShape(bounds, null, this.previewColor); + shape.isDashed = true; + + if (this.htmlPreview) { + shape.dialect = mxConstants.DIALECT_STRICTHTML; + shape.init(this.graph.container); + } else { + // Makes sure to use either VML or SVG shapes in order to implement + // event-transparency on the background area of the rectangle since + // HTML shapes do not let mouseevents through even when transparent + shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? + mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG; + shape.init(this.graph.getView().getOverlayPane()); + shape.pointerEvents = false; + + // Workaround for artifacts on iOS + if (mxClient.IS_IOS) { + shape.getSvgScreenOffset = () => { + return 0; + }; + } + } + + return shape; + }; + + /** + * Function: start + * + * Starts the handling of the mouse gesture. + */ + start = (cell, x, y, cells) => { + this.cell = cell; + this.first = mxUtils.convertPoint(this.graph.container, x, y); + this.cells = (cells != null) ? cells : this.getCells(this.cell); + this.bounds = this.graph.getView().getBounds(this.cells); + this.pBounds = this.getPreviewBounds(this.cells); + this.allCells = new mxDictionary(); + this.cloning = false; + this.cellCount = 0; + + for (var i = 0; i < this.cells.length; i++) { + this.cellCount += this.addStates(this.cells[i], this.allCells); + } + + if (this.guidesEnabled) { + this.guide = new mxGuide(this.graph, this.getGuideStates()); + var parent = this.graph.model.getParent(cell); + var ignore = this.graph.model.getChildCount(parent) < 2; + + // Uses connected states as guides + var connected = new mxDictionary(); + var opps = this.graph.getOpposites(this.graph.getEdges(this.cell), this.cell); + + for (var i = 0; i < opps.length; i++) { + var state = this.graph.view.getState(opps[i]); + + if (state != null && !connected.get(state)) { + connected.put(state, true); + } + } + + this.guide.isStateIgnored = mxUtils.bind(this, (state) => { + var p = this.graph.model.getParent(state.cell); + + return state.cell != null && ((!this.cloning && + this.isCellMoving(state.cell)) || + (state.cell != (this.target || parent) && !ignore && + !connected.get(state) && + (this.target == null || this.graph.model.getChildCount( + this.target) >= 2) && p != (this.target || parent))); + }); + } + }; + + /** + * Function: addStates + * + * Adds the states for the given cell recursively to the given dictionary. + */ + addStates = (cell, dict) => { + var state = this.graph.view.getState(cell); + var count = 0; + + if (state != null && dict.get(cell) == null) { + dict.put(cell, state); + count++; + + var childCount = this.graph.model.getChildCount(cell); + + for (var i = 0; i < childCount; i++) { + count += this.addStates(this.graph.model.getChildAt(cell, i), dict); + } + } + + return count; + }; + + /** + * Function: isCellMoving + * + * Returns true if the given cell is currently being moved. + */ + isCellMoving = (cell) => { + return this.allCells.get(cell) != null; + }; + + /** + * Function: useGuidesForEvent + * + * Returns true if the guides should be used for the given . + * This implementation returns . + */ + useGuidesForEvent = (me) => { + return (this.guide != null) ? this.guide.isEnabledForEvent(me.getEvent()) && + !this.graph.isConstrainedEvent(me.getEvent()) : true; + }; + + + /** + * Function: snap + * + * Snaps the given vector to the grid and returns the given mxPoint instance. + */ + snap = (vector) => { + var scale = (this.scaleGrid) ? this.graph.view.scale : 1; + + vector.x = this.graph.snap(vector.x / scale) * scale; + vector.y = this.graph.snap(vector.y / scale) * scale; + + return vector; + }; + + /** + * Function: getDelta + * + * Returns an that represents the vector for moving the cells + * for the given . + */ + getDelta = (me) => { + var point = mxUtils.convertPoint(this.graph.container, me.getX(), me.getY()); + + return new mxPoint(point.x - this.first.x - this.graph.panDx, + point.y - this.first.y - this.graph.panDy); + }; + + /** + * Function: updateHint + * + * Hook for subclassers do show details while the handler is active. + */ + updateHint = (me) => { + }; + + /** + * Function: removeHint + * + * Hooks for subclassers to hide details when the handler gets inactive. + */ + removeHint = () => { + }; + + /** + * Function: roundLength + * + * Hook for rounding the unscaled vector. Allows for half steps in the raster so + * numbers coming in should be rounded if no half steps are allowed (ie for non + * aligned standard moving where pixel steps should be preferred). + */ + roundLength = (length) => { + return Math.round(length * 100) / 100; + }; + + /** + * Function: isValidDropTarget + * + * Returns true if the given cell is a valid drop target. + */ + isValidDropTarget = (target, me) => { + return this.graph.model.getParent(this.cell) != target; + }; + + /** + * Function: checkPreview + * + * Updates the preview if cloning state has changed. + */ + checkPreview = () => { + if (this.livePreviewActive && this.cloning) { + this.resetLivePreview(); + this.livePreviewActive = false; + } else if (this.maxLivePreview >= this.cellCount && !this.livePreviewActive && this.allowLivePreview) { + if (!this.cloning || !this.livePreviewActive) { + this.livePreviewActive = true; + this.livePreviewUsed = true; + } + } else if (!this.livePreviewUsed && this.shape == null) { + this.shape = this.createPreviewShape(this.bounds); + } + }; + + /** + * Function: mouseMove + * + * Handles the event by highlighting possible drop targets and updating the + * preview. + */ + mouseMove = (sender, me) => { + var graph = this.graph; + + if (!me.isConsumed() && graph.isMouseDown && this.cell != null && + this.first != null && this.bounds != null && !this.suspended) { + // Stops moving if a multi touch event is received + if (mxEvent.isMultiTouchEvent(me.getEvent())) { + this.reset(); + return; + } + + var delta = this.getDelta(me); + var tol = graph.tolerance; + + if (this.shape != null || this.livePreviewActive || Math.abs(delta.x) > tol || Math.abs(delta.y) > tol) { + // Highlight is used for highlighting drop targets + if (this.highlight == null) { + this.highlight = new mxCellHighlight(this.graph, + mxConstants.DROP_TARGET_COLOR, 3); + } + + var clone = graph.isCloneEvent(me.getEvent()) && graph.isCellsCloneable() && this.isCloneEnabled(); + var gridEnabled = graph.isGridEnabledEvent(me.getEvent()); + var cell = me.getCell(); + var hideGuide = true; + var target = null; + this.cloning = clone; + + if (graph.isDropEnabled() && this.highlightEnabled) { + // Contains a call to getCellAt to find the cell under the mouse + target = graph.getDropTarget(this.cells, me.getEvent(), cell, clone); + } + + var state = graph.getView().getState(target); + var highlight = false; + + if (state != null && (clone || this.isValidDropTarget(target, me))) { + if (this.target != target) { this.target = target; this.setHighlightColor(mxConstants.DROP_TARGET_COLOR); - } - + } + highlight = true; - } - else - { - this.target = null; + } else { + this.target = null; - if (this.connectOnDrop && cell != null && this.cells.length == 1 && - graph.getModel().isVertex(cell) && graph.isCellConnectable(cell)) - { - state = graph.getView().getState(cell); - - if (state != null) - { - var error = graph.getEdgeValidationError(null, this.cell, cell); - var color = (error == null) ? - mxConstants.VALID_COLOR : - mxConstants.INVALID_CONNECT_TARGET_COLOR; - this.setHighlightColor(color); - highlight = true; - } - } - } - - if (state != null && highlight) - { - this.highlight.highlight(state); - } - else - { - this.highlight.hide(); - } + if (this.connectOnDrop && cell != null && this.cells.length == 1 && + graph.getModel().isVertex(cell) && graph.isCellConnectable(cell)) { + state = graph.getView().getState(cell); - if (this.guide != null && this.useGuidesForEvent(me)) - { - delta = this.guide.move(this.bounds, delta, gridEnabled, clone); - hideGuide = false; - } - else - { - delta = this.graph.snapDelta(delta, this.bounds, !gridEnabled, false, false); - } - - if (this.guide != null && hideGuide) - { - this.guide.hide(); - } - - // Constrained movement if shift key is pressed - if (graph.isConstrainedEvent(me.getEvent())) - { - if (Math.abs(delta.x) > Math.abs(delta.y)) - { - delta.y = 0; - } - else - { - delta.x = 0; - } - } - - this.checkPreview(); - - if (this.currentDx != delta.x || this.currentDy != delta.y) - { - this.currentDx = delta.x; - this.currentDy = delta.y; - this.updatePreview(); - } - } - - this.updateHint(me); - this.consumeMouseEvent(mxEvent.MOUSE_MOVE, me); - - // Cancels the bubbling of events to the container so - // that the droptarget is not reset due to an mouseMove - // fired on the container with no associated state. - mxEvent.consume(me.getEvent()); - } - else if ((this.isMoveEnabled() || this.isCloneEnabled()) && this.updateCursor && !me.isConsumed() && - (me.getState() != null || me.sourceState != null) && !graph.isMouseDown) - { - var cursor = graph.getCursorForMouseEvent(me); - - if (cursor == null && graph.isEnabled() && graph.isCellMovable(me.getCell())) - { - if (graph.getModel().isEdge(me.getCell())) - { - cursor = mxConstants.CURSOR_MOVABLE_EDGE; - } - else - { - cursor = mxConstants.CURSOR_MOVABLE_VERTEX; - } - } - - // Sets the cursor on the original source state under the mouse - // instead of the event source state which can be the parent - if (cursor != null && me.sourceState != null) - { - me.sourceState.setCursor(cursor); - } - } -}; - -/** - * Function: updatePreview - * - * Updates the bounds of the preview shape. - */ -updatePreview = (remote)=> -{ - if (this.livePreviewUsed && !remote) - { - if (this.cells != null) - { - this.setHandlesVisibleForCells( - this.graph.selectionCellsHandler. - getHandledSelectionCells(), false); - this.updateLivePreview(this.currentDx, this.currentDy); - } - } - else - { - this.updatePreviewShape(); - } -}; - -/** - * Function: updatePreviewShape - * - * Updates the bounds of the preview shape. - */ -updatePreviewShape = ()=> -{ - if (this.shape != null && this.pBounds != null) - { - this.shape.bounds = new mxRectangle(Math.round(this.pBounds.x + this.currentDx), - Math.round(this.pBounds.y + this.currentDy), this.pBounds.width, this.pBounds.height); - this.shape.redraw(); - } -}; - -/** - * Function: updateLivePreview - * - * Updates the bounds of the preview shape. - */ -updateLivePreview = (dx, dy)=> -{ - if (!this.suspended) - { - var states = []; - - if (this.allCells != null) - { - this.allCells.visit(mxUtils.bind(this, (key, state)=> - { - var realState = this.graph.view.getState(state.cell); - - // Checks if cell was removed or replaced - if (realState != state) - { - state.destroy(); - - if (realState != null) - { - this.allCells.put(state.cell, realState); - } - else - { - this.allCells.remove(state.cell); - } - - state = realState; - } - - if (state != null) - { - // Saves current state - var tempState = state.clone(); - states.push([state, tempState]); - - // Makes transparent for events to detect drop targets - if (state.shape != null) - { - if (state.shape.originalPointerEvents == null) - { - state.shape.originalPointerEvents = state.shape.pointerEvents; + if (state != null) { + var error = graph.getEdgeValidationError(null, this.cell, cell); + var color = (error == null) ? + mxConstants.VALID_COLOR : + mxConstants.INVALID_CONNECT_TARGET_COLOR; + this.setHighlightColor(color); + highlight = true; } - - state.shape.pointerEvents = false; - - if (state.text != null) - { - if (state.text.originalPointerEvents == null) - { - state.text.originalPointerEvents = state.text.pointerEvents; + } + } + + if (state != null && highlight) { + this.highlight.highlight(state); + } else { + this.highlight.hide(); + } + + if (this.guide != null && this.useGuidesForEvent(me)) { + delta = this.guide.move(this.bounds, delta, gridEnabled, clone); + hideGuide = false; + } else { + delta = this.graph.snapDelta(delta, this.bounds, !gridEnabled, false, false); + } + + if (this.guide != null && hideGuide) { + this.guide.hide(); + } + + // Constrained movement if shift key is pressed + if (graph.isConstrainedEvent(me.getEvent())) { + if (Math.abs(delta.x) > Math.abs(delta.y)) { + delta.y = 0; + } else { + delta.x = 0; + } + } + + this.checkPreview(); + + if (this.currentDx != delta.x || this.currentDy != delta.y) { + this.currentDx = delta.x; + this.currentDy = delta.y; + this.updatePreview(); + } + } + + this.updateHint(me); + this.consumeMouseEvent(mxEvent.MOUSE_MOVE, me); + + // Cancels the bubbling of events to the container so + // that the droptarget is not reset due to an mouseMove + // fired on the container with no associated state. + mxEvent.consume(me.getEvent()); + } else if ((this.isMoveEnabled() || this.isCloneEnabled()) && this.updateCursor && !me.isConsumed() && + (me.getState() != null || me.sourceState != null) && !graph.isMouseDown) { + var cursor = graph.getCursorForMouseEvent(me); + + if (cursor == null && graph.isEnabled() && graph.isCellMovable(me.getCell())) { + if (graph.getModel().isEdge(me.getCell())) { + cursor = mxConstants.CURSOR_MOVABLE_EDGE; + } else { + cursor = mxConstants.CURSOR_MOVABLE_VERTEX; + } + } + + // Sets the cursor on the original source state under the mouse + // instead of the event source state which can be the parent + if (cursor != null && me.sourceState != null) { + me.sourceState.setCursor(cursor); + } + } + }; + + /** + * Function: updatePreview + * + * Updates the bounds of the preview shape. + */ + updatePreview = (remote) => { + if (this.livePreviewUsed && !remote) { + if (this.cells != null) { + this.setHandlesVisibleForCells( + this.graph.selectionCellsHandler.getHandledSelectionCells(), false); + this.updateLivePreview(this.currentDx, this.currentDy); + } + } else { + this.updatePreviewShape(); + } + }; + + /** + * Function: updatePreviewShape + * + * Updates the bounds of the preview shape. + */ + updatePreviewShape = () => { + if (this.shape != null && this.pBounds != null) { + this.shape.bounds = new mxRectangle(Math.round(this.pBounds.x + this.currentDx), + Math.round(this.pBounds.y + this.currentDy), this.pBounds.width, this.pBounds.height); + this.shape.redraw(); + } + }; + + /** + * Function: updateLivePreview + * + * Updates the bounds of the preview shape. + */ + updateLivePreview = (dx, dy) => { + if (!this.suspended) { + var states = []; + + if (this.allCells != null) { + this.allCells.visit(mxUtils.bind(this, (key, state) => { + var realState = this.graph.view.getState(state.cell); + + // Checks if cell was removed or replaced + if (realState != state) { + state.destroy(); + + if (realState != null) { + this.allCells.put(state.cell, realState); + } else { + this.allCells.remove(state.cell); + } + + state = realState; + } + + if (state != null) { + // Saves current state + var tempState = state.clone(); + states.push([state, tempState]); + + // Makes transparent for events to detect drop targets + if (state.shape != null) { + if (state.shape.originalPointerEvents == null) { + state.shape.originalPointerEvents = state.shape.pointerEvents; + } + + state.shape.pointerEvents = false; + + if (state.text != null) { + if (state.text.originalPointerEvents == null) { + state.text.originalPointerEvents = state.text.pointerEvents; + } + + state.text.pointerEvents = false; + } + } + + // Temporarily changes position + if (this.graph.model.isVertex(state.cell)) { + state.x += dx; + state.y += dy; + + // Draws the live preview + if (!this.cloning) { + state.view.graph.cellRenderer.redraw(state, true); + + // Forces redraw of connected edges after all states + // have been updated but avoids update of state + state.view.invalidate(state.cell); + state.invalid = false; + + // Hides folding icon + if (state.control != null && state.control.node != null) { + state.control.node.style.visibility = 'hidden'; + } + } + // Clone live preview may use text bounds + else if (state.text != null) { + state.text.updateBoundingBox(); + + // Fixes preview box for edge labels + if (state.text.boundingBox != null) { + state.text.boundingBox.x += dx; + state.text.boundingBox.y += dy; + } + + if (state.text.unrotatedBoundingBox != null) { + state.text.unrotatedBoundingBox.x += dx; + state.text.unrotatedBoundingBox.y += dy; + } } - - state.text.pointerEvents = false; } } - - // Temporarily changes position - if (this.graph.model.isVertex(state.cell)) - { - state.x += dx; - state.y += dy; - - // Draws the live preview - if (!this.cloning) - { + })); + } + + // Resets the handler if everything was removed + if (states.length == 0) { + this.reset(); + } else { + // Redraws connected edges + var s = this.graph.view.scale; + + for (var i = 0; i < states.length; i++) { + var state = states[i][0]; + + if (this.graph.model.isEdge(state.cell)) { + var geometry = this.graph.getCellGeometry(state.cell); + var points = []; + + if (geometry != null && geometry.points != null) { + for (var j = 0; j < geometry.points.length; j++) { + if (geometry.points[j] != null) { + points.push(new mxPoint( + geometry.points[j].x + dx / s, + geometry.points[j].y + dy / s)); + } + } + } + + var source = state.visibleSourceState; + var target = state.visibleTargetState; + var pts = states[i][1].absolutePoints; + + if (source == null || !this.isCellMoving(source.cell)) { + var pt0 = pts[0]; + state.setAbsoluteTerminalPoint(new mxPoint(pt0.x + dx, pt0.y + dy), true); + source = null; + } else { + state.view.updateFixedTerminalPoint(state, source, true, + this.graph.getConnectionConstraint(state, source, true)); + } + + if (target == null || !this.isCellMoving(target.cell)) { + var ptn = pts[pts.length - 1]; + state.setAbsoluteTerminalPoint(new mxPoint(ptn.x + dx, ptn.y + dy), false); + target = null; + } else { + state.view.updateFixedTerminalPoint(state, target, false, + this.graph.getConnectionConstraint(state, target, false)); + } + + state.view.updatePoints(state, points, source, target); + state.view.updateFloatingTerminalPoints(state, source, target); + state.view.updateEdgeLabelOffset(state); + state.invalid = false; + + // Draws the live preview but avoids update of state + if (!this.cloning) { state.view.graph.cellRenderer.redraw(state, true); - - // Forces redraw of connected edges after all states - // have been updated but avoids update of state - state.view.invalidate(state.cell); - state.invalid = false; - - // Hides folding icon - if (state.control != null && state.control.node != null) - { - state.control.node.style.visibility = 'hidden'; - } - } - // Clone live preview may use text bounds - else if (state.text != null) - { - state.text.updateBoundingBox(); - - // Fixes preview box for edge labels - if (state.text.boundingBox != null) - { - state.text.boundingBox.x += dx; - state.text.boundingBox.y += dy; - } - - if (state.text.unrotatedBoundingBox != null) - { - state.text.unrotatedBoundingBox.x += dx; - state.text.unrotatedBoundingBox.y += dy; - } } } } + + this.graph.view.validate(); + this.redrawHandles(states); + this.resetPreviewStates(states); + } + } + }; + + /** + * Function: redrawHandles + * + * Redraws the preview shape for the given states array. + */ + redrawHandles = (states) => { + for (var i = 0; i < states.length; i++) { + var handler = this.graph.selectionCellsHandler.getHandler(states[i][0].cell); + + if (handler != null) { + handler.redraw(true); + } + } + }; + + /** + * Function: resetPreviewStates + * + * Resets the given preview states array. + */ + resetPreviewStates = (states) => { + for (var i = 0; i < states.length; i++) { + states[i][0].setState(states[i][1]); + } + }; + + /** + * Function: suspend + * + * Suspends the livew preview. + */ + suspend = () => { + if (!this.suspended) { + if (this.livePreviewUsed) { + this.updateLivePreview(0, 0); + } + + if (this.shape != null) { + this.shape.node.style.visibility = 'hidden'; + } + + if (this.guide != null) { + this.guide.setVisible(false); + } + + this.suspended = true; + } + }; + + /** + * Function: resume + * + * Suspends the livew preview. + */ + resume = () => { + if (this.suspended) { + this.suspended = null; + + if (this.livePreviewUsed) { + this.livePreviewActive = true; + } + + if (this.shape != null) { + this.shape.node.style.visibility = 'visible'; + } + + if (this.guide != null) { + this.guide.setVisible(true); + } + } + }; + + /** + * Function: resetLivePreview + * + * Resets the livew preview. + */ + resetLivePreview = () => { + if (this.allCells != null) { + this.allCells.visit(mxUtils.bind(this, (key, state) => { + // Restores event handling + if (state.shape != null && state.shape.originalPointerEvents != null) { + state.shape.pointerEvents = state.shape.originalPointerEvents; + state.shape.originalPointerEvents = null; + + // Forces repaint even if not moved to update pointer events + state.shape.bounds = null; + + if (state.text != null) { + state.text.pointerEvents = state.text.originalPointerEvents; + state.text.originalPointerEvents = null; + } + } + + // Shows folding icon + if (state.control != null && state.control.node != null && + state.control.node.style.visibility == 'hidden') { + state.control.node.style.visibility = ''; + } + + // Fixes preview box for edge labels + if (!this.cloning) { + if (state.text != null) { + state.text.updateBoundingBox(); + } + } + + // Forces repaint of connected edges + state.view.invalidate(state.cell); })); - } - - // Resets the handler if everything was removed - if (states.length == 0) - { - this.reset(); - } - else - { - // Redraws connected edges - var s = this.graph.view.scale; - - for (var i = 0; i < states.length; i++) - { - var state = states[i][0]; - - if (this.graph.model.isEdge(state.cell)) - { - var geometry = this.graph.getCellGeometry(state.cell); - var points = []; - - if (geometry != null && geometry.points != null) - { - for (var j = 0; j < geometry.points.length; j++) - { - if (geometry.points[j] != null) - { - points.push(new mxPoint( - geometry.points[j].x + dx / s, - geometry.points[j].y + dy / s)); - } - } - } - - var source = state.visibleSourceState; - var target = state.visibleTargetState; - var pts = states[i][1].absolutePoints; - - if (source == null || !this.isCellMoving(source.cell)) - { - var pt0 = pts[0]; - state.setAbsoluteTerminalPoint(new mxPoint(pt0.x + dx, pt0.y + dy), true); - source = null; - } - else - { - state.view.updateFixedTerminalPoint(state, source, true, - this.graph.getConnectionConstraint(state, source, true)); - } - - if (target == null || !this.isCellMoving(target.cell)) - { - var ptn = pts[pts.length - 1]; - state.setAbsoluteTerminalPoint(new mxPoint(ptn.x + dx, ptn.y + dy), false); - target = null; - } - else - { - state.view.updateFixedTerminalPoint(state, target, false, - this.graph.getConnectionConstraint(state, target, false)); - } - - state.view.updatePoints(state, points, source, target); - state.view.updateFloatingTerminalPoints(state, source, target); - state.view.updateEdgeLabelOffset(state); - state.invalid = false; - - // Draws the live preview but avoids update of state - if (!this.cloning) - { - state.view.graph.cellRenderer.redraw(state, true); - } - } - } - + + // Repaints all invalid states this.graph.view.validate(); - this.redrawHandles(states); - this.resetPreviewStates(states); } - } -}; + }; -/** - * Function: redrawHandles - * - * Redraws the preview shape for the given states array. - */ -redrawHandles = (states)=> -{ - for (var i = 0; i < states.length; i++) - { - var handler = this.graph.selectionCellsHandler.getHandler(states[i][0].cell); - - if (handler != null) - { - handler.redraw(true); - } - } -}; + /** + * Function: setHandlesVisibleForCells + * + * Sets wether the handles attached to the given cells are visible. + * + * Parameters: + * + * cells - Array of . + * visible - Boolean that specifies if the handles should be visible. + * force - Forces an update of the handler regardless of the last used value. + */ + setHandlesVisibleForCells = (cells, visible, force) => { + if (force || this.handlesVisible != visible) { + this.handlesVisible = visible; -/** - * Function: resetPreviewStates - * - * Resets the given preview states array. - */ -resetPreviewStates = (states)=> -{ - for (var i = 0; i < states.length; i++) - { - states[i][0].setState(states[i][1]); - } -}; + for (var i = 0; i < cells.length; i++) { + var handler = this.graph.selectionCellsHandler.getHandler(cells[i]); -/** - * Function: suspend - * - * Suspends the livew preview. - */ -suspend = ()=> -{ - if (!this.suspended) - { - if (this.livePreviewUsed) - { - this.updateLivePreview(0, 0); - } - - if (this.shape != null) - { - this.shape.node.style.visibility = 'hidden'; - } - - if (this.guide != null) - { - this.guide.setVisible(false); - } - - this.suspended = true; - } -}; + if (handler != null) { + handler.setHandlesVisible(visible); -/** - * Function: resume - * - * Suspends the livew preview. - */ -resume = ()=> -{ - if (this.suspended) - { - this.suspended = null; - - if (this.livePreviewUsed) - { - this.livePreviewActive = true; - } - - if (this.shape != null) - { - this.shape.node.style.visibility = 'visible'; - } - - if (this.guide != null) - { - this.guide.setVisible(true); - } - } -}; - -/** - * Function: resetLivePreview - * - * Resets the livew preview. - */ -resetLivePreview = ()=> -{ - if (this.allCells != null) - { - this.allCells.visit(mxUtils.bind(this, (key, state)=> - { - // Restores event handling - if (state.shape != null && state.shape.originalPointerEvents != null) - { - state.shape.pointerEvents = state.shape.originalPointerEvents; - state.shape.originalPointerEvents = null; - - // Forces repaint even if not moved to update pointer events - state.shape.bounds = null; - - if (state.text != null) - { - state.text.pointerEvents = state.text.originalPointerEvents; - state.text.originalPointerEvents = null; - } - } - - // Shows folding icon - if (state.control != null && state.control.node != null && - state.control.node.style.visibility == 'hidden') - { - state.control.node.style.visibility = ''; - } - - // Fixes preview box for edge labels - if (!this.cloning) - { - if (state.text != null) - { - state.text.updateBoundingBox(); - } - } - - // Forces repaint of connected edges - state.view.invalidate(state.cell); - })); - - // Repaints all invalid states - this.graph.view.validate(); - } -}; - -/** - * Function: setHandlesVisibleForCells - * - * Sets wether the handles attached to the given cells are visible. - * - * Parameters: - * - * cells - Array of . - * visible - Boolean that specifies if the handles should be visible. - * force - Forces an update of the handler regardless of the last used value. - */ -setHandlesVisibleForCells = (cells, visible, force)=> -{ - if (force || this.handlesVisible != visible) - { - this.handlesVisible = visible; - - for (var i = 0; i < cells.length; i++) - { - var handler = this.graph.selectionCellsHandler.getHandler(cells[i]); - - if (handler != null) - { - handler.setHandlesVisible(visible); - - if (visible) - { - handler.redraw(); + if (visible) { + handler.redraw(); + } } } } - } -}; + }; -/** - * Function: setHighlightColor - * - * Sets the color of the rectangle used to highlight drop targets. - * - * Parameters: - * - * color - String that represents the new highlight color. - */ -setHighlightColor = (color)=> -{ - if (this.highlight != null) - { - this.highlight.setHighlightColor(color); - } -}; + /** + * Function: setHighlightColor + * + * Sets the color of the rectangle used to highlight drop targets. + * + * Parameters: + * + * color - String that represents the new highlight color. + */ + setHighlightColor = (color) => { + if (this.highlight != null) { + this.highlight.setHighlightColor(color); + } + }; -/** - * Function: mouseUp - * - * Handles the event by applying the changes to the selection cells. - */ -mouseUp = (sender, me)=> -{ - if (!me.isConsumed()) - { - if (this.livePreviewUsed) - { + /** + * Function: mouseUp + * + * Handles the event by applying the changes to the selection cells. + */ + mouseUp = (sender, me) => { + if (!me.isConsumed()) { + if (this.livePreviewUsed) { + this.resetLivePreview(); + } + + if (this.cell != null && this.first != null && (this.shape != null || this.livePreviewUsed) && + this.currentDx != null && this.currentDy != null) { + var graph = this.graph; + var cell = me.getCell(); + + if (this.connectOnDrop && this.target == null && cell != null && graph.getModel().isVertex(cell) && + graph.isCellConnectable(cell) && graph.isEdgeValid(null, this.cell, cell)) { + graph.connectionHandler.connect(this.cell, cell, me.getEvent()); + } else { + var clone = graph.isCloneEvent(me.getEvent()) && graph.isCellsCloneable() && this.isCloneEnabled(); + var scale = graph.getView().scale; + var dx = this.roundLength(this.currentDx / scale); + var dy = this.roundLength(this.currentDy / scale); + var target = this.target; + + if (graph.isSplitEnabled() && graph.isSplitTarget(target, this.cells, me.getEvent())) { + graph.splitEdge(target, this.cells, null, dx, dy, + me.getGraphX(), me.getGraphY()); + } else { + this.moveCells(this.cells, dx, dy, clone, this.target, me.getEvent()); + } + } + } else if (this.isSelectEnabled() && this.delayedSelection && this.cell != null) { + this.selectDelayed(me); + } + } + + // Consumes the event if a cell was initially clicked + if (this.cellWasClicked) { + this.consumeMouseEvent(mxEvent.MOUSE_UP, me); + } + + this.reset(); + }; + + /** + * Function: reset + * + * Resets the state of this handler. + */ + reset = () => { + if (this.livePreviewUsed) { this.resetLivePreview(); + this.setHandlesVisibleForCells( + this.graph.selectionCellsHandler.getHandledSelectionCells(), true); } - - if (this.cell != null && this.first != null && (this.shape != null || this.livePreviewUsed) && - this.currentDx != null && this.currentDy != null) - { - var graph = this.graph; - var cell = me.getCell(); - - if (this.connectOnDrop && this.target == null && cell != null && graph.getModel().isVertex(cell) && - graph.isCellConnectable(cell) && graph.isEdgeValid(null, this.cell, cell)) - { - graph.connectionHandler.connect(this.cell, cell, me.getEvent()); - } - else - { - var clone = graph.isCloneEvent(me.getEvent()) && graph.isCellsCloneable() && this.isCloneEnabled(); - var scale = graph.getView().scale; - var dx = this.roundLength(this.currentDx / scale); - var dy = this.roundLength(this.currentDy / scale); - var target = this.target; - - if (graph.isSplitEnabled() && graph.isSplitTarget(target, this.cells, me.getEvent())) - { - graph.splitEdge(target, this.cells, null, dx, dy, - me.getGraphX(), me.getGraphY()); + + this.destroyShapes(); + this.removeHint(); + + this.delayedSelection = false; + this.livePreviewActive = null; + this.livePreviewUsed = null; + this.cellWasClicked = false; + this.suspended = null; + this.currentDx = null; + this.currentDy = null; + this.cellCount = null; + this.cloning = false; + this.allCells = null; + this.pBounds = null; + this.guides = null; + this.target = null; + this.first = null; + this.cells = null; + this.cell = null; + }; + + /** + * Function: shouldRemoveCellsFromParent + * + * Returns true if the given cells should be removed from the parent for the specified + * mousereleased event. + */ + shouldRemoveCellsFromParent = (parent, cells, evt) => { + if (this.graph.getModel().isVertex(parent)) { + var pState = this.graph.getView().getState(parent); + + if (pState != null) { + var pt = mxUtils.convertPoint(this.graph.container, + mxEvent.getClientX(evt), mxEvent.getClientY(evt)); + var alpha = mxUtils.toRadians(mxUtils.getValue(pState.style, mxConstants.STYLE_ROTATION) || 0); + + if (alpha != 0) { + var cos = Math.cos(-alpha); + var sin = Math.sin(-alpha); + var cx = new mxPoint(pState.getCenterX(), pState.getCenterY()); + pt = mxUtils.getRotatedPoint(pt, cos, sin, cx); } - else - { - this.moveCells(this.cells, dx, dy, clone, this.target, me.getEvent()); + + return !mxUtils.contains(pState, pt.x, pt.y); + } + } + + return false; + }; + + /** + * Function: moveCells + * + * Moves the given cells by the specified amount. + */ + moveCells = (cells, dx, dy, clone, target, evt) => { + if (clone) { + cells = this.graph.getCloneableCells(cells); + } + + // Removes cells from parent + var parent = this.graph.getModel().getParent(this.cell); + + if (target == null && this.isRemoveCellsFromParent() && + this.shouldRemoveCellsFromParent(parent, cells, evt)) { + target = this.graph.getDefaultParent(); + } + + // Cloning into locked cells is not allowed + clone = clone && !this.graph.isCellLocked(target || this.graph.getDefaultParent()); + + this.graph.getModel().beginUpdate(); + try { + var parents = []; + + // Removes parent if all child cells are removed + if (!clone && target != null && this.removeEmptyParents) { + // Collects all non-selected parents + var dict = new mxDictionary(); + + for (var i = 0; i < cells.length; i++) { + dict.put(cells[i], true); + } + + // LATER: Recurse up the cell hierarchy + for (var i = 0; i < cells.length; i++) { + var par = this.graph.model.getParent(cells[i]); + + if (par != null && !dict.get(par)) { + dict.put(par, true); + parents.push(par); + } } } - } - else if (this.isSelectEnabled() && this.delayedSelection && this.cell != null) - { - this.selectDelayed(me); - } - } - // Consumes the event if a cell was initially clicked - if (this.cellWasClicked) - { - this.consumeMouseEvent(mxEvent.MOUSE_UP, me); - } + // Passes all selected cells in order to correctly clone or move into + // the target cell. The method checks for each cell if its movable. + cells = this.graph.moveCells(cells, dx, dy, clone, target, evt); - this.reset(); -}; + // Removes parent if all child cells are removed + var temp = []; -/** - * Function: reset - * - * Resets the state of this handler. - */ -reset = ()=> -{ - if (this.livePreviewUsed) - { - this.resetLivePreview(); - this.setHandlesVisibleForCells( - this.graph.selectionCellsHandler. - getHandledSelectionCells(), true); - } - - this.destroyShapes(); - this.removeHint(); - - this.delayedSelection = false; - this.livePreviewActive = null; - this.livePreviewUsed = null; - this.cellWasClicked = false; - this.suspended = null; - this.currentDx = null; - this.currentDy = null; - this.cellCount = null; - this.cloning = false; - this.allCells = null; - this.pBounds = null; - this.guides = null; - this.target = null; - this.first = null; - this.cells = null; - this.cell = null; -}; - -/** - * Function: shouldRemoveCellsFromParent - * - * Returns true if the given cells should be removed from the parent for the specified - * mousereleased event. - */ -shouldRemoveCellsFromParent = (parent, cells, evt)=> -{ - if (this.graph.getModel().isVertex(parent)) - { - var pState = this.graph.getView().getState(parent); - - if (pState != null) - { - var pt = mxUtils.convertPoint(this.graph.container, - mxEvent.getClientX(evt), mxEvent.getClientY(evt)); - var alpha = mxUtils.toRadians(mxUtils.getValue(pState.style, mxConstants.STYLE_ROTATION) || 0); - - if (alpha != 0) - { - var cos = Math.cos(-alpha); - var sin = Math.sin(-alpha); - var cx = new mxPoint(pState.getCenterX(), pState.getCenterY()); - pt = mxUtils.getRotatedPoint(pt, cos, sin, cx); - } - - return !mxUtils.contains(pState, pt.x, pt.y); - } - } - - return false; -}; - -/** - * Function: moveCells - * - * Moves the given cells by the specified amount. - */ -moveCells = (cells, dx, dy, clone, target, evt)=> -{ - if (clone) - { - cells = this.graph.getCloneableCells(cells); - } - - // Removes cells from parent - var parent = this.graph.getModel().getParent(this.cell); - - if (target == null && this.isRemoveCellsFromParent() && - this.shouldRemoveCellsFromParent(parent, cells, evt)) - { - target = this.graph.getDefaultParent(); - } - - // Cloning into locked cells is not allowed - clone = clone && !this.graph.isCellLocked(target || this.graph.getDefaultParent()); - - this.graph.getModel().beginUpdate(); - try - { - var parents = []; - - // Removes parent if all child cells are removed - if (!clone && target != null && this.removeEmptyParents) - { - // Collects all non-selected parents - var dict = new mxDictionary(); - - for (var i = 0; i < cells.length; i++) - { - dict.put(cells[i], true); - } - - // LATER: Recurse up the cell hierarchy - for (var i = 0; i < cells.length; i++) - { - var par = this.graph.model.getParent(cells[i]); - - if (par != null && !dict.get(par)) - { - dict.put(par, true); - parents.push(par); + for (var i = 0; i < parents.length; i++) { + if (this.shouldRemoveParent(parents[i])) { + temp.push(parents[i]); } } + + this.graph.removeCells(temp, false); + } finally { + this.graph.getModel().endUpdate(); } - - // Passes all selected cells in order to correctly clone or move into - // the target cell. The method checks for each cell if its movable. - cells = this.graph.moveCells(cells, dx, dy, clone, target, evt); - // Removes parent if all child cells are removed - var temp = []; - - for (var i = 0; i < parents.length; i++) - { - if (this.shouldRemoveParent(parents[i])) - { - temp.push(parents[i]); - } + // Selects the new cells if cells have been cloned + if (clone) { + this.graph.setSelectionCells(cells); } - - this.graph.removeCells(temp, false); - } - finally - { - this.graph.getModel().endUpdate(); - } - // Selects the new cells if cells have been cloned - if (clone) - { - this.graph.setSelectionCells(cells); - } + if (this.isSelectEnabled() && this.scrollOnMove) { + this.graph.scrollCellToVisible(cells[0]); + } + }; - if (this.isSelectEnabled() && this.scrollOnMove) - { - this.graph.scrollCellToVisible(cells[0]); - } -}; + /** + * Function: shouldRemoveParent + * + * Returns true if the given parent should be removed after removal of child cells. + */ + shouldRemoveParent = (parent) => { + var state = this.graph.view.getState(parent); -/** - * Function: shouldRemoveParent - * - * Returns true if the given parent should be removed after removal of child cells. - */ -shouldRemoveParent = (parent)=> -{ - var state = this.graph.view.getState(parent); - - return state != null && (this.graph.model.isEdge(state.cell) || this.graph.model.isVertex(state.cell)) && - this.graph.isCellDeletable(state.cell) && this.graph.model.getChildCount(state.cell) == 0 && - this.graph.isTransparentState(state); -}; + return state != null && (this.graph.model.isEdge(state.cell) || this.graph.model.isVertex(state.cell)) && + this.graph.isCellDeletable(state.cell) && this.graph.model.getChildCount(state.cell) == 0 && + this.graph.isTransparentState(state); + }; -/** - * Function: destroyShapes - * - * Destroy the preview and highlight shapes. - */ -destroyShapes = ()=> -{ - // Destroys the preview dashed rectangle - if (this.shape != null) - { - this.shape.destroy(); - this.shape = null; - } - - if (this.guide != null) - { - this.guide.destroy(); - this.guide = null; - } - - // Destroys the drop target highlight - if (this.highlight != null) - { - this.highlight.destroy(); - this.highlight = null; - } -}; + /** + * Function: destroyShapes + * + * Destroy the preview and highlight shapes. + */ + destroyShapes = () => { + // Destroys the preview dashed rectangle + if (this.shape != null) { + this.shape.destroy(); + this.shape = null; + } -/** - * Function: destroy - * - * Destroys the handler and all its resources and DOM nodes. - */ -destroy = ()=> -{ - this.graph.removeMouseListener(this); - this.graph.removeListener(this.panHandler); - - if (this.escapeHandler != null) - { - this.graph.removeListener(this.escapeHandler); - this.escapeHandler = null; - } - - if (this.refreshHandler != null) - { - this.graph.getModel().removeListener(this.refreshHandler); - this.graph.removeListener(this.refreshHandler); - this.refreshHandler = null; - } - - mxEvent.removeListener(document, 'keydown', this.keyHandler); - mxEvent.removeListener(document, 'keyup', this.keyHandler); - - this.destroyShapes(); - this.removeHint(); -}; + if (this.guide != null) { + this.guide.destroy(); + this.guide = null; + } + + // Destroys the drop target highlight + if (this.highlight != null) { + this.highlight.destroy(); + this.highlight = null; + } + }; + + /** + * Function: destroy + * + * Destroys the handler and all its resources and DOM nodes. + */ + destroy = () => { + this.graph.removeMouseListener(this); + this.graph.removeListener(this.panHandler); + + if (this.escapeHandler != null) { + this.graph.removeListener(this.escapeHandler); + this.escapeHandler = null; + } + + if (this.refreshHandler != null) { + this.graph.getModel().removeListener(this.refreshHandler); + this.graph.removeListener(this.refreshHandler); + this.refreshHandler = null; + } + + mxEvent.removeListener(document, 'keydown', this.keyHandler); + mxEvent.removeListener(document, 'keyup', this.keyHandler); + + this.destroyShapes(); + this.removeHint(); + }; +} + +export default mxGraphHandler; diff --git a/src/js/handler/mxHandle.js b/src/js/handler/mxHandle.js index 62252f844..64b817a98 100644 --- a/src/js/handler/mxHandle.js +++ b/src/js/handler/mxHandle.js @@ -2,351 +2,322 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * Class: mxHandle - * - * Implements a single custom handle for vertices. - * - * Constructor: mxHandle - * - * Constructs a new handle for the given state. - * - * Parameters: - * - * state - of the cell to be handled. - */ -function mxHandle(state, cursor, image, shape) -{ - this.graph = state.view.graph; - this.state = state; - this.cursor = (cursor != null) ? cursor : this.cursor; - this.image = (image != null) ? image : this.image; - this.shape = (shape != null) ? shape : null; - this.init(); -}; -/** - * Variable: cursor - * - * Specifies the cursor to be used for this handle. Default is 'default'. - */ -cursor = 'default'; +class mxHandle { + /** + * Variable: cursor + * + * Specifies the cursor to be used for this handle. Default is 'default'. + */ + cursor = 'default'; -/** - * Variable: image - * - * Specifies the to be used to render the handle. Default is null. - */ -image = null; + /** + * Variable: image + * + * Specifies the to be used to render the handle. Default is null. + */ + image = null; -/** - * Variable: ignoreGrid - * - * Default is false. - */ -ignoreGrid = false; + /** + * Variable: ignoreGrid + * + * Default is false. + */ + ignoreGrid = false; -/** - * Function: getPosition - * - * Hook for subclassers to return the current position of the handle. - */ -getPosition = (bounds)=> { }; + /** + * Class: mxHandle + * + * Implements a single custom handle for vertices. + * + * Constructor: mxHandle + * + * Constructs a new handle for the given state. + * + * Parameters: + * + * state - of the cell to be handled. + */ + constructor(state, cursor, image, shape) { + this.graph = state.view.graph; + this.state = state; + this.cursor = (cursor != null) ? cursor : this.cursor; + this.image = (image != null) ? image : this.image; + this.shape = (shape != null) ? shape : null; + this.init(); + }; -/** - * Function: setPosition - * - * Hooks for subclassers to update the style in the . - */ -setPosition = (bounds, pt, me)=> { }; + /** + * Function: getPosition + * + * Hook for subclassers to return the current position of the handle. + */ + getPosition = (bounds) => { + }; -/** - * Function: execute - * - * Hook for subclassers to execute the handle. - */ -execute = (me)=> { }; + /** + * Function: setPosition + * + * Hooks for subclassers to update the style in the . + */ + setPosition = (bounds, pt, me) => { + }; -/** - * Function: copyStyle - * - * Sets the cell style with the given name to the corresponding value in . - */ -copyStyle = (key)=> -{ - this.graph.setCellStyles(key, this.state.style[key], [this.state.cell]); -}; + /** + * Function: execute + * + * Hook for subclassers to execute the handle. + */ + execute = (me) => { + }; -/** - * Function: processEvent - * - * Processes the given and invokes . - */ -processEvent = (me)=> -{ - var scale = this.graph.view.scale; - var tr = this.graph.view.translate; - var pt = new mxPoint(me.getGraphX() / scale - tr.x, me.getGraphY() / scale - tr.y); - - // Center shape on mouse cursor - if (this.shape != null && this.shape.bounds != null) - { - pt.x -= this.shape.bounds.width / scale / 4; - pt.y -= this.shape.bounds.height / scale / 4; - } + /** + * Function: copyStyle + * + * Sets the cell style with the given name to the corresponding value in . + */ + copyStyle = (key) => { + this.graph.setCellStyles(key, this.state.style[key], [this.state.cell]); + }; - // Snaps to grid for the rotated position then applies the rotation for the direction after that - var alpha1 = -mxUtils.toRadians(this.getRotation()); - var alpha2 = -mxUtils.toRadians(this.getTotalRotation()) - alpha1; - pt = this.flipPoint(this.rotatePoint(this.snapPoint(this.rotatePoint(pt, alpha1), - this.ignoreGrid || !this.graph.isGridEnabledEvent(me.getEvent())), alpha2)); - this.setPosition(this.state.getPaintBounds(), pt, me); - this.redraw(); -}; + /** + * Function: processEvent + * + * Processes the given and invokes . + */ + processEvent = (me) => { + var scale = this.graph.view.scale; + var tr = this.graph.view.translate; + var pt = new mxPoint(me.getGraphX() / scale - tr.x, me.getGraphY() / scale - tr.y); -/** - * Function: positionChanged - * - * Should be called after in . - * This repaints the state using . - */ -positionChanged = ()=> -{ - if (this.state.text != null) - { - this.state.text.apply(this.state); - } - - if (this.state.shape != null) - { - this.state.shape.apply(this.state); - } - - this.graph.cellRenderer.redraw(this.state, true); -}; - -/** - * Function: getRotation - * - * Returns the rotation defined in the style of the cell. - */ -getRotation = ()=> -{ - if (this.state.shape != null) - { - return this.state.shape.getRotation(); - } - - return 0; -}; - -/** - * Function: getTotalRotation - * - * Returns the rotation from the style and the rotation from the direction of - * the cell. - */ -getTotalRotation = ()=> -{ - if (this.state.shape != null) - { - return this.state.shape.getShapeRotation(); - } - - return 0; -}; - -/** - * Function: init - * - * Creates and initializes the shapes required for this handle. - */ -init = ()=> -{ - var html = this.isHtmlRequired(); - - if (this.image != null) - { - this.shape = new mxImageShape(new mxRectangle(0, 0, this.image.width, this.image.height), this.image.src); - this.shape.preserveImageAspect = false; - } - else if (this.shape == null) - { - this.shape = this.createShape(html); - } - - this.initShape(html); -}; - -/** - * Function: createShape - * - * Creates and returns the shape for this handle. - */ -createShape = (html)=> -{ - var bounds = new mxRectangle(0, 0, mxConstants.HANDLE_SIZE, mxConstants.HANDLE_SIZE); - - return new mxRectangleShape(bounds, mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR); -}; - -/** - * Function: initShape - * - * Initializes and sets its cursor. - */ -initShape = (html)=> -{ - if (html && this.shape.isHtmlAllowed()) - { - this.shape.dialect = mxConstants.DIALECT_STRICTHTML; - this.shape.init(this.graph.container); - } - else - { - this.shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? - mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG; - - if (this.cursor != null) - { - this.shape.init(this.graph.getView().getOverlayPane()); + // Center shape on mouse cursor + if (this.shape != null && this.shape.bounds != null) { + pt.x -= this.shape.bounds.width / scale / 4; + pt.y -= this.shape.bounds.height / scale / 4; } - } - mxEvent.redirectMouseEvents(this.shape.node, this.graph, this.state); - this.shape.node.style.cursor = this.cursor; -}; + // Snaps to grid for the rotated position then applies the rotation for the direction after that + var alpha1 = -mxUtils.toRadians(this.getRotation()); + var alpha2 = -mxUtils.toRadians(this.getTotalRotation()) - alpha1; + pt = this.flipPoint(this.rotatePoint(this.snapPoint(this.rotatePoint(pt, alpha1), + this.ignoreGrid || !this.graph.isGridEnabledEvent(me.getEvent())), alpha2)); + this.setPosition(this.state.getPaintBounds(), pt, me); + this.redraw(); + }; -/** - * Function: redraw - * - * Renders the shape for this handle. - */ -redraw = ()=> -{ - if (this.shape != null && this.state.shape != null) - { - var pt = this.getPosition(this.state.getPaintBounds()); - - if (pt != null) - { - var alpha = mxUtils.toRadians(this.getTotalRotation()); - pt = this.rotatePoint(this.flipPoint(pt), alpha); - - var scale = this.graph.view.scale; - var tr = this.graph.view.translate; - this.shape.bounds.x = Math.floor((pt.x + tr.x) * scale - this.shape.bounds.width / 2); - this.shape.bounds.y = Math.floor((pt.y + tr.y) * scale - this.shape.bounds.height / 2); - - // Needed to force update of text bounds - this.shape.redraw(); + /** + * Function: positionChanged + * + * Should be called after in . + * This repaints the state using . + */ + positionChanged = () => { + if (this.state.text != null) { + this.state.text.apply(this.state); } - } -}; -/** - * Function: isHtmlRequired - * - * Returns true if this handle should be rendered in HTML. This returns true if - * the text node is in the graph container. - */ -isHtmlRequired = ()=> -{ - return this.state.text != null && this.state.text.node.parentNode == this.graph.container; -}; + if (this.state.shape != null) { + this.state.shape.apply(this.state); + } -/** - * Function: rotatePoint - * - * Rotates the point by the given angle. - */ -rotatePoint = (pt, alpha)=> -{ - var bounds = this.state.getCellBounds(); - var cx = new mxPoint(bounds.getCenterX(), bounds.getCenterY()); - var cos = Math.cos(alpha); - var sin = Math.sin(alpha); + this.graph.cellRenderer.redraw(this.state, true); + }; - return mxUtils.getRotatedPoint(pt, cos, sin, cx); -}; + /** + * Function: getRotation + * + * Returns the rotation defined in the style of the cell. + */ + getRotation = () => { + if (this.state.shape != null) { + return this.state.shape.getRotation(); + } -/** - * Function: flipPoint - * - * Flips the given point vertically and/or horizontally. - */ -flipPoint = (pt)=> -{ - if (this.state.shape != null) - { + return 0; + }; + + /** + * Function: getTotalRotation + * + * Returns the rotation from the style and the rotation from the direction of + * the cell. + */ + getTotalRotation = () => { + if (this.state.shape != null) { + return this.state.shape.getShapeRotation(); + } + + return 0; + }; + + /** + * Function: init + * + * Creates and initializes the shapes required for this handle. + */ + init = () => { + var html = this.isHtmlRequired(); + + if (this.image != null) { + this.shape = new mxImageShape(new mxRectangle(0, 0, this.image.width, this.image.height), this.image.src); + this.shape.preserveImageAspect = false; + } else if (this.shape == null) { + this.shape = this.createShape(html); + } + + this.initShape(html); + }; + + /** + * Function: createShape + * + * Creates and returns the shape for this handle. + */ + createShape = (html) => { + var bounds = new mxRectangle(0, 0, mxConstants.HANDLE_SIZE, mxConstants.HANDLE_SIZE); + + return new mxRectangleShape(bounds, mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR); + }; + + /** + * Function: initShape + * + * Initializes and sets its cursor. + */ + initShape = (html) => { + if (html && this.shape.isHtmlAllowed()) { + this.shape.dialect = mxConstants.DIALECT_STRICTHTML; + this.shape.init(this.graph.container); + } else { + this.shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? + mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG; + + if (this.cursor != null) { + this.shape.init(this.graph.getView().getOverlayPane()); + } + } + + mxEvent.redirectMouseEvents(this.shape.node, this.graph, this.state); + this.shape.node.style.cursor = this.cursor; + }; + + /** + * Function: redraw + * + * Renders the shape for this handle. + */ + redraw = () => { + if (this.shape != null && this.state.shape != null) { + var pt = this.getPosition(this.state.getPaintBounds()); + + if (pt != null) { + var alpha = mxUtils.toRadians(this.getTotalRotation()); + pt = this.rotatePoint(this.flipPoint(pt), alpha); + + var scale = this.graph.view.scale; + var tr = this.graph.view.translate; + this.shape.bounds.x = Math.floor((pt.x + tr.x) * scale - this.shape.bounds.width / 2); + this.shape.bounds.y = Math.floor((pt.y + tr.y) * scale - this.shape.bounds.height / 2); + + // Needed to force update of text bounds + this.shape.redraw(); + } + } + }; + + /** + * Function: isHtmlRequired + * + * Returns true if this handle should be rendered in HTML. This returns true if + * the text node is in the graph container. + */ + isHtmlRequired = () => { + return this.state.text != null && this.state.text.node.parentNode == this.graph.container; + }; + + /** + * Function: rotatePoint + * + * Rotates the point by the given angle. + */ + rotatePoint = (pt, alpha) => { var bounds = this.state.getCellBounds(); - - if (this.state.shape.flipH) - { - pt.x = 2 * bounds.x + bounds.width - pt.x; + var cx = new mxPoint(bounds.getCenterX(), bounds.getCenterY()); + var cos = Math.cos(alpha); + var sin = Math.sin(alpha); + + return mxUtils.getRotatedPoint(pt, cos, sin, cx); + }; + + /** + * Function: flipPoint + * + * Flips the given point vertically and/or horizontally. + */ + flipPoint = (pt) => { + if (this.state.shape != null) { + var bounds = this.state.getCellBounds(); + + if (this.state.shape.flipH) { + pt.x = 2 * bounds.x + bounds.width - pt.x; + } + + if (this.state.shape.flipV) { + pt.y = 2 * bounds.y + bounds.height - pt.y; + } } - - if (this.state.shape.flipV) - { - pt.y = 2 * bounds.y + bounds.height - pt.y; + + return pt; + }; + + /** + * Function: snapPoint + * + * Snaps the given point to the grid if ignore is false. This modifies + * the given point in-place and also returns it. + */ + snapPoint = (pt, ignore) => { + if (!ignore) { + pt.x = this.graph.snap(pt.x); + pt.y = this.graph.snap(pt.y); } - } - - return pt; -}; -/** - * Function: snapPoint - * - * Snaps the given point to the grid if ignore is false. This modifies - * the given point in-place and also returns it. - */ -snapPoint = (pt, ignore)=> -{ - if (!ignore) - { - pt.x = this.graph.snap(pt.x); - pt.y = this.graph.snap(pt.y); - } - - return pt; -}; + return pt; + }; -/** - * Function: setVisible - * - * Shows or hides this handle. - */ -setVisible = (visible)=> -{ - if (this.shape != null && this.shape.node != null) - { - this.shape.node.style.display = (visible) ? '' : 'none'; - } -}; + /** + * Function: setVisible + * + * Shows or hides this handle. + */ + setVisible = (visible) => { + if (this.shape != null && this.shape.node != null) { + this.shape.node.style.display = (visible) ? '' : 'none'; + } + }; -/** - * Function: reset - * - * Resets the state of this handle by setting its visibility to true. - */ -reset = ()=> -{ - this.setVisible(true); - this.state.style = this.graph.getCellStyle(this.state.cell); - this.positionChanged(); -}; + /** + * Function: reset + * + * Resets the state of this handle by setting its visibility to true. + */ + reset = () => { + this.setVisible(true); + this.state.style = this.graph.getCellStyle(this.state.cell); + this.positionChanged(); + }; -/** - * Function: destroy - * - * Destroys this handle. - */ -destroy = ()=> -{ - if (this.shape != null) - { - this.shape.destroy(); - this.shape = null; - } -}; + /** + * Function: destroy + * + * Destroys this handle. + */ + destroy = () => { + if (this.shape != null) { + this.shape.destroy(); + this.shape = null; + } + }; +} + +export default mxHandle; diff --git a/src/js/handler/mxKeyHandler.js b/src/js/handler/mxKeyHandler.js index cb0cf5704..74432cf29 100644 --- a/src/js/handler/mxKeyHandler.js +++ b/src/js/handler/mxKeyHandler.js @@ -2,416 +2,387 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * Class: mxKeyHandler - * - * Event handler that listens to keystroke events. This is not a singleton, - * however, it is normally only required once if the target is the document - * element (default). - * - * This handler installs a key event listener in the topmost DOM node and - * processes all events that originate from descandants of - * or from the topmost DOM node. The latter means that all unhandled keystrokes - * are handled by this object regardless of the focused state of the . - * - * Example: - * - * The following example creates a key handler that listens to the delete key - * (46) and deletes the selection cells if the graph is enabled. - * - * (code) - * var keyHandler = new mxKeyHandler(graph); - * keyHandler.bindKey(46, (evt)=> - * { - * if (graph.isEnabled()) - * { - * graph.removeCells(); - * } - * }); - * (end) - * - * Keycodes: - * - * See http://tinyurl.com/yp8jgl or http://tinyurl.com/229yqw for a list of - * keycodes or install a key event listener into the document element and print - * the key codes of the respective events to the console. - * - * To support the Command key and the Control key on the Mac, the following - * code can be used. - * - * (code) - * keyHandler.getFunction = (evt)=> - * { - * if (evt != null) - * { - * return (mxEvent.isControlDown(evt) || (mxClient.IS_MAC && evt.metaKey)) ? this.controlKeys[evt.keyCode] : this.normalKeys[evt.keyCode]; - * } - * - * return null; - * }; - * (end) - * - * Constructor: mxKeyHandler - * - * Constructs an event handler that executes functions bound to specific - * keystrokes. - * - * Parameters: - * - * graph - Reference to the associated . - * target - Optional reference to the event target. If null, the document - * element is used as the event target, that is, the object where the key - * event listener is installed. - */ -function mxKeyHandler(graph, target) -{ - if (graph != null) - { - this.graph = graph; - this.target = target || document.documentElement; - - // Creates the arrays to map from keycodes to functions - this.normalKeys = []; - this.shiftKeys = []; - this.controlKeys = []; - this.controlShiftKeys = []; - - this.keydownHandler = mxUtils.bind(this, (evt)=> - { - this.keyDown(evt); - }); - // Installs the keystroke listener in the target - mxEvent.addListener(this.target, 'keydown', this.keydownHandler); - } -}; +class mxKeyHandler { + /** + * Variable: graph + * + * Reference to the associated with this handler. + */ + graph = null; -/** - * Variable: graph - * - * Reference to the associated with this handler. - */ -graph = null; + /** + * Variable: target + * + * Reference to the target DOM, that is, the DOM node where the key event + * listeners are installed. + */ + target = null; -/** - * Variable: target - * - * Reference to the target DOM, that is, the DOM node where the key event - * listeners are installed. - */ -target = null; + /** + * Variable: normalKeys + * + * Maps from keycodes to functions for non-pressed control keys. + */ + normalKeys = null; -/** - * Variable: normalKeys - * - * Maps from keycodes to functions for non-pressed control keys. - */ -normalKeys = null; + /** + * Variable: shiftKeys + * + * Maps from keycodes to functions for pressed shift keys. + */ + shiftKeys = null; -/** - * Variable: shiftKeys - * - * Maps from keycodes to functions for pressed shift keys. - */ -shiftKeys = null; + /** + * Variable: controlKeys + * + * Maps from keycodes to functions for pressed control keys. + */ + controlKeys = null; -/** - * Variable: controlKeys - * - * Maps from keycodes to functions for pressed control keys. - */ -controlKeys = null; + /** + * Variable: controlShiftKeys + * + * Maps from keycodes to functions for pressed control and shift keys. + */ + controlShiftKeys = null; -/** - * Variable: controlShiftKeys - * - * Maps from keycodes to functions for pressed control and shift keys. - */ -controlShiftKeys = null; + /** + * Variable: enabled + * + * Specifies if events are handled. Default is true. + */ + enabled = true; -/** - * Variable: enabled - * - * Specifies if events are handled. Default is true. - */ -enabled = true; + /** + * Class: mxKeyHandler + * + * Event handler that listens to keystroke events. This is not a singleton, + * however, it is normally only required once if the target is the document + * element (default). + * + * This handler installs a key event listener in the topmost DOM node and + * processes all events that originate from descandants of + * or from the topmost DOM node. The latter means that all unhandled keystrokes + * are handled by this object regardless of the focused state of the . + * + * Example: + * + * The following example creates a key handler that listens to the delete key + * (46) and deletes the selection cells if the graph is enabled. + * + * (code) + * var keyHandler = new mxKeyHandler(graph); + * keyHandler.bindKey(46, (evt)=> + * { + * if (graph.isEnabled()) + * { + * graph.removeCells(); + * } + * }); + * (end) + * + * Keycodes: + * + * See http://tinyurl.com/yp8jgl or http://tinyurl.com/229yqw for a list of + * keycodes or install a key event listener into the document element and print + * the key codes of the respective events to the console. + * + * To support the Command key and the Control key on the Mac, the following + * code can be used. + * + * (code) + * keyHandler.getFunction = (evt)=> + * { + * if (evt != null) + * { + * return (mxEvent.isControlDown(evt) || (mxClient.IS_MAC && evt.metaKey)) ? this.controlKeys[evt.keyCode] : this.normalKeys[evt.keyCode]; + * } + * + * return null; + * }; + * (end) + * + * Constructor: mxKeyHandler + * + * Constructs an event handler that executes functions bound to specific + * keystrokes. + * + * Parameters: + * + * graph - Reference to the associated . + * target - Optional reference to the event target. If null, the document + * element is used as the event target, that is, the object where the key + * event listener is installed. + */ + constructor(graph, target) { + if (graph != null) { + this.graph = graph; + this.target = target || document.documentElement; -/** - * Function: isEnabled - * - * Returns true if events are handled. This implementation returns - * . - */ -isEnabled = ()=> -{ - return this.enabled; -}; + // Creates the arrays to map from keycodes to functions + this.normalKeys = []; + this.shiftKeys = []; + this.controlKeys = []; + this.controlShiftKeys = []; -/** - * Function: setEnabled - * - * Enables or disables event handling by updating . - * - * Parameters: - * - * enabled - Boolean that specifies the new enabled state. - */ -setEnabled = (enabled)=> -{ - this.enabled = enabled; -}; + this.keydownHandler = mxUtils.bind(this, (evt) => { + this.keyDown(evt); + }); -/** - * Function: bindKey - * - * Binds the specified keycode to the given function. This binding is used - * if the control key is not pressed. - * - * Parameters: - * - * code - Integer that specifies the keycode. - * funct - JavaScript function that takes the key event as an argument. - */ -bindKey = (code, funct)=> -{ - this.normalKeys[code] = funct; -}; + // Installs the keystroke listener in the target + mxEvent.addListener(this.target, 'keydown', this.keydownHandler); + } + }; -/** - * Function: bindShiftKey - * - * Binds the specified keycode to the given function. This binding is used - * if the shift key is pressed. - * - * Parameters: - * - * code - Integer that specifies the keycode. - * funct - JavaScript function that takes the key event as an argument. - */ -bindShiftKey = (code, funct)=> -{ - this.shiftKeys[code] = funct; -}; + /** + * Function: isEnabled + * + * Returns true if events are handled. This implementation returns + * . + */ + isEnabled = () => { + return this.enabled; + }; -/** - * Function: bindControlKey - * - * Binds the specified keycode to the given function. This binding is used - * if the control key is pressed. - * - * Parameters: - * - * code - Integer that specifies the keycode. - * funct - JavaScript function that takes the key event as an argument. - */ -bindControlKey = (code, funct)=> -{ - this.controlKeys[code] = funct; -}; + /** + * Function: setEnabled + * + * Enables or disables event handling by updating . + * + * Parameters: + * + * enabled - Boolean that specifies the new enabled state. + */ + setEnabled = (enabled) => { + this.enabled = enabled; + }; -/** - * Function: bindControlShiftKey - * - * Binds the specified keycode to the given function. This binding is used - * if the control and shift key are pressed. - * - * Parameters: - * - * code - Integer that specifies the keycode. - * funct - JavaScript function that takes the key event as an argument. - */ -bindControlShiftKey = (code, funct)=> -{ - this.controlShiftKeys[code] = funct; -}; + /** + * Function: bindKey + * + * Binds the specified keycode to the given function. This binding is used + * if the control key is not pressed. + * + * Parameters: + * + * code - Integer that specifies the keycode. + * funct - JavaScript function that takes the key event as an argument. + */ + bindKey = (code, funct) => { + this.normalKeys[code] = funct; + }; -/** - * Function: isControlDown - * - * Returns true if the control key is pressed. This uses . - * - * Parameters: - * - * evt - Key event whose control key pressed state should be returned. - */ -isControlDown = (evt)=> -{ - return mxEvent.isControlDown(evt); -}; + /** + * Function: bindShiftKey + * + * Binds the specified keycode to the given function. This binding is used + * if the shift key is pressed. + * + * Parameters: + * + * code - Integer that specifies the keycode. + * funct - JavaScript function that takes the key event as an argument. + */ + bindShiftKey = (code, funct) => { + this.shiftKeys[code] = funct; + }; -/** - * Function: getFunction - * - * Returns the function associated with the given key event or null if no - * function is associated with the given event. - * - * Parameters: - * - * evt - Key event whose associated function should be returned. - */ -getFunction = (evt)=> -{ - if (evt != null && !mxEvent.isAltDown(evt)) - { - if (this.isControlDown(evt)) - { - if (mxEvent.isShiftDown(evt)) - { - return this.controlShiftKeys[evt.keyCode]; - } - else - { - return this.controlKeys[evt.keyCode]; + /** + * Function: bindControlKey + * + * Binds the specified keycode to the given function. This binding is used + * if the control key is pressed. + * + * Parameters: + * + * code - Integer that specifies the keycode. + * funct - JavaScript function that takes the key event as an argument. + */ + bindControlKey = (code, funct) => { + this.controlKeys[code] = funct; + }; + + /** + * Function: bindControlShiftKey + * + * Binds the specified keycode to the given function. This binding is used + * if the control and shift key are pressed. + * + * Parameters: + * + * code - Integer that specifies the keycode. + * funct - JavaScript function that takes the key event as an argument. + */ + bindControlShiftKey = (code, funct) => { + this.controlShiftKeys[code] = funct; + }; + + /** + * Function: isControlDown + * + * Returns true if the control key is pressed. This uses . + * + * Parameters: + * + * evt - Key event whose control key pressed state should be returned. + */ + isControlDown = (evt) => { + return mxEvent.isControlDown(evt); + }; + + /** + * Function: getFunction + * + * Returns the function associated with the given key event or null if no + * function is associated with the given event. + * + * Parameters: + * + * evt - Key event whose associated function should be returned. + */ + getFunction = (evt) => { + if (evt != null && !mxEvent.isAltDown(evt)) { + if (this.isControlDown(evt)) { + if (mxEvent.isShiftDown(evt)) { + return this.controlShiftKeys[evt.keyCode]; + } else { + return this.controlKeys[evt.keyCode]; + } + } else { + if (mxEvent.isShiftDown(evt)) { + return this.shiftKeys[evt.keyCode]; + } else { + return this.normalKeys[evt.keyCode]; + } } } - else - { - if (mxEvent.isShiftDown(evt)) - { - return this.shiftKeys[evt.keyCode]; + + return null; + }; + + /** + * Function: isGraphEvent + * + * Returns true if the event should be processed by this handler, that is, + * if the event source is either the target, one of its direct children, a + * descendant of the , or the of the + * . + * + * Parameters: + * + * evt - Key event that represents the keystroke. + */ + isGraphEvent = (evt) => { + var source = mxEvent.getSource(evt); + + // Accepts events from the target object or + // in-place editing inside graph + if ((source == this.target || source.parentNode == this.target) || + (this.graph.cellEditor != null && this.graph.cellEditor.isEventSource(evt))) { + return true; + } + + // Accepts events from inside the container + return mxUtils.isAncestorNode(this.graph.container, source); + }; + + /** + * Function: keyDown + * + * Handles the event by invoking the function bound to the respective keystroke + * if returns true for the given event and if + * returns false, except for escape for which + * is not invoked. + * + * Parameters: + * + * evt - Key event that represents the keystroke. + */ + keyDown = (evt) => { + if (this.isEnabledForEvent(evt)) { + // Cancels the editing if escape is pressed + if (evt.keyCode == 27 /* Escape */) { + this.escape(evt); } - else - { - return this.normalKeys[evt.keyCode]; + + // Invokes the function for the keystroke + else if (!this.isEventIgnored(evt)) { + var boundFunction = this.getFunction(evt); + + if (boundFunction != null) { + boundFunction(evt); + mxEvent.consume(evt); + } } } - } - - return null; -}; - -/** - * Function: isGraphEvent - * - * Returns true if the event should be processed by this handler, that is, - * if the event source is either the target, one of its direct children, a - * descendant of the , or the of the - * . - * - * Parameters: - * - * evt - Key event that represents the keystroke. - */ -isGraphEvent = (evt)=> -{ - var source = mxEvent.getSource(evt); - - // Accepts events from the target object or - // in-place editing inside graph - if ((source == this.target || source.parentNode == this.target) || - (this.graph.cellEditor != null && this.graph.cellEditor.isEventSource(evt))) - { - return true; - } - - // Accepts events from inside the container - return mxUtils.isAncestorNode(this.graph.container, source); -}; + }; -/** - * Function: keyDown - * - * Handles the event by invoking the function bound to the respective keystroke - * if returns true for the given event and if - * returns false, except for escape for which - * is not invoked. - * - * Parameters: - * - * evt - Key event that represents the keystroke. - */ -keyDown = (evt)=> -{ - if (this.isEnabledForEvent(evt)) - { - // Cancels the editing if escape is pressed - if (evt.keyCode == 27 /* Escape */) - { - this.escape(evt); + /** + * Function: isEnabledForEvent + * + * Returns true if the given event should be handled. is + * called later if the event is not an escape key stroke, in which case + * is called. This implementation returns true if + * returns true for both, this handler and , if the event is not + * consumed and if returns true. + * + * Parameters: + * + * evt - Key event that represents the keystroke. + */ + isEnabledForEvent = (evt) => { + return (this.graph.isEnabled() && !mxEvent.isConsumed(evt) && + this.isGraphEvent(evt) && this.isEnabled()); + }; + + /** + * Function: isEventIgnored + * + * Returns true if the given keystroke should be ignored. This returns + * graph.isEditing(). + * + * Parameters: + * + * evt - Key event that represents the keystroke. + */ + isEventIgnored = (evt) => { + return this.graph.isEditing(); + }; + + /** + * Function: escape + * + * Hook to process ESCAPE keystrokes. This implementation invokes + * to cancel the current editing, connecting + * and/or other ongoing modifications. + * + * Parameters: + * + * evt - Key event that represents the keystroke. Possible keycode in this + * case is 27 (ESCAPE). + */ + escape = (evt) => { + if (this.graph.isEscapeEnabled()) { + this.graph.escape(evt); } - - // Invokes the function for the keystroke - else if (!this.isEventIgnored(evt)) - { - var boundFunction = this.getFunction(evt); - - if (boundFunction != null) - { - boundFunction(evt); - mxEvent.consume(evt); - } + }; + + /** + * Function: destroy + * + * Destroys the handler and all its references into the DOM. This does + * normally not need to be called, it is called automatically when the + * window unloads (in IE). + */ + destroy = () => { + if (this.target != null && this.keydownHandler != null) { + mxEvent.removeListener(this.target, 'keydown', this.keydownHandler); + this.keydownHandler = null; } - } -}; -/** - * Function: isEnabledForEvent - * - * Returns true if the given event should be handled. is - * called later if the event is not an escape key stroke, in which case - * is called. This implementation returns true if - * returns true for both, this handler and , if the event is not - * consumed and if returns true. - * - * Parameters: - * - * evt - Key event that represents the keystroke. - */ -isEnabledForEvent = (evt)=> -{ - return (this.graph.isEnabled() && !mxEvent.isConsumed(evt) && - this.isGraphEvent(evt) && this.isEnabled()); -}; + this.target = null; + }; +} -/** - * Function: isEventIgnored - * - * Returns true if the given keystroke should be ignored. This returns - * graph.isEditing(). - * - * Parameters: - * - * evt - Key event that represents the keystroke. - */ -isEventIgnored = (evt)=> -{ - return this.graph.isEditing(); -}; - -/** - * Function: escape - * - * Hook to process ESCAPE keystrokes. This implementation invokes - * to cancel the current editing, connecting - * and/or other ongoing modifications. - * - * Parameters: - * - * evt - Key event that represents the keystroke. Possible keycode in this - * case is 27 (ESCAPE). - */ -escape = (evt)=> -{ - if (this.graph.isEscapeEnabled()) - { - this.graph.escape(evt); - } -}; - -/** - * Function: destroy - * - * Destroys the handler and all its references into the DOM. This does - * normally not need to be called, it is called automatically when the - * window unloads (in IE). - */ -destroy = ()=> -{ - if (this.target != null && this.keydownHandler != null) - { - mxEvent.removeListener(this.target, 'keydown', this.keydownHandler); - this.keydownHandler = null; - } - - this.target = null; -}; +export default mxKeyHandler; diff --git a/src/js/handler/mxPanningHandler.js b/src/js/handler/mxPanningHandler.js index 5b8f88ae2..e93a40a3a 100644 --- a/src/js/handler/mxPanningHandler.js +++ b/src/js/handler/mxPanningHandler.js @@ -2,493 +2,450 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * Class: mxPanningHandler - * - * Event handler that pans and creates popupmenus. To use the left - * mousebutton for panning without interfering with cell moving and - * resizing, use and . For grid size - * steps while panning, use . This handler is built-into - * and enabled using . - * - * Constructor: mxPanningHandler - * - * Constructs an event handler that creates a - * and pans the graph. - * - * Event: mxEvent.PAN_START - * - * Fires when the panning handler changes its state to true. The - * event property contains the corresponding . - * - * Event: mxEvent.PAN - * - * Fires while handle is processing events. The event property contains - * the corresponding . - * - * Event: mxEvent.PAN_END - * - * Fires when the panning handler changes its state to false. The - * event property contains the corresponding . - */ -function mxPanningHandler(graph) -{ - if (graph != null) - { - this.graph = graph; - this.graph.addMouseListener(this); - // Handles force panning event - this.forcePanningHandler = mxUtils.bind(this, (sender, evt)=> - { - var evtName = evt.getProperty('eventName'); - var me = evt.getProperty('event'); +class mxPanningHandler extends mxEventSource { + /** + * Variable: graph + * + * Reference to the enclosing . + */ + graph = null; - if (evtName == mxEvent.MOUSE_DOWN && this.isForcePanningEvent(me)) - { - this.start(me); - this.active = true; - this.fireEvent(new mxEventObject(mxEvent.PAN_START, 'event', me)); - me.consume(); - } - }); + /** + * Variable: useLeftButtonForPanning + * + * Specifies if panning should be active for the left mouse button. + * Setting this to true may conflict with . Default is false. + */ + useLeftButtonForPanning = false; - this.graph.addListener(mxEvent.FIRE_MOUSE_EVENT, this.forcePanningHandler); + /** + * Variable: usePopupTrigger + * + * Specifies if should also be used for panning. + */ + usePopupTrigger = true; - // Handles pinch gestures - this.gestureHandler = mxUtils.bind(this, (sender, eo)=> - { - if (this.isPinchEnabled()) - { - var evt = eo.getProperty('event'); + /** + * Variable: ignoreCell + * + * Specifies if panning should be active even if there is a cell under the + * mousepointer. Default is false. + */ + ignoreCell = false; - if (!mxEvent.isConsumed(evt) && evt.type == 'gesturestart') - { - this.initialScale = this.graph.view.scale; + /** + * Variable: previewEnabled + * + * Specifies if the panning should be previewed. Default is true. + */ + previewEnabled = true; - // Forces start of panning when pinch gesture starts - if (!this.active && this.mouseDownEvent != null) - { - this.start(this.mouseDownEvent); - this.mouseDownEvent = null; + /** + * Variable: useGrid + * + * Specifies if the panning steps should be aligned to the grid size. + * Default is false. + */ + useGrid = false; + + /** + * Variable: panningEnabled + * + * Specifies if panning should be enabled. Default is true. + */ + panningEnabled = true; + + /** + * Variable: pinchEnabled + * + * Specifies if pinch gestures should be handled as zoom. Default is true. + */ + pinchEnabled = true; + + /** + * Variable: maxScale + * + * Specifies the maximum scale. Default is 8. + */ + maxScale = 8; + + /** + * Variable: minScale + * + * Specifies the minimum scale. Default is 0.01. + */ + minScale = 0.01; + + /** + * Variable: dx + * + * Holds the current horizontal offset. + */ + dx = null; + + /** + * Variable: dy + * + * Holds the current vertical offset. + */ + dy = null; + + /** + * Variable: startX + * + * Holds the x-coordinate of the start point. + */ + startX = 0; + + /** + * Variable: startY + * + * Holds the y-coordinate of the start point. + */ + startY = 0; + + /** + * Class: mxPanningHandler + * + * Event handler that pans and creates popupmenus. To use the left + * mousebutton for panning without interfering with cell moving and + * resizing, use and . For grid size + * steps while panning, use . This handler is built-into + * and enabled using . + * + * Constructor: mxPanningHandler + * + * Constructs an event handler that creates a + * and pans the graph. + * + * Event: mxEvent.PAN_START + * + * Fires when the panning handler changes its state to true. The + * event property contains the corresponding . + * + * Event: mxEvent.PAN + * + * Fires while handle is processing events. The event property contains + * the corresponding . + * + * Event: mxEvent.PAN_END + * + * Fires when the panning handler changes its state to false. The + * event property contains the corresponding . + */ + constructor(graph) { + // super not called + if (graph != null) { + this.graph = graph; + this.graph.addMouseListener(this); + + // Handles force panning event + this.forcePanningHandler = mxUtils.bind(this, (sender, evt) => { + var evtName = evt.getProperty('eventName'); + var me = evt.getProperty('event'); + + if (evtName == mxEvent.MOUSE_DOWN && this.isForcePanningEvent(me)) { + this.start(me); + this.active = true; + this.fireEvent(new mxEventObject(mxEvent.PAN_START, 'event', me)); + me.consume(); + } + }); + + this.graph.addListener(mxEvent.FIRE_MOUSE_EVENT, this.forcePanningHandler); + + // Handles pinch gestures + this.gestureHandler = mxUtils.bind(this, (sender, eo) => { + if (this.isPinchEnabled()) { + var evt = eo.getProperty('event'); + + if (!mxEvent.isConsumed(evt) && evt.type == 'gesturestart') { + this.initialScale = this.graph.view.scale; + + // Forces start of panning when pinch gesture starts + if (!this.active && this.mouseDownEvent != null) { + this.start(this.mouseDownEvent); + this.mouseDownEvent = null; + } + } else if (evt.type == 'gestureend' && this.initialScale != null) { + this.initialScale = null; + } + + if (this.initialScale != null) { + this.zoomGraph(evt); } } - else if (evt.type == 'gestureend' && this.initialScale != null) - { - this.initialScale = null; - } + }); - if (this.initialScale != null) - { - this.zoomGraph(evt); - } - } - }); + this.graph.addListener(mxEvent.GESTURE, this.gestureHandler); - this.graph.addListener(mxEvent.GESTURE, this.gestureHandler); - - this.mouseUpListener = mxUtils.bind(this, ()=> - { - if (this.active) - { + this.mouseUpListener = mxUtils.bind(this, () => { + if (this.active) { this.reset(); } - }); + }); - // Stops scrolling on every mouseup anywhere in the document - mxEvent.addListener(document, 'mouseup', this.mouseUpListener); - } -}; - -/** - * Extends mxEventSource. - */ -mxPanningHandler.prototype = new mxEventSource(); -constructor = mxPanningHandler; - -/** - * Variable: graph - * - * Reference to the enclosing . - */ -graph = null; - -/** - * Variable: useLeftButtonForPanning - * - * Specifies if panning should be active for the left mouse button. - * Setting this to true may conflict with . Default is false. - */ -useLeftButtonForPanning = false; - -/** - * Variable: usePopupTrigger - * - * Specifies if should also be used for panning. - */ -usePopupTrigger = true; - -/** - * Variable: ignoreCell - * - * Specifies if panning should be active even if there is a cell under the - * mousepointer. Default is false. - */ -ignoreCell = false; - -/** - * Variable: previewEnabled - * - * Specifies if the panning should be previewed. Default is true. - */ -previewEnabled = true; - -/** - * Variable: useGrid - * - * Specifies if the panning steps should be aligned to the grid size. - * Default is false. - */ -useGrid = false; - -/** - * Variable: panningEnabled - * - * Specifies if panning should be enabled. Default is true. - */ -panningEnabled = true; - -/** - * Variable: pinchEnabled - * - * Specifies if pinch gestures should be handled as zoom. Default is true. - */ -pinchEnabled = true; - -/** - * Variable: maxScale - * - * Specifies the maximum scale. Default is 8. - */ -maxScale = 8; - -/** - * Variable: minScale - * - * Specifies the minimum scale. Default is 0.01. - */ -minScale = 0.01; - -/** - * Variable: dx - * - * Holds the current horizontal offset. - */ -dx = null; - -/** - * Variable: dy - * - * Holds the current vertical offset. - */ -dy = null; - -/** - * Variable: startX - * - * Holds the x-coordinate of the start point. - */ -startX = 0; - -/** - * Variable: startY - * - * Holds the y-coordinate of the start point. - */ -startY = 0; - -/** - * Function: isActive - * - * Returns true if the handler is currently active. - */ -isActive = ()=> -{ - return this.active || this.initialScale != null; -}; - -/** - * Function: isPanningEnabled - * - * Returns . - */ -isPanningEnabled = ()=> -{ - return this.panningEnabled; -}; - -/** - * Function: setPanningEnabled - * - * Sets . - */ -setPanningEnabled = (value)=> -{ - this.panningEnabled = value; -}; - -/** - * Function: isPinchEnabled - * - * Returns . - */ -isPinchEnabled = ()=> -{ - return this.pinchEnabled; -}; - -/** - * Function: setPinchEnabled - * - * Sets . - */ -setPinchEnabled = (value)=> -{ - this.pinchEnabled = value; -}; - -/** - * Function: isPanningTrigger - * - * Returns true if the given event is a panning trigger for the optional - * given cell. This returns true if control-shift is pressed or if - * is true and the event is a popup trigger. - */ -isPanningTrigger = (me)=> -{ - var evt = me.getEvent(); - - return (this.useLeftButtonForPanning && me.getState() == null && - mxEvent.isLeftMouseButton(evt)) || (mxEvent.isControlDown(evt) && - mxEvent.isShiftDown(evt)) || (this.usePopupTrigger && mxEvent.isPopupTrigger(evt)); -}; - -/** - * Function: isForcePanningEvent - * - * Returns true if the given should start panning. This - * implementation always returns true if is true or for - * multi touch events. - */ -isForcePanningEvent = (me)=> -{ - return this.ignoreCell || mxEvent.isMultiTouchEvent(me.getEvent()); -}; - -/** - * Function: mouseDown - * - * Handles the event by initiating the panning. By consuming the event all - * subsequent events of the gesture are redirected to this handler. - */ -mouseDown = (sender, me)=> -{ - this.mouseDownEvent = me; - - if (!me.isConsumed() && this.isPanningEnabled() && !this.active && this.isPanningTrigger(me)) - { - this.start(me); - this.consumePanningTrigger(me); - } -}; - -/** - * Function: start - * - * Starts panning at the given event. - */ -start = (me)=> -{ - this.dx0 = -this.graph.container.scrollLeft; - this.dy0 = -this.graph.container.scrollTop; - - // Stores the location of the trigger event - this.startX = me.getX(); - this.startY = me.getY(); - this.dx = null; - this.dy = null; - - this.panningTrigger = true; -}; - -/** - * Function: consumePanningTrigger - * - * Consumes the given if it was a panning trigger in - * . The default is to invoke . Note that this - * will block any further event processing. If you haven't disabled built-in - * context menus and require immediate selection of the cell on mouseDown in - * Safari and/or on the Mac, then use the following code: - * - * (code) - * consumePanningTrigger = (me)=> - * { - * if (me.evt.preventDefault) - * { - * me.evt.preventDefault(); - * } - * - * // Stops event processing in IE - * me.evt.returnValue = false; - * - * // Sets local consumed state - * if (!mxClient.IS_SF && !mxClient.IS_MAC) - * { - * me.consumed = true; - * } - * }; - * (end) - */ -consumePanningTrigger = (me)=> -{ - me.consume(); -}; - -/** - * Function: mouseMove - * - * Handles the event by updating the panning on the graph. - */ -mouseMove = (sender, me)=> -{ - this.dx = me.getX() - this.startX; - this.dy = me.getY() - this.startY; - - if (this.active) - { - if (this.previewEnabled) - { - // Applies the grid to the panning steps - if (this.useGrid) - { - this.dx = this.graph.snap(this.dx); - this.dy = this.graph.snap(this.dy); - } - - this.graph.panGraph(this.dx + this.dx0, this.dy + this.dy0); + // Stops scrolling on every mouseup anywhere in the document + mxEvent.addListener(document, 'mouseup', this.mouseUpListener); } + }; - this.fireEvent(new mxEventObject(mxEvent.PAN, 'event', me)); - } - else if (this.panningTrigger) - { - var tmp = this.active; + /** + * Function: isActive + * + * Returns true if the handler is currently active. + */ + isActive = () => { + return this.active || this.initialScale != null; + }; - // Panning is activated only if the mouse is moved - // beyond the graph tolerance - this.active = Math.abs(this.dx) > this.graph.tolerance || Math.abs(this.dy) > this.graph.tolerance; + /** + * Function: isPanningEnabled + * + * Returns . + */ + isPanningEnabled = () => { + return this.panningEnabled; + }; - if (!tmp && this.active) - { - this.fireEvent(new mxEventObject(mxEvent.PAN_START, 'event', me)); + /** + * Function: setPanningEnabled + * + * Sets . + */ + setPanningEnabled = (value) => { + this.panningEnabled = value; + }; + + /** + * Function: isPinchEnabled + * + * Returns . + */ + isPinchEnabled = () => { + return this.pinchEnabled; + }; + + /** + * Function: setPinchEnabled + * + * Sets . + */ + setPinchEnabled = (value) => { + this.pinchEnabled = value; + }; + + /** + * Function: isPanningTrigger + * + * Returns true if the given event is a panning trigger for the optional + * given cell. This returns true if control-shift is pressed or if + * is true and the event is a popup trigger. + */ + isPanningTrigger = (me) => { + var evt = me.getEvent(); + + return (this.useLeftButtonForPanning && me.getState() == null && + mxEvent.isLeftMouseButton(evt)) || (mxEvent.isControlDown(evt) && + mxEvent.isShiftDown(evt)) || (this.usePopupTrigger && mxEvent.isPopupTrigger(evt)); + }; + + /** + * Function: isForcePanningEvent + * + * Returns true if the given should start panning. This + * implementation always returns true if is true or for + * multi touch events. + */ + isForcePanningEvent = (me) => { + return this.ignoreCell || mxEvent.isMultiTouchEvent(me.getEvent()); + }; + + /** + * Function: mouseDown + * + * Handles the event by initiating the panning. By consuming the event all + * subsequent events of the gesture are redirected to this handler. + */ + mouseDown = (sender, me) => { + this.mouseDownEvent = me; + + if (!me.isConsumed() && this.isPanningEnabled() && !this.active && this.isPanningTrigger(me)) { + this.start(me); + this.consumePanningTrigger(me); } - } + }; - if (this.active || this.panningTrigger) - { + /** + * Function: start + * + * Starts panning at the given event. + */ + start = (me) => { + this.dx0 = -this.graph.container.scrollLeft; + this.dy0 = -this.graph.container.scrollTop; + + // Stores the location of the trigger event + this.startX = me.getX(); + this.startY = me.getY(); + this.dx = null; + this.dy = null; + + this.panningTrigger = true; + }; + + /** + * Function: consumePanningTrigger + * + * Consumes the given if it was a panning trigger in + * . The default is to invoke . Note that this + * will block any further event processing. If you haven't disabled built-in + * context menus and require immediate selection of the cell on mouseDown in + * Safari and/or on the Mac, then use the following code: + * + * (code) + * consumePanningTrigger = (me)=> + * { + * if (me.evt.preventDefault) + * { + * me.evt.preventDefault(); + * } + * + * // Stops event processing in IE + * me.evt.returnValue = false; + * + * // Sets local consumed state + * if (!mxClient.IS_SF && !mxClient.IS_MAC) + * { + * me.consumed = true; + * } + * }; + * (end) + */ + consumePanningTrigger = (me) => { me.consume(); - } -}; + }; -/** - * Function: mouseUp - * - * Handles the event by setting the translation on the view or showing the - * popupmenu. - */ -mouseUp = (sender, me)=> -{ - if (this.active) - { - if (this.dx != null && this.dy != null) - { - // Ignores if scrollbars have been used for panning - if (!this.graph.useScrollbarsForPanning || !mxUtils.hasScrollbars(this.graph.container)) - { - var scale = this.graph.getView().scale; - var t = this.graph.getView().translate; - this.graph.panGraph(0, 0); - this.panGraph(t.x + this.dx / scale, t.y + this.dy / scale); + /** + * Function: mouseMove + * + * Handles the event by updating the panning on the graph. + */ + mouseMove = (sender, me) => { + this.dx = me.getX() - this.startX; + this.dy = me.getY() - this.startY; + + if (this.active) { + if (this.previewEnabled) { + // Applies the grid to the panning steps + if (this.useGrid) { + this.dx = this.graph.snap(this.dx); + this.dy = this.graph.snap(this.dy); + } + + this.graph.panGraph(this.dx + this.dx0, this.dy + this.dy0); } + this.fireEvent(new mxEventObject(mxEvent.PAN, 'event', me)); + } else if (this.panningTrigger) { + var tmp = this.active; + + // Panning is activated only if the mouse is moved + // beyond the graph tolerance + this.active = Math.abs(this.dx) > this.graph.tolerance || Math.abs(this.dy) > this.graph.tolerance; + + if (!tmp && this.active) { + this.fireEvent(new mxEventObject(mxEvent.PAN_START, 'event', me)); + } + } + + if (this.active || this.panningTrigger) { me.consume(); } + }; - this.fireEvent(new mxEventObject(mxEvent.PAN_END, 'event', me)); - } + /** + * Function: mouseUp + * + * Handles the event by setting the translation on the view or showing the + * popupmenu. + */ + mouseUp = (sender, me) => { + if (this.active) { + if (this.dx != null && this.dy != null) { + // Ignores if scrollbars have been used for panning + if (!this.graph.useScrollbarsForPanning || !mxUtils.hasScrollbars(this.graph.container)) { + var scale = this.graph.getView().scale; + var t = this.graph.getView().translate; + this.graph.panGraph(0, 0); + this.panGraph(t.x + this.dx / scale, t.y + this.dy / scale); + } - this.reset(); -}; + me.consume(); + } -/** - * Function: zoomGraph - * - * Zooms the graph to the given value and consumed the event if needed. - */ -zoomGraph = (evt)=> -{ - var value = Math.round(this.initialScale * evt.scale * 100) / 100; + this.fireEvent(new mxEventObject(mxEvent.PAN_END, 'event', me)); + } - if (this.minScale != null) - { - value = Math.max(this.minScale, value); - } + this.reset(); + }; - if (this.maxScale != null) - { - value = Math.min(this.maxScale, value); - } + /** + * Function: zoomGraph + * + * Zooms the graph to the given value and consumed the event if needed. + */ + zoomGraph = (evt) => { + var value = Math.round(this.initialScale * evt.scale * 100) / 100; - if (this.graph.view.scale != value) - { - this.graph.zoomTo(value); - mxEvent.consume(evt); - } -}; + if (this.minScale != null) { + value = Math.max(this.minScale, value); + } -/** - * Function: reset - * - * Resets the state of this handler. - */ -reset = ()=> -{ - this.panningTrigger = false; - this.mouseDownEvent = null; - this.active = false; - this.dx = null; - this.dy = null; -}; + if (this.maxScale != null) { + value = Math.min(this.maxScale, value); + } -/** - * Function: panGraph - * - * Pans by the given amount. - */ -panGraph = (dx, dy)=> -{ - this.graph.getView().setTranslate(dx, dy); -}; + if (this.graph.view.scale != value) { + this.graph.zoomTo(value); + mxEvent.consume(evt); + } + }; -/** - * Function: destroy - * - * Destroys the handler and all its resources and DOM nodes. - */ -destroy = ()=> -{ - this.graph.removeMouseListener(this); - this.graph.removeListener(this.forcePanningHandler); - this.graph.removeListener(this.gestureHandler); - mxEvent.removeListener(document, 'mouseup', this.mouseUpListener); -}; + /** + * Function: reset + * + * Resets the state of this handler. + */ + reset = () => { + this.panningTrigger = false; + this.mouseDownEvent = null; + this.active = false; + this.dx = null; + this.dy = null; + }; + + /** + * Function: panGraph + * + * Pans by the given amount. + */ + panGraph = (dx, dy) => { + this.graph.getView().setTranslate(dx, dy); + }; + + /** + * Function: destroy + * + * Destroys the handler and all its resources and DOM nodes. + */ + destroy = () => { + this.graph.removeMouseListener(this); + this.graph.removeListener(this.forcePanningHandler); + this.graph.removeListener(this.gestureHandler); + mxEvent.removeListener(document, 'mouseup', this.mouseUpListener); + }; +} + +export default mxPanningHandler; diff --git a/src/js/handler/mxSelectionCellsHandler.js b/src/js/handler/mxSelectionCellsHandler.js index 2543e1b1d..8fb161907 100644 --- a/src/js/handler/mxSelectionCellsHandler.js +++ b/src/js/handler/mxSelectionCellsHandler.js @@ -2,343 +2,302 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * Class: mxSelectionCellsHandler - * - * An event handler that manages cell handlers and invokes their mouse event - * processing functions. - * - * Group: Events - * - * Event: mxEvent.ADD - * - * Fires if a cell has been added to the selection. The state - * property contains the that has been added. - * - * Event: mxEvent.REMOVE - * - * Fires if a cell has been remove from the selection. The state - * property contains the that has been removed. - * - * Parameters: - * - * graph - Reference to the enclosing . - */ -function mxSelectionCellsHandler(graph) -{ - mxEventSource.call(this); - - this.graph = graph; - this.handlers = new mxDictionary(); - this.graph.addMouseListener(this); - - this.refreshHandler = mxUtils.bind(this, (sender, evt)=> - { - if (this.isEnabled()) - { - this.refresh(); - } - }); - - this.graph.getSelectionModel().addListener(mxEvent.CHANGE, this.refreshHandler); - this.graph.getModel().addListener(mxEvent.CHANGE, this.refreshHandler); - this.graph.getView().addListener(mxEvent.SCALE, this.refreshHandler); - this.graph.getView().addListener(mxEvent.TRANSLATE, this.refreshHandler); - this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, this.refreshHandler); - this.graph.getView().addListener(mxEvent.DOWN, this.refreshHandler); - this.graph.getView().addListener(mxEvent.UP, this.refreshHandler); -}; -/** - * Extends mxEventSource. - */ -mxUtils.extend(mxSelectionCellsHandler, mxEventSource); +class mxSelectionCellsHandler extends mxEventSource { + /** + * Variable: graph + * + * Reference to the enclosing . + */ + graph = null; -/** - * Variable: graph - * - * Reference to the enclosing . - */ -graph = null; + /** + * Variable: enabled + * + * Specifies if events are handled. Default is true. + */ + enabled = true; -/** - * Variable: enabled - * - * Specifies if events are handled. Default is true. - */ -enabled = true; + /** + * Variable: refreshHandler + * + * Keeps a reference to an event listener for later removal. + */ + refreshHandler = null; -/** - * Variable: refreshHandler - * - * Keeps a reference to an event listener for later removal. - */ -refreshHandler = null; + /** + * Variable: maxHandlers + * + * Defines the maximum number of handlers to paint individually. Default is 100. + */ + maxHandlers = 100; -/** - * Variable: maxHandlers - * - * Defines the maximum number of handlers to paint individually. Default is 100. - */ -maxHandlers = 100; + /** + * Variable: handlers + * + * that maps from cells to handlers. + */ + handlers = null; -/** - * Variable: handlers - * - * that maps from cells to handlers. - */ -handlers = null; + /** + * Class: mxSelectionCellsHandler + * + * An event handler that manages cell handlers and invokes their mouse event + * processing functions. + * + * Group: Events + * + * Event: mxEvent.ADD + * + * Fires if a cell has been added to the selection. The state + * property contains the that has been added. + * + * Event: mxEvent.REMOVE + * + * Fires if a cell has been remove from the selection. The state + * property contains the that has been removed. + * + * Parameters: + * + * graph - Reference to the enclosing . + */ + constructor(graph) { + super(); -/** - * Function: isEnabled - * - * Returns . - */ -isEnabled = ()=> -{ - return this.enabled; -}; + this.graph = graph; + this.handlers = new mxDictionary(); + this.graph.addMouseListener(this); -/** - * Function: setEnabled - * - * Sets . - */ -setEnabled = (value)=> -{ - this.enabled = value; -}; + this.refreshHandler = mxUtils.bind(this, (sender, evt) => { + if (this.isEnabled()) { + this.refresh(); + } + }); -/** - * Function: getHandler - * - * Returns the handler for the given cell. - */ -getHandler = (cell)=> -{ - return this.handlers.get(cell); -}; + this.graph.getSelectionModel().addListener(mxEvent.CHANGE, this.refreshHandler); + this.graph.getModel().addListener(mxEvent.CHANGE, this.refreshHandler); + this.graph.getView().addListener(mxEvent.SCALE, this.refreshHandler); + this.graph.getView().addListener(mxEvent.TRANSLATE, this.refreshHandler); + this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, this.refreshHandler); + this.graph.getView().addListener(mxEvent.DOWN, this.refreshHandler); + this.graph.getView().addListener(mxEvent.UP, this.refreshHandler); + }; -/** - * Function: isHandled - * - * Returns true if the given cell has a handler. - */ -isHandled = (cell)=> -{ - return this.getHandler(cell) != null; -}; + /** + * Function: isEnabled + * + * Returns . + */ + isEnabled = () => { + return this.enabled; + }; -/** - * Function: reset - * - * Resets all handlers. - */ -reset = ()=> -{ - this.handlers.visit((key, handler)=> - { - handler.reset.apply(handler); - }); -}; + /** + * Function: setEnabled + * + * Sets . + */ + setEnabled = (value) => { + this.enabled = value; + }; -/** - * Function: getHandledSelectionCells - * - * Reloads or updates all handlers. - */ -getHandledSelectionCells = ()=> -{ - return this.graph.getSelectionCells(); -}; + /** + * Function: getHandler + * + * Returns the handler for the given cell. + */ + getHandler = (cell) => { + return this.handlers.get(cell); + }; -/** - * Function: refresh - * - * Reloads or updates all handlers. - */ -refresh = ()=> -{ - // Removes all existing handlers - var oldHandlers = this.handlers; - this.handlers = new mxDictionary(); - - // Creates handles for all selection cells - var tmp = mxUtils.sortCells(this.getHandledSelectionCells(), false); + /** + * Function: isHandled + * + * Returns true if the given cell has a handler. + */ + isHandled = (cell) => { + return this.getHandler(cell) != null; + }; - // Destroys or updates old handlers - for (var i = 0; i < tmp.length; i++) - { - var state = this.graph.view.getState(tmp[i]); + /** + * Function: reset + * + * Resets all handlers. + */ + reset = () => { + this.handlers.visit((key, handler) => { + handler.reset.apply(handler); + }); + }; - if (state != null) - { - var handler = oldHandlers.remove(tmp[i]); + /** + * Function: getHandledSelectionCells + * + * Reloads or updates all handlers. + */ + getHandledSelectionCells = () => { + return this.graph.getSelectionCells(); + }; - if (handler != null) - { - if (handler.state != state) - { - handler.destroy(); - handler = null; - } - else if (!this.isHandlerActive(handler)) - { - if (handler.refresh != null) - { - handler.refresh(); + /** + * Function: refresh + * + * Reloads or updates all handlers. + */ + refresh = () => { + // Removes all existing handlers + var oldHandlers = this.handlers; + this.handlers = new mxDictionary(); + + // Creates handles for all selection cells + var tmp = mxUtils.sortCells(this.getHandledSelectionCells(), false); + + // Destroys or updates old handlers + for (var i = 0; i < tmp.length; i++) { + var state = this.graph.view.getState(tmp[i]); + + if (state != null) { + var handler = oldHandlers.remove(tmp[i]); + + if (handler != null) { + if (handler.state != state) { + handler.destroy(); + handler = null; + } else if (!this.isHandlerActive(handler)) { + if (handler.refresh != null) { + handler.refresh(); + } + + handler.redraw(); } - - handler.redraw(); + } + + if (handler != null) { + this.handlers.put(tmp[i], handler); } } - - if (handler != null) - { - this.handlers.put(tmp[i], handler); + } + + // Destroys unused handlers + oldHandlers.visit(mxUtils.bind(this, (key, handler) => { + this.fireEvent(new mxEventObject(mxEvent.REMOVE, 'state', handler.state)); + handler.destroy(); + })); + + // Creates new handlers and updates parent highlight on existing handlers + for (var i = 0; i < tmp.length; i++) { + var state = this.graph.view.getState(tmp[i]); + + if (state != null) { + var handler = this.handlers.get(tmp[i]); + + if (handler == null) { + handler = this.graph.createHandler(state); + this.fireEvent(new mxEventObject(mxEvent.ADD, 'state', state)); + this.handlers.put(tmp[i], handler); + } else { + handler.updateParentHighlight(); + } } } - } - - // Destroys unused handlers - oldHandlers.visit(mxUtils.bind(this, (key, handler)=> - { - this.fireEvent(new mxEventObject(mxEvent.REMOVE, 'state', handler.state)); - handler.destroy(); - })); - - // Creates new handlers and updates parent highlight on existing handlers - for (var i = 0; i < tmp.length; i++) - { - var state = this.graph.view.getState(tmp[i]); + }; - if (state != null) - { - var handler = this.handlers.get(tmp[i]); + /** + * Function: isHandlerActive + * + * Returns true if the given handler is active and should not be redrawn. + */ + isHandlerActive = (handler) => { + return handler.index != null; + }; - if (handler == null) - { - handler = this.graph.createHandler(state); - this.fireEvent(new mxEventObject(mxEvent.ADD, 'state', state)); - this.handlers.put(tmp[i], handler); - } - else - { - handler.updateParentHighlight(); + /** + * Function: updateHandler + * + * Updates the handler for the given shape if one exists. + */ + updateHandler = (state) => { + var handler = this.handlers.remove(state.cell); + + if (handler != null) { + // Transfers the current state to the new handler + var index = handler.index; + var x = handler.startX; + var y = handler.startY; + + handler.destroy(); + handler = this.graph.createHandler(state); + + if (handler != null) { + this.handlers.put(state.cell, handler); + + if (index != null && x != null && y != null) { + handler.start(x, y, index); + } } } - } -}; + }; -/** - * Function: isHandlerActive - * - * Returns true if the given handler is active and should not be redrawn. - */ -isHandlerActive = (handler)=> -{ - return handler.index != null; -}; + /** + * Function: mouseDown + * + * Redirects the given event to the handlers. + */ + mouseDown = (sender, me) => { + if (this.graph.isEnabled() && this.isEnabled()) { + var args = [sender, me]; -/** - * Function: updateHandler - * - * Updates the handler for the given shape if one exists. - */ -updateHandler = (state)=> -{ - var handler = this.handlers.remove(state.cell); - - if (handler != null) - { - // Transfers the current state to the new handler - var index = handler.index; - var x = handler.startX; - var y = handler.startY; - - handler.destroy(); - handler = this.graph.createHandler(state); - - if (handler != null) - { - this.handlers.put(state.cell, handler); - - if (index != null && x != null && y != null) - { - handler.start(x, y, index); - } + this.handlers.visit((key, handler) => { + handler.mouseDown.apply(handler, args); + }); } - } -}; + }; -/** - * Function: mouseDown - * - * Redirects the given event to the handlers. - */ -mouseDown = (sender, me)=> -{ - if (this.graph.isEnabled() && this.isEnabled()) - { - var args = [sender, me]; + /** + * Function: mouseMove + * + * Redirects the given event to the handlers. + */ + mouseMove = (sender, me) => { + if (this.graph.isEnabled() && this.isEnabled()) { + var args = [sender, me]; - this.handlers.visit((key, handler)=> - { - handler.mouseDown.apply(handler, args); - }); - } -}; + this.handlers.visit((key, handler) => { + handler.mouseMove.apply(handler, args); + }); + } + }; -/** - * Function: mouseMove - * - * Redirects the given event to the handlers. - */ -mouseMove = (sender, me)=> -{ - if (this.graph.isEnabled() && this.isEnabled()) - { - var args = [sender, me]; + /** + * Function: mouseUp + * + * Redirects the given event to the handlers. + */ + mouseUp = (sender, me) => { + if (this.graph.isEnabled() && this.isEnabled()) { + var args = [sender, me]; - this.handlers.visit((key, handler)=> - { - handler.mouseMove.apply(handler, args); - }); - } -}; + this.handlers.visit((key, handler) => { + handler.mouseUp.apply(handler, args); + }); + } + }; -/** - * Function: mouseUp - * - * Redirects the given event to the handlers. - */ -mouseUp = (sender, me)=> -{ - if (this.graph.isEnabled() && this.isEnabled()) - { - var args = [sender, me]; + /** + * Function: destroy + * + * Destroys the handler and all its resources and DOM nodes. + */ + destroy = () => { + this.graph.removeMouseListener(this); - this.handlers.visit((key, handler)=> - { - handler.mouseUp.apply(handler, args); - }); - } -}; + if (this.refreshHandler != null) { + this.graph.getSelectionModel().removeListener(this.refreshHandler); + this.graph.getModel().removeListener(this.refreshHandler); + this.graph.getView().removeListener(this.refreshHandler); + this.refreshHandler = null; + } + }; +} -/** - * Function: destroy - * - * Destroys the handler and all its resources and DOM nodes. - */ -destroy = ()=> -{ - this.graph.removeMouseListener(this); - - if (this.refreshHandler != null) - { - this.graph.getSelectionModel().removeListener(this.refreshHandler); - this.graph.getModel().removeListener(this.refreshHandler); - this.graph.getView().removeListener(this.refreshHandler); - this.refreshHandler = null; - } -}; +export default mxSelectionCellsHandler; diff --git a/src/js/handler/mxTooltipHandler.js b/src/js/handler/mxTooltipHandler.js index 93eddf482..30623af24 100644 --- a/src/js/handler/mxTooltipHandler.js +++ b/src/js/handler/mxTooltipHandler.js @@ -2,352 +2,322 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * Class: mxTooltipHandler - * - * Graph event handler that displays tooltips. is used to - * get the tooltip for a cell or handle. This handler is built-into - * and enabled using . - * - * Example: - * - * (code> - * new mxTooltipHandler(graph); - * (end) - * - * Constructor: mxTooltipHandler - * - * Constructs an event handler that displays tooltips with the specified - * delay (in milliseconds). If no delay is specified then a default delay - * of 500 ms (0.5 sec) is used. - * - * Parameters: - * - * graph - Reference to the enclosing . - * delay - Optional delay in milliseconds. - */ -function mxTooltipHandler(graph, delay) -{ - if (graph != null) - { - this.graph = graph; - this.delay = delay || 500; - this.graph.addMouseListener(this); - } -}; -/** - * Variable: zIndex - * - * Specifies the zIndex for the tooltip and its shadow. Default is 10005. - */ -zIndex = 10005; +class mxTooltipHandler { + /** + * Class: mxTooltipHandler + * + * Graph event handler that displays tooltips. is used to + * get the tooltip for a cell or handle. This handler is built-into + * and enabled using . + * + * Example: + * + * (code> + * new mxTooltipHandler(graph); + * (end) + * + * Constructor: mxTooltipHandler + * + * Constructs an event handler that displays tooltips with the specified + * delay (in milliseconds). If no delay is specified then a default delay + * of 500 ms (0.5 sec) is used. + * + * Parameters: + * + * graph - Reference to the enclosing . + * delay - Optional delay in milliseconds. + */ + constructor(graph, delay) { + if (graph != null) { + this.graph = graph; + this.delay = delay || 500; + this.graph.addMouseListener(this); + } + }; -/** - * Variable: graph - * - * Reference to the enclosing . - */ -graph = null; + /** + * Variable: zIndex + * + * Specifies the zIndex for the tooltip and its shadow. Default is 10005. + */ + zIndex = 10005; -/** - * Variable: delay - * - * Delay to show the tooltip in milliseconds. Default is 500. - */ -delay = null; + /** + * Variable: graph + * + * Reference to the enclosing . + */ + graph = null; -/** - * Variable: ignoreTouchEvents - * - * Specifies if touch and pen events should be ignored. Default is true. - */ -ignoreTouchEvents = true; + /** + * Variable: delay + * + * Delay to show the tooltip in milliseconds. Default is 500. + */ + delay = null; -/** - * Variable: hideOnHover - * - * Specifies if the tooltip should be hidden if the mouse is moved over the - * current cell. Default is false. - */ -hideOnHover = false; + /** + * Variable: ignoreTouchEvents + * + * Specifies if touch and pen events should be ignored. Default is true. + */ + ignoreTouchEvents = true; -/** - * Variable: destroyed - * - * True if this handler was destroyed using . - */ -destroyed = false; + /** + * Variable: hideOnHover + * + * Specifies if the tooltip should be hidden if the mouse is moved over the + * current cell. Default is false. + */ + hideOnHover = false; -/** - * Variable: enabled - * - * Specifies if events are handled. Default is true. - */ -enabled = true; + /** + * Variable: destroyed + * + * True if this handler was destroyed using . + */ + destroyed = false; -/** - * Function: isEnabled - * - * Returns true if events are handled. This implementation - * returns . - */ -isEnabled = ()=> -{ - return this.enabled; -}; + /** + * Variable: enabled + * + * Specifies if events are handled. Default is true. + */ + enabled = true; -/** - * Function: setEnabled - * - * Enables or disables event handling. This implementation - * updates . - */ -setEnabled = (enabled)=> -{ - this.enabled = enabled; -}; + /** + * Function: isEnabled + * + * Returns true if events are handled. This implementation + * returns . + */ + isEnabled = () => { + return this.enabled; + }; -/** - * Function: isHideOnHover - * - * Returns . - */ -isHideOnHover = ()=> -{ - return this.hideOnHover; -}; + /** + * Function: setEnabled + * + * Enables or disables event handling. This implementation + * updates . + */ + setEnabled = (enabled) => { + this.enabled = enabled; + }; -/** - * Function: setHideOnHover - * - * Sets . - */ -setHideOnHover = (value)=> -{ - this.hideOnHover = value; -}; + /** + * Function: isHideOnHover + * + * Returns . + */ + isHideOnHover = () => { + return this.hideOnHover; + }; -/** - * Function: init - * - * Initializes the DOM nodes required for this tooltip handler. - */ -init = ()=> -{ - if (document.body != null) - { - this.div = document.createElement('div'); - this.div.className = 'mxTooltip'; - this.div.style.visibility = 'hidden'; + /** + * Function: setHideOnHover + * + * Sets . + */ + setHideOnHover = (value) => { + this.hideOnHover = value; + }; - document.body.appendChild(this.div); + /** + * Function: init + * + * Initializes the DOM nodes required for this tooltip handler. + */ + init = () => { + if (document.body != null) { + this.div = document.createElement('div'); + this.div.className = 'mxTooltip'; + this.div.style.visibility = 'hidden'; - mxEvent.addGestureListeners(this.div, mxUtils.bind(this, (evt)=> - { - var source = mxEvent.getSource(evt); - - if (source.nodeName != 'A') - { + document.body.appendChild(this.div); + + mxEvent.addGestureListeners(this.div, mxUtils.bind(this, (evt) => { + var source = mxEvent.getSource(evt); + + if (source.nodeName != 'A') { + this.hideTooltip(); + } + })); + } + }; + + /** + * Function: getStateForEvent + * + * Returns the to be used for showing a tooltip for this event. + */ + getStateForEvent = (me) => { + return me.getState(); + }; + + /** + * Function: mouseDown + * + * Handles the event by initiating a rubberband selection. By consuming the + * event all subsequent events of the gesture are redirected to this + * handler. + */ + mouseDown = (sender, me) => { + this.reset(me, false); + this.hideTooltip(); + }; + + /** + * Function: mouseMove + * + * Handles the event by updating the rubberband selection. + */ + mouseMove = (sender, me) => { + if (me.getX() != this.lastX || me.getY() != this.lastY) { + this.reset(me, true); + var state = this.getStateForEvent(me); + + if (this.isHideOnHover() || state != this.state || (me.getSource() != this.node && + (!this.stateSource || (state != null && this.stateSource == + (me.isSource(state.shape) || !me.isSource(state.text)))))) { this.hideTooltip(); } - })); - } -}; + } -/** - * Function: getStateForEvent - * - * Returns the to be used for showing a tooltip for this event. - */ -getStateForEvent = (me)=> -{ - return me.getState(); -}; + this.lastX = me.getX(); + this.lastY = me.getY(); + }; -/** - * Function: mouseDown - * - * Handles the event by initiating a rubberband selection. By consuming the - * event all subsequent events of the gesture are redirected to this - * handler. - */ -mouseDown = (sender, me)=> -{ - this.reset(me, false); - this.hideTooltip(); -}; - -/** - * Function: mouseMove - * - * Handles the event by updating the rubberband selection. - */ -mouseMove = (sender, me)=> -{ - if (me.getX() != this.lastX || me.getY() != this.lastY) - { + /** + * Function: mouseUp + * + * Handles the event by resetting the tooltip timer or hiding the existing + * tooltip. + */ + mouseUp = (sender, me) => { this.reset(me, true); - var state = this.getStateForEvent(me); - - if (this.isHideOnHover() || state != this.state || (me.getSource() != this.node && - (!this.stateSource || (state != null && this.stateSource == - (me.isSource(state.shape) || !me.isSource(state.text)))))) - { - this.hideTooltip(); + this.hideTooltip(); + }; + + + /** + * Function: resetTimer + * + * Resets the timer. + */ + resetTimer = () => { + if (this.thread != null) { + window.clearTimeout(this.thread); + this.thread = null; } - } - - this.lastX = me.getX(); - this.lastY = me.getY(); -}; + }; -/** - * Function: mouseUp - * - * Handles the event by resetting the tooltip timer or hiding the existing - * tooltip. - */ -mouseUp = (sender, me)=> -{ - this.reset(me, true); - this.hideTooltip(); -}; + /** + * Function: reset + * + * Resets and/or restarts the timer to trigger the display of the tooltip. + */ + reset = (me, restart, state) => { + if (!this.ignoreTouchEvents || mxEvent.isMouseEvent(me.getEvent())) { + this.resetTimer(); + state = (state != null) ? state : this.getStateForEvent(me); + if (restart && this.isEnabled() && state != null && (this.div == null || + this.div.style.visibility == 'hidden')) { + var node = me.getSource(); + var x = me.getX(); + var y = me.getY(); + var stateSource = me.isSource(state.shape) || me.isSource(state.text); -/** - * Function: resetTimer - * - * Resets the timer. - */ -resetTimer = ()=> -{ - if (this.thread != null) - { - window.clearTimeout(this.thread); - this.thread = null; - } -}; + this.thread = window.setTimeout(mxUtils.bind(this, () => { + if (!this.graph.isEditing() && !this.graph.popupMenuHandler.isMenuShowing() && !this.graph.isMouseDown) { + // Uses information from inside event cause using the event at + // this (delayed) point in time is not possible in IE as it no + // longer contains the required information (member not found) + var tip = this.graph.getTooltip(state, node, x, y); + this.show(tip, x, y); + this.state = state; + this.node = node; + this.stateSource = stateSource; + } + }), this.delay); + } + } + }; -/** - * Function: reset - * - * Resets and/or restarts the timer to trigger the display of the tooltip. - */ -reset = (me, restart, state)=> -{ - if (!this.ignoreTouchEvents || mxEvent.isMouseEvent(me.getEvent())) - { + /** + * Function: hide + * + * Hides the tooltip and resets the timer. + */ + hide = () => { this.resetTimer(); - state = (state != null) ? state : this.getStateForEvent(me); - - if (restart && this.isEnabled() && state != null && (this.div == null || - this.div.style.visibility == 'hidden')) - { - var node = me.getSource(); - var x = me.getX(); - var y = me.getY(); - var stateSource = me.isSource(state.shape) || me.isSource(state.text); - - this.thread = window.setTimeout(mxUtils.bind(this, ()=> - { - if (!this.graph.isEditing() && !this.graph.popupMenuHandler.isMenuShowing() && !this.graph.isMouseDown) - { - // Uses information from inside event cause using the event at - // this (delayed) point in time is not possible in IE as it no - // longer contains the required information (member not found) - var tip = this.graph.getTooltip(state, node, x, y); - this.show(tip, x, y); - this.state = state; - this.node = node; - this.stateSource = stateSource; - } - }), this.delay); - } - } -}; + this.hideTooltip(); + }; -/** - * Function: hide - * - * Hides the tooltip and resets the timer. - */ -hide = ()=> -{ - this.resetTimer(); - this.hideTooltip(); -}; - -/** - * Function: hideTooltip - * - * Hides the tooltip. - */ -hideTooltip = ()=> -{ - if (this.div != null) - { - this.div.style.visibility = 'hidden'; - this.div.innerHTML = ''; - } -}; - -/** - * Function: show - * - * Shows the tooltip for the specified cell and optional index at the - * specified location (with a vertical offset of 10 pixels). - */ -show = (tip, x, y)=> -{ - if (!this.destroyed && tip != null && tip.length > 0) - { - // Initializes the DOM nodes if required - if (this.div == null) - { - this.init(); - } - - var origin = mxUtils.getScrollOrigin(); - - this.div.style.zIndex = this.zIndex; - this.div.style.left = (x + origin.x) + 'px'; - this.div.style.top = (y + mxConstants.TOOLTIP_VERTICAL_OFFSET + - origin.y) + 'px'; - - if (!mxUtils.isNode(tip)) - { - this.div.innerHTML = tip.replace(/\n/g, '
'); - } - else - { + /** + * Function: hideTooltip + * + * Hides the tooltip. + */ + hideTooltip = () => { + if (this.div != null) { + this.div.style.visibility = 'hidden'; this.div.innerHTML = ''; - this.div.appendChild(tip); } - - this.div.style.visibility = ''; - mxUtils.fit(this.div); - } -}; + }; -/** - * Function: destroy - * - * Destroys the handler and all its resources and DOM nodes. - */ -destroy = ()=> -{ - if (!this.destroyed) - { - this.graph.removeMouseListener(this); - mxEvent.release(this.div); - - if (this.div != null && this.div.parentNode != null) - { - this.div.parentNode.removeChild(this.div); + /** + * Function: show + * + * Shows the tooltip for the specified cell and optional index at the + * specified location (with a vertical offset of 10 pixels). + */ + show = (tip, x, y) => { + if (!this.destroyed && tip != null && tip.length > 0) { + // Initializes the DOM nodes if required + if (this.div == null) { + this.init(); + } + + var origin = mxUtils.getScrollOrigin(); + + this.div.style.zIndex = this.zIndex; + this.div.style.left = (x + origin.x) + 'px'; + this.div.style.top = (y + mxConstants.TOOLTIP_VERTICAL_OFFSET + + origin.y) + 'px'; + + if (!mxUtils.isNode(tip)) { + this.div.innerHTML = tip.replace(/\n/g, '
'); + } else { + this.div.innerHTML = ''; + this.div.appendChild(tip); + } + + this.div.style.visibility = ''; + mxUtils.fit(this.div); } - - this.destroyed = true; - this.div = null; - } -}; + }; + + /** + * Function: destroy + * + * Destroys the handler and all its resources and DOM nodes. + */ + destroy = () => { + if (!this.destroyed) { + this.graph.removeMouseListener(this); + mxEvent.release(this.div); + + if (this.div != null && this.div.parentNode != null) { + this.div.parentNode.removeChild(this.div); + } + + this.destroyed = true; + this.div = null; + } + }; +} + +export default mxTooltipHandler; diff --git a/src/js/io/mxCellCodec.js b/src/js/io/mxCellCodec.js index e423595a4..7c7adacee 100644 --- a/src/js/io/mxCellCodec.js +++ b/src/js/io/mxCellCodec.js @@ -3,8 +3,12 @@ * Copyright (c) 2006-2015, Gaudenz Alder */ +import mxCell from "FIXME"; +import mxObjectCodec from "./mxObjectCodec"; +import mxCodecRegistry from "./mxCodecRegistry"; -/** +class mxCellCodec extends mxObjectCodec { + /** * Class: mxCellCodec * * Codec for s. This class is created and registered @@ -43,26 +47,27 @@ * mxCodecRegistry.addAlias('CustomCell', 'mxCell'); * (end) */ - var codec = new mxObjectCodec(new mxCell(), - ['children', 'edges', 'overlays', 'mxTransient'], - ['parent', 'source', 'target']); + constructor() { + super(new mxCell(), + ['children', 'edges', 'overlays', 'mxTransient'], + ['parent', 'source', 'target']); + } /** * Function: isCellCodec * * Returns true since this is a cell codec. */ - codec.isCellCodec = ()=> - { + isCellCodec = () => { return true; }; /** * Overidden to disable conversion of value to number. */ - codec.isNumericAttribute = (dec, attr, obj)=> - { - return attr.nodeName !== 'value' && isNumericAttribute.apply(this, arguments); + isNumericAttribute = (dec, attr, obj) => { + return attr.nodeName !== 'value' && + super.isNumericAttribute(dec, attr, obj); }; /** @@ -70,11 +75,10 @@ * * Excludes user objects that are XML nodes. */ - codec.isExcluded = (obj, attr, value, isWrite)=> - { - return isExcluded.apply(this, arguments) || - (isWrite && attr == 'value' && - value.nodeType == mxConstants.NODETYPE_ELEMENT); + isExcluded = (obj, attr, value, isWrite) => { + return super.isExcluded(obj, attr, value, isWrite) || + (isWrite && attr === 'value' && + value.nodeType === mxConstants.NODETYPE_ELEMENT); }; /** @@ -83,10 +87,8 @@ * Encodes an and wraps the XML up inside the * XML of the user object (inversion). */ - codec.afterEncode = (enc, obj, node)=> - { - if (obj.value != null && obj.value.nodeType == mxConstants.NODETYPE_ELEMENT) - { + afterEncode = (enc, obj, node) => { + if (obj.value != null && obj.value.nodeType === mxConstants.NODETYPE_ELEMENT) { // Wraps the graphical annotation up in the user object (inversion) // by putting the result of the default encoding into a clone of the // user object (node type 1) and returning this cloned user object. @@ -110,26 +112,21 @@ * Decodes an and uses the enclosing XML node as * the user object for the cell (inversion). */ - codec.beforeDecode = (dec, node, obj)=> - { + beforeDecode = (dec, node, obj) => { var inner = node.cloneNode(true); var classname = this.getName(); - if (node.nodeName != classname) - { + if (node.nodeName !== classname) { // Passes the inner graphical annotation node to the // object codec for further processing of the cell. var tmp = node.getElementsByTagName(classname)[0]; - if (tmp != null && tmp.parentNode == node) - { + if (tmp != null && tmp.parentNode === node) { mxUtils.removeWhitespace(tmp, true); mxUtils.removeWhitespace(tmp, false); tmp.parentNode.removeChild(tmp); inner = tmp; - } - else - { + } else { inner = null; } @@ -137,39 +134,31 @@ obj.value = node.cloneNode(true); var id = obj.value.getAttribute('id'); - if (id != null) - { + if (id != null) { obj.setId(id); obj.value.removeAttribute('id'); } - } - else - { + } else { // Uses ID from XML file as ID for cell in model obj.setId(node.getAttribute('id')); } // Preprocesses and removes all Id-references in order to use the // correct encoder (this) for the known references to cells (all). - if (inner != null) - { - for (var i = 0; i < this.idrefs.length; i++) - { + if (inner != null) { + for (var i = 0; i < this.idrefs.length; i++) { var attr = this.idrefs[i]; var ref = inner.getAttribute(attr); - if (ref != null) - { + if (ref != null) { inner.removeAttribute(attr); var object = dec.objects[ref] || dec.lookup(ref); - if (object == null) - { + if (object == null) { // Needs to decode forward reference var element = dec.getElementById(ref); - if (element != null) - { + if (element != null) { var decoder = mxCodecRegistry.codecs[element.nodeName] || this; object = decoder.decode(dec, element); } @@ -182,6 +171,7 @@ return inner; }; +} mxCodecRegistry.register(new mxCellCodec()); export default mxCellCodec; diff --git a/src/js/io/mxChildChangeCodec.js b/src/js/io/mxChildChangeCodec.js index 584a0d528..c1202c655 100644 --- a/src/js/io/mxChildChangeCodec.js +++ b/src/js/io/mxChildChangeCodec.js @@ -3,6 +3,10 @@ * Copyright (c) 2006-2015, Gaudenz Alder */ +import mxObjectCodec from "FIXME"; +import mxChildChange from "FIXME"; +import mxCodecRegistry from "./mxCodecRegistry"; + class mxChildChangeCodec extends mxObjectCodec { /** * Class: mxChildChangeCodec @@ -48,7 +52,7 @@ class mxChildChangeCodec extends mxObjectCodec { * Excludes references to parent or previous if not in the model. */ isExcluded = (obj, attr, value, write) => { - return isExcluded.apply(this, arguments) || + return super.isExcluded(obj, attr, value, write) || (write && value != null && (attr === 'previous' || attr === 'parent') && !obj.model.contains(value)); }; diff --git a/src/js/io/mxCodec.js b/src/js/io/mxCodec.js index 5f6ac7d4e..a98b4fb50 100644 --- a/src/js/io/mxCodec.js +++ b/src/js/io/mxCodec.js @@ -2,620 +2,565 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * Class: mxCodec - * - * XML codec for JavaScript object graphs. See for a - * description of the general encoding/decoding scheme. This class uses the - * codecs registered in for encoding/decoding each object. - * - * References: - * - * In order to resolve references, especially forward references, the mxCodec - * constructor must be given the document that contains the referenced - * elements. - * - * Examples: - * - * The following code is used to encode a graph model. - * - * (code) - * var encoder = new mxCodec(); - * var result = encoder.encode(graph.getModel()); - * var xml = mxUtils.getXml(result); - * (end) - * - * Example: - * - * Using the code below, an XML document is decoded into an existing model. The - * document may be obtained using one of the functions in mxUtils for loading - * an XML file, eg. , or using for parsing an - * XML string. - * - * (code) - * var doc = mxUtils.parseXml(xmlString); - * var codec = new mxCodec(doc); - * codec.decode(doc.documentElement, graph.getModel()); - * (end) - * - * Example: - * - * This example demonstrates parsing a list of isolated cells into an existing - * graph model. Note that the cells do not have a parent reference so they can - * be added anywhere in the cell hierarchy after parsing. - * - * (code) - * var xml = ''; - * var doc = mxUtils.parseXml(xml); - * var codec = new mxCodec(doc); - * var elt = doc.documentElement.firstChild; - * var cells = []; - * - * while (elt != null) - * { - * cells.push(codec.decode(elt)); - * elt = elt.nextSibling; - * } - * - * graph.addCells(cells); - * (end) - * - * Example: - * - * Using the following code, the selection cells of a graph are encoded and the - * output is displayed in a dialog box. - * - * (code) - * var enc = new mxCodec(); - * var cells = graph.getSelectionCells(); - * mxUtils.alert(mxUtils.getPrettyXml(enc.encode(cells))); - * (end) - * - * Newlines in the XML can be converted to
, in which case a '
' argument - * must be passed to as the second argument. - * - * Debugging: - * - * For debugging I/O you can use the following code to get the sequence of - * encoded objects: - * - * (code) - * var oldEncode = encode; - * encode = (obj)=> - * { - * mxLog.show(); - * mxLog.debug('mxCodec.encode: obj='+mxUtils.getFunctionName(obj.constructor)); - * - * return oldEncode.apply(this, arguments); - * }; - * (end) - * - * Note that the I/O system adds object codecs for new object automatically. For - * decoding those objects, the constructor should be written as follows: - * - * (code) - * var MyObj = (name)=> - * { - * // ... - * }; - * (end) - * - * Constructor: mxCodec - * - * Constructs an XML encoder/decoder for the specified - * owner document. - * - * Parameters: - * - * document - Optional XML document that contains the data. - * If no document is specified then a new document is created - * using . - */ -function mxCodec(document) -{ - this.document = document || mxUtils.createXmlDocument(); - this.objects = []; -}; -/** - * Variable: document - * - * The owner document of the codec. - */ -document = null; +import mxUtils from "../util/mxUtils"; -/** - * Variable: objects - * - * Maps from IDs to objects. - */ -objects = null; +class mxCodec { + /** + * Variable: document + * + * The owner document of the codec. + */ + document = null; -/** - * Variable: elements - * - * Lookup table for resolving IDs to elements. - */ -elements = null; + /** + * Variable: objects + * + * Maps from IDs to objects. + */ + objects = null; -/** - * Variable: encodeDefaults - * - * Specifies if default values should be encoded. Default is false. - */ -encodeDefaults = false; + /** + * Variable: elements + * + * Lookup table for resolving IDs to elements. + */ + elements = null; + /** + * Variable: encodeDefaults + * + * Specifies if default values should be encoded. Default is false. + */ + encodeDefaults = false; -/** - * Function: putObject - * - * Assoiates the given object with the given ID and returns the given object. - * - * Parameters - * - * id - ID for the object to be associated with. - * obj - Object to be associated with the ID. - */ -putObject = (id, obj)=> -{ - this.objects[id] = obj; - - return obj; -}; + /** + * Class: mxCodec + * + * XML codec for JavaScript object graphs. See for a + * description of the general encoding/decoding scheme. This class uses the + * codecs registered in for encoding/decoding each object. + * + * References: + * + * In order to resolve references, especially forward references, the mxCodec + * constructor must be given the document that contains the referenced + * elements. + * + * Examples: + * + * The following code is used to encode a graph model. + * + * (code) + * var encoder = new mxCodec(); + * var result = encoder.encode(graph.getModel()); + * var xml = mxUtils.getXml(result); + * (end) + * + * Example: + * + * Using the code below, an XML document is decoded into an existing model. The + * document may be obtained using one of the functions in mxUtils for loading + * an XML file, eg. , or using for parsing an + * XML string. + * + * (code) + * var doc = mxUtils.parseXml(xmlString); + * var codec = new mxCodec(doc); + * codec.decode(doc.documentElement, graph.getModel()); + * (end) + * + * Example: + * + * This example demonstrates parsing a list of isolated cells into an existing + * graph model. Note that the cells do not have a parent reference so they can + * be added anywhere in the cell hierarchy after parsing. + * + * (code) + * var xml = ''; + * var doc = mxUtils.parseXml(xml); + * var codec = new mxCodec(doc); + * var elt = doc.documentElement.firstChild; + * var cells = []; + * + * while (elt != null) + * { + * cells.push(codec.decode(elt)); + * elt = elt.nextSibling; + * } + * + * graph.addCells(cells); + * (end) + * + * Example: + * + * Using the following code, the selection cells of a graph are encoded and the + * output is displayed in a dialog box. + * + * (code) + * var enc = new mxCodec(); + * var cells = graph.getSelectionCells(); + * mxUtils.alert(mxUtils.getPrettyXml(enc.encode(cells))); + * (end) + * + * Newlines in the XML can be converted to
, in which case a '
' argument + * must be passed to as the second argument. + * + * Debugging: + * + * For debugging I/O you can use the following code to get the sequence of + * encoded objects: + * + * (code) + * var oldEncode = encode; + * encode = (obj)=> + * { + * mxLog.show(); + * mxLog.debug('mxCodec.encode: obj='+mxUtils.getFunctionName(obj.constructor)); + * + * return oldEncode.apply(this, arguments); + * }; + * (end) + * + * Note that the I/O system adds object codecs for new object automatically. For + * decoding those objects, the constructor should be written as follows: + * + * (code) + * var MyObj = (name)=> + * { + * // ... + * }; + * (end) + * + * Constructor: mxCodec + * + * Constructs an XML encoder/decoder for the specified + * owner document. + * + * Parameters: + * + * document - Optional XML document that contains the data. + * If no document is specified then a new document is created + * using . + */ + constructor(document) { + this.document = document || mxUtils.createXmlDocument(); + this.objects = []; + }; -/** - * Function: getObject - * - * Returns the decoded object for the element with the specified ID in - * . If the object is not known then is used to find an - * object. If no object is found, then the element with the respective ID - * from the document is parsed using . - */ -getObject = (id)=> -{ - var obj = null; + /** + * Function: putObject + * + * Assoiates the given object with the given ID and returns the given object. + * + * Parameters + * + * id - ID for the object to be associated with. + * obj - Object to be associated with the ID. + */ + putObject = (id, obj) => { + this.objects[id] = obj; - if (id != null) - { - obj = this.objects[id]; - - if (obj == null) - { - obj = this.lookup(id); - - if (obj == null) - { - var node = this.getElementById(id); - - if (node != null) - { - obj = this.decode(node); + return obj; + }; + + /** + * Function: getObject + * + * Returns the decoded object for the element with the specified ID in + * . If the object is not known then is used to find an + * object. If no object is found, then the element with the respective ID + * from the document is parsed using . + */ + getObject = (id) => { + var obj = null; + + if (id != null) { + obj = this.objects[id]; + + if (obj == null) { + obj = this.lookup(id); + + if (obj == null) { + var node = this.getElementById(id); + + if (node != null) { + obj = this.decode(node); + } } } } - } - - return obj; -}; -/** - * Function: lookup - * - * Hook for subclassers to implement a custom lookup mechanism for cell IDs. - * This implementation always returns null. - * - * Example: - * - * (code) - * var codec = new mxCodec(); - * codec.lookup = (id)=> - * { - * return model.getCell(id); - * }; - * (end) - * - * Parameters: - * - * id - ID of the object to be returned. - */ -lookup = (id)=> -{ - return null; -}; + return obj; + }; -/** - * Function: getElementById - * - * Returns the element with the given ID from . - * - * Parameters: - * - * id - String that contains the ID. - */ -getElementById = (id)=> -{ - this.updateElements(); - - return this.elements[id]; -}; + /** + * Function: lookup + * + * Hook for subclassers to implement a custom lookup mechanism for cell IDs. + * This implementation always returns null. + * + * Example: + * + * (code) + * var codec = new mxCodec(); + * codec.lookup = (id)=> + * { + * return model.getCell(id); + * }; + * (end) + * + * Parameters: + * + * id - ID of the object to be returned. + */ + lookup = (id) => { + return null; + }; -/** - * Function: updateElements - * - * Returns the element with the given ID from . - * - * Parameters: - * - * id - String that contains the ID. - */ -updateElements = ()=> -{ - if (this.elements == null) - { - this.elements = new Object(); - - if (this.document.documentElement != null) - { - this.addElement(this.document.documentElement); - } - } -}; + /** + * Function: getElementById + * + * Returns the element with the given ID from . + * + * Parameters: + * + * id - String that contains the ID. + */ + getElementById = (id) => { + this.updateElements(); -/** - * Function: addElement - * - * Adds the given element to if it has an ID. - */ -addElement = (node)=> -{ - if (node.nodeType == mxConstants.NODETYPE_ELEMENT) - { - var id = node.getAttribute('id'); - - if (id != null) - { - if (this.elements[id] == null) - { - this.elements[id] = node; - } - else if (this.elements[id] != node) - { - throw new Error(id + ': Duplicate ID'); + return this.elements[id]; + }; + + /** + * Function: updateElements + * + * Returns the element with the given ID from . + * + * Parameters: + * + * id - String that contains the ID. + */ + updateElements = () => { + if (this.elements == null) { + this.elements = {}; + + if (this.document.documentElement != null) { + this.addElement(this.document.documentElement); } } - } - - node = node.firstChild; - - while (node != null) - { - this.addElement(node); - node = node.nextSibling; - } -}; + }; -/** - * Function: getId - * - * Returns the ID of the specified object. This implementation - * calls first and if that returns null handles - * the object as an by returning their IDs using - * . If no ID exists for the given cell, then - * an on-the-fly ID is generated using . - * - * Parameters: - * - * obj - Object to return the ID for. - */ -getId = (obj)=> -{ - var id = null; - - if (obj != null) - { - id = this.reference(obj); - - if (id == null && obj instanceof mxCell) - { - id = obj.getId(); - - if (id == null) - { - // Uses an on-the-fly Id - id = mxCellPath.create(obj); - - if (id.length == 0) - { - id = 'root'; + /** + * Function: addElement + * + * Adds the given element to if it has an ID. + */ + addElement = (node) => { + if (node.nodeType === mxConstants.NODETYPE_ELEMENT) { + var id = node.getAttribute('id'); + + if (id != null) { + if (this.elements[id] == null) { + this.elements[id] = node; + } else if (this.elements[id] !== node) { + throw new Error(id + ': Duplicate ID'); } } } - } - - return id; -}; -/** - * Function: reference - * - * Hook for subclassers to implement a custom method - * for retrieving IDs from objects. This implementation - * always returns null. - * - * Example: - * - * (code) - * var codec = new mxCodec(); - * codec.reference = (obj)=> - * { - * return obj.getCustomId(); - * }; - * (end) - * - * Parameters: - * - * obj - Object whose ID should be returned. - */ -reference = (obj)=> -{ - return null; -}; + node = node.firstChild; -/** - * Function: encode - * - * Encodes the specified object and returns the resulting - * XML node. - * - * Parameters: - * - * obj - Object to be encoded. - */ -encode = (obj)=> -{ - var node = null; - - if (obj != null && obj.constructor != null) - { - var enc = mxCodecRegistry.getCodec(obj.constructor); - - if (enc != null) - { - node = enc.encode(this, obj); + while (node != null) { + this.addElement(node); + node = node.nextSibling; } - else - { - if (mxUtils.isNode(obj)) - { - node = mxUtils.importNode(this.document, obj, true); + }; + + /** + * Function: getId + * + * Returns the ID of the specified object. This implementation + * calls first and if that returns null handles + * the object as an by returning their IDs using + * . If no ID exists for the given cell, then + * an on-the-fly ID is generated using . + * + * Parameters: + * + * obj - Object to return the ID for. + */ + getId = (obj) => { + var id = null; + + if (obj != null) { + id = this.reference(obj); + + if (id == null && obj instanceof mxCell) { + id = obj.getId(); + + if (id == null) { + // Uses an on-the-fly Id + id = mxCellPath.create(obj); + + if (id.length === 0) { + id = 'root'; + } + } } - else - { + } + + return id; + }; + + /** + * Function: reference + * + * Hook for subclassers to implement a custom method + * for retrieving IDs from objects. This implementation + * always returns null. + * + * Example: + * + * (code) + * var codec = new mxCodec(); + * codec.reference = (obj)=> + * { + * return obj.getCustomId(); + * }; + * (end) + * + * Parameters: + * + * obj - Object whose ID should be returned. + */ + reference = (obj) => { + return null; + }; + + /** + * Function: encode + * + * Encodes the specified object and returns the resulting + * XML node. + * + * Parameters: + * + * obj - Object to be encoded. + */ + encode = (obj) => { + var node = null; + + if (obj != null && obj.constructor != null) { + var enc = mxCodecRegistry.getCodec(obj.constructor); + + if (enc != null) { + node = enc.encode(this, obj); + } else { + if (mxUtils.isNode(obj)) { + node = mxUtils.importNode(this.document, obj, true); + } else { mxLog.warn('mxCodec.encode: No codec for ' + mxUtils.getFunctionName(obj.constructor)); + } } } - } - - return node; -}; -/** - * Function: decode - * - * Decodes the given XML node. The optional "into" - * argument specifies an existing object to be - * used. If no object is given, then a new instance - * is created using the constructor from the codec. - * - * The function returns the passed in object or - * the new instance if no object was given. - * - * Parameters: - * - * node - XML node to be decoded. - * into - Optional object to be decodec into. - */ -decode = (node, into)=> -{ - this.updateElements(); - var obj = null; - - if (node != null && node.nodeType == mxConstants.NODETYPE_ELEMENT) - { - var ctor = null; - - try - { - ctor = window[node.nodeName]; - } - catch (err) - { - // ignore - } - - var dec = mxCodecRegistry.getCodec(ctor); - - if (dec != null) - { - obj = dec.decode(this, node, into); - } - else - { - obj = node.cloneNode(true); - obj.removeAttribute('as'); - } - } - - return obj; -}; + return node; + }; -/** - * Function: encodeCell - * - * Encoding of cell hierarchies is built-into the core, but - * is a higher-level function that needs to be explicitely - * used by the respective object encoders (eg. , - * and ). This - * implementation writes the given cell and its children as a - * (flat) sequence into the given node. The children are not - * encoded if the optional includeChildren is false. The - * function is in charge of adding the result into the - * given node and has no return value. - * - * Parameters: - * - * cell - to be encoded. - * node - Parent XML node to add the encoded cell into. - * includeChildren - Optional boolean indicating if the - * function should include all descendents. Default is true. - */ -encodeCell = (cell, node, includeChildren)=> -{ - node.appendChild(this.encode(cell)); - - if (includeChildren == null || includeChildren) - { - var childCount = cell.getChildCount(); - - for (var i = 0; i < childCount; i++) - { - this.encodeCell(cell.getChildAt(i), node); - } - } -}; + /** + * Function: decode + * + * Decodes the given XML node. The optional "into" + * argument specifies an existing object to be + * used. If no object is given, then a new instance + * is created using the constructor from the codec. + * + * The function returns the passed in object or + * the new instance if no object was given. + * + * Parameters: + * + * node - XML node to be decoded. + * into - Optional object to be decodec into. + */ + decode = (node, into) => { + this.updateElements(); + var obj = null; -/** - * Function: isCellCodec - * - * Returns true if the given codec is a cell codec. This uses - * to check if the codec is of the - * given type. - */ -isCellCodec = (codec)=> -{ - if (codec != null && typeof(codec.isCellCodec) == 'function') - { - return codec.isCellCodec(); - } - - return false; -}; + if (node != null && node.nodeType === mxConstants.NODETYPE_ELEMENT) { + var ctor = null; -/** - * Function: decodeCell - * - * Decodes cells that have been encoded using inversion, ie. - * where the user object is the enclosing node in the XML, - * and restores the group and graph structure in the cells. - * Returns a new instance that represents the - * given node. - * - * Parameters: - * - * node - XML node that contains the cell data. - * restoreStructures - Optional boolean indicating whether - * the graph structure should be restored by calling insert - * and insertEdge on the parent and terminals, respectively. - * Default is true. - */ -decodeCell = (node, restoreStructures)=> -{ - restoreStructures = (restoreStructures != null) ? restoreStructures : true; - var cell = null; - - if (node != null && node.nodeType == mxConstants.NODETYPE_ELEMENT) - { - // Tries to find a codec for the given node name. If that does - // not return a codec then the node is the user object (an XML node - // that contains the mxCell, aka inversion). - var decoder = mxCodecRegistry.getCodec(node.nodeName); - - // Tries to find the codec for the cell inside the user object. - // This assumes all node names inside the user object are either - // not registered or they correspond to a class for cells. - if (!this.isCellCodec(decoder)) - { - var child = node.firstChild; - - while (child != null && !this.isCellCodec(decoder)) - { - decoder = mxCodecRegistry.getCodec(child.nodeName); - child = child.nextSibling; + try { + ctor = window[node.nodeName]; + } catch (err) { + // ignore + } + + var dec = mxCodecRegistry.getCodec(ctor); + + if (dec != null) { + obj = dec.decode(this, node, into); + } else { + obj = node.cloneNode(true); + obj.removeAttribute('as'); } } - - if (!this.isCellCodec(decoder)) - { - decoder = mxCodecRegistry.getCodec(mxCell); + + return obj; + }; + + /** + * Function: encodeCell + * + * Encoding of cell hierarchies is built-into the core, but + * is a higher-level function that needs to be explicitely + * used by the respective object encoders (eg. , + * and ). This + * implementation writes the given cell and its children as a + * (flat) sequence into the given node. The children are not + * encoded if the optional includeChildren is false. The + * function is in charge of adding the result into the + * given node and has no return value. + * + * Parameters: + * + * cell - to be encoded. + * node - Parent XML node to add the encoded cell into. + * includeChildren - Optional boolean indicating if the + * function should include all descendents. Default is true. + */ + encodeCell = (cell, node, includeChildren) => { + node.appendChild(this.encode(cell)); + + if (includeChildren == null || includeChildren) { + var childCount = cell.getChildCount(); + + for (var i = 0; i < childCount; i++) { + this.encodeCell(cell.getChildAt(i), node); + } + } + }; + + /** + * Function: isCellCodec + * + * Returns true if the given codec is a cell codec. This uses + * to check if the codec is of the + * given type. + */ + isCellCodec = (codec) => { + if (codec != null && typeof (codec.isCellCodec) == 'function') { + return codec.isCellCodec(); } - cell = decoder.decode(this, node); - - if (restoreStructures) - { - this.insertIntoGraph(cell); + return false; + }; + + /** + * Function: decodeCell + * + * Decodes cells that have been encoded using inversion, ie. + * where the user object is the enclosing node in the XML, + * and restores the group and graph structure in the cells. + * Returns a new instance that represents the + * given node. + * + * Parameters: + * + * node - XML node that contains the cell data. + * restoreStructures - Optional boolean indicating whether + * the graph structure should be restored by calling insert + * and insertEdge on the parent and terminals, respectively. + * Default is true. + */ + decodeCell = (node, restoreStructures) => { + restoreStructures = (restoreStructures != null) ? restoreStructures : true; + var cell = null; + + if (node != null && node.nodeType === mxConstants.NODETYPE_ELEMENT) { + // Tries to find a codec for the given node name. If that does + // not return a codec then the node is the user object (an XML node + // that contains the mxCell, aka inversion). + var decoder = mxCodecRegistry.getCodec(node.nodeName); + + // Tries to find the codec for the cell inside the user object. + // This assumes all node names inside the user object are either + // not registered or they correspond to a class for cells. + if (!this.isCellCodec(decoder)) { + var child = node.firstChild; + + while (child != null && !this.isCellCodec(decoder)) { + decoder = mxCodecRegistry.getCodec(child.nodeName); + child = child.nextSibling; + } + } + + if (!this.isCellCodec(decoder)) { + decoder = mxCodecRegistry.getCodec(mxCell); + } + + cell = decoder.decode(this, node); + + if (restoreStructures) { + this.insertIntoGraph(cell); + } } - } - - return cell; -}; -/** - * Function: insertIntoGraph - * - * Inserts the given cell into its parent and terminal cells. - */ -insertIntoGraph = (cell)=> -{ - var parent = cell.parent; - var source = cell.getTerminal(true); - var target = cell.getTerminal(false); + return cell; + }; - // Fixes possible inconsistencies during insert into graph - cell.setTerminal(null, false); - cell.setTerminal(null, true); - cell.parent = null; - - if (parent != null) - { - if (parent == cell) - { - throw new Error(parent.id + ': Self Reference'); + /** + * Function: insertIntoGraph + * + * Inserts the given cell into its parent and terminal cells. + */ + insertIntoGraph = (cell) => { + var parent = cell.parent; + var source = cell.getTerminal(true); + var target = cell.getTerminal(false); + + // Fixes possible inconsistencies during insert into graph + cell.setTerminal(null, false); + cell.setTerminal(null, true); + cell.parent = null; + + if (parent != null) { + if (parent === cell) { + throw new Error(parent.id + ': Self Reference'); + } else { + parent.insert(cell); + } } - else - { - parent.insert(cell); + + if (source != null) { + source.insertEdge(cell, true); } - } - if (source != null) - { - source.insertEdge(cell, true); - } + if (target != null) { + target.insertEdge(cell, false); + } + }; - if (target != null) - { - target.insertEdge(cell, false); - } -}; + /** + * Function: setAttribute + * + * Sets the attribute on the specified node to value. This is a + * helper method that makes sure the attribute and value arguments + * are not null. + * + * Parameters: + * + * node - XML node to set the attribute for. + * attributes - Attributename to be set. + * value - New value of the attribute. + */ + setAttribute = (node, attribute, value) => { + if (attribute != null && value != null) { + node.setAttribute(attribute, value); + } + }; +} -/** - * Function: setAttribute - * - * Sets the attribute on the specified node to value. This is a - * helper method that makes sure the attribute and value arguments - * are not null. - * - * Parameters: - * - * node - XML node to set the attribute for. - * attributes - Attributename to be set. - * value - New value of the attribute. - */ -setAttribute = (node, attribute, value)=> -{ - if (attribute != null && value != null) - { - node.setAttribute(attribute, value); - } -}; +export default mxCodec; diff --git a/src/js/io/mxCodecRegistry.js b/src/js/io/mxCodecRegistry.js index 8f25df531..fad9ff54a 100644 --- a/src/js/io/mxCodecRegistry.js +++ b/src/js/io/mxCodecRegistry.js @@ -2,8 +2,11 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -var mxCodecRegistry = -{ + +import mxUtils from "../util/mxUtils"; +import mxObjectCodec from "./mxObjectCodec"; + +var mxCodecRegistry = { /** * Class: mxCodecRegistry * @@ -11,7 +14,7 @@ var mxCodecRegistry = * * Adding an : * - * 1. Define a default codec with a new instance of the + * 1. Define a default codec with a new instance of the * object to be handled. * * (code) @@ -32,18 +35,18 @@ var mxCodecRegistry = * mxCodecRegistry.register(codec); * (end) * - * may be used to either create a new - * instance of an object or to configure an existing instance, + * may be used to either create a new + * instance of an object or to configure an existing instance, * in which case the into argument points to the existing * object. In this case, we say the codec "configures" the * object. - * + * * Variable: codecs * * Maps from constructor names to codecs. */ codecs: [], - + /** * Variable: aliases * @@ -61,17 +64,14 @@ var mxCodecRegistry = * * codec - to be registered. */ - register: (codec)=> - { - if (codec != null) - { + register: (codec) => { + if (codec != null) { var name = codec.getName(); mxCodecRegistry.codecs[name] = codec; - + var classname = mxUtils.getFunctionName(codec.template.constructor); - if (classname != name) - { + if (classname !== name) { mxCodecRegistry.addAlias(classname, name); } } @@ -84,8 +84,7 @@ var mxCodecRegistry = * * Adds an alias for mapping a classname to a codecname. */ - addAlias: (classname, codecname)=> - { + addAlias: (classname, codecname) => { mxCodecRegistry.aliases[classname] = codecname; }, @@ -97,41 +96,35 @@ var mxCodecRegistry = * * Parameters: * - * ctor - JavaScript constructor function. + * ctor - JavaScript constructor function. */ - getCodec: (ctor)=> - { + getCodec: (ctor) => { var codec = null; - - if (ctor != null) - { + + if (ctor != null) { var name = mxUtils.getFunctionName(ctor); var tmp = mxCodecRegistry.aliases[name]; - - if (tmp != null) - { + + if (tmp != null) { name = tmp; } - + codec = mxCodecRegistry.codecs[name]; - + // Registers a new default codec for the given constructor // if no codec has been previously defined. - if (codec == null) - { - try - { + if (codec == null) { + try { codec = new mxObjectCodec(new ctor()); mxCodecRegistry.register(codec); - } - catch (e) - { + } catch (e) { // ignore } } } - + return codec; } - }; + +export default mxCodecRegistry; diff --git a/src/js/io/mxDefaultKeyHandlerCodec.js b/src/js/io/mxDefaultKeyHandlerCodec.js index 4806fef2e..41ab033c5 100644 --- a/src/js/io/mxDefaultKeyHandlerCodec.js +++ b/src/js/io/mxDefaultKeyHandlerCodec.js @@ -3,6 +3,10 @@ * Copyright (c) 2006-2015, Gaudenz Alder */ +import mxObjectCodec from "FIXME"; +import mxDefaultKeyHandler from "FIXME"; +import mxCodecRegistry from "./mxCodecRegistry"; + class mxDefaultKeyHandlerCodec extends mxObjectCodec { /** * Class: mxDefaultKeyHandlerCodec diff --git a/src/js/io/mxDefaultPopupMenuCodec.js b/src/js/io/mxDefaultPopupMenuCodec.js index 094d6083f..66c871eb7 100644 --- a/src/js/io/mxDefaultPopupMenuCodec.js +++ b/src/js/io/mxDefaultPopupMenuCodec.js @@ -3,6 +3,9 @@ * Copyright (c) 2006-2015, Gaudenz Alder */ +import mxDefaultPopupMenu from "FIXME"; +import mxCodecRegistry from "./mxCodecRegistry"; + class mxDefaultPopupMenuCodec extends mxObjectCodec { /** * Class: mxDefaultPopupMenuCodec diff --git a/src/js/io/mxDefaultToolbarCodec.js b/src/js/io/mxDefaultToolbarCodec.js index 6907ea20b..4b7e6446e 100644 --- a/src/js/io/mxDefaultToolbarCodec.js +++ b/src/js/io/mxDefaultToolbarCodec.js @@ -3,6 +3,10 @@ * Copyright (c) 2006-2015, Gaudenz Alder */ +import mxObjectCodec from "FIXME"; +import mxDefaultToolbar from "FIXME"; +import mxCodecRegistry from "./mxCodecRegistry"; + class mxDefaultToolbarCodec extends mxObjectCodec { /** * Class: mxDefaultToolbarCodec diff --git a/src/js/io/mxEditorCodec.js b/src/js/io/mxEditorCodec.js index 360c6cae0..a9b61d974 100644 --- a/src/js/io/mxEditorCodec.js +++ b/src/js/io/mxEditorCodec.js @@ -2,8 +2,13 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -mxCodecRegistry.register(()=> -{ + +import mxEditor from "FIXME"; +import mxWindow from "FIXME"; +import mxObjectCodec from "FIXME"; +import mxCodecRegistry from "./mxCodecRegistry"; + +class mxEditorCodec extends mxObjectCodec { /** * Class: mxEditorCodec * @@ -20,9 +25,11 @@ mxCodecRegistry.register(()=> * - graphContainer * - toolbarContainer */ - var codec = new mxObjectCodec(new mxEditor(), - ['modified', 'lastSnapshot', 'ignoredChanges', - 'undoManager', 'graphContainer', 'toolbarContainer']); + constructor() { + super(new mxEditor(), + ['modified', 'lastSnapshot', 'ignoredChanges', + 'undoManager', 'graphContainer', 'toolbarContainer']); + } /** * Function: beforeDecode @@ -78,13 +85,11 @@ mxCodecRegistry.register(()=> * * (end) */ - codec.afterDecode = (dec, node, obj)=> - { + afterDecode = (dec, node, obj) => { // Assigns the specified templates for edges var defaultEdge = node.getAttribute('defaultEdge'); - if (defaultEdge != null) - { + if (defaultEdge != null) { node.removeAttribute('defaultEdge'); obj.defaultEdge = obj.templates[defaultEdge]; } @@ -92,8 +97,7 @@ mxCodecRegistry.register(()=> // Assigns the specified templates for groups var defaultGroup = node.getAttribute('defaultGroup'); - if (defaultGroup != null) - { + if (defaultGroup != null) { node.removeAttribute('defaultGroup'); obj.defaultGroup = obj.templates[defaultGroup]; } @@ -106,20 +110,15 @@ mxCodecRegistry.register(()=> * * Overrides decode child to handle special child nodes. */ - codec.decodeChild = (dec, child, obj)=> - { - if (child.nodeName == 'Array') - { + decodeChild = (dec, child, obj) => { + if (child.nodeName === 'Array') { var role = child.getAttribute('as'); - if (role == 'templates') - { + if (role === 'templates') { this.decodeTemplates(dec, child, obj); return; } - } - else if (child.nodeName == 'ui') - { + } else if (child.nodeName === 'ui') { this.decodeUi(dec, child, obj); return; } @@ -132,29 +131,22 @@ mxCodecRegistry.register(()=> * * Decodes the ui elements from the given node. */ - codec.decodeUi = (dec, node, editor)=> - { + decodeUi = (dec, node, editor) => { var tmp = node.firstChild; - while (tmp != null) - { - if (tmp.nodeName == 'add') - { + while (tmp != null) { + if (tmp.nodeName === 'add') { var as = tmp.getAttribute('as'); var elt = tmp.getAttribute('element'); var style = tmp.getAttribute('style'); var element = null; - if (elt != null) - { + if (elt != null) { element = document.getElementById(elt); - if (element != null && style != null) - { + if (element != null && style != null) { element.style.cssText += ';' + style; } - } - else - { + } else { var x = parseInt(tmp.getAttribute('x')); var y = parseInt(tmp.getAttribute('y')); var width = tmp.getAttribute('width'); @@ -165,38 +157,25 @@ mxCodecRegistry.register(()=> element.style.cssText = style; var wnd = new mxWindow(mxResources.get(as) || as, - element, x, y, width, height, false, true); + element, x, y, width, height, false, true); wnd.setVisible(true); } // TODO: Make more generic - if (as == 'graph') - { + if (as === 'graph') { editor.setGraphContainer(element); - } - else if (as == 'toolbar') - { + } else if (as === 'toolbar') { editor.setToolbarContainer(element); - } - else if (as == 'title') - { + } else if (as === 'title') { editor.setTitleContainer(element); - } - else if (as == 'status') - { + } else if (as === 'status') { editor.setStatusContainer(element); - } - else if (as == 'map') - { + } else if (as === 'map') { editor.setMapContainer(element); } - } - else if (tmp.nodeName == 'resource') - { + } else if (tmp.nodeName === 'resource') { mxResources.add(tmp.getAttribute('basename')); - } - else if (tmp.nodeName == 'stylesheet') - { + } else if (tmp.nodeName === 'stylesheet') { mxClient.link('stylesheet', tmp.getAttribute('name')); } @@ -209,26 +188,21 @@ mxCodecRegistry.register(()=> * * Decodes the cells from the given node as templates. */ - codec.decodeTemplates = (dec, node, editor)=> - { - if (editor.templates == null) - { + decodeTemplates = (dec, node, editor) => { + if (editor.templates == null) { editor.templates = []; } var children = mxUtils.getChildNodes(node); - for (var j=0; j } } }; +} - // Returns the codec into the registry - return codec; - -}()); +mxCodecRegistry.register(new mxEditorCodec()); +export default mxEditorCodec; diff --git a/src/js/io/mxGenericChangeCodec.js b/src/js/io/mxGenericChangeCodec.js index d897d65ec..34a32e06e 100644 --- a/src/js/io/mxGenericChangeCodec.js +++ b/src/js/io/mxGenericChangeCodec.js @@ -2,58 +2,65 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -/** - * Class: mxGenericChangeCodec - * - * Codec for s, s, s, - * s and s. This class is created - * and registered dynamically at load time and used implicitly - * via and the . - * - * Transient Fields: - * - * - model - * - previous - * - * Reference Fields: - * - * - cell - * - * Constructor: mxGenericChangeCodec - * - * Factory function that creates a for - * the specified change and fieldname. - * - * Parameters: - * - * obj - An instance of the change object. - * variable - The fieldname for the change data. - */ -var mxGenericChangeCodec = (obj, variable)=> -{ - var codec = new mxObjectCodec(obj, ['model', 'previous'], ['cell']); + +import mxObjectCodec from "FIXME"; +import mxValueChange from "FIXME"; +import mxStyleChange from "FIXME"; +import mxGeometryChange from "FIXME"; +import mxCollapseChange from "FIXME"; +import mxVisibleChange from "FIXME"; +import mxCellAttributeChange from "FIXME"; +import mxCodecRegistry from "./mxCodecRegistry"; + +class mxGenericChangeCodec extends mxObjectCodec { + /** + * Class: mxGenericChangeCodec + * + * Codec for s, s, s, + * s and s. This class is created + * and registered dynamically at load time and used implicitly + * via and the . + * + * Transient Fields: + * + * - model + * - previous + * + * Reference Fields: + * + * - cell + * + * Constructor: mxGenericChangeCodec + * + * Factory function that creates a for + * the specified change and fieldname. + * + * Parameters: + * + * obj - An instance of the change object. + * variable - The fieldname for the change data. + */ + constructor(obj, variable) { + super(obj, ['model', 'previous'], ['cell']); + this.variable = variable; + } /** * Function: afterDecode * * Restores the state by assigning the previous value. */ - codec.afterDecode = (dec, node, obj)=> - { + afterDecode = (dec, node, obj) => { // Allows forward references in sessions. This is a workaround // for the sequence of edits in mxGraph.moveCells and cellsAdded. - if (mxUtils.isNode(obj.cell)) - { + if (mxUtils.isNode(obj.cell)) { obj.cell = dec.decodeCell(obj.cell, false); } - obj.previous = obj[variable]; - + obj.previous = obj[this.variable]; return obj; }; - - return codec; -}; +} // Registers the codecs mxCodecRegistry.register(mxGenericChangeCodec(new mxValueChange(), 'value')); diff --git a/src/js/io/mxGraphCodec.js b/src/js/io/mxGraphCodec.js index 83e3f5005..e1f2848d2 100644 --- a/src/js/io/mxGraphCodec.js +++ b/src/js/io/mxGraphCodec.js @@ -2,8 +2,11 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -mxCodecRegistry.register(()=> -{ + +import mxGraph from "FIXME"; +import mxCodecRegistry from "./mxCodecRegistry"; + +class mxGraphCodec extends mxObjectCodec { /** * Class: mxGraphCodec * @@ -21,8 +24,11 @@ mxCodecRegistry.register(()=> * - editor * - selection */ - return new mxObjectCodec(new mxGraph(), - ['graphListeners', 'eventListeners', 'view', 'container', - 'cellRenderer', 'editor', 'selection']); + constructor() { + super(new mxGraph(), ['graphListeners', 'eventListeners', 'view', 'container', + 'cellRenderer', 'editor', 'selection']); + } +} -}()); +mxCodecRegistry.register(new mxGraphCodec()); +export default mxGraphCodec; diff --git a/src/js/io/mxGraphViewCodec.js b/src/js/io/mxGraphViewCodec.js index eb36191f6..cc2435ceb 100644 --- a/src/js/io/mxGraphViewCodec.js +++ b/src/js/io/mxGraphViewCodec.js @@ -2,8 +2,12 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -mxCodecRegistry.register(()=> -{ + +import mxGraphView from "FIXME"; +import mxObjectCodec from "FIXME"; +import mxCodecRegistry from "./mxCodecRegistry"; + +class mxGraphViewCodec extends mxObjectCodec { /** * Class: mxGraphViewCodec * @@ -14,7 +18,9 @@ mxCodecRegistry.register(()=> * the graph, that is, it contains absolute coordinates with * computed perimeters, edge styles and cell styles. */ - var codec = new mxObjectCodec(new mxGraphView()); + constructor() { + super(new mxGraphView()) + } /** * Function: encode @@ -23,10 +29,9 @@ mxCodecRegistry.register(()=> * starting at the model's root. This returns the * top-level graph node of the recursive encoding. */ - codec.encode = (enc, view)=> - { + encode = (enc, view) => { return this.encodeCell(enc, view, - view.graph.getModel().getRoot()); + view.graph.getModel().getRoot()); }; /** @@ -49,60 +54,44 @@ mxCodecRegistry.register(()=> * values from the cell style are added as attribute * values to the node. */ - codec.encodeCell = (enc, view, cell)=> - { + encodeCell = (enc, view, cell) => { var model = view.graph.getModel(); var state = view.getState(cell); var parent = model.getParent(cell); - if (parent == null || state != null) - { + if (parent == null || state != null) { var childCount = model.getChildCount(cell); var geo = view.graph.getCellGeometry(cell); var name = null; - if (parent == model.getRoot()) - { + if (parent === model.getRoot()) { name = 'layer'; - } - else if (parent == null) - { + } else if (parent == null) { name = 'graph'; - } - else if (model.isEdge(cell)) - { + } else if (model.isEdge(cell)) { name = 'edge'; - } - else if (childCount > 0 && geo != null) - { + } else if (childCount > 0 && geo != null) { name = 'group'; - } - else if (model.isVertex(cell)) - { + } else if (model.isVertex(cell)) { name = 'vertex'; } - if (name != null) - { + if (name != null) { var node = enc.document.createElement(name); var lab = view.graph.getLabel(cell); - if (lab != null) - { + if (lab != null) { node.setAttribute('label', view.graph.getLabel(cell)); - if (view.graph.isHtmlLabel(cell)) - { + if (view.graph.isHtmlLabel(cell)) { node.setAttribute('html', true); } } - if (parent == null) - { + if (parent == null) { var bounds = view.getGraphBounds(); - if (bounds != null) - { + if (bounds != null) { node.setAttribute('x', Math.round(bounds.x)); node.setAttribute('y', Math.round(bounds.y)); node.setAttribute('width', Math.round(bounds.width)); @@ -110,48 +99,40 @@ mxCodecRegistry.register(()=> } node.setAttribute('scale', view.scale); - } - else if (state != null && geo != null) - { + } else if (state != null && geo != null) { // Writes each key, value in the style pair to an attribute - for (var i in state.style) - { - var value = state.style[i]; + for (var i in state.style) { + var value = state.style[i]; - // Tries to turn objects and functions into strings - if (typeof(value) == 'function' && - typeof(value) == 'object') - { - value = mxStyleRegistry.getName(value); - } - - if (value != null && - typeof(value) != 'function' && - typeof(value) != 'object') - { - node.setAttribute(i, value); - } + // Tries to turn objects and functions into strings + if (typeof (value) == 'function' && + typeof (value) == 'object') { + value = mxStyleRegistry.getName(value); } + if (value != null && + typeof (value) != 'function' && + typeof (value) != 'object') { + node.setAttribute(i, value); + } + } + var abs = state.absolutePoints; // Writes the list of points into one attribute - if (abs != null && abs.length > 0) - { + if (abs != null && abs.length > 0) { var pts = Math.round(abs[0].x) + ',' + Math.round(abs[0].y); - for (var i=1; i var offset = state.absoluteOffset; // Writes the offset into 2 attributes - if (offset != null) - { - if (offset.x != 0) - { + if (offset != null) { + if (offset.x !== 0) { node.setAttribute('dx', Math.round(offset.x)); } - if (offset.y != 0) - { + if (offset.y !== 0) { node.setAttribute('dy', Math.round(offset.y)); } } } - for (var i=0; i { // Handles known numeric attributes for generic objects - var result = (obj.constructor == mxGeometry && - (attr.name == 'x' || attr.name == 'y' || - attr.name == 'width' || attr.name == 'height')) || - (obj.constructor == mxPoint && - (attr.name == 'x' || attr.name == 'y')) || + var result = (obj.constructor === mxGeometry && + (attr.name === 'x' || attr.name === 'y' || + attr.name === 'width' || attr.name === 'height')) || + (obj.constructor === mxPoint && + (attr.name === 'x' || attr.name === 'y')) || mxUtils.isNumeric(attr.value); return result; @@ -787,7 +787,7 @@ class mxObjectCodec { * obj - Objec to encode the attribute into. */ isIgnoredAttribute = (dec, attr, obj) => { - return attr.nodeName == 'as' || attr.nodeName == 'id'; + return attr.nodeName === 'as' || attr.nodeName === 'id'; }; /** @@ -875,7 +875,7 @@ class mxObjectCodec { var template = this.getFieldTemplate(obj, fieldname, child); var value = null; - if (child.nodeName == 'add') { + if (child.nodeName === 'add') { value = child.getAttribute('value'); if (value == null && mxObjectCodec.allowEval) { @@ -925,7 +925,7 @@ class mxObjectCodec { * override this with the correct code to add an entry to an object. */ addObjectValue = (obj, fieldname, value, template) => { - if (value != null && value != template) { + if (value != null && value !== template) { if (fieldname != null && fieldname.length > 0) { obj[fieldname] = value; } else { @@ -949,7 +949,7 @@ class mxObjectCodec { * into - Optional object to pass-thru to the codec. */ processInclude = (dec, node, into) => { - if (node.nodeName == 'include') { + if (node.nodeName === 'include') { var name = node.getAttribute('name'); if (name != null) { diff --git a/src/js/io/mxRootChangeCodec.js b/src/js/io/mxRootChangeCodec.js index c7fa1900d..2cf90a312 100644 --- a/src/js/io/mxRootChangeCodec.js +++ b/src/js/io/mxRootChangeCodec.js @@ -4,6 +4,7 @@ */ import mxRootChange from "FIXME"; +import mxCodecRegistry from "./mxCodecRegistry"; class mxRootChangeCodec extends mxObjectCodec { /** diff --git a/src/js/io/mxStylesheetCodec.js b/src/js/io/mxStylesheetCodec.js index 452239796..bcd79ae82 100644 --- a/src/js/io/mxStylesheetCodec.js +++ b/src/js/io/mxStylesheetCodec.js @@ -3,6 +3,10 @@ * Copyright (c) 2006-2015, Gaudenz Alder */ +import mxUtils from "../util/mxUtils"; +import mxCodecRegistry from "./mxCodecRegistry"; +import mxStylesheet from "FIXME"; + class mxStylesheetCodec extends mxObjectCodec { /** * Variable: allowEval @@ -142,7 +146,7 @@ class mxStylesheetCodec extends mxObjectCodec { extend + ' not found to extend'); } - style = new Object(); + style = {}; } var entry = node.firstChild; diff --git a/src/js/io/mxTerminalChangeCodec.js b/src/js/io/mxTerminalChangeCodec.js index 0b4b75b9e..1395e2136 100644 --- a/src/js/io/mxTerminalChangeCodec.js +++ b/src/js/io/mxTerminalChangeCodec.js @@ -2,8 +2,11 @@ * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ -mxCodecRegistry.register(()=> -{ + +import mxObjectCodec from "FIXME"; +import mxTerminalChange from "FIXME"; + +class mxTerminalChangeCodec extends mxObjectCodec { /** * Class: mxTerminalChangeCodec * @@ -21,22 +24,21 @@ mxCodecRegistry.register(()=> * - cell * - terminal */ - var codec = new mxObjectCodec(new mxTerminalChange(), - ['model', 'previous'], ['cell', 'terminal']); + constructor() { + super(new mxTerminalChange(), ['model', 'previous'], ['cell', 'terminal']); + } /** * Function: afterDecode * * Restores the state by assigning the previous value. */ - codec.afterDecode = (dec, node, obj)=> - { + afterDecode = (dec, node, obj) => { obj.previous = obj.terminal; - + return obj; }; +} - // Returns the codec into the registry - return codec; - -}()); +mxCodecRegistry.register(new mxTerminalChangeCodec()); +export default mxTerminalChangeCodec; diff --git a/src/js/layout/hierarchical/model/mxGraphHierarchyModel.js b/src/js/layout/hierarchical/model/mxGraphHierarchyModel.js index 5cf6599eb..286156df1 100644 --- a/src/js/layout/hierarchical/model/mxGraphHierarchyModel.js +++ b/src/js/layout/hierarchical/model/mxGraphHierarchyModel.js @@ -472,7 +472,7 @@ class mxGraphHierarchyModel { if (internalNode != null) { if (seenNodes == null) { - seenNodes = new Object(); + seenNodes = {}; } if (trackAncestors) { diff --git a/src/js/layout/hierarchical/model/mxSwimlaneModel.js b/src/js/layout/hierarchical/model/mxSwimlaneModel.js index f8f6bf579..6ada1dc20 100644 --- a/src/js/layout/hierarchical/model/mxSwimlaneModel.js +++ b/src/js/layout/hierarchical/model/mxSwimlaneModel.js @@ -279,7 +279,7 @@ class mxSwimlaneModel { this.ranksPerGroup = []; var startNodes = []; - var seen = new Object(); + var seen = {}; if (this.roots != null) { for (var i = 0; i < this.roots.length; i++) { @@ -559,7 +559,7 @@ class mxSwimlaneModel { if (internalNode != null) { if (seenNodes == null) { - seenNodes = new Object(); + seenNodes = {}; } if (trackAncestors) { diff --git a/src/js/layout/hierarchical/stage/mxCoordinateAssignment.js b/src/js/layout/hierarchical/stage/mxCoordinateAssignment.js index e360789ba..941da4deb 100644 --- a/src/js/layout/hierarchical/stage/mxCoordinateAssignment.js +++ b/src/js/layout/hierarchical/stage/mxCoordinateAssignment.js @@ -492,7 +492,7 @@ class mxCoordinateAssignment extends mxHierarchicalLayoutStage { // , the order is given by the weighted sum of the in or out edges, // depending on whether we're traveling up or down the hierarchy. var weightedValues = []; - var cellMap = new Object(); + var cellMap = {}; for (var i = 0; i < rank.length; i++) { var currentCell = rank[i]; diff --git a/src/js/layout/hierarchical/stage/mxMinimumCycleRemover.js b/src/js/layout/hierarchical/stage/mxMinimumCycleRemover.js index e5c886ca7..2c8defa1e 100644 --- a/src/js/layout/hierarchical/stage/mxMinimumCycleRemover.js +++ b/src/js/layout/hierarchical/stage/mxMinimumCycleRemover.js @@ -35,9 +35,9 @@ class mxMinimumCycleRemover extends mxHierarchicalLayoutStage { */ execute = (parent) => { var model = this.layout.getModel(); - var seenNodes = new Object(); + var seenNodes = {}; var unseenNodesArray = model.vertexMapper.getValues(); - var unseenNodes = new Object(); + var unseenNodes = {}; for (var i = 0; i < unseenNodesArray.length; i++) { unseenNodes[unseenNodesArray[i].id] = unseenNodesArray[i]; diff --git a/src/js/layout/hierarchical/stage/mxSwimlaneOrdering.js b/src/js/layout/hierarchical/stage/mxSwimlaneOrdering.js index 8d9ae6d00..7c80816fb 100644 --- a/src/js/layout/hierarchical/stage/mxSwimlaneOrdering.js +++ b/src/js/layout/hierarchical/stage/mxSwimlaneOrdering.js @@ -35,7 +35,7 @@ class mxSwimlaneOrdering extends mxHierarchicalLayoutStage { */ execute = (parent) => { var model = this.layout.getModel(); - var seenNodes = new Object(); + var seenNodes = {}; var unseenNodes = mxUtils.clone(model.vertexMapper, null, true); // Perform a dfs through the internal model. If a cycle is found, diff --git a/src/js/layout/mxCompactTreeLayout.js b/src/js/layout/mxCompactTreeLayout.js index 2cd1d354c..cbae0234d 100644 --- a/src/js/layout/mxCompactTreeLayout.js +++ b/src/js/layout/mxCompactTreeLayout.js @@ -291,7 +291,7 @@ class mxCompactTreeLayout extends mxGraphLayout { if (this.root != null) { if (this.resizeParent) { - this.parentsChanged = new Object(); + this.parentsChanged = {}; } else { this.parentsChanged = null; } @@ -312,7 +312,7 @@ class mxCompactTreeLayout extends mxGraphLayout { model.beginUpdate(); try { - this.visited = new Object(); + this.visited = {}; this.node = this.dfs(this.root, parent); if (this.alignRanks) { @@ -768,7 +768,7 @@ class mxCompactTreeLayout extends mxGraphLayout { * Function: createNode */ createNode = (cell) => { - var node = new Object(); + var node = {}; node.cell = cell; node.x = 0; node.y = 0; @@ -789,7 +789,7 @@ class mxCompactTreeLayout extends mxGraphLayout { node.offsetX = 0; node.offsetY = 0; - node.contour = new Object(); + node.contour = {}; return node; }; @@ -834,7 +834,7 @@ class mxCompactTreeLayout extends mxGraphLayout { * Function: createLine */ createLine = (dx, dy, next) => { - var line = new Object(); + var line = {}; line.dx = dx; line.dy = dy; line.next = next; diff --git a/src/js/model/mxGraphModel.js b/src/js/model/mxGraphModel.js index e96474b82..b754709c5 100644 --- a/src/js/model/mxGraphModel.js +++ b/src/js/model/mxGraphModel.js @@ -646,7 +646,7 @@ class mxGraphModel extends mxEventSource { // Lazily creates the cells dictionary if (this.cells == null) { - this.cells = new Object(); + this.cells = {}; } this.cells[cell.getId()] = cell; @@ -1853,7 +1853,7 @@ class mxGraphModel extends mxEventSource { this.beginUpdate(); try { - var mapping = new Object(); + var mapping = {}; this.mergeChildrenImpl(from, to, cloneAllEdges, mapping); // Post-processes all edges in the mapping and @@ -2000,7 +2000,7 @@ class mxGraphModel extends mxEventSource { */ cloneCells = (cells, includeChildren, mapping) => { includeChildren = (includeChildren != null) ? includeChildren : true; - mapping = (mapping != null) ? mapping : new Object(); + mapping = (mapping != null) ? mapping : {}; var clones = []; for (var i = 0; i < cells.length; i++) { diff --git a/src/js/util/mxClipboard.js b/src/js/util/mxClipboard.js index dfcda2f77..cdec06cec 100644 --- a/src/js/util/mxClipboard.js +++ b/src/js/util/mxClipboard.js @@ -31,7 +31,7 @@ var mxClipboard = * 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/src/js/util/mxXmlRequest.js b/src/js/util/mxXmlRequest.js index 2e6ecf826..cacd1aa7d 100644 --- a/src/js/util/mxXmlRequest.js +++ b/src/js/util/mxXmlRequest.js @@ -2,454 +2,427 @@ * Copyright (c) 2006-2020, JGraph Ltd * Copyright (c) 2006-2020, draw.io AG */ -/** - * Class: mxXmlRequest - * - * XML HTTP request wrapper. See also: , and - * . This class provides a cross-browser abstraction for Ajax - * requests. - * - * Encoding: - * - * For encoding parameter values, the built-in encodeURIComponent JavaScript - * method must be used. For automatic encoding of post data in the - * switch can be set to true (default). The encoding - * will be carried out using the conte type of the page. That is, the page - * containting the editor should contain a meta tag in the header, eg. - * - * - * Example: - * - * (code) - * var onload = (req)=> - * { - * mxUtils.alert(req.getDocumentElement()); - * } - * - * var onerror = (req)=> - * { - * mxUtils.alert('Error'); - * } - * new mxXmlRequest(url, 'key=value').send(onload, onerror); - * (end) - * - * Sends an asynchronous POST request to the specified URL. - * - * Example: - * - * (code) - * var req = new mxXmlRequest(url, 'key=value', 'POST', false); - * req.send(); - * mxUtils.alert(req.getDocumentElement()); - * (end) - * - * Sends a synchronous POST request to the specified URL. - * - * Example: - * - * (code) - * var encoder = new mxCodec(); - * var result = encoder.encode(graph.getModel()); - * var xml = encodeURIComponent(mxUtils.getXml(result)); - * new mxXmlRequest(url, 'xml='+xml).send(); - * (end) - * - * Sends an encoded graph model to the specified URL using xml as the - * parameter name. The parameter can then be retrieved in C# as follows: - * - * (code) - * string xml = HttpUtility.UrlDecode(context.Request.Params["xml"]); - * (end) - * - * Or in Java as follows: - * - * (code) - * String xml = URLDecoder.decode(request.getParameter("xml"), "UTF-8").replace("\n", " "); - * (end) - * - * Note that the linefeeds should only be replaced if the XML is - * processed in Java, for example when creating an image. - * - * Constructor: mxXmlRequest - * - * Constructs an XML HTTP request. - * - * Parameters: - * - * url - Target URL of the request. - * params - Form encoded parameters to send with a POST request. - * method - String that specifies the request method. Possible values are - * POST and GET. Default is POST. - * async - Boolean specifying if an asynchronous request should be used. - * Default is true. - * username - String specifying the username to be used for the request. - * password - String specifying the password to be used for the request. - */ -function mxXmlRequest(url, params, method, async, username, password) -{ - this.url = url; - this.params = params; - this.method = method || 'POST'; - this.async = (async != null) ? async : true; - this.username = username; - this.password = password; -}; -/** - * Variable: url - * - * Holds the target URL of the request. - */ -url = null; +class mxXmlRequest { + /** + * Variable: url + * + * Holds the target URL of the request. + */ + url = null; -/** - * Variable: params - * - * Holds the form encoded data for the POST request. - */ -params = null; + /** + * Variable: params + * + * Holds the form encoded data for the POST request. + */ + params = null; -/** - * Variable: method - * - * Specifies the request method. Possible values are POST and GET. Default - * is POST. - */ -method = null; + /** + * Variable: method + * + * Specifies the request method. Possible values are POST and GET. Default + * is POST. + */ + method = null; -/** - * Variable: async - * - * Boolean indicating if the request is asynchronous. - */ -async = null; + /** + * Variable: async + * + * Boolean indicating if the request is asynchronous. + */ + async = null; -/** - * Variable: binary - * - * Boolean indicating if the request is binary. This option is ignored in IE. - * In all other browsers the requested mime type is set to - * text/plain; charset=x-user-defined. Default is false. - */ -binary = false; + /** + * Variable: binary + * + * Boolean indicating if the request is binary. This option is ignored in IE. + * In all other browsers the requested mime type is set to + * text/plain; charset=x-user-defined. Default is false. + */ + binary = false; -/** - * Variable: withCredentials - * - * Specifies if withCredentials should be used in HTML5-compliant browsers. Default is - * false. - */ -withCredentials = false; + /** + * Variable: withCredentials + * + * Specifies if withCredentials should be used in HTML5-compliant browsers. Default is + * false. + */ + withCredentials = false; -/** - * Variable: username - * - * Specifies the username to be used for authentication. - */ -username = null; + /** + * Variable: username + * + * Specifies the username to be used for authentication. + */ + username = null; -/** - * Variable: password - * - * Specifies the password to be used for authentication. - */ -password = null; + /** + * Variable: password + * + * Specifies the password to be used for authentication. + */ + password = null; -/** - * Variable: request - * - * Holds the inner, browser-specific request object. - */ -request = null; + /** + * Variable: request + * + * Holds the inner, browser-specific request object. + */ + request = null; -/** - * Variable: decodeSimulateValues - * - * Specifies if request values should be decoded as URIs before setting the - * textarea value in . Defaults to false for backwards compatibility, - * to avoid another decode on the server this should be set to true. - */ -decodeSimulateValues = false; + /** + * Variable: decodeSimulateValues + * + * Specifies if request values should be decoded as URIs before setting the + * textarea value in . Defaults to false for backwards compatibility, + * to avoid another decode on the server this should be set to true. + */ + decodeSimulateValues = false; -/** - * Function: isBinary - * - * Returns . - */ -isBinary = ()=> -{ - return this.binary; -}; + /** + * Class: mxXmlRequest + * + * XML HTTP request wrapper. See also: , and + * . This class provides a cross-browser abstraction for Ajax + * requests. + * + * Encoding: + * + * For encoding parameter values, the built-in encodeURIComponent JavaScript + * method must be used. For automatic encoding of post data in the + * switch can be set to true (default). The encoding + * will be carried out using the conte type of the page. That is, the page + * containting the editor should contain a meta tag in the header, eg. + * + * + * Example: + * + * (code) + * var onload = (req)=> + * { + * mxUtils.alert(req.getDocumentElement()); + * } + * + * var onerror = (req)=> + * { + * mxUtils.alert('Error'); + * } + * new mxXmlRequest(url, 'key=value').send(onload, onerror); + * (end) + * + * Sends an asynchronous POST request to the specified URL. + * + * Example: + * + * (code) + * var req = new mxXmlRequest(url, 'key=value', 'POST', false); + * req.send(); + * mxUtils.alert(req.getDocumentElement()); + * (end) + * + * Sends a synchronous POST request to the specified URL. + * + * Example: + * + * (code) + * var encoder = new mxCodec(); + * var result = encoder.encode(graph.getModel()); + * var xml = encodeURIComponent(mxUtils.getXml(result)); + * new mxXmlRequest(url, 'xml='+xml).send(); + * (end) + * + * Sends an encoded graph model to the specified URL using xml as the + * parameter name. The parameter can then be retrieved in C# as follows: + * + * (code) + * string xml = HttpUtility.UrlDecode(context.Request.Params["xml"]); + * (end) + * + * Or in Java as follows: + * + * (code) + * String xml = URLDecoder.decode(request.getParameter("xml"), "UTF-8").replace("\n", " "); + * (end) + * + * Note that the linefeeds should only be replaced if the XML is + * processed in Java, for example when creating an image. + * + * Constructor: mxXmlRequest + * + * Constructs an XML HTTP request. + * + * Parameters: + * + * url - Target URL of the request. + * params - Form encoded parameters to send with a POST request. + * method - String that specifies the request method. Possible values are + * POST and GET. Default is POST. + * async - Boolean specifying if an asynchronous request should be used. + * Default is true. + * username - String specifying the username to be used for the request. + * password - String specifying the password to be used for the request. + */ + constructor(url, params, method, async, username, password) { + this.url = url; + this.params = params; + this.method = method || 'POST'; + this.async = (async != null) ? async : true; + this.username = username; + this.password = password; + }; -/** - * Function: setBinary - * - * Sets . - */ -setBinary = (value)=> -{ - this.binary = value; -}; + /** + * Function: isBinary + * + * Returns . + */ + isBinary = () => { + return this.binary; + }; -/** - * Function: getText - * - * Returns the response as a string. - */ -getText = ()=> -{ - return this.request.responseText; -}; + /** + * Function: setBinary + * + * Sets . + */ + setBinary = (value) => { + this.binary = value; + }; -/** - * Function: isReady - * - * Returns true if the response is ready. - */ -isReady = ()=> -{ - return this.request.readyState == 4; -}; + /** + * Function: getText + * + * Returns the response as a string. + */ + getText = () => { + return this.request.responseText; + }; -/** - * Function: getDocumentElement - * - * Returns the document element of the response XML document. - */ -getDocumentElement = ()=> -{ - var doc = this.getXml(); + /** + * Function: isReady + * + * Returns true if the response is ready. + */ + isReady = () => { + return this.request.readyState == 4; + }; - if (doc != null) - { - return doc.documentElement; - } + /** + * Function: getDocumentElement + * + * Returns the document element of the response XML document. + */ + getDocumentElement = () => { + var doc = this.getXml(); - return null; -}; + if (doc != null) { + return doc.documentElement; + } -/** - * Function: getXml - * - * Returns the response as an XML document. Use to get - * the document element of the XML document. - */ -getXml = ()=> -{ - var xml = this.request.responseXML; + return null; + }; - // Handles missing response headers in IE, the first condition handles - // the case where responseXML is there, but using its nodes leads to - // type errors in the mxCellCodec when putting the nodes into a new - // document. This happens in IE9 standards mode and with XML user - // objects only, as they are used directly as values in cells. - if (xml == null || xml.documentElement == null) - { - xml = mxUtils.parseXml(this.request.responseText); - } + /** + * Function: getXml + * + * Returns the response as an XML document. Use to get + * the document element of the XML document. + */ + getXml = () => { + var xml = this.request.responseXML; - return xml; -}; + // Handles missing response headers in IE, the first condition handles + // the case where responseXML is there, but using its nodes leads to + // type errors in the mxCellCodec when putting the nodes into a new + // document. This happens in IE9 standards mode and with XML user + // objects only, as they are used directly as values in cells. + if (xml == null || xml.documentElement == null) { + xml = mxUtils.parseXml(this.request.responseText); + } -/** - * Function: getStatus - * - * Returns the status as a number, eg. 404 for "Not found" or 200 for "OK". - * Note: The NS_ERROR_NOT_AVAILABLE for invalid responses cannot be cought. - */ -getStatus = ()=> -{ - return (this.request != null) ? this.request.status : null; -}; + return xml; + }; -/** - * Function: create - * - * Creates and returns the inner object. - */ -create = ()=> -{ - if (window.XMLHttpRequest) - { - return ()=> - { - var req = new XMLHttpRequest(); + /** + * Function: getStatus + * + * Returns the status as a number, eg. 404 for "Not found" or 200 for "OK". + * Note: The NS_ERROR_NOT_AVAILABLE for invalid responses cannot be cought. + */ + getStatus = () => { + return (this.request != null) ? this.request.status : null; + }; - // TODO: Check for overrideMimeType required here? - if (this.isBinary() && req.overrideMimeType) - { - req.overrideMimeType('text/plain; charset=x-user-defined'); - } + /** + * Function: create + * + * Creates and returns the inner object. + */ + create = () => { + if (window.XMLHttpRequest) { + return () => { + var req = new XMLHttpRequest(); - return req; - }; - } - else if (typeof(ActiveXObject) != 'undefined') - { - return ()=> - { - // TODO: Implement binary option - return new ActiveXObject('Microsoft.XMLHTTP'); - }; - } -}(); - -/** - * Function: send - * - * Send the to the target URL using the specified functions to - * process the response asychronously. - * - * Note: Due to technical limitations, onerror is currently ignored. - * - * Parameters: - * - * onload - Function to be invoked if a successful response was received. - * onerror - Function to be called on any error. Unused in this implementation, intended for overriden function. - * timeout - Optional timeout in ms before calling ontimeout. - * ontimeout - Optional function to execute on timeout. - */ -send = (onload, onerror, timeout, ontimeout)=> -{ - this.request = this.create(); - - if (this.request != null) - { - if (onload != null) - { - this.request.onreadystatechange = mxUtils.bind(this, ()=> - { - if (this.isReady()) - { - onload(this); - this.request.onreadystatechange = null; + // TODO: Check for overrideMimeType required here? + if (this.isBinary() && req.overrideMimeType) { + req.overrideMimeType('text/plain; charset=x-user-defined'); } - }); + + return req; + }; + } else if (typeof (ActiveXObject) != 'undefined') { + return () => { + // TODO: Implement binary option + return new ActiveXObject('Microsoft.XMLHTTP'); + }; } - - this.request.open(this.method, this.url, this.async, - this.username, this.password); - this.setRequestHeaders(this.request, this.params); - - if (window.XMLHttpRequest && this.withCredentials) - { - this.request.withCredentials = 'true'; - } - - if ((document.documentMode == null) && - window.XMLHttpRequest && timeout != null && ontimeout != null) - { - this.request.timeout = timeout; - this.request.ontimeout = ontimeout; - } - - this.request.send(this.params); } -}; +() + ; -/** - * Function: setRequestHeaders - * - * Sets the headers for the given request and parameters. This sets the - * content-type to application/x-www-form-urlencoded if any params exist. - * - * Example: - * - * (code) - * request.setRequestHeaders = (request, params)=> - * { - * if (params != null) - * { - * request.setRequestHeader('Content-Type', - * 'multipart/form-data'); - * request.setRequestHeader('Content-Length', - * params.length); - * } - * }; - * (end) - * - * Use the code above before calling if you require a - * multipart/form-data request. - */ -setRequestHeaders = (request, params)=> -{ - if (params != null) - { - request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); - } -}; + /** + * Function: send + * + * Send the to the target URL using the specified functions to + * process the response asychronously. + * + * Note: Due to technical limitations, onerror is currently ignored. + * + * Parameters: + * + * onload - Function to be invoked if a successful response was received. + * onerror - Function to be called on any error. Unused in this implementation, intended for overriden function. + * timeout - Optional timeout in ms before calling ontimeout. + * ontimeout - Optional function to execute on timeout. + */ + send = (onload, onerror, timeout, ontimeout) => { + this.request = this.create(); -/** - * Function: simulate - * - * Creates and posts a request to the given target URL using a dynamically - * created form inside the given document. - * - * Parameters: - * - * docs - Document that contains the form element. - * target - Target to send the form result to. - */ -simulate = (doc, target)=> -{ - doc = doc || document; - var old = null; - - if (doc == document) - { - old = window.onbeforeunload; - window.onbeforeunload = null; - } - - var form = doc.createElement('form'); - form.setAttribute('method', this.method); - form.setAttribute('action', this.url); - - if (target != null) - { - form.setAttribute('target', target); - } - - form.style.display = 'none'; - form.style.visibility = 'hidden'; - - var pars = (this.params.indexOf('&') > 0) ? - this.params.split('&') : - this.params.split(); - - // Adds the parameters as textareas to the form - for (var i=0; i 0) - { - var name = pars[i].substring(0, pos); - var value = pars[i].substring(pos+1); - - if (this.decodeSimulateValues) - { - value = decodeURIComponent(value); + if (this.request != null) { + if (onload != null) { + this.request.onreadystatechange = mxUtils.bind(this, () => { + if (this.isReady()) { + onload(this); + this.request.onreadystatechange = null; + } + }); } - var textarea = doc.createElement('textarea'); - textarea.setAttribute('wrap', 'off'); - textarea.setAttribute('name', name); - mxUtils.write(textarea, value); - form.appendChild(textarea); + this.request.open(this.method, this.url, this.async, + this.username, this.password); + this.setRequestHeaders(this.request, this.params); + + if (window.XMLHttpRequest && this.withCredentials) { + this.request.withCredentials = 'true'; + } + + if ((document.documentMode == null) && + window.XMLHttpRequest && timeout != null && ontimeout != null) { + this.request.timeout = timeout; + this.request.ontimeout = ontimeout; + } + + this.request.send(this.params); } - } + }; - doc.body.appendChild(form); - form.submit(); + /** + * Function: setRequestHeaders + * + * Sets the headers for the given request and parameters. This sets the + * content-type to application/x-www-form-urlencoded if any params exist. + * + * Example: + * + * (code) + * request.setRequestHeaders = (request, params)=> + * { + * if (params != null) + * { + * request.setRequestHeader('Content-Type', + * 'multipart/form-data'); + * request.setRequestHeader('Content-Length', + * params.length); + * } + * }; + * (end) + * + * Use the code above before calling if you require a + * multipart/form-data request. + */ + setRequestHeaders = (request, params) => { + if (params != null) { + request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + } + }; - if (form.parentNode != null) - { - form.parentNode.removeChild(form); - } + /** + * Function: simulate + * + * Creates and posts a request to the given target URL using a dynamically + * created form inside the given document. + * + * Parameters: + * + * docs - Document that contains the form element. + * target - Target to send the form result to. + */ + simulate = (doc, target) => { + doc = doc || document; + var old = null; - if (old != null) - { - window.onbeforeunload = old; - } -}; + if (doc == document) { + old = window.onbeforeunload; + window.onbeforeunload = null; + } + + var form = doc.createElement('form'); + form.setAttribute('method', this.method); + form.setAttribute('action', this.url); + + if (target != null) { + form.setAttribute('target', target); + } + + form.style.display = 'none'; + form.style.visibility = 'hidden'; + + var pars = (this.params.indexOf('&') > 0) ? + this.params.split('&') : + this.params.split(); + + // Adds the parameters as textareas to the form + for (var i = 0; i < pars.length; i++) { + var pos = pars[i].indexOf('='); + + if (pos > 0) { + var name = pars[i].substring(0, pos); + var value = pars[i].substring(pos + 1); + + if (this.decodeSimulateValues) { + value = decodeURIComponent(value); + } + + var textarea = doc.createElement('textarea'); + textarea.setAttribute('wrap', 'off'); + textarea.setAttribute('name', name); + mxUtils.write(textarea, value); + form.appendChild(textarea); + } + } + + doc.body.appendChild(form); + form.submit(); + + if (form.parentNode != null) { + form.parentNode.removeChild(form); + } + + if (old != null) { + window.onbeforeunload = old; + } + }; +} + +export default mxXmlRequest; diff --git a/src/js/view/mxCellRenderer.js b/src/js/view/mxCellRenderer.js index c6bb30323..3a40e2450 100644 --- a/src/js/view/mxCellRenderer.js +++ b/src/js/view/mxCellRenderer.js @@ -41,7 +41,7 @@ function mxCellRenderer() { }; * known to all instances of this class. For adding new shapes you should * use the static function. */ -mxCellRenderer.defaultShapes = new Object(); +mxCellRenderer.defaultShapes = {}; /** * Variable: defaultEdgeShape diff --git a/src/js/view/mxGraph.js b/src/js/view/mxGraph.js index 413aaf3c4..de9bb315d 100644 --- a/src/js/view/mxGraph.js +++ b/src/js/view/mxGraph.js @@ -199,7 +199,7 @@ * shapename and the stylename we'll use boxstyle for the stylename: * * (code) - * var style = new Object(); + * var style = {}; * style[mxConstants.STYLE_SHAPE] = 'box'; * style[mxConstants.STYLE_STROKECOLOR] = '#000000'; * style[mxConstants.STYLE_FONTCOLOR] = '#000000'; @@ -3325,7 +3325,7 @@ getCellStyle = (cell)=> // Returns a non-null value if no style can be found if (style == null) { - style = new Object(); + style = {}; } return style; @@ -8690,7 +8690,7 @@ validateEdge = (edge, source, target)=> validateGraph = (cell, context)=> { cell = (cell != null) ? cell : this.model.getRoot(); - context = (context != null) ? context : new Object(); + context = (context != null) ? context : {}; var isValid = true; var childCount = this.model.getChildCount(cell); @@ -8702,7 +8702,7 @@ validateGraph = (cell, context)=> if (this.isValidRoot(tmp)) { - ctx = new Object(); + ctx = {}; } var warn = this.validateGraph(tmp, ctx); diff --git a/src/js/view/mxStylesheet.js b/src/js/view/mxStylesheet.js index 0263593a8..07837d702 100644 --- a/src/js/view/mxStylesheet.js +++ b/src/js/view/mxStylesheet.js @@ -53,7 +53,7 @@ */ function mxStylesheet() { - this.styles = new Object(); + this.styles = {}; this.putDefaultVertexStyle(this.createDefaultVertexStyle()); this.putDefaultEdgeStyle(this.createDefaultEdgeStyle()); @@ -75,7 +75,7 @@ FIXME; */ createDefaultVertexStyle = ()=> { - var style = new Object(); + var style = {}; style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE; style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter; @@ -95,7 +95,7 @@ createDefaultVertexStyle = ()=> */ createDefaultEdgeStyle = ()=> { - var style = new Object(); + var style = {}; style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_CONNECTOR; style[mxConstants.STYLE_ENDARROW] = mxConstants.ARROW_CLASSIC; @@ -163,7 +163,7 @@ getDefaultEdgeStyle = ()=> * existing stylesheet: * * (code) - * var style = new Object(); + * var style = {}; * style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE; * style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter; * style[mxConstants.STYLE_ROUNDED] = true; @@ -220,7 +220,7 @@ getCellStyle = (name, defaultStyle)=> } else { - style = new Object(); + style = {}; } // Parses each key, value pair into the existing style