mxLayoutManager.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. /**
  2. * Copyright (c) 2006-2015, JGraph Ltd
  3. * Copyright (c) 2006-2015, Gaudenz Alder
  4. */
  5. /**
  6. * Class: mxLayoutManager
  7. *
  8. * Implements a layout manager that runs a given layout after any changes to the graph:
  9. *
  10. * Example:
  11. *
  12. * (code)
  13. * var layoutMgr = new mxLayoutManager(graph);
  14. * layoutMgr.getLayout = function(cell, eventName)
  15. * {
  16. * return layout;
  17. * };
  18. * (end)
  19. *
  20. * See <getLayout> for a description of the possible eventNames.
  21. *
  22. * Event: mxEvent.LAYOUT_CELLS
  23. *
  24. * Fires between begin- and endUpdate after all cells have been layouted in
  25. * <layoutCells>. The <code>cells</code> property contains all cells that have
  26. * been passed to <layoutCells>.
  27. *
  28. * Constructor: mxLayoutManager
  29. *
  30. * Constructs a new automatic layout for the given graph.
  31. *
  32. * Arguments:
  33. *
  34. * graph - Reference to the enclosing graph.
  35. */
  36. function mxLayoutManager(graph)
  37. {
  38. // Executes the layout before the changes are dispatched
  39. this.undoHandler = mxUtils.bind(this, function(sender, evt)
  40. {
  41. if (this.isEnabled())
  42. {
  43. this.beforeUndo(evt.getProperty('edit'));
  44. }
  45. });
  46. // Notifies the layout of a move operation inside a parent
  47. this.moveHandler = mxUtils.bind(this, function(sender, evt)
  48. {
  49. if (this.isEnabled())
  50. {
  51. this.cellsMoved(evt.getProperty('cells'), evt.getProperty('event'));
  52. }
  53. });
  54. // Notifies the layout of a move operation inside a parent
  55. this.resizeHandler = mxUtils.bind(this, function(sender, evt)
  56. {
  57. if (this.isEnabled())
  58. {
  59. this.cellsResized(evt.getProperty('cells'), evt.getProperty('bounds'),
  60. evt.getProperty('previous'));
  61. }
  62. });
  63. this.setGraph(graph);
  64. };
  65. /**
  66. * Extends mxEventSource.
  67. */
  68. mxLayoutManager.prototype = new mxEventSource();
  69. mxLayoutManager.prototype.constructor = mxLayoutManager;
  70. /**
  71. * Variable: graph
  72. *
  73. * Reference to the enclosing <mxGraph>.
  74. */
  75. mxLayoutManager.prototype.graph = null;
  76. /**
  77. * Variable: bubbling
  78. *
  79. * Specifies if the layout should bubble along
  80. * the cell hierarchy. Default is true.
  81. */
  82. mxLayoutManager.prototype.bubbling = true;
  83. /**
  84. * Variable: enabled
  85. *
  86. * Specifies if event handling is enabled. Default is true.
  87. */
  88. mxLayoutManager.prototype.enabled = true;
  89. /**
  90. * Variable: undoHandler
  91. *
  92. * Holds the function that handles the endUpdate event.
  93. */
  94. mxLayoutManager.prototype.undoHandler = null;
  95. /**
  96. * Variable: moveHandler
  97. *
  98. * Holds the function that handles the move event.
  99. */
  100. mxLayoutManager.prototype.moveHandler = null;
  101. /**
  102. * Variable: resizeHandler
  103. *
  104. * Holds the function that handles the resize event.
  105. */
  106. mxLayoutManager.prototype.resizeHandler = null;
  107. /**
  108. * Function: isEnabled
  109. *
  110. * Returns true if events are handled. This implementation
  111. * returns <enabled>.
  112. */
  113. mxLayoutManager.prototype.isEnabled = function()
  114. {
  115. return this.enabled;
  116. };
  117. /**
  118. * Function: setEnabled
  119. *
  120. * Enables or disables event handling. This implementation
  121. * updates <enabled>.
  122. *
  123. * Parameters:
  124. *
  125. * enabled - Boolean that specifies the new enabled state.
  126. */
  127. mxLayoutManager.prototype.setEnabled = function(enabled)
  128. {
  129. this.enabled = enabled;
  130. };
  131. /**
  132. * Function: isBubbling
  133. *
  134. * Returns true if a layout should bubble, that is, if the parent layout
  135. * should be executed whenever a cell layout (layout of the children of
  136. * a cell) has been executed. This implementation returns <bubbling>.
  137. */
  138. mxLayoutManager.prototype.isBubbling = function()
  139. {
  140. return this.bubbling;
  141. };
  142. /**
  143. * Function: setBubbling
  144. *
  145. * Sets <bubbling>.
  146. */
  147. mxLayoutManager.prototype.setBubbling = function(value)
  148. {
  149. this.bubbling = value;
  150. };
  151. /**
  152. * Function: getGraph
  153. *
  154. * Returns the graph that this layout operates on.
  155. */
  156. mxLayoutManager.prototype.getGraph = function()
  157. {
  158. return this.graph;
  159. };
  160. /**
  161. * Function: setGraph
  162. *
  163. * Sets the graph that the layouts operate on.
  164. */
  165. mxLayoutManager.prototype.setGraph = function(graph)
  166. {
  167. if (this.graph != null)
  168. {
  169. var model = this.graph.getModel();
  170. model.removeListener(this.undoHandler);
  171. this.graph.removeListener(this.moveHandler);
  172. this.graph.removeListener(this.resizeHandler);
  173. }
  174. this.graph = graph;
  175. if (this.graph != null)
  176. {
  177. var model = this.graph.getModel();
  178. model.addListener(mxEvent.BEFORE_UNDO, this.undoHandler);
  179. this.graph.addListener(mxEvent.MOVE_CELLS, this.moveHandler);
  180. this.graph.addListener(mxEvent.RESIZE_CELLS, this.resizeHandler);
  181. }
  182. };
  183. /**
  184. * Function: hasLayout
  185. *
  186. * Returns true if the given cell has a layout. This implementation invokes
  187. * <getLayout> with <mxEvent.LAYOUT_CELLS> as the eventName. Override this
  188. * if creating layouts in <getLayout> is expensive and return true if
  189. * <getLayout> will return a layout for the given cell for
  190. * <mxEvent.BEGIN_UPDATE> or <mxEvent.END_UPDATE>.
  191. */
  192. mxLayoutManager.prototype.hasLayout = function(cell)
  193. {
  194. return this.getLayout(cell, mxEvent.LAYOUT_CELLS);
  195. };
  196. /**
  197. * Function: getLayout
  198. *
  199. * Returns the layout for the given cell and eventName. Possible
  200. * event names are <mxEvent.MOVE_CELLS> and <mxEvent.RESIZE_CELLS>
  201. * when cells are moved or resized and <mxEvent.BEGIN_UPDATE> or
  202. * <mxEvent.END_UPDATE> for the bottom up and top down phases after
  203. * changes to the graph model. <mxEvent.LAYOUT_CELLS> is used to
  204. * check if a layout exists for the given cell. This is called
  205. * from <hasLayout>.
  206. */
  207. mxLayoutManager.prototype.getLayout = function(cell, eventName)
  208. {
  209. return null;
  210. };
  211. /**
  212. * Function: beforeUndo
  213. *
  214. * Called from <undoHandler>.
  215. *
  216. * Parameters:
  217. *
  218. * cell - Array of <mxCells> that have been moved.
  219. * evt - Mouse event that represents the mousedown.
  220. */
  221. mxLayoutManager.prototype.beforeUndo = function(undoableEdit)
  222. {
  223. this.executeLayoutForCells(this.getCellsForChanges(undoableEdit.changes));
  224. };
  225. /**
  226. * Function: cellsMoved
  227. *
  228. * Called from <moveHandler>.
  229. *
  230. * Parameters:
  231. *
  232. * cell - Array of <mxCells> that have been moved.
  233. * evt - Mouse event that represents the mousedown.
  234. */
  235. mxLayoutManager.prototype.cellsMoved = function(cells, evt)
  236. {
  237. if (cells != null && evt != null)
  238. {
  239. var point = mxUtils.convertPoint(this.getGraph().container,
  240. mxEvent.getClientX(evt), mxEvent.getClientY(evt));
  241. var model = this.getGraph().getModel();
  242. for (var i = 0; i < cells.length; i++)
  243. {
  244. var layout = this.getLayout(model.getParent(cells[i]), mxEvent.MOVE_CELLS);
  245. if (layout != null)
  246. {
  247. layout.moveCell(cells[i], point.x, point.y);
  248. }
  249. }
  250. }
  251. };
  252. /**
  253. * Function: cellsResized
  254. *
  255. * Called from <resizeHandler>.
  256. *
  257. * Parameters:
  258. *
  259. * cell - Array of <mxCells> that have been resized.
  260. * bounds - <mxRectangle> taht represents the new bounds.
  261. */
  262. mxLayoutManager.prototype.cellsResized = function(cells, bounds, prev)
  263. {
  264. if (cells != null && bounds != null)
  265. {
  266. var model = this.getGraph().getModel();
  267. for (var i = 0; i < cells.length; i++)
  268. {
  269. var layout = this.getLayout(model.getParent(cells[i]), mxEvent.RESIZE_CELLS);
  270. if (layout != null)
  271. {
  272. layout.resizeCell(cells[i], bounds[i], prev[i]);
  273. }
  274. }
  275. }
  276. };
  277. /**
  278. * Function: getCellsForChanges
  279. *
  280. * Returns the cells for which a layout should be executed.
  281. */
  282. mxLayoutManager.prototype.getCellsForChanges = function(changes)
  283. {
  284. var result = [];
  285. for (var i = 0; i < changes.length; i++)
  286. {
  287. var change = changes[i];
  288. if (change instanceof mxRootChange)
  289. {
  290. return [];
  291. }
  292. else
  293. {
  294. result = result.concat(this.getCellsForChange(change));
  295. }
  296. }
  297. return result;
  298. };
  299. /**
  300. * Function: getCellsForChange
  301. *
  302. * Executes all layouts which have been scheduled during the
  303. * changes.
  304. */
  305. mxLayoutManager.prototype.getCellsForChange = function(change)
  306. {
  307. if (change instanceof mxChildChange)
  308. {
  309. return this.addCellsWithLayout(change.child,
  310. this.addCellsWithLayout(change.previous));
  311. }
  312. else if (change instanceof mxTerminalChange ||
  313. change instanceof mxGeometryChange)
  314. {
  315. return this.addCellsWithLayout(change.cell);
  316. }
  317. else if (change instanceof mxVisibleChange ||
  318. change instanceof mxStyleChange)
  319. {
  320. return this.addCellsWithLayout(change.cell);
  321. }
  322. return [];
  323. };
  324. /**
  325. * Function: addCellsWithLayout
  326. *
  327. * Adds all ancestors of the given cell that have a layout.
  328. */
  329. mxLayoutManager.prototype.addCellsWithLayout = function(cell, result)
  330. {
  331. return this.addDescendantsWithLayout(cell,
  332. this.addAncestorsWithLayout(cell, result));
  333. };
  334. /**
  335. * Function: addAncestorsWithLayout
  336. *
  337. * Adds all ancestors of the given cell that have a layout.
  338. */
  339. mxLayoutManager.prototype.addAncestorsWithLayout = function(cell, result)
  340. {
  341. result = (result != null) ? result : [];
  342. if (cell != null)
  343. {
  344. var layout = this.hasLayout(cell);
  345. if (layout != null)
  346. {
  347. result.push(cell);
  348. }
  349. if (this.isBubbling())
  350. {
  351. var model = this.getGraph().getModel();
  352. this.addAncestorsWithLayout(
  353. model.getParent(cell), result);
  354. }
  355. }
  356. return result;
  357. };
  358. /**
  359. * Function: addDescendantsWithLayout
  360. *
  361. * Adds all descendants of the given cell that have a layout.
  362. */
  363. mxLayoutManager.prototype.addDescendantsWithLayout = function(cell, result)
  364. {
  365. result = (result != null) ? result : [];
  366. if (cell != null && this.hasLayout(cell))
  367. {
  368. var model = this.getGraph().getModel();
  369. for (var i = 0; i < model.getChildCount(cell); i++)
  370. {
  371. var child = model.getChildAt(cell, i);
  372. if (this.hasLayout(child))
  373. {
  374. result.push(child);
  375. this.addDescendantsWithLayout(child, result);
  376. }
  377. }
  378. }
  379. return result;
  380. };
  381. /**
  382. * Function: executeLayoutForCells
  383. *
  384. * Executes all layouts for the given cells in two phases: In the first phase
  385. * layouts for child cells are executed before layouts for parent cells with
  386. * <mxEvent.BEGIN_UPDATE>, in the second phase layouts for parent cells are
  387. * executed before layouts for child cells with <mxEvent.END_UPDATE>.
  388. */
  389. mxLayoutManager.prototype.executeLayoutForCells = function(cells)
  390. {
  391. var sorted = mxUtils.sortCells(cells, false);
  392. this.layoutCells(sorted, true);
  393. this.layoutCells(sorted.reverse(), false);
  394. };
  395. /**
  396. * Function: layoutCells
  397. *
  398. * Executes all layouts which have been scheduled during the changes.
  399. */
  400. mxLayoutManager.prototype.layoutCells = function(cells, bubble)
  401. {
  402. if (cells.length > 0)
  403. {
  404. // Invokes the layouts while removing duplicates
  405. var model = this.getGraph().getModel();
  406. model.beginUpdate();
  407. try
  408. {
  409. var last = null;
  410. for (var i = 0; i < cells.length; i++)
  411. {
  412. if (cells[i] != model.getRoot() && cells[i] != last)
  413. {
  414. this.executeLayout(cells[i], bubble);
  415. last = cells[i];
  416. }
  417. }
  418. this.fireEvent(new mxEventObject(mxEvent.LAYOUT_CELLS, 'cells', cells));
  419. }
  420. finally
  421. {
  422. model.endUpdate();
  423. }
  424. }
  425. };
  426. /**
  427. * Function: executeLayout
  428. *
  429. * Executes the given layout on the given parent.
  430. */
  431. mxLayoutManager.prototype.executeLayout = function(cell, bubble)
  432. {
  433. var layout = this.getLayout(cell, (bubble) ?
  434. mxEvent.BEGIN_UPDATE : mxEvent.END_UPDATE);
  435. if (layout != null)
  436. {
  437. layout.execute(cell);
  438. }
  439. };
  440. /**
  441. * Function: destroy
  442. *
  443. * Removes all handlers from the <graph> and deletes the reference to it.
  444. */
  445. mxLayoutManager.prototype.destroy = function()
  446. {
  447. this.setGraph(null);
  448. };
  449. __mxOutput.mxLayoutManager = typeof mxLayoutManager !== 'undefined' ? mxLayoutManager : undefined;