mxPanningHandler.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  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 document
  89. mxEvent.addListener(document, 'mouseup', this.mouseUpListener);
  90. }
  91. };
  92. /**
  93. * Extends mxEventSource.
  94. */
  95. mxPanningHandler.prototype = new mxEventSource();
  96. mxPanningHandler.prototype.constructor = mxPanningHandler;
  97. /**
  98. * Variable: graph
  99. *
  100. * Reference to the enclosing <mxGraph>.
  101. */
  102. mxPanningHandler.prototype.graph = null;
  103. /**
  104. * Variable: useLeftButtonForPanning
  105. *
  106. * Specifies if panning should be active for the left mouse button.
  107. * Setting this to true may conflict with <mxRubberband>. Default is false.
  108. */
  109. mxPanningHandler.prototype.useLeftButtonForPanning = false;
  110. /**
  111. * Variable: usePopupTrigger
  112. *
  113. * Specifies if <mxEvent.isPopupTrigger> should also be used for panning.
  114. */
  115. mxPanningHandler.prototype.usePopupTrigger = true;
  116. /**
  117. * Variable: ignoreCell
  118. *
  119. * Specifies if panning should be active even if there is a cell under the
  120. * mousepointer. Default is false.
  121. */
  122. mxPanningHandler.prototype.ignoreCell = false;
  123. /**
  124. * Variable: previewEnabled
  125. *
  126. * Specifies if the panning should be previewed. Default is true.
  127. */
  128. mxPanningHandler.prototype.previewEnabled = true;
  129. /**
  130. * Variable: useGrid
  131. *
  132. * Specifies if the panning steps should be aligned to the grid size.
  133. * Default is false.
  134. */
  135. mxPanningHandler.prototype.useGrid = false;
  136. /**
  137. * Variable: panningEnabled
  138. *
  139. * Specifies if panning should be enabled. Default is true.
  140. */
  141. mxPanningHandler.prototype.panningEnabled = true;
  142. /**
  143. * Variable: pinchEnabled
  144. *
  145. * Specifies if pinch gestures should be handled as zoom. Default is true.
  146. */
  147. mxPanningHandler.prototype.pinchEnabled = true;
  148. /**
  149. * Variable: maxScale
  150. *
  151. * Specifies the maximum scale. Default is 8.
  152. */
  153. mxPanningHandler.prototype.maxScale = 8;
  154. /**
  155. * Variable: minScale
  156. *
  157. * Specifies the minimum scale. Default is 0.01.
  158. */
  159. mxPanningHandler.prototype.minScale = 0.01;
  160. /**
  161. * Variable: dx
  162. *
  163. * Holds the current horizontal offset.
  164. */
  165. mxPanningHandler.prototype.dx = null;
  166. /**
  167. * Variable: dy
  168. *
  169. * Holds the current vertical offset.
  170. */
  171. mxPanningHandler.prototype.dy = null;
  172. /**
  173. * Variable: startX
  174. *
  175. * Holds the x-coordinate of the start point.
  176. */
  177. mxPanningHandler.prototype.startX = 0;
  178. /**
  179. * Variable: startY
  180. *
  181. * Holds the y-coordinate of the start point.
  182. */
  183. mxPanningHandler.prototype.startY = 0;
  184. /**
  185. * Function: isActive
  186. *
  187. * Returns true if the handler is currently active.
  188. */
  189. mxPanningHandler.prototype.isActive = function()
  190. {
  191. return this.active || this.initialScale != null;
  192. };
  193. /**
  194. * Function: isPanningEnabled
  195. *
  196. * Returns <panningEnabled>.
  197. */
  198. mxPanningHandler.prototype.isPanningEnabled = function()
  199. {
  200. return this.panningEnabled;
  201. };
  202. /**
  203. * Function: setPanningEnabled
  204. *
  205. * Sets <panningEnabled>.
  206. */
  207. mxPanningHandler.prototype.setPanningEnabled = function(value)
  208. {
  209. this.panningEnabled = value;
  210. };
  211. /**
  212. * Function: isPinchEnabled
  213. *
  214. * Returns <pinchEnabled>.
  215. */
  216. mxPanningHandler.prototype.isPinchEnabled = function()
  217. {
  218. return this.pinchEnabled;
  219. };
  220. /**
  221. * Function: setPinchEnabled
  222. *
  223. * Sets <pinchEnabled>.
  224. */
  225. mxPanningHandler.prototype.setPinchEnabled = function(value)
  226. {
  227. this.pinchEnabled = value;
  228. };
  229. /**
  230. * Function: isPanningTrigger
  231. *
  232. * Returns true if the given event is a panning trigger for the optional
  233. * given cell. This returns true if control-shift is pressed or if
  234. * <usePopupTrigger> is true and the event is a popup trigger.
  235. */
  236. mxPanningHandler.prototype.isPanningTrigger = function(me)
  237. {
  238. var evt = me.getEvent();
  239. return (this.useLeftButtonForPanning && me.getState() == null &&
  240. mxEvent.isLeftMouseButton(evt)) || (mxEvent.isControlDown(evt) &&
  241. mxEvent.isShiftDown(evt)) || (this.usePopupTrigger && mxEvent.isPopupTrigger(evt));
  242. };
  243. /**
  244. * Function: isForcePanningEvent
  245. *
  246. * Returns true if the given <mxMouseEvent> should start panning. This
  247. * implementation always returns true if <ignoreCell> is true or for
  248. * multi touch events.
  249. */
  250. mxPanningHandler.prototype.isForcePanningEvent = function(me)
  251. {
  252. return this.ignoreCell || mxEvent.isMultiTouchEvent(me.getEvent());
  253. };
  254. /**
  255. * Function: mouseDown
  256. *
  257. * Handles the event by initiating the panning. By consuming the event all
  258. * subsequent events of the gesture are redirected to this handler.
  259. */
  260. mxPanningHandler.prototype.mouseDown = function(sender, me)
  261. {
  262. this.mouseDownEvent = me;
  263. if (!me.isConsumed() && this.isPanningEnabled() && !this.active && this.isPanningTrigger(me))
  264. {
  265. this.start(me);
  266. this.consumePanningTrigger(me);
  267. }
  268. };
  269. /**
  270. * Function: start
  271. *
  272. * Starts panning at the given event.
  273. */
  274. mxPanningHandler.prototype.start = function(me)
  275. {
  276. this.dx0 = -this.graph.container.scrollLeft;
  277. this.dy0 = -this.graph.container.scrollTop;
  278. // Stores the location of the trigger event
  279. this.startX = me.getX();
  280. this.startY = me.getY();
  281. this.dx = null;
  282. this.dy = null;
  283. this.panningTrigger = true;
  284. };
  285. /**
  286. * Function: consumePanningTrigger
  287. *
  288. * Consumes the given <mxMouseEvent> if it was a panning trigger in
  289. * <mouseDown>. The default is to invoke <mxMouseEvent.consume>. Note that this
  290. * will block any further event processing. If you haven't disabled built-in
  291. * context menus and require immediate selection of the cell on mouseDown in
  292. * Safari and/or on the Mac, then use the following code:
  293. *
  294. * (code)
  295. * mxPanningHandler.prototype.consumePanningTrigger = function(me)
  296. * {
  297. * if (me.evt.preventDefault)
  298. * {
  299. * me.evt.preventDefault();
  300. * }
  301. *
  302. * // Stops event processing in IE
  303. * me.evt.returnValue = false;
  304. *
  305. * // Sets local consumed state
  306. * if (!mxClient.IS_SF && !mxClient.IS_MAC)
  307. * {
  308. * me.consumed = true;
  309. * }
  310. * };
  311. * (end)
  312. */
  313. mxPanningHandler.prototype.consumePanningTrigger = function(me)
  314. {
  315. me.consume();
  316. };
  317. /**
  318. * Function: mouseMove
  319. *
  320. * Handles the event by updating the panning on the graph.
  321. */
  322. mxPanningHandler.prototype.mouseMove = function(sender, me)
  323. {
  324. this.dx = me.getX() - this.startX;
  325. this.dy = me.getY() - this.startY;
  326. if (this.active)
  327. {
  328. if (this.previewEnabled)
  329. {
  330. // Applies the grid to the panning steps
  331. if (this.useGrid)
  332. {
  333. this.dx = this.graph.snap(this.dx);
  334. this.dy = this.graph.snap(this.dy);
  335. }
  336. this.graph.panGraph(this.dx + this.dx0, this.dy + this.dy0);
  337. }
  338. this.fireEvent(new mxEventObject(mxEvent.PAN, 'event', me));
  339. }
  340. else if (this.panningTrigger)
  341. {
  342. var tmp = this.active;
  343. // Panning is activated only if the mouse is moved
  344. // beyond the graph tolerance
  345. this.active = Math.abs(this.dx) > this.graph.tolerance || Math.abs(this.dy) > this.graph.tolerance;
  346. if (!tmp && this.active)
  347. {
  348. this.fireEvent(new mxEventObject(mxEvent.PAN_START, 'event', me));
  349. }
  350. }
  351. if (this.active || this.panningTrigger)
  352. {
  353. me.consume();
  354. }
  355. };
  356. /**
  357. * Function: mouseUp
  358. *
  359. * Handles the event by setting the translation on the view or showing the
  360. * popupmenu.
  361. */
  362. mxPanningHandler.prototype.mouseUp = function(sender, me)
  363. {
  364. if (this.active)
  365. {
  366. if (this.dx != null && this.dy != null)
  367. {
  368. // Ignores if scrollbars have been used for panning
  369. if (!this.graph.useScrollbarsForPanning || !mxUtils.hasScrollbars(this.graph.container))
  370. {
  371. var scale = this.graph.getView().scale;
  372. var t = this.graph.getView().translate;
  373. this.graph.panGraph(0, 0);
  374. this.panGraph(t.x + this.dx / scale, t.y + this.dy / scale);
  375. }
  376. me.consume();
  377. }
  378. this.fireEvent(new mxEventObject(mxEvent.PAN_END, 'event', me));
  379. }
  380. this.reset();
  381. };
  382. /**
  383. * Function: zoomGraph
  384. *
  385. * Zooms the graph to the given value and consumed the event if needed.
  386. */
  387. mxPanningHandler.prototype.zoomGraph = function(evt)
  388. {
  389. var value = Math.round(this.initialScale * evt.scale * 100) / 100;
  390. if (this.minScale != null)
  391. {
  392. value = Math.max(this.minScale, value);
  393. }
  394. if (this.maxScale != null)
  395. {
  396. value = Math.min(this.maxScale, value);
  397. }
  398. if (this.graph.view.scale != value)
  399. {
  400. this.graph.zoomTo(value);
  401. mxEvent.consume(evt);
  402. }
  403. };
  404. /**
  405. * Function: reset
  406. *
  407. * Resets the state of this handler.
  408. */
  409. mxPanningHandler.prototype.reset = function()
  410. {
  411. this.panningTrigger = false;
  412. this.mouseDownEvent = null;
  413. this.active = false;
  414. this.dx = null;
  415. this.dy = null;
  416. };
  417. /**
  418. * Function: panGraph
  419. *
  420. * Pans <graph> by the given amount.
  421. */
  422. mxPanningHandler.prototype.panGraph = function(dx, dy)
  423. {
  424. this.graph.getView().setTranslate(dx, dy);
  425. };
  426. /**
  427. * Function: destroy
  428. *
  429. * Destroys the handler and all its resources and DOM nodes.
  430. */
  431. mxPanningHandler.prototype.destroy = function()
  432. {
  433. this.graph.removeMouseListener(this);
  434. this.graph.removeListener(this.forcePanningHandler);
  435. this.graph.removeListener(this.gestureHandler);
  436. mxEvent.removeListener(document, 'mouseup', this.mouseUpListener);
  437. };
  438. __mxOutput.mxPanningHandler = typeof mxPanningHandler !== 'undefined' ? mxPanningHandler : undefined;