diff options
author | Adhitya Kamakshidasan | 2016-04-04 20:02:27 +0530 |
---|---|---|
committer | Adhitya Kamakshidasan | 2016-04-04 20:02:27 +0530 |
commit | 5d474b6e265806c9df3fc80e06f8b4dd7fe16aea (patch) | |
tree | f64a5027d57f49b9833a8eea48acbd0905a1ceb3 /src/js/view/mxOutline.js | |
download | xcos-on-web-5d474b6e265806c9df3fc80e06f8b4dd7fe16aea.tar.gz xcos-on-web-5d474b6e265806c9df3fc80e06f8b4dd7fe16aea.tar.bz2 xcos-on-web-5d474b6e265806c9df3fc80e06f8b4dd7fe16aea.zip |
Initial Commit
Diffstat (limited to 'src/js/view/mxOutline.js')
-rw-r--r-- | src/js/view/mxOutline.js | 649 |
1 files changed, 649 insertions, 0 deletions
diff --git a/src/js/view/mxOutline.js b/src/js/view/mxOutline.js new file mode 100644 index 0000000..a0d6fd3 --- /dev/null +++ b/src/js/view/mxOutline.js @@ -0,0 +1,649 @@ +/** + * $Id: mxOutline.js,v 1.81 2012-06-20 14:13:37 gaudenz Exp $ + * Copyright (c) 2006-2010, JGraph Ltd + */ +/** + * Class: mxOutline + * + * Implements an outline (aka overview) for a graph. Set <updateOnPan> to true + * to enable updates while the source graph is panning. + * + * Example: + * + * (code) + * var outline = new mxOutline(graph, div); + * (end) + * + * If the selection border in the outline appears behind the contents of the + * graph, then you can use the following code. (This may happen when using a + * transparent container for the outline in IE.) + * + * (code) + * mxOutline.prototype.graphRenderHint = mxConstants.RENDERING_HINT_EXACT; + * (end) + * + * To move the graph to the top, left corner the following code can be used. + * + * (code) + * var scale = graph.view.scale; + * var bounds = graph.getGraphBounds(); + * graph.view.setTranslate(-bounds.x / scale, -bounds.y / scale); + * (end) + * + * To toggle the suspended mode, the following can be used. + * + * (code) + * outline.suspended = !outln.suspended; + * if (!outline.suspended) + * { + * outline.update(true); + * } + * (end) + * + * Constructor: mxOutline + * + * Constructs a new outline for the specified graph inside the given + * container. + * + * Parameters: + * + * source - <mxGraph> to create the outline for. + * container - DOM node that will contain the outline. + */ +function mxOutline(source, container) +{ + this.source = source; + + if (container != null) + { + this.init(container); + } +}; + +/** + * Function: source + * + * Reference to the source <mxGraph>. + */ +mxOutline.prototype.source = null; + +/** + * Function: outline + * + * Reference to the outline <mxGraph>. + */ +mxOutline.prototype.outline = null; + +/** + * Function: graphRenderHint + * + * Renderhint to be used for the outline graph. Default is faster. + */ +mxOutline.prototype.graphRenderHint = mxConstants.RENDERING_HINT_FASTER; + +/** + * Variable: enabled + * + * Specifies if events are handled. Default is true. + */ +mxOutline.prototype.enabled = true; + +/** + * Variable: showViewport + * + * Specifies a viewport rectangle should be shown. Default is true. + */ +mxOutline.prototype.showViewport = true; + +/** + * Variable: border + * + * Border to be added at the bottom and right. Default is 10. + */ +mxOutline.prototype.border = 10; + +/** + * Variable: enabled + * + * Specifies the size of the sizer handler. Default is 8. + */ +mxOutline.prototype.sizerSize = 8; + +/** + * Variable: updateOnPan + * + * Specifies if <update> should be called for <mxEvent.PAN> in the source + * graph. Default is false. + */ +mxOutline.prototype.updateOnPan = false; + +/** + * Variable: sizerImage + * + * Optional <mxImage> to be used for the sizer. Default is null. + */ +mxOutline.prototype.sizerImage = null; + +/** + * Variable: suspended + * + * Optional boolean flag to suspend updates. Default is false. This flag will + * also suspend repaints of the outline. To toggle this switch, use the + * following code. + * + * (code) + * nav.suspended = !nav.suspended; + * + * if (!nav.suspended) + * { + * nav.update(true); + * } + * (end) + */ +mxOutline.prototype.suspended = false; + +/** + * Function: init + * + * Initializes the outline inside the given container. + */ +mxOutline.prototype.init = function(container) +{ + this.outline = new mxGraph(container, this.source.getModel(), this.graphRenderHint, this.source.getStylesheet()); + this.outline.foldingEnabled = false; + this.outline.autoScroll = false; + + // Do not repaint when suspended + var outlineGraphModelChanged = this.outline.graphModelChanged; + this.outline.graphModelChanged = mxUtils.bind(this, function(changes) + { + if (!this.suspended && this.outline != null) + { + outlineGraphModelChanged.apply(this.outline, arguments); + } + }); + + // Enables faster painting in SVG + if (mxClient.IS_SVG) + { + var node = this.outline.getView().getCanvas().parentNode; + node.setAttribute('shape-rendering', 'optimizeSpeed'); + node.setAttribute('image-rendering', 'optimizeSpeed'); + } + + // Hides cursors and labels + this.outline.labelsVisible = false; + this.outline.setEnabled(false); + + this.updateHandler = mxUtils.bind(this, function(sender, evt) + { + if (!this.suspended && !this.active) + { + this.update(); + } + }); + + // Updates the scale of the outline after a change of the main graph + this.source.getModel().addListener(mxEvent.CHANGE, this.updateHandler); + this.outline.addMouseListener(this); + + // Adds listeners to keep the outline in sync with the source graph + var view = this.source.getView(); + view.addListener(mxEvent.SCALE, this.updateHandler); + view.addListener(mxEvent.TRANSLATE, this.updateHandler); + view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.updateHandler); + view.addListener(mxEvent.DOWN, this.updateHandler); + view.addListener(mxEvent.UP, this.updateHandler); + + // Updates blue rectangle on scroll + mxEvent.addListener(this.source.container, 'scroll', this.updateHandler); + + this.panHandler = mxUtils.bind(this, function(sender) + { + if (this.updateOnPan) + { + this.updateHandler.apply(this, arguments); + } + }); + this.source.addListener(mxEvent.PAN, this.panHandler); + + // Refreshes the graph in the outline after a refresh of the main graph + this.refreshHandler = mxUtils.bind(this, function(sender) + { + this.outline.setStylesheet(this.source.getStylesheet()); + this.outline.refresh(); + }); + this.source.addListener(mxEvent.REFRESH, this.refreshHandler); + + // Creates the blue rectangle for the viewport + this.bounds = new mxRectangle(0, 0, 0, 0); + this.selectionBorder = new mxRectangleShape(this.bounds, null, + mxConstants.OUTLINE_COLOR, mxConstants.OUTLINE_STROKEWIDTH); + this.selectionBorder.dialect = + (this.outline.dialect != mxConstants.DIALECT_SVG) ? + mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG; + this.selectionBorder.crisp = true; + this.selectionBorder.init(this.outline.getView().getOverlayPane()); + mxEvent.redirectMouseEvents(this.selectionBorder.node, this.outline); + this.selectionBorder.node.style.background = ''; + + // Creates a small blue rectangle for sizing (sizer handle) + this.sizer = this.createSizer(); + this.sizer.init(this.outline.getView().getOverlayPane()); + + if (this.enabled) + { + this.sizer.node.style.cursor = 'pointer'; + } + + // Redirects all events from the sizerhandle to the outline + mxEvent.addListener(this.sizer.node, (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown', + mxUtils.bind(this, function(evt) + { + this.outline.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt)); + }) + ); + + this.selectionBorder.node.style.display = (this.showViewport) ? '' : 'none'; + this.sizer.node.style.display = this.selectionBorder.node.style.display; + this.selectionBorder.node.style.cursor = 'move'; + + this.update(false); +}; + +/** + * Function: isEnabled + * + * Returns true if events are handled. This implementation + * returns <enabled>. + */ +mxOutline.prototype.isEnabled = function() +{ + return this.enabled; +}; + +/** + * Function: setEnabled + * + * Enables or disables event handling. This implementation + * updates <enabled>. + * + * Parameters: + * + * value - Boolean that specifies the new enabled state. + */ +mxOutline.prototype.setEnabled = function(value) +{ + this.enabled = value; +}; + +/** + * Function: setZoomEnabled + * + * Enables or disables the zoom handling by showing or hiding the respective + * handle. + * + * Parameters: + * + * value - Boolean that specifies the new enabled state. + */ +mxOutline.prototype.setZoomEnabled = function(value) +{ + this.sizer.node.style.visibility = (value) ? 'visible' : 'hidden'; +}; + +/** + * Function: refresh + * + * Invokes <update> and revalidate the outline. This method is deprecated. + */ +mxOutline.prototype.refresh = function() +{ + this.update(true); +}; + +/** + * Function: createSizer + * + * Creates the shape used as the sizer. + */ +mxOutline.prototype.createSizer = function() +{ + if (this.sizerImage != null) + { + var sizer = new mxImageShape(new mxRectangle(0, 0, this.sizerImage.width, this.sizerImage.height), this.sizerImage.src); + sizer.dialect = this.outline.dialect; + + return sizer; + } + else + { + var sizer = new mxRectangleShape(new mxRectangle(0, 0, this.sizerSize, this.sizerSize), + mxConstants.OUTLINE_HANDLE_FILLCOLOR, mxConstants.OUTLINE_HANDLE_STROKECOLOR); + sizer.dialect = this.outline.dialect; + sizer.crisp = true; + + return sizer; + } +}; + +/** + * Function: getSourceContainerSize + * + * Returns the size of the source container. + */ +mxOutline.prototype.getSourceContainerSize = function() +{ + return new mxRectangle(0, 0, this.source.container.scrollWidth, this.source.container.scrollHeight); +}; + +/** + * Function: getOutlineOffset + * + * Returns the offset for drawing the outline graph. + */ +mxOutline.prototype.getOutlineOffset = function(scale) +{ + return null; +}; + +/** + * Function: update + * + * Updates the outline. + */ +mxOutline.prototype.update = function(revalidate) +{ + if (this.source != null) + { + var sourceScale = this.source.view.scale; + var scaledGraphBounds = this.source.getGraphBounds(); + var unscaledGraphBounds = new mxRectangle(scaledGraphBounds.x / sourceScale + this.source.panDx, + scaledGraphBounds.y / sourceScale + this.source.panDy, scaledGraphBounds.width / sourceScale, + scaledGraphBounds.height / sourceScale); + + var unscaledFinderBounds = new mxRectangle(0, 0, + this.source.container.clientWidth / sourceScale, + this.source.container.clientHeight / sourceScale); + + var union = unscaledGraphBounds.clone(); + union.add(unscaledFinderBounds); + + // Zooms to the scrollable area if that is bigger than the graph + var size = this.getSourceContainerSize(); + var completeWidth = Math.max(size.width / sourceScale, union.width); + var completeHeight = Math.max(size.height / sourceScale, union.height); + + var availableWidth = Math.max(0, this.outline.container.clientWidth - this.border); + var availableHeight = Math.max(0, this.outline.container.clientHeight - this.border); + + var outlineScale = Math.min(availableWidth / completeWidth, availableHeight / completeHeight); + var scale = outlineScale; + + if (scale > 0) + { + if (this.outline.getView().scale != scale) + { + this.outline.getView().scale = scale; + revalidate = true; + } + + var navView = this.outline.getView(); + + if (navView.currentRoot != this.source.getView().currentRoot) + { + navView.setCurrentRoot(this.source.getView().currentRoot); + } + + var t = this.source.view.translate; + var tx = t.x + this.source.panDx; + var ty = t.y + this.source.panDy; + + var off = this.getOutlineOffset(scale); + + if (off != null) + { + tx += off.x; + ty += off.y; + } + + if (unscaledGraphBounds.x < 0) + { + tx = tx - unscaledGraphBounds.x; + } + if (unscaledGraphBounds.y < 0) + { + ty = ty - unscaledGraphBounds.y; + } + + if (navView.translate.x != tx || navView.translate.y != ty) + { + navView.translate.x = tx; + navView.translate.y = ty; + revalidate = true; + } + + // Prepares local variables for computations + var t2 = navView.translate; + scale = this.source.getView().scale; + var scale2 = scale / navView.scale; + var scale3 = 1.0 / navView.scale; + var container = this.source.container; + + // Updates the bounds of the viewrect in the navigation + this.bounds = new mxRectangle( + (t2.x - t.x - this.source.panDx) / scale3, + (t2.y - t.y - this.source.panDy) / scale3, + (container.clientWidth / scale2), + (container.clientHeight / scale2)); + + // Adds the scrollbar offset to the finder + this.bounds.x += this.source.container.scrollLeft * navView.scale / scale; + this.bounds.y += this.source.container.scrollTop * navView.scale / scale; + + var b = this.selectionBorder.bounds; + + if (b.x != this.bounds.x || b.y != this.bounds.y || b.width != this.bounds.width || b.height != this.bounds.height) + { + this.selectionBorder.bounds = this.bounds; + this.selectionBorder.redraw(); + } + + // Updates the bounds of the zoom handle at the bottom right + var b = this.sizer.bounds; + var b2 = new mxRectangle(this.bounds.x + this.bounds.width - b.width / 2, + this.bounds.y + this.bounds.height - b.height / 2, b.width, b.height); + + if (b.x != b2.x || b.y != b2.y || b.width != b2.width || b.height != b2.height) + { + this.sizer.bounds = b2; + + // Avoids update of visibility in redraw for VML + if (this.sizer.node.style.visibility != 'hidden') + { + this.sizer.redraw(); + } + } + + if (revalidate) + { + this.outline.view.revalidate(); + } + } + } +}; + +/** + * Function: mouseDown + * + * Handles the event by starting a translation or zoom. + */ +mxOutline.prototype.mouseDown = function(sender, me) +{ + if (this.enabled && this.showViewport) + { + this.zoom = me.isSource(this.sizer); + this.startX = me.getX(); + this.startY = me.getY(); + this.active = true; + + if (this.source.useScrollbarsForPanning && + mxUtils.hasScrollbars(this.source.container)) + { + this.dx0 = this.source.container.scrollLeft; + this.dy0 = this.source.container.scrollTop; + } + else + { + this.dx0 = 0; + this.dy0 = 0; + } + } + + me.consume(); +}; + +/** + * Function: mouseMove + * + * Handles the event by previewing the viewrect in <graph> and updating the + * rectangle that represents the viewrect in the outline. + */ +mxOutline.prototype.mouseMove = function(sender, me) +{ + if (this.active) + { + this.selectionBorder.node.style.display = (this.showViewport) ? '' : 'none'; + this.sizer.node.style.display = this.selectionBorder.node.style.display; + + var dx = me.getX() - this.startX; + var dy = me.getY() - this.startY; + var bounds = null; + + if (!this.zoom) + { + // Previews the panning on the source graph + var scale = this.outline.getView().scale; + bounds = new mxRectangle(this.bounds.x + dx, + this.bounds.y + dy, this.bounds.width, this.bounds.height); + this.selectionBorder.bounds = bounds; + this.selectionBorder.redraw(); + dx /= scale; + dx *= this.source.getView().scale; + dy /= scale; + dy *= this.source.getView().scale; + this.source.panGraph(-dx - this.dx0, -dy - this.dy0); + } + else + { + // Does *not* preview zooming on the source graph + var container = this.source.container; + var viewRatio = container.clientWidth / container.clientHeight; + dy = dx / viewRatio; + bounds = new mxRectangle(this.bounds.x, + this.bounds.y, + Math.max(1, this.bounds.width + dx), + Math.max(1, this.bounds.height + dy)); + this.selectionBorder.bounds = bounds; + this.selectionBorder.redraw(); + } + + // Updates the zoom handle + var b = this.sizer.bounds; + this.sizer.bounds = new mxRectangle( + bounds.x + bounds.width - b.width / 2, + bounds.y + bounds.height - b.height / 2, + b.width, b.height); + + // Avoids update of visibility in redraw for VML + if (this.sizer.node.style.visibility != 'hidden') + { + this.sizer.redraw(); + } + + me.consume(); + } +}; + +/** + * Function: mouseUp + * + * Handles the event by applying the translation or zoom to <graph>. + */ +mxOutline.prototype.mouseUp = function(sender, me) +{ + if (this.active) + { + var dx = me.getX() - this.startX; + var dy = me.getY() - this.startY; + + if (Math.abs(dx) > 0 || Math.abs(dy) > 0) + { + if (!this.zoom) + { + // Applies the new translation if the source + // has no scrollbars + if (!this.source.useScrollbarsForPanning || + !mxUtils.hasScrollbars(this.source.container)) + { + this.source.panGraph(0, 0); + dx /= this.outline.getView().scale; + dy /= this.outline.getView().scale; + var t = this.source.getView().translate; + this.source.getView().setTranslate(t.x - dx, t.y - dy); + } + } + else + { + // Applies the new zoom + var w = this.selectionBorder.bounds.width; + var scale = this.source.getView().scale; + this.source.zoomTo(scale - (dx * scale) / w, false); + } + + this.update(); + me.consume(); + } + + // Resets the state of the handler + this.index = null; + this.active = false; + } +}; + +/** + * Function: destroy + * + * Destroy this outline and removes all listeners from <source>. + */ +mxOutline.prototype.destroy = function() +{ + if (this.source != null) + { + this.source.removeListener(this.panHandler); + this.source.removeListener(this.refreshHandler); + this.source.getModel().removeListener(this.updateHandler); + this.source.getView().removeListener(this.updateHandler); + mxEvent.addListener(this.source.container, 'scroll', this.updateHandler); + this.source = null; + } + + if (this.outline != null) + { + this.outline.removeMouseListener(this); + this.outline.destroy(); + this.outline = null; + } + + if (this.selectionBorder != null) + { + this.selectionBorder.destroy(); + this.selectionBorder = null; + } + + if (this.sizer != null) + { + this.sizer.destroy(); + this.sizer = null; + } +}; |