summaryrefslogtreecommitdiff
path: root/src/js/view/mxCellRenderer.js
diff options
context:
space:
mode:
authorAdhitya Kamakshidasan2016-04-04 20:02:27 +0530
committerAdhitya Kamakshidasan2016-04-04 20:02:27 +0530
commit5d474b6e265806c9df3fc80e06f8b4dd7fe16aea (patch)
treef64a5027d57f49b9833a8eea48acbd0905a1ceb3 /src/js/view/mxCellRenderer.js
downloadxcos-on-web-5d474b6e265806c9df3fc80e06f8b4dd7fe16aea.tar.gz
xcos-on-web-5d474b6e265806c9df3fc80e06f8b4dd7fe16aea.tar.bz2
xcos-on-web-5d474b6e265806c9df3fc80e06f8b4dd7fe16aea.zip
Initial Commit
Diffstat (limited to 'src/js/view/mxCellRenderer.js')
-rw-r--r--src/js/view/mxCellRenderer.js1480
1 files changed, 1480 insertions, 0 deletions
diff --git a/src/js/view/mxCellRenderer.js b/src/js/view/mxCellRenderer.js
new file mode 100644
index 0000000..6b506ad
--- /dev/null
+++ b/src/js/view/mxCellRenderer.js
@@ -0,0 +1,1480 @@
+/**
+ * $Id: mxCellRenderer.js,v 1.189 2012-11-20 09:06:07 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCellRenderer
+ *
+ * Renders cells into a document object model. The <defaultShapes> is a global
+ * map of shapename, constructor pairs that is used in all instances. You can
+ * get a list of all available shape names using the following code.
+ *
+ * In general the cell renderer is in charge of creating, redrawing and
+ * destroying the shape and label associated with a cell state, as well as
+ * some other graphical objects, namely controls and overlays. The shape
+ * hieararchy in the display (ie. the hierarchy in which the DOM nodes
+ * appear in the document) does not reflect the cell hierarchy. The shapes
+ * are a (flat) sequence of shapes and labels inside the draw pane of the
+ * graph view, with some exceptions, namely the HTML labels being placed
+ * directly inside the graph container for certain browsers.
+ *
+ * (code)
+ * mxLog.show();
+ * for (var i in mxCellRenderer.prototype.defaultShapes)
+ * {
+ * mxLog.debug(i);
+ * }
+ * (end)
+ *
+ * Constructor: mxCellRenderer
+ *
+ * Constructs a new cell renderer with the following built-in shapes:
+ * arrow, rectangle, ellipse, rhombus, image, line, label, cylinder,
+ * swimlane, connector, actor and cloud.
+ */
+function mxCellRenderer()
+{
+ this.shapes = mxUtils.clone(this.defaultShapes);
+};
+
+/**
+ * Variable: shapes
+ *
+ * Array that maps from shape names to shape constructors. All entries
+ * in <defaultShapes> are added to this array.
+ */
+mxCellRenderer.prototype.shapes = null;
+
+/**
+ * Variable: defaultEdgeShape
+ *
+ * Defines the default shape for edges. Default is <mxConnector>.
+ */
+mxCellRenderer.prototype.defaultEdgeShape = mxConnector;
+
+/**
+ * Variable: defaultVertexShape
+ *
+ * Defines the default shape for vertices. Default is <mxRectangleShape>.
+ */
+mxCellRenderer.prototype.defaultVertexShape = mxRectangleShape;
+
+/**
+ * Variable: defaultShapes
+ *
+ * Static array that contains the globally registered shapes which are
+ * known to all instances of this class. For adding instance-specific
+ * shapes you should use <registerShape> on the instance. For adding
+ * a shape to this array you can use the following code:
+ *
+ * (code)
+ * mxCellRenderer.prototype.defaultShapes['myshape'] = myShape;
+ * (end)
+ *
+ * Where 'myshape' is the key under which the shape is to be registered
+ * and myShape is the name of the constructor function.
+ */
+mxCellRenderer.prototype.defaultShapes = new Object();
+
+// Adds default shapes into the default shapes array
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_ARROW] = mxArrow;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_RECTANGLE] = mxRectangleShape;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_ELLIPSE] = mxEllipse;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_DOUBLE_ELLIPSE] = mxDoubleEllipse;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_RHOMBUS] = mxRhombus;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_IMAGE] = mxImageShape;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_LINE] = mxLine;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_LABEL] = mxLabel;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_CYLINDER] = mxCylinder;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_SWIMLANE] = mxSwimlane;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_CONNECTOR] = mxConnector;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_ACTOR] = mxActor;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_CLOUD] = mxCloud;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_TRIANGLE] = mxTriangle;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_HEXAGON] = mxHexagon;
+
+/**
+ * Function: registerShape
+ *
+ * Registers the given constructor under the specified key in this instance
+ * of the renderer.
+ *
+ * Example:
+ *
+ * (code)
+ * this.registerShape(mxConstants.SHAPE_RECTANGLE, mxRectangleShape);
+ * (end)
+ *
+ * Parameters:
+ *
+ * key - String representing the shape name.
+ * shape - Constructor of the <mxShape> subclass.
+ */
+mxCellRenderer.prototype.registerShape = function(key, shape)
+{
+ this.shapes[key] = shape;
+};
+
+/**
+ * Function: initialize
+ *
+ * Initializes the display for the given cell state. This is required once
+ * after the cell state has been created. This is invoked in
+ * mxGraphView.createState.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the display should be initialized.
+ * rendering - Optional boolean that specifies if the cell should actually
+ * be initialized for any given DOM node. If this is false then init
+ * will not be called on the shape.
+ */
+mxCellRenderer.prototype.initialize = function(state, rendering)
+{
+ var model = state.view.graph.getModel();
+
+ if (state.view.graph.container != null && state.shape == null &&
+ state.cell != state.view.currentRoot &&
+ (model.isVertex(state.cell) || model.isEdge(state.cell)))
+ {
+ this.createShape(state);
+
+ if (state.shape != null && (rendering == null || rendering))
+ {
+ this.initializeShape(state);
+
+ // Maintains the model order in the DOM
+ if (state.view.graph.ordered || model.isEdge(state.cell))
+ {
+ //state.orderChanged = true;
+ state.invalidOrder = true;
+ }
+ else if (state.view.graph.keepEdgesInForeground && this.firstEdge != null)
+ {
+ if (this.firstEdge.parentNode == state.shape.node.parentNode)
+ {
+ this.insertState(state, this.firstEdge);
+ }
+ else
+ {
+ this.firstEdge = null;
+ }
+ }
+
+ state.shape.scale = state.view.scale;
+
+ this.createCellOverlays(state);
+ this.installListeners(state);
+ }
+ }
+};
+
+/**
+ * Function: initializeShape
+ *
+ * Initializes the shape in the given state by calling its init method with
+ * the correct container.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the shape should be initialized.
+ */
+mxCellRenderer.prototype.initializeShape = function(state)
+{
+ state.shape.init(state.view.getDrawPane());
+};
+
+/**
+ * Returns the previous state that has a shape inside the given parent.
+ */
+mxCellRenderer.prototype.getPreviousStateInContainer = function(state, container)
+{
+ var result = null;
+ var graph = state.view.graph;
+ var model = graph.getModel();
+ var child = state.cell;
+ var p = model.getParent(child);
+
+ while (p != null && result == null)
+ {
+ result = this.findPreviousStateInContainer(graph, p, child, container);
+ child = p;
+ p = model.getParent(child);
+ }
+
+ return result;
+};
+
+/**
+ * Returns the previous state that has a shape inside the given parent.
+ */
+mxCellRenderer.prototype.findPreviousStateInContainer = function(graph, cell, stop, container)
+{
+ // Recurse first
+ var result = null;
+ var model = graph.getModel();
+
+ if (stop != null)
+ {
+ var start = cell.getIndex(stop);
+
+ for (var i = start - 1; i >= 0 && result == null; i--)
+ {
+ result = this.findPreviousStateInContainer(graph, model.getChildAt(cell, i), null, container);
+ }
+ }
+ else
+ {
+ var childCount = model.getChildCount(cell);
+
+ for (var i = childCount - 1; i >= 0 && result == null; i--)
+ {
+ result = this.findPreviousStateInContainer(graph, model.getChildAt(cell, i), null, container);
+ }
+ }
+
+ if (result == null)
+ {
+ result = graph.view.getState(cell);
+
+ if (result != null && (result.shape == null || result.shape.node == null ||
+ result.shape.node.parentNode != container))
+ {
+ result = null;
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: order
+ *
+ * Orders the DOM node of the shape for the given state according to the
+ * position of the corresponding cell in the graph model.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose shape's DOM node should be ordered.
+ */
+mxCellRenderer.prototype.order = function(state)
+{
+ var container = state.shape.node.parentNode;
+ var previous = this.getPreviousStateInContainer(state, container);
+ var nextNode = container.firstChild;
+
+ if (previous != null)
+ {
+ nextNode = previous.shape.node;
+
+ if (previous.text != null && previous.text.node != null &&
+ previous.text.node.parentNode == container)
+ {
+ nextNode = previous.text.node;
+ }
+
+ nextNode = nextNode.nextSibling;
+ }
+
+ this.insertState(state, nextNode);
+};
+
+/**
+ * Function: orderEdge
+ *
+ * Orders the DOM node of the shape for the given edge's state according to
+ * the <mxGraph.keepEdgesInBackground> and <mxGraph.keepEdgesInBackground>
+ * rules.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose shape's DOM node should be ordered.
+ */
+mxCellRenderer.prototype.orderEdge = function(state)
+{
+ var view = state.view;
+ var model = view.graph.getModel();
+
+ // Moves edges to the foreground/background
+ if (view.graph.keepEdgesInForeground)
+ {
+ if (this.firstEdge == null || this.firstEdge.parentNode == null ||
+ this.firstEdge.parentNode != state.shape.node.parentNode)
+ {
+ this.firstEdge = state.shape.node;
+ }
+ }
+ else if (view.graph.keepEdgesInBackground)
+ {
+ var node = state.shape.node;
+ var parent = node.parentNode;
+
+ // Keeps the DOM node in front of its parent
+ var pcell = model.getParent(state.cell);
+ var pstate = view.getState(pcell);
+
+ if (pstate != null && pstate.shape != null && pstate.shape.node != null)
+ {
+ var child = pstate.shape.node.nextSibling;
+
+ if (child != null && child != node)
+ {
+ this.insertState(state, child);
+ }
+ }
+ else
+ {
+ var child = parent.firstChild;
+
+ if (child != null && child != node)
+ {
+ this.insertState(state, child);
+ }
+ }
+ }
+};
+
+/**
+ * Function: insertState
+ *
+ * Inserts the given state before the given node into its parent.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the shape should be created.
+ */
+mxCellRenderer.prototype.insertState = function(state, nextNode)
+{
+ state.shape.node.parentNode.insertBefore(state.shape.node, nextNode);
+
+ if (state.text != null && state.text.node != null &&
+ state.text.node.parentNode == state.shape.node.parentNode)
+ {
+ state.shape.node.parentNode.insertBefore(state.text.node, state.shape.node.nextSibling);
+ }
+};
+
+/**
+ * Function: createShape
+ *
+ * Creates the shape for the given cell state. The shape is configured
+ * using <configureShape>.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the shape should be created.
+ */
+mxCellRenderer.prototype.createShape = function(state)
+{
+ if (state.style != null)
+ {
+ // Checks if there is a stencil for the name and creates
+ // a shape instance for the stencil if one exists
+ var key = state.style[mxConstants.STYLE_SHAPE];
+ var stencil = mxStencilRegistry.getStencil(key);
+
+ if (stencil != null)
+ {
+ state.shape = new mxStencilShape(stencil);
+ }
+ else
+ {
+ var ctor = this.getShapeConstructor(state);
+ state.shape = new ctor();
+ }
+
+ // Sets the initial bounds and points (will be updated in redraw)
+ state.shape.points = state.absolutePoints;
+ state.shape.bounds = new mxRectangle(
+ state.x, state.y, state.width, state.height);
+ state.shape.dialect = state.view.graph.dialect;
+
+ this.configureShape(state);
+ }
+};
+
+/**
+ * Function: getShapeConstructor
+ *
+ * Returns the constructor to be used for creating the shape.
+ */
+mxCellRenderer.prototype.getShapeConstructor = function(state)
+{
+ var key = state.style[mxConstants.STYLE_SHAPE];
+ var ctor = (key != null) ? this.shapes[key] : null;
+
+ if (ctor == null)
+ {
+ ctor = (state.view.graph.getModel().isEdge(state.cell)) ?
+ this.defaultEdgeShape : this.defaultVertexShape;
+ }
+
+ return ctor;
+};
+
+/**
+ * Function: configureShape
+ *
+ * Configures the shape for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the shape should be configured.
+ */
+mxCellRenderer.prototype.configureShape = function(state)
+{
+ state.shape.apply(state);
+ var image = state.view.graph.getImage(state);
+
+ if (image != null)
+ {
+ state.shape.image = image;
+ }
+
+ var indicator = state.view.graph.getIndicatorColor(state);
+ var key = state.view.graph.getIndicatorShape(state);
+ var ctor = (key != null) ? this.shapes[key] : null;
+
+ // Configures the indicator shape or image
+ if (indicator != null)
+ {
+ state.shape.indicatorShape = ctor;
+ state.shape.indicatorColor = indicator;
+ state.shape.indicatorGradientColor =
+ state.view.graph.getIndicatorGradientColor(state);
+ state.shape.indicatorDirection =
+ state.style[mxConstants.STYLE_INDICATOR_DIRECTION];
+ }
+ else
+ {
+ var indicator = state.view.graph.getIndicatorImage(state);
+
+ if (indicator != null)
+ {
+ state.shape.indicatorImage = indicator;
+ }
+ }
+
+ this.postConfigureShape(state);
+};
+
+/**
+ * Function: postConfigureShape
+ *
+ * Replaces any reserved words used for attributes, eg. inherit,
+ * indicated or swimlane for colors in the shape for the given state.
+ * This implementation resolves these keywords on the fill, stroke
+ * and gradient color keys.
+ */
+mxCellRenderer.prototype.postConfigureShape = function(state)
+{
+ if (state.shape != null)
+ {
+ this.resolveColor(state, 'indicatorColor', mxConstants.STYLE_FILLCOLOR);
+ this.resolveColor(state, 'indicatorGradientColor', mxConstants.STYLE_GRADIENTCOLOR);
+ this.resolveColor(state, 'fill', mxConstants.STYLE_FILLCOLOR);
+ this.resolveColor(state, 'stroke', mxConstants.STYLE_STROKECOLOR);
+ this.resolveColor(state, 'gradient', mxConstants.STYLE_GRADIENTCOLOR);
+ }
+};
+
+/**
+ * Function: resolveColor
+ *
+ * Resolves special keywords 'inherit', 'indicated' and 'swimlane' and sets
+ * the respective color on the shape.
+ */
+mxCellRenderer.prototype.resolveColor = function(state, field, key)
+{
+ var value = state.shape[field];
+ var graph = state.view.graph;
+ var referenced = null;
+
+ if (value == 'inherit')
+ {
+ referenced = graph.model.getParent(state.cell);
+ }
+ else if (value == 'swimlane')
+ {
+ if (graph.model.getTerminal(state.cell, false) != null)
+ {
+ referenced = graph.model.getTerminal(state.cell, false);
+ }
+ else
+ {
+ referenced = state.cell;
+ }
+
+ referenced = graph.getSwimlane(referenced);
+ key = graph.swimlaneIndicatorColorAttribute;
+ }
+ else if (value == 'indicated')
+ {
+ state.shape[field] = state.shape.indicatorColor;
+ }
+
+ if (referenced != null)
+ {
+ var rstate = graph.getView().getState(referenced);
+ state.shape[field] = null;
+
+ if (rstate != null)
+ {
+ if (rstate.shape != null && field != 'indicatorColor')
+ {
+ state.shape[field] = rstate.shape[field];
+ }
+ else
+ {
+ state.shape[field] = rstate.style[key];
+ }
+ }
+ }
+};
+
+/**
+ * Function: getLabelValue
+ *
+ * Returns the value to be used for the label.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the label should be created.
+ */
+mxCellRenderer.prototype.getLabelValue = function(state)
+{
+ var graph = state.view.graph;
+ var value = graph.getLabel(state.cell);
+
+ if (!graph.isHtmlLabel(state.cell) && !mxUtils.isNode(value) &&
+ graph.dialect != mxConstants.DIALECT_SVG && value != null)
+ {
+ value = mxUtils.htmlEntities(value, false);
+ }
+
+ return value;
+};
+
+/**
+ * Function: createLabel
+ *
+ * Creates the label for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the label should be created.
+ */
+mxCellRenderer.prototype.createLabel = function(state, value)
+{
+ var graph = state.view.graph;
+ var isEdge = graph.getModel().isEdge(state.cell);
+
+ if (state.style[mxConstants.STYLE_FONTSIZE] > 0 ||
+ state.style[mxConstants.STYLE_FONTSIZE] == null)
+ {
+ // Avoids using DOM node for empty labels
+ var isForceHtml = (graph.isHtmlLabel(state.cell) ||
+ (value != null && mxUtils.isNode(value))) &&
+ graph.dialect == mxConstants.DIALECT_SVG;
+
+ state.text = new mxText(value, new mxRectangle(),
+ (state.style[mxConstants.STYLE_ALIGN] ||
+ mxConstants.ALIGN_CENTER),
+ graph.getVerticalAlign(state),
+ state.style[mxConstants.STYLE_FONTCOLOR],
+ state.style[mxConstants.STYLE_FONTFAMILY],
+ state.style[mxConstants.STYLE_FONTSIZE],
+ state.style[mxConstants.STYLE_FONTSTYLE],
+ state.style[mxConstants.STYLE_SPACING],
+ state.style[mxConstants.STYLE_SPACING_TOP],
+ state.style[mxConstants.STYLE_SPACING_RIGHT],
+ state.style[mxConstants.STYLE_SPACING_BOTTOM],
+ state.style[mxConstants.STYLE_SPACING_LEFT],
+ state.style[mxConstants.STYLE_HORIZONTAL],
+ state.style[mxConstants.STYLE_LABEL_BACKGROUNDCOLOR],
+ state.style[mxConstants.STYLE_LABEL_BORDERCOLOR],
+ graph.isWrapping(state.cell) && graph.isHtmlLabel(state.cell),
+ graph.isLabelClipped(state.cell),
+ state.style[mxConstants.STYLE_OVERFLOW],
+ state.style[mxConstants.STYLE_LABEL_PADDING]);
+ state.text.opacity = state.style[mxConstants.STYLE_TEXT_OPACITY];
+
+ state.text.dialect = (isForceHtml) ?
+ mxConstants.DIALECT_STRICTHTML :
+ state.view.graph.dialect;
+ this.initializeLabel(state);
+
+ // Workaround for touch devices routing all events for a mouse
+ // gesture (down, move, up) via the initial DOM node. IE is even
+ // worse in that it redirects the event via the initial DOM node
+ // but the event source is the node under the mouse, so we need
+ // to check if this is the case and force getCellAt for the
+ // subsequent mouseMoves and the final mouseUp.
+ var forceGetCell = false;
+
+ var getState = function(evt)
+ {
+ var result = state;
+
+ if (mxClient.IS_TOUCH || forceGetCell)
+ {
+ 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(graph.container, x, y);
+ result = graph.view.getState(graph.getCellAt(pt.x, pt.y));
+ }
+
+ return result;
+ };
+
+ // TODO: Add handling for gestures
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+ var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
+
+ mxEvent.addListener(state.text.node, md,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isLabelEvent(state, evt))
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_DOWN,
+ new mxMouseEvent(evt, state));
+ forceGetCell = graph.dialect != mxConstants.DIALECT_SVG && mxEvent.getSource(evt).nodeName == 'IMG';
+ }
+ })
+ );
+
+ mxEvent.addListener(state.text.node, mm,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isLabelEvent(state, evt))
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
+ new mxMouseEvent(evt, getState(evt)));
+ }
+ })
+ );
+
+ mxEvent.addListener(state.text.node, mu,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isLabelEvent(state, evt))
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_UP,
+ new mxMouseEvent(evt, getState(evt)));
+ forceGetCell = false;
+ }
+ })
+ );
+
+ mxEvent.addListener(state.text.node, 'dblclick',
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isLabelEvent(state, evt))
+ {
+ graph.dblClick(evt, state.cell);
+ mxEvent.consume(evt);
+ }
+ })
+ );
+ }
+};
+
+/**
+ * Function: initializeLabel
+ *
+ * Initiailzes the label with a suitable container.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose label should be initialized.
+ */
+mxCellRenderer.prototype.initializeLabel = function(state)
+{
+ var graph = state.view.graph;
+
+ if (state.text.dialect != mxConstants.DIALECT_SVG)
+ {
+ // Adds the text to the container if the dialect is not SVG and we
+ // have an SVG-based browser which doesn't support foreignObjects
+ if (mxClient.IS_SVG && mxClient.NO_FO)
+ {
+ state.text.init(graph.container);
+ }
+ else if (mxUtils.isVml(state.view.getDrawPane()))
+ {
+ if (state.shape.label != null)
+ {
+ state.text.init(state.shape.label);
+ }
+ else
+ {
+ state.text.init(state.shape.node);
+ }
+ }
+ }
+
+ if (state.text.node == null)
+ {
+ state.text.init(state.view.getDrawPane());
+
+ if (state.shape != null && state.text != null)
+ {
+ state.shape.node.parentNode.insertBefore(
+ state.text.node, state.shape.node.nextSibling);
+ }
+ }
+};
+
+/**
+ * Function: createCellOverlays
+ *
+ * Creates the actual shape for showing the overlay for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the overlay should be created.
+ */
+mxCellRenderer.prototype.createCellOverlays = function(state)
+{
+ var graph = state.view.graph;
+ var overlays = graph.getCellOverlays(state.cell);
+ var dict = null;
+
+ if (overlays != null)
+ {
+ dict = new mxDictionary();
+
+ for (var i = 0; i < overlays.length; i++)
+ {
+ var shape = (state.overlays != null) ? state.overlays.remove(overlays[i]) : null;
+
+ if (shape == null)
+ {
+ var tmp = new mxImageShape(new mxRectangle(),
+ overlays[i].image.src);
+ tmp.dialect = state.view.graph.dialect;
+ tmp.preserveImageAspect = false;
+ tmp.overlay = overlays[i];
+ this.initializeOverlay(state, tmp);
+ this.installCellOverlayListeners(state, overlays[i], tmp);
+
+ if (overlays[i].cursor != null)
+ {
+ tmp.node.style.cursor = overlays[i].cursor;
+ }
+
+ dict.put(overlays[i], tmp);
+ }
+ else
+ {
+ dict.put(overlays[i], shape);
+ }
+ }
+ }
+
+ // Removes unused
+ if (state.overlays != null)
+ {
+ state.overlays.visit(function(id, shape)
+ {
+ shape.destroy();
+ });
+ }
+
+ state.overlays = dict;
+};
+
+/**
+ * Function: initializeOverlay
+ *
+ * Initializes the given overlay.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the overlay should be created.
+ * overlay - <mxImageShape> that represents the overlay.
+ */
+mxCellRenderer.prototype.initializeOverlay = function(state, overlay)
+{
+ overlay.init(state.view.getOverlayPane());
+};
+
+/**
+ * Function: installOverlayListeners
+ *
+ * Installs the listeners for the given <mxCellState>, <mxCellOverlay> and
+ * <mxShape> that represents the overlay.
+ */
+mxCellRenderer.prototype.installCellOverlayListeners = function(state, overlay, shape)
+{
+ var graph = state.view.graph;
+
+ mxEvent.addListener(shape.node, 'click', function (evt)
+ {
+ if (graph.isEditing())
+ {
+ graph.stopEditing(!graph.isInvokesStopCellEditing());
+ }
+
+ overlay.fireEvent(new mxEventObject(mxEvent.CLICK,
+ 'event', evt, 'cell', state.cell));
+ });
+
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+
+ mxEvent.addListener(shape.node, md, function (evt)
+ {
+ mxEvent.consume(evt);
+ });
+
+ mxEvent.addListener(shape.node, mm, function (evt)
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
+ new mxMouseEvent(evt, state));
+ });
+
+ if (mxClient.IS_TOUCH)
+ {
+ mxEvent.addListener(shape.node, 'touchend', function (evt)
+ {
+ overlay.fireEvent(new mxEventObject(mxEvent.CLICK,
+ 'event', evt, 'cell', state.cell));
+ });
+ }
+};
+
+/**
+ * Function: createControl
+ *
+ * Creates the control for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the control should be created.
+ */
+mxCellRenderer.prototype.createControl = function(state)
+{
+ var graph = state.view.graph;
+ var image = graph.getFoldingImage(state);
+
+ if (graph.foldingEnabled && image != null)
+ {
+ if (state.control == null)
+ {
+ var b = new mxRectangle(0, 0, image.width, image.height);
+ state.control = new mxImageShape(b, image.src);
+ state.control.dialect = graph.dialect;
+ state.control.preserveImageAspect = false;
+
+ this.initControl(state, state.control, true, function (evt)
+ {
+ if (graph.isEnabled())
+ {
+ var collapse = !graph.isCellCollapsed(state.cell);
+ graph.foldCells(collapse, false, [state.cell]);
+ mxEvent.consume(evt);
+ }
+ });
+ }
+ }
+ else if (state.control != null)
+ {
+ state.control.destroy();
+ state.control = null;
+ }
+};
+
+/**
+ * Function: initControl
+ *
+ * Initializes the given control and returns the corresponding DOM node.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the control should be initialized.
+ * control - <mxShape> to be initialized.
+ * handleEvents - Boolean indicating if mousedown and mousemove should fire events via the graph.
+ * clickHandler - Optional function to implement clicks on the control.
+ */
+mxCellRenderer.prototype.initControl = function(state, control, handleEvents, clickHandler)
+{
+ var graph = state.view.graph;
+
+ // In the special case where the label is in HTML and the display is SVG the image
+ // should go into the graph container directly in order to be clickable. Otherwise
+ // it is obscured by the HTML label that overlaps the cell.
+ var isForceHtml = graph.isHtmlLabel(state.cell) &&
+ mxClient.NO_FO &&
+ graph.dialect == mxConstants.DIALECT_SVG;
+
+ if (isForceHtml)
+ {
+ control.dialect = mxConstants.DIALECT_PREFERHTML;
+ control.init(graph.container);
+ control.node.style.zIndex = 1;
+ }
+ else
+ {
+ control.init(state.view.getOverlayPane());
+ }
+
+ var node = control.innerNode || control.node;
+
+ if (clickHandler)
+ {
+ if (graph.isEnabled())
+ {
+ node.style.cursor = 'pointer';
+ }
+
+ mxEvent.addListener(node, 'click', clickHandler);
+ }
+
+ if (handleEvents)
+ {
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+
+ mxEvent.addListener(node, md, function (evt)
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, state));
+ mxEvent.consume(evt);
+ });
+
+ mxEvent.addListener(node, mm, function (evt)
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, state));
+ });
+ }
+
+ return node;
+};
+
+/**
+ * Function: isShapeEvent
+ *
+ * Returns true if the event is for the shape of the given state. This
+ * implementation always returns true.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose shape fired the event.
+ * evt - Mouse event which was fired.
+ */
+mxCellRenderer.prototype.isShapeEvent = function(state, evt)
+{
+ return true;
+};
+
+/**
+ * Function: isLabelEvent
+ *
+ * Returns true if the event is for the label of the given state. This
+ * implementation always returns true.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose label fired the event.
+ * evt - Mouse event which was fired.
+ */
+mxCellRenderer.prototype.isLabelEvent = function(state, evt)
+{
+ return true;
+};
+
+/**
+ * Function: installListeners
+ *
+ * Installs the event listeners for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the event listeners should be isntalled.
+ */
+mxCellRenderer.prototype.installListeners = function(state)
+{
+ var graph = state.view.graph;
+
+ // Receives events from transparent backgrounds
+ if (graph.dialect == mxConstants.DIALECT_SVG)
+ {
+ var events = 'all';
+
+ // Disabled fill-events on non-filled edges
+ if (graph.getModel().isEdge(state.cell) && state.shape.stroke != null &&
+ (state.shape.fill == null || state.shape.fill == mxConstants.NONE))
+ {
+ events = 'visibleStroke';
+ }
+
+ // Specifies the event-processing on the shape
+ if (state.shape.innerNode != null)
+ {
+ state.shape.innerNode.setAttribute('pointer-events', events);
+ }
+ else
+ {
+ state.shape.node.setAttribute('pointer-events', events);
+ }
+ }
+
+ // Workaround for touch devices routing all events for a mouse
+ // gesture (down, move, up) via the initial DOM node. Same for
+ // HTML images in all IE versions (VML images are working).
+ var getState = function(evt)
+ {
+ var result = state;
+
+ if ((graph.dialect != mxConstants.DIALECT_SVG && mxEvent.getSource(evt).nodeName == 'IMG') || mxClient.IS_TOUCH)
+ {
+ 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(graph.container, x, y);
+ result = graph.view.getState(graph.getCellAt(pt.x, pt.y));
+ }
+
+ return result;
+ };
+
+ // Experimental support for two-finger pinch to resize cells
+ var gestureInProgress = false;
+
+ mxEvent.addListener(state.shape.node, 'gesturestart',
+ mxUtils.bind(this, function(evt)
+ {
+ // FIXME: Breaks encapsulation to reset the double
+ // tap event handling when gestures take place
+ graph.lastTouchTime = 0;
+
+ gestureInProgress = true;
+ mxEvent.consume(evt);
+ })
+ );
+
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+ var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
+
+ mxEvent.addListener(state.shape.node, md,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isShapeEvent(state, evt) && !gestureInProgress)
+ {
+ // Redirects events from the "event-transparent" region of
+ // a swimlane to the graph. This is only required in HTML,
+ // SVG and VML do not fire mouse events on transparent
+ // backgrounds.
+ graph.fireMouseEvent(mxEvent.MOUSE_DOWN,
+ new mxMouseEvent(evt, (state.shape != null &&
+ mxEvent.getSource(evt) == state.shape.content) ?
+ null : state));
+ }
+ else if (gestureInProgress)
+ {
+ mxEvent.consume(evt);
+ }
+ })
+ );
+
+ mxEvent.addListener(state.shape.node, mm,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isShapeEvent(state, evt) && !gestureInProgress)
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
+ new mxMouseEvent(evt, (state.shape != null &&
+ mxEvent.getSource(evt) == state.shape.content) ?
+ null : getState(evt)));
+ }
+ else if (gestureInProgress)
+ {
+ mxEvent.consume(evt);
+ }
+ })
+ );
+
+ mxEvent.addListener(state.shape.node, mu,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isShapeEvent(state, evt) && !gestureInProgress)
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_UP,
+ new mxMouseEvent(evt, (state.shape != null &&
+ mxEvent.getSource(evt) == state.shape.content) ?
+ null : getState(evt)));
+ }
+ else if (gestureInProgress)
+ {
+ mxEvent.consume(evt);
+ }
+ })
+ );
+
+ // Experimental handling for gestures. Double-tap handling is implemented
+ // in mxGraph.fireMouseEvent.
+ var dc = (mxClient.IS_TOUCH) ? 'gestureend' : 'dblclick';
+
+ mxEvent.addListener(state.shape.node, dc,
+ mxUtils.bind(this, function(evt)
+ {
+ gestureInProgress = false;
+
+ if (dc == 'gestureend')
+ {
+ // FIXME: Breaks encapsulation to reset the double
+ // tap event handling when gestures take place
+ graph.lastTouchTime = 0;
+
+ if (graph.gestureEnabled)
+ {
+ graph.handleGesture(state, evt);
+ mxEvent.consume(evt);
+ }
+ }
+ else if (this.isShapeEvent(state, evt))
+ {
+ graph.dblClick(evt, (state.shape != null &&
+ mxEvent.getSource(evt) == state.shape.content) ?
+ null : state.cell);
+ mxEvent.consume(evt);
+ }
+ })
+ );
+};
+
+/**
+ * Function: redrawLabel
+ *
+ * Redraws the label for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose label should be redrawn.
+ */
+mxCellRenderer.prototype.redrawLabel = function(state)
+{
+ var value = this.getLabelValue(state);
+
+ // FIXME: Add label always if HTML label and NO_FO
+ if (state.text == null && value != null && (mxUtils.isNode(value) || value.length > 0))
+ {
+ this.createLabel(state, value);
+ }
+ else if (state.text != null && (value == null || value.length == 0))
+ {
+ state.text.destroy();
+ state.text = null;
+ }
+
+ if (state.text != null)
+ {
+ var graph = state.view.graph;
+ var wrapping = graph.isWrapping(state.cell);
+ var clipping = graph.isLabelClipped(state.cell);
+ var bounds = this.getLabelBounds(state);
+
+ if (state.text.value != value || state.text.isWrapping != wrapping ||
+ state.text.isClipping != clipping || state.text.scale != state.view.scale ||
+ !state.text.bounds.equals(bounds))
+ {
+ state.text.value = value;
+ state.text.bounds = bounds;
+ state.text.scale = this.getTextScale(state);
+ state.text.isWrapping = wrapping;
+ state.text.isClipping = clipping;
+
+ state.text.redraw();
+ }
+ }
+};
+
+/**
+ * Function: getTextScale
+ *
+ * Returns the scaling used for the label of the given state
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose label scale should be returned.
+ */
+mxCellRenderer.prototype.getTextScale = function(state)
+{
+ return state.view.scale;
+};
+
+/**
+ * Function: getLabelBounds
+ *
+ * Returns the bounds to be used to draw the label of the given state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose label bounds should be returned.
+ */
+mxCellRenderer.prototype.getLabelBounds = function(state)
+{
+ var graph = state.view.graph;
+ var isEdge = graph.getModel().isEdge(state.cell);
+ var bounds = new mxRectangle(state.absoluteOffset.x, state.absoluteOffset.y);
+
+ if (!isEdge)
+ {
+ bounds.x += state.x;
+ bounds.y += state.y;
+
+ // Minimum of 1 fixes alignment bug in HTML labels
+ bounds.width = Math.max(1, state.width);
+ bounds.height = Math.max(1, state.height);
+
+ if (graph.isSwimlane(state.cell))
+ {
+ var scale = graph.view.scale;
+ var size = graph.getStartSize(state.cell);
+
+ if (size.width > 0)
+ {
+ bounds.width = size.width * scale;
+ }
+ else if (size.height > 0)
+ {
+ bounds.height = size.height * scale;
+ }
+ }
+ }
+
+ return bounds;
+};
+
+/**
+ * Function: redrawCellOverlays
+ *
+ * Redraws the overlays for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose overlays should be redrawn.
+ */
+mxCellRenderer.prototype.redrawCellOverlays = function(state)
+{
+ this.createCellOverlays(state);
+
+ if (state.overlays != null)
+ {
+ state.overlays.visit(function(id, shape)
+ {
+ var bounds = shape.overlay.getBounds(state);
+
+ if (shape.bounds == null || shape.scale != state.view.scale ||
+ !shape.bounds.equals(bounds))
+ {
+ shape.bounds = bounds;
+ shape.scale = state.view.scale;
+ shape.redraw();
+ }
+ });
+ }
+};
+
+/**
+ * Function: redrawControl
+ *
+ * Redraws the control for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose control should be redrawn.
+ */
+mxCellRenderer.prototype.redrawControl = function(state)
+{
+ if (state.control != null)
+ {
+ var bounds = this.getControlBounds(state);
+ var s = state.view.scale;
+
+ if (state.control.scale != s || !state.control.bounds.equals(bounds))
+ {
+ state.control.bounds = bounds;
+ state.control.scale = s;
+ state.control.redraw();
+ }
+ }
+};
+
+/**
+ * Function: getControlBounds
+ *
+ * Returns the bounds to be used to draw the control (folding icon) of the
+ * given state.
+ */
+mxCellRenderer.prototype.getControlBounds = function(state)
+{
+ if (state.control != null)
+ {
+ var oldScale = state.control.scale;
+ var w = state.control.bounds.width / oldScale;
+ var h = state.control.bounds.height / oldScale;
+ var s = state.view.scale;
+
+ return (state.view.graph.getModel().isEdge(state.cell)) ?
+ new mxRectangle(state.x + state.width / 2 - w / 2 * s,
+ state.y + state.height / 2 - h / 2 * s, w * s, h * s)
+ : new mxRectangle(state.x + w / 2 * s,
+ state.y + h / 2 * s, w * s, h * s);
+ }
+
+ return null;
+};
+
+/**
+ * Function: redraw
+ *
+ * Updates the bounds or points and scale of the shapes for the given cell
+ * state. This is called in mxGraphView.validatePoints as the last step of
+ * updating all cells.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the shapes should be updated.
+ * force - Optional boolean that specifies if the cell should be reconfiured
+ * and redrawn without any additional checks.
+ * rendering - Optional boolean that specifies if the cell should actually
+ * be drawn into the DOM. If this is false then redraw and/or reconfigure
+ * will not be called on the shape.
+ */
+mxCellRenderer.prototype.redraw = function(state, force, rendering)
+{
+ if (state.shape != null)
+ {
+ var model = state.view.graph.getModel();
+ var isEdge = model.isEdge(state.cell);
+ reconfigure = (force != null) ? force : false;
+
+ // Handles changes of the collapse icon
+ this.createControl(state);
+
+ // Handles changes to the order in the DOM
+ if (state.orderChanged || state.invalidOrder)
+ {
+ if (state.view.graph.ordered)
+ {
+ this.order(state);
+ }
+ else
+ {
+ // Assert state.cell is edge
+ this.orderEdge(state);
+ }
+
+ // Required to update inherited styles
+ reconfigure = state.orderChanged;
+ }
+
+ delete state.invalidOrder;
+ delete state.orderChanged;
+
+ // Checks if the style in the state is different from the style
+ // in the shape and re-applies the style if required
+ if (!reconfigure && !mxUtils.equalEntries(state.shape.style, state.style))
+ {
+ reconfigure = true;
+ }
+
+ // Reconfiures the shape after an order or style change
+ if (reconfigure)
+ {
+ this.configureShape(state);
+ state.shape.reconfigure();
+ }
+
+ // Redraws the cell if required
+ if (force || state.shape.bounds == null || state.shape.scale != state.view.scale ||
+ !state.shape.bounds.equals(state) ||
+ !mxUtils.equalPoints(state.shape.points, state.absolutePoints))
+ {
+ // FIXME: Move indicator color update into shape.redraw
+// var indicator = state.view.graph.getIndicatorColor(state);
+// if (indicator != null)
+// {
+// state.shape.indicatorColor = indicator;
+// }
+
+ if (state.absolutePoints != null)
+ {
+ state.shape.points = state.absolutePoints.slice();
+ }
+ else
+ {
+ state.shape.points = null;
+ }
+
+ state.shape.bounds = new mxRectangle(
+ state.x, state.y, state.width, state.height);
+ state.shape.scale = state.view.scale;
+
+ if (rendering == null || rendering)
+ {
+ state.shape.redraw();
+ }
+ else
+ {
+ state.shape.updateBoundingBox();
+ }
+ }
+
+ // Updates the text label, overlays and control
+ if (rendering == null || rendering)
+ {
+ this.redrawLabel(state);
+ this.redrawCellOverlays(state);
+ this.redrawControl(state);
+ }
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the shapes associated with the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the shapes should be destroyed.
+ */
+mxCellRenderer.prototype.destroy = function(state)
+{
+ if (state.shape != null)
+ {
+ if (state.text != null)
+ {
+ state.text.destroy();
+ state.text = null;
+ }
+
+ if (state.overlays != null)
+ {
+ state.overlays.visit(function(id, shape)
+ {
+ shape.destroy();
+ });
+
+ state.overlays = null;
+ }
+
+ if (state.control != null)
+ {
+ state.control.destroy();
+ state.control = null;
+ }
+
+ state.shape.destroy();
+ state.shape = null;
+ }
+};