mxRubberband.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. /**
  2. * Copyright (c) 2006-2016, JGraph Ltd
  3. * Copyright (c) 2006-2016, Gaudenz Alder
  4. */
  5. /**
  6. * Class: mxRubberband
  7. *
  8. * Event handler that selects rectangular regions. This is not built-into
  9. * <mxGraph>. To enable rubberband selection in a graph, use the following code.
  10. *
  11. * Example:
  12. *
  13. * (code)
  14. * var rubberband = new mxRubberband(graph);
  15. * (end)
  16. *
  17. * Constructor: mxRubberband
  18. *
  19. * Constructs an event handler that selects rectangular regions in the graph
  20. * using rubberband selection.
  21. */
  22. function mxRubberband(graph)
  23. {
  24. if (graph != null)
  25. {
  26. this.graph = graph;
  27. this.graph.addMouseListener(this);
  28. // Handles force rubberband event
  29. this.forceRubberbandHandler = mxUtils.bind(this, function(sender, evt)
  30. {
  31. var evtName = evt.getProperty('eventName');
  32. var me = evt.getProperty('event');
  33. if (evtName == mxEvent.MOUSE_DOWN && this.isForceRubberbandEvent(me))
  34. {
  35. var offset = mxUtils.getOffset(this.graph.container);
  36. var origin = mxUtils.getScrollOrigin(this.graph.container);
  37. origin.x -= offset.x;
  38. origin.y -= offset.y;
  39. this.start(me.getX() + origin.x, me.getY() + origin.y);
  40. me.consume(false);
  41. }
  42. });
  43. this.graph.addListener(mxEvent.FIRE_MOUSE_EVENT, this.forceRubberbandHandler);
  44. // Repaints the marquee after autoscroll
  45. this.panHandler = mxUtils.bind(this, function()
  46. {
  47. this.repaint();
  48. });
  49. this.graph.addListener(mxEvent.PAN, this.panHandler);
  50. // Does not show menu if any touch gestures take place after the trigger
  51. this.gestureHandler = mxUtils.bind(this, function(sender, eo)
  52. {
  53. if (this.first != null)
  54. {
  55. this.reset();
  56. }
  57. });
  58. this.graph.addListener(mxEvent.GESTURE, this.gestureHandler);
  59. // Automatic deallocation of memory
  60. if (mxClient.IS_IE)
  61. {
  62. mxEvent.addListener(window, 'unload',
  63. mxUtils.bind(this, function()
  64. {
  65. this.destroy();
  66. })
  67. );
  68. }
  69. }
  70. };
  71. /**
  72. * Variable: defaultOpacity
  73. *
  74. * Specifies the default opacity to be used for the rubberband div. Default
  75. * is 20.
  76. */
  77. mxRubberband.prototype.defaultOpacity = 20;
  78. /**
  79. * Variable: enabled
  80. *
  81. * Specifies if events are handled. Default is true.
  82. */
  83. mxRubberband.prototype.enabled = true;
  84. /**
  85. * Variable: div
  86. *
  87. * Holds the DIV element which is currently visible.
  88. */
  89. mxRubberband.prototype.div = null;
  90. /**
  91. * Variable: sharedDiv
  92. *
  93. * Holds the DIV element which is used to display the rubberband.
  94. */
  95. mxRubberband.prototype.sharedDiv = null;
  96. /**
  97. * Variable: currentX
  98. *
  99. * Holds the value of the x argument in the last call to <update>.
  100. */
  101. mxRubberband.prototype.currentX = 0;
  102. /**
  103. * Variable: currentY
  104. *
  105. * Holds the value of the y argument in the last call to <update>.
  106. */
  107. mxRubberband.prototype.currentY = 0;
  108. /**
  109. * Variable: fadeOut
  110. *
  111. * Optional fade out effect. Default is false.
  112. */
  113. mxRubberband.prototype.fadeOut = false;
  114. /**
  115. * Function: isEnabled
  116. *
  117. * Returns true if events are handled. This implementation returns
  118. * <enabled>.
  119. */
  120. mxRubberband.prototype.isEnabled = function()
  121. {
  122. return this.enabled;
  123. };
  124. /**
  125. * Function: setEnabled
  126. *
  127. * Enables or disables event handling. This implementation updates
  128. * <enabled>.
  129. */
  130. mxRubberband.prototype.setEnabled = function(enabled)
  131. {
  132. this.enabled = enabled;
  133. };
  134. /**
  135. * Function: isForceRubberbandEvent
  136. *
  137. * Returns true if the given <mxMouseEvent> should start rubberband selection.
  138. * This implementation returns true if the alt key is pressed.
  139. */
  140. mxRubberband.prototype.isForceRubberbandEvent = function(me)
  141. {
  142. return mxEvent.isAltDown(me.getEvent());
  143. };
  144. /**
  145. * Function: mouseDown
  146. *
  147. * Handles the event by initiating a rubberband selection. By consuming the
  148. * event all subsequent events of the gesture are redirected to this
  149. * handler.
  150. */
  151. mxRubberband.prototype.mouseDown = function(sender, me)
  152. {
  153. if (!me.isConsumed() && this.isEnabled() && this.graph.isEnabled() &&
  154. me.getState() == null && !mxEvent.isMultiTouchEvent(me.getEvent()))
  155. {
  156. var offset = mxUtils.getOffset(this.graph.container);
  157. var origin = mxUtils.getScrollOrigin(this.graph.container);
  158. origin.x -= offset.x;
  159. origin.y -= offset.y;
  160. this.start(me.getX() + origin.x, me.getY() + origin.y);
  161. // Does not prevent the default for this event so that the
  162. // event processing chain is still executed even if we start
  163. // rubberbanding. This is required eg. in ExtJs to hide the
  164. // current context menu. In mouseMove we'll make sure we're
  165. // not selecting anything while we're rubberbanding.
  166. me.consume(false);
  167. }
  168. };
  169. /**
  170. * Function: start
  171. *
  172. * Sets the start point for the rubberband selection.
  173. */
  174. mxRubberband.prototype.start = function(x, y)
  175. {
  176. this.first = new mxPoint(x, y);
  177. var container = this.graph.container;
  178. function createMouseEvent(evt)
  179. {
  180. var me = new mxMouseEvent(evt);
  181. var pt = mxUtils.convertPoint(container, me.getX(), me.getY());
  182. me.graphX = pt.x;
  183. me.graphY = pt.y;
  184. return me;
  185. };
  186. this.dragHandler = mxUtils.bind(this, function(evt)
  187. {
  188. this.mouseMove(this.graph, createMouseEvent(evt));
  189. });
  190. this.dropHandler = mxUtils.bind(this, function(evt)
  191. {
  192. this.mouseUp(this.graph, createMouseEvent(evt));
  193. });
  194. // Workaround for rubberband stopping if the mouse leaves the container in Firefox
  195. if (mxClient.IS_FF)
  196. {
  197. mxEvent.addGestureListeners(document, null, this.dragHandler, this.dropHandler);
  198. }
  199. };
  200. /**
  201. * Function: mouseMove
  202. *
  203. * Handles the event by updating therubberband selection.
  204. */
  205. mxRubberband.prototype.mouseMove = function(sender, me)
  206. {
  207. if (!me.isConsumed() && this.first != null)
  208. {
  209. var origin = mxUtils.getScrollOrigin(this.graph.container);
  210. var offset = mxUtils.getOffset(this.graph.container);
  211. origin.x -= offset.x;
  212. origin.y -= offset.y;
  213. var x = me.getX() + origin.x;
  214. var y = me.getY() + origin.y;
  215. var dx = this.first.x - x;
  216. var dy = this.first.y - y;
  217. var tol = this.graph.tolerance;
  218. if (this.div != null || Math.abs(dx) > tol || Math.abs(dy) > tol)
  219. {
  220. if (this.div == null)
  221. {
  222. this.div = this.createShape();
  223. }
  224. // Clears selection while rubberbanding. This is required because
  225. // the event is not consumed in mouseDown.
  226. mxUtils.clearSelection();
  227. this.update(x, y);
  228. me.consume();
  229. }
  230. }
  231. };
  232. /**
  233. * Function: createShape
  234. *
  235. * Creates the rubberband selection shape.
  236. */
  237. mxRubberband.prototype.createShape = function()
  238. {
  239. if (this.sharedDiv == null)
  240. {
  241. this.sharedDiv = document.createElement('div');
  242. this.sharedDiv.className = 'mxRubberband';
  243. mxUtils.setOpacity(this.sharedDiv, this.defaultOpacity);
  244. }
  245. this.graph.container.appendChild(this.sharedDiv);
  246. var result = this.sharedDiv;
  247. if (mxClient.IS_SVG && (!mxClient.IS_IE || document.documentMode >= 10) && this.fadeOut)
  248. {
  249. this.sharedDiv = null;
  250. }
  251. return result;
  252. };
  253. /**
  254. * Function: isActive
  255. *
  256. * Returns true if this handler is active.
  257. */
  258. mxRubberband.prototype.isActive = function(sender, me)
  259. {
  260. return this.div != null && this.div.style.display != 'none';
  261. };
  262. /**
  263. * Function: mouseUp
  264. *
  265. * Handles the event by selecting the region of the rubberband using
  266. * <mxGraph.selectRegion>.
  267. */
  268. mxRubberband.prototype.mouseUp = function(sender, me)
  269. {
  270. var active = this.isActive();
  271. this.reset();
  272. if (active)
  273. {
  274. this.execute(me.getEvent());
  275. me.consume();
  276. }
  277. };
  278. /**
  279. * Function: execute
  280. *
  281. * Resets the state of this handler and selects the current region
  282. * for the given event.
  283. */
  284. mxRubberband.prototype.execute = function(evt)
  285. {
  286. var rect = new mxRectangle(this.x, this.y, this.width, this.height);
  287. this.graph.selectRegion(rect, evt);
  288. };
  289. /**
  290. * Function: reset
  291. *
  292. * Resets the state of the rubberband selection.
  293. */
  294. mxRubberband.prototype.reset = function()
  295. {
  296. if (this.div != null)
  297. {
  298. if (mxClient.IS_SVG && (!mxClient.IS_IE || document.documentMode >= 10) && this.fadeOut)
  299. {
  300. var temp = this.div;
  301. mxUtils.setPrefixedStyle(temp.style, 'transition', 'all 0.2s linear');
  302. temp.style.pointerEvents = 'none';
  303. temp.style.opacity = 0;
  304. window.setTimeout(function()
  305. {
  306. temp.parentNode.removeChild(temp);
  307. }, 200);
  308. }
  309. else
  310. {
  311. this.div.parentNode.removeChild(this.div);
  312. }
  313. }
  314. mxEvent.removeGestureListeners(document, null, this.dragHandler, this.dropHandler);
  315. this.dragHandler = null;
  316. this.dropHandler = null;
  317. this.currentX = 0;
  318. this.currentY = 0;
  319. this.first = null;
  320. this.div = null;
  321. };
  322. /**
  323. * Function: update
  324. *
  325. * Sets <currentX> and <currentY> and calls <repaint>.
  326. */
  327. mxRubberband.prototype.update = function(x, y)
  328. {
  329. this.currentX = x;
  330. this.currentY = y;
  331. this.repaint();
  332. };
  333. /**
  334. * Function: repaint
  335. *
  336. * Computes the bounding box and updates the style of the <div>.
  337. */
  338. mxRubberband.prototype.repaint = function()
  339. {
  340. if (this.div != null)
  341. {
  342. var x = this.currentX - this.graph.panDx;
  343. var y = this.currentY - this.graph.panDy;
  344. this.x = Math.min(this.first.x, x);
  345. this.y = Math.min(this.first.y, y);
  346. this.width = Math.max(this.first.x, x) - this.x;
  347. this.height = Math.max(this.first.y, y) - this.y;
  348. var dx = (mxClient.IS_VML) ? this.graph.panDx : 0;
  349. var dy = (mxClient.IS_VML) ? this.graph.panDy : 0;
  350. this.div.style.left = (this.x + dx) + 'px';
  351. this.div.style.top = (this.y + dy) + 'px';
  352. this.div.style.width = Math.max(1, this.width) + 'px';
  353. this.div.style.height = Math.max(1, this.height) + 'px';
  354. }
  355. };
  356. /**
  357. * Function: destroy
  358. *
  359. * Destroys the handler and all its resources and DOM nodes. This does
  360. * normally not need to be called, it is called automatically when the
  361. * window unloads.
  362. */
  363. mxRubberband.prototype.destroy = function()
  364. {
  365. if (!this.destroyed)
  366. {
  367. this.destroyed = true;
  368. this.graph.removeMouseListener(this);
  369. this.graph.removeListener(this.forceRubberbandHandler);
  370. this.graph.removeListener(this.panHandler);
  371. this.reset();
  372. if (this.sharedDiv != null)
  373. {
  374. this.sharedDiv = null;
  375. }
  376. }
  377. };
  378. __mxOutput.mxRubberband = typeof mxRubberband !== 'undefined' ? mxRubberband : undefined;