| /** |
| * Copyright (c) 2006-2016, JGraph Ltd |
| * Copyright (c) 2006-2016, Gaudenz Alder |
| */ |
| /** |
| * 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); |
| |
| // Handles force rubberband event |
| this.forceRubberbandHandler = mxUtils.bind(this, function(sender, evt) |
| { |
| var evtName = evt.getProperty('eventName'); |
| var me = evt.getProperty('event'); |
| |
| if (evtName == mxEvent.MOUSE_DOWN && this.isForceRubberbandEvent(me)) |
| { |
| 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); |
| me.consume(false); |
| } |
| }); |
| |
| this.graph.addListener(mxEvent.FIRE_MOUSE_EVENT, this.forceRubberbandHandler); |
| |
| // Repaints the marquee after autoscroll |
| this.panHandler = mxUtils.bind(this, function() |
| { |
| this.repaint(); |
| }); |
| |
| this.graph.addListener(mxEvent.PAN, this.panHandler); |
| |
| // Does not show menu if any touch gestures take place after the trigger |
| this.gestureHandler = mxUtils.bind(this, function(sender, eo) |
| { |
| if (this.first != null) |
| { |
| this.reset(); |
| } |
| }); |
| |
| this.graph.addListener(mxEvent.GESTURE, this.gestureHandler); |
| |
| // 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: isForceRubberbandEvent |
| * |
| * Returns true if the given <mxMouseEvent> should start rubberband selection. |
| * This implementation returns true if the alt key is pressed. |
| */ |
| mxRubberband.prototype.isForceRubberbandEvent = function(me) |
| { |
| return mxEvent.isAltDown(me.getEvent()); |
| }; |
| |
| /** |
| * 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() && |
| me.getState() == null && !mxEvent.isMultiTouchEvent(me.getEvent())) |
| { |
| 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); |
| |
| // 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); |
| |
| 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)); |
| }); |
| |
| // Workaround for rubberband stopping if the mouse leaves the container in Firefox |
| if (mxClient.IS_FF) |
| { |
| mxEvent.addGestureListeners(document, null, this.dragHandler, this.dropHandler); |
| } |
| }; |
| |
| /** |
| * 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: isActive |
| * |
| * Returns true if this handler is active. |
| */ |
| mxRubberband.prototype.isActive = function(sender, me) |
| { |
| return this.div != null && this.div.style.display != 'none'; |
| }; |
| |
| /** |
| * Function: mouseUp |
| * |
| * Handles the event by selecting the region of the rubberband using |
| * <mxGraph.selectRegion>. |
| */ |
| mxRubberband.prototype.mouseUp = function(sender, me) |
| { |
| var active = this.isActive(); |
| this.reset(); |
| |
| if (active) |
| { |
| this.execute(me.getEvent()); |
| me.consume(); |
| } |
| }; |
| |
| /** |
| * Function: execute |
| * |
| * Resets the state of this handler and selects the current region |
| * for the given event. |
| */ |
| mxRubberband.prototype.execute = function(evt) |
| { |
| var rect = new mxRectangle(this.x, this.y, this.width, this.height); |
| this.graph.selectRegion(rect, evt); |
| }; |
| |
| /** |
| * Function: reset |
| * |
| * Resets the state of the rubberband selection. |
| */ |
| mxRubberband.prototype.reset = function() |
| { |
| if (this.div != null) |
| { |
| this.div.parentNode.removeChild(this.div); |
| } |
| |
| mxEvent.removeGestureListeners(document, null, this.dragHandler, this.dropHandler); |
| this.dragHandler = null; |
| 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.forceRubberbandHandler); |
| this.graph.removeListener(this.panHandler); |
| this.reset(); |
| |
| if (this.sharedDiv != null) |
| { |
| this.sharedDiv = null; |
| } |
| } |
| }; |