From 0b3058763e9b3ef2c6d21d893c02322f8038d92d Mon Sep 17 00:00:00 2001 From: sbosse Date: Mon, 21 Jul 2025 23:40:21 +0200 Subject: [PATCH] Mon 21 Jul 22:43:21 CEST 2025 --- js/ui/mxgraph/src/js/view/mxGraph.js | 12737 +++++++++++++++++++++++++ 1 file changed, 12737 insertions(+) create mode 100644 js/ui/mxgraph/src/js/view/mxGraph.js diff --git a/js/ui/mxgraph/src/js/view/mxGraph.js b/js/ui/mxgraph/src/js/view/mxGraph.js new file mode 100644 index 0000000..0048987 --- /dev/null +++ b/js/ui/mxgraph/src/js/view/mxGraph.js @@ -0,0 +1,12737 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +/** + * Class: mxGraph + * + * Extends to implement a graph component for + * the browser. This is the main class of the package. To activate + * panning and connections use and . + * For rubberband selection you must create a new instance of + * . The following listeners are added to + * by default: + * + * - : that displays tooltips + * - : for panning and popup menus + * - : for creating connections + * - : for moving and cloning cells + * + * These listeners will be called in the above order if they are enabled. + * + * Background Images: + * + * To display a background image, set the image, image width and + * image height using . If one of the + * above values has changed then the 's + * should be invoked. + * + * Cell Images: + * + * To use images in cells, a shape must be specified in the default + * vertex style (or any named style). Possible shapes are + * and . + * The code to change the shape used in the default vertex style, + * the following code is used: + * + * (code) + * var style = graph.getStylesheet().getDefaultVertexStyle(); + * style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_IMAGE; + * (end) + * + * For the default vertex style, the image to be displayed can be + * specified in a cell's style using the + * key and the image URL as a value, for example: + * + * (code) + * image=http://www.example.com/image.gif + * (end) + * + * For a named style, the the stylename must be the first element + * of the cell style: + * + * (code) + * stylename;image=http://www.example.com/image.gif + * (end) + * + * A cell style can have any number of key=value pairs added, divided + * by a semicolon as follows: + * + * (code) + * [stylename;|key=value;] + * (end) + * + * Labels: + * + * The cell labels are defined by which uses + * if is true. If a label must be rendered as HTML markup, then + * should return true for the respective cell. If all labels + * contain HTML markup, can be set to true. NOTE: Enabling HTML + * labels carries a possible security risk (see the section on security in + * the manual). + * + * If wrapping is needed for a label, then and must + * return true for the cell whose label should be wrapped. See for + * an example. + * + * If clipping is needed to keep the rendering of a HTML label inside the + * bounds of its vertex, then should return true for the + * respective cell. + * + * By default, edge labels are movable and vertex labels are fixed. This can be + * changed by setting and , or by + * overriding . + * + * In-place Editing: + * + * In-place editing is started with a doubleclick or by typing F2. + * Programmatically, is used to check if the cell is editable + * () and call , which invokes + * . The editor uses the value returned + * by as the editing value. + * + * After in-place editing, is called, which invokes + * , which in turn calls + * via . + * + * The event that triggers in-place editing is passed through to the + * , which may take special actions depending on the type of the + * event or mouse location, and is also passed to . The event + * is then passed back to the event processing functions which can perform + * specific actions based on the trigger event. + * + * Tooltips: + * + * Tooltips are implemented by , which calls + * if a cell is under the mousepointer. The default implementation checks if + * the cell has a getTooltip function and calls it if it exists. Hence, in order + * to provide custom tooltips, the cell must provide a getTooltip function, or + * one of the two above functions must be overridden. + * + * Typically, for custom cell tooltips, the latter function is overridden as + * follows: + * + * (code) + * graph.getTooltipForCell = function(cell) + * { + * var label = this.convertValueToString(cell); + * return 'Tooltip for '+label; + * } + * (end) + * + * When using a config file, the function is overridden in the mxGraph section + * using the following entry: + * + * (code) + * + * (end) + * + * "this" refers to the graph in the implementation, so for example to check if + * a cell is an edge, you use this.getModel().isEdge(cell) + * + * For replacing the default implementation of (rather than + * replacing the function on a specific instance), the following code should be + * used after loading the JavaScript files, but before creating a new mxGraph + * instance using : + * + * (code) + * mxGraph.prototype.getTooltipForCell = function(cell) + * { + * var label = this.convertValueToString(cell); + * return 'Tooltip for '+label; + * } + * (end) + * + * 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: + * + * (code) + * mxCellRenderer.registerShape('box', BoxShape); + * (end) + * + * 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 .) + * + * 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 with the following format: [stylename;|key=value;]. The + * string is resolved to a collection of key, value pairs, where the keys are + * overridden with the values in the string. + * + * When introducing a new shape, the name under which the shape is registered + * must be used in the stylesheet. There are three ways of doing this: + * + * - By changing the default style, so that all vertices will use the new + * shape + * - By defining a new style, so that only vertices with the respective + * cellstyle will use the new shape + * - By using shape=box in the cellstyle's optional list of key, value pairs + * to be overridden + * + * In the first case, the code to fetch and modify the default style for + * vertices is as follows: + * + * (code) + * var style = graph.getStylesheet().getDefaultVertexStyle(); + * style[mxConstants.STYLE_SHAPE] = 'box'; + * (end) + * + * 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: + * + * (code) + * var style = new Object(); + * style[mxConstants.STYLE_SHAPE] = 'box'; + * style[mxConstants.STYLE_STROKECOLOR] = '#000000'; + * style[mxConstants.STYLE_FONTCOLOR] = '#000000'; + * graph.getStylesheet().putCellStyle('boxstyle', style); + * (end) + * + * The code adds a new style with the name boxstyle to the stylesheet. To use + * this style with a cell, it must be referenced from the cellstyle as follows: + * + * (code) + * var vertex = graph.insertVertex(parent, null, 'Hello, World!', 20, 20, 80, 20, + * 'boxstyle'); + * (end) + * + * To summarize, each new shape must be registered in the with + * a unique name. That name is then used as the value of the shape-key in a + * default or custom style. If there are multiple custom shapes, then there + * should be a separate style for each shape. + * + * Inheriting Styles: + * + * For fill-, stroke-, gradient- and indicatorColors special keywords can be + * used. The inherit keyword for one of these colors will inherit the color + * for the same key from the parent cell. The swimlane keyword does the same, + * but inherits from the nearest swimlane in the ancestor hierarchy. Finally, + * the indicated keyword will use the color of the indicator as the color for + * the given key. + * + * Scrollbars: + * + * The overflow CSS property defines if scrollbars are used to + * display the graph. For values of 'auto' or 'scroll', the scrollbars will + * be shown. Note that the flag is normally not used + * together with scrollbars, as it will resize the container to match the + * size of the graph after each change. + * + * Multiplicities and Validation: + * + * To control the possible connections in mxGraph, is + * used. The default implementation of the function uses , + * which is an array of . Using this class allows to establish + * simple multiplicities, which are enforced by the graph. + * + * The uses to determine for which terminals it + * applies. The default implementation of works with DOM nodes (XML + * nodes) and checks if the given type parameter matches the nodeName of the + * node (case insensitive). Optionally, an attributename and value can be + * specified which are also checked. + * + * is called whenever the connectivity of an edge + * changes. It returns an empty string or an error message if the edge is + * invalid or null if the edge is valid. If the returned string is not empty + * then it is displayed as an error message. + * + * allows to specify the multiplicity between a terminal and + * its possible neighbors. For example, if any rectangle may only be connected + * to, say, a maximum of two circles you can add the following rule to + * : + * + * (code) + * graph.multiplicities.push(new mxMultiplicity( + * true, 'rectangle', null, null, 0, 2, ['circle'], + * 'Only 2 targets allowed', + * 'Only shape targets allowed')); + * (end) + * + * This will display the first error message whenever a rectangle is connected + * to more than two circles and the second error message if a rectangle is + * connected to anything but a circle. + * + * For certain multiplicities, such as a minimum of 1 connection, which cannot + * be enforced at cell creation time (unless the cell is created together with + * the connection), mxGraph offers which checks all multiplicities + * for all cells and displays the respective error messages in an overlay icon + * on the cells. + * + * If a cell is collapsed and contains validation errors, a respective warning + * icon is attached to the collapsed cell. + * + * Auto-Layout: + * + * For automatic layout, the hook is provided in . + * It can be overridden to return a layout algorithm for the children of a + * given cell. + * + * Unconnected edges: + * + * The default values for all switches are designed to meet the requirements of + * general diagram drawing applications. A very typical set of settings to + * avoid edges that are not connected is the following: + * + * (code) + * graph.setAllowDanglingEdges(false); + * graph.setDisconnectOnMove(false); + * (end) + * + * Setting the switch to true is optional. This switch + * controls if edges are inserted after a copy, paste or clone-drag if they are + * invalid. For example, edges are invalid if copied or control-dragged without + * having selected the corresponding terminals and allowDanglingEdges is + * false, in which case the edges will not be cloned if the switch is false. + * + * Output: + * + * To produce an XML representation for a diagram, the following code can be + * used. + * + * (code) + * var enc = new mxCodec(mxUtils.createXmlDocument()); + * var node = enc.encode(graph.getModel()); + * (end) + * + * This will produce an XML node than can be handled using the DOM API or + * turned into a string representation using the following code: + * + * (code) + * var xml = mxUtils.getXml(node); + * (end) + * + * To obtain a formatted string, mxUtils.getPrettyXml can be used instead. + * + * This string can now be stored in a local persistent storage (for example + * using Google Gears) or it can be passed to a backend using mxUtils.post as + * follows. The url variable is the URL of the Java servlet, PHP page or HTTP + * handler, depending on the server. + * + * (code) + * var xmlString = encodeURIComponent(mxUtils.getXml(node)); + * mxUtils.post(url, 'xml='+xmlString, function(req) + * { + * // Process server response using req of type mxXmlRequest + * }); + * (end) + * + * Input: + * + * To load an XML representation of a diagram into an existing graph object + * mxUtils.load can be used as follows. The url variable is the URL of the Java + * servlet, PHP page or HTTP handler that produces the XML string. + * + * (code) + * var xmlDoc = mxUtils.load(url).getXml(); + * var node = xmlDoc.documentElement; + * var dec = new mxCodec(node.ownerDocument); + * dec.decode(node, graph.getModel()); + * (end) + * + * For creating a page that loads the client and a diagram using a single + * request please refer to the deployment examples in the backends. + * + * Functional dependencies: + * + * (see images/callgraph.png) + * + * Resources: + * + * resources/graph - Language resources for mxGraph + * + * Group: Events + * + * Event: mxEvent.ROOT + * + * Fires if the root in the model has changed. This event has no properties. + * + * Event: mxEvent.ALIGN_CELLS + * + * Fires between begin- and endUpdate in . The cells + * and align properties contain the respective arguments that were + * passed to . + * + * Event: mxEvent.FLIP_EDGE + * + * Fires between begin- and endUpdate in . The edge + * property contains the edge passed to . + * + * Event: mxEvent.ORDER_CELLS + * + * Fires between begin- and endUpdate in . The cells + * and back properties contain the respective arguments that were + * passed to . + * + * Event: mxEvent.CELLS_ORDERED + * + * Fires between begin- and endUpdate in . The cells + * and back arguments contain the respective arguments that were + * passed to . + * + * Event: mxEvent.GROUP_CELLS + * + * Fires between begin- and endUpdate in . The group, + * cells and border arguments contain the respective + * arguments that were passed to . + * + * Event: mxEvent.UNGROUP_CELLS + * + * Fires between begin- and endUpdate in . The cells + * property contains the array of cells that was passed to . + * + * Event: mxEvent.REMOVE_CELLS_FROM_PARENT + * + * Fires between begin- and endUpdate in . The + * cells property contains the array of cells that was passed to + * . + * + * Event: mxEvent.ADD_CELLS + * + * Fires between begin- and endUpdate in . The cells, + * parent, index, source and + * target properties contain the respective arguments that were + * passed to . + * + * Event: mxEvent.CELLS_ADDED + * + * Fires between begin- and endUpdate in . The cells, + * parent, index, source, + * target and absolute properties contain the + * respective arguments that were passed to . + * + * Event: mxEvent.REMOVE_CELLS + * + * Fires between begin- and endUpdate in . The cells + * and includeEdges arguments contain the respective arguments + * that were passed to . + * + * Event: mxEvent.CELLS_REMOVED + * + * Fires between begin- and endUpdate in . The cells + * argument contains the array of cells that was removed. + * + * Event: mxEvent.SPLIT_EDGE + * + * Fires between begin- and endUpdate in . The edge + * property contains the edge to be splitted, the cells, + * newEdge, dx and dy properties contain + * the respective arguments that were passed to . + * + * Event: mxEvent.TOGGLE_CELLS + * + * Fires between begin- and endUpdate in . The show, + * cells and includeEdges properties contain the + * respective arguments that were passed to . + * + * Event: mxEvent.FOLD_CELLS + * + * Fires between begin- and endUpdate in . The + * collapse, cells and recurse + * properties contain the respective arguments that were passed to . + * + * Event: mxEvent.CELLS_FOLDED + * + * Fires between begin- and endUpdate in cellsFolded. The + * collapse, cells and recurse + * properties contain the respective arguments that were passed to + * . + * + * Event: mxEvent.UPDATE_CELL_SIZE + * + * Fires between begin- and endUpdate in . The + * cell and ignoreChildren properties contain the + * respective arguments that were passed to . + * + * Event: mxEvent.RESIZE_CELLS + * + * Fires between begin- and endUpdate in . The cells + * and bounds properties contain the respective arguments that + * were passed to . + * + * Event: mxEvent.CELLS_RESIZED + * + * Fires between begin- and endUpdate in . The cells + * and bounds properties contain the respective arguments that + * were passed to . + * + * Event: mxEvent.MOVE_CELLS + * + * Fires between begin- and endUpdate in . The cells, + * dx, dy, clone, target + * and event properties contain the respective arguments that + * were passed to . + * + * Event: mxEvent.CELLS_MOVED + * + * Fires between begin- and endUpdate in . The cells, + * dx, dy and disconnect properties + * contain the respective arguments that were passed to . + * + * Event: mxEvent.CONNECT_CELL + * + * Fires between begin- and endUpdate in . The edge, + * terminal and source properties contain the + * respective arguments that were passed to . + * + * Event: mxEvent.CELL_CONNECTED + * + * Fires between begin- and endUpdate in . The + * edge, terminal and source properties + * contain the respective arguments that were passed to . + * + * Event: mxEvent.REFRESH + * + * Fires after was executed. This event has no properties. + * + * Event: mxEvent.CLICK + * + * Fires in after a click event. The event property + * contains the original mouse event and cell property contains + * the cell under the mouse or null if the background was clicked. + * + * Event: mxEvent.DOUBLE_CLICK + * + * Fires in after a double click. The event property + * contains the original mouse event and the cell property + * contains the cell under the mouse or null if the background was clicked. + * + * Event: mxEvent.GESTURE + * + * Fires in after a touch gesture. The event + * property contains the original gesture end event and the cell + * property contains the optional cell associated with the gesture. + * + * Event: mxEvent.TAP_AND_HOLD + * + * Fires in if a tap and hold event was detected. The event + * property contains the initial touch event and the cell property + * contains the cell under the mouse or null if the background was clicked. + * + * Event: mxEvent.FIRE_MOUSE_EVENT + * + * Fires in before the mouse listeners are invoked. The + * eventName property contains the event name and the + * event property contains the . + * + * Event: mxEvent.SIZE + * + * Fires after was executed. The bounds property + * contains the new graph bounds. + * + * Event: mxEvent.START_EDITING + * + * Fires before the in-place editor starts in . The + * cell property contains the cell that is being edited and the + * event property contains the optional event argument that was + * passed to . + * + * Event: mxEvent.EDITING_STARTED + * + * Fires after the in-place editor starts in . The + * cell property contains the cell that is being edited and the + * event property contains the optional event argument that was + * passed to . + * + * Event: mxEvent.EDITING_STOPPED + * + * Fires after the in-place editor stops in . + * + * Event: mxEvent.LABEL_CHANGED + * + * Fires between begin- and endUpdate in . The + * cell property contains the cell, the value + * property contains the new value for the cell, the old property + * contains the old value and the optional event property contains + * the mouse event that started the edit. + * + * Event: mxEvent.ADD_OVERLAY + * + * Fires after an overlay is added in . The cell + * property contains the cell and the overlay property contains + * the that was added. + * + * Event: mxEvent.REMOVE_OVERLAY + * + * Fires after an overlay is removed in and + * . The cell property contains the cell and + * the overlay property contains the that was + * removed. + * + * Constructor: mxGraph + * + * Constructs a new mxGraph in the specified container. Model is an optional + * mxGraphModel. If no model is provided, a new mxGraphModel instance is + * used as the model. The container must have a valid owner document prior + * to calling this function in Internet Explorer. RenderHint is a string to + * affect the display performance and rendering in IE, but not in SVG-based + * browsers. The parameter is mapped to , which may + * be one of for SVG-based browsers, + * for fastest display mode, + * for faster display mode, + * for fast and + * for exact display mode (slowest). The dialects are defined in mxConstants. + * The default values are DIALECT_SVG for SVG-based browsers and + * DIALECT_MIXED for IE. + * + * The possible values for the renderingHint parameter are explained below: + * + * fast - The parameter is based on the fact that the display performance is + * highly improved in IE if the VML is not contained within a VML group + * element. The lack of a group element only slightly affects the display while + * panning, but improves the performance by almost a factor of 2, while keeping + * the display sufficiently accurate. This also allows to render certain shapes as HTML + * if the display accuracy is not affected, which is implemented by + * . This is the default setting and is mapped to + * DIALECT_MIXEDHTML. + * faster - Same as fast, but more expensive shapes are avoided. This is + * controlled by . The default implementation will + * avoid gradients and rounded rectangles, but more significant shapes, such + * as rhombus, ellipse, actor and cylinder will be rendered accurately. This + * setting is mapped to DIALECT_PREFERHTML. + * fastest - Almost anything will be rendered in Html. This allows for + * rectangles, labels and images. This setting is mapped to + * DIALECT_STRICTHTML. + * exact - If accurate panning is required and if the diagram is small (up + * to 100 cells), then this value should be used. In this mode, a group is + * created that contains the VML. This allows for accurate panning and is + * mapped to DIALECT_VML. + * + * Example: + * + * To create a graph inside a DOM node with an id of graph: + * (code) + * var container = document.getElementById('graph'); + * var graph = new mxGraph(container); + * (end) + * + * Parameters: + * + * container - Optional DOM node that acts as a container for the graph. + * If this is null then the container can be initialized later using + * . + * model - Optional that constitutes the graph data. + * renderHint - Optional string that specifies the display accuracy and + * performance. Default is mxConstants.DIALECT_MIXEDHTML (for IE). + * stylesheet - Optional to be used in the graph. + */ +function mxGraph(container, model, renderHint, stylesheet) +{ + // Initializes the variable in case the prototype has been + // modified to hold some listeners (which is possible because + // the createHandlers call is executed regardless of the + // arguments passed into the ctor). + this.mouseListeners = null; + + // Converts the renderHint into a dialect + this.renderHint = renderHint; + + if (mxClient.IS_SVG) + { + this.dialect = mxConstants.DIALECT_SVG; + } + else if (renderHint == mxConstants.RENDERING_HINT_EXACT && mxClient.IS_VML) + { + this.dialect = mxConstants.DIALECT_VML; + } + else if (renderHint == mxConstants.RENDERING_HINT_FASTEST) + { + this.dialect = mxConstants.DIALECT_STRICTHTML; + } + else if (renderHint == mxConstants.RENDERING_HINT_FASTER) + { + this.dialect = mxConstants.DIALECT_PREFERHTML; + } + else // default for VML + { + this.dialect = mxConstants.DIALECT_MIXEDHTML; + } + + // Initializes the main members that do not require a container + this.model = (model != null) ? model : new mxGraphModel(); + this.multiplicities = []; + this.imageBundles = []; + this.cellRenderer = this.createCellRenderer(); + this.setSelectionModel(this.createSelectionModel()); + this.setStylesheet((stylesheet != null) ? stylesheet : this.createStylesheet()); + this.view = this.createGraphView(); + + // Adds a graph model listener to update the view + this.graphModelChangeListener = mxUtils.bind(this, function(sender, evt) + { + this.graphModelChanged(evt.getProperty('edit').changes); + }); + + this.model.addListener(mxEvent.CHANGE, this.graphModelChangeListener); + + // Installs basic event handlers with disabled default settings. + this.createHandlers(); + + // Initializes the display if a container was specified + if (container != null) + { + this.init(container); + } + + this.view.revalidate(); +}; + +/** + * Installs the required language resources at class + * loading time. + */ +if (mxLoadResources) +{ + mxResources.add(mxClient.basePath+'/resources/graph'); +} + +/** + * Extends mxEventSource. + */ +mxGraph.prototype = new mxEventSource(); +mxGraph.prototype.constructor = mxGraph; + +/** + * Variable: EMPTY_ARRAY + * + * Immutable empty array instance. + */ +mxGraph.prototype.EMPTY_ARRAY = []; + +/** + * Group: Variables + */ + +/** + * Variable: mouseListeners + * + * Holds the mouse event listeners. See . + */ +mxGraph.prototype.mouseListeners = null; + +/** + * Variable: isMouseDown + * + * Holds the state of the mouse button. + */ +mxGraph.prototype.isMouseDown = false; + +/** + * Variable: model + * + * Holds the that contains the cells to be displayed. + */ +mxGraph.prototype.model = null; + +/** + * Variable: view + * + * Holds the that caches the for the cells. + */ +mxGraph.prototype.view = null; + +/** + * Variable: stylesheet + * + * Holds the that defines the appearance of the cells. + * + * + * Example: + * + * Use the following code to read a stylesheet into an existing graph. + * + * (code) + * var req = mxUtils.load('stylesheet.xml'); + * var root = req.getDocumentElement(); + * var dec = new mxCodec(root.ownerDocument); + * dec.decode(root, graph.stylesheet); + * (end) + */ +mxGraph.prototype.stylesheet = null; + +/** + * Variable: selectionModel + * + * Holds the that models the current selection. + */ +mxGraph.prototype.selectionModel = null; + +/** + * Variable: cellEditor + * + * Holds the that is used as the in-place editing. + */ +mxGraph.prototype.cellEditor = null; + +/** + * Variable: cellRenderer + * + * Holds the for rendering the cells in the graph. + */ +mxGraph.prototype.cellRenderer = null; + +/** + * Variable: multiplicities + * + * An array of describing the allowed + * connections in a graph. + */ +mxGraph.prototype.multiplicities = null; + +/** + * Variable: renderHint + * + * RenderHint as it was passed to the constructor. + */ +mxGraph.prototype.renderHint = null; + +/** + * Variable: dialect + * + * Dialect to be used for drawing the graph. Possible values are all + * constants in with a DIALECT-prefix. + */ +mxGraph.prototype.dialect = null; + +/** + * Variable: gridSize + * + * Specifies the grid size. Default is 10. + */ +mxGraph.prototype.gridSize = 10; + +/** + * Variable: gridEnabled + * + * Specifies if the grid is enabled. This is used in . Default is + * true. + */ +mxGraph.prototype.gridEnabled = true; + +/** + * Variable: portsEnabled + * + * Specifies if ports are enabled. This is used in to update + * the respective style. Default is true. + */ +mxGraph.prototype.portsEnabled = true; + +/** + * Variable: nativeDoubleClickEnabled + * + * Specifies if native double click events should be detected. Default is true. + */ +mxGraph.prototype.nativeDblClickEnabled = true; + +/** + * Variable: doubleTapEnabled + * + * Specifies if double taps on touch-based devices should be handled as a + * double click. Default is true. + */ +mxGraph.prototype.doubleTapEnabled = true; + +/** + * Variable: doubleTapTimeout + * + * Specifies the timeout for double taps and non-native double clicks. Default + * is 500 ms. + */ +mxGraph.prototype.doubleTapTimeout = 500; + +/** + * Variable: doubleTapTolerance + * + * Specifies the tolerance for double taps and double clicks in quirks mode. + * Default is 25 pixels. + */ +mxGraph.prototype.doubleTapTolerance = 25; + +/** + * Variable: lastTouchX + * + * Holds the x-coordinate of the last touch event for double tap detection. + */ +mxGraph.prototype.lastTouchY = 0; + +/** + * Variable: lastTouchX + * + * Holds the y-coordinate of the last touch event for double tap detection. + */ +mxGraph.prototype.lastTouchY = 0; + +/** + * Variable: lastTouchTime + * + * Holds the time of the last touch event for double click detection. + */ +mxGraph.prototype.lastTouchTime = 0; + +/** + * Variable: tapAndHoldEnabled + * + * Specifies if tap and hold should be used for starting connections on touch-based + * devices. Default is true. + */ +mxGraph.prototype.tapAndHoldEnabled = true; + +/** + * Variable: tapAndHoldDelay + * + * Specifies the time for a tap and hold. Default is 500 ms. + */ +mxGraph.prototype.tapAndHoldDelay = 500; + +/** + * Variable: tapAndHoldInProgress + * + * True if the timer for tap and hold events is running. + */ +mxGraph.prototype.tapAndHoldInProgress = false; + +/** + * Variable: tapAndHoldValid + * + * True as long as the timer is running and the touch events + * stay within the given . + */ +mxGraph.prototype.tapAndHoldValid = false; + +/** + * Variable: initialTouchX + * + * Holds the x-coordinate of the intial touch event for tap and hold. + */ +mxGraph.prototype.initialTouchX = 0; + +/** + * Variable: initialTouchY + * + * Holds the y-coordinate of the intial touch event for tap and hold. + */ +mxGraph.prototype.initialTouchY = 0; + +/** + * Variable: tolerance + * + * Tolerance for a move to be handled as a single click. + * Default is 4 pixels. + */ +mxGraph.prototype.tolerance = 4; + +/** + * Variable: defaultOverlap + * + * Value returned by if returns + * true for the given cell. is used in if + * returns true. The value specifies the + * portion of the child which is allowed to overlap the parent. + */ +mxGraph.prototype.defaultOverlap = 0.5; + +/** + * Variable: defaultParent + * + * Specifies the default parent to be used to insert new cells. + * This is used in . Default is null. + */ +mxGraph.prototype.defaultParent = null; + +/** + * Variable: alternateEdgeStyle + * + * Specifies the alternate edge style to be used if the main control point + * on an edge is being doubleclicked. Default is null. + */ +mxGraph.prototype.alternateEdgeStyle = null; + +/** + * Variable: backgroundImage + * + * Specifies the to be returned by . Default + * is null. + * + * Example: + * + * (code) + * var img = new mxImage('http://www.example.com/maps/examplemap.jpg', 1024, 768); + * graph.setBackgroundImage(img); + * graph.view.validate(); + * (end) + */ +mxGraph.prototype.backgroundImage = null; + +/** + * Variable: pageVisible + * + * Specifies if the background page should be visible. Default is false. + * Not yet implemented. + */ +mxGraph.prototype.pageVisible = false; + +/** + * Variable: pageBreaksVisible + * + * Specifies if a dashed line should be drawn between multiple pages. Default + * is false. If you change this value while a graph is being displayed then you + * should call to force an update of the display. + */ +mxGraph.prototype.pageBreaksVisible = false; + +/** + * Variable: pageBreakColor + * + * Specifies the color for page breaks. Default is 'gray'. + */ +mxGraph.prototype.pageBreakColor = 'gray'; + +/** + * Variable: pageBreakDashed + * + * Specifies the page breaks should be dashed. Default is true. + */ +mxGraph.prototype.pageBreakDashed = true; + +/** + * Variable: minPageBreakDist + * + * Specifies the minimum distance for page breaks to be visible. Default is + * 20 (in pixels). + */ +mxGraph.prototype.minPageBreakDist = 20; + +/** + * Variable: preferPageSize + * + * Specifies if the graph size should be rounded to the next page number in + * . This is only used if the graph container has scrollbars. + * Default is false. + */ +mxGraph.prototype.preferPageSize = false; + +/** + * Variable: pageFormat + * + * Specifies the page format for the background page. Default is + * . This is used as the default in + * and for painting the background page if is + * true and the pagebreaks if is true. + */ +mxGraph.prototype.pageFormat = mxConstants.PAGE_FORMAT_A4_PORTRAIT; + +/** + * Variable: pageScale + * + * Specifies the scale of the background page. Default is 1.5. + * Not yet implemented. + */ +mxGraph.prototype.pageScale = 1.5; + +/** + * Variable: enabled + * + * Specifies the return value for . Default is true. + */ +mxGraph.prototype.enabled = true; + +/** + * Variable: escapeEnabled + * + * Specifies if should invoke when the escape key + * is pressed. Default is true. + */ +mxGraph.prototype.escapeEnabled = true; + +/** + * Variable: invokesStopCellEditing + * + * If true, when editing is to be stopped by way of selection changing, + * data in diagram changing or other means stopCellEditing is invoked, and + * changes are saved. This is implemented in a focus handler in + * . Default is true. + */ +mxGraph.prototype.invokesStopCellEditing = true; + +/** + * Variable: enterStopsCellEditing + * + * If true, pressing the enter key without pressing control or shift will stop + * editing and accept the new value. This is used in to stop + * cell editing. Note: You can always use F2 and escape to stop editing. + * Default is false. + */ +mxGraph.prototype.enterStopsCellEditing = false; + +/** + * Variable: useScrollbarsForPanning + * + * Specifies if scrollbars should be used for panning in if + * any scrollbars are available. If scrollbars are enabled in CSS, but no + * scrollbars appear because the graph is smaller than the container size, + * then no panning occurs if this is true. Default is true. + */ +mxGraph.prototype.useScrollbarsForPanning = true; + +/** + * Variable: exportEnabled + * + * Specifies the return value for . Default is true. + */ +mxGraph.prototype.exportEnabled = true; + +/** + * Variable: importEnabled + * + * Specifies the return value for . Default is true. + */ +mxGraph.prototype.importEnabled = true; + +/** + * Variable: cellsLocked + * + * Specifies the return value for . Default is false. + */ +mxGraph.prototype.cellsLocked = false; + +/** + * Variable: cellsCloneable + * + * Specifies the return value for . Default is true. + */ +mxGraph.prototype.cellsCloneable = true; + +/** + * Variable: foldingEnabled + * + * Specifies if folding (collapse and expand via an image icon in the graph + * should be enabled). Default is true. + */ +mxGraph.prototype.foldingEnabled = true; + +/** + * Variable: cellsEditable + * + * Specifies the return value for . Default is true. + */ +mxGraph.prototype.cellsEditable = true; + +/** + * Variable: cellsDeletable + * + * Specifies the return value for . Default is true. + */ +mxGraph.prototype.cellsDeletable = true; + +/** + * Variable: cellsMovable + * + * Specifies the return value for . Default is true. + */ +mxGraph.prototype.cellsMovable = true; + +/** + * Variable: edgeLabelsMovable + * + * Specifies the return value for edges in . Default is true. + */ +mxGraph.prototype.edgeLabelsMovable = true; + +/** + * Variable: vertexLabelsMovable + * + * Specifies the return value for vertices in . Default is false. + */ +mxGraph.prototype.vertexLabelsMovable = false; + +/** + * Variable: dropEnabled + * + * Specifies the return value for . Default is false. + */ +mxGraph.prototype.dropEnabled = false; + +/** + * Variable: splitEnabled + * + * Specifies if dropping onto edges should be enabled. This is ignored if + * is false. If enabled, it will call to carry + * out the drop operation. Default is true. + */ +mxGraph.prototype.splitEnabled = true; + +/** + * Variable: cellsResizable + * + * Specifies the return value for . Default is true. + */ +mxGraph.prototype.cellsResizable = true; + +/** + * Variable: cellsBendable + * + * Specifies the return value for . Default is true. + */ +mxGraph.prototype.cellsBendable = true; + +/** + * Variable: cellsSelectable + * + * Specifies the return value for . Default is true. + */ +mxGraph.prototype.cellsSelectable = true; + +/** + * Variable: cellsDisconnectable + * + * Specifies the return value for . Default is true. + */ +mxGraph.prototype.cellsDisconnectable = true; + +/** + * Variable: autoSizeCells + * + * Specifies if the graph should automatically update the cell size after an + * edit. This is used in . Default is false. + */ +mxGraph.prototype.autoSizeCells = false; + +/** + * Variable: autoSizeCellsOnAdd + * + * Specifies if autoSize style should be applied when cells are added. Default is false. + */ +mxGraph.prototype.autoSizeCellsOnAdd = false; + +/** + * Variable: autoScroll + * + * Specifies if the graph should automatically scroll if the mouse goes near + * the container edge while dragging. This is only taken into account if the + * container has scrollbars. Default is true. + * + * If you need this to work without scrollbars then set to + * true. Please consult the for details. In general, with + * no scrollbars, the use of is recommended. + */ +mxGraph.prototype.autoScroll = true; + +/** + * Variable: ignoreScrollbars + * + * Specifies if the graph should automatically scroll regardless of the + * scrollbars. This will scroll the container using positive values for + * scroll positions (ie usually only rightwards and downwards). To avoid + * possible conflicts with panning, set to true. + */ +mxGraph.prototype.ignoreScrollbars = false; + +/** + * Variable: translateToScrollPosition + * + * Specifies if the graph should automatically convert the current scroll + * position to a translate in the graph view when a mouseUp event is received. + * This can be used to avoid conflicts when using and + * with no scrollbars in the container. + */ +mxGraph.prototype.translateToScrollPosition = false; + +/** + * Variable: timerAutoScroll + * + * Specifies if autoscrolling should be carried out via mxPanningManager even + * if the container has scrollbars. This disables and + * uses instead. If this is true then is + * disabled. It should only be used with a scroll buffer or when scollbars + * are visible and scrollable in all directions. Default is false. + */ +mxGraph.prototype.timerAutoScroll = false; + +/** + * Variable: allowAutoPanning + * + * Specifies if panning via should be allowed to implement autoscroll + * if no scrollbars are available in . To enable panning + * inside the container, near the edge, set to a + * positive value. Default is false. + */ +mxGraph.prototype.allowAutoPanning = false; + +/** + * Variable: autoExtend + * + * Specifies if the size of the graph should be automatically extended if the + * mouse goes near the container edge while dragging. This is only taken into + * account if the container has scrollbars. Default is true. See . + */ +mxGraph.prototype.autoExtend = true; + +/** + * Variable: maximumGraphBounds + * + * that specifies the area in which all cells in the diagram + * should be placed. Uses in . Use a width or height of + * 0 if you only want to give a upper, left corner. + */ +mxGraph.prototype.maximumGraphBounds = null; + +/** + * Variable: minimumGraphSize + * + * that specifies the minimum size of the graph. This is ignored + * if the graph container has no scrollbars. Default is null. + */ +mxGraph.prototype.minimumGraphSize = null; + +/** + * Variable: minimumContainerSize + * + * that specifies the minimum size of the if + * is true. + */ +mxGraph.prototype.minimumContainerSize = null; + +/** + * Variable: maximumContainerSize + * + * that specifies the maximum size of the container if + * is true. + */ +mxGraph.prototype.maximumContainerSize = null; + +/** + * Variable: resizeContainer + * + * Specifies if the container should be resized to the graph size when + * the graph size has changed. Default is false. + */ +mxGraph.prototype.resizeContainer = false; + +/** + * Variable: border + * + * Border to be added to the bottom and right side when the container is + * being resized after the graph has been changed. Default is 0. + */ +mxGraph.prototype.border = 0; + +/** + * Variable: keepEdgesInForeground + * + * Specifies if edges should appear in the foreground regardless of their order + * in the model. If and are + * both true then the normal order is applied. Default is false. + */ +mxGraph.prototype.keepEdgesInForeground = false; + +/** + * Variable: keepEdgesInBackground + * + * Specifies if edges should appear in the background regardless of their order + * in the model. If and are + * both true then the normal order is applied. Default is false. + */ +mxGraph.prototype.keepEdgesInBackground = false; + +/** + * Variable: allowNegativeCoordinates + * + * Specifies if negative coordinates for vertices are allowed. Default is true. + */ +mxGraph.prototype.allowNegativeCoordinates = true; + +/** + * Variable: constrainChildren + * + * Specifies if a child should be constrained inside the parent bounds after a + * move or resize of the child. Default is true. + */ +mxGraph.prototype.constrainChildren = true; + +/** + * Variable: constrainRelativeChildren + * + * Specifies if child cells with relative geometries should be constrained + * inside the parent bounds, if is true, and/or the + * . Default is false. + */ +mxGraph.prototype.constrainRelativeChildren = false; + +/** + * Variable: extendParents + * + * Specifies if a parent should contain the child bounds after a resize of + * the child. Default is true. This has precedence over . + */ +mxGraph.prototype.extendParents = true; + +/** + * Variable: extendParentsOnAdd + * + * Specifies if parents should be extended according to the + * switch if cells are added. Default is true. + */ +mxGraph.prototype.extendParentsOnAdd = true; + +/** + * Variable: extendParentsOnAdd + * + * Specifies if parents should be extended according to the + * switch if cells are added. Default is false for backwards compatiblity. + */ +mxGraph.prototype.extendParentsOnMove = false; + +/** + * Variable: recursiveResize + * + * Specifies the return value for . Default is + * false for backwards compatiblity. + */ +mxGraph.prototype.recursiveResize = false; + +/** + * Variable: collapseToPreferredSize + * + * Specifies if the cell size should be changed to the preferred size when + * a cell is first collapsed. Default is true. + */ +mxGraph.prototype.collapseToPreferredSize = true; + +/** + * Variable: zoomFactor + * + * Specifies the factor used for and . Default is 1.2 + * (120%). + */ +mxGraph.prototype.zoomFactor = 1.2; + +/** + * Variable: keepSelectionVisibleOnZoom + * + * Specifies if the viewport should automatically contain the selection cells + * after a zoom operation. Default is false. + */ +mxGraph.prototype.keepSelectionVisibleOnZoom = false; + +/** + * Variable: centerZoom + * + * Specifies if the zoom operations should go into the center of the actual + * diagram rather than going from top, left. Default is true. + */ +mxGraph.prototype.centerZoom = true; + +/** + * Variable: resetViewOnRootChange + * + * Specifies if the scale and translate should be reset if the root changes in + * the model. Default is true. + */ +mxGraph.prototype.resetViewOnRootChange = true; + +/** + * Variable: resetEdgesOnResize + * + * Specifies if edge control points should be reset after the resize of a + * connected cell. Default is false. + */ +mxGraph.prototype.resetEdgesOnResize = false; + +/** + * Variable: resetEdgesOnMove + * + * Specifies if edge control points should be reset after the move of a + * connected cell. Default is false. + */ +mxGraph.prototype.resetEdgesOnMove = false; + +/** + * Variable: resetEdgesOnConnect + * + * Specifies if edge control points should be reset after the the edge has been + * reconnected. Default is true. + */ +mxGraph.prototype.resetEdgesOnConnect = true; + +/** + * Variable: allowLoops + * + * Specifies if loops (aka self-references) are allowed. Default is false. + */ +mxGraph.prototype.allowLoops = false; + +/** + * Variable: defaultLoopStyle + * + * to be used for loops. This is a fallback for loops if the + * is undefined. Default is . + */ +mxGraph.prototype.defaultLoopStyle = mxEdgeStyle.Loop; + +/** + * Variable: multigraph + * + * Specifies if multiple edges in the same direction between the same pair of + * vertices are allowed. Default is true. + */ +mxGraph.prototype.multigraph = true; + +/** + * Variable: connectableEdges + * + * Specifies if edges are connectable. Default is false. This overrides the + * connectable field in edges. + */ +mxGraph.prototype.connectableEdges = false; + +/** + * Variable: allowDanglingEdges + * + * Specifies if edges with disconnected terminals are allowed in the graph. + * Default is true. + */ +mxGraph.prototype.allowDanglingEdges = true; + +/** + * Variable: cloneInvalidEdges + * + * Specifies if edges that are cloned should be validated and only inserted + * if they are valid. Default is true. + */ +mxGraph.prototype.cloneInvalidEdges = false; + +/** + * Variable: disconnectOnMove + * + * Specifies if edges should be disconnected from their terminals when they + * are moved. Default is true. + */ +mxGraph.prototype.disconnectOnMove = true; + +/** + * Variable: labelsVisible + * + * Specifies if labels should be visible. This is used in . Default + * is true. + */ +mxGraph.prototype.labelsVisible = true; + +/** + * Variable: htmlLabels + * + * Specifies the return value for . Default is false. + */ +mxGraph.prototype.htmlLabels = false; + +/** + * Variable: swimlaneSelectionEnabled + * + * Specifies if swimlanes should be selectable via the content if the + * mouse is released. Default is true. + */ +mxGraph.prototype.swimlaneSelectionEnabled = true; + +/** + * Variable: swimlaneNesting + * + * Specifies if nesting of swimlanes is allowed. Default is true. + */ +mxGraph.prototype.swimlaneNesting = true; + +/** + * Variable: swimlaneIndicatorColorAttribute + * + * The attribute used to find the color for the indicator if the indicator + * color is set to 'swimlane'. Default is . + */ +mxGraph.prototype.swimlaneIndicatorColorAttribute = mxConstants.STYLE_FILLCOLOR; + +/** + * Variable: imageBundles + * + * Holds the list of image bundles. + */ +mxGraph.prototype.imageBundles = null; + +/** + * Variable: minFitScale + * + * Specifies the minimum scale to be applied in . Default is 0.1. Set this + * to null to allow any value. + */ +mxGraph.prototype.minFitScale = 0.1; + +/** + * Variable: maxFitScale + * + * Specifies the maximum scale to be applied in . Default is 8. Set this + * to null to allow any value. + */ +mxGraph.prototype.maxFitScale = 8; + +/** + * Variable: panDx + * + * Current horizontal panning value. Default is 0. + */ +mxGraph.prototype.panDx = 0; + +/** + * Variable: panDy + * + * Current vertical panning value. Default is 0. + */ +mxGraph.prototype.panDy = 0; + +/** + * Variable: collapsedImage + * + * Specifies the to indicate a collapsed state. + * Default value is mxClient.imageBasePath + '/collapsed.gif' + */ +mxGraph.prototype.collapsedImage = new mxImage(mxClient.imageBasePath + '/collapsed.gif', 9, 9); + +/** + * Variable: expandedImage + * + * Specifies the to indicate a expanded state. + * Default value is mxClient.imageBasePath + '/expanded.gif' + */ +mxGraph.prototype.expandedImage = new mxImage(mxClient.imageBasePath + '/expanded.gif', 9, 9); + +/** + * Variable: warningImage + * + * Specifies the for the image to be used to display a warning + * overlay. See . Default value is mxClient.imageBasePath + + * '/warning'. The extension for the image depends on the platform. It is + * '.png' on the Mac and '.gif' on all other platforms. + */ +mxGraph.prototype.warningImage = new mxImage(mxClient.imageBasePath + '/warning'+ + ((mxClient.IS_MAC) ? '.png' : '.gif'), 16, 16); + +/** + * Variable: alreadyConnectedResource + * + * Specifies the resource key for the error message to be displayed in + * non-multigraphs when two vertices are already connected. If the resource + * for this key does not exist then the value is used as the error message. + * Default is 'alreadyConnected'. + */ +mxGraph.prototype.alreadyConnectedResource = (mxClient.language != 'none') ? 'alreadyConnected' : ''; + +/** + * Variable: containsValidationErrorsResource + * + * Specifies the resource key for the warning message to be displayed when + * a collapsed cell contains validation errors. If the resource for this + * key does not exist then the value is used as the warning message. + * Default is 'containsValidationErrors'. + */ +mxGraph.prototype.containsValidationErrorsResource = (mxClient.language != 'none') ? 'containsValidationErrors' : ''; + +/** + * Variable: collapseExpandResource + * + * Specifies the resource key for the tooltip on the collapse/expand icon. + * If the resource for this key does not exist then the value is used as + * the tooltip. Default is 'collapse-expand'. + */ +mxGraph.prototype.collapseExpandResource = (mxClient.language != 'none') ? 'collapse-expand' : ''; + +/** + * Function: init + * + * Initializes the and creates the respective datastructures. + * + * Parameters: + * + * container - DOM node that will contain the graph display. + */ +mxGraph.prototype.init = function(container) +{ + this.container = container; + + // Initializes the in-place editor + this.cellEditor = this.createCellEditor(); + + // Initializes the container using the view + this.view.init(); + + // Updates the size of the container for the current graph + this.sizeDidChange(); + + // Hides tooltips and resets tooltip timer if mouse leaves container + mxEvent.addListener(container, 'mouseleave', mxUtils.bind(this, function() + { + if (this.tooltipHandler != null) + { + this.tooltipHandler.hide(); + } + })); + + // Automatic deallocation of memory + if (mxClient.IS_IE) + { + mxEvent.addListener(window, 'unload', mxUtils.bind(this, function() + { + this.destroy(); + })); + + // Disable shift-click for text + mxEvent.addListener(container, 'selectstart', + mxUtils.bind(this, function(evt) + { + return this.isEditing() || (!this.isMouseDown && !mxEvent.isShiftDown(evt)); + }) + ); + } + + // Workaround for missing last shape and connect preview in IE8 standards + // mode if no initial graph displayed or no label for shape defined + if (document.documentMode == 8) + { + container.insertAdjacentHTML('beforeend', ''); + } +}; + +/** + * Function: createHandlers + * + * Creates the tooltip-, panning-, connection- and graph-handler (in this + * order). This is called in the constructor before is called. + */ +mxGraph.prototype.createHandlers = function() +{ + this.tooltipHandler = this.createTooltipHandler(); + this.tooltipHandler.setEnabled(false); + this.selectionCellsHandler = this.createSelectionCellsHandler(); + this.connectionHandler = this.createConnectionHandler(); + this.connectionHandler.setEnabled(false); + this.graphHandler = this.createGraphHandler(); + this.panningHandler = this.createPanningHandler(); + this.panningHandler.panningEnabled = false; + this.popupMenuHandler = this.createPopupMenuHandler(); +}; + +/** + * Function: createTooltipHandler + * + * Creates and returns a new to be used in this graph. + */ +mxGraph.prototype.createTooltipHandler = function() +{ + return new mxTooltipHandler(this); +}; + +/** + * Function: createSelectionCellsHandler + * + * Creates and returns a new to be used in this graph. + */ +mxGraph.prototype.createSelectionCellsHandler = function() +{ + return new mxSelectionCellsHandler(this); +}; + +/** + * Function: createConnectionHandler + * + * Creates and returns a new to be used in this graph. + */ +mxGraph.prototype.createConnectionHandler = function() +{ + return new mxConnectionHandler(this); +}; + +/** + * Function: createGraphHandler + * + * Creates and returns a new to be used in this graph. + */ +mxGraph.prototype.createGraphHandler = function() +{ + return new mxGraphHandler(this); +}; + +/** + * Function: createPanningHandler + * + * Creates and returns a new to be used in this graph. + */ +mxGraph.prototype.createPanningHandler = function() +{ + return new mxPanningHandler(this); +}; + +/** + * Function: createPopupMenuHandler + * + * Creates and returns a new to be used in this graph. + */ +mxGraph.prototype.createPopupMenuHandler = function() +{ + return new mxPopupMenuHandler(this); +}; + +/** + * Function: createSelectionModel + * + * Creates a new to be used in this graph. + */ +mxGraph.prototype.createSelectionModel = function() +{ + return new mxGraphSelectionModel(this); +}; + +/** + * Function: createStylesheet + * + * Creates a new to be used in this graph. + */ +mxGraph.prototype.createStylesheet = function() +{ + return new mxStylesheet(); +}; + +/** + * Function: createGraphView + * + * Creates a new to be used in this graph. + */ +mxGraph.prototype.createGraphView = function() +{ + return new mxGraphView(this); +}; + +/** + * Function: createCellRenderer + * + * Creates a new to be used in this graph. + */ +mxGraph.prototype.createCellRenderer = function() +{ + return new mxCellRenderer(); +}; + +/** + * Function: createCellEditor + * + * Creates a new to be used in this graph. + */ +mxGraph.prototype.createCellEditor = function() +{ + return new mxCellEditor(this); +}; + +/** + * Function: getModel + * + * Returns the that contains the cells. + */ +mxGraph.prototype.getModel = function() +{ + return this.model; +}; + +/** + * Function: getView + * + * Returns the that contains the . + */ +mxGraph.prototype.getView = function() +{ + return this.view; +}; + +/** + * Function: getStylesheet + * + * Returns the that defines the style. + */ +mxGraph.prototype.getStylesheet = function() +{ + return this.stylesheet; +}; + +/** + * Function: setStylesheet + * + * Sets the that defines the style. + */ +mxGraph.prototype.setStylesheet = function(stylesheet) +{ + this.stylesheet = stylesheet; +}; + +/** + * Function: getSelectionModel + * + * Returns the that contains the selection. + */ +mxGraph.prototype.getSelectionModel = function() +{ + return this.selectionModel; +}; + +/** + * Function: setSelectionModel + * + * Sets the that contains the selection. + */ +mxGraph.prototype.setSelectionModel = function(selectionModel) +{ + this.selectionModel = selectionModel; +}; + +/** + * Function: getSelectionCellsForChanges + * + * Returns the cells to be selected for the given array of changes. + */ +mxGraph.prototype.getSelectionCellsForChanges = function(changes) +{ + var cells = []; + + for (var i = 0; i < changes.length; i++) + { + var change = changes[i]; + + if (change.constructor != mxRootChange) + { + var cell = null; + + if (change instanceof mxChildChange && change.previous == null) + { + cell = change.child; + } + else if (change.cell != null && change.cell instanceof mxCell) + { + cell = change.cell; + } + + if (cell != null && mxUtils.indexOf(cells, cell) < 0) + { + cells.push(cell); + } + } + } + + return this.getModel().getTopmostCells(cells); +}; + +/** + * Function: graphModelChanged + * + * Called when the graph model changes. Invokes on each + * item of the given array to update the view accordingly. + * + * Parameters: + * + * changes - Array that contains the individual changes. + */ +mxGraph.prototype.graphModelChanged = function(changes) +{ + for (var i = 0; i < changes.length; i++) + { + this.processChange(changes[i]); + } + + this.removeSelectionCells(this.getRemovedCellsForChanges(changes)); + + this.view.validate(); + this.sizeDidChange(); +}; + +/** + * Function: getRemovedCellsForChanges + * + * Returns the cells that have been removed from the model. + */ +mxGraph.prototype.getRemovedCellsForChanges = function(changes) +{ + var result = []; + + for (var i = 0; i < changes.length; i++) + { + var change = changes[i]; + + // Resets the view settings, removes all cells and clears + // the selection if the root changes. + if (change instanceof mxRootChange) + { + break; + } + else if (change instanceof mxChildChange) + { + if (change.previous != null && change.parent == null) + { + result = result.concat(this.model.getDescendants(change.child)); + } + } + else if (change instanceof mxVisibleChange) + { + result = result.concat(this.model.getDescendants(change.cell)); + } + } + + return result; +}; + +/** + * Function: processChange + * + * Processes the given change and invalidates the respective cached data + * in . This fires a event if the root has changed in the + * model. + * + * Parameters: + * + * change - Object that represents the change on the model. + */ +mxGraph.prototype.processChange = function(change) +{ + // Resets the view settings, removes all cells and clears + // the selection if the root changes. + if (change instanceof mxRootChange) + { + this.clearSelection(); + this.removeStateForCell(change.previous); + + if (this.resetViewOnRootChange) + { + this.view.scale = 1; + this.view.translate.x = 0; + this.view.translate.y = 0; + } + + this.fireEvent(new mxEventObject(mxEvent.ROOT)); + } + + // Adds or removes a child to the view by online invaliding + // the minimal required portions of the cache, namely, the + // old and new parent and the child. + else if (change instanceof mxChildChange) + { + var newParent = this.model.getParent(change.child); + this.view.invalidate(change.child, true, true); + + if (newParent == null || this.isCellCollapsed(newParent)) + { + this.view.invalidate(change.child, true, true); + this.removeStateForCell(change.child); + + // Handles special case of current root of view being removed + if (this.view.currentRoot == change.child) + { + this.home(); + } + } + + if (newParent != change.previous) + { + // Refreshes the collapse/expand icons on the parents + if (newParent != null) + { + this.view.invalidate(newParent, false, false); + } + + if (change.previous != null) + { + this.view.invalidate(change.previous, false, false); + } + } + } + + // Handles two special cases where the shape does not need to be + // recreated from scratch, it only needs to be invalidated. + else if (change instanceof mxTerminalChange || change instanceof mxGeometryChange) + { + // Checks if the geometry has changed to avoid unnessecary revalidation + if (change instanceof mxTerminalChange || ((change.previous == null && change.geometry != null) || + (change.previous != null && !change.previous.equals(change.geometry)))) + { + this.view.invalidate(change.cell); + } + } + + // Handles two special cases where only the shape, but no + // descendants need to be recreated + else if (change instanceof mxValueChange) + { + this.view.invalidate(change.cell, false, false); + } + + // Requires a new mxShape in JavaScript + else if (change instanceof mxStyleChange) + { + this.view.invalidate(change.cell, true, true); + var state = this.view.getState(change.cell); + + if (state != null) + { + state.style = null; + } + } + + // Removes the state from the cache by default + else if (change.cell != null && change.cell instanceof mxCell) + { + this.removeStateForCell(change.cell); + } +}; + +/** + * Function: removeStateForCell + * + * Removes all cached information for the given cell and its descendants. + * This is called when a cell was removed from the model. + * + * Paramters: + * + * cell - that was removed from the model. + */ +mxGraph.prototype.removeStateForCell = function(cell) +{ + var childCount = this.model.getChildCount(cell); + + for (var i = 0; i < childCount; i++) + { + this.removeStateForCell(this.model.getChildAt(cell, i)); + } + + this.view.invalidate(cell, false, true); + this.view.removeState(cell); +}; + +/** + * Group: Overlays + */ + +/** + * Function: addCellOverlay + * + * Adds an for the specified cell. This method fires an + * event and returns the new . + * + * Parameters: + * + * cell - to add the overlay for. + * overlay - to be added for the cell. + */ +mxGraph.prototype.addCellOverlay = function(cell, overlay) +{ + if (cell.overlays == null) + { + cell.overlays = []; + } + + cell.overlays.push(overlay); + + var state = this.view.getState(cell); + + // Immediately updates the cell display if the state exists + if (state != null) + { + this.cellRenderer.redraw(state); + } + + this.fireEvent(new mxEventObject(mxEvent.ADD_OVERLAY, + 'cell', cell, 'overlay', overlay)); + + return overlay; +}; + +/** + * Function: getCellOverlays + * + * Returns the array of for the given cell or null, if + * no overlays are defined. + * + * Parameters: + * + * cell - whose overlays should be returned. + */ +mxGraph.prototype.getCellOverlays = function(cell) +{ + return cell.overlays; +}; + +/** + * Function: removeCellOverlay + * + * Removes and returns the given from the given cell. This + * method fires a event. If no overlay is given, then all + * overlays are removed using . + * + * Parameters: + * + * cell - whose overlay should be removed. + * overlay - Optional to be removed. + */ +mxGraph.prototype.removeCellOverlay = function(cell, overlay) +{ + if (overlay == null) + { + this.removeCellOverlays(cell); + } + else + { + var index = mxUtils.indexOf(cell.overlays, overlay); + + if (index >= 0) + { + cell.overlays.splice(index, 1); + + if (cell.overlays.length == 0) + { + cell.overlays = null; + } + + // Immediately updates the cell display if the state exists + var state = this.view.getState(cell); + + if (state != null) + { + this.cellRenderer.redraw(state); + } + + this.fireEvent(new mxEventObject(mxEvent.REMOVE_OVERLAY, + 'cell', cell, 'overlay', overlay)); + } + else + { + overlay = null; + } + } + + return overlay; +}; + +/** + * Function: removeCellOverlays + * + * Removes all from the given cell. This method + * fires a event for each and returns + * the array of that was removed from the cell. + * + * Parameters: + * + * cell - whose overlays should be removed + */ +mxGraph.prototype.removeCellOverlays = function(cell) +{ + var overlays = cell.overlays; + + if (overlays != null) + { + cell.overlays = null; + + // Immediately updates the cell display if the state exists + var state = this.view.getState(cell); + + if (state != null) + { + this.cellRenderer.redraw(state); + } + + for (var i = 0; i < overlays.length; i++) + { + this.fireEvent(new mxEventObject(mxEvent.REMOVE_OVERLAY, + 'cell', cell, 'overlay', overlays[i])); + } + } + + return overlays; +}; + +/** + * Function: clearCellOverlays + * + * Removes all in the graph for the given cell and all its + * descendants. If no cell is specified then all overlays are removed from + * the graph. This implementation uses to remove the + * overlays from the individual cells. + * + * Parameters: + * + * cell - Optional that represents the root of the subtree to + * remove the overlays from. Default is the root in the model. + */ +mxGraph.prototype.clearCellOverlays = function(cell) +{ + cell = (cell != null) ? cell : this.model.getRoot(); + this.removeCellOverlays(cell); + + // Recursively removes all overlays from the children + var childCount = this.model.getChildCount(cell); + + for (var i = 0; i < childCount; i++) + { + var child = this.model.getChildAt(cell, i); + this.clearCellOverlays(child); // recurse + } +}; + +/** + * Function: setCellWarning + * + * Creates an overlay for the given cell using the warning and image or + * and returns the new . The warning is + * displayed as a tooltip in a red font and may contain HTML markup. If + * the warning is null or a zero length string, then all overlays are + * removed from the cell. + * + * Example: + * + * (code) + * graph.setCellWarning(cell, 'Warning:: Hello, World!'); + * (end) + * + * Parameters: + * + * cell - whose warning should be set. + * warning - String that represents the warning to be displayed. + * img - Optional to be used for the overlay. Default is + * . + * isSelect - Optional boolean indicating if a click on the overlay + * should select the corresponding cell. Default is false. + */ +mxGraph.prototype.setCellWarning = function(cell, warning, img, isSelect) +{ + if (warning != null && warning.length > 0) + { + img = (img != null) ? img : this.warningImage; + + // Creates the overlay with the image and warning + var overlay = new mxCellOverlay(img, + ''+warning+''); + + // Adds a handler for single mouseclicks to select the cell + if (isSelect) + { + overlay.addListener(mxEvent.CLICK, + mxUtils.bind(this, function(sender, evt) + { + if (this.isEnabled()) + { + this.setSelectionCell(cell); + } + }) + ); + } + + // Sets and returns the overlay in the graph + return this.addCellOverlay(cell, overlay); + } + else + { + this.removeCellOverlays(cell); + } + + return null; +}; + +/** + * Group: In-place editing + */ + +/** + * Function: startEditing + * + * Calls using the given cell or the first selection + * cell. + * + * Parameters: + * + * evt - Optional mouse event that triggered the editing. + */ +mxGraph.prototype.startEditing = function(evt) +{ + this.startEditingAtCell(null, evt); +}; + +/** + * Function: startEditingAtCell + * + * Fires a event and invokes + * on . After editing was started, a event is + * fired. + * + * Parameters: + * + * cell - to start the in-place editor for. + * evt - Optional mouse event that triggered the editing. + */ +mxGraph.prototype.startEditingAtCell = function(cell, evt) +{ + if (evt == null || !mxEvent.isMultiTouchEvent(evt)) + { + if (cell == null) + { + cell = this.getSelectionCell(); + + if (cell != null && !this.isCellEditable(cell)) + { + cell = null; + } + } + + if (cell != null) + { + this.fireEvent(new mxEventObject(mxEvent.START_EDITING, + 'cell', cell, 'event', evt)); + this.cellEditor.startEditing(cell, evt); + this.fireEvent(new mxEventObject(mxEvent.EDITING_STARTED, + 'cell', cell, 'event', evt)); + } + } +}; + +/** + * Function: getEditingValue + * + * Returns the initial value for in-place editing. This implementation + * returns for the given cell. If this function is + * overridden, then should take care + * of correctly storing the actual new value inside the user object. + * + * Parameters: + * + * cell - for which the initial editing value should be returned. + * evt - Optional mouse event that triggered the editor. + */ +mxGraph.prototype.getEditingValue = function(cell, evt) +{ + return this.convertValueToString(cell); +}; + +/** + * Function: stopEditing + * + * Stops the current editing and fires a event. + * + * Parameters: + * + * cancel - Boolean that specifies if the current editing value + * should be stored. + */ +mxGraph.prototype.stopEditing = function(cancel) +{ + this.cellEditor.stopEditing(cancel); + this.fireEvent(new mxEventObject(mxEvent.EDITING_STOPPED, 'cancel', cancel)); +}; + +/** + * Function: labelChanged + * + * Sets the label of the specified cell to the given value using + * and fires while the + * transaction is in progress. Returns the cell whose label was changed. + * + * Parameters: + * + * cell - whose label should be changed. + * value - New label to be assigned. + * evt - Optional event that triggered the change. + */ +mxGraph.prototype.labelChanged = function(cell, value, evt) +{ + this.model.beginUpdate(); + try + { + var old = cell.value; + this.cellLabelChanged(cell, value, this.isAutoSizeCell(cell)); + this.fireEvent(new mxEventObject(mxEvent.LABEL_CHANGED, + 'cell', cell, 'value', value, 'old', old, 'event', evt)); + } + finally + { + this.model.endUpdate(); + } + + return cell; +}; + +/** + * Function: cellLabelChanged + * + * Sets the new label for a cell. If autoSize is true then + * will be called. + * + * In the following example, the function is extended to map changes to + * attributes in an XML node, as shown in . + * Alternatively, the handling of this can be implemented as shown in + * without the need to clone the + * user object. + * + * (code) + * var graphCellLabelChanged = graph.cellLabelChanged; + * graph.cellLabelChanged = function(cell, newValue, autoSize) + * { + * // Cloned for correct undo/redo + * var elt = cell.value.cloneNode(true); + * elt.setAttribute('label', newValue); + * + * newValue = elt; + * graphCellLabelChanged.apply(this, arguments); + * }; + * (end) + * + * Parameters: + * + * cell - whose label should be changed. + * value - New label to be assigned. + * autoSize - Boolean that specifies if should be called. + */ +mxGraph.prototype.cellLabelChanged = function(cell, value, autoSize) +{ + this.model.beginUpdate(); + try + { + this.model.setValue(cell, value); + + if (autoSize) + { + this.cellSizeUpdated(cell, false); + } + } + finally + { + this.model.endUpdate(); + } +}; + +/** + * Group: Event processing + */ + +/** + * Function: escape + * + * Processes an escape keystroke. + * + * Parameters: + * + * evt - Mouseevent that represents the keystroke. + */ +mxGraph.prototype.escape = function(evt) +{ + this.fireEvent(new mxEventObject(mxEvent.ESCAPE, 'event', evt)); +}; + +/** + * Function: click + * + * Processes a singleclick on an optional cell and fires a event. + * The click event is fired initially. If the graph is enabled and the + * event has not been consumed, then the cell is selected using + * or the selection is cleared using + * . The events consumed state is set to true if the + * corresponding has been consumed. + * + * To handle a click event, use the following code. + * + * (code) + * graph.addListener(mxEvent.CLICK, function(sender, evt) + * { + * var e = evt.getProperty('event'); // mouse event + * var cell = evt.getProperty('cell'); // cell may be null + * + * if (cell != null) + * { + * // Do something useful with cell and consume the event + * evt.consume(); + * } + * }); + * (end) + * + * Parameters: + * + * me - that represents the single click. + */ +mxGraph.prototype.click = function(me) +{ + var evt = me.getEvent(); + var cell = me.getCell(); + var mxe = new mxEventObject(mxEvent.CLICK, 'event', evt, 'cell', cell); + + if (me.isConsumed()) + { + mxe.consume(); + } + + this.fireEvent(mxe); + + // Handles the event if it has not been consumed + if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed()) + { + if (cell != null) + { + this.selectCellForEvent(cell, evt); + } + else + { + var swimlane = null; + + if (this.isSwimlaneSelectionEnabled()) + { + // Gets the swimlane at the location (includes + // content area of swimlanes) + swimlane = this.getSwimlaneAt(me.getGraphX(), me.getGraphY()); + } + + // Selects the swimlane and consumes the event + if (swimlane != null) + { + this.selectCellForEvent(swimlane, evt); + } + + // Ignores the event if the control key is pressed + else if (!this.isToggleEvent(evt)) + { + this.clearSelection(); + } + } + } +}; + +/** + * Function: dblClick + * + * Processes a doubleclick on an optional cell and fires a + * event. The event is fired initially. If the graph is enabled and the + * event has not been consumed, then is called with the given + * cell. The event is ignored if no cell was specified. + * + * Example for overriding this method. + * + * (code) + * graph.dblClick = function(evt, cell) + * { + * var mxe = new mxEventObject(mxEvent.DOUBLE_CLICK, 'event', evt, 'cell', cell); + * this.fireEvent(mxe); + * + * if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed()) + * { + * mxUtils.alert('Hello, World!'); + * mxe.consume(); + * } + * } + * (end) + * + * Example listener for this event. + * + * (code) + * graph.addListener(mxEvent.DOUBLE_CLICK, function(sender, evt) + * { + * var cell = evt.getProperty('cell'); + * // do something with the cell and consume the + * // event to prevent in-place editing from start + * }); + * (end) + * + * Parameters: + * + * evt - Mouseevent that represents the doubleclick. + * cell - Optional under the mousepointer. + */ +mxGraph.prototype.dblClick = function(evt, cell) +{ + var mxe = new mxEventObject(mxEvent.DOUBLE_CLICK, 'event', evt, 'cell', cell); + this.fireEvent(mxe); + + // Handles the event if it has not been consumed + if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed() && + cell != null && this.isCellEditable(cell) && !this.isEditing(cell)) + { + this.startEditingAtCell(cell, evt); + mxEvent.consume(evt); + } +}; + +/** + * Function: tapAndHold + * + * Handles the by highlighting the . + * + * Parameters: + * + * me - that represents the touch event. + * state - Optional that is associated with the event. + */ +mxGraph.prototype.tapAndHold = function(me) +{ + var evt = me.getEvent(); + var mxe = new mxEventObject(mxEvent.TAP_AND_HOLD, 'event', evt, 'cell', me.getCell()); + + // LATER: Check if event should be consumed if me is consumed + this.fireEvent(mxe); + + if (mxe.isConsumed()) + { + // Resets the state of the panning handler + this.panningHandler.panningTrigger = false; + } + + // Handles the event if it has not been consumed + if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed() && this.connectionHandler.isEnabled()) + { + var state = this.view.getState(this.connectionHandler.marker.getCell(me)); + + if (state != null) + { + this.connectionHandler.marker.currentColor = this.connectionHandler.marker.validColor; + this.connectionHandler.marker.markedState = state; + this.connectionHandler.marker.mark(); + + this.connectionHandler.first = new mxPoint(me.getGraphX(), me.getGraphY()); + this.connectionHandler.edgeState = this.connectionHandler.createEdgeState(me); + this.connectionHandler.previous = state; + this.connectionHandler.fireEvent(new mxEventObject(mxEvent.START, 'state', this.connectionHandler.previous)); + } + } +}; + +/** + * Function: scrollPointToVisible + * + * Scrolls the graph to the given point, extending the graph container if + * specified. + */ +mxGraph.prototype.scrollPointToVisible = function(x, y, extend, border) +{ + if (!this.timerAutoScroll && (this.ignoreScrollbars || mxUtils.hasScrollbars(this.container))) + { + var c = this.container; + border = (border != null) ? border : 20; + + if (x >= c.scrollLeft && y >= c.scrollTop && x <= c.scrollLeft + c.clientWidth && + y <= c.scrollTop + c.clientHeight) + { + var dx = c.scrollLeft + c.clientWidth - x; + + if (dx < border) + { + var old = c.scrollLeft; + c.scrollLeft += border - dx; + + // Automatically extends the canvas size to the bottom, right + // if the event is outside of the canvas and the edge of the + // canvas has been reached. Notes: Needs fix for IE. + if (extend && old == c.scrollLeft) + { + if (this.dialect == mxConstants.DIALECT_SVG) + { + var root = this.view.getDrawPane().ownerSVGElement; + var width = this.container.scrollWidth + border - dx; + + // Updates the clipping region. This is an expensive + // operation that should not be executed too often. + root.style.width = width + 'px'; + } + else + { + var width = Math.max(c.clientWidth, c.scrollWidth) + border - dx; + var canvas = this.view.getCanvas(); + canvas.style.width = width + 'px'; + } + + c.scrollLeft += border - dx; + } + } + else + { + dx = x - c.scrollLeft; + + if (dx < border) + { + c.scrollLeft -= border - dx; + } + } + + var dy = c.scrollTop + c.clientHeight - y; + + if (dy < border) + { + var old = c.scrollTop; + c.scrollTop += border - dy; + + if (old == c.scrollTop && extend) + { + if (this.dialect == mxConstants.DIALECT_SVG) + { + var root = this.view.getDrawPane().ownerSVGElement; + var height = this.container.scrollHeight + border - dy; + + // Updates the clipping region. This is an expensive + // operation that should not be executed too often. + root.style.height = height + 'px'; + } + else + { + var height = Math.max(c.clientHeight, c.scrollHeight) + border - dy; + var canvas = this.view.getCanvas(); + canvas.style.height = height + 'px'; + } + + c.scrollTop += border - dy; + } + } + else + { + dy = y - c.scrollTop; + + if (dy < border) + { + c.scrollTop -= border - dy; + } + } + } + } + else if (this.allowAutoPanning && !this.panningHandler.isActive()) + { + if (this.panningManager == null) + { + this.panningManager = this.createPanningManager(); + } + + this.panningManager.panTo(x + this.panDx, y + this.panDy); + } +}; + + +/** + * Function: createPanningManager + * + * Creates and returns an . + */ +mxGraph.prototype.createPanningManager = function() +{ + return new mxPanningManager(this); +}; + +/** + * Function: getBorderSizes + * + * Returns the size of the border and padding on all four sides of the + * container. The left, top, right and bottom borders are stored in the x, y, + * width and height of the returned , respectively. + */ +mxGraph.prototype.getBorderSizes = function() +{ + var css = mxUtils.getCurrentStyle(this.container); + + return new mxRectangle(mxUtils.parseCssNumber(css.paddingLeft) + + ((css.borderLeftStyle != 'none') ? mxUtils.parseCssNumber(css.borderLeftWidth) : 0), + mxUtils.parseCssNumber(css.paddingTop) + + ((css.borderTopStyle != 'none') ? mxUtils.parseCssNumber(css.borderTopWidth) : 0), + mxUtils.parseCssNumber(css.paddingRight) + + ((css.borderRightStyle != 'none') ? mxUtils.parseCssNumber(css.borderRightWidth) : 0), + mxUtils.parseCssNumber(css.paddingBottom) + + ((css.borderBottomStyle != 'none') ? mxUtils.parseCssNumber(css.borderBottomWidth) : 0)); +}; + +/** + * Function: getPreferredPageSize + * + * Returns the preferred size of the background page if is true. + */ +mxGraph.prototype.getPreferredPageSize = function(bounds, width, height) +{ + var scale = this.view.scale; + var tr = this.view.translate; + var fmt = this.pageFormat; + var ps = scale * this.pageScale; + var page = new mxRectangle(0, 0, fmt.width * ps, fmt.height * ps); + + var hCount = (this.pageBreaksVisible) ? Math.ceil(width / page.width) : 1; + var vCount = (this.pageBreaksVisible) ? Math.ceil(height / page.height) : 1; + + return new mxRectangle(0, 0, hCount * page.width + 2 + tr.x / scale, vCount * page.height + 2 + tr.y / scale); +}; + +/** + * Function: fit + * + * Scales the graph such that the complete diagram fits into and + * returns the current scale in the view. To fit an initial graph prior to + * rendering, set to false prior to changing the model + * and execute the following after changing the model. + * + * (code) + * graph.fit(); + * graph.view.rendering = true; + * graph.refresh(); + * (end) + * + * To fit and center the graph, the following code can be used. + * + * (code) + * var margin = 2; + * var max = 3; + * + * var bounds = graph.getGraphBounds(); + * var cw = graph.container.clientWidth - margin; + * var ch = graph.container.clientHeight - margin; + * var w = bounds.width / graph.view.scale; + * var h = bounds.height / graph.view.scale; + * var s = Math.min(max, Math.min(cw / w, ch / h)); + * + * graph.view.scaleAndTranslate(s, + * (margin + cw - w * s) / (2 * s) - bounds.x / graph.view.scale, + * (margin + ch - h * s) / (2 * s) - bounds.y / graph.view.scale); + * (end) + * + * Parameters: + * + * border - Optional number that specifies the border. Default is . + * keepOrigin - Optional boolean that specifies if the translate should be + * changed. Default is false. + * margin - Optional margin in pixels. Default is 0. + * enabled - Optional boolean that specifies if the scale should be set or + * just returned. Default is true. + * ignoreWidth - Optional boolean that specifies if the width should be + * ignored. Default is false. + * ignoreHeight - Optional boolean that specifies if the height should be + * ignored. Default is false. + */ +mxGraph.prototype.fit = function(border, keepOrigin, margin, enabled, ignoreWidth, ignoreHeight) +{ + if (this.container != null) + { + border = (border != null) ? border : this.getBorder(); + keepOrigin = (keepOrigin != null) ? keepOrigin : false; + margin = (margin != null) ? margin : 0; + enabled = (enabled != null) ? enabled : true; + ignoreWidth = (ignoreWidth != null) ? ignoreWidth : false; + ignoreHeight = (ignoreHeight != null) ? ignoreHeight : false; + + // Adds spacing and border from css + var cssBorder = this.getBorderSizes(); + var w1 = this.container.offsetWidth - cssBorder.x - cssBorder.width - 1; + var h1 = this.container.offsetHeight - cssBorder.y - cssBorder.height - 1; + var bounds = this.view.getGraphBounds(); + + if (bounds.width > 0 && bounds.height > 0) + { + if (keepOrigin && bounds.x != null && bounds.y != null) + { + bounds = bounds.clone(); + bounds.width += bounds.x; + bounds.height += bounds.y; + bounds.x = 0; + bounds.y = 0; + } + + // LATER: Use unscaled bounding boxes to fix rounding errors + var s = this.view.scale; + var w2 = bounds.width / s; + var h2 = bounds.height / s; + + // Fits to the size of the background image if required + if (this.backgroundImage != null) + { + w2 = Math.max(w2, this.backgroundImage.width - bounds.x / s); + h2 = Math.max(h2, this.backgroundImage.height - bounds.y / s); + } + + var b = ((keepOrigin) ? border : 2 * border) + margin; + + w1 -= b; + h1 -= b; + + var s2 = (((ignoreWidth) ? h1 / h2 : (ignoreHeight) ? w1 / w2 : + Math.min(w1 / w2, h1 / h2))); + + if (this.minFitScale != null) + { + s2 = Math.max(s2, this.minFitScale); + } + + if (this.maxFitScale != null) + { + s2 = Math.min(s2, this.maxFitScale); + } + + if (enabled) + { + if (!keepOrigin) + { + if (!mxUtils.hasScrollbars(this.container)) + { + var x0 = (bounds.x != null) ? Math.floor(this.view.translate.x - bounds.x / s + border / s2 + margin / 2) : border; + var y0 = (bounds.y != null) ? Math.floor(this.view.translate.y - bounds.y / s + border / s2 + margin / 2) : border; + + this.view.scaleAndTranslate(s2, x0, y0); + } + else + { + this.view.setScale(s2); + var b2 = this.getGraphBounds(); + + if (b2.x != null) + { + this.container.scrollLeft = b2.x; + } + + if (b2.y != null) + { + this.container.scrollTop = b2.y; + } + } + } + else if (this.view.scale != s2) + { + this.view.setScale(s2); + } + } + else + { + return s2; + } + } + } + + return this.view.scale; +}; + +/** + * Function: sizeDidChange + * + * Called when the size of the graph has changed. This implementation fires + * a event after updating the clipping region of the SVG element in + * SVG-bases browsers. + */ +mxGraph.prototype.sizeDidChange = function() +{ + var bounds = this.getGraphBounds(); + + if (this.container != null) + { + var border = this.getBorder(); + + var width = Math.max(0, bounds.x + bounds.width + border); + var height = Math.max(0, bounds.y + bounds.height + border); + + if (this.minimumContainerSize != null) + { + width = Math.max(width, this.minimumContainerSize.width); + height = Math.max(height, this.minimumContainerSize.height); + } + + if (this.resizeContainer) + { + this.doResizeContainer(width, height); + } + + if (this.preferPageSize || (!mxClient.IS_IE && this.pageVisible)) + { + var size = this.getPreferredPageSize(bounds, width, height); + + if (size != null) + { + width = size.width * this.view.scale; + height = size.height * this.view.scale; + } + } + + if (this.minimumGraphSize != null) + { + width = Math.max(width, this.minimumGraphSize.width * this.view.scale); + height = Math.max(height, this.minimumGraphSize.height * this.view.scale); + } + + width = Math.ceil(width); + height = Math.ceil(height); + + if (this.dialect == mxConstants.DIALECT_SVG) + { + var root = this.view.getDrawPane().ownerSVGElement; + + root.style.minWidth = Math.max(1, width) + 'px'; + root.style.minHeight = Math.max(1, height) + 'px'; + root.style.width = '100%'; + root.style.height = '100%'; + } + else + { + if (mxClient.IS_QUIRKS) + { + // Quirks mode has no minWidth/minHeight support + this.view.updateHtmlCanvasSize(Math.max(1, width), Math.max(1, height)); + } + else + { + this.view.canvas.style.minWidth = Math.max(1, width) + 'px'; + this.view.canvas.style.minHeight = Math.max(1, height) + 'px'; + } + } + + this.updatePageBreaks(this.pageBreaksVisible, width, height); + } + + this.fireEvent(new mxEventObject(mxEvent.SIZE, 'bounds', bounds)); +}; + +/** + * Function: doResizeContainer + * + * Resizes the container for the given graph width and height. + */ +mxGraph.prototype.doResizeContainer = function(width, height) +{ + if (this.maximumContainerSize != null) + { + width = Math.min(this.maximumContainerSize.width, width); + height = Math.min(this.maximumContainerSize.height, height); + } + + this.container.style.width = Math.ceil(width) + 'px'; + this.container.style.height = Math.ceil(height) + 'px'; +}; + +/** + * Function: updatePageBreaks + * + * Invokes from to redraw the page breaks. + * + * Parameters: + * + * visible - Boolean that specifies if page breaks should be shown. + * width - Specifies the width of the container in pixels. + * height - Specifies the height of the container in pixels. + */ +mxGraph.prototype.updatePageBreaks = function(visible, width, height) +{ + var scale = this.view.scale; + var tr = this.view.translate; + var fmt = this.pageFormat; + var ps = scale * this.pageScale; + var bounds = new mxRectangle(0, 0, fmt.width * ps, fmt.height * ps); + + var gb = mxRectangle.fromRectangle(this.getGraphBounds()); + bounds.x = Math.floor((gb.x - tr.x * scale) / bounds.width) * bounds.width + tr.x * scale; + bounds.y = Math.floor((gb.y - tr.y * scale) / bounds.height) * bounds.height + tr.y * scale; + + gb.width = Math.ceil((gb.width + (gb.x - bounds.x)) / bounds.width) * bounds.width; + gb.height = Math.ceil((gb.height + (gb.y - bounds.y)) / bounds.height) * bounds.height; + + // Does not show page breaks if the scale is too small + visible = visible && Math.min(bounds.width, bounds.height) > this.minPageBreakDist; + + var horizontalCount = (visible) ? Math.ceil(gb.width / bounds.width) + 1 : 0; + var verticalCount = (visible) ? Math.ceil(gb.height / bounds.height) + 1 : 0; + var right = (horizontalCount - 1) * bounds.width; + var bottom = (verticalCount - 1) * bounds.height; + + if (this.horizontalPageBreaks == null && horizontalCount > 0) + { + this.horizontalPageBreaks = []; + } + + if (this.verticalPageBreaks == null && verticalCount > 0) + { + this.verticalPageBreaks = []; + } + + var drawPageBreaks = mxUtils.bind(this, function(breaks) + { + if (breaks != null) + { + var count = (breaks == this.horizontalPageBreaks) ? horizontalCount : verticalCount; + + for (var i = 0; i <= count; i++) + { + var pts = (breaks == this.horizontalPageBreaks) ? + [new mxPoint(bounds.x + i * bounds.width, bounds.y), + new mxPoint(bounds.x + i * bounds.width, bounds.y + bottom)] : + [new mxPoint(bounds.x, bounds.y + i * bounds.height), + new mxPoint(bounds.x + right, bounds.y + i * bounds.height)]; + + if (breaks[i] != null) + { + breaks[i].points = pts; + breaks[i].redraw(); + } + else + { + var pageBreak = new mxPolyline(pts, this.pageBreakColor); + pageBreak.dialect = this.dialect; + pageBreak.pointerEvents = false; + pageBreak.isDashed = this.pageBreakDashed; + pageBreak.init(this.view.backgroundPane); + pageBreak.redraw(); + + breaks[i] = pageBreak; + } + } + + for (var i = count; i < breaks.length; i++) + { + breaks[i].destroy(); + } + + breaks.splice(count, breaks.length - count); + } + }); + + drawPageBreaks(this.horizontalPageBreaks); + drawPageBreaks(this.verticalPageBreaks); +}; + +/** + * Group: Cell styles + */ + +/** + * Function: getCellStyle + * + * Returns an array of key, value pairs representing the cell style for the + * given cell. If no string is defined in the model that specifies the + * style, then the default style for the cell is returned or , + * if not style can be found. Note: You should try and get the cell state + * for the given cell and use the cached style in the state before using + * this method. + * + * Parameters: + * + * cell - whose style should be returned as an array. + */ +mxGraph.prototype.getCellStyle = function(cell) +{ + var stylename = this.model.getStyle(cell); + var style = null; + + // Gets the default style for the cell + if (this.model.isEdge(cell)) + { + style = this.stylesheet.getDefaultEdgeStyle(); + } + else + { + style = this.stylesheet.getDefaultVertexStyle(); + } + + // Resolves the stylename using the above as the default + if (stylename != null) + { + style = this.postProcessCellStyle(this.stylesheet.getCellStyle(stylename, style)); + } + + // Returns a non-null value if no style can be found + if (style == null) + { + style = mxGraph.prototype.EMPTY_ARRAY; + } + + return style; +}; + +/** + * Function: postProcessCellStyle + * + * Tries to resolve the value for the image style in the image bundles and + * turns short data URIs as defined in mxImageBundle to data URIs as + * defined in RFC 2397 of the IETF. + */ +mxGraph.prototype.postProcessCellStyle = function(style) +{ + if (style != null) + { + var key = style[mxConstants.STYLE_IMAGE]; + var image = this.getImageFromBundles(key); + + if (image != null) + { + style[mxConstants.STYLE_IMAGE] = image; + } + else + { + image = key; + } + + // Converts short data uris to normal data uris + if (image != null && image.substring(0, 11) == 'data:image/') + { + if (image.substring(0, 20) == 'data:image/svg+xml,<') + { + // Required for FF and IE11 + image = image.substring(0, 19) + encodeURIComponent(image.substring(19)); + } + else if (image.substring(0, 22) != 'data:image/svg+xml,%3C') + { + var comma = image.indexOf(','); + + // Adds base64 encoding prefix if needed + if (comma > 0 && image.substring(comma - 7, comma + 1) != ';base64,') + { + image = image.substring(0, comma) + ';base64,' + + image.substring(comma + 1); + } + } + + style[mxConstants.STYLE_IMAGE] = image; + } + } + + return style; +}; + +/** + * Function: setCellStyle + * + * Sets the style of the specified cells. If no cells are given, then the + * selection cells are changed. + * + * Parameters: + * + * style - String representing the new style of the cells. + * cells - Optional array of to set the style for. Default is the + * selection cells. + */ +mxGraph.prototype.setCellStyle = function(style, cells) +{ + cells = cells || this.getSelectionCells(); + + if (cells != null) + { + this.model.beginUpdate(); + try + { + for (var i = 0; i < cells.length; i++) + { + this.model.setStyle(cells[i], style); + } + } + finally + { + this.model.endUpdate(); + } + } +}; + +/** + * Function: toggleCellStyle + * + * Toggles the boolean value for the given key in the style of the given cell + * and returns the new value as 0 or 1. If no cell is specified then the + * selection cell is used. + * + * Parameter: + * + * key - String representing the key for the boolean value to be toggled. + * defaultValue - Optional boolean default value if no value is defined. + * Default is false. + * cell - Optional whose style should be modified. Default is + * the selection cell. + */ +mxGraph.prototype.toggleCellStyle = function(key, defaultValue, cell) +{ + cell = cell || this.getSelectionCell(); + + return this.toggleCellStyles(key, defaultValue, [cell]); +}; + +/** + * Function: toggleCellStyles + * + * Toggles the boolean value for the given key in the style of the given cells + * and returns the new value as 0 or 1. If no cells are specified, then the + * selection cells are used. For example, this can be used to toggle + * or any other style with a boolean value. + * + * Parameter: + * + * key - String representing the key for the boolean value to be toggled. + * defaultValue - Optional boolean default value if no value is defined. + * Default is false. + * cells - Optional array of whose styles should be modified. + * Default is the selection cells. + */ +mxGraph.prototype.toggleCellStyles = function(key, defaultValue, cells) +{ + defaultValue = (defaultValue != null) ? defaultValue : false; + cells = cells || this.getSelectionCells(); + var value = null; + + if (cells != null && cells.length > 0) + { + var state = this.view.getState(cells[0]); + var style = (state != null) ? state.style : this.getCellStyle(cells[0]); + + if (style != null) + { + value = (mxUtils.getValue(style, key, defaultValue)) ? 0 : 1; + this.setCellStyles(key, value, cells); + } + } + + return value; +}; + +/** + * Function: setCellStyles + * + * Sets the key to value in the styles of the given cells. This will modify + * the existing cell styles in-place and override any existing assignment + * for the given key. If no cells are specified, then the selection cells + * are changed. If no value is specified, then the respective key is + * removed from the styles. + * + * Parameters: + * + * key - String representing the key to be assigned. + * value - String representing the new value for the key. + * cells - Optional array of to change the style for. Default is + * the selection cells. + */ +mxGraph.prototype.setCellStyles = function(key, value, cells) +{ + cells = cells || this.getSelectionCells(); + mxUtils.setCellStyles(this.model, cells, key, value); +}; + +/** + * Function: toggleCellStyleFlags + * + * Toggles the given bit for the given key in the styles of the specified + * cells. + * + * Parameters: + * + * key - String representing the key to toggle the flag in. + * flag - Integer that represents the bit to be toggled. + * cells - Optional array of to change the style for. Default is + * the selection cells. + */ +mxGraph.prototype.toggleCellStyleFlags = function(key, flag, cells) +{ + this.setCellStyleFlags(key, flag, null, cells); +}; + +/** + * Function: setCellStyleFlags + * + * Sets or toggles the given bit for the given key in the styles of the + * specified cells. + * + * Parameters: + * + * key - String representing the key to toggle the flag in. + * flag - Integer that represents the bit to be toggled. + * value - Boolean value to be used or null if the value should be toggled. + * cells - Optional array of to change the style for. Default is + * the selection cells. + */ +mxGraph.prototype.setCellStyleFlags = function(key, flag, value, cells) +{ + cells = cells || this.getSelectionCells(); + + if (cells != null && cells.length > 0) + { + if (value == null) + { + var state = this.view.getState(cells[0]); + var style = (state != null) ? state.style : this.getCellStyle(cells[0]); + + if (style != null) + { + var current = parseInt(style[key] || 0); + value = !((current & flag) == flag); + } + } + + mxUtils.setCellStyleFlags(this.model, cells, key, flag, value); + } +}; + +/** + * Group: Cell alignment and orientation + */ + +/** + * Function: alignCells + * + * Aligns the given cells vertically or horizontally according to the given + * alignment using the optional parameter as the coordinate. + * + * Parameters: + * + * align - Specifies the alignment. Possible values are all constants in + * mxConstants with an ALIGN prefix. + * cells - Array of to be aligned. + * param - Optional coordinate for the alignment. + */ +mxGraph.prototype.alignCells = function(align, cells, param) +{ + if (cells == null) + { + cells = this.getSelectionCells(); + } + + if (cells != null && cells.length > 1) + { + // Finds the required coordinate for the alignment + if (param == null) + { + for (var i = 0; i < cells.length; i++) + { + var state = this.view.getState(cells[i]); + + if (state != null && !this.model.isEdge(cells[i])) + { + if (param == null) + { + if (align == mxConstants.ALIGN_CENTER) + { + param = state.x + state.width / 2; + break; + } + else if (align == mxConstants.ALIGN_RIGHT) + { + param = state.x + state.width; + } + else if (align == mxConstants.ALIGN_TOP) + { + param = state.y; + } + else if (align == mxConstants.ALIGN_MIDDLE) + { + param = state.y + state.height / 2; + break; + } + else if (align == mxConstants.ALIGN_BOTTOM) + { + param = state.y + state.height; + } + else + { + param = state.x; + } + } + else + { + if (align == mxConstants.ALIGN_RIGHT) + { + param = Math.max(param, state.x + state.width); + } + else if (align == mxConstants.ALIGN_TOP) + { + param = Math.min(param, state.y); + } + else if (align == mxConstants.ALIGN_BOTTOM) + { + param = Math.max(param, state.y + state.height); + } + else + { + param = Math.min(param, state.x); + } + } + } + } + } + + // Aligns the cells to the coordinate + if (param != null) + { + var s = this.view.scale; + + this.model.beginUpdate(); + try + { + for (var i = 0; i < cells.length; i++) + { + var state = this.view.getState(cells[i]); + + if (state != null) + { + var geo = this.getCellGeometry(cells[i]); + + if (geo != null && !this.model.isEdge(cells[i])) + { + geo = geo.clone(); + + if (align == mxConstants.ALIGN_CENTER) + { + geo.x += (param - state.x - state.width / 2) / s; + } + else if (align == mxConstants.ALIGN_RIGHT) + { + geo.x += (param - state.x - state.width) / s; + } + else if (align == mxConstants.ALIGN_TOP) + { + geo.y += (param - state.y) / s; + } + else if (align == mxConstants.ALIGN_MIDDLE) + { + geo.y += (param - state.y - state.height / 2) / s; + } + else if (align == mxConstants.ALIGN_BOTTOM) + { + geo.y += (param - state.y - state.height) / s; + } + else + { + geo.x += (param - state.x) / s; + } + + this.resizeCell(cells[i], geo); + } + } + } + + this.fireEvent(new mxEventObject(mxEvent.ALIGN_CELLS, + 'align', align, 'cells', cells)); + } + finally + { + this.model.endUpdate(); + } + } + } + + return cells; +}; + +/** + * Function: flipEdge + * + * Toggles the style of the given edge between null (or empty) and + * . This method fires while the + * transaction is in progress. Returns the edge that was flipped. + * + * Here is an example that overrides this implementation to invert the + * value of without removing any existing styles. + * + * (code) + * graph.flipEdge = function(edge) + * { + * if (edge != null) + * { + * var state = this.view.getState(edge); + * var style = (state != null) ? state.style : this.getCellStyle(edge); + * + * if (style != null) + * { + * var elbow = mxUtils.getValue(style, mxConstants.STYLE_ELBOW, + * mxConstants.ELBOW_HORIZONTAL); + * var value = (elbow == mxConstants.ELBOW_HORIZONTAL) ? + * mxConstants.ELBOW_VERTICAL : mxConstants.ELBOW_HORIZONTAL; + * this.setCellStyles(mxConstants.STYLE_ELBOW, value, [edge]); + * } + * } + * }; + * (end) + * + * Parameters: + * + * edge - whose style should be changed. + */ +mxGraph.prototype.flipEdge = function(edge) +{ + if (edge != null && + this.alternateEdgeStyle != null) + { + this.model.beginUpdate(); + try + { + var style = this.model.getStyle(edge); + + if (style == null || style.length == 0) + { + this.model.setStyle(edge, this.alternateEdgeStyle); + } + else + { + this.model.setStyle(edge, null); + } + + // Removes all existing control points + this.resetEdge(edge); + this.fireEvent(new mxEventObject(mxEvent.FLIP_EDGE, 'edge', edge)); + } + finally + { + this.model.endUpdate(); + } + } + + return edge; +}; + +/** + * Function: addImageBundle + * + * Adds the specified . + */ +mxGraph.prototype.addImageBundle = function(bundle) +{ + this.imageBundles.push(bundle); +}; + +/** + * Function: removeImageBundle + * + * Removes the specified . + */ +mxGraph.prototype.removeImageBundle = function(bundle) +{ + var tmp = []; + + for (var i = 0; i < this.imageBundles.length; i++) + { + if (this.imageBundles[i] != bundle) + { + tmp.push(this.imageBundles[i]); + } + } + + this.imageBundles = tmp; +}; + +/** + * Function: getImageFromBundles + * + * Searches all for the specified key and returns the value + * for the first match or null if the key is not found. + */ +mxGraph.prototype.getImageFromBundles = function(key) +{ + if (key != null) + { + for (var i = 0; i < this.imageBundles.length; i++) + { + var image = this.imageBundles[i].getImage(key); + + if (image != null) + { + return image; + } + } + } + + return null; +}; + +/** + * Group: Order + */ + +/** + * Function: orderCells + * + * Moves the given cells to the front or back. The change is carried out + * using . This method fires while the + * transaction is in progress. + * + * Parameters: + * + * back - Boolean that specifies if the cells should be moved to back. + * cells - Array of to move to the background. If null is + * specified then the selection cells are used. + */ +mxGraph.prototype.orderCells = function(back, cells) +{ + if (cells == null) + { + cells = mxUtils.sortCells(this.getSelectionCells(), true); + } + + this.model.beginUpdate(); + try + { + this.cellsOrdered(cells, back); + this.fireEvent(new mxEventObject(mxEvent.ORDER_CELLS, + 'back', back, 'cells', cells)); + } + finally + { + this.model.endUpdate(); + } + + return cells; +}; + +/** + * Function: cellsOrdered + * + * Moves the given cells to the front or back. This method fires + * while the transaction is in progress. + * + * Parameters: + * + * cells - Array of whose order should be changed. + * back - Boolean that specifies if the cells should be moved to back. + */ +mxGraph.prototype.cellsOrdered = function(cells, back) +{ + if (cells != null) + { + this.model.beginUpdate(); + try + { + for (var i = 0; i < cells.length; i++) + { + var parent = this.model.getParent(cells[i]); + + if (back) + { + this.model.add(parent, cells[i], i); + } + else + { + this.model.add(parent, cells[i], + this.model.getChildCount(parent) - 1); + } + } + + this.fireEvent(new mxEventObject(mxEvent.CELLS_ORDERED, + 'back', back, 'cells', cells)); + } + finally + { + this.model.endUpdate(); + } + } +}; + +/** + * Group: Grouping + */ + +/** + * Function: groupCells + * + * Adds the cells into the given group. The change is carried out using + * , and . This method fires + * while the transaction is in progress. Returns the + * new group. A group is only created if there is at least one entry in the + * given array of cells. + * + * Parameters: + * + * group - that represents the target group. If null is specified + * then a new group is created using . + * border - Optional integer that specifies the border between the child + * area and the group bounds. Default is 0. + * cells - Optional array of to be grouped. If null is specified + * then the selection cells are used. + */ +mxGraph.prototype.groupCells = function(group, border, cells) +{ + if (cells == null) + { + cells = mxUtils.sortCells(this.getSelectionCells(), true); + } + + cells = this.getCellsForGroup(cells); + + if (group == null) + { + group = this.createGroupCell(cells); + } + + var bounds = this.getBoundsForGroup(group, cells, border); + + if (cells.length > 0 && bounds != null) + { + // Uses parent of group or previous parent of first child + var parent = this.model.getParent(group); + + if (parent == null) + { + parent = this.model.getParent(cells[0]); + } + + this.model.beginUpdate(); + try + { + // Checks if the group has a geometry and + // creates one if one does not exist + if (this.getCellGeometry(group) == null) + { + this.model.setGeometry(group, new mxGeometry()); + } + + // Adds the group into the parent + var index = this.model.getChildCount(parent); + this.cellsAdded([group], parent, index, null, null, false); + + // Adds the children into the group and moves + index = this.model.getChildCount(group); + this.cellsAdded(cells, group, index, null, null, false, false); + this.cellsMoved(cells, -bounds.x, -bounds.y, false, true); + + // Resizes the group + this.cellsResized([group], [bounds], false); + + this.fireEvent(new mxEventObject(mxEvent.GROUP_CELLS, + 'group', group, 'border', border, 'cells', cells)); + } + finally + { + this.model.endUpdate(); + } + } + + return group; +}; + +/** + * Function: getCellsForGroup + * + * Returns the cells with the same parent as the first cell + * in the given array. + */ +mxGraph.prototype.getCellsForGroup = function(cells) +{ + var result = []; + + if (cells != null && cells.length > 0) + { + var parent = this.model.getParent(cells[0]); + result.push(cells[0]); + + // Filters selection cells with the same parent + for (var i = 1; i < cells.length; i++) + { + if (this.model.getParent(cells[i]) == parent) + { + result.push(cells[i]); + } + } + } + + return result; +}; + +/** + * Function: getBoundsForGroup + * + * Returns the bounds to be used for the given group and children. + */ +mxGraph.prototype.getBoundsForGroup = function(group, children, border) +{ + var result = this.getBoundingBoxFromGeometry(children, true); + + if (result != null) + { + if (this.isSwimlane(group)) + { + var size = this.getStartSize(group); + + result.x -= size.width; + result.y -= size.height; + result.width += size.width; + result.height += size.height; + } + + // Adds the border + if (border != null) + { + result.x -= border; + result.y -= border; + result.width += 2 * border; + result.height += 2 * border; + } + } + + return result; +}; + +/** + * Function: createGroupCell + * + * Hook for creating the group cell to hold the given array of if + * no group cell was given to the function. + * + * The following code can be used to set the style of new group cells. + * + * (code) + * var graphCreateGroupCell = graph.createGroupCell; + * graph.createGroupCell = function(cells) + * { + * var group = graphCreateGroupCell.apply(this, arguments); + * group.setStyle('group'); + * + * return group; + * }; + */ +mxGraph.prototype.createGroupCell = function(cells) +{ + var group = new mxCell(''); + group.setVertex(true); + group.setConnectable(false); + + return group; +}; + +/** + * Function: ungroupCells + * + * Ungroups the given cells by moving the children the children to their + * parents parent and removing the empty groups. Returns the children that + * have been removed from the groups. + * + * Parameters: + * + * cells - Array of cells to be ungrouped. If null is specified then the + * selection cells are used. + */ +mxGraph.prototype.ungroupCells = function(cells) +{ + var result = []; + + if (cells == null) + { + cells = this.getSelectionCells(); + + // Finds the cells with children + var tmp = []; + + for (var i = 0; i < cells.length; i++) + { + if (this.model.getChildCount(cells[i]) > 0) + { + tmp.push(cells[i]); + } + } + + cells = tmp; + } + + if (cells != null && cells.length > 0) + { + this.model.beginUpdate(); + try + { + for (var i = 0; i < cells.length; i++) + { + var children = this.model.getChildren(cells[i]); + + if (children != null && children.length > 0) + { + children = children.slice(); + var parent = this.model.getParent(cells[i]); + var index = this.model.getChildCount(parent); + + this.cellsAdded(children, parent, index, null, null, true); + result = result.concat(children); + } + } + + this.removeCellsAfterUngroup(cells); + this.fireEvent(new mxEventObject(mxEvent.UNGROUP_CELLS, 'cells', cells)); + } + finally + { + this.model.endUpdate(); + } + } + + return result; +}; + +/** + * Function: removeCellsAfterUngroup + * + * Hook to remove the groups after . + * + * Parameters: + * + * cells - Array of that were ungrouped. + */ +mxGraph.prototype.removeCellsAfterUngroup = function(cells) +{ + this.cellsRemoved(this.addAllEdges(cells)); +}; + +/** + * Function: removeCellsFromParent + * + * Removes the specified cells from their parents and adds them to the + * default parent. Returns the cells that were removed from their parents. + * + * Parameters: + * + * cells - Array of to be removed from their parents. + */ +mxGraph.prototype.removeCellsFromParent = function(cells) +{ + if (cells == null) + { + cells = this.getSelectionCells(); + } + + this.model.beginUpdate(); + try + { + var parent = this.getDefaultParent(); + var index = this.model.getChildCount(parent); + + this.cellsAdded(cells, parent, index, null, null, true); + this.fireEvent(new mxEventObject(mxEvent.REMOVE_CELLS_FROM_PARENT, 'cells', cells)); + } + finally + { + this.model.endUpdate(); + } + + return cells; +}; + +/** + * Function: updateGroupBounds + * + * Updates the bounds of the given groups to include all children and returns + * the passed-in cells. Call this with the groups in parent to child order, + * top-most group first, the cells are processed in reverse order and cells + * with no children are ignored. + * + * Parameters: + * + * cells - The groups whose bounds should be updated. If this is null, then + * the selection cells are used. + * border - Optional border to be added in the group. Default is 0. + * moveGroup - Optional boolean that allows the group to be moved. Default + * is false. + * topBorder - Optional top border to be added in the group. Default is 0. + * rightBorder - Optional top border to be added in the group. Default is 0. + * bottomBorder - Optional top border to be added in the group. Default is 0. + * leftBorder - Optional top border to be added in the group. Default is 0. + */ +mxGraph.prototype.updateGroupBounds = function(cells, border, moveGroup, topBorder, rightBorder, bottomBorder, leftBorder) +{ + if (cells == null) + { + cells = this.getSelectionCells(); + } + + border = (border != null) ? border : 0; + moveGroup = (moveGroup != null) ? moveGroup : false; + topBorder = (topBorder != null) ? topBorder : 0; + rightBorder = (rightBorder != null) ? rightBorder : 0; + bottomBorder = (bottomBorder != null) ? bottomBorder : 0; + leftBorder = (leftBorder != null) ? leftBorder : 0; + + this.model.beginUpdate(); + try + { + for (var i = cells.length - 1; i >= 0; i--) + { + var geo = this.getCellGeometry(cells[i]); + + if (geo != null) + { + var children = this.getChildCells(cells[i]); + + if (children != null && children.length > 0) + { + var bounds = this.getBoundingBoxFromGeometry(children, true); + + if (bounds != null && bounds.width > 0 && bounds.height > 0) + { + var left = 0; + var top = 0; + + // Adds the size of the title area for swimlanes + if (this.isSwimlane(cells[i])) + { + var size = this.getStartSize(cells[i]); + left = size.width; + top = size.height; + } + + geo = geo.clone(); + + if (moveGroup) + { + geo.x = Math.round(geo.x + bounds.x - border - left - leftBorder); + geo.y = Math.round(geo.y + bounds.y - border - top - topBorder); + } + + geo.width = Math.round(bounds.width + 2 * border + left + leftBorder + rightBorder); + geo.height = Math.round(bounds.height + 2 * border + top + topBorder + bottomBorder); + + this.model.setGeometry(cells[i], geo); + this.moveCells(children, border + left - bounds.x + leftBorder, + border + top - bounds.y + topBorder); + } + } + } + } + } + finally + { + this.model.endUpdate(); + } + + return cells; +}; + +/** + * Function: getBoundingBox + * + * Returns the bounding box for the given array of . The bounding box for + * each cell and its descendants is computed using . + * + * Parameters: + * + * cells - Array of whose bounding box should be returned. + */ +mxGraph.prototype.getBoundingBox = function(cells) +{ + var result = null; + + if (cells != null && cells.length > 0) + { + for (var i = 0; i < cells.length; i++) + { + if (this.model.isVertex(cells[i]) || this.model.isEdge(cells[i])) + { + var bbox = this.view.getBoundingBox(this.view.getState(cells[i]), true); + + if (bbox != null) + { + if (result == null) + { + result = mxRectangle.fromRectangle(bbox); + } + else + { + result.add(bbox); + } + } + } + } + } + + return result; +}; + +/** + * Group: Cell cloning, insertion and removal + */ + +/** + * Function: cloneCells + * + * Returns the clones for the given cells. The clones are created recursively + * using . If the terminal of an edge is not in the + * given array, then the respective end is assigned a terminal point and the + * terminal is removed. + * + * Parameters: + * + * cells - Array of to be cloned. + * allowInvalidEdges - Optional boolean that specifies if invalid edges + * should be cloned. Default is true. + * mapping - Optional mapping for existing clones. + */ +mxGraph.prototype.cloneCells = function(cells, allowInvalidEdges, mapping) +{ + allowInvalidEdges = (allowInvalidEdges != null) ? allowInvalidEdges : true; + var clones = null; + + if (cells != null) + { + // Creates a dictionary for fast lookups + var dict = new mxDictionary(); + var tmp = []; + + for (var i = 0; i < cells.length; i++) + { + dict.put(cells[i], true); + tmp.push(cells[i]); + } + + if (tmp.length > 0) + { + var scale = this.view.scale; + var trans = this.view.translate; + clones = this.model.cloneCells(cells, true, mapping); + + for (var i = 0; i < cells.length; i++) + { + if (!allowInvalidEdges && this.model.isEdge(clones[i]) && + this.getEdgeValidationError(clones[i], + this.model.getTerminal(clones[i], true), + this.model.getTerminal(clones[i], false)) != null) + { + clones[i] = null; + } + else + { + var g = this.model.getGeometry(clones[i]); + + if (g != null) + { + var state = this.view.getState(cells[i]); + var pstate = this.view.getState(this.model.getParent(cells[i])); + + if (state != null && pstate != null) + { + var dx = pstate.origin.x; + var dy = pstate.origin.y; + + if (this.model.isEdge(clones[i])) + { + var pts = state.absolutePoints; + + // Checks if the source is cloned or sets the terminal point + var src = this.model.getTerminal(cells[i], true); + + while (src != null && !dict.get(src)) + { + src = this.model.getParent(src); + } + + if (src == null) + { + g.setTerminalPoint( + new mxPoint(pts[0].x / scale - trans.x, + pts[0].y / scale - trans.y), true); + } + + // Checks if the target is cloned or sets the terminal point + var trg = this.model.getTerminal(cells[i], false); + + while (trg != null && !dict.get(trg)) + { + trg = this.model.getParent(trg); + } + + if (trg == null) + { + var n = pts.length - 1; + g.setTerminalPoint( + new mxPoint(pts[n].x / scale - trans.x, + pts[n].y / scale - trans.y), false); + } + + // Translates the control points + var points = g.points; + + if (points != null) + { + for (var j = 0; j < points.length; j++) + { + points[j].x += dx; + points[j].y += dy; + } + } + } + else + { + g.translate(dx, dy); + } + } + } + } + } + } + else + { + clones = []; + } + } + + return clones; +}; + +/** + * Function: insertVertex + * + * Adds a new vertex into the given parent using value as the user + * object and the given coordinates as the of the new vertex. + * The id and style are used for the respective properties of the new + * , which is returned. + * + * When adding new vertices from a mouse event, one should take into + * account the offset of the graph container and the scale and translation + * of the view in order to find the correct unscaled, untranslated + * coordinates using as follows: + * + * (code) + * var pt = graph.getPointForEvent(evt); + * var parent = graph.getDefaultParent(); + * graph.insertVertex(parent, null, + * 'Hello, World!', x, y, 220, 30); + * (end) + * + * For adding image cells, the style parameter can be assigned as + * + * (code) + * stylename;image=imageUrl + * (end) + * + * See for more information on using images. + * + * Parameters: + * + * parent - that specifies the parent of the new vertex. + * id - Optional string that defines the Id of the new vertex. + * value - Object to be used as the user object. + * x - Integer that defines the x coordinate of the vertex. + * y - Integer that defines the y coordinate of the vertex. + * width - Integer that defines the width of the vertex. + * height - Integer that defines the height of the vertex. + * style - Optional string that defines the cell style. + * relative - Optional boolean that specifies if the geometry is relative. + * Default is false. + */ +mxGraph.prototype.insertVertex = function(parent, id, value, + x, y, width, height, style, relative) +{ + var vertex = this.createVertex(parent, id, value, x, y, width, height, style, relative); + + return this.addCell(vertex, parent); +}; + +/** + * Function: createVertex + * + * Hook method that creates the new vertex for . + */ +mxGraph.prototype.createVertex = function(parent, id, value, + x, y, width, height, style, relative) +{ + // Creates the geometry for the vertex + var geometry = new mxGeometry(x, y, width, height); + geometry.relative = (relative != null) ? relative : false; + + // Creates the vertex + var vertex = new mxCell(value, geometry, style); + vertex.setId(id); + vertex.setVertex(true); + vertex.setConnectable(true); + + return vertex; +}; + +/** + * Function: insertEdge + * + * Adds a new edge into the given parent using value as the user + * object and the given source and target as the terminals of the new edge. + * The id and style are used for the respective properties of the new + * , which is returned. + * + * Parameters: + * + * parent - that specifies the parent of the new edge. + * id - Optional string that defines the Id of the new edge. + * value - JavaScript object to be used as the user object. + * source - that defines the source of the edge. + * target - that defines the target of the edge. + * style - Optional string that defines the cell style. + */ +mxGraph.prototype.insertEdge = function(parent, id, value, source, target, style) +{ + var edge = this.createEdge(parent, id, value, source, target, style); + + return this.addEdge(edge, parent, source, target); +}; + +/** + * Function: createEdge + * + * Hook method that creates the new edge for . This + * implementation does not set the source and target of the edge, these + * are set when the edge is added to the model. + * + */ +mxGraph.prototype.createEdge = function(parent, id, value, source, target, style) +{ + // Creates the edge + var edge = new mxCell(value, new mxGeometry(), style); + edge.setId(id); + edge.setEdge(true); + edge.geometry.relative = true; + + return edge; +}; + +/** + * Function: addEdge + * + * Adds the edge to the parent and connects it to the given source and + * target terminals. This is a shortcut method. Returns the edge that was + * added. + * + * Parameters: + * + * edge - to be inserted into the given parent. + * parent - that represents the new parent. If no parent is + * given then the default parent is used. + * source - Optional that represents the source terminal. + * target - Optional that represents the target terminal. + * index - Optional index to insert the cells at. Default is to append. + */ +mxGraph.prototype.addEdge = function(edge, parent, source, target, index) +{ + return this.addCell(edge, parent, index, source, target); +}; + +/** + * Function: addCell + * + * Adds the cell to the parent and connects it to the given source and + * target terminals. This is a shortcut method. Returns the cell that was + * added. + * + * Parameters: + * + * cell - to be inserted into the given parent. + * parent - that represents the new parent. If no parent is + * given then the default parent is used. + * index - Optional index to insert the cells at. Default is to append. + * source - Optional that represents the source terminal. + * target - Optional that represents the target terminal. + */ +mxGraph.prototype.addCell = function(cell, parent, index, source, target) +{ + return this.addCells([cell], parent, index, source, target)[0]; +}; + +/** + * Function: addCells + * + * Adds the cells to the parent at the given index, connecting each cell to + * the optional source and target terminal. The change is carried out using + * . This method fires while the + * transaction is in progress. Returns the cells that were added. + * + * Parameters: + * + * cells - Array of to be inserted. + * parent - that represents the new parent. If no parent is + * given then the default parent is used. + * index - Optional index to insert the cells at. Default is to append. + * source - Optional source for all inserted cells. + * target - Optional target for all inserted cells. + */ +mxGraph.prototype.addCells = function(cells, parent, index, source, target) +{ + if (parent == null) + { + parent = this.getDefaultParent(); + } + + if (index == null) + { + index = this.model.getChildCount(parent); + } + + this.model.beginUpdate(); + try + { + this.cellsAdded(cells, parent, index, source, target, false, true); + this.fireEvent(new mxEventObject(mxEvent.ADD_CELLS, 'cells', cells, + 'parent', parent, 'index', index, 'source', source, 'target', target)); + } + finally + { + this.model.endUpdate(); + } + + return cells; +}; + +/** + * Function: cellsAdded + * + * Adds the specified cells to the given parent. This method fires + * while the transaction is in progress. + */ +mxGraph.prototype.cellsAdded = function(cells, parent, index, source, target, absolute, constrain) +{ + if (cells != null && parent != null && index != null) + { + this.model.beginUpdate(); + try + { + var parentState = (absolute) ? this.view.getState(parent) : null; + var o1 = (parentState != null) ? parentState.origin : null; + var zero = new mxPoint(0, 0); + + for (var i = 0; i < cells.length; i++) + { + if (cells[i] == null) + { + index--; + } + else + { + var previous = this.model.getParent(cells[i]); + + // Keeps the cell at its absolute location + if (o1 != null && cells[i] != parent && parent != previous) + { + var oldState = this.view.getState(previous); + var o2 = (oldState != null) ? oldState.origin : zero; + var geo = this.model.getGeometry(cells[i]); + + if (geo != null) + { + var dx = o2.x - o1.x; + var dy = o2.y - o1.y; + + // FIXME: Cells should always be inserted first before any other edit + // to avoid forward references in sessions. + geo = geo.clone(); + geo.translate(dx, dy); + + if (!geo.relative && this.model.isVertex(cells[i]) && + !this.isAllowNegativeCoordinates()) + { + geo.x = Math.max(0, geo.x); + geo.y = Math.max(0, geo.y); + } + + this.model.setGeometry(cells[i], geo); + } + } + + // Decrements all following indices + // if cell is already in parent + if (parent == previous && index + i > this.model.getChildCount(parent)) + { + index--; + } + + this.model.add(parent, cells[i], index + i); + + if (this.autoSizeCellsOnAdd) + { + this.autoSizeCell(cells[i], true); + } + + // Extends the parent or constrains the child + if (this.isExtendParentsOnAdd(cells[i]) && this.isExtendParent(cells[i])) + { + this.extendParent(cells[i]); + } + + // Additionally constrains the child after extending the parent + if (constrain == null || constrain) + { + this.constrainChild(cells[i]); + } + + // Sets the source terminal + if (source != null) + { + this.cellConnected(cells[i], source, true); + } + + // Sets the target terminal + if (target != null) + { + this.cellConnected(cells[i], target, false); + } + } + } + + this.fireEvent(new mxEventObject(mxEvent.CELLS_ADDED, 'cells', cells, + 'parent', parent, 'index', index, 'source', source, 'target', target, + 'absolute', absolute)); + } + finally + { + this.model.endUpdate(); + } + } +}; + +/** + * Function: autoSizeCell + * + * Resizes the specified cell to just fit around the its label and/or children + * + * Parameters: + * + * cell - to be resized. + * recurse - Optional boolean which specifies if all descendants should be + * autosized. Default is true. + */ +mxGraph.prototype.autoSizeCell = function(cell, recurse) +{ + recurse = (recurse != null) ? recurse : true; + + if (recurse) + { + var childCount = this.model.getChildCount(cell); + + for (var i = 0; i < childCount; i++) + { + this.autoSizeCell(this.model.getChildAt(cell, i)); + } + } + + if (this.getModel().isVertex(cell) && this.isAutoSizeCell(cell)) + { + this.updateCellSize(cell); + } +}; + +/** + * Function: removeCells + * + * Removes the given cells from the graph including all connected edges if + * includeEdges is true. The change is carried out using . + * This method fires while the transaction is in + * progress. The removed cells are returned as an array. + * + * Parameters: + * + * cells - Array of to remove. If null is specified then the + * selection cells which are deletable are used. + * includeEdges - Optional boolean which specifies if all connected edges + * should be removed as well. Default is true. + */ +mxGraph.prototype.removeCells = function(cells, includeEdges) +{ + includeEdges = (includeEdges != null) ? includeEdges : true; + + if (cells == null) + { + cells = this.getDeletableCells(this.getSelectionCells()); + } + + // Adds all edges to the cells + if (includeEdges) + { + // FIXME: Remove duplicate cells in result or do not add if + // in cells or descendant of cells + cells = this.getDeletableCells(this.addAllEdges(cells)); + } + + this.model.beginUpdate(); + try + { + this.cellsRemoved(cells); + this.fireEvent(new mxEventObject(mxEvent.REMOVE_CELLS, + 'cells', cells, 'includeEdges', includeEdges)); + } + finally + { + this.model.endUpdate(); + } + + return cells; +}; + +/** + * Function: cellsRemoved + * + * Removes the given cells from the model. This method fires + * while the transaction is in progress. + * + * Parameters: + * + * cells - Array of to remove. + */ +mxGraph.prototype.cellsRemoved = function(cells) +{ + if (cells != null && cells.length > 0) + { + var scale = this.view.scale; + var tr = this.view.translate; + + this.model.beginUpdate(); + try + { + // Creates hashtable for faster lookup + var dict = new mxDictionary(); + + for (var i = 0; i < cells.length; i++) + { + dict.put(cells[i], true); + } + + for (var i = 0; i < cells.length; i++) + { + // Disconnects edges which are not in cells + var edges = this.getAllEdges([cells[i]]); + + var disconnectTerminal = mxUtils.bind(this, function(edge, source) + { + var geo = this.model.getGeometry(edge); + + if (geo != null) + { + var state = this.view.getState(edge); + + if (state != null) + { + // Checks which side of the edge is being disconnected + var tmp = state.getVisibleTerminal(source); + var connected = false; + + while (tmp != null) + { + if (cells[i] == tmp) + { + connected = true; + break; + } + + tmp = this.model.getParent(tmp); + } + + if (connected) + { + var dx = tr.x; + var dy = tr.y; + var parentState = this.view.getState(this.model.getParent(edge)); + + if (parentState != null && this.model.isVertex(parentState.cell)) + { + dx = parentState.x / scale; + dy = parentState.y / scale; + } + + geo = geo.clone(); + var pts = state.absolutePoints; + var n = (source) ? 0 : pts.length - 1; + geo.setTerminalPoint(new mxPoint(pts[n].x / scale - dx, pts[n].y / scale - dy), source); + this.model.setTerminal(edges[j], null, source); + this.model.setGeometry(edges[j], geo); + } + } + } + }); + + for (var j = 0; j < edges.length; j++) + { + if (!dict.get(edges[j])) + { + disconnectTerminal(edges[j], true); + disconnectTerminal(edges[j], false); + } + } + + this.model.remove(cells[i]); + } + + this.fireEvent(new mxEventObject(mxEvent.CELLS_REMOVED, 'cells', cells)); + } + finally + { + this.model.endUpdate(); + } + } +}; + +/** + * Function: splitEdge + * + * Splits the given edge by adding the newEdge between the previous source + * and the given cell and reconnecting the source of the given edge to the + * given cell. This method fires while the transaction + * is in progress. Returns the new edge that was inserted. + * + * Parameters: + * + * edge - that represents the edge to be splitted. + * cells - that represents the cells to insert into the edge. + * newEdge - that represents the edge to be inserted. + * dx - Optional integer that specifies the vector to move the cells. + * dy - Optional integer that specifies the vector to move the cells. + */ +mxGraph.prototype.splitEdge = function(edge, cells, newEdge, dx, dy) +{ + dx = dx || 0; + dy = dy || 0; + + var parent = this.model.getParent(edge); + var source = this.model.getTerminal(edge, true); + + this.model.beginUpdate(); + try + { + + if (newEdge == null) + { + newEdge = this.cloneCells([edge])[0]; + + // Removes waypoints before/after new cell + var state = this.view.getState(edge); + var geo = this.getCellGeometry(newEdge); + + if (geo != null && geo.points != null && state != null) + { + var t = this.view.translate; + var s = this.view.scale; + var idx = mxUtils.findNearestSegment(state, (dx + t.x) * s, (dy + t.y) * s); + geo.points = geo.points.slice(0, idx); + + geo = this.getCellGeometry(edge); + + if (geo != null && geo.points != null) + { + geo = geo.clone(); + geo.points = geo.points.slice(idx); + this.model.setGeometry(edge, geo); + } + } + } + + this.cellsMoved(cells, dx, dy, false, false); + this.cellsAdded(cells, parent, this.model.getChildCount(parent), null, null, + true); + this.cellsAdded([newEdge], parent, this.model.getChildCount(parent), + source, cells[0], false); + this.cellConnected(edge, cells[0], true); + this.fireEvent(new mxEventObject(mxEvent.SPLIT_EDGE, 'edge', edge, + 'cells', cells, 'newEdge', newEdge, 'dx', dx, 'dy', dy)); + } + finally + { + this.model.endUpdate(); + } + + return newEdge; +}; + +/** + * Group: Cell visibility + */ + +/** + * Function: toggleCells + * + * Sets the visible state of the specified cells and all connected edges + * if includeEdges is true. The change is carried out using . + * This method fires while the transaction is in + * progress. Returns the cells whose visible state was changed. + * + * Parameters: + * + * show - Boolean that specifies the visible state to be assigned. + * cells - Array of whose visible state should be changed. If + * null is specified then the selection cells are used. + * includeEdges - Optional boolean indicating if the visible state of all + * connected edges should be changed as well. Default is true. + */ +mxGraph.prototype.toggleCells = function(show, cells, includeEdges) +{ + if (cells == null) + { + cells = this.getSelectionCells(); + } + + // Adds all connected edges recursively + if (includeEdges) + { + cells = this.addAllEdges(cells); + } + + this.model.beginUpdate(); + try + { + this.cellsToggled(cells, show); + this.fireEvent(new mxEventObject(mxEvent.TOGGLE_CELLS, + 'show', show, 'cells', cells, 'includeEdges', includeEdges)); + } + finally + { + this.model.endUpdate(); + } + + return cells; +}; + +/** + * Function: cellsToggled + * + * Sets the visible state of the specified cells. + * + * Parameters: + * + * cells - Array of whose visible state should be changed. + * show - Boolean that specifies the visible state to be assigned. + */ +mxGraph.prototype.cellsToggled = function(cells, show) +{ + if (cells != null && cells.length > 0) + { + this.model.beginUpdate(); + try + { + for (var i = 0; i < cells.length; i++) + { + this.model.setVisible(cells[i], show); + } + } + finally + { + this.model.endUpdate(); + } + } +}; + +/** + * Group: Folding + */ + +/** + * Function: foldCells + * + * Sets the collapsed state of the specified cells and all descendants + * if recurse is true. The change is carried out using . + * This method fires while the transaction is in + * progress. Returns the cells whose collapsed state was changed. + * + * Parameters: + * + * collapsed - Boolean indicating the collapsed state to be assigned. + * recurse - Optional boolean indicating if the collapsed state of all + * descendants should be set. Default is false. + * cells - Array of whose collapsed state should be set. If + * null is specified then the foldable selection cells are used. + * checkFoldable - Optional boolean indicating of isCellFoldable should be + * checked. Default is false. + * evt - Optional native event that triggered the invocation. + */ +mxGraph.prototype.foldCells = function(collapse, recurse, cells, checkFoldable, evt) +{ + recurse = (recurse != null) ? recurse : false; + + if (cells == null) + { + cells = this.getFoldableCells(this.getSelectionCells(), collapse); + } + + this.stopEditing(false); + + this.model.beginUpdate(); + try + { + this.cellsFolded(cells, collapse, recurse, checkFoldable); + this.fireEvent(new mxEventObject(mxEvent.FOLD_CELLS, + 'collapse', collapse, 'recurse', recurse, 'cells', cells)); + } + finally + { + this.model.endUpdate(); + } + + return cells; +}; + +/** + * Function: cellsFolded + * + * Sets the collapsed state of the specified cells. This method fires + * while the transaction is in progress. Returns the + * cells whose collapsed state was changed. + * + * Parameters: + * + * cells - Array of whose collapsed state should be set. + * collapsed - Boolean indicating the collapsed state to be assigned. + * recurse - Boolean indicating if the collapsed state of all descendants + * should be set. + * checkFoldable - Optional boolean indicating of isCellFoldable should be + * checked. Default is false. + */ +mxGraph.prototype.cellsFolded = function(cells, collapse, recurse, checkFoldable) +{ + if (cells != null && cells.length > 0) + { + this.model.beginUpdate(); + try + { + for (var i = 0; i < cells.length; i++) + { + if ((!checkFoldable || this.isCellFoldable(cells[i], collapse)) && + collapse != this.isCellCollapsed(cells[i])) + { + this.model.setCollapsed(cells[i], collapse); + this.swapBounds(cells[i], collapse); + + if (this.isExtendParent(cells[i])) + { + this.extendParent(cells[i]); + } + + if (recurse) + { + var children = this.model.getChildren(cells[i]); + this.foldCells(children, collapse, recurse); + } + + this.constrainChild(cells[i]); + } + } + + this.fireEvent(new mxEventObject(mxEvent.CELLS_FOLDED, + 'cells', cells, 'collapse', collapse, 'recurse', recurse)); + } + finally + { + this.model.endUpdate(); + } + } +}; + +/** + * Function: swapBounds + * + * Swaps the alternate and the actual bounds in the geometry of the given + * cell invoking before carrying out the swap. + * + * Parameters: + * + * cell - for which the bounds should be swapped. + * willCollapse - Boolean indicating if the cell is going to be collapsed. + */ +mxGraph.prototype.swapBounds = function(cell, willCollapse) +{ + if (cell != null) + { + var geo = this.model.getGeometry(cell); + + if (geo != null) + { + geo = geo.clone(); + + this.updateAlternateBounds(cell, geo, willCollapse); + geo.swap(); + + this.model.setGeometry(cell, geo); + } + } +}; + +/** + * Function: updateAlternateBounds + * + * Updates or sets the alternate bounds in the given geometry for the given + * cell depending on whether the cell is going to be collapsed. If no + * alternate bounds are defined in the geometry and + * is true, then the preferred size is used for + * the alternate bounds. The top, left corner is always kept at the same + * location. + * + * Parameters: + * + * cell - for which the geometry is being udpated. + * g - for which the alternate bounds should be updated. + * willCollapse - Boolean indicating if the cell is going to be collapsed. + */ +mxGraph.prototype.updateAlternateBounds = function(cell, geo, willCollapse) +{ + if (cell != null && geo != null) + { + var state = this.view.getState(cell); + var style = (state != null) ? state.style : this.getCellStyle(cell); + + if (geo.alternateBounds == null) + { + var bounds = geo; + + if (this.collapseToPreferredSize) + { + var tmp = this.getPreferredSizeForCell(cell); + + if (tmp != null) + { + bounds = tmp; + + var startSize = mxUtils.getValue(style, mxConstants.STYLE_STARTSIZE); + + if (startSize > 0) + { + bounds.height = Math.max(bounds.height, startSize); + } + } + } + + geo.alternateBounds = new mxRectangle(0, 0, bounds.width, bounds.height); + } + + if (geo.alternateBounds != null) + { + geo.alternateBounds.x = geo.x; + geo.alternateBounds.y = geo.y; + + var alpha = mxUtils.toRadians(style[mxConstants.STYLE_ROTATION] || 0); + + if (alpha != 0) + { + var dx = geo.alternateBounds.getCenterX() - geo.getCenterX(); + var dy = geo.alternateBounds.getCenterY() - geo.getCenterY(); + + var cos = Math.cos(alpha); + var sin = Math.sin(alpha); + + var dx2 = cos * dx - sin * dy; + var dy2 = sin * dx + cos * dy; + + geo.alternateBounds.x += dx2 - dx; + geo.alternateBounds.y += dy2 - dy; + } + } + } +}; + +/** + * Function: addAllEdges + * + * Returns an array with the given cells and all edges that are connected + * to a cell or one of its descendants. + */ +mxGraph.prototype.addAllEdges = function(cells) +{ + var allCells = cells.slice(); + + return mxUtils.removeDuplicates(allCells.concat(this.getAllEdges(cells))); +}; + +/** + * Function: getAllEdges + * + * Returns all edges connected to the given cells or its descendants. + */ +mxGraph.prototype.getAllEdges = function(cells) +{ + var edges = []; + + if (cells != null) + { + for (var i = 0; i < cells.length; i++) + { + var edgeCount = this.model.getEdgeCount(cells[i]); + + for (var j = 0; j < edgeCount; j++) + { + edges.push(this.model.getEdgeAt(cells[i], j)); + } + + // Recurses + var children = this.model.getChildren(cells[i]); + edges = edges.concat(this.getAllEdges(children)); + } + } + + return edges; +}; + +/** + * Group: Cell sizing + */ + +/** + * Function: updateCellSize + * + * Updates the size of the given cell in the model using . + * This method fires while the transaction is in + * progress. Returns the cell whose size was updated. + * + * Parameters: + * + * cell - whose size should be updated. + */ +mxGraph.prototype.updateCellSize = function(cell, ignoreChildren) +{ + ignoreChildren = (ignoreChildren != null) ? ignoreChildren : false; + + this.model.beginUpdate(); + try + { + this.cellSizeUpdated(cell, ignoreChildren); + this.fireEvent(new mxEventObject(mxEvent.UPDATE_CELL_SIZE, + 'cell', cell, 'ignoreChildren', ignoreChildren)); + } + finally + { + this.model.endUpdate(); + } + + return cell; +}; + +/** + * Function: cellSizeUpdated + * + * Updates the size of the given cell in the model using + * to get the new size. + * + * Parameters: + * + * cell - for which the size should be changed. + */ +mxGraph.prototype.cellSizeUpdated = function(cell, ignoreChildren) +{ + if (cell != null) + { + this.model.beginUpdate(); + try + { + var size = this.getPreferredSizeForCell(cell); + var geo = this.model.getGeometry(cell); + + if (size != null && geo != null) + { + var collapsed = this.isCellCollapsed(cell); + geo = geo.clone(); + + if (this.isSwimlane(cell)) + { + var state = this.view.getState(cell); + var style = (state != null) ? state.style : this.getCellStyle(cell); + var cellStyle = this.model.getStyle(cell); + + if (cellStyle == null) + { + cellStyle = ''; + } + + if (mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true)) + { + cellStyle = mxUtils.setStyle(cellStyle, + mxConstants.STYLE_STARTSIZE, size.height + 8); + + if (collapsed) + { + geo.height = size.height + 8; + } + + geo.width = size.width; + } + else + { + cellStyle = mxUtils.setStyle(cellStyle, + mxConstants.STYLE_STARTSIZE, size.width + 8); + + if (collapsed) + { + geo.width = size.width + 8; + } + + geo.height = size.height; + } + + this.model.setStyle(cell, cellStyle); + } + else + { + geo.width = size.width; + geo.height = size.height; + } + + if (!ignoreChildren && !collapsed) + { + var bounds = this.view.getBounds(this.model.getChildren(cell)); + + if (bounds != null) + { + var tr = this.view.translate; + var scale = this.view.scale; + + var width = (bounds.x + bounds.width) / scale - geo.x - tr.x; + var height = (bounds.y + bounds.height) / scale - geo.y - tr.y; + + geo.width = Math.max(geo.width, width); + geo.height = Math.max(geo.height, height); + } + } + + this.cellsResized([cell], [geo], false); + } + } + finally + { + this.model.endUpdate(); + } + } +}; + +/** + * Function: getPreferredSizeForCell + * + * Returns the preferred width and height of the given as an + * . To implement a minimum width, add a new style eg. + * minWidth in the vertex and override this method as follows. + * + * (code) + * var graphGetPreferredSizeForCell = graph.getPreferredSizeForCell; + * graph.getPreferredSizeForCell = function(cell) + * { + * var result = graphGetPreferredSizeForCell.apply(this, arguments); + * var style = this.getCellStyle(cell); + * + * if (style['minWidth'] > 0) + * { + * result.width = Math.max(style['minWidth'], result.width); + * } + * + * return result; + * }; + * (end) + * + * Parameters: + * + * cell - for which the preferred size should be returned. + */ +mxGraph.prototype.getPreferredSizeForCell = function(cell) +{ + var result = null; + + if (cell != null) + { + var state = this.view.getState(cell) || this.view.createState(cell); + var style = state.style; + + if (!this.model.isEdge(cell)) + { + var fontSize = style[mxConstants.STYLE_FONTSIZE] || mxConstants.DEFAULT_FONTSIZE; + var dx = 0; + var dy = 0; + + // Adds dimension of image if shape is a label + if (this.getImage(state) != null || style[mxConstants.STYLE_IMAGE] != null) + { + if (style[mxConstants.STYLE_SHAPE] == mxConstants.SHAPE_LABEL) + { + if (style[mxConstants.STYLE_VERTICAL_ALIGN] == mxConstants.ALIGN_MIDDLE) + { + dx += parseFloat(style[mxConstants.STYLE_IMAGE_WIDTH]) || mxLabel.prototype.imageSize; + } + + if (style[mxConstants.STYLE_ALIGN] != mxConstants.ALIGN_CENTER) + { + dy += parseFloat(style[mxConstants.STYLE_IMAGE_HEIGHT]) || mxLabel.prototype.imageSize; + } + } + } + + // Adds spacings + dx += 2 * (style[mxConstants.STYLE_SPACING] || 0); + dx += style[mxConstants.STYLE_SPACING_LEFT] || 0; + dx += style[mxConstants.STYLE_SPACING_RIGHT] || 0; + + dy += 2 * (style[mxConstants.STYLE_SPACING] || 0); + dy += style[mxConstants.STYLE_SPACING_TOP] || 0; + dy += style[mxConstants.STYLE_SPACING_BOTTOM] || 0; + + // Add spacing for collapse/expand icon + // LATER: Check alignment and use constants + // for image spacing + var image = this.getFoldingImage(state); + + if (image != null) + { + dx += image.width + 8; + } + + // Adds space for label + var value = this.cellRenderer.getLabelValue(state); + + if (value != null && value.length > 0) + { + if (!this.isHtmlLabel(state.cell)) + { + value = mxUtils.htmlEntities(value); + } + + value = value.replace(/\n/g, '
'); + + var size = mxUtils.getSizeForString(value, fontSize, style[mxConstants.STYLE_FONTFAMILY]); + var width = size.width + dx; + var height = size.height + dy; + + if (!mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true)) + { + var tmp = height; + + height = width; + width = tmp; + } + + if (this.gridEnabled) + { + width = this.snap(width + this.gridSize / 2); + height = this.snap(height + this.gridSize / 2); + } + + result = new mxRectangle(0, 0, width, height); + } + else + { + var gs2 = 4 * this.gridSize; + result = new mxRectangle(0, 0, gs2, gs2); + } + } + } + + return result; +}; + +/** + * Function: resizeCell + * + * Sets the bounds of the given cell using . Returns the + * cell which was passed to the function. + * + * Parameters: + * + * cell - whose bounds should be changed. + * bounds - that represents the new bounds. + */ +mxGraph.prototype.resizeCell = function(cell, bounds, recurse) +{ + return this.resizeCells([cell], [bounds], recurse)[0]; +}; + +/** + * Function: resizeCells + * + * Sets the bounds of the given cells and fires a + * event while the transaction is in progress. Returns the cells which + * have been passed to the function. + * + * Parameters: + * + * cells - Array of whose bounds should be changed. + * bounds - Array of that represent the new bounds. + */ +mxGraph.prototype.resizeCells = function(cells, bounds, recurse) +{ + recurse = (recurse != null) ? recurse : this.isRecursiveResize(); + + this.model.beginUpdate(); + try + { + this.cellsResized(cells, bounds, recurse); + this.fireEvent(new mxEventObject(mxEvent.RESIZE_CELLS, + 'cells', cells, 'bounds', bounds)); + } + finally + { + this.model.endUpdate(); + } + + return cells; +}; + +/** + * Function: cellsResized + * + * Sets the bounds of the given cells and fires a + * event. If is true, then the parent is extended if a + * child size is changed so that it overlaps with the parent. + * + * The following example shows how to control group resizes to make sure + * that all child cells stay within the group. + * + * (code) + * graph.addListener(mxEvent.CELLS_RESIZED, function(sender, evt) + * { + * var cells = evt.getProperty('cells'); + * + * if (cells != null) + * { + * for (var i = 0; i < cells.length; i++) + * { + * if (graph.getModel().getChildCount(cells[i]) > 0) + * { + * var geo = graph.getCellGeometry(cells[i]); + * + * if (geo != null) + * { + * var children = graph.getChildCells(cells[i], true, true); + * var bounds = graph.getBoundingBoxFromGeometry(children, true); + * + * geo = geo.clone(); + * geo.width = Math.max(geo.width, bounds.width); + * geo.height = Math.max(geo.height, bounds.height); + * + * graph.getModel().setGeometry(cells[i], geo); + * } + * } + * } + * } + * }); + * (end) + * + * Parameters: + * + * cells - Array of whose bounds should be changed. + * bounds - Array of that represent the new bounds. + * recurse - Optional boolean that specifies if the children should be resized. + */ +mxGraph.prototype.cellsResized = function(cells, bounds, recurse) +{ + recurse = (recurse != null) ? recurse : false; + + if (cells != null && bounds != null && cells.length == bounds.length) + { + this.model.beginUpdate(); + try + { + for (var i = 0; i < cells.length; i++) + { + this.cellResized(cells[i], bounds[i], false, recurse); + + if (this.isExtendParent(cells[i])) + { + this.extendParent(cells[i]); + } + + this.constrainChild(cells[i]); + } + + if (this.resetEdgesOnResize) + { + this.resetEdges(cells); + } + + this.fireEvent(new mxEventObject(mxEvent.CELLS_RESIZED, + 'cells', cells, 'bounds', bounds)); + } + finally + { + this.model.endUpdate(); + } + } +}; + +/** + * Function: cellResized + * + * Resizes the parents recursively so that they contain the complete area + * of the resized child cell. + * + * Parameters: + * + * cell - whose bounds should be changed. + * bounds - that represent the new bounds. + * ignoreRelative - Boolean that indicates if relative cells should be ignored. + * recurse - Optional boolean that specifies if the children should be resized. + */ +mxGraph.prototype.cellResized = function(cell, bounds, ignoreRelative, recurse) +{ + var geo = this.model.getGeometry(cell); + + if (geo != null && (geo.x != bounds.x || geo.y != bounds.y || + geo.width != bounds.width || geo.height != bounds.height)) + { + geo = geo.clone(); + + if (!ignoreRelative && geo.relative) + { + var offset = geo.offset; + + if (offset != null) + { + offset.x += bounds.x - geo.x; + offset.y += bounds.y - geo.y; + } + } + else + { + geo.x = bounds.x; + geo.y = bounds.y; + } + + geo.width = bounds.width; + geo.height = bounds.height; + + if (!geo.relative && this.model.isVertex(cell) && !this.isAllowNegativeCoordinates()) + { + geo.x = Math.max(0, geo.x); + geo.y = Math.max(0, geo.y); + } + + this.model.beginUpdate(); + try + { + if (recurse) + { + this.resizeChildCells(cell, geo); + } + + this.model.setGeometry(cell, geo); + this.constrainChildCells(cell); + } + finally + { + this.model.endUpdate(); + } + } +}; + +/** + * Function: resizeChildCells + * + * Resizes the child cells of the given cell for the given new geometry with + * respect to the current geometry of the cell. + * + * Parameters: + * + * cell - that has been resized. + * newGeo - that represents the new bounds. + */ +mxGraph.prototype.resizeChildCells = function(cell, newGeo) +{ + var geo = this.model.getGeometry(cell); + var dx = newGeo.width / geo.width; + var dy = newGeo.height / geo.height; + var childCount = this.model.getChildCount(cell); + + for (var i = 0; i < childCount; i++) + { + this.scaleCell(this.model.getChildAt(cell, i), dx, dy, true); + } +}; + +/** + * Function: constrainChildCells + * + * Constrains the children of the given cell using . + * + * Parameters: + * + * cell - that has been resized. + */ +mxGraph.prototype.constrainChildCells = function(cell) +{ + var childCount = this.model.getChildCount(cell); + + for (var i = 0; i < childCount; i++) + { + this.constrainChild(this.model.getChildAt(cell, i)); + } +}; + +/** + * Function: scaleCell + * + * Scales the points, position and size of the given cell according to the + * given vertical and horizontal scaling factors. + * + * Parameters: + * + * cell - whose geometry should be scaled. + * dx - Horizontal scaling factor. + * dy - Vertical scaling factor. + * recurse - Boolean indicating if the child cells should be scaled. + */ +mxGraph.prototype.scaleCell = function(cell, dx, dy, recurse) +{ + var geo = this.model.getGeometry(cell); + + if (geo != null) + { + var state = this.view.getState(cell); + var style = (state != null) ? state.style : this.getCellStyle(cell); + + geo = geo.clone(); + + // Stores values for restoring based on style + var x = geo.x; + var y = geo.y + var w = geo.width; + var h = geo.height; + + geo.scale(dx, dy, style[mxConstants.STYLE_ASPECT] == 'fixed'); + + if (style[mxConstants.STYLE_RESIZE_WIDTH] == '1') + { + geo.width = w * dx; + } + else if (style[mxConstants.STYLE_RESIZE_WIDTH] == '0') + { + geo.width = w; + } + + if (style[mxConstants.STYLE_RESIZE_HEIGHT] == '1') + { + geo.height = h * dy; + } + else if (style[mxConstants.STYLE_RESIZE_HEIGHT] == '0') + { + geo.height = h; + } + + if (!this.isCellMovable(cell)) + { + geo.x = x; + geo.y = y; + } + + if (!this.isCellResizable(cell)) + { + geo.width = w; + geo.height = h; + } + + if (this.model.isVertex(cell)) + { + this.cellResized(cell, geo, true, recurse); + } + else + { + this.model.setGeometry(cell, geo); + } + } +}; + +/** + * Function: extendParent + * + * Resizes the parents recursively so that they contain the complete area + * of the resized child cell. + * + * Parameters: + * + * cell - that has been resized. + */ +mxGraph.prototype.extendParent = function(cell) +{ + if (cell != null) + { + var parent = this.model.getParent(cell); + var p = this.getCellGeometry(parent); + + if (parent != null && p != null && !this.isCellCollapsed(parent)) + { + var geo = this.getCellGeometry(cell); + + if (geo != null && !geo.relative && + (p.width < geo.x + geo.width || + p.height < geo.y + geo.height)) + { + p = p.clone(); + + p.width = Math.max(p.width, geo.x + geo.width); + p.height = Math.max(p.height, geo.y + geo.height); + + this.cellsResized([parent], [p], false); + } + } + } +}; + +/** + * Group: Cell moving + */ + +/** + * Function: importCells + * + * Clones and inserts the given cells into the graph using the move + * method and returns the inserted cells. This shortcut is used if + * cells are inserted via datatransfer. + * + * Parameters: + * + * cells - Array of to be imported. + * dx - Integer that specifies the x-coordinate of the vector. Default is 0. + * dy - Integer that specifies the y-coordinate of the vector. Default is 0. + * target - that represents the new parent of the cells. + * evt - Mouseevent that triggered the invocation. + * mapping - Optional mapping for existing clones. + */ +mxGraph.prototype.importCells = function(cells, dx, dy, target, evt, mapping) +{ + return this.moveCells(cells, dx, dy, true, target, evt, mapping); +}; + +/** + * Function: moveCells + * + * Moves or clones the specified cells and moves the cells or clones by the + * given amount, adding them to the optional target cell. The evt is the + * mouse event as the mouse was released. The change is carried out using + * . This method fires while the + * transaction is in progress. Returns the cells that were moved. + * + * Use the following code to move all cells in the graph. + * + * (code) + * graph.moveCells(graph.getChildCells(null, true, true), 10, 10); + * (end) + * + * Parameters: + * + * cells - Array of to be moved, cloned or added to the target. + * dx - Integer that specifies the x-coordinate of the vector. Default is 0. + * dy - Integer that specifies the y-coordinate of the vector. Default is 0. + * clone - Boolean indicating if the cells should be cloned. Default is false. + * target - that represents the new parent of the cells. + * evt - Mouseevent that triggered the invocation. + * mapping - Optional mapping for existing clones. + */ +mxGraph.prototype.moveCells = function(cells, dx, dy, clone, target, evt, mapping) +{ + dx = (dx != null) ? dx : 0; + dy = (dy != null) ? dy : 0; + clone = (clone != null) ? clone : false; + + if (cells != null && (dx != 0 || dy != 0 || clone || target != null)) + { + // Removes descandants with ancestors in cells to avoid multiple moving + cells = this.model.getTopmostCells(cells); + + this.model.beginUpdate(); + try + { + // Faster cell lookups to remove relative edge labels with selected + // terminals to avoid explicit and implicit move at same time + var dict = new mxDictionary(); + + for (var i = 0; i < cells.length; i++) + { + dict.put(cells[i], true); + } + + var isSelected = mxUtils.bind(this, function(cell) + { + while (cell != null) + { + if (dict.get(cell)) + { + return true; + } + + cell = this.model.getParent(cell); + } + + return false; + }); + + // Removes relative edge labels with selected terminals + var checked = []; + + for (var i = 0; i < cells.length; i++) + { + var geo = this.getCellGeometry(cells[i]); + var parent = this.model.getParent(cells[i]); + + if ((geo == null || !geo.relative) || !this.model.isEdge(parent) || + (!isSelected(this.model.getTerminal(parent, true)) && + !isSelected(this.model.getTerminal(parent, false)))) + { + checked.push(cells[i]); + } + } + + cells = checked; + + if (clone) + { + cells = this.cloneCells(cells, this.isCloneInvalidEdges(), mapping); + + if (target == null) + { + target = this.getDefaultParent(); + } + } + + // FIXME: Cells should always be inserted first before any other edit + // to avoid forward references in sessions. + // Need to disable allowNegativeCoordinates if target not null to + // allow for temporary negative numbers until cellsAdded is called. + var previous = this.isAllowNegativeCoordinates(); + + if (target != null) + { + this.setAllowNegativeCoordinates(true); + } + + this.cellsMoved(cells, dx, dy, !clone && this.isDisconnectOnMove() + && this.isAllowDanglingEdges(), target == null, + this.isExtendParentsOnMove() && target == null); + + this.setAllowNegativeCoordinates(previous); + + if (target != null) + { + var index = this.model.getChildCount(target); + this.cellsAdded(cells, target, index, null, null, true); + } + + // Dispatches a move event + this.fireEvent(new mxEventObject(mxEvent.MOVE_CELLS, 'cells', cells, + 'dx', dx, 'dy', dy, 'clone', clone, 'target', target, 'event', evt)); + } + finally + { + this.model.endUpdate(); + } + } + + return cells; +}; + +/** + * Function: cellsMoved + * + * Moves the specified cells by the given vector, disconnecting the cells + * using disconnectGraph is disconnect is true. This method fires + * while the transaction is in progress. + */ +mxGraph.prototype.cellsMoved = function(cells, dx, dy, disconnect, constrain, extend) +{ + if (cells != null && (dx != 0 || dy != 0)) + { + extend = (extend != null) ? extend : false; + + this.model.beginUpdate(); + try + { + if (disconnect) + { + this.disconnectGraph(cells); + } + + for (var i = 0; i < cells.length; i++) + { + this.translateCell(cells[i], dx, dy); + + if (extend && this.isExtendParent(cells[i])) + { + this.extendParent(cells[i]); + } + else if (constrain) + { + this.constrainChild(cells[i]); + } + } + + if (this.resetEdgesOnMove) + { + this.resetEdges(cells); + } + + this.fireEvent(new mxEventObject(mxEvent.CELLS_MOVED, + 'cells', cells, 'dx', dx, 'dy', dy, 'disconnect', disconnect)); + } + finally + { + this.model.endUpdate(); + } + } +}; + +/** + * Function: translateCell + * + * Translates the geometry of the given cell and stores the new, + * translated geometry in the model as an atomic change. + */ +mxGraph.prototype.translateCell = function(cell, dx, dy) +{ + var geo = this.model.getGeometry(cell); + + if (geo != null) + { + dx = parseFloat(dx); + dy = parseFloat(dy); + geo = geo.clone(); + geo.translate(dx, dy); + + if (!geo.relative && this.model.isVertex(cell) && !this.isAllowNegativeCoordinates()) + { + geo.x = Math.max(0, parseFloat(geo.x)); + geo.y = Math.max(0, parseFloat(geo.y)); + } + + if (geo.relative && !this.model.isEdge(cell)) + { + var parent = this.model.getParent(cell); + var angle = 0; + + if (this.model.isVertex(parent)) + { + var state = this.view.getState(parent); + var style = (state != null) ? state.style : this.getCellStyle(parent); + + angle = mxUtils.getValue(style, mxConstants.STYLE_ROTATION, 0); + } + + if (angle != 0) + { + var rad = mxUtils.toRadians(-angle); + var cos = Math.cos(rad); + var sin = Math.sin(rad); + var pt = mxUtils.getRotatedPoint(new mxPoint(dx, dy), cos, sin, new mxPoint(0, 0)); + dx = pt.x; + dy = pt.y; + } + + if (geo.offset == null) + { + geo.offset = new mxPoint(dx, dy); + } + else + { + geo.offset.x = parseFloat(geo.offset.x) + dx; + geo.offset.y = parseFloat(geo.offset.y) + dy; + } + } + + this.model.setGeometry(cell, geo); + } +}; + +/** + * Function: getCellContainmentArea + * + * Returns the inside which a cell is to be kept. + * + * Parameters: + * + * cell - for which the area should be returned. + */ +mxGraph.prototype.getCellContainmentArea = function(cell) +{ + if (cell != null && !this.model.isEdge(cell)) + { + var parent = this.model.getParent(cell); + + if (parent != null && parent != this.getDefaultParent()) + { + var g = this.model.getGeometry(parent); + + if (g != null) + { + var x = 0; + var y = 0; + var w = g.width; + var h = g.height; + + if (this.isSwimlane(parent)) + { + var size = this.getStartSize(parent); + + var state = this.view.getState(parent); + var style = (state != null) ? state.style : this.getCellStyle(parent); + var dir = mxUtils.getValue(style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST); + var flipH = mxUtils.getValue(style, mxConstants.STYLE_FLIPH, 0) == 1; + var flipV = mxUtils.getValue(style, mxConstants.STYLE_FLIPV, 0) == 1; + + if (dir == mxConstants.DIRECTION_SOUTH || dir == mxConstants.DIRECTION_NORTH) + { + var tmp = size.width; + size.width = size.height; + size.height = tmp; + } + + if ((dir == mxConstants.DIRECTION_EAST && !flipV) || (dir == mxConstants.DIRECTION_NORTH && !flipH) || + (dir == mxConstants.DIRECTION_WEST && flipV) || (dir == mxConstants.DIRECTION_SOUTH && flipH)) + { + x = size.width; + y = size.height; + } + + w -= size.width; + h -= size.height; + } + + return new mxRectangle(x, y, w, h); + } + } + } + + return null; +}; + +/** + * Function: getMaximumGraphBounds + * + * Returns the bounds inside which the diagram should be kept as an + * . + */ +mxGraph.prototype.getMaximumGraphBounds = function() +{ + return this.maximumGraphBounds; +}; + +/** + * Function: constrainChild + * + * Keeps the given cell inside the bounds returned by + * for its parent, according to the rules defined by + * and . This modifies the cell's geometry + * in-place and does not clone it. + * + * Parameters: + * + * cells - which should be constrained. + * sizeFirst - Specifies if the size should be changed first. Default is true. + */ +mxGraph.prototype.constrainChild = function(cell, sizeFirst) +{ + sizeFirst = (sizeFirst != null) ? sizeFirst : true; + + if (cell != null) + { + var geo = this.getCellGeometry(cell); + + if (geo != null && (this.isConstrainRelativeChildren() || !geo.relative)) + { + var parent = this.model.getParent(cell); + var pgeo = this.getCellGeometry(parent); + var max = this.getMaximumGraphBounds(); + + // Finds parent offset + if (max != null) + { + var off = this.getBoundingBoxFromGeometry([parent], false); + + if (off != null) + { + max = mxRectangle.fromRectangle(max); + + max.x -= off.x; + max.y -= off.y; + } + } + + if (this.isConstrainChild(cell)) + { + var tmp = this.getCellContainmentArea(cell); + + if (tmp != null) + { + var overlap = this.getOverlap(cell); + + if (overlap > 0) + { + tmp = mxRectangle.fromRectangle(tmp); + + tmp.x -= tmp.width * overlap; + tmp.y -= tmp.height * overlap; + tmp.width += 2 * tmp.width * overlap; + tmp.height += 2 * tmp.height * overlap; + } + + // Find the intersection between max and tmp + if (max == null) + { + max = tmp; + } + else + { + max = mxRectangle.fromRectangle(max); + max.intersect(tmp); + } + } + } + + if (max != null) + { + var cells = [cell]; + + if (!this.isCellCollapsed(cell)) + { + var desc = this.model.getDescendants(cell); + + for (var i = 0; i < desc.length; i++) + { + if (this.isCellVisible(desc[i])) + { + cells.push(desc[i]); + } + } + } + + var bbox = this.getBoundingBoxFromGeometry(cells, false); + + if (bbox != null) + { + geo = geo.clone(); + + // Cumulative horizontal movement + var dx = 0; + + if (geo.width > max.width) + { + dx = geo.width - max.width; + geo.width -= dx; + } + + if (bbox.x + bbox.width > max.x + max.width) + { + dx -= bbox.x + bbox.width - max.x - max.width - dx; + } + + // Cumulative vertical movement + var dy = 0; + + if (geo.height > max.height) + { + dy = geo.height - max.height; + geo.height -= dy; + } + + if (bbox.y + bbox.height > max.y + max.height) + { + dy -= bbox.y + bbox.height - max.y - max.height - dy; + } + + if (bbox.x < max.x) + { + dx -= bbox.x - max.x; + } + + if (bbox.y < max.y) + { + dy -= bbox.y - max.y; + } + + if (dx != 0 || dy != 0) + { + if (geo.relative) + { + // Relative geometries are moved via absolute offset + if (geo.offset == null) + { + geo.offset = new mxPoint(); + } + + geo.offset.x += dx; + geo.offset.y += dy; + } + else + { + geo.x += dx; + geo.y += dy; + } + } + + this.model.setGeometry(cell, geo); + } + } + } + } +}; + +/** + * Function: resetEdges + * + * Resets the control points of the edges that are connected to the given + * cells if not both ends of the edge are in the given cells array. + * + * Parameters: + * + * cells - Array of for which the connected edges should be + * reset. + */ +mxGraph.prototype.resetEdges = function(cells) +{ + if (cells != null) + { + // Prepares faster cells lookup + var dict = new mxDictionary(); + + for (var i = 0; i < cells.length; i++) + { + dict.put(cells[i], true); + } + + this.model.beginUpdate(); + try + { + for (var i = 0; i < cells.length; i++) + { + var edges = this.model.getEdges(cells[i]); + + if (edges != null) + { + for (var j = 0; j < edges.length; j++) + { + var state = this.view.getState(edges[j]); + + var source = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[j], true); + var target = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[j], false); + + // Checks if one of the terminals is not in the given array + if (!dict.get(source) || !dict.get(target)) + { + this.resetEdge(edges[j]); + } + } + } + + this.resetEdges(this.model.getChildren(cells[i])); + } + } + finally + { + this.model.endUpdate(); + } + } +}; + +/** + * Function: resetEdge + * + * Resets the control points of the given edge. + * + * Parameters: + * + * edge - whose points should be reset. + */ +mxGraph.prototype.resetEdge = function(edge) +{ + var geo = this.model.getGeometry(edge); + + // Resets the control points + if (geo != null && geo.points != null && geo.points.length > 0) + { + geo = geo.clone(); + geo.points = []; + this.model.setGeometry(edge, geo); + } + + return edge; +}; + +/** + * Group: Cell connecting and connection constraints + */ + +/** + * Function: getOutlineConstraint + * + * Returns the constraint used to connect to the outline of the given state. + */ +mxGraph.prototype.getOutlineConstraint = function(point, terminalState, me) +{ + if (terminalState.shape != null) + { + var bounds = this.view.getPerimeterBounds(terminalState); + var direction = terminalState.style[mxConstants.STYLE_DIRECTION]; + + if (direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_SOUTH) + { + bounds.x += bounds.width / 2 - bounds.height / 2; + bounds.y += bounds.height / 2 - bounds.width / 2; + var tmp = bounds.width; + bounds.width = bounds.height; + bounds.height = tmp; + } + + var alpha = mxUtils.toRadians(terminalState.shape.getShapeRotation()); + + if (alpha != 0) + { + var cos = Math.cos(-alpha); + var sin = Math.sin(-alpha); + + var ct = new mxPoint(bounds.getCenterX(), bounds.getCenterY()); + point = mxUtils.getRotatedPoint(point, cos, sin, ct); + } + + var sx = 1; + var sy = 1; + var dx = 0; + var dy = 0; + + // LATER: Add flipping support for image shapes + if (this.getModel().isVertex(terminalState.cell)) + { + var flipH = terminalState.style[mxConstants.STYLE_FLIPH]; + var flipV = terminalState.style[mxConstants.STYLE_FLIPV]; + + // Legacy support for stencilFlipH/V + if (terminalState.shape != null && terminalState.shape.stencil != null) + { + flipH = mxUtils.getValue(terminalState.style, 'stencilFlipH', 0) == 1 || flipH; + flipV = mxUtils.getValue(terminalState.style, 'stencilFlipV', 0) == 1 || flipV; + } + + if (direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_SOUTH) + { + var tmp = flipH; + flipH = flipV; + flipV = tmp; + } + + if (flipH) + { + sx = -1; + dx = -bounds.width; + } + + if (flipV) + { + sy = -1; + dy = -bounds.height ; + } + } + + point = new mxPoint((point.x - bounds.x) * sx - dx + bounds.x, (point.y - bounds.y) * sy - dy + bounds.y); + + var x = Math.round((point.x - bounds.x) * 1000 / bounds.width) / 1000; + var y = Math.round((point.y - bounds.y) * 1000 / bounds.height) / 1000; + + return new mxConnectionConstraint(new mxPoint(x, y), false); + } + + return null; +}; + +/** + * Function: getAllConnectionConstraints + * + * Returns an array of all for the given terminal. If + * the shape of the given terminal is a then the constraints + * of the corresponding are returned. + * + * Parameters: + * + * terminal - that represents the terminal. + * source - Boolean that specifies if the terminal is the source or target. + */ +mxGraph.prototype.getAllConnectionConstraints = function(terminal, source) +{ + if (terminal != null && terminal.shape != null && terminal.shape.stencil != null) + { + return terminal.shape.stencil.constraints; + } + + return null; +}; + +/** + * Function: getConnectionConstraint + * + * Returns an that describes the given connection + * point. This result can then be passed to . + * + * Parameters: + * + * edge - that represents the edge. + * terminal - that represents the terminal. + * source - Boolean indicating if the terminal is the source or target. + */ +mxGraph.prototype.getConnectionConstraint = function(edge, terminal, source) +{ + var point = null; + var x = edge.style[(source) ? mxConstants.STYLE_EXIT_X : mxConstants.STYLE_ENTRY_X]; + + if (x != null) + { + var y = edge.style[(source) ? mxConstants.STYLE_EXIT_Y : mxConstants.STYLE_ENTRY_Y]; + + if (y != null) + { + point = new mxPoint(parseFloat(x), parseFloat(y)); + } + } + + var perimeter = false; + + if (point != null) + { + perimeter = mxUtils.getValue(edge.style, (source) ? mxConstants.STYLE_EXIT_PERIMETER : + mxConstants.STYLE_ENTRY_PERIMETER, true); + } + + return new mxConnectionConstraint(point, perimeter); +}; + +/** + * Function: setConnectionConstraint + * + * Sets the that describes the given connection point. + * If no constraint is given then nothing is changed. To remove an existing + * constraint from the given edge, use an empty constraint instead. + * + * Parameters: + * + * edge - that represents the edge. + * terminal - that represents the terminal. + * source - Boolean indicating if the terminal is the source or target. + * constraint - Optional to be used for this + * connection. + */ +mxGraph.prototype.setConnectionConstraint = function(edge, terminal, source, constraint) +{ + if (constraint != null) + { + this.model.beginUpdate(); + + try + { + if (constraint == null || constraint.point == null) + { + this.setCellStyles((source) ? mxConstants.STYLE_EXIT_X : + mxConstants.STYLE_ENTRY_X, null, [edge]); + this.setCellStyles((source) ? mxConstants.STYLE_EXIT_Y : + mxConstants.STYLE_ENTRY_Y, null, [edge]); + this.setCellStyles((source) ? mxConstants.STYLE_EXIT_PERIMETER : + mxConstants.STYLE_ENTRY_PERIMETER, null, [edge]); + } + else if (constraint.point != null) + { + this.setCellStyles((source) ? mxConstants.STYLE_EXIT_X : + mxConstants.STYLE_ENTRY_X, constraint.point.x, [edge]); + this.setCellStyles((source) ? mxConstants.STYLE_EXIT_Y : + mxConstants.STYLE_ENTRY_Y, constraint.point.y, [edge]); + + // Only writes 0 since 1 is default + if (!constraint.perimeter) + { + this.setCellStyles((source) ? mxConstants.STYLE_EXIT_PERIMETER : + mxConstants.STYLE_ENTRY_PERIMETER, '0', [edge]); + } + else + { + this.setCellStyles((source) ? mxConstants.STYLE_EXIT_PERIMETER : + mxConstants.STYLE_ENTRY_PERIMETER, null, [edge]); + } + } + } + finally + { + this.model.endUpdate(); + } + } +}; + +/** + * Function: getConnectionPoint + * + * Returns the nearest point in the list of absolute points or the center + * of the opposite terminal. + * + * Parameters: + * + * vertex - that represents the vertex. + * constraint - that represents the connection point + * constraint as returned by . + */ +mxGraph.prototype.getConnectionPoint = function(vertex, constraint) +{ + var point = null; + + if (vertex != null && constraint.point != null) + { + var bounds = this.view.getPerimeterBounds(vertex); + var cx = new mxPoint(bounds.getCenterX(), bounds.getCenterY()); + var direction = vertex.style[mxConstants.STYLE_DIRECTION]; + var r1 = 0; + + // Bounds need to be rotated by 90 degrees for further computation + if (direction != null) + { + if (direction == mxConstants.DIRECTION_NORTH) + { + r1 += 270; + } + else if (direction == mxConstants.DIRECTION_WEST) + { + r1 += 180; + } + else if (direction == mxConstants.DIRECTION_SOUTH) + { + r1 += 90; + } + + // Bounds need to be rotated by 90 degrees for further computation + if (direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_SOUTH) + { + bounds.rotate90(); + } + } + + if (constraint.point != null) + { + var sx = 1; + var sy = 1; + var dx = 0; + var dy = 0; + + // LATER: Add flipping support for image shapes + if (this.getModel().isVertex(vertex.cell)) + { + var flipH = vertex.style[mxConstants.STYLE_FLIPH]; + var flipV = vertex.style[mxConstants.STYLE_FLIPV]; + + // Legacy support for stencilFlipH/V + if (vertex.shape != null && vertex.shape.stencil != null) + { + flipH = mxUtils.getValue(vertex.style, 'stencilFlipH', 0) == 1 || flipH; + flipV = mxUtils.getValue(vertex.style, 'stencilFlipV', 0) == 1 || flipV; + } + + if (direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_SOUTH) + { + var tmp = flipH; + flipH = flipV; + flipV = tmp; + } + + if (flipH) + { + sx = -1; + dx = -bounds.width; + } + + if (flipV) + { + sy = -1; + dy = -bounds.height ; + } + } + + point = new mxPoint(bounds.x + constraint.point.x * bounds.width * sx - dx, + bounds.y + constraint.point.y * bounds.height * sy - dy); + } + + // Rotation for direction before projection on perimeter + var r2 = vertex.style[mxConstants.STYLE_ROTATION] || 0; + + if (constraint.perimeter) + { + if (r1 != 0 && point != null) + { + // Only 90 degrees steps possible here so no trig needed + var cos = 0; + var sin = 0; + + if (r1 == 90) + { + sin = 1; + } + else if (r1 == 180) + { + cos = -1; + } + else if (r1 == 270) + { + sin = -1; + } + + point = mxUtils.getRotatedPoint(point, cos, sin, cx); + } + + if (point != null && constraint.perimeter) + { + point = this.view.getPerimeterPoint(vertex, point, false); + } + } + else + { + r2 += r1; + } + + // Generic rotation after projection on perimeter + if (r2 != 0 && point != null) + { + var rad = mxUtils.toRadians(r2); + var cos = Math.cos(rad); + var sin = Math.sin(rad); + + point = mxUtils.getRotatedPoint(point, cos, sin, cx); + } + } + + if (point != null) + { + point.x = Math.round(point.x); + point.y = Math.round(point.y); + } + + return point; +}; + +/** + * Function: connectCell + * + * Connects the specified end of the given edge to the given terminal + * using and fires while the + * transaction is in progress. Returns the updated edge. + * + * Parameters: + * + * edge - whose terminal should be updated. + * terminal - that represents the new terminal to be used. + * source - Boolean indicating if the new terminal is the source or target. + * constraint - Optional to be used for this + * connection. + */ +mxGraph.prototype.connectCell = function(edge, terminal, source, constraint) +{ + this.model.beginUpdate(); + try + { + var previous = this.model.getTerminal(edge, source); + this.cellConnected(edge, terminal, source, constraint); + this.fireEvent(new mxEventObject(mxEvent.CONNECT_CELL, + 'edge', edge, 'terminal', terminal, 'source', source, + 'previous', previous)); + } + finally + { + this.model.endUpdate(); + } + + return edge; +}; + +/** + * Function: cellConnected + * + * Sets the new terminal for the given edge and resets the edge points if + * is true. This method fires + * while the transaction is in progress. + * + * Parameters: + * + * edge - whose terminal should be updated. + * terminal - that represents the new terminal to be used. + * source - Boolean indicating if the new terminal is the source or target. + * constraint - to be used for this connection. + */ +mxGraph.prototype.cellConnected = function(edge, terminal, source, constraint) +{ + if (edge != null) + { + this.model.beginUpdate(); + try + { + var previous = this.model.getTerminal(edge, source); + + // Updates the constraint + this.setConnectionConstraint(edge, terminal, source, constraint); + + // Checks if the new terminal is a port, uses the ID of the port in the + // style and the parent of the port as the actual terminal of the edge. + if (this.isPortsEnabled()) + { + var id = null; + + if (this.isPort(terminal)) + { + id = terminal.getId(); + terminal = this.getTerminalForPort(terminal, source); + } + + // Sets or resets all previous information for connecting to a child port + var key = (source) ? mxConstants.STYLE_SOURCE_PORT : + mxConstants.STYLE_TARGET_PORT; + this.setCellStyles(key, id, [edge]); + } + + this.model.setTerminal(edge, terminal, source); + + if (this.resetEdgesOnConnect) + { + this.resetEdge(edge); + } + + this.fireEvent(new mxEventObject(mxEvent.CELL_CONNECTED, + 'edge', edge, 'terminal', terminal, 'source', source, + 'previous', previous)); + } + finally + { + this.model.endUpdate(); + } + } +}; + +/** + * Function: disconnectGraph + * + * Disconnects the given edges from the terminals which are not in the + * given array. + * + * Parameters: + * + * cells - Array of to be disconnected. + */ +mxGraph.prototype.disconnectGraph = function(cells) +{ + if (cells != null) + { + this.model.beginUpdate(); + try + { + var scale = this.view.scale; + var tr = this.view.translate; + + // Fast lookup for finding cells in array + var dict = new mxDictionary(); + + for (var i = 0; i < cells.length; i++) + { + dict.put(cells[i], true); + } + + for (var i = 0; i < cells.length; i++) + { + if (this.model.isEdge(cells[i])) + { + var geo = this.model.getGeometry(cells[i]); + + if (geo != null) + { + var state = this.view.getState(cells[i]); + var pstate = this.view.getState( + this.model.getParent(cells[i])); + + if (state != null && + pstate != null) + { + geo = geo.clone(); + + var dx = -pstate.origin.x; + var dy = -pstate.origin.y; + var pts = state.absolutePoints; + + var src = this.model.getTerminal(cells[i], true); + + if (src != null && this.isCellDisconnectable(cells[i], src, true)) + { + while (src != null && !dict.get(src)) + { + src = this.model.getParent(src); + } + + if (src == null) + { + geo.setTerminalPoint( + new mxPoint(pts[0].x / scale - tr.x + dx, + pts[0].y / scale - tr.y + dy), true); + this.model.setTerminal(cells[i], null, true); + } + } + + var trg = this.model.getTerminal(cells[i], false); + + if (trg != null && this.isCellDisconnectable(cells[i], trg, false)) + { + while (trg != null && !dict.get(trg)) + { + trg = this.model.getParent(trg); + } + + if (trg == null) + { + var n = pts.length - 1; + geo.setTerminalPoint( + new mxPoint(pts[n].x / scale - tr.x + dx, + pts[n].y / scale - tr.y + dy), false); + this.model.setTerminal(cells[i], null, false); + } + } + + this.model.setGeometry(cells[i], geo); + } + } + } + } + } + finally + { + this.model.endUpdate(); + } + } +}; + +/** + * Group: Drilldown + */ + +/** + * Function: getCurrentRoot + * + * Returns the current root of the displayed cell hierarchy. This is a + * shortcut to in . + */ +mxGraph.prototype.getCurrentRoot = function() +{ + return this.view.currentRoot; +}; + +/** + * Function: getTranslateForRoot + * + * Returns the translation to be used if the given cell is the root cell as + * an . This implementation returns null. + * + * Example: + * + * To keep the children at their absolute position while stepping into groups, + * this function can be overridden as follows. + * + * (code) + * var offset = new mxPoint(0, 0); + * + * while (cell != null) + * { + * var geo = this.model.getGeometry(cell); + * + * if (geo != null) + * { + * offset.x -= geo.x; + * offset.y -= geo.y; + * } + * + * cell = this.model.getParent(cell); + * } + * + * return offset; + * (end) + * + * Parameters: + * + * cell - that represents the root. + */ +mxGraph.prototype.getTranslateForRoot = function(cell) +{ + return null; +}; + +/** + * Function: isPort + * + * Returns true if the given cell is a "port", that is, when connecting to + * it, the cell returned by getTerminalForPort should be used as the + * terminal and the port should be referenced by the ID in either the + * mxConstants.STYLE_SOURCE_PORT or the or the + * mxConstants.STYLE_TARGET_PORT. Note that a port should not be movable. + * This implementation always returns false. + * + * A typical implementation is the following: + * + * (code) + * graph.isPort = function(cell) + * { + * var geo = this.getCellGeometry(cell); + * + * return (geo != null) ? geo.relative : false; + * }; + * (end) + * + * Parameters: + * + * cell - that represents the port. + */ +mxGraph.prototype.isPort = function(cell) +{ + return false; +}; + +/** + * Function: getTerminalForPort + * + * Returns the terminal to be used for a given port. This implementation + * always returns the parent cell. + * + * Parameters: + * + * cell - that represents the port. + * source - If the cell is the source or target port. + */ +mxGraph.prototype.getTerminalForPort = function(cell, source) +{ + return this.model.getParent(cell); +}; + +/** + * Function: getChildOffsetForCell + * + * Returns the offset to be used for the cells inside the given cell. The + * root and layer cells may be identified using and + * . For all other current roots, the + * field points to the respective cell, so that + * the following holds: cell == this.view.currentRoot. This implementation + * returns null. + * + * Parameters: + * + * cell - whose offset should be returned. + */ +mxGraph.prototype.getChildOffsetForCell = function(cell) +{ + return null; +}; + +/** + * Function: enterGroup + * + * Uses the given cell as the root of the displayed cell hierarchy. If no + * cell is specified then the selection cell is used. The cell is only used + * if returns true. + * + * Parameters: + * + * cell - Optional to be used as the new root. Default is the + * selection cell. + */ +mxGraph.prototype.enterGroup = function(cell) +{ + cell = cell || this.getSelectionCell(); + + if (cell != null && this.isValidRoot(cell)) + { + this.view.setCurrentRoot(cell); + this.clearSelection(); + } +}; + +/** + * Function: exitGroup + * + * Changes the current root to the next valid root in the displayed cell + * hierarchy. + */ +mxGraph.prototype.exitGroup = function() +{ + var root = this.model.getRoot(); + var current = this.getCurrentRoot(); + + if (current != null) + { + var next = this.model.getParent(current); + + // Finds the next valid root in the hierarchy + while (next != root && !this.isValidRoot(next) && + this.model.getParent(next) != root) + { + next = this.model.getParent(next); + } + + // Clears the current root if the new root is + // the model's root or one of the layers. + if (next == root || this.model.getParent(next) == root) + { + this.view.setCurrentRoot(null); + } + else + { + this.view.setCurrentRoot(next); + } + + var state = this.view.getState(current); + + // Selects the previous root in the graph + if (state != null) + { + this.setSelectionCell(current); + } + } +}; + +/** + * Function: home + * + * Uses the root of the model as the root of the displayed cell hierarchy + * and selects the previous root. + */ +mxGraph.prototype.home = function() +{ + var current = this.getCurrentRoot(); + + if (current != null) + { + this.view.setCurrentRoot(null); + var state = this.view.getState(current); + + if (state != null) + { + this.setSelectionCell(current); + } + } +}; + +/** + * Function: isValidRoot + * + * Returns true if the given cell is a valid root for the cell display + * hierarchy. This implementation returns true for all non-null values. + * + * Parameters: + * + * cell - which should be checked as a possible root. + */ +mxGraph.prototype.isValidRoot = function(cell) +{ + return (cell != null); +}; + +/** + * Group: Graph display + */ + +/** + * Function: getGraphBounds + * + * Returns the bounds of the visible graph. Shortcut to + * . See also: . + */ + mxGraph.prototype.getGraphBounds = function() + { + return this.view.getGraphBounds(); + }; + +/** + * Function: getCellBounds + * + * Returns the scaled, translated bounds for the given cell. See + * for arrays. + * + * Parameters: + * + * cell - whose bounds should be returned. + * includeEdge - Optional boolean that specifies if the bounds of + * the connected edges should be included. Default is false. + * includeDescendants - Optional boolean that specifies if the bounds + * of all descendants should be included. Default is false. + */ +mxGraph.prototype.getCellBounds = function(cell, includeEdges, includeDescendants) +{ + var cells = [cell]; + + // Includes all connected edges + if (includeEdges) + { + cells = cells.concat(this.model.getEdges(cell)); + } + + var result = this.view.getBounds(cells); + + // Recursively includes the bounds of the children + if (includeDescendants) + { + var childCount = this.model.getChildCount(cell); + + for (var i = 0; i < childCount; i++) + { + var tmp = this.getCellBounds(this.model.getChildAt(cell, i), + includeEdges, true); + + if (result != null) + { + result.add(tmp); + } + else + { + result = tmp; + } + } + } + + return result; +}; + +/** + * Function: getBoundingBoxFromGeometry + * + * Returns the bounding box for the geometries of the vertices in the + * given array of cells. This can be used to find the graph bounds during + * a layout operation (ie. before the last endUpdate) as follows: + * + * (code) + * var cells = graph.getChildCells(graph.getDefaultParent(), true, true); + * var bounds = graph.getBoundingBoxFromGeometry(cells, true); + * (end) + * + * This can then be used to move cells to the origin: + * + * (code) + * if (bounds.x < 0 || bounds.y < 0) + * { + * graph.moveCells(cells, -Math.min(bounds.x, 0), -Math.min(bounds.y, 0)) + * } + * (end) + * + * Or to translate the graph view: + * + * (code) + * if (bounds.x < 0 || bounds.y < 0) + * { + * graph.view.setTranslate(-Math.min(bounds.x, 0), -Math.min(bounds.y, 0)); + * } + * (end) + * + * Parameters: + * + * cells - Array of whose bounds should be returned. + * includeEdges - Specifies if edge bounds should be included by computing + * the bounding box for all points in geometry. Default is false. + */ +mxGraph.prototype.getBoundingBoxFromGeometry = function(cells, includeEdges) +{ + includeEdges = (includeEdges != null) ? includeEdges : false; + var result = null; + + if (cells != null) + { + for (var i = 0; i < cells.length; i++) + { + if (includeEdges || this.model.isVertex(cells[i])) + { + // Computes the bounding box for the points in the geometry + var geo = this.getCellGeometry(cells[i]); + + if (geo != null) + { + var bbox = null; + + if (this.model.isEdge(cells[i])) + { + var pts = geo.points; + + if (pts != null && pts.length > 0) + { + var tmp = new mxRectangle(pts[0].x, pts[0].y, 0, 0); + + var addPoint = function(pt) + { + if (pt != null) + { + tmp.add(new mxRectangle(pt.x, pt.y, 0, 0)); + } + }; + + for (var j = 1; j < pts.length; j++) + { + addPoint(pts[j]); + } + + addPoint(geo.getTerminalPoint(true)); + addPoint(geo.getTerminalPoint(false)); + + bbox = tmp; + } + } + else + { + var parent = this.model.getParent(cells[i]); + + if (geo.relative) + { + if (this.model.isVertex(parent) && parent != this.view.currentRoot) + { + var tmp = this.getBoundingBoxFromGeometry([parent], false); + + if (tmp != null) + { + bbox = new mxRectangle(geo.x * tmp.width, geo.y * tmp.height, geo.width, geo.height); + + if (mxUtils.indexOf(cells, parent) >= 0) + { + bbox.x += tmp.x; + bbox.y += tmp.y; + } + } + } + } + else + { + bbox = mxRectangle.fromRectangle(geo); + + if (this.model.isVertex(parent) && mxUtils.indexOf(cells, parent) >= 0) + { + var tmp = this.getBoundingBoxFromGeometry([parent], false); + + if (tmp != null) + { + bbox.x += tmp.x; + bbox.y += tmp.y; + } + } + } + + if (bbox != null && geo.offset != null) + { + bbox.x += geo.offset.x; + bbox.y += geo.offset.y; + } + } + + if (bbox != null) + { + if (result == null) + { + result = mxRectangle.fromRectangle(bbox); + } + else + { + result.add(bbox); + } + } + } + } + } + } + + return result; +}; + +/** + * Function: refresh + * + * Clears all cell states or the states for the hierarchy starting at the + * given cell and validates the graph. This fires a refresh event as the + * last step. + * + * Parameters: + * + * cell - Optional for which the cell states should be cleared. + */ +mxGraph.prototype.refresh = function(cell) +{ + this.view.clear(cell, cell == null); + this.view.validate(); + this.sizeDidChange(); + this.fireEvent(new mxEventObject(mxEvent.REFRESH)); +}; + +/** + * Function: snap + * + * Snaps the given numeric value to the grid if is true. + * + * Parameters: + * + * value - Numeric value to be snapped to the grid. + */ +mxGraph.prototype.snap = function(value) +{ + if (this.gridEnabled) + { + value = Math.round(value / this.gridSize ) * this.gridSize; + } + + return value; +}; + +/** + * Function: panGraph + * + * Shifts the graph display by the given amount. This is used to preview + * panning operations, use to set a persistent + * translation of the view. Fires . + * + * Parameters: + * + * dx - Amount to shift the graph along the x-axis. + * dy - Amount to shift the graph along the y-axis. + */ +mxGraph.prototype.panGraph = function(dx, dy) +{ + if (this.useScrollbarsForPanning && mxUtils.hasScrollbars(this.container)) + { + this.container.scrollLeft = -dx; + this.container.scrollTop = -dy; + } + else + { + var canvas = this.view.getCanvas(); + + if (this.dialect == mxConstants.DIALECT_SVG) + { + // Puts everything inside the container in a DIV so that it + // can be moved without changing the state of the container + if (dx == 0 && dy == 0) + { + // Workaround for ignored removeAttribute on SVG element in IE9 standards + if (mxClient.IS_IE) + { + canvas.setAttribute('transform', 'translate(' + dx + ',' + dy + ')'); + } + else + { + canvas.removeAttribute('transform'); + } + + if (this.shiftPreview1 != null) + { + var child = this.shiftPreview1.firstChild; + + while (child != null) + { + var next = child.nextSibling; + this.container.appendChild(child); + child = next; + } + + if (this.shiftPreview1.parentNode != null) + { + this.shiftPreview1.parentNode.removeChild(this.shiftPreview1); + } + + this.shiftPreview1 = null; + + this.container.appendChild(canvas.parentNode); + + child = this.shiftPreview2.firstChild; + + while (child != null) + { + var next = child.nextSibling; + this.container.appendChild(child); + child = next; + } + + if (this.shiftPreview2.parentNode != null) + { + this.shiftPreview2.parentNode.removeChild(this.shiftPreview2); + } + + this.shiftPreview2 = null; + } + } + else + { + canvas.setAttribute('transform', 'translate(' + dx + ',' + dy + ')'); + + if (this.shiftPreview1 == null) + { + // Needs two divs for stuff before and after the SVG element + this.shiftPreview1 = document.createElement('div'); + this.shiftPreview1.style.position = 'absolute'; + this.shiftPreview1.style.overflow = 'visible'; + + this.shiftPreview2 = document.createElement('div'); + this.shiftPreview2.style.position = 'absolute'; + this.shiftPreview2.style.overflow = 'visible'; + + var current = this.shiftPreview1; + var child = this.container.firstChild; + + while (child != null) + { + var next = child.nextSibling; + + // SVG element is moved via transform attribute + if (child != canvas.parentNode) + { + current.appendChild(child); + } + else + { + current = this.shiftPreview2; + } + + child = next; + } + + // Inserts elements only if not empty + if (this.shiftPreview1.firstChild != null) + { + this.container.insertBefore(this.shiftPreview1, canvas.parentNode); + } + + if (this.shiftPreview2.firstChild != null) + { + this.container.appendChild(this.shiftPreview2); + } + } + + this.shiftPreview1.style.left = dx + 'px'; + this.shiftPreview1.style.top = dy + 'px'; + this.shiftPreview2.style.left = dx + 'px'; + this.shiftPreview2.style.top = dy + 'px'; + } + } + else + { + canvas.style.left = dx + 'px'; + canvas.style.top = dy + 'px'; + } + + this.panDx = dx; + this.panDy = dy; + + this.fireEvent(new mxEventObject(mxEvent.PAN)); + } +}; + +/** + * Function: zoomIn + * + * Zooms into the graph by . + */ +mxGraph.prototype.zoomIn = function() +{ + this.zoom(this.zoomFactor); +}; + +/** + * Function: zoomOut + * + * Zooms out of the graph by . + */ +mxGraph.prototype.zoomOut = function() +{ + this.zoom(1 / this.zoomFactor); +}; + +/** + * Function: zoomActual + * + * Resets the zoom and panning in the view. + */ +mxGraph.prototype.zoomActual = function() +{ + if (this.view.scale == 1) + { + this.view.setTranslate(0, 0); + } + else + { + this.view.translate.x = 0; + this.view.translate.y = 0; + + this.view.setScale(1); + } +}; + +/** + * Function: zoomTo + * + * Zooms the graph to the given scale with an optional boolean center + * argument, which is passd to . + */ +mxGraph.prototype.zoomTo = function(scale, center) +{ + this.zoom(scale / this.view.scale, center); +}; + +/** + * Function: center + * + * Centers the graph in the container. + * + * Parameters: + * + * horizontal - Optional boolean that specifies if the graph should be centered + * horizontally. Default is true. + * vertical - Optional boolean that specifies if the graph should be centered + * vertically. Default is true. + * cx - Optional float that specifies the horizontal center. Default is 0.5. + * cy - Optional float that specifies the vertical center. Default is 0.5. + */ +mxGraph.prototype.center = function(horizontal, vertical, cx, cy) +{ + horizontal = (horizontal != null) ? horizontal : true; + vertical = (vertical != null) ? vertical : true; + cx = (cx != null) ? cx : 0.5; + cy = (cy != null) ? cy : 0.5; + + var hasScrollbars = mxUtils.hasScrollbars(this.container); + var cw = this.container.clientWidth; + var ch = this.container.clientHeight; + var bounds = this.getGraphBounds(); + + var t = this.view.translate; + var s = this.view.scale; + + var dx = (horizontal) ? cw - bounds.width : 0; + var dy = (vertical) ? ch - bounds.height : 0; + + if (!hasScrollbars) + { + this.view.setTranslate((horizontal) ? Math.floor(t.x - bounds.x * s + dx * cx / s) : t.x, + (vertical) ? Math.floor(t.y - bounds.y * s + dy * cy / s) : t.y); + } + else + { + bounds.x -= t.x; + bounds.y -= t.y; + + var sw = this.container.scrollWidth; + var sh = this.container.scrollHeight; + + if (sw > cw) + { + dx = 0; + } + + if (sh > ch) + { + dy = 0; + } + + this.view.setTranslate(Math.floor(dx / 2 - bounds.x), Math.floor(dy / 2 - bounds.y)); + this.container.scrollLeft = (sw - cw) / 2; + this.container.scrollTop = (sh - ch) / 2; + } +}; + +/** + * Function: zoom + * + * Zooms the graph using the given factor. Center is an optional boolean + * argument that keeps the graph scrolled to the center. If the center argument + * is omitted, then will be used as its value. + */ +mxGraph.prototype.zoom = function(factor, center) +{ + center = (center != null) ? center : this.centerZoom; + var scale = Math.round(this.view.scale * factor * 100) / 100; + var state = this.view.getState(this.getSelectionCell()); + factor = scale / this.view.scale; + + if (this.keepSelectionVisibleOnZoom && state != null) + { + var rect = new mxRectangle(state.x * factor, state.y * factor, + state.width * factor, state.height * factor); + + // Refreshes the display only once if a scroll is carried out + this.view.scale = scale; + + if (!this.scrollRectToVisible(rect)) + { + this.view.revalidate(); + + // Forces an event to be fired but does not revalidate again + this.view.setScale(scale); + } + } + else + { + var hasScrollbars = mxUtils.hasScrollbars(this.container); + + if (center && !hasScrollbars) + { + var dx = this.container.offsetWidth; + var dy = this.container.offsetHeight; + + if (factor > 1) + { + var f = (factor - 1) / (scale * 2); + dx *= -f; + dy *= -f; + } + else + { + var f = (1 / factor - 1) / (this.view.scale * 2); + dx *= f; + dy *= f; + } + + this.view.scaleAndTranslate(scale, + this.view.translate.x + dx, + this.view.translate.y + dy); + } + else + { + // Allows for changes of translate and scrollbars during setscale + var tx = this.view.translate.x; + var ty = this.view.translate.y; + var sl = this.container.scrollLeft; + var st = this.container.scrollTop; + + this.view.setScale(scale); + + if (hasScrollbars) + { + var dx = 0; + var dy = 0; + + if (center) + { + dx = this.container.offsetWidth * (factor - 1) / 2; + dy = this.container.offsetHeight * (factor - 1) / 2; + } + + this.container.scrollLeft = (this.view.translate.x - tx) * this.view.scale + Math.round(sl * factor + dx); + this.container.scrollTop = (this.view.translate.y - ty) * this.view.scale + Math.round(st * factor + dy); + } + } + } +}; + +/** + * Function: zoomToRect + * + * Zooms the graph to the specified rectangle. If the rectangle does not have same aspect + * ratio as the display container, it is increased in the smaller relative dimension only + * until the aspect match. The original rectangle is centralised within this expanded one. + * + * Note that the input rectangular must be un-scaled and un-translated. + * + * Parameters: + * + * rect - The un-scaled and un-translated rectangluar region that should be just visible + * after the operation + */ +mxGraph.prototype.zoomToRect = function(rect) +{ + var scaleX = this.container.clientWidth / rect.width; + var scaleY = this.container.clientHeight / rect.height; + var aspectFactor = scaleX / scaleY; + + // Remove any overlap of the rect outside the client area + rect.x = Math.max(0, rect.x); + rect.y = Math.max(0, rect.y); + var rectRight = Math.min(this.container.scrollWidth, rect.x + rect.width); + var rectBottom = Math.min(this.container.scrollHeight, rect.y + rect.height); + rect.width = rectRight - rect.x; + rect.height = rectBottom - rect.y; + + // The selection area has to be increased to the same aspect + // ratio as the container, centred around the centre point of the + // original rect passed in. + if (aspectFactor < 1.0) + { + // Height needs increasing + var newHeight = rect.height / aspectFactor; + var deltaHeightBuffer = (newHeight - rect.height) / 2.0; + rect.height = newHeight; + + // Assign up to half the buffer to the upper part of the rect, not crossing 0 + // put the rest on the bottom + var upperBuffer = Math.min(rect.y , deltaHeightBuffer); + rect.y = rect.y - upperBuffer; + + // Check if the bottom has extended too far + rectBottom = Math.min(this.container.scrollHeight, rect.y + rect.height); + rect.height = rectBottom - rect.y; + } + else + { + // Width needs increasing + var newWidth = rect.width * aspectFactor; + var deltaWidthBuffer = (newWidth - rect.width) / 2.0; + rect.width = newWidth; + + // Assign up to half the buffer to the upper part of the rect, not crossing 0 + // put the rest on the bottom + var leftBuffer = Math.min(rect.x , deltaWidthBuffer); + rect.x = rect.x - leftBuffer; + + // Check if the right hand side has extended too far + rectRight = Math.min(this.container.scrollWidth, rect.x + rect.width); + rect.width = rectRight - rect.x; + } + + var scale = this.container.clientWidth / rect.width; + var newScale = this.view.scale * scale; + + if (!mxUtils.hasScrollbars(this.container)) + { + this.view.scaleAndTranslate(newScale, (this.view.translate.x - rect.x / this.view.scale), (this.view.translate.y - rect.y / this.view.scale)); + } + else + { + this.view.setScale(newScale); + this.container.scrollLeft = Math.round(rect.x * scale); + this.container.scrollTop = Math.round(rect.y * scale); + } +}; + +/** + * Function: scrollCellToVisible + * + * Pans the graph so that it shows the given cell. Optionally the cell may + * be centered in the container. + * + * To center a given graph if the has no scrollbars, use the following code. + * + * [code] + * var bounds = graph.getGraphBounds(); + * graph.view.setTranslate(-bounds.x - (bounds.width - container.clientWidth) / 2, + * -bounds.y - (bounds.height - container.clientHeight) / 2); + * [/code] + * + * Parameters: + * + * cell - to be made visible. + * center - Optional boolean flag. Default is false. + */ +mxGraph.prototype.scrollCellToVisible = function(cell, center) +{ + var x = -this.view.translate.x; + var y = -this.view.translate.y; + + var state = this.view.getState(cell); + + if (state != null) + { + var bounds = new mxRectangle(x + state.x, y + state.y, state.width, + state.height); + + if (center && this.container != null) + { + var w = this.container.clientWidth; + var h = this.container.clientHeight; + + bounds.x = bounds.getCenterX() - w / 2; + bounds.width = w; + bounds.y = bounds.getCenterY() - h / 2; + bounds.height = h; + } + + var tr = new mxPoint(this.view.translate.x, this.view.translate.y); + + if (this.scrollRectToVisible(bounds)) + { + // Triggers an update via the view's event source + var tr2 = new mxPoint(this.view.translate.x, this.view.translate.y); + this.view.translate.x = tr.x; + this.view.translate.y = tr.y; + this.view.setTranslate(tr2.x, tr2.y); + } + } +}; + +/** + * Function: scrollRectToVisible + * + * Pans the graph so that it shows the given rectangle. + * + * Parameters: + * + * rect - to be made visible. + */ +mxGraph.prototype.scrollRectToVisible = function(rect) +{ + var isChanged = false; + + if (rect != null) + { + var w = this.container.offsetWidth; + var h = this.container.offsetHeight; + + var widthLimit = Math.min(w, rect.width); + var heightLimit = Math.min(h, rect.height); + + if (mxUtils.hasScrollbars(this.container)) + { + var c = this.container; + rect.x += this.view.translate.x; + rect.y += this.view.translate.y; + var dx = c.scrollLeft - rect.x; + var ddx = Math.max(dx - c.scrollLeft, 0); + + if (dx > 0) + { + c.scrollLeft -= dx + 2; + } + else + { + dx = rect.x + widthLimit - c.scrollLeft - c.clientWidth; + + if (dx > 0) + { + c.scrollLeft += dx + 2; + } + } + + var dy = c.scrollTop - rect.y; + var ddy = Math.max(0, dy - c.scrollTop); + + if (dy > 0) + { + c.scrollTop -= dy + 2; + } + else + { + dy = rect.y + heightLimit - c.scrollTop - c.clientHeight; + + if (dy > 0) + { + c.scrollTop += dy + 2; + } + } + + if (!this.useScrollbarsForPanning && (ddx != 0 || ddy != 0)) + { + this.view.setTranslate(ddx, ddy); + } + } + else + { + var x = -this.view.translate.x; + var y = -this.view.translate.y; + + var s = this.view.scale; + + if (rect.x + widthLimit > x + w) + { + this.view.translate.x -= (rect.x + widthLimit - w - x) / s; + isChanged = true; + } + + if (rect.y + heightLimit > y + h) + { + this.view.translate.y -= (rect.y + heightLimit - h - y) / s; + isChanged = true; + } + + if (rect.x < x) + { + this.view.translate.x += (x - rect.x) / s; + isChanged = true; + } + + if (rect.y < y) + { + this.view.translate.y += (y - rect.y) / s; + isChanged = true; + } + + if (isChanged) + { + this.view.refresh(); + + // Repaints selection marker (ticket 18) + if (this.selectionCellsHandler != null) + { + this.selectionCellsHandler.refresh(); + } + } + } + } + + return isChanged; +}; + +/** + * Function: getCellGeometry + * + * Returns the for the given cell. This implementation uses + * . Subclasses can override this to implement + * specific geometries for cells in only one graph, that is, it can return + * geometries that depend on the current state of the view. + * + * Parameters: + * + * cell - whose geometry should be returned. + */ +mxGraph.prototype.getCellGeometry = function(cell) +{ + return this.model.getGeometry(cell); +}; + +/** + * Function: isCellVisible + * + * Returns true if the given cell is visible in this graph. This + * implementation uses . Subclassers can override + * this to implement specific visibility for cells in only one graph, that + * is, without affecting the visible state of the cell. + * + * When using dynamic filter expressions for cell visibility, then the + * graph should be revalidated after the filter expression has changed. + * + * Parameters: + * + * cell - whose visible state should be returned. + */ +mxGraph.prototype.isCellVisible = function(cell) +{ + return this.model.isVisible(cell); +}; + +/** + * Function: isCellCollapsed + * + * Returns true if the given cell is collapsed in this graph. This + * implementation uses . Subclassers can override + * this to implement specific collapsed states for cells in only one graph, + * that is, without affecting the collapsed state of the cell. + * + * When using dynamic filter expressions for the collapsed state, then the + * graph should be revalidated after the filter expression has changed. + * + * Parameters: + * + * cell - whose collapsed state should be returned. + */ +mxGraph.prototype.isCellCollapsed = function(cell) +{ + return this.model.isCollapsed(cell); +}; + +/** + * Function: isCellConnectable + * + * Returns true if the given cell is connectable in this graph. This + * implementation uses . Subclassers can override + * this to implement specific connectable states for cells in only one graph, + * that is, without affecting the connectable state of the cell in the model. + * + * Parameters: + * + * cell - whose connectable state should be returned. + */ +mxGraph.prototype.isCellConnectable = function(cell) +{ + return this.model.isConnectable(cell); +}; + +/** + * Function: isOrthogonal + * + * Returns true if perimeter points should be computed such that the + * resulting edge has only horizontal or vertical segments. + * + * Parameters: + * + * edge - that represents the edge. + */ +mxGraph.prototype.isOrthogonal = function(edge) +{ + var orthogonal = edge.style[mxConstants.STYLE_ORTHOGONAL]; + + if (orthogonal != null) + { + return orthogonal; + } + + var tmp = this.view.getEdgeStyle(edge); + + return tmp == mxEdgeStyle.SegmentConnector || + tmp == mxEdgeStyle.ElbowConnector || + tmp == mxEdgeStyle.SideToSide || + tmp == mxEdgeStyle.TopToBottom || + tmp == mxEdgeStyle.EntityRelation || + tmp == mxEdgeStyle.OrthConnector; +}; + +/** + * Function: isLoop + * + * Returns true if the given cell state is a loop. + * + * Parameters: + * + * state - that represents a potential loop. + */ +mxGraph.prototype.isLoop = function(state) +{ + var src = state.getVisibleTerminalState(true); + var trg = state.getVisibleTerminalState(false); + + return (src != null && src == trg); +}; + +/** + * Function: isCloneEvent + * + * Returns true if the given event is a clone event. This implementation + * returns true if control is pressed. + */ +mxGraph.prototype.isCloneEvent = function(evt) +{ + return mxEvent.isControlDown(evt); +}; + +/** + * Function: isToggleEvent + * + * Returns true if the given event is a toggle event. This implementation + * returns true if the meta key (Cmd) is pressed on Macs or if control is + * pressed on any other platform. + */ +mxGraph.prototype.isToggleEvent = function(evt) +{ + return (mxClient.IS_MAC) ? mxEvent.isMetaDown(evt) : mxEvent.isControlDown(evt); +}; + +/** + * Function: isGridEnabledEvent + * + * Returns true if the given mouse event should be aligned to the grid. + */ +mxGraph.prototype.isGridEnabledEvent = function(evt) +{ + return evt != null && !mxEvent.isAltDown(evt); +}; + +/** + * Function: isConstrainedEvent + * + * Returns true if the given mouse event should be aligned to the grid. + */ +mxGraph.prototype.isConstrainedEvent = function(evt) +{ + return mxEvent.isShiftDown(evt); +}; + +/** + * Function: isIgnoreTerminalEvent + * + * Returns true if the given mouse event should not allow any connections to be + * made. This implementation returns false. + */ +mxGraph.prototype.isIgnoreTerminalEvent = function(evt) +{ + return false; +}; + +/** + * Group: Validation + */ + +/** + * Function: validationAlert + * + * Displays the given validation error in a dialog. This implementation uses + * mxUtils.alert. + */ +mxGraph.prototype.validationAlert = function(message) +{ + mxUtils.alert(message); +}; + +/** + * Function: isEdgeValid + * + * Checks if the return value of for the given + * arguments is null. + * + * Parameters: + * + * edge - that represents the edge to validate. + * source - that represents the source terminal. + * target - that represents the target terminal. + */ +mxGraph.prototype.isEdgeValid = function(edge, source, target) +{ + return this.getEdgeValidationError(edge, source, target) == null; +}; + +/** + * Function: getEdgeValidationError + * + * Returns the validation error message to be displayed when inserting or + * changing an edges' connectivity. A return value of null means the edge + * is valid, a return value of '' means it's not valid, but do not display + * an error message. Any other (non-empty) string returned from this method + * is displayed as an error message when trying to connect an edge to a + * source and target. This implementation uses the , and + * checks , and to generate + * validation errors. + * + * For extending this method with specific checks for source/target cells, + * the method can be extended as follows. Returning an empty string means + * the edge is invalid with no error message, a non-null string specifies + * the error message, and null means the edge is valid. + * + * (code) + * graph.getEdgeValidationError = function(edge, source, target) + * { + * if (source != null && target != null && + * this.model.getValue(source) != null && + * this.model.getValue(target) != null) + * { + * if (target is not valid for source) + * { + * return 'Invalid Target'; + * } + * } + * + * // "Supercall" + * return mxGraph.prototype.getEdgeValidationError.apply(this, arguments); + * } + * (end) + * + * Parameters: + * + * edge - that represents the edge to validate. + * source - that represents the source terminal. + * target - that represents the target terminal. + */ +mxGraph.prototype.getEdgeValidationError = function(edge, source, target) +{ + if (edge != null && !this.isAllowDanglingEdges() && (source == null || target == null)) + { + return ''; + } + + if (edge != null && this.model.getTerminal(edge, true) == null && + this.model.getTerminal(edge, false) == null) + { + return null; + } + + // Checks if we're dealing with a loop + if (!this.allowLoops && source == target && source != null) + { + return ''; + } + + // Checks if the connection is generally allowed + if (!this.isValidConnection(source, target)) + { + return ''; + } + + if (source != null && target != null) + { + var error = ''; + + // Checks if the cells are already connected + // and adds an error message if required + if (!this.multigraph) + { + var tmp = this.model.getEdgesBetween(source, target, true); + + // Checks if the source and target are not connected by another edge + if (tmp.length > 1 || (tmp.length == 1 && tmp[0] != edge)) + { + error += (mxResources.get(this.alreadyConnectedResource) || + this.alreadyConnectedResource)+'\n'; + } + } + + // Gets the number of outgoing edges from the source + // and the number of incoming edges from the target + // without counting the edge being currently changed. + var sourceOut = this.model.getDirectedEdgeCount(source, true, edge); + var targetIn = this.model.getDirectedEdgeCount(target, false, edge); + + // Checks the change against each multiplicity rule + if (this.multiplicities != null) + { + for (var i = 0; i < this.multiplicities.length; i++) + { + var err = this.multiplicities[i].check(this, edge, source, + target, sourceOut, targetIn); + + if (err != null) + { + error += err; + } + } + } + + // Validates the source and target terminals independently + var err = this.validateEdge(edge, source, target); + + if (err != null) + { + error += err; + } + + return (error.length > 0) ? error : null; + } + + return (this.allowDanglingEdges) ? null : ''; +}; + +/** + * Function: validateEdge + * + * Hook method for subclassers to return an error message for the given + * edge and terminals. This implementation returns null. + * + * Parameters: + * + * edge - that represents the edge to validate. + * source - that represents the source terminal. + * target - that represents the target terminal. + */ +mxGraph.prototype.validateEdge = function(edge, source, target) +{ + return null; +}; + +/** + * Function: validateGraph + * + * Validates the graph by validating each descendant of the given cell or + * the root of the model. Context is an object that contains the validation + * state for the complete validation run. The validation errors are + * attached to their cells using . Returns null in the case of + * successful validation or an array of strings (warnings) in the case of + * failed validations. + * + * Paramters: + * + * cell - Optional to start the validation recursion. Default is + * the graph root. + * context - Object that represents the global validation state. + */ +mxGraph.prototype.validateGraph = function(cell, context) +{ + cell = (cell != null) ? cell : this.model.getRoot(); + context = (context != null) ? context : new Object(); + + var isValid = true; + var childCount = this.model.getChildCount(cell); + + for (var i = 0; i < childCount; i++) + { + var tmp = this.model.getChildAt(cell, i); + var ctx = context; + + if (this.isValidRoot(tmp)) + { + ctx = new Object(); + } + + var warn = this.validateGraph(tmp, ctx); + + if (warn != null) + { + this.setCellWarning(tmp, warn.replace(/\n/g, '
')); + } + else + { + this.setCellWarning(tmp, null); + } + + isValid = isValid && warn == null; + } + + var warning = ''; + + // Adds error for invalid children if collapsed (children invisible) + if (this.isCellCollapsed(cell) && !isValid) + { + warning += (mxResources.get(this.containsValidationErrorsResource) || + this.containsValidationErrorsResource) + '\n'; + } + + // Checks edges and cells using the defined multiplicities + if (this.model.isEdge(cell)) + { + warning += this.getEdgeValidationError(cell, + this.model.getTerminal(cell, true), + this.model.getTerminal(cell, false)) || ''; + } + else + { + warning += this.getCellValidationError(cell) || ''; + } + + // Checks custom validation rules + var err = this.validateCell(cell, context); + + if (err != null) + { + warning += err; + } + + // Updates the display with the warning icons + // before any potential alerts are displayed. + // LATER: Move this into addCellOverlay. Redraw + // should check if overlay was added or removed. + if (this.model.getParent(cell) == null) + { + this.view.validate(); + } + + return (warning.length > 0 || !isValid) ? warning : null; +}; + +/** + * Function: getCellValidationError + * + * Checks all that cannot be enforced while the graph is + * being modified, namely, all multiplicities that require a minimum of + * 1 edge. + * + * Parameters: + * + * cell - for which the multiplicities should be checked. + */ +mxGraph.prototype.getCellValidationError = function(cell) +{ + var outCount = this.model.getDirectedEdgeCount(cell, true); + var inCount = this.model.getDirectedEdgeCount(cell, false); + var value = this.model.getValue(cell); + var error = ''; + + if (this.multiplicities != null) + { + for (var i = 0; i < this.multiplicities.length; i++) + { + var rule = this.multiplicities[i]; + + if (rule.source && mxUtils.isNode(value, rule.type, + rule.attr, rule.value) && ((rule.max == 0 && outCount > 0) || + (rule.min == 1 && outCount == 0) || (rule.max == 1 && outCount > 1))) + { + error += rule.countError + '\n'; + } + else if (!rule.source && mxUtils.isNode(value, rule.type, + rule.attr, rule.value) && ((rule.max == 0 && inCount > 0) || + (rule.min == 1 && inCount == 0) || (rule.max == 1 && inCount > 1))) + { + error += rule.countError + '\n'; + } + } + } + + return (error.length > 0) ? error : null; +}; + +/** + * Function: validateCell + * + * Hook method for subclassers to return an error message for the given + * cell and validation context. This implementation returns null. Any HTML + * breaks will be converted to linefeeds in the calling method. + * + * Parameters: + * + * cell - that represents the cell to validate. + * context - Object that represents the global validation state. + */ +mxGraph.prototype.validateCell = function(cell, context) +{ + return null; +}; + +/** + * Group: Graph appearance + */ + +/** + * Function: getBackgroundImage + * + * Returns the as an . + */ +mxGraph.prototype.getBackgroundImage = function() +{ + return this.backgroundImage; +}; + +/** + * Function: setBackgroundImage + * + * Sets the new . + * + * Parameters: + * + * image - New to be used for the background. + */ +mxGraph.prototype.setBackgroundImage = function(image) +{ + this.backgroundImage = image; +}; + +/** + * Function: getFoldingImage + * + * Returns the used to display the collapsed state of + * the specified cell state. This returns null for all edges. + */ +mxGraph.prototype.getFoldingImage = function(state) +{ + if (state != null && this.foldingEnabled && !this.getModel().isEdge(state.cell)) + { + var tmp = this.isCellCollapsed(state.cell); + + if (this.isCellFoldable(state.cell, !tmp)) + { + return (tmp) ? this.collapsedImage : this.expandedImage; + } + } + + return null; +}; + +/** + * Function: convertValueToString + * + * Returns the textual representation for the given cell. This + * implementation returns the nodename or string-representation of the user + * object. + * + * Example: + * + * The following returns the label attribute from the cells user + * object if it is an XML node. + * + * (code) + * graph.convertValueToString = function(cell) + * { + * return cell.getAttribute('label'); + * } + * (end) + * + * See also: . + * + * Parameters: + * + * cell - whose textual representation should be returned. + */ +mxGraph.prototype.convertValueToString = function(cell) +{ + var value = this.model.getValue(cell); + + if (value != null) + { + if (mxUtils.isNode(value)) + { + return value.nodeName; + } + else if (typeof(value.toString) == 'function') + { + return value.toString(); + } + } + + return ''; +}; + +/** + * Function: getLabel + * + * Returns a string or DOM node that represents the label for the given + * cell. This implementation uses if + * is true. Otherwise it returns an empty string. + * + * To truncate a label to match the size of the cell, the following code + * can be used. + * + * (code) + * graph.getLabel = function(cell) + * { + * var label = mxGraph.prototype.getLabel.apply(this, arguments); + * + * if (label != null && this.model.isVertex(cell)) + * { + * var geo = this.getCellGeometry(cell); + * + * if (geo != null) + * { + * var max = parseInt(geo.width / 8); + * + * if (label.length > max) + * { + * label = label.substring(0, max)+'...'; + * } + * } + * } + * return mxUtils.htmlEntities(label); + * } + * (end) + * + * A resize listener is needed in the graph to force a repaint of the label + * after a resize. + * + * (code) + * graph.addListener(mxEvent.RESIZE_CELLS, function(sender, evt) + * { + * var cells = evt.getProperty('cells'); + * + * for (var i = 0; i < cells.length; i++) + * { + * this.view.removeState(cells[i]); + * } + * }); + * (end) + * + * Parameters: + * + * cell - whose label should be returned. + */ +mxGraph.prototype.getLabel = function(cell) +{ + var result = ''; + + if (this.labelsVisible && cell != null) + { + var state = this.view.getState(cell); + var style = (state != null) ? state.style : this.getCellStyle(cell); + + if (!mxUtils.getValue(style, mxConstants.STYLE_NOLABEL, false)) + { + result = this.convertValueToString(cell); + } + } + + return result; +}; + +/** + * Function: isHtmlLabel + * + * Returns true if the label must be rendered as HTML markup. The default + * implementation returns . + * + * Parameters: + * + * cell - whose label should be displayed as HTML markup. + */ +mxGraph.prototype.isHtmlLabel = function(cell) +{ + return this.isHtmlLabels(); +}; + +/** + * Function: isHtmlLabels + * + * Returns . + */ +mxGraph.prototype.isHtmlLabels = function() +{ + return this.htmlLabels; +}; + +/** + * Function: setHtmlLabels + * + * Sets . + */ +mxGraph.prototype.setHtmlLabels = function(value) +{ + this.htmlLabels = value; +}; + +/** + * Function: isWrapping + * + * This enables wrapping for HTML labels. + * + * Returns true if no white-space CSS style directive should be used for + * displaying the given cells label. This implementation returns true if + * in the style of the given cell is 'wrap'. + * + * This is used as a workaround for IE ignoring the white-space directive + * of child elements if the directive appears in a parent element. It + * should be overridden to return true if a white-space directive is used + * in the HTML markup that represents the given cells label. In order for + * HTML markup to work in labels, must also return true + * for the given cell. + * + * Example: + * + * (code) + * graph.getLabel = function(cell) + * { + * var tmp = mxGraph.prototype.getLabel.apply(this, arguments); // "supercall" + * + * if (this.model.isEdge(cell)) + * { + * tmp = '
'+tmp+'
'; + * } + * + * return tmp; + * } + * + * graph.isWrapping = function(state) + * { + * return this.model.isEdge(state.cell); + * } + * (end) + * + * Makes sure no edge label is wider than 150 pixels, otherwise the content + * is wrapped. Note: No width must be specified for wrapped vertex labels as + * the vertex defines the width in its geometry. + * + * Parameters: + * + * state - whose label should be wrapped. + */ +mxGraph.prototype.isWrapping = function(cell) +{ + var state = this.view.getState(cell); + var style = (state != null) ? state.style : this.getCellStyle(cell); + + return (style != null) ? style[mxConstants.STYLE_WHITE_SPACE] == 'wrap' : false; +}; + +/** + * Function: isLabelClipped + * + * Returns true if the overflow portion of labels should be hidden. If this + * returns true then vertex labels will be clipped to the size of the vertices. + * This implementation returns true if in the + * style of the given cell is 'hidden'. + * + * Parameters: + * + * state - whose label should be clipped. + */ +mxGraph.prototype.isLabelClipped = function(cell) +{ + var state = this.view.getState(cell); + var style = (state != null) ? state.style : this.getCellStyle(cell); + + return (style != null) ? style[mxConstants.STYLE_OVERFLOW] == 'hidden' : false; +}; + +/** + * Function: getTooltip + * + * Returns the string or DOM node that represents the tooltip for the given + * state, node and coordinate pair. This implementation checks if the given + * node is a folding icon or overlay and returns the respective tooltip. If + * this does not result in a tooltip, the handler for the cell is retrieved + * from and the optional getTooltipForNode method is + * called. If no special tooltip exists here then is used + * with the cell in the given state as the argument to return a tooltip for the + * given state. + * + * Parameters: + * + * state - whose tooltip should be returned. + * node - DOM node that is currently under the mouse. + * x - X-coordinate of the mouse. + * y - Y-coordinate of the mouse. + */ +mxGraph.prototype.getTooltip = function(state, node, x, y) +{ + var tip = null; + + if (state != null) + { + // Checks if the mouse is over the folding icon + if (state.control != null && (node == state.control.node || + node.parentNode == state.control.node)) + { + tip = this.collapseExpandResource; + tip = mxUtils.htmlEntities(mxResources.get(tip) || tip).replace(/\\n/g, '
'); + } + + if (tip == null && state.overlays != null) + { + state.overlays.visit(function(id, shape) + { + // LATER: Exit loop if tip is not null + if (tip == null && (node == shape.node || node.parentNode == shape.node)) + { + tip = shape.overlay.toString(); + } + }); + } + + if (tip == null) + { + var handler = this.selectionCellsHandler.getHandler(state.cell); + + if (handler != null && typeof(handler.getTooltipForNode) == 'function') + { + tip = handler.getTooltipForNode(node); + } + } + + if (tip == null) + { + tip = this.getTooltipForCell(state.cell); + } + } + + return tip; +}; + +/** + * Function: getTooltipForCell + * + * Returns the string or DOM node to be used as the tooltip for the given + * cell. This implementation uses the cells getTooltip function if it + * exists, or else it returns for the cell. + * + * Example: + * + * (code) + * graph.getTooltipForCell = function(cell) + * { + * return 'Hello, World!'; + * } + * (end) + * + * Replaces all tooltips with the string Hello, World! + * + * Parameters: + * + * cell - whose tooltip should be returned. + */ +mxGraph.prototype.getTooltipForCell = function(cell) +{ + var tip = null; + + if (cell != null && cell.getTooltip != null) + { + tip = cell.getTooltip(); + } + else + { + tip = this.convertValueToString(cell); + } + + return tip; +}; + +/** + * Function: getCursorForMouseEvent + * + * Returns the cursor value to be used for the CSS of the shape for the + * given event. This implementation calls . + * + * Parameters: + * + * me - whose cursor should be returned. + */ +mxGraph.prototype.getCursorForMouseEvent = function(me) +{ + return this.getCursorForCell(me.getCell()); +}; + +/** + * Function: getCursorForCell + * + * Returns the cursor value to be used for the CSS of the shape for the + * given cell. This implementation returns null. + * + * Parameters: + * + * cell - whose cursor should be returned. + */ +mxGraph.prototype.getCursorForCell = function(cell) +{ + return null; +}; + +/** + * Function: getStartSize + * + * Returns the start size of the given swimlane, that is, the width or + * height of the part that contains the title, depending on the + * horizontal style. The return value is an with either + * width or height set as appropriate. + * + * Parameters: + * + * swimlane - whose start size should be returned. + */ +mxGraph.prototype.getStartSize = function(swimlane) +{ + var result = new mxRectangle(); + var state = this.view.getState(swimlane); + var style = (state != null) ? state.style : this.getCellStyle(swimlane); + + if (style != null) + { + var size = parseInt(mxUtils.getValue(style, + mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE)); + + if (mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true)) + { + result.height = size; + } + else + { + result.width = size; + } + } + + return result; +}; + +/** + * Function: getImage + * + * Returns the image URL for the given cell state. This implementation + * returns the value stored under in the cell + * style. + * + * Parameters: + * + * state - whose image URL should be returned. + */ +mxGraph.prototype.getImage = function(state) +{ + return (state != null && state.style != null) ? state.style[mxConstants.STYLE_IMAGE] : null; +}; + +/** + * Function: getVerticalAlign + * + * Returns the vertical alignment for the given cell state. This + * implementation returns the value stored under + * in the cell style. + * + * Parameters: + * + * state - whose vertical alignment should be + * returned. + */ +mxGraph.prototype.getVerticalAlign = function(state) +{ + return (state != null && state.style != null) ? + (state.style[mxConstants.STYLE_VERTICAL_ALIGN] || + mxConstants.ALIGN_MIDDLE ): + null; +}; + +/** + * Function: getIndicatorColor + * + * Returns the indicator color for the given cell state. This + * implementation returns the value stored under + * in the cell style. + * + * Parameters: + * + * state - whose indicator color should be + * returned. + */ +mxGraph.prototype.getIndicatorColor = function(state) +{ + return (state != null && state.style != null) ? state.style[mxConstants.STYLE_INDICATOR_COLOR] : null; +}; + +/** + * Function: getIndicatorGradientColor + * + * Returns the indicator gradient color for the given cell state. This + * implementation returns the value stored under + * in the cell style. + * + * Parameters: + * + * state - whose indicator gradient color should be + * returned. + */ +mxGraph.prototype.getIndicatorGradientColor = function(state) +{ + return (state != null && state.style != null) ? state.style[mxConstants.STYLE_INDICATOR_GRADIENTCOLOR] : null; +}; + +/** + * Function: getIndicatorShape + * + * Returns the indicator shape for the given cell state. This + * implementation returns the value stored under + * in the cell style. + * + * Parameters: + * + * state - whose indicator shape should be returned. + */ +mxGraph.prototype.getIndicatorShape = function(state) +{ + return (state != null && state.style != null) ? state.style[mxConstants.STYLE_INDICATOR_SHAPE] : null; +}; + +/** + * Function: getIndicatorImage + * + * Returns the indicator image for the given cell state. This + * implementation returns the value stored under + * in the cell style. + * + * Parameters: + * + * state - whose indicator image should be returned. + */ +mxGraph.prototype.getIndicatorImage = function(state) +{ + return (state != null && state.style != null) ? state.style[mxConstants.STYLE_INDICATOR_IMAGE] : null; +}; + +/** + * Function: getBorder + * + * Returns the value of . + */ +mxGraph.prototype.getBorder = function() +{ + return this.border; +}; + +/** + * Function: setBorder + * + * Sets the value of . + * + * Parameters: + * + * value - Positive integer that represents the border to be used. + */ +mxGraph.prototype.setBorder = function(value) +{ + this.border = value; +}; + +/** + * Function: isSwimlane + * + * Returns true if the given cell is a swimlane in the graph. A swimlane is + * a container cell with some specific behaviour. This implementation + * checks if the shape associated with the given cell is a . + * + * Parameters: + * + * cell - to be checked. + */ +mxGraph.prototype.isSwimlane = function (cell) +{ + if (cell != null) + { + if (this.model.getParent(cell) != this.model.getRoot()) + { + var state = this.view.getState(cell); + var style = (state != null) ? state.style : this.getCellStyle(cell); + + if (style != null && !this.model.isEdge(cell)) + { + return style[mxConstants.STYLE_SHAPE] == mxConstants.SHAPE_SWIMLANE; + } + } + } + + return false; +}; + +/** + * Group: Graph behaviour + */ + +/** + * Function: isResizeContainer + * + * Returns . + */ +mxGraph.prototype.isResizeContainer = function() +{ + return this.resizeContainer; +}; + +/** + * Function: setResizeContainer + * + * Sets . + * + * Parameters: + * + * value - Boolean indicating if the container should be resized. + */ +mxGraph.prototype.setResizeContainer = function(value) +{ + this.resizeContainer = value; +}; + +/** + * Function: isEnabled + * + * Returns true if the graph is . + */ +mxGraph.prototype.isEnabled = function() +{ + return this.enabled; +}; + +/** + * Function: setEnabled + * + * Specifies if the graph should allow any interactions. This + * implementation updates . + * + * Parameters: + * + * value - Boolean indicating if the graph should be enabled. + */ +mxGraph.prototype.setEnabled = function(value) +{ + this.enabled = value; +}; + +/** + * Function: isEscapeEnabled + * + * Returns . + */ +mxGraph.prototype.isEscapeEnabled = function() +{ + return this.escapeEnabled; +}; + +/** + * Function: setEscapeEnabled + * + * Sets . + * + * Parameters: + * + * enabled - Boolean indicating if escape should be enabled. + */ +mxGraph.prototype.setEscapeEnabled = function(value) +{ + this.escapeEnabled = value; +}; + +/** + * Function: isInvokesStopCellEditing + * + * Returns . + */ +mxGraph.prototype.isInvokesStopCellEditing = function() +{ + return this.invokesStopCellEditing; +}; + +/** + * Function: setInvokesStopCellEditing + * + * Sets . + */ +mxGraph.prototype.setInvokesStopCellEditing = function(value) +{ + this.invokesStopCellEditing = value; +}; + +/** + * Function: isEnterStopsCellEditing + * + * Returns . + */ +mxGraph.prototype.isEnterStopsCellEditing = function() +{ + return this.enterStopsCellEditing; +}; + +/** + * Function: setEnterStopsCellEditing + * + * Sets . + */ +mxGraph.prototype.setEnterStopsCellEditing = function(value) +{ + this.enterStopsCellEditing = value; +}; + +/** + * Function: isCellLocked + * + * Returns true if the given cell may not be moved, sized, bended, + * disconnected, edited or selected. This implementation returns true for + * all vertices with a relative geometry if is false. + * + * Parameters: + * + * cell - whose locked state should be returned. + */ +mxGraph.prototype.isCellLocked = function(cell) +{ + var geometry = this.model.getGeometry(cell); + + return this.isCellsLocked() || (geometry != null && this.model.isVertex(cell) && geometry.relative); +}; + +/** + * Function: isCellsLocked + * + * Returns true if the given cell may not be moved, sized, bended, + * disconnected, edited or selected. This implementation returns true for + * all vertices with a relative geometry if is false. + * + * Parameters: + * + * cell - whose locked state should be returned. + */ +mxGraph.prototype.isCellsLocked = function() +{ + return this.cellsLocked; +}; + +/** + * Function: setLocked + * + * Sets if any cell may be moved, sized, bended, disconnected, edited or + * selected. + * + * Parameters: + * + * value - Boolean that defines the new value for . + */ +mxGraph.prototype.setCellsLocked = function(value) +{ + this.cellsLocked = value; +}; + +/** + * Function: getCloneableCells + * + * Returns the cells which may be exported in the given array of cells. + */ +mxGraph.prototype.getCloneableCells = function(cells) +{ + return this.model.filterCells(cells, mxUtils.bind(this, function(cell) + { + return this.isCellCloneable(cell); + })); +}; + +/** + * Function: isCellCloneable + * + * Returns true if the given cell is cloneable. This implementation returns + * for all cells unless a cell style specifies + * to be 0. + * + * Parameters: + * + * cell - Optional whose cloneable state should be returned. + */ +mxGraph.prototype.isCellCloneable = function(cell) +{ + var state = this.view.getState(cell); + var style = (state != null) ? state.style : this.getCellStyle(cell); + + return this.isCellsCloneable() && style[mxConstants.STYLE_CLONEABLE] != 0; +}; + +/** + * Function: isCellsCloneable + * + * Returns , that is, if the graph allows cloning of cells + * by using control-drag. + */ +mxGraph.prototype.isCellsCloneable = function() +{ + return this.cellsCloneable; +}; + +/** + * Function: setCellsCloneable + * + * Specifies if the graph should allow cloning of cells by holding down the + * control key while cells are being moved. This implementation updates + * . + * + * Parameters: + * + * value - Boolean indicating if the graph should be cloneable. + */ +mxGraph.prototype.setCellsCloneable = function(value) +{ + this.cellsCloneable = value; +}; + +/** + * Function: getExportableCells + * + * Returns the cells which may be exported in the given array of cells. + */ +mxGraph.prototype.getExportableCells = function(cells) +{ + return this.model.filterCells(cells, mxUtils.bind(this, function(cell) + { + return this.canExportCell(cell); + })); +}; + +/** + * Function: canExportCell + * + * Returns true if the given cell may be exported to the clipboard. This + * implementation returns for all cells. + * + * Parameters: + * + * cell - that represents the cell to be exported. + */ +mxGraph.prototype.canExportCell = function(cell) +{ + return this.exportEnabled; +}; + +/** + * Function: getImportableCells + * + * Returns the cells which may be imported in the given array of cells. + */ +mxGraph.prototype.getImportableCells = function(cells) +{ + return this.model.filterCells(cells, mxUtils.bind(this, function(cell) + { + return this.canImportCell(cell); + })); +}; + +/** + * Function: canImportCell + * + * Returns true if the given cell may be imported from the clipboard. + * This implementation returns for all cells. + * + * Parameters: + * + * cell - that represents the cell to be imported. + */ +mxGraph.prototype.canImportCell = function(cell) +{ + return this.importEnabled; +}; + +/** + * Function: isCellSelectable + * + * Returns true if the given cell is selectable. This implementation + * returns . + * + * To add a new style for making cells (un)selectable, use the following code. + * + * (code) + * mxGraph.prototype.isCellSelectable = function(cell) + * { + * var state = this.view.getState(cell); + * var style = (state != null) ? state.style : this.getCellStyle(cell); + * + * return this.isCellsSelectable() && !this.isCellLocked(cell) && style['selectable'] != 0; + * }; + * (end) + * + * You can then use the new style as shown in this example. + * + * (code) + * graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30, 'selectable=0'); + * (end) + * + * Parameters: + * + * cell - whose selectable state should be returned. + */ +mxGraph.prototype.isCellSelectable = function(cell) +{ + return this.isCellsSelectable(); +}; + +/** + * Function: isCellsSelectable + * + * Returns . + */ +mxGraph.prototype.isCellsSelectable = function() +{ + return this.cellsSelectable; +}; + +/** + * Function: setCellsSelectable + * + * Sets . + */ +mxGraph.prototype.setCellsSelectable = function(value) +{ + this.cellsSelectable = value; +}; + +/** + * Function: getDeletableCells + * + * Returns the cells which may be exported in the given array of cells. + */ +mxGraph.prototype.getDeletableCells = function(cells) +{ + return this.model.filterCells(cells, mxUtils.bind(this, function(cell) + { + return this.isCellDeletable(cell); + })); +}; + +/** + * Function: isCellDeletable + * + * Returns true if the given cell is moveable. This returns + * for all given cells if a cells style does not specify + * to be 0. + * + * Parameters: + * + * cell - whose deletable state should be returned. + */ +mxGraph.prototype.isCellDeletable = function(cell) +{ + var state = this.view.getState(cell); + var style = (state != null) ? state.style : this.getCellStyle(cell); + + return this.isCellsDeletable() && style[mxConstants.STYLE_DELETABLE] != 0; +}; + +/** + * Function: isCellsDeletable + * + * Returns . + */ +mxGraph.prototype.isCellsDeletable = function() +{ + return this.cellsDeletable; +}; + +/** + * Function: setCellsDeletable + * + * Sets . + * + * Parameters: + * + * value - Boolean indicating if the graph should allow deletion of cells. + */ +mxGraph.prototype.setCellsDeletable = function(value) +{ + this.cellsDeletable = value; +}; + +/** + * Function: isLabelMovable + * + * Returns true if the given edges's label is moveable. This returns + * for all given cells if does not return true + * for the given cell. + * + * Parameters: + * + * cell - whose label should be moved. + */ +mxGraph.prototype.isLabelMovable = function(cell) +{ + return !this.isCellLocked(cell) && + ((this.model.isEdge(cell) && this.edgeLabelsMovable) || + (this.model.isVertex(cell) && this.vertexLabelsMovable)); +}; + +/** + * Function: isCellRotatable + * + * Returns true if the given cell is rotatable. This returns true for the given + * cell if its style does not specify to be 0. + * + * Parameters: + * + * cell - whose rotatable state should be returned. + */ +mxGraph.prototype.isCellRotatable = function(cell) +{ + var state = this.view.getState(cell); + var style = (state != null) ? state.style : this.getCellStyle(cell); + + return style[mxConstants.STYLE_ROTATABLE] != 0; +}; + +/** + * Function: getMovableCells + * + * Returns the cells which are movable in the given array of cells. + */ +mxGraph.prototype.getMovableCells = function(cells) +{ + return this.model.filterCells(cells, mxUtils.bind(this, function(cell) + { + return this.isCellMovable(cell); + })); +}; + +/** + * Function: isCellMovable + * + * Returns true if the given cell is moveable. This returns + * for all given cells if does not return true for the given + * cell and its style does not specify to be 0. + * + * Parameters: + * + * cell - whose movable state should be returned. + */ +mxGraph.prototype.isCellMovable = function(cell) +{ + var state = this.view.getState(cell); + var style = (state != null) ? state.style : this.getCellStyle(cell); + + return this.isCellsMovable() && !this.isCellLocked(cell) && style[mxConstants.STYLE_MOVABLE] != 0; +}; + +/** + * Function: isCellsMovable + * + * Returns . + */ +mxGraph.prototype.isCellsMovable = function() +{ + return this.cellsMovable; +}; + +/** + * Function: setCellsMovable + * + * Specifies if the graph should allow moving of cells. This implementation + * updates . + * + * Parameters: + * + * value - Boolean indicating if the graph should allow moving of cells. + */ +mxGraph.prototype.setCellsMovable = function(value) +{ + this.cellsMovable = value; +}; + +/** + * Function: isGridEnabled + * + * Returns as a boolean. + */ +mxGraph.prototype.isGridEnabled = function() +{ + return this.gridEnabled; +}; + +/** + * Function: setGridEnabled + * + * Specifies if the grid should be enabled. + * + * Parameters: + * + * value - Boolean indicating if the grid should be enabled. + */ +mxGraph.prototype.setGridEnabled = function(value) +{ + this.gridEnabled = value; +}; + +/** + * Function: isPortsEnabled + * + * Returns as a boolean. + */ +mxGraph.prototype.isPortsEnabled = function() +{ + return this.portsEnabled; +}; + +/** + * Function: setPortsEnabled + * + * Specifies if the ports should be enabled. + * + * Parameters: + * + * value - Boolean indicating if the ports should be enabled. + */ +mxGraph.prototype.setPortsEnabled = function(value) +{ + this.portsEnabled = value; +}; + +/** + * Function: getGridSize + * + * Returns . + */ +mxGraph.prototype.getGridSize = function() +{ + return this.gridSize; +}; + +/** + * Function: setGridSize + * + * Sets . + */ +mxGraph.prototype.setGridSize = function(value) +{ + this.gridSize = value; +}; + +/** + * Function: getTolerance + * + * Returns . + */ +mxGraph.prototype.getTolerance = function() +{ + return this.tolerance; +}; + +/** + * Function: setTolerance + * + * Sets . + */ +mxGraph.prototype.setTolerance = function(value) +{ + this.tolerance = value; +}; + +/** + * Function: isVertexLabelsMovable + * + * Returns . + */ +mxGraph.prototype.isVertexLabelsMovable = function() +{ + return this.vertexLabelsMovable; +}; + +/** + * Function: setVertexLabelsMovable + * + * Sets . + */ +mxGraph.prototype.setVertexLabelsMovable = function(value) +{ + this.vertexLabelsMovable = value; +}; + +/** + * Function: isEdgeLabelsMovable + * + * Returns . + */ +mxGraph.prototype.isEdgeLabelsMovable = function() +{ + return this.edgeLabelsMovable; +}; + +/** + * Function: isEdgeLabelsMovable + * + * Sets . + */ +mxGraph.prototype.setEdgeLabelsMovable = function(value) +{ + this.edgeLabelsMovable = value; +}; + +/** + * Function: isSwimlaneNesting + * + * Returns as a boolean. + */ +mxGraph.prototype.isSwimlaneNesting = function() +{ + return this.swimlaneNesting; +}; + +/** + * Function: setSwimlaneNesting + * + * Specifies if swimlanes can be nested by drag and drop. This is only + * taken into account if dropEnabled is true. + * + * Parameters: + * + * value - Boolean indicating if swimlanes can be nested. + */ +mxGraph.prototype.setSwimlaneNesting = function(value) +{ + this.swimlaneNesting = value; +}; + +/** + * Function: isSwimlaneSelectionEnabled + * + * Returns as a boolean. + */ +mxGraph.prototype.isSwimlaneSelectionEnabled = function() +{ + return this.swimlaneSelectionEnabled; +}; + +/** + * Function: setSwimlaneSelectionEnabled + * + * Specifies if swimlanes should be selected if the mouse is released + * over their content area. + * + * Parameters: + * + * value - Boolean indicating if swimlanes content areas + * should be selected when the mouse is released over them. + */ +mxGraph.prototype.setSwimlaneSelectionEnabled = function(value) +{ + this.swimlaneSelectionEnabled = value; +}; + +/** + * Function: isMultigraph + * + * Returns as a boolean. + */ +mxGraph.prototype.isMultigraph = function() +{ + return this.multigraph; +}; + +/** + * Function: setMultigraph + * + * Specifies if the graph should allow multiple connections between the + * same pair of vertices. + * + * Parameters: + * + * value - Boolean indicating if the graph allows multiple connections + * between the same pair of vertices. + */ +mxGraph.prototype.setMultigraph = function(value) +{ + this.multigraph = value; +}; + +/** + * Function: isAllowLoops + * + * Returns as a boolean. + */ +mxGraph.prototype.isAllowLoops = function() +{ + return this.allowLoops; +}; + +/** + * Function: setAllowDanglingEdges + * + * Specifies if dangling edges are allowed, that is, if edges are allowed + * that do not have a source and/or target terminal defined. + * + * Parameters: + * + * value - Boolean indicating if dangling edges are allowed. + */ +mxGraph.prototype.setAllowDanglingEdges = function(value) +{ + this.allowDanglingEdges = value; +}; + +/** + * Function: isAllowDanglingEdges + * + * Returns as a boolean. + */ +mxGraph.prototype.isAllowDanglingEdges = function() +{ + return this.allowDanglingEdges; +}; + +/** + * Function: setConnectableEdges + * + * Specifies if edges should be connectable. + * + * Parameters: + * + * value - Boolean indicating if edges should be connectable. + */ +mxGraph.prototype.setConnectableEdges = function(value) +{ + this.connectableEdges = value; +}; + +/** + * Function: isConnectableEdges + * + * Returns as a boolean. + */ +mxGraph.prototype.isConnectableEdges = function() +{ + return this.connectableEdges; +}; + +/** + * Function: setCloneInvalidEdges + * + * Specifies if edges should be inserted when cloned but not valid wrt. + * . If false such edges will be silently ignored. + * + * Parameters: + * + * value - Boolean indicating if cloned invalid edges should be + * inserted into the graph or ignored. + */ +mxGraph.prototype.setCloneInvalidEdges = function(value) +{ + this.cloneInvalidEdges = value; +}; + +/** + * Function: isCloneInvalidEdges + * + * Returns as a boolean. + */ +mxGraph.prototype.isCloneInvalidEdges = function() +{ + return this.cloneInvalidEdges; +}; + +/** + * Function: setAllowLoops + * + * Specifies if loops are allowed. + * + * Parameters: + * + * value - Boolean indicating if loops are allowed. + */ +mxGraph.prototype.setAllowLoops = function(value) +{ + this.allowLoops = value; +}; + +/** + * Function: isDisconnectOnMove + * + * Returns as a boolean. + */ +mxGraph.prototype.isDisconnectOnMove = function() +{ + return this.disconnectOnMove; +}; + +/** + * Function: setDisconnectOnMove + * + * Specifies if edges should be disconnected when moved. (Note: Cloned + * edges are always disconnected.) + * + * Parameters: + * + * value - Boolean indicating if edges should be disconnected + * when moved. + */ +mxGraph.prototype.setDisconnectOnMove = function(value) +{ + this.disconnectOnMove = value; +}; + +/** + * Function: isDropEnabled + * + * Returns as a boolean. + */ +mxGraph.prototype.isDropEnabled = function() +{ + return this.dropEnabled; +}; + +/** + * Function: setDropEnabled + * + * Specifies if the graph should allow dropping of cells onto or into other + * cells. + * + * Parameters: + * + * dropEnabled - Boolean indicating if the graph should allow dropping + * of cells into other cells. + */ +mxGraph.prototype.setDropEnabled = function(value) +{ + this.dropEnabled = value; +}; + +/** + * Function: isSplitEnabled + * + * Returns as a boolean. + */ +mxGraph.prototype.isSplitEnabled = function() +{ + return this.splitEnabled; +}; + +/** + * Function: setSplitEnabled + * + * Specifies if the graph should allow dropping of cells onto or into other + * cells. + * + * Parameters: + * + * dropEnabled - Boolean indicating if the graph should allow dropping + * of cells into other cells. + */ +mxGraph.prototype.setSplitEnabled = function(value) +{ + this.splitEnabled = value; +}; + +/** + * Function: isCellResizable + * + * Returns true if the given cell is resizable. This returns + * for all given cells if does not return + * true for the given cell and its style does not specify + * to be 0. + * + * Parameters: + * + * cell - whose resizable state should be returned. + */ +mxGraph.prototype.isCellResizable = function(cell) +{ + var state = this.view.getState(cell); + var style = (state != null) ? state.style : this.getCellStyle(cell); + + return this.isCellsResizable() && !this.isCellLocked(cell) && + mxUtils.getValue(style, mxConstants.STYLE_RESIZABLE, '1') != '0'; +}; + +/** + * Function: isCellsResizable + * + * Returns . + */ +mxGraph.prototype.isCellsResizable = function() +{ + return this.cellsResizable; +}; + +/** + * Function: setCellsResizable + * + * Specifies if the graph should allow resizing of cells. This + * implementation updates . + * + * Parameters: + * + * value - Boolean indicating if the graph should allow resizing of + * cells. + */ +mxGraph.prototype.setCellsResizable = function(value) +{ + this.cellsResizable = value; +}; + +/** + * Function: isTerminalPointMovable + * + * Returns true if the given terminal point is movable. This is independent + * from and and controls if terminal + * points can be moved in the graph if the edge is not connected. Note that it + * is required for this to return true to connect unconnected edges. This + * implementation returns true. + * + * Parameters: + * + * cell - whose terminal point should be moved. + * source - Boolean indicating if the source or target terminal should be moved. + */ +mxGraph.prototype.isTerminalPointMovable = function(cell, source) +{ + return true; +}; + +/** + * Function: isCellBendable + * + * Returns true if the given cell is bendable. This returns + * for all given cells if does not return true for the given + * cell and its style does not specify to be 0. + * + * Parameters: + * + * cell - whose bendable state should be returned. + */ +mxGraph.prototype.isCellBendable = function(cell) +{ + var state = this.view.getState(cell); + var style = (state != null) ? state.style : this.getCellStyle(cell); + + return this.isCellsBendable() && !this.isCellLocked(cell) && style[mxConstants.STYLE_BENDABLE] != 0; +}; + +/** + * Function: isCellsBendable + * + * Returns . + */ +mxGraph.prototype.isCellsBendable = function() +{ + return this.cellsBendable; +}; + +/** + * Function: setCellsBendable + * + * Specifies if the graph should allow bending of edges. This + * implementation updates . + * + * Parameters: + * + * value - Boolean indicating if the graph should allow bending of + * edges. + */ +mxGraph.prototype.setCellsBendable = function(value) +{ + this.cellsBendable = value; +}; + +/** + * Function: isCellEditable + * + * Returns true if the given cell is editable. This returns for + * all given cells if does not return true for the given cell + * and its style does not specify to be 0. + * + * Parameters: + * + * cell - whose editable state should be returned. + */ +mxGraph.prototype.isCellEditable = function(cell) +{ + var state = this.view.getState(cell); + var style = (state != null) ? state.style : this.getCellStyle(cell); + + return this.isCellsEditable() && !this.isCellLocked(cell) && style[mxConstants.STYLE_EDITABLE] != 0; +}; + +/** + * Function: isCellsEditable + * + * Returns . + */ +mxGraph.prototype.isCellsEditable = function() +{ + return this.cellsEditable; +}; + +/** + * Function: setCellsEditable + * + * Specifies if the graph should allow in-place editing for cell labels. + * This implementation updates . + * + * Parameters: + * + * value - Boolean indicating if the graph should allow in-place + * editing. + */ +mxGraph.prototype.setCellsEditable = function(value) +{ + this.cellsEditable = value; +}; + +/** + * Function: isCellDisconnectable + * + * Returns true if the given cell is disconnectable from the source or + * target terminal. This returns for all given + * cells if does not return true for the given cell. + * + * Parameters: + * + * cell - whose disconnectable state should be returned. + * terminal - that represents the source or target terminal. + * source - Boolean indicating if the source or target terminal is to be + * disconnected. + */ +mxGraph.prototype.isCellDisconnectable = function(cell, terminal, source) +{ + return this.isCellsDisconnectable() && !this.isCellLocked(cell); +}; + +/** + * Function: isCellsDisconnectable + * + * Returns . + */ +mxGraph.prototype.isCellsDisconnectable = function() +{ + return this.cellsDisconnectable; +}; + +/** + * Function: setCellsDisconnectable + * + * Sets . + */ +mxGraph.prototype.setCellsDisconnectable = function(value) +{ + this.cellsDisconnectable = value; +}; + +/** + * Function: isValidSource + * + * Returns true if the given cell is a valid source for new connections. + * This implementation returns true for all non-null values and is + * called by is called by . + * + * Parameters: + * + * cell - that represents a possible source or null. + */ +mxGraph.prototype.isValidSource = function(cell) +{ + return (cell == null && this.allowDanglingEdges) || + (cell != null && (!this.model.isEdge(cell) || + this.connectableEdges) && this.isCellConnectable(cell)); +}; + +/** + * Function: isValidTarget + * + * Returns for the given cell. This is called by + * . + * + * Parameters: + * + * cell - that represents a possible target or null. + */ +mxGraph.prototype.isValidTarget = function(cell) +{ + return this.isValidSource(cell); +}; + +/** + * Function: isValidConnection + * + * Returns true if the given target cell is a valid target for source. + * This is a boolean implementation for not allowing connections between + * certain pairs of vertices and is called by . + * This implementation returns true if returns true for + * the source and returns true for the target. + * + * Parameters: + * + * source - that represents the source cell. + * target - that represents the target cell. + */ +mxGraph.prototype.isValidConnection = function(source, target) +{ + return this.isValidSource(source) && this.isValidTarget(target); +}; + +/** + * Function: setConnectable + * + * Specifies if the graph should allow new connections. This implementation + * updates in . + * + * Parameters: + * + * connectable - Boolean indicating if new connections should be allowed. + */ +mxGraph.prototype.setConnectable = function(connectable) +{ + this.connectionHandler.setEnabled(connectable); +}; + +/** + * Function: isConnectable + * + * Returns true if the is enabled. + */ +mxGraph.prototype.isConnectable = function(connectable) +{ + return this.connectionHandler.isEnabled(); +}; + +/** + * Function: setTooltips + * + * Specifies if tooltips should be enabled. This implementation updates + * in . + * + * Parameters: + * + * enabled - Boolean indicating if tooltips should be enabled. + */ +mxGraph.prototype.setTooltips = function (enabled) +{ + this.tooltipHandler.setEnabled(enabled); +}; + +/** + * Function: setPanning + * + * Specifies if panning should be enabled. This implementation updates + * in . + * + * Parameters: + * + * enabled - Boolean indicating if panning should be enabled. + */ +mxGraph.prototype.setPanning = function(enabled) +{ + this.panningHandler.panningEnabled = enabled; +}; + +/** + * Function: isEditing + * + * Returns true if the given cell is currently being edited. + * If no cell is specified then this returns true if any + * cell is currently being edited. + * + * Parameters: + * + * cell - that should be checked. + */ +mxGraph.prototype.isEditing = function(cell) +{ + if (this.cellEditor != null) + { + var editingCell = this.cellEditor.getEditingCell(); + + return (cell == null) ? editingCell != null : cell == editingCell; + } + + return false; +}; + +/** + * Function: isAutoSizeCell + * + * Returns true if the size of the given cell should automatically be + * updated after a change of the label. This implementation returns + * or checks if the cell style does specify + * to be 1. + * + * Parameters: + * + * cell - that should be resized. + */ +mxGraph.prototype.isAutoSizeCell = function(cell) +{ + var state = this.view.getState(cell); + var style = (state != null) ? state.style : this.getCellStyle(cell); + + return this.isAutoSizeCells() || style[mxConstants.STYLE_AUTOSIZE] == 1; +}; + +/** + * Function: isAutoSizeCells + * + * Returns . + */ +mxGraph.prototype.isAutoSizeCells = function() +{ + return this.autoSizeCells; +}; + +/** + * Function: setAutoSizeCells + * + * Specifies if cell sizes should be automatically updated after a label + * change. This implementation sets to the given parameter. + * To update the size of cells when the cells are added, set + * to true. + * + * Parameters: + * + * value - Boolean indicating if cells should be resized + * automatically. + */ +mxGraph.prototype.setAutoSizeCells = function(value) +{ + this.autoSizeCells = value; +}; + +/** + * Function: isExtendParent + * + * Returns true if the parent of the given cell should be extended if the + * child has been resized so that it overlaps the parent. This + * implementation returns if the cell is not an edge. + * + * Parameters: + * + * cell - that has been resized. + */ +mxGraph.prototype.isExtendParent = function(cell) +{ + return !this.getModel().isEdge(cell) && this.isExtendParents(); +}; + +/** + * Function: isExtendParents + * + * Returns . + */ +mxGraph.prototype.isExtendParents = function() +{ + return this.extendParents; +}; + +/** + * Function: setExtendParents + * + * Sets . + * + * Parameters: + * + * value - New boolean value for . + */ +mxGraph.prototype.setExtendParents = function(value) +{ + this.extendParents = value; +}; + +/** + * Function: isExtendParentsOnAdd + * + * Returns . + */ +mxGraph.prototype.isExtendParentsOnAdd = function(cell) +{ + return this.extendParentsOnAdd; +}; + +/** + * Function: setExtendParentsOnAdd + * + * Sets . + * + * Parameters: + * + * value - New boolean value for . + */ +mxGraph.prototype.setExtendParentsOnAdd = function(value) +{ + this.extendParentsOnAdd = value; +}; + +/** + * Function: isExtendParentsOnMove + * + * Returns . + */ +mxGraph.prototype.isExtendParentsOnMove = function() +{ + return this.extendParentsOnMove; +}; + +/** + * Function: setExtendParentsOnMove + * + * Sets . + * + * Parameters: + * + * value - New boolean value for . + */ +mxGraph.prototype.setExtendParentsOnMove = function(value) +{ + this.extendParentsOnMove = value; +}; + +/** + * Function: isRecursiveResize + * + * Returns . + * + * Parameters: + * + * state - that is being resized. + */ +mxGraph.prototype.isRecursiveResize = function(state) +{ + return this.recursiveResize; +}; + +/** + * Function: setRecursiveResize + * + * Sets . + * + * Parameters: + * + * value - New boolean value for . + */ +mxGraph.prototype.setRecursiveResize = function(value) +{ + this.recursiveResize = value; +}; + +/** + * Function: isConstrainChild + * + * Returns true if the given cell should be kept inside the bounds of its + * parent according to the rules defined by and + * . This implementation returns false for all children + * of edges and otherwise. + * + * Parameters: + * + * cell - that should be constrained. + */ +mxGraph.prototype.isConstrainChild = function(cell) +{ + return this.isConstrainChildren() && !this.getModel().isEdge(this.getModel().getParent(cell)); +}; + +/** + * Function: isConstrainChildren + * + * Returns . + */ +mxGraph.prototype.isConstrainChildren = function() +{ + return this.constrainChildren; +}; + +/** + * Function: setConstrainChildren + * + * Sets . + */ +mxGraph.prototype.setConstrainChildren = function(value) +{ + this.constrainChildren = value; +}; + +/** + * Function: isConstrainRelativeChildren + * + * Returns . + */ +mxGraph.prototype.isConstrainRelativeChildren = function() +{ + return this.constrainRelativeChildren; +}; + +/** + * Function: setConstrainRelativeChildren + * + * Sets . + */ +mxGraph.prototype.setConstrainRelativeChildren = function(value) +{ + this.constrainRelativeChildren = value; +}; + +/** + * Function: isConstrainChildren + * + * Returns . + */ +mxGraph.prototype.isAllowNegativeCoordinates = function() +{ + return this.allowNegativeCoordinates; +}; + +/** + * Function: setConstrainChildren + * + * Sets . + */ +mxGraph.prototype.setAllowNegativeCoordinates = function(value) +{ + this.allowNegativeCoordinates = value; +}; + +/** + * Function: getOverlap + * + * Returns a decimal number representing the amount of the width and height + * of the given cell that is allowed to overlap its parent. A value of 0 + * means all children must stay inside the parent, 1 means the child is + * allowed to be placed outside of the parent such that it touches one of + * the parents sides. If returns false for the given + * cell, then this method returns 0. + * + * Parameters: + * + * cell - for which the overlap ratio should be returned. + */ +mxGraph.prototype.getOverlap = function(cell) +{ + return (this.isAllowOverlapParent(cell)) ? this.defaultOverlap : 0; +}; + +/** + * Function: isAllowOverlapParent + * + * Returns true if the given cell is allowed to be placed outside of the + * parents area. + * + * Parameters: + * + * cell - that represents the child to be checked. + */ +mxGraph.prototype.isAllowOverlapParent = function(cell) +{ + return false; +}; + +/** + * Function: getFoldableCells + * + * Returns the cells which are movable in the given array of cells. + */ +mxGraph.prototype.getFoldableCells = function(cells, collapse) +{ + return this.model.filterCells(cells, mxUtils.bind(this, function(cell) + { + return this.isCellFoldable(cell, collapse); + })); +}; + +/** + * Function: isCellFoldable + * + * Returns true if the given cell is foldable. This implementation + * returns true if the cell has at least one child and its style + * does not specify to be 0. + * + * Parameters: + * + * cell - whose foldable state should be returned. + */ +mxGraph.prototype.isCellFoldable = function(cell, collapse) +{ + var state = this.view.getState(cell); + var style = (state != null) ? state.style : this.getCellStyle(cell); + + return this.model.getChildCount(cell) > 0 && style[mxConstants.STYLE_FOLDABLE] != 0; +}; + +/** + * Function: isValidDropTarget + * + * Returns true if the given cell is a valid drop target for the specified + * cells. If is true then this returns for + * the given arguments else it returns true if the cell is not collapsed + * and its child count is greater than 0. + * + * Parameters: + * + * cell - that represents the possible drop target. + * cells - that should be dropped into the target. + * evt - Mouseevent that triggered the invocation. + */ +mxGraph.prototype.isValidDropTarget = function(cell, cells, evt) +{ + return cell != null && ((this.isSplitEnabled() && + this.isSplitTarget(cell, cells, evt)) || (!this.model.isEdge(cell) && + (this.isSwimlane(cell) || (this.model.getChildCount(cell) > 0 && + !this.isCellCollapsed(cell))))); +}; + +/** + * Function: isSplitTarget + * + * Returns true if the given edge may be splitted into two edges with the + * given cell as a new terminal between the two. + * + * Parameters: + * + * target - that represents the edge to be splitted. + * cells - that should split the edge. + * evt - Mouseevent that triggered the invocation. + */ +mxGraph.prototype.isSplitTarget = function(target, cells, evt) +{ + if (this.model.isEdge(target) && cells != null && cells.length == 1 && + this.isCellConnectable(cells[0]) && this.getEdgeValidationError(target, + this.model.getTerminal(target, true), cells[0]) == null) + { + var src = this.model.getTerminal(target, true); + var trg = this.model.getTerminal(target, false); + + return (!this.model.isAncestor(cells[0], src) && + !this.model.isAncestor(cells[0], trg)); + } + + return false; +}; + +/** + * Function: getDropTarget + * + * Returns the given cell if it is a drop target for the given cells or the + * nearest ancestor that may be used as a drop target for the given cells. + * If the given array contains a swimlane and is false + * then this always returns null. If no cell is given, then the bottommost + * swimlane at the location of the given event is returned. + * + * This function should only be used if returns true. + * + * Parameters: + * + * cells - Array of which are to be dropped onto the target. + * evt - Mouseevent for the drag and drop. + * cell - that is under the mousepointer. + * clone - Optional boolean to indicate of cells will be cloned. + */ +mxGraph.prototype.getDropTarget = function(cells, evt, cell, clone) +{ + if (!this.isSwimlaneNesting()) + { + for (var i = 0; i < cells.length; i++) + { + if (this.isSwimlane(cells[i])) + { + return null; + } + } + } + + var pt = mxUtils.convertPoint(this.container, + mxEvent.getClientX(evt), mxEvent.getClientY(evt)); + pt.x -= this.panDx; + pt.y -= this.panDy; + var swimlane = this.getSwimlaneAt(pt.x, pt.y); + + if (cell == null) + { + cell = swimlane; + } + else if (swimlane != null) + { + // Checks if the cell is an ancestor of the swimlane + // under the mouse and uses the swimlane in that case + var tmp = this.model.getParent(swimlane); + + while (tmp != null && this.isSwimlane(tmp) && tmp != cell) + { + tmp = this.model.getParent(tmp); + } + + if (tmp == cell) + { + cell = swimlane; + } + } + + while (cell != null && !this.isValidDropTarget(cell, cells, evt) && + !this.model.isLayer(cell)) + { + cell = this.model.getParent(cell); + } + + // Checks if parent is dropped into child if not cloning + if (clone == null || !clone) + { + var parent = cell; + + while (parent != null && mxUtils.indexOf(cells, parent) < 0) + { + parent = this.model.getParent(parent); + } + } + + return (!this.model.isLayer(cell) && parent == null) ? cell : null; +}; + +/** + * Group: Cell retrieval + */ + +/** + * Function: getDefaultParent + * + * Returns or or the first child + * child of if both are null. The value returned by + * this function should be used as the parent for new cells (aka default + * layer). + */ +mxGraph.prototype.getDefaultParent = function() +{ + var parent = this.getCurrentRoot(); + + if (parent == null) + { + parent = this.defaultParent; + + if (parent == null) + { + var root = this.model.getRoot(); + parent = this.model.getChildAt(root, 0); + } + } + + return parent; +}; + +/** + * Function: setDefaultParent + * + * Sets the to the given cell. Set this to null to return + * the first child of the root in getDefaultParent. + */ +mxGraph.prototype.setDefaultParent = function(cell) +{ + this.defaultParent = cell; +}; + +/** + * Function: getSwimlane + * + * Returns the nearest ancestor of the given cell which is a swimlane, or + * the given cell, if it is itself a swimlane. + * + * Parameters: + * + * cell - for which the ancestor swimlane should be returned. + */ +mxGraph.prototype.getSwimlane = function(cell) +{ + while (cell != null && !this.isSwimlane(cell)) + { + cell = this.model.getParent(cell); + } + + return cell; +}; + +/** + * Function: getSwimlaneAt + * + * Returns the bottom-most swimlane that intersects the given point (x, y) + * in the cell hierarchy that starts at the given parent. + * + * Parameters: + * + * x - X-coordinate of the location to be checked. + * y - Y-coordinate of the location to be checked. + * parent - that should be used as the root of the recursion. + * Default is . + */ +mxGraph.prototype.getSwimlaneAt = function (x, y, parent) +{ + parent = parent || this.getDefaultParent(); + + if (parent != null) + { + var childCount = this.model.getChildCount(parent); + + for (var i = 0; i < childCount; i++) + { + var child = this.model.getChildAt(parent, i); + var result = this.getSwimlaneAt(x, y, child); + + if (result != null) + { + return result; + } + else if (this.isSwimlane(child)) + { + var state = this.view.getState(child); + + if (this.intersects(state, x, y)) + { + return child; + } + } + } + } + + return null; +}; + +/** + * Function: getCellAt + * + * Returns the bottom-most cell that intersects the given point (x, y) in + * the cell hierarchy starting at the given parent. This will also return + * swimlanes if the given location intersects the content area of the + * swimlane. If this is not desired, then the may be + * used if the returned cell is a swimlane to determine if the location + * is inside the content area or on the actual title of the swimlane. + * + * Parameters: + * + * x - X-coordinate of the location to be checked. + * y - Y-coordinate of the location to be checked. + * parent - that should be used as the root of the recursion. + * Default is current root of the view or the root of the model. + * vertices - Optional boolean indicating if vertices should be returned. + * Default is true. + * edges - Optional boolean indicating if edges should be returned. Default + * is true. + * ignoreFn - Optional function that returns true if cell should be ignored. + * The function is passed the cell state and the x and y parameter. + */ +mxGraph.prototype.getCellAt = function(x, y, parent, vertices, edges, ignoreFn) +{ + vertices = (vertices != null) ? vertices : true; + edges = (edges != null) ? edges : true; + + if (parent == null) + { + parent = this.getCurrentRoot(); + + if (parent == null) + { + parent = this.getModel().getRoot(); + } + } + + if (parent != null) + { + var childCount = this.model.getChildCount(parent); + + for (var i = childCount - 1; i >= 0; i--) + { + var cell = this.model.getChildAt(parent, i); + var result = this.getCellAt(x, y, cell, vertices, edges, ignoreFn); + + if (result != null) + { + return result; + } + else if (this.isCellVisible(cell) && (edges && this.model.isEdge(cell) || + vertices && this.model.isVertex(cell))) + { + var state = this.view.getState(cell); + + if (state != null && (ignoreFn == null || !ignoreFn(state, x, y)) && + this.intersects(state, x, y)) + { + return cell; + } + } + } + } + + return null; +}; + +/** + * Function: intersects + * + * Returns the bottom-most cell that intersects the given point (x, y) in + * the cell hierarchy that starts at the given parent. + * + * Parameters: + * + * state - that represents the cell state. + * x - X-coordinate of the location to be checked. + * y - Y-coordinate of the location to be checked. + */ +mxGraph.prototype.intersects = function(state, x, y) +{ + if (state != null) + { + var pts = state.absolutePoints; + + if (pts != null) + { + var t2 = this.tolerance * this.tolerance; + var pt = pts[0]; + + for (var i = 1; i < pts.length; i++) + { + var next = pts[i]; + var dist = mxUtils.ptSegDistSq(pt.x, pt.y, next.x, next.y, x, y); + + if (dist <= t2) + { + return true; + } + + pt = next; + } + } + else + { + 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 cx = new mxPoint(state.getCenterX(), state.getCenterY()); + var pt = mxUtils.getRotatedPoint(new mxPoint(x, y), cos, sin, cx); + x = pt.x; + y = pt.y; + } + + if (mxUtils.contains(state, x, y)) + { + return true; + } + } + } + + return false; +}; + +/** + * Function: hitsSwimlaneContent + * + * Returns true if the given coordinate pair is inside the content + * are of the given swimlane. + * + * Parameters: + * + * swimlane - that specifies the swimlane. + * x - X-coordinate of the mouse event. + * y - Y-coordinate of the mouse event. + */ +mxGraph.prototype.hitsSwimlaneContent = function(swimlane, x, y) +{ + var state = this.getView().getState(swimlane); + var size = this.getStartSize(swimlane); + + if (state != null) + { + var scale = this.getView().getScale(); + x -= state.x; + y -= state.y; + + if (size.width > 0 && x > 0 && x > size.width * scale) + { + return true; + } + else if (size.height > 0 && y > 0 && y > size.height * scale) + { + return true; + } + } + + return false; +}; + +/** + * Function: getChildVertices + * + * Returns the visible child vertices of the given parent. + * + * Parameters: + * + * parent - whose children should be returned. + */ +mxGraph.prototype.getChildVertices = function(parent) +{ + return this.getChildCells(parent, true, false); +}; + +/** + * Function: getChildEdges + * + * Returns the visible child edges of the given parent. + * + * Parameters: + * + * parent - whose child vertices should be returned. + */ +mxGraph.prototype.getChildEdges = function(parent) +{ + return this.getChildCells(parent, false, true); +}; + +/** + * Function: getChildCells + * + * Returns the visible child vertices or edges in the given parent. If + * vertices and edges is false, then all children are returned. + * + * Parameters: + * + * parent - whose children should be returned. + * vertices - Optional boolean that specifies if child vertices should + * be returned. Default is false. + * edges - Optional boolean that specifies if child edges should + * be returned. Default is false. + */ +mxGraph.prototype.getChildCells = function(parent, vertices, edges) +{ + parent = (parent != null) ? parent : this.getDefaultParent(); + vertices = (vertices != null) ? vertices : false; + edges = (edges != null) ? edges : false; + + var cells = this.model.getChildCells(parent, vertices, edges); + var result = []; + + // Filters out the non-visible child cells + for (var i = 0; i < cells.length; i++) + { + if (this.isCellVisible(cells[i])) + { + result.push(cells[i]); + } + } + + return result; +}; + +/** + * Function: getConnections + * + * Returns all visible edges connected to the given cell without loops. + * + * Parameters: + * + * cell - whose connections should be returned. + * parent - Optional parent of the opposite end for a connection to be + * returned. + */ +mxGraph.prototype.getConnections = function(cell, parent) +{ + return this.getEdges(cell, parent, true, true, false); +}; + +/** + * Function: getIncomingEdges + * + * Returns the visible incoming edges for the given cell. If the optional + * parent argument is specified, then only child edges of the given parent + * are returned. + * + * Parameters: + * + * cell - whose incoming edges should be returned. + * parent - Optional parent of the opposite end for an edge to be + * returned. + */ +mxGraph.prototype.getIncomingEdges = function(cell, parent) +{ + return this.getEdges(cell, parent, true, false, false); +}; + +/** + * Function: getOutgoingEdges + * + * Returns the visible outgoing edges for the given cell. If the optional + * parent argument is specified, then only child edges of the given parent + * are returned. + * + * Parameters: + * + * cell - whose outgoing edges should be returned. + * parent - Optional parent of the opposite end for an edge to be + * returned. + */ +mxGraph.prototype.getOutgoingEdges = function(cell, parent) +{ + return this.getEdges(cell, parent, false, true, false); +}; + +/** + * Function: getEdges + * + * Returns the incoming and/or outgoing edges for the given cell. + * If the optional parent argument is specified, then only edges are returned + * where the opposite is in the given parent cell. If at least one of incoming + * or outgoing is true, then loops are ignored, if both are false, then all + * edges connected to the given cell are returned including loops. + * + * Parameters: + * + * cell - whose edges should be returned. + * parent - Optional parent of the opposite end for an edge to be + * returned. + * incoming - Optional boolean that specifies if incoming edges should + * be included in the result. Default is true. + * outgoing - Optional boolean that specifies if outgoing edges should + * be included in the result. Default is true. + * includeLoops - Optional boolean that specifies if loops should be + * included in the result. Default is true. + * recurse - Optional boolean the specifies if the parent specified only + * need be an ancestral parent, true, or the direct parent, false. + * Default is false + */ +mxGraph.prototype.getEdges = function(cell, parent, incoming, outgoing, includeLoops, recurse) +{ + incoming = (incoming != null) ? incoming : true; + outgoing = (outgoing != null) ? outgoing : true; + includeLoops = (includeLoops != null) ? includeLoops : true; + recurse = (recurse != null) ? recurse : false; + + var edges = []; + var isCollapsed = this.isCellCollapsed(cell); + var childCount = this.model.getChildCount(cell); + + for (var i = 0; i < childCount; i++) + { + var child = this.model.getChildAt(cell, i); + + if (isCollapsed || !this.isCellVisible(child)) + { + edges = edges.concat(this.model.getEdges(child, incoming, outgoing)); + } + } + + edges = edges.concat(this.model.getEdges(cell, incoming, outgoing)); + var result = []; + + for (var i = 0; i < edges.length; i++) + { + var state = this.view.getState(edges[i]); + + var source = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[i], true); + var target = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[i], false); + + if ((includeLoops && source == target) || ((source != target) && ((incoming && + target == cell && (parent == null || this.isValidAncestor(source, parent, recurse))) || + (outgoing && source == cell && (parent == null || + this.isValidAncestor(target, parent, recurse)))))) + { + result.push(edges[i]); + } + } + + return result; +}; + +/** + * Function: isValidAncestor + * + * Returns whether or not the specified parent is a valid + * ancestor of the specified cell, either direct or indirectly + * based on whether ancestor recursion is enabled. + * + * Parameters: + * + * cell - the possible child cell + * parent - the possible parent cell + * recurse - boolean whether or not to recurse the child ancestors + */ +mxGraph.prototype.isValidAncestor = function(cell, parent, recurse) +{ + return (recurse ? this.model.isAncestor(parent, cell) : this.model + .getParent(cell) == parent); +}; + +/** + * Function: getOpposites + * + * Returns all distinct visible opposite cells for the specified terminal + * on the given edges. + * + * Parameters: + * + * edges - Array of that contains the edges whose opposite + * terminals should be returned. + * terminal - Terminal that specifies the end whose opposite should be + * returned. + * source - Optional boolean that specifies if source terminals should be + * included in the result. Default is true. + * targets - Optional boolean that specifies if targer terminals should be + * included in the result. Default is true. + */ +mxGraph.prototype.getOpposites = function(edges, terminal, sources, targets) +{ + sources = (sources != null) ? sources : true; + targets = (targets != null) ? targets : true; + + var terminals = []; + + // Fast lookup to avoid duplicates in terminals array + var dict = new mxDictionary(); + + if (edges != null) + { + for (var i = 0; i < edges.length; i++) + { + var state = this.view.getState(edges[i]); + + var source = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[i], true); + var target = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[i], false); + + // Checks if the terminal is the source of the edge and if the + // target should be stored in the result + if (source == terminal && target != null && target != terminal && targets) + { + if (!dict.get(target)) + { + dict.put(target, true); + terminals.push(target); + } + } + + // Checks if the terminal is the taget of the edge and if the + // source should be stored in the result + else if (target == terminal && source != null && source != terminal && sources) + { + if (!dict.get(source)) + { + dict.put(source, true); + terminals.push(source); + } + } + } + } + + return terminals; +}; + +/** + * Function: getEdgesBetween + * + * Returns the edges between the given source and target. This takes into + * account collapsed and invisible cells and returns the connected edges + * as displayed on the screen. + * + * Parameters: + * + * source - + * target - + * directed - + */ +mxGraph.prototype.getEdgesBetween = function(source, target, directed) +{ + directed = (directed != null) ? directed : false; + var edges = this.getEdges(source); + var result = []; + + // Checks if the edge is connected to the correct + // cell and returns the first match + for (var i = 0; i < edges.length; i++) + { + var state = this.view.getState(edges[i]); + + var src = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[i], true); + var trg = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[i], false); + + if ((src == source && trg == target) || (!directed && src == target && trg == source)) + { + result.push(edges[i]); + } + } + + return result; +}; + +/** + * Function: getPointForEvent + * + * Returns an representing the given event in the unscaled, + * non-translated coordinate space of and applies the grid. + * + * Parameters: + * + * evt - Mousevent that contains the mouse pointer location. + * addOffset - Optional boolean that specifies if the position should be + * offset by half of the . Default is true. + */ + mxGraph.prototype.getPointForEvent = function(evt, addOffset) + { + var p = mxUtils.convertPoint(this.container, + mxEvent.getClientX(evt), mxEvent.getClientY(evt)); + + var s = this.view.scale; + var tr = this.view.translate; + var off = (addOffset != false) ? this.gridSize / 2 : 0; + + p.x = this.snap(p.x / s - tr.x - off); + p.y = this.snap(p.y / s - tr.y - off); + + return p; + }; + +/** + * Function: getCells + * + * Returns the child vertices and edges of the given parent that are contained + * in the given rectangle. The result is added to the optional result array, + * which is returned. If no result array is specified then a new array is + * created and returned. + * + * Parameters: + * + * x - X-coordinate of the rectangle. + * y - Y-coordinate of the rectangle. + * width - Width of the rectangle. + * height - Height of the rectangle. + * parent - that should be used as the root of the recursion. + * Default is current root of the view or the root of the model. + * result - Optional array to store the result in. + */ +mxGraph.prototype.getCells = function(x, y, width, height, parent, result) +{ + result = (result != null) ? result : []; + + if (width > 0 || height > 0) + { + var model = this.getModel(); + var right = x + width; + var bottom = y + height; + + if (parent == null) + { + parent = this.getCurrentRoot(); + + if (parent == null) + { + parent = model.getRoot(); + } + } + + if (parent != null) + { + var childCount = model.getChildCount(parent); + + for (var i = 0; i < childCount; i++) + { + var cell = model.getChildAt(parent, i); + var state = this.view.getState(cell); + + if (state != null && this.isCellVisible(cell)) + { + var deg = mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION) || 0; + var box = state; + + if (deg != 0) + { + box = mxUtils.getBoundingBox(box, deg); + } + + if ((model.isEdge(cell) || model.isVertex(cell)) && + box.x >= x && box.y + box.height <= bottom && + box.y >= y && box.x + box.width <= right) + { + result.push(cell); + } + else + { + this.getCells(x, y, width, height, cell, result); + } + } + } + } + } + + return result; +}; + +/** + * Function: getCellsBeyond + * + * Returns the children of the given parent that are contained in the + * halfpane from the given point (x0, y0) rightwards or downwards + * depending on rightHalfpane and bottomHalfpane. + * + * Parameters: + * + * x0 - X-coordinate of the origin. + * y0 - Y-coordinate of the origin. + * parent - Optional whose children should be checked. Default is + * . + * rightHalfpane - Boolean indicating if the cells in the right halfpane + * from the origin should be returned. + * bottomHalfpane - Boolean indicating if the cells in the bottom halfpane + * from the origin should be returned. + */ +mxGraph.prototype.getCellsBeyond = function(x0, y0, parent, rightHalfpane, bottomHalfpane) +{ + var result = []; + + if (rightHalfpane || bottomHalfpane) + { + if (parent == null) + { + parent = this.getDefaultParent(); + } + + if (parent != null) + { + var childCount = this.model.getChildCount(parent); + + for (var i = 0; i < childCount; i++) + { + var child = this.model.getChildAt(parent, i); + var state = this.view.getState(child); + + if (this.isCellVisible(child) && state != null) + { + if ((!rightHalfpane || state.x >= x0) && + (!bottomHalfpane || state.y >= y0)) + { + result.push(child); + } + } + } + } + } + + return result; +}; + +/** + * Function: findTreeRoots + * + * Returns all children in the given parent which do not have incoming + * edges. If the result is empty then the with the greatest difference + * between incoming and outgoing edges is returned. + * + * Parameters: + * + * parent - whose children should be checked. + * isolate - Optional boolean that specifies if edges should be ignored if + * the opposite end is not a child of the given parent cell. Default is + * false. + * invert - Optional boolean that specifies if outgoing or incoming edges + * should be counted for a tree root. If false then outgoing edges will be + * counted. Default is false. + */ +mxGraph.prototype.findTreeRoots = function(parent, isolate, invert) +{ + isolate = (isolate != null) ? isolate : false; + invert = (invert != null) ? invert : false; + var roots = []; + + if (parent != null) + { + var model = this.getModel(); + var childCount = model.getChildCount(parent); + var best = null; + var maxDiff = 0; + + for (var i=0; i 0) || + (!invert && fanIn == 0 && fanOut > 0)) + { + roots.push(cell); + } + + var diff = (invert) ? fanIn - fanOut : fanOut - fanIn; + + if (diff > maxDiff) + { + maxDiff = diff; + best = cell; + } + } + } + + if (roots.length == 0 && best != null) + { + roots.push(best); + } + } + + return roots; +}; + +/** + * Function: traverse + * + * Traverses the (directed) graph invoking the given function for each + * visited vertex and edge. The function is invoked with the current vertex + * and the incoming edge as a parameter. This implementation makes sure + * each vertex is only visited once. The function may return false if the + * traversal should stop at the given vertex. + * + * Example: + * + * (code) + * mxLog.show(); + * var cell = graph.getSelectionCell(); + * graph.traverse(cell, false, function(vertex, edge) + * { + * mxLog.debug(graph.getLabel(vertex)); + * }); + * (end) + * + * Parameters: + * + * vertex - that represents the vertex where the traversal starts. + * directed - Optional boolean indicating if edges should only be traversed + * from source to target. Default is true. + * func - Visitor function that takes the current vertex and the incoming + * edge as arguments. The traversal stops if the function returns false. + * edge - Optional that represents the incoming edge. This is + * null for the first step of the traversal. + * visited - Optional from cells to true for the visited cells. + */ +mxGraph.prototype.traverse = function(vertex, directed, func, edge, visited) +{ + if (func != null && vertex != null) + { + directed = (directed != null) ? directed : true; + visited = visited || new mxDictionary(); + + if (!visited.get(vertex)) + { + visited.put(vertex, true); + var result = func(vertex, edge); + + if (result == null || result) + { + var edgeCount = this.model.getEdgeCount(vertex); + + if (edgeCount > 0) + { + for (var i = 0; i < edgeCount; i++) + { + var e = this.model.getEdgeAt(vertex, i); + var isSource = this.model.getTerminal(e, true) == vertex; + + if (!directed || isSource) + { + var next = this.model.getTerminal(e, !isSource); + this.traverse(next, directed, func, e, visited); + } + } + } + } + } + } +}; + +/** + * Group: Selection + */ + +/** + * Function: isCellSelected + * + * Returns true if the given cell is selected. + * + * Parameters: + * + * cell - for which the selection state should be returned. + */ +mxGraph.prototype.isCellSelected = function(cell) +{ + return this.getSelectionModel().isSelected(cell); +}; + +/** + * Function: isSelectionEmpty + * + * Returns true if the selection is empty. + */ +mxGraph.prototype.isSelectionEmpty = function() +{ + return this.getSelectionModel().isEmpty(); +}; + +/** + * Function: clearSelection + * + * Clears the selection using . + */ +mxGraph.prototype.clearSelection = function() +{ + return this.getSelectionModel().clear(); +}; + +/** + * Function: getSelectionCount + * + * Returns the number of selected cells. + */ +mxGraph.prototype.getSelectionCount = function() +{ + return this.getSelectionModel().cells.length; +}; + +/** + * Function: getSelectionCell + * + * Returns the first cell from the array of selected . + */ +mxGraph.prototype.getSelectionCell = function() +{ + return this.getSelectionModel().cells[0]; +}; + +/** + * Function: getSelectionCells + * + * Returns the array of selected . + */ +mxGraph.prototype.getSelectionCells = function() +{ + return this.getSelectionModel().cells.slice(); +}; + +/** + * Function: setSelectionCell + * + * Sets the selection cell. + * + * Parameters: + * + * cell - to be selected. + */ +mxGraph.prototype.setSelectionCell = function(cell) +{ + this.getSelectionModel().setCell(cell); +}; + +/** + * Function: setSelectionCells + * + * Sets the selection cell. + * + * Parameters: + * + * cells - Array of to be selected. + */ +mxGraph.prototype.setSelectionCells = function(cells) +{ + this.getSelectionModel().setCells(cells); +}; + +/** + * Function: addSelectionCell + * + * Adds the given cell to the selection. + * + * Parameters: + * + * cell - to be add to the selection. + */ +mxGraph.prototype.addSelectionCell = function(cell) +{ + this.getSelectionModel().addCell(cell); +}; + +/** + * Function: addSelectionCells + * + * Adds the given cells to the selection. + * + * Parameters: + * + * cells - Array of to be added to the selection. + */ +mxGraph.prototype.addSelectionCells = function(cells) +{ + this.getSelectionModel().addCells(cells); +}; + +/** + * Function: removeSelectionCell + * + * Removes the given cell from the selection. + * + * Parameters: + * + * cell - to be removed from the selection. + */ +mxGraph.prototype.removeSelectionCell = function(cell) +{ + this.getSelectionModel().removeCell(cell); +}; + +/** + * Function: removeSelectionCells + * + * Removes the given cells from the selection. + * + * Parameters: + * + * cells - Array of to be removed from the selection. + */ +mxGraph.prototype.removeSelectionCells = function(cells) +{ + this.getSelectionModel().removeCells(cells); +}; + +/** + * Function: selectRegion + * + * Selects and returns the cells inside the given rectangle for the + * specified event. + * + * Parameters: + * + * rect - that represents the region to be selected. + * evt - Mouseevent that triggered the selection. + */ +mxGraph.prototype.selectRegion = function(rect, evt) +{ + var cells = this.getCells(rect.x, rect.y, rect.width, rect.height); + this.selectCellsForEvent(cells, evt); + + return cells; +}; + +/** + * Function: selectNextCell + * + * Selects the next cell. + */ +mxGraph.prototype.selectNextCell = function() +{ + this.selectCell(true); +}; + +/** + * Function: selectPreviousCell + * + * Selects the previous cell. + */ +mxGraph.prototype.selectPreviousCell = function() +{ + this.selectCell(); +}; + +/** + * Function: selectParentCell + * + * Selects the parent cell. + */ +mxGraph.prototype.selectParentCell = function() +{ + this.selectCell(false, true); +}; + +/** + * Function: selectChildCell + * + * Selects the first child cell. + */ +mxGraph.prototype.selectChildCell = function() +{ + this.selectCell(false, false, true); +}; + +/** + * Function: selectCell + * + * Selects the next, parent, first child or previous cell, if all arguments + * are false. + * + * Parameters: + * + * isNext - Boolean indicating if the next cell should be selected. + * isParent - Boolean indicating if the parent cell should be selected. + * isChild - Boolean indicating if the first child cell should be selected. + */ +mxGraph.prototype.selectCell = function(isNext, isParent, isChild) +{ + var sel = this.selectionModel; + var cell = (sel.cells.length > 0) ? sel.cells[0] : null; + + if (sel.cells.length > 1) + { + sel.clear(); + } + + var parent = (cell != null) ? + this.model.getParent(cell) : + this.getDefaultParent(); + + var childCount = this.model.getChildCount(parent); + + if (cell == null && childCount > 0) + { + var child = this.model.getChildAt(parent, 0); + this.setSelectionCell(child); + } + else if ((cell == null || isParent) && + this.view.getState(parent) != null && + this.model.getGeometry(parent) != null) + { + if (this.getCurrentRoot() != parent) + { + this.setSelectionCell(parent); + } + } + else if (cell != null && isChild) + { + var tmp = this.model.getChildCount(cell); + + if (tmp > 0) + { + var child = this.model.getChildAt(cell, 0); + this.setSelectionCell(child); + } + } + else if (childCount > 0) + { + var i = parent.getIndex(cell); + + if (isNext) + { + i++; + var child = this.model.getChildAt(parent, i % childCount); + this.setSelectionCell(child); + } + else + { + i--; + var index = (i < 0) ? childCount - 1 : i; + var child = this.model.getChildAt(parent, index); + this.setSelectionCell(child); + } + } +}; + +/** + * Function: selectAll + * + * Selects all children of the given parent cell or the children of the + * default parent if no parent is specified. To select leaf vertices and/or + * edges use . + * + * Parameters: + * + * parent - Optional whose children should be selected. + * Default is . + * descendants - Optional boolean specifying whether all descendants should be + * selected. Default is false. + */ +mxGraph.prototype.selectAll = function(parent, descendants) +{ + parent = parent || this.getDefaultParent(); + + var cells = (descendants) ? this.model.filterDescendants(function(cell) + { + return cell != parent; + }, parent) : this.model.getChildren(parent); + + if (cells != null) + { + this.setSelectionCells(cells); + } +}; + +/** + * Function: selectVertices + * + * Select all vertices inside the given parent or the default parent. + */ +mxGraph.prototype.selectVertices = function(parent) +{ + this.selectCells(true, false, parent); +}; + +/** + * Function: selectVertices + * + * Select all vertices inside the given parent or the default parent. + */ +mxGraph.prototype.selectEdges = function(parent) +{ + this.selectCells(false, true, parent); +}; + +/** + * Function: selectCells + * + * Selects all vertices and/or edges depending on the given boolean + * arguments recursively, starting at the given parent or the default + * parent if no parent is specified. Use to select all cells. + * For vertices, only cells with no children are selected. + * + * Parameters: + * + * vertices - Boolean indicating if vertices should be selected. + * edges - Boolean indicating if edges should be selected. + * parent - Optional that acts as the root of the recursion. + * Default is . + */ +mxGraph.prototype.selectCells = function(vertices, edges, parent) +{ + parent = parent || this.getDefaultParent(); + + var filter = mxUtils.bind(this, function(cell) + { + return this.view.getState(cell) != null && + ((this.model.getChildCount(cell) == 0 && this.model.isVertex(cell) && vertices + && !this.model.isEdge(this.model.getParent(cell))) || + (this.model.isEdge(cell) && edges)); + }); + + var cells = this.model.filterDescendants(filter, parent); + this.setSelectionCells(cells); +}; + +/** + * Function: selectCellForEvent + * + * Selects the given cell by either adding it to the selection or + * replacing the selection depending on whether the given mouse event is a + * toggle event. + * + * Parameters: + * + * cell - to be selected. + * evt - Optional mouseevent that triggered the selection. + */ +mxGraph.prototype.selectCellForEvent = function(cell, evt) +{ + var isSelected = this.isCellSelected(cell); + + if (this.isToggleEvent(evt)) + { + if (isSelected) + { + this.removeSelectionCell(cell); + } + else + { + this.addSelectionCell(cell); + } + } + else if (!isSelected || this.getSelectionCount() != 1) + { + this.setSelectionCell(cell); + } +}; + +/** + * Function: selectCellsForEvent + * + * Selects the given cells by either adding them to the selection or + * replacing the selection depending on whether the given mouse event is a + * toggle event. + * + * Parameters: + * + * cells - Array of to be selected. + * evt - Optional mouseevent that triggered the selection. + */ +mxGraph.prototype.selectCellsForEvent = function(cells, evt) +{ + if (this.isToggleEvent(evt)) + { + this.addSelectionCells(cells); + } + else + { + this.setSelectionCells(cells); + } +}; + +/** + * Group: Selection state + */ + +/** + * Function: createHandler + * + * Creates a new handler for the given cell state. This implementation + * returns a new of the corresponding cell is an edge, + * otherwise it returns an . + * + * Parameters: + * + * state - whose handler should be created. + */ +mxGraph.prototype.createHandler = function(state) +{ + var result = null; + + if (state != null) + { + if (this.model.isEdge(state.cell)) + { + var source = state.getVisibleTerminalState(true); + var target = state.getVisibleTerminalState(false); + var geo = this.getCellGeometry(state.cell); + + var edgeStyle = this.view.getEdgeStyle(state, (geo != null) ? geo.points : null, source, target); + result = this.createEdgeHandler(state, edgeStyle); + } + else + { + result = this.createVertexHandler(state); + } + } + + return result; +}; + +/** + * Function: createVertexHandler + * + * Hooks to create a new for the given . + * + * Parameters: + * + * state - to create the handler for. + */ +mxGraph.prototype.createVertexHandler = function(state) +{ + return new mxVertexHandler(state); +}; + +/** + * Function: createEdgeHandler + * + * Hooks to create a new for the given . + * + * Parameters: + * + * state - to create the handler for. + */ +mxGraph.prototype.createEdgeHandler = function(state, edgeStyle) +{ + var result = null; + + if (edgeStyle == mxEdgeStyle.Loop || + edgeStyle == mxEdgeStyle.ElbowConnector || + edgeStyle == mxEdgeStyle.SideToSide || + edgeStyle == mxEdgeStyle.TopToBottom) + { + result = this.createElbowEdgeHandler(state); + } + else if (edgeStyle == mxEdgeStyle.SegmentConnector || + edgeStyle == mxEdgeStyle.OrthConnector) + { + result = this.createEdgeSegmentHandler(state); + } + else + { + result = new mxEdgeHandler(state); + } + + return result; +}; + +/** + * Function: createEdgeSegmentHandler + * + * Hooks to create a new for the given . + * + * Parameters: + * + * state - to create the handler for. + */ +mxGraph.prototype.createEdgeSegmentHandler = function(state) +{ + return new mxEdgeSegmentHandler(state); +}; + +/** + * Function: createElbowEdgeHandler + * + * Hooks to create a new for the given . + * + * Parameters: + * + * state - to create the handler for. + */ +mxGraph.prototype.createElbowEdgeHandler = function(state) +{ + return new mxElbowEdgeHandler(state); +}; + +/** + * Group: Graph events + */ + +/** + * Function: addMouseListener + * + * Adds a listener to the graph event dispatch loop. The listener + * must implement the mouseDown, mouseMove and mouseUp methods + * as shown in the class. + * + * Parameters: + * + * listener - Listener to be added to the graph event listeners. + */ +mxGraph.prototype.addMouseListener = function(listener) +{ + if (this.mouseListeners == null) + { + this.mouseListeners = []; + } + + this.mouseListeners.push(listener); +}; + +/** + * Function: removeMouseListener + * + * Removes the specified graph listener. + * + * Parameters: + * + * listener - Listener to be removed from the graph event listeners. + */ +mxGraph.prototype.removeMouseListener = function(listener) +{ + if (this.mouseListeners != null) + { + for (var i = 0; i < this.mouseListeners.length; i++) + { + if (this.mouseListeners[i] == listener) + { + this.mouseListeners.splice(i, 1); + break; + } + } + } +}; + +/** + * Function: updateMouseEvent + * + * Sets the graphX and graphY properties if the given if + * required and returned the event. + * + * Parameters: + * + * me - to be updated. + * evtName - Name of the mouse event. + */ +mxGraph.prototype.updateMouseEvent = function(me, evtName) +{ + if (me.graphX == null || me.graphY == null) + { + var pt = mxUtils.convertPoint(this.container, me.getX(), me.getY()); + + me.graphX = pt.x - this.panDx; + me.graphY = pt.y - this.panDy; + + // Searches for rectangles using method if native hit detection is disabled on shape + if (me.getCell() == null && this.isMouseDown && evtName == mxEvent.MOUSE_MOVE) + { + me.state = this.view.getState(this.getCellAt(pt.x, pt.y, null, null, null, function(state) + { + return state.shape == null || state.shape.paintBackground != mxRectangleShape.prototype.paintBackground || + mxUtils.getValue(state.style, mxConstants.STYLE_POINTER_EVENTS, '1') == '1' || + (state.shape.fill != null && state.shape.fill != mxConstants.NONE); + })); + } + } + + return me; +}; + +/** + * Function: getStateForEvent + * + * Returns the state for the given touch event. + */ +mxGraph.prototype.getStateForTouchEvent = function(evt) +{ + var x = mxEvent.getClientX(evt); + var y = mxEvent.getClientY(evt); + + // Dispatches the drop event to the graph which + // consumes and executes the source function + var pt = mxUtils.convertPoint(this.container, x, y); + + return this.view.getState(this.getCellAt(pt.x, pt.y)); +}; + +/** + * Function: isEventIgnored + * + * Returns true if the event should be ignored in . + */ +mxGraph.prototype.isEventIgnored = function(evtName, me, sender) +{ + var mouseEvent = mxEvent.isMouseEvent(me.getEvent()); + var result = false; + + // Drops events that are fired more than once + if (me.getEvent() == this.lastEvent) + { + result = true; + } + else + { + this.lastEvent = me.getEvent(); + } + + // Installs event listeners to capture the complete gesture from the event source + // for non-MS touch events as a workaround for all events for the same geture being + // fired from the event source even if that was removed from the DOM. + if (this.eventSource != null && evtName != mxEvent.MOUSE_MOVE) + { + mxEvent.removeGestureListeners(this.eventSource, null, this.mouseMoveRedirect, this.mouseUpRedirect); + this.mouseMoveRedirect = null; + this.mouseUpRedirect = null; + this.eventSource = null; + } + else if (this.eventSource != null && me.getSource() != this.eventSource) + { + result = true; + } + else if (mxClient.IS_TOUCH && evtName == mxEvent.MOUSE_DOWN && !mouseEvent) + { + this.eventSource = me.getSource(); + + this.mouseMoveRedirect = mxUtils.bind(this, function(evt) + { + this.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, this.getStateForTouchEvent(evt))); + }); + this.mouseUpRedirect = mxUtils.bind(this, function(evt) + { + this.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, this.getStateForTouchEvent(evt))); + }); + + mxEvent.addGestureListeners(this.eventSource, null, this.mouseMoveRedirect, this.mouseUpRedirect); + } + + // Factored out the workarounds for FF to make it easier to override/remove + // Note this method has side-effects! + if (this.isSyntheticEventIgnored(evtName, me, sender)) + { + result = true; + } + + // Never fires mouseUp/-Down for double clicks + if (!mxEvent.isPopupTrigger(this.lastEvent) && evtName != mxEvent.MOUSE_MOVE && this.lastEvent.detail == 2) + { + return true; + } + + // Filters out of sequence events or mixed event types during a gesture + if (evtName == mxEvent.MOUSE_UP && this.isMouseDown) + { + this.isMouseDown = false; + } + else if (evtName == mxEvent.MOUSE_DOWN && !this.isMouseDown) + { + this.isMouseDown = true; + this.isMouseTrigger = mouseEvent; + } + // Drops mouse events that are fired during touch gestures as a workaround for Webkit + // and mouse events that are not in sync with the current internal button state + else if (!result && (((!mxClient.IS_FF || evtName != mxEvent.MOUSE_MOVE) && + this.isMouseDown && this.isMouseTrigger != mouseEvent) || + (evtName == mxEvent.MOUSE_DOWN && this.isMouseDown) || + (evtName == mxEvent.MOUSE_UP && !this.isMouseDown))) + { + result = true; + } + + if (!result && evtName == mxEvent.MOUSE_DOWN) + { + this.lastMouseX = me.getX(); + this.lastMouseY = me.getY(); + } + + return result; +}; + +/** + * Function: isSyntheticEventIgnored + * + * Hook for ignoring synthetic mouse events after touchend in Firefox. + */ +mxGraph.prototype.isSyntheticEventIgnored = function(evtName, me, sender) +{ + var result = false; + var mouseEvent = mxEvent.isMouseEvent(me.getEvent()); + + // LATER: This does not cover all possible cases that can go wrong in FF + if (this.ignoreMouseEvents && mouseEvent && evtName != mxEvent.MOUSE_MOVE) + { + this.ignoreMouseEvents = evtName != mxEvent.MOUSE_UP; + result = true; + } + else if (mxClient.IS_FF && !mouseEvent && evtName == mxEvent.MOUSE_UP) + { + this.ignoreMouseEvents = true; + } + + return result; +}; + +/** + * Function: isEventSourceIgnored + * + * Returns true if the event should be ignored in . This + * implementation returns true for select, option and input (if not of type + * checkbox, radio, button, submit or file) event sources if the event is not + * a mouse event or a left mouse button press event. + * + * Parameters: + * + * evtName - The name of the event. + * me - that should be ignored. + */ +mxGraph.prototype.isEventSourceIgnored = function(evtName, me) +{ + var source = me.getSource(); + var name = (source.nodeName != null) ? source.nodeName.toLowerCase() : ''; + var candidate = !mxEvent.isMouseEvent(me.getEvent()) || mxEvent.isLeftMouseButton(me.getEvent()); + + return evtName == mxEvent.MOUSE_DOWN && candidate && (name == 'select' || name == 'option' || + (name == 'input' && source.type != 'checkbox' && source.type != 'radio' && + source.type != 'button' && source.type != 'submit' && source.type != 'file')); +}; + +/** + * Function: getEventState + * + * Returns the to be used when firing the mouse event for the + * given state. This implementation returns the given state. + * + * Parameters: + * + * - State whose event source should be returned. + */ +mxGraph.prototype.getEventState = function(state) +{ + return state; +}; + +/** + * Function: fireMouseEvent + * + * Dispatches the given event in the graph event dispatch loop. Possible + * event names are , and + * . All listeners are invoked for all events regardless + * of the consumed state of the event. + * + * Parameters: + * + * evtName - String that specifies the type of event to be dispatched. + * me - to be fired. + * sender - Optional sender argument. Default is this. + */ +mxGraph.prototype.fireMouseEvent = function(evtName, me, sender) +{ + if (this.isEventSourceIgnored(evtName, me)) + { + if (this.tooltipHandler != null) + { + this.tooltipHandler.hide(); + } + + return; + } + + if (sender == null) + { + sender = this; + } + + // Updates the graph coordinates in the event + me = this.updateMouseEvent(me, evtName); + + // Detects and processes double taps for touch-based devices which do not have native double click events + // or where detection of double click is not always possible (quirks, IE10+). Note that this can only handle + // double clicks on cells because the sequence of events in IE prevents detection on the background, it fires + // two mouse ups, one of which without a cell but no mousedown for the second click which means we cannot + // detect which mouseup(s) are part of the first click, ie we do not know when the first click ends. + if ((!this.nativeDblClickEnabled && !mxEvent.isPopupTrigger(me.getEvent())) || (this.doubleTapEnabled && + mxClient.IS_TOUCH && mxEvent.isTouchEvent(me.getEvent()))) + { + var currentTime = new Date().getTime(); + + // NOTE: Second mouseDown for double click missing in quirks mode + if ((!mxClient.IS_QUIRKS && evtName == mxEvent.MOUSE_DOWN) || (mxClient.IS_QUIRKS && evtName == mxEvent.MOUSE_UP && !this.fireDoubleClick)) + { + if (this.lastTouchEvent != null && this.lastTouchEvent != me.getEvent() && + currentTime - this.lastTouchTime < this.doubleTapTimeout && + Math.abs(this.lastTouchX - me.getX()) < this.doubleTapTolerance && + Math.abs(this.lastTouchY - me.getY()) < this.doubleTapTolerance && + this.doubleClickCounter < 2) + { + this.doubleClickCounter++; + var doubleClickFired = false; + + if (evtName == mxEvent.MOUSE_UP) + { + if (me.getCell() == this.lastTouchCell && this.lastTouchCell != null) + { + this.lastTouchTime = 0; + var cell = this.lastTouchCell; + this.lastTouchCell = null; + + // Fires native dblclick event via event source + // NOTE: This fires two double click events on edges in quirks mode. While + // trying to fix this, we realized that nativeDoubleClick can be disabled for + // quirks and IE10+ (or we didn't find the case mentioned above where it + // would not work), ie. all double clicks seem to be working without this. + if (mxClient.IS_QUIRKS) + { + me.getSource().fireEvent('ondblclick'); + } + + this.dblClick(me.getEvent(), cell); + doubleClickFired = true; + } + } + else + { + this.fireDoubleClick = true; + this.lastTouchTime = 0; + } + + // Do not ignore mouse up in quirks in this case + if (!mxClient.IS_QUIRKS || doubleClickFired) + { + mxEvent.consume(me.getEvent()); + return; + } + } + else if (this.lastTouchEvent == null || this.lastTouchEvent != me.getEvent()) + { + this.lastTouchCell = me.getCell(); + this.lastTouchX = me.getX(); + this.lastTouchY = me.getY(); + this.lastTouchTime = currentTime; + this.lastTouchEvent = me.getEvent(); + this.doubleClickCounter = 0; + } + } + else if ((this.isMouseDown || evtName == mxEvent.MOUSE_UP) && this.fireDoubleClick) + { + this.fireDoubleClick = false; + var cell = this.lastTouchCell; + this.lastTouchCell = null; + this.isMouseDown = false; + + // Workaround for Chrome/Safari not firing native double click events for double touch on background + var valid = (cell != null) || (mxEvent.isTouchEvent(me.getEvent()) && (mxClient.IS_GC || mxClient.IS_SF)); + + if (valid && Math.abs(this.lastTouchX - me.getX()) < this.doubleTapTolerance && + Math.abs(this.lastTouchY - me.getY()) < this.doubleTapTolerance) + { + this.dblClick(me.getEvent(), cell); + } + else + { + mxEvent.consume(me.getEvent()); + } + + return; + } + } + + if (!this.isEventIgnored(evtName, me, sender)) + { + // Updates the event state via getEventState + me.state = this.getEventState(me.getState()); + this.fireEvent(new mxEventObject(mxEvent.FIRE_MOUSE_EVENT, 'eventName', evtName, 'event', me)); + + if ((mxClient.IS_OP || mxClient.IS_SF || mxClient.IS_GC || mxClient.IS_IE11 || + (mxClient.IS_IE && mxClient.IS_SVG) || me.getEvent().target != this.container)) + { + if (evtName == mxEvent.MOUSE_MOVE && this.isMouseDown && this.autoScroll && !mxEvent.isMultiTouchEvent(me.getEvent)) + { + this.scrollPointToVisible(me.getGraphX(), me.getGraphY(), this.autoExtend); + } + else if (evtName == mxEvent.MOUSE_UP && this.ignoreScrollbars && this.translateToScrollPosition && + (this.container.scrollLeft != 0 || this.container.scrollTop != 0)) + { + var s = this.view.scale; + var tr = this.view.translate; + this.view.setTranslate(tr.x - this.container.scrollLeft / s, tr.y - this.container.scrollTop / s); + this.container.scrollLeft = 0; + this.container.scrollTop = 0; + } + + if (this.mouseListeners != null) + { + var args = [sender, me]; + + // Does not change returnValue in Opera + if (!me.getEvent().preventDefault) + { + me.getEvent().returnValue = true; + } + + for (var i = 0; i < this.mouseListeners.length; i++) + { + var l = this.mouseListeners[i]; + + if (evtName == mxEvent.MOUSE_DOWN) + { + l.mouseDown.apply(l, args); + } + else if (evtName == mxEvent.MOUSE_MOVE) + { + l.mouseMove.apply(l, args); + } + else if (evtName == mxEvent.MOUSE_UP) + { + l.mouseUp.apply(l, args); + } + } + } + + // Invokes the click handler + if (evtName == mxEvent.MOUSE_UP) + { + this.click(me); + } + } + + // Detects tapAndHold events using a timer + if (mxEvent.isTouchEvent(me.getEvent()) && evtName == mxEvent.MOUSE_DOWN && + this.tapAndHoldEnabled && !this.tapAndHoldInProgress) + { + this.tapAndHoldInProgress = true; + this.initialTouchX = me.getGraphX(); + this.initialTouchY = me.getGraphY(); + + var handler = function() + { + if (this.tapAndHoldValid) + { + this.tapAndHold(me); + } + + this.tapAndHoldInProgress = false; + this.tapAndHoldValid = false; + }; + + if (this.tapAndHoldThread) + { + window.clearTimeout(this.tapAndHoldThread); + } + + this.tapAndHoldThread = window.setTimeout(mxUtils.bind(this, handler), this.tapAndHoldDelay); + this.tapAndHoldValid = true; + } + else if (evtName == mxEvent.MOUSE_UP) + { + this.tapAndHoldInProgress = false; + this.tapAndHoldValid = false; + } + else if (this.tapAndHoldValid) + { + this.tapAndHoldValid = + Math.abs(this.initialTouchX - me.getGraphX()) < this.tolerance && + Math.abs(this.initialTouchY - me.getGraphY()) < this.tolerance; + } + + // Stops editing for all events other than from cellEditor + if (evtName == mxEvent.MOUSE_DOWN && this.isEditing() && !this.cellEditor.isEventSource(me.getEvent())) + { + this.stopEditing(!this.isInvokesStopCellEditing()); + } + + this.consumeMouseEvent(evtName, me, sender); + } +}; + +/** + * Function: consumeMouseEvent + * + * Destroys the graph and all its resources. + */ +mxGraph.prototype.consumeMouseEvent = function(evtName, me, sender) +{ + // Workaround for duplicate click in Windows 8 with Chrome/FF/Opera with touch + if (evtName == mxEvent.MOUSE_DOWN && mxEvent.isTouchEvent(me.getEvent())) + { + me.consume(false); + } +}; + +/** + * Function: fireGestureEvent + * + * Dispatches a event. The following example will resize the + * cell under the mouse based on the scale property of the native touch event. + * + * (code) + * graph.addListener(mxEvent.GESTURE, function(sender, eo) + * { + * var evt = eo.getProperty('event'); + * var state = graph.view.getState(eo.getProperty('cell')); + * + * if (graph.isEnabled() && graph.isCellResizable(state.cell) && Math.abs(1 - evt.scale) > 0.2) + * { + * var scale = graph.view.scale; + * var tr = graph.view.translate; + * + * var w = state.width * evt.scale; + * var h = state.height * evt.scale; + * var x = state.x - (w - state.width) / 2; + * var y = state.y - (h - state.height) / 2; + * + * var bounds = new mxRectangle(graph.snap(x / scale) - tr.x, + * graph.snap(y / scale) - tr.y, graph.snap(w / scale), graph.snap(h / scale)); + * graph.resizeCell(state.cell, bounds); + * eo.consume(); + * } + * }); + * (end) + * + * Parameters: + * + * evt - Gestureend event that represents the gesture. + * cell - Optional associated with the gesture. + */ +mxGraph.prototype.fireGestureEvent = function(evt, cell) +{ + // Resets double tap event handling when gestures take place + this.lastTouchTime = 0; + this.fireEvent(new mxEventObject(mxEvent.GESTURE, 'event', evt, 'cell', cell)); +}; + +/** + * Function: destroy + * + * Destroys the graph and all its resources. + */ +mxGraph.prototype.destroy = function() +{ + if (!this.destroyed) + { + this.destroyed = true; + + if (this.tooltipHandler != null) + { + this.tooltipHandler.destroy(); + } + + if (this.selectionCellsHandler != null) + { + this.selectionCellsHandler.destroy(); + } + + if (this.panningHandler != null) + { + this.panningHandler.destroy(); + } + + if (this.popupMenuHandler != null) + { + this.popupMenuHandler.destroy(); + } + + if (this.connectionHandler != null) + { + this.connectionHandler.destroy(); + } + + if (this.graphHandler != null) + { + this.graphHandler.destroy(); + } + + if (this.cellEditor != null) + { + this.cellEditor.destroy(); + } + + if (this.view != null) + { + this.view.destroy(); + } + + if (this.model != null && this.graphModelChangeListener != null) + { + this.model.removeListener(this.graphModelChangeListener); + this.graphModelChangeListener = null; + } + + this.container = null; + } +};