mxPanningHandler.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. /**
  2. * Copyright (c) 2006-2015, JGraph Ltd
  3. * Copyright (c) 2006-2015, Gaudenz Alder
  4. */
  5. /**
  6. * Class: mxPanningHandler
  7. *
  8. * Event handler that pans and creates popupmenus. To use the left
  9. * mousebutton for panning without interfering with cell moving and
  10. * resizing, use <isUseLeftButton> and <isIgnoreCell>. For grid size
  11. * steps while panning, use <useGrid>. This handler is built-into
  12. * <mxGraph.panningHandler> and enabled using <mxGraph.setPanning>.
  13. *
  14. * Constructor: mxPanningHandler
  15. *
  16. * Constructs an event handler that creates a <mxPopupMenu>
  17. * and pans the graph.
  18. *
  19. * Event: mxEvent.PAN_START
  20. *
  21. * Fires when the panning handler changes its <active> state to true. The
  22. * <code>event</code> property contains the corresponding <mxMouseEvent>.
  23. *
  24. * Event: mxEvent.PAN
  25. *
  26. * Fires while handle is processing events. The <code>event</code> property contains
  27. * the corresponding <mxMouseEvent>.
  28. *
  29. * Event: mxEvent.PAN_END
  30. *
  31. * Fires when the panning handler changes its <active> state to false. The
  32. * <code>event</code> property contains the corresponding <mxMouseEvent>.
  33. */
  34. function mxPanningHandler(graph)
  35. {
  36. if (graph != null)
  37. {
  38. this.graph = graph;
  39. this.graph.addMouseListener(this);
  40. // Handles force panning event
  41. this.forcePanningHandler = mxUtils.bind(this, function(sender, evt)
  42. {
  43. var evtName = evt.getProperty('eventName');
  44. var me = evt.getProperty('event');
  45. if (evtName == mxEvent.MOUSE_DOWN && this.isForcePanningEvent(me))
  46. {
  47. this.start(me);
  48. this.active = true;
  49. this.fireEvent(new mxEventObject(mxEvent.PAN_START, 'event', me));
  50. me.consume();
  51. }
  52. });
  53. this.graph.addListener(mxEvent.FIRE_MOUSE_EVENT, this.forcePanningHandler);
  54. // Handles pinch gestures
  55. this.gestureHandler = mxUtils.bind(this, function(sender, eo)
  56. {
  57. if (this.isPinchEnabled())
  58. {
  59. var evt = eo.getProperty('event');
  60. if (!mxEvent.isConsumed(evt) && evt.type == 'gesturestart')
  61. {
  62. this.initialScale = this.graph.view.scale;
  63. // Forces start of panning when pinch gesture starts
  64. if (!this.active && this.mouseDownEvent != null)
  65. {
  66. this.start(this.mouseDownEvent);
  67. this.mouseDownEvent = null;
  68. }
  69. }
  70. else if (evt.type == 'gestureend' && this.initialScale != null)
  71. {
  72. this.initialScale = null;
  73. }
  74. if (this.initialScale != null)
  75. {
  76. this.zoomGraph(evt);
  77. }
  78. }
  79. });
  80. this.graph.addListener(mxEvent.GESTURE, this.gestureHandler);
  81. this.mouseUpListener = mxUtils.bind(this, function()
  82. {
  83. if (this.active)
  84. {
  85. this.reset();
  86. }
  87. });
  88. // Stops scrolling on every mouseup anywhere in the
  89. // document and when the mouse leaves the window
  90. mxEvent.addGestureListeners(document, null, null, this.mouseUpListener);
  91. mxEvent.addListener(document, 'mouseleave',this.mouseUpListener);
  92. }
  93. };
  94. /**
  95. * Extends mxEventSource.
  96. */
  97. mxPanningHandler.prototype = new mxEventSource();
  98. mxPanningHandler.prototype.constructor = mxPanningHandler;
  99. /**
  100. * Variable: graph
  101. *
  102. * Reference to the enclosing <mxGraph>.
  103. */
  104. mxPanningHandler.prototype.graph = null;
  105. /**
  106. * Variable: useLeftButtonForPanning
  107. *
  108. * Specifies if panning should be active for the left mouse button.
  109. * Setting this to true may conflict with <mxRubberband>. Default is false.
  110. */
  111. mxPanningHandler.prototype.useLeftButtonForPanning = false;
  112. /**
  113. * Variable: usePopupTrigger
  114. *
  115. * Specifies if <mxEvent.isPopupTrigger> should also be used for panning.
  116. */
  117. mxPanningHandler.prototype.usePopupTrigger = true;
  118. /**
  119. * Variable: ignoreCell
  120. *
  121. * Specifies if panning should be active even if there is a cell under the
  122. * mousepointer. Default is false.
  123. */
  124. mxPanningHandler.prototype.ignoreCell = false;
  125. /**
  126. * Variable: previewEnabled
  127. *
  128. * Specifies if the panning should be previewed. Default is true.
  129. */
  130. mxPanningHandler.prototype.previewEnabled = true;
  131. /**
  132. * Variable: useGrid
  133. *
  134. * Specifies if the panning steps should be aligned to the grid size.
  135. * Default is false.
  136. */
  137. mxPanningHandler.prototype.useGrid = false;
  138. /**
  139. * Variable: panningEnabled
  140. *
  141. * Specifies if panning should be enabled. Default is true.
  142. */
  143. mxPanningHandler.prototype.panningEnabled = true;
  144. /**
  145. * Variable: pinchEnabled
  146. *
  147. * Specifies if pinch gestures should be handled as zoom. Default is true.
  148. */
  149. mxPanningHandler.prototype.pinchEnabled = true;
  150. /**
  151. * Variable: maxScale
  152. *
  153. * Specifies the maximum scale. Default is 8.
  154. */
  155. mxPanningHandler.prototype.maxScale = 8;
  156. /**
  157. * Variable: minScale
  158. *
  159. * Specifies the minimum scale. Default is 0.01.
  160. */
  161. mxPanningHandler.prototype.minScale = 0.01;
  162. /**
  163. * Variable: dx
  164. *
  165. * Holds the current horizontal offset.
  166. */
  167. mxPanningHandler.prototype.dx = null;
  168. /**
  169. * Variable: dy
  170. *
  171. * Holds the current vertical offset.
  172. */
  173. mxPanningHandler.prototype.dy = null;
  174. /**
  175. * Variable: startX
  176. *
  177. * Holds the x-coordinate of the start point.
  178. */
  179. mxPanningHandler.prototype.startX = 0;
  180. /**
  181. * Variable: startY
  182. *
  183. * Holds the y-coordinate of the start point.
  184. */
  185. mxPanningHandler.prototype.startY = 0;
  186. /**
  187. * Function: isActive
  188. *
  189. * Returns true if the handler is currently active.
  190. */
  191. mxPanningHandler.prototype.isActive = function()
  192. {
  193. return this.active || this.initialScale != null;
  194. };
  195. /**
  196. * Function: isPanningEnabled
  197. *
  198. * Returns <panningEnabled>.
  199. */
  200. mxPanningHandler.prototype.isPanningEnabled = function()
  201. {
  202. return this.panningEnabled;
  203. };
  204. /**
  205. * Function: setPanningEnabled
  206. *
  207. * Sets <panningEnabled>.
  208. */
  209. mxPanningHandler.prototype.setPanningEnabled = function(value)
  210. {
  211. this.panningEnabled = value;
  212. };
  213. /**
  214. * Function: isPinchEnabled
  215. *
  216. * Returns <pinchEnabled>.
  217. */
  218. mxPanningHandler.prototype.isPinchEnabled = function()
  219. {
  220. return this.pinchEnabled;
  221. };
  222. /**
  223. * Function: setPinchEnabled
  224. *
  225. * Sets <pinchEnabled>.
  226. */
  227. mxPanningHandler.prototype.setPinchEnabled = function(value)
  228. {
  229. this.pinchEnabled = value;
  230. };
  231. /**
  232. * Function: isPanningTrigger
  233. *
  234. * Returns true if the given event is a panning trigger for the optional
  235. * given cell. This returns true if control-shift is pressed or if
  236. * <usePopupTrigger> is true and the event is a popup trigger.
  237. */
  238. mxPanningHandler.prototype.isPanningTrigger = function(me)
  239. {
  240. var evt = me.getEvent();
  241. return (this.useLeftButtonForPanning && me.getState() == null &&
  242. mxEvent.isLeftMouseButton(evt)) || (mxEvent.isControlDown(evt) &&
  243. mxEvent.isShiftDown(evt)) || (this.usePopupTrigger && mxEvent.isPopupTrigger(evt));
  244. };
  245. /**
  246. * Function: isForcePanningEvent
  247. *
  248. * Returns true if the given <mxMouseEvent> should start panning. This
  249. * implementation always returns true if <ignoreCell> is true or for
  250. * multi touch events.
  251. */
  252. mxPanningHandler.prototype.isForcePanningEvent = function(me)
  253. {
  254. return this.ignoreCell || mxEvent.isMultiTouchEvent(me.getEvent());
  255. };
  256. /**
  257. * Function: mouseDown
  258. *
  259. * Handles the event by initiating the panning. By consuming the event all
  260. * subsequent events of the gesture are redirected to this handler.
  261. */
  262. mxPanningHandler.prototype.mouseDown = function(sender, me)
  263. {
  264. this.mouseDownEvent = me;
  265. if (!me.isConsumed() && this.isPanningEnabled() &&
  266. !this.active && this.isPanningTrigger(me))
  267. {
  268. this.start(me);
  269. this.consumePanningTrigger(me);
  270. }
  271. };
  272. /**
  273. * Function: start
  274. *
  275. * Starts panning at the given event.
  276. */
  277. mxPanningHandler.prototype.start = function(me)
  278. {
  279. this.dx0 = -this.graph.container.scrollLeft;
  280. this.dy0 = -this.graph.container.scrollTop;
  281. // Stores the location of the trigger event
  282. this.startX = me.getX();
  283. this.startY = me.getY();
  284. this.dx = null;
  285. this.dy = null;
  286. this.panningTrigger = true;
  287. };
  288. /**
  289. * Function: consumePanningTrigger
  290. *
  291. * Consumes the given <mxMouseEvent> if it was a panning trigger in
  292. * <mouseDown>. The default is to invoke <mxMouseEvent.consume>. Note that this
  293. * will block any further event processing. If you haven't disabled built-in
  294. * context menus and require immediate selection of the cell on mouseDown in
  295. * Safari and/or on the Mac, then use the following code:
  296. *
  297. * (code)
  298. * mxPanningHandler.prototype.consumePanningTrigger = function(me)
  299. * {
  300. * if (me.evt.preventDefault)
  301. * {
  302. * me.evt.preventDefault();
  303. * }
  304. *
  305. * // Stops event processing in IE
  306. * me.evt.returnValue = false;
  307. *
  308. * // Sets local consumed state
  309. * if (!mxClient.IS_SF && !mxClient.IS_MAC)
  310. * {
  311. * me.consumed = true;
  312. * }
  313. * };
  314. * (end)
  315. */
  316. mxPanningHandler.prototype.consumePanningTrigger = function(me)
  317. {
  318. me.consume();
  319. };
  320. /**
  321. * Function: mouseMove
  322. *
  323. * Handles the event by updating the panning on the graph.
  324. */
  325. mxPanningHandler.prototype.mouseMove = function(sender, me)
  326. {
  327. this.dx = me.getX() - this.startX;
  328. this.dy = me.getY() - this.startY;
  329. if (this.active)
  330. {
  331. if (this.previewEnabled)
  332. {
  333. // Applies the grid to the panning steps
  334. if (this.useGrid)
  335. {
  336. this.dx = this.graph.snap(this.dx);
  337. this.dy = this.graph.snap(this.dy);
  338. }
  339. this.graph.panGraph(this.dx + this.dx0, this.dy + this.dy0);
  340. }
  341. this.fireEvent(new mxEventObject(mxEvent.PAN, 'event', me));
  342. }
  343. else if (this.panningTrigger)
  344. {
  345. var tmp = this.active;
  346. // Panning is activated only if the mouse is moved
  347. // beyond the graph tolerance
  348. this.active = Math.abs(this.dx) > this.graph.tolerance || Math.abs(this.dy) > this.graph.tolerance;
  349. if (!tmp && this.active)
  350. {
  351. this.fireEvent(new mxEventObject(mxEvent.PAN_START, 'event', me));
  352. }
  353. }
  354. if (this.active || this.panningTrigger)
  355. {
  356. me.consume();
  357. }
  358. };
  359. /**
  360. * Function: mouseUp
  361. *
  362. * Handles the event by setting the translation on the view or showing the
  363. * popupmenu.
  364. */
  365. mxPanningHandler.prototype.mouseUp = function(sender, me)
  366. {
  367. if (this.active)
  368. {
  369. if (this.dx != null && this.dy != null)
  370. {
  371. // Ignores if scrollbars have been used for panning
  372. if (!this.graph.useScrollbarsForPanning || !mxUtils.hasScrollbars(this.graph.container))
  373. {
  374. var scale = this.graph.getView().scale;
  375. var t = this.graph.getView().translate;
  376. this.graph.panGraph(0, 0);
  377. this.panGraph(t.x + this.dx / scale, t.y + this.dy / scale);
  378. }
  379. me.consume();
  380. }
  381. this.fireEvent(new mxEventObject(mxEvent.PAN_END, 'event', me));
  382. }
  383. this.reset();
  384. };
  385. /**
  386. * Function: zoomGraph
  387. *
  388. * Zooms the graph to the given value and consumed the event if needed.
  389. */
  390. mxPanningHandler.prototype.zoomGraph = function(evt)
  391. {
  392. var value = Math.round(this.initialScale * evt.scale * 100) / 100;
  393. if (this.minScale != null)
  394. {
  395. value = Math.max(this.minScale, value);
  396. }
  397. if (this.maxScale != null)
  398. {
  399. value = Math.min(this.maxScale, value);
  400. }
  401. if (this.graph.view.scale != value)
  402. {
  403. this.graph.zoomTo(value);
  404. mxEvent.consume(evt);
  405. }
  406. };
  407. /**
  408. * Function: reset
  409. *
  410. * Resets the state of this handler.
  411. */
  412. mxPanningHandler.prototype.reset = function()
  413. {
  414. this.graph.isMouseDown = false
  415. this.panningTrigger = false;
  416. this.mouseDownEvent = null;
  417. this.active = false;
  418. this.dx = null;
  419. this.dy = null;
  420. };
  421. /**
  422. * Function: panGraph
  423. *
  424. * Pans <graph> by the given amount.
  425. */
  426. mxPanningHandler.prototype.panGraph = function(dx, dy)
  427. {
  428. this.graph.getView().setTranslate(dx, dy);
  429. };
  430. /**
  431. * Function: destroy
  432. *
  433. * Destroys the handler and all its resources and DOM nodes.
  434. */
  435. mxPanningHandler.prototype.destroy = function()
  436. {
  437. this.graph.removeMouseListener(this);
  438. this.graph.removeListener(this.forcePanningHandler);
  439. this.graph.removeListener(this.gestureHandler);
  440. mxEvent.removeGestureListeners(document, null, null, this.mouseUpListener);
  441. mxEvent.removeListener(document, 'mouseleave',this.mouseUpListener);
  442. };