/** * $Id: mxRubberband.js,v 1.48 2012-04-13 12:53:30 gaudenz Exp $ * Copyright (c) 2006-2010, JGraph Ltd */ /** * Class: mxRubberband * * Event handler that selects rectangular regions. This is not built-into * <mxGraph>. To enable rubberband selection in a graph, use the following code. * * Example: * * (code) * var rubberband = new mxRubberband(graph); * (end) * * Constructor: mxRubberband * * Constructs an event handler that selects rectangular regions in the graph * using rubberband selection. */ function mxRubberband(graph) { if (graph != null) { this.graph = graph; this.graph.addMouseListener(this); // Repaints the marquee after autoscroll this.panHandler = mxUtils.bind(this, function() { this.repaint(); }); this.graph.addListener(mxEvent.PAN, this.panHandler); // Automatic deallocation of memory if (mxClient.IS_IE) { mxEvent.addListener(window, 'unload', mxUtils.bind(this, function() { this.destroy(); }) ); } } }; /** * Variable: defaultOpacity * * Specifies the default opacity to be used for the rubberband div. Default * is 20. */ mxRubberband.prototype.defaultOpacity = 20; /** * Variable: enabled * * Specifies if events are handled. Default is true. */ mxRubberband.prototype.enabled = true; /** * Variable: div * * Holds the DIV element which is currently visible. */ mxRubberband.prototype.div = null; /** * Variable: sharedDiv * * Holds the DIV element which is used to display the rubberband. */ mxRubberband.prototype.sharedDiv = null; /** * Variable: currentX * * Holds the value of the x argument in the last call to <update>. */ mxRubberband.prototype.currentX = 0; /** * Variable: currentY * * Holds the value of the y argument in the last call to <update>. */ mxRubberband.prototype.currentY = 0; /** * Function: isEnabled * * Returns true if events are handled. This implementation returns * <enabled>. */ mxRubberband.prototype.isEnabled = function() { return this.enabled; }; /** * Function: setEnabled * * Enables or disables event handling. This implementation updates * <enabled>. */ mxRubberband.prototype.setEnabled = function(enabled) { this.enabled = enabled; }; /** * Function: mouseDown * * Handles the event by initiating a rubberband selection. By consuming the * event all subsequent events of the gesture are redirected to this * handler. */ mxRubberband.prototype.mouseDown = function(sender, me) { if (!me.isConsumed() && this.isEnabled() && this.graph.isEnabled() && (this.graph.isForceMarqueeEvent(me.getEvent()) || me.getState() == null)) { var offset = mxUtils.getOffset(this.graph.container); var origin = mxUtils.getScrollOrigin(this.graph.container); origin.x -= offset.x; origin.y -= offset.y; this.start(me.getX() + origin.x, me.getY() + origin.y); // Workaround for rubberband stopping if the mouse leaves the // graph container in Firefox. if (mxClient.IS_NS && !mxClient.IS_SF && !mxClient.IS_GC) { var container = this.graph.container; function createMouseEvent(evt) { var me = new mxMouseEvent(evt); var pt = mxUtils.convertPoint(container, me.getX(), me.getY()); me.graphX = pt.x; me.graphY = pt.y; return me; }; this.dragHandler = mxUtils.bind(this, function(evt) { this.mouseMove(this.graph, createMouseEvent(evt)); }); this.dropHandler = mxUtils.bind(this, function(evt) { this.mouseUp(this.graph, createMouseEvent(evt)); }); mxEvent.addListener(document, 'mousemove', this.dragHandler); mxEvent.addListener(document, 'mouseup', this.dropHandler); } // Does not prevent the default for this event so that the // event processing chain is still executed even if we start // rubberbanding. This is required eg. in ExtJs to hide the // current context menu. In mouseMove we'll make sure we're // not selecting anything while we're rubberbanding. me.consume(false); } }; /** * Function: start * * Sets the start point for the rubberband selection. */ mxRubberband.prototype.start = function(x, y) { this.first = new mxPoint(x, y); }; /** * Function: mouseMove * * Handles the event by updating therubberband selection. */ mxRubberband.prototype.mouseMove = function(sender, me) { if (!me.isConsumed() && this.first != null) { var origin = mxUtils.getScrollOrigin(this.graph.container); var offset = mxUtils.getOffset(this.graph.container); origin.x -= offset.x; origin.y -= offset.y; var x = me.getX() + origin.x; var y = me.getY() + origin.y; var dx = this.first.x - x; var dy = this.first.y - y; var tol = this.graph.tolerance; if (this.div != null || Math.abs(dx) > tol || Math.abs(dy) > tol) { if (this.div == null) { this.div = this.createShape(); } // Clears selection while rubberbanding. This is required because // the event is not consumed in mouseDown. mxUtils.clearSelection(); this.update(x, y); me.consume(); } } }; /** * Function: createShape * * Creates the rubberband selection shape. */ mxRubberband.prototype.createShape = function() { if (this.sharedDiv == null) { this.sharedDiv = document.createElement('div'); this.sharedDiv.className = 'mxRubberband'; mxUtils.setOpacity(this.sharedDiv, this.defaultOpacity); } this.graph.container.appendChild(this.sharedDiv); return this.sharedDiv; }; /** * Function: mouseUp * * Handles the event by selecting the region of the rubberband using * <mxGraph.selectRegion>. */ mxRubberband.prototype.mouseUp = function(sender, me) { var execute = this.div != null; this.reset(); if (execute) { var rect = new mxRectangle(this.x, this.y, this.width, this.height); this.graph.selectRegion(rect, me.getEvent()); me.consume(); } }; /** * Function: reset * * Resets the state of the rubberband selection. */ mxRubberband.prototype.reset = function() { if (this.div != null) { this.div.parentNode.removeChild(this.div); } if (this.dragHandler != null) { mxEvent.removeListener(document, 'mousemove', this.dragHandler); this.dragHandler = null; } if (this.dropHandler != null) { mxEvent.removeListener(document, 'mouseup', this.dropHandler); this.dropHandler = null; } this.currentX = 0; this.currentY = 0; this.first = null; this.div = null; }; /** * Function: update * * Sets <currentX> and <currentY> and calls <repaint>. */ mxRubberband.prototype.update = function(x, y) { this.currentX = x; this.currentY = y; this.repaint(); }; /** * Function: repaint * * Computes the bounding box and updates the style of the <div>. */ mxRubberband.prototype.repaint = function() { if (this.div != null) { var x = this.currentX - this.graph.panDx; var y = this.currentY - this.graph.panDy; this.x = Math.min(this.first.x, x); this.y = Math.min(this.first.y, y); this.width = Math.max(this.first.x, x) - this.x; this.height = Math.max(this.first.y, y) - this.y; var dx = (mxClient.IS_VML) ? this.graph.panDx : 0; var dy = (mxClient.IS_VML) ? this.graph.panDy : 0; this.div.style.left = (this.x + dx) + 'px'; this.div.style.top = (this.y + dy) + 'px'; this.div.style.width = Math.max(1, this.width) + 'px'; this.div.style.height = Math.max(1, this.height) + 'px'; } }; /** * Function: destroy * * Destroys the handler and all its resources and DOM nodes. This does * normally not need to be called, it is called automatically when the * window unloads. */ mxRubberband.prototype.destroy = function() { if (!this.destroyed) { this.destroyed = true; this.graph.removeMouseListener(this); this.graph.removeListener(this.panHandler); this.reset(); if (this.sharedDiv != null) { this.sharedDiv = null; } } };