/** * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ /** * Class: mxGuide * * Implements the alignment of selection cells to other cells in the graph. * * Constructor: mxGuide * * Constructs a new guide object. */ function mxGuide(graph, states) { this.graph = graph; this.setStates(states); }; /** * Variable: graph * * Reference to the enclosing instance. */ mxGuide.prototype.graph = null; /** * Variable: states * * Contains the that are used for alignment. */ mxGuide.prototype.states = null; /** * Variable: horizontal * * Specifies if horizontal guides are enabled. Default is true. */ mxGuide.prototype.horizontal = true; /** * Variable: vertical * * Specifies if vertical guides are enabled. Default is true. */ mxGuide.prototype.vertical = true; /** * Variable: guideX * * Holds the for the horizontal guide. */ mxGuide.prototype.guideX = null; /** * Variable: guideY * * Holds the for the vertical guide. */ mxGuide.prototype.guideY = null; /** * Variable: rounded * * Specifies if rounded coordinates should be used. Default is false. */ mxGuide.prototype.rounded = false; /** * Variable: tolerance * * Default tolerance in px if grid is disabled. Default is 2. */ mxGuide.prototype.tolerance = 2; /** * Function: setStates * * Sets the that should be used for alignment. */ mxGuide.prototype.setStates = function(states) { this.states = states; }; /** * Function: isEnabledForEvent * * Returns true if the guide should be enabled for the given native event. This * implementation always returns true. */ mxGuide.prototype.isEnabledForEvent = function(evt) { return true; }; /** * Function: getGuideTolerance * * Returns the tolerance for the guides. Default value is gridSize / 2. */ mxGuide.prototype.getGuideTolerance = function(gridEnabled) { return (gridEnabled && this.graph.gridEnabled) ? this.graph.gridSize / 2 : this.tolerance; }; /** * Function: createGuideShape * * Returns the mxShape to be used for painting the respective guide. This * implementation returns a new, dashed and crisp using * and as the format. * * Parameters: * * horizontal - Boolean that specifies which guide should be created. */ mxGuide.prototype.createGuideShape = function(horizontal) { var guide = new mxPolyline([], mxConstants.GUIDE_COLOR, mxConstants.GUIDE_STROKEWIDTH); guide.isDashed = true; return guide; }; /** * Function: isStateIgnored * * Returns true if the given state should be ignored. */ mxGuide.prototype.isStateIgnored = function(state) { return false; }; /** * Function: move * * Moves the by the given and returnt the snapped point. */ mxGuide.prototype.move = function(bounds, delta, gridEnabled, clone) { if (this.states != null && (this.horizontal || this.vertical) && bounds != null && delta != null) { var scale = this.graph.getView().scale; var tt = this.getGuideTolerance(gridEnabled) * scale; var b = bounds.clone(); b.x += delta.x; b.y += delta.y; var overrideX = false; var stateX = null; var valueX = null; var overrideY = false; var stateY = null; var valueY = null; var ttX = tt; var ttY = tt; var left = b.x; var right = b.x + b.width; var center = b.getCenterX(); var top = b.y; var bottom = b.y + b.height; var middle = b.getCenterY(); // Snaps the left, center and right to the given x-coordinate function snapX(x, state, centerAlign) { var override = false; if (centerAlign && Math.abs(x - center) < ttX) { delta.x = x - bounds.getCenterX(); ttX = Math.abs(x - center); override = true; } else if (!centerAlign) { if (Math.abs(x - left) < ttX) { delta.x = x - bounds.x; ttX = Math.abs(x - left); override = true; } else if (Math.abs(x - right) < ttX) { delta.x = x - bounds.x - bounds.width; ttX = Math.abs(x - right); override = true; } } if (override) { stateX = state; valueX = x; if (this.guideX == null) { this.guideX = this.createGuideShape(true); // Makes sure to use either VML or SVG shapes in order to implement // event-transparency on the background area of the rectangle since // HTML shapes do not let mouseevents through even when transparent this.guideX.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG; this.guideX.pointerEvents = false; this.guideX.init(this.graph.getView().getOverlayPane()); } } overrideX = overrideX || override; }; // Snaps the top, middle or bottom to the given y-coordinate function snapY(y, state, centerAlign) { var override = false; if (centerAlign && Math.abs(y - middle) < ttY) { delta.y = y - bounds.getCenterY(); ttY = Math.abs(y - middle); override = true; } else if (!centerAlign) { if (Math.abs(y - top) < ttY) { delta.y = y - bounds.y; ttY = Math.abs(y - top); override = true; } else if (Math.abs(y - bottom) < ttY) { delta.y = y - bounds.y - bounds.height; ttY = Math.abs(y - bottom); override = true; } } if (override) { stateY = state; valueY = y; if (this.guideY == null) { this.guideY = this.createGuideShape(false); // Makes sure to use either VML or SVG shapes in order to implement // event-transparency on the background area of the rectangle since // HTML shapes do not let mouseevents through even when transparent this.guideY.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG; this.guideY.pointerEvents = false; this.guideY.init(this.graph.getView().getOverlayPane()); } } overrideY = overrideY || override; }; for (var i = 0; i < this.states.length; i++) { var state = this.states[i]; if (state != null && !this.isStateIgnored(state)) { // Align x if (this.horizontal) { snapX.call(this, state.getCenterX(), state, true); snapX.call(this, state.x, state, false); snapX.call(this, state.x + state.width, state, false); // Aligns left and right of shape to center of page if (state.cell == null) { snapX.call(this, state.getCenterX(), state, false); } } // Align y if (this.vertical) { snapY.call(this, state.getCenterY(), state, true); snapY.call(this, state.y, state, false); snapY.call(this, state.y + state.height, state, false); // Aligns left and right of shape to center of page if (state.cell == null) { snapY.call(this, state.getCenterY(), state, false); } } } } // Moves cells to the raster if not aligned this.graph.snapDelta(delta, bounds, !gridEnabled, overrideX, overrideY); delta = this.getDelta(bounds, stateX, delta.x, stateY, delta.y) // Redraws the guides var c = this.graph.container; if (!overrideX && this.guideX != null) { this.guideX.node.style.visibility = 'hidden'; } else if (this.guideX != null) { var minY = null; var maxY = null; if (stateX != null && bounds != null) { minY = Math.min(bounds.y + delta.y - this.graph.panDy, stateX.y); maxY = Math.max(bounds.y + bounds.height + delta.y - this.graph.panDy, stateX.y + stateX.height); } if (minY != null && maxY != null) { this.guideX.points = [new mxPoint(valueX, minY), new mxPoint(valueX, maxY)]; } else { this.guideX.points = [new mxPoint(valueX, -this.graph.panDy), new mxPoint(valueX, c.scrollHeight - 3 - this.graph.panDy)]; } this.guideX.stroke = this.getGuideColor(stateX, true); this.guideX.node.style.visibility = 'visible'; this.guideX.redraw(); } if (!overrideY && this.guideY != null) { this.guideY.node.style.visibility = 'hidden'; } else if (this.guideY != null) { var minX = null; var maxX = null; if (stateY != null && bounds != null) { minX = Math.min(bounds.x + delta.x - this.graph.panDx, stateY.x); maxX = Math.max(bounds.x + bounds.width + delta.x - this.graph.panDx, stateY.x + stateY.width); } if (minX != null && maxX != null) { this.guideY.points = [new mxPoint(minX, valueY), new mxPoint(maxX, valueY)]; } else { this.guideY.points = [new mxPoint(-this.graph.panDx, valueY), new mxPoint(c.scrollWidth - 3 - this.graph.panDx, valueY)]; } this.guideY.stroke = this.getGuideColor(stateY, false); this.guideY.node.style.visibility = 'visible'; this.guideY.redraw(); } } return delta; }; /** * Function: getDelta * * Rounds to pixels for virtual states (eg. page guides) */ mxGuide.prototype.getDelta = function(bounds, stateX, dx, stateY, dy) { var s = this.graph.view.scale; if (this.rounded || (stateX != null && stateX.cell == null)) { dx = Math.round((bounds.x + dx) / s) * s - bounds.x; } if (this.rounded || (stateY != null && stateY.cell == null)) { dy = Math.round((bounds.y + dy) / s) * s - bounds.y; } return new mxPoint(dx, dy); }; /** * Function: getGuideColor * * Returns the color for the given state. */ mxGuide.prototype.getGuideColor = function(state, horizontal) { return mxConstants.GUIDE_COLOR; }; /** * Function: hide * * Hides all current guides. */ mxGuide.prototype.hide = function() { this.setVisible(false); }; /** * Function: setVisible * * Shows or hides the current guides. */ mxGuide.prototype.setVisible = function(visible) { if (this.guideX != null) { this.guideX.node.style.visibility = (visible) ? 'visible' : 'hidden'; } if (this.guideY != null) { this.guideY.node.style.visibility = (visible) ? 'visible' : 'hidden'; } }; /** * Function: destroy * * Destroys all resources that this object uses. */ mxGuide.prototype.destroy = function() { if (this.guideX != null) { this.guideX.destroy(); this.guideX = null; } if (this.guideY != null) { this.guideY.destroy(); this.guideY = null; } };