mxLayoutManager.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  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) != null;
  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 mxValueChange ||
  313. change instanceof mxTerminalChange ||
  314. change instanceof mxGeometryChange ||
  315. change instanceof mxVisibleChange ||
  316. change instanceof mxStyleChange)
  317. {
  318. return this.addCellsWithLayout(change.cell);
  319. }
  320. return [];
  321. };
  322. /**
  323. * Function: addCellsWithLayout
  324. *
  325. * Adds all ancestors of the given cell that have a layout.
  326. */
  327. mxLayoutManager.prototype.addCellsWithLayout = function(cell, result)
  328. {
  329. return this.addDescendantsWithLayout(cell,
  330. this.addAncestorsWithLayout(cell, result));
  331. };
  332. /**
  333. * Function: addAncestorsWithLayout
  334. *
  335. * Adds all ancestors of the given cell that have a layout.
  336. */
  337. mxLayoutManager.prototype.addAncestorsWithLayout = function(cell, result)
  338. {
  339. result = (result != null) ? result : [];
  340. if (cell != null)
  341. {
  342. if (this.hasLayout(cell))
  343. {
  344. result.push(cell);
  345. }
  346. if (this.isBubbling())
  347. {
  348. var model = this.getGraph().getModel();
  349. this.addAncestorsWithLayout(
  350. model.getParent(cell), result);
  351. }
  352. }
  353. return result;
  354. };
  355. /**
  356. * Function: addDescendantsWithLayout
  357. *
  358. * Adds all descendants of the given cell that have a layout.
  359. */
  360. mxLayoutManager.prototype.addDescendantsWithLayout = function(cell, result)
  361. {
  362. result = (result != null) ? result : [];
  363. if (cell != null && this.hasLayout(cell))
  364. {
  365. var model = this.getGraph().getModel();
  366. for (var i = 0; i < model.getChildCount(cell); i++)
  367. {
  368. var child = model.getChildAt(cell, i);
  369. if (this.hasLayout(child))
  370. {
  371. result.push(child);
  372. this.addDescendantsWithLayout(child, result);
  373. }
  374. }
  375. }
  376. return result;
  377. };
  378. /**
  379. * Function: executeLayoutForCells
  380. *
  381. * Executes all layouts for the given cells in two phases: In the first phase
  382. * layouts for child cells are executed before layouts for parent cells with
  383. * <mxEvent.BEGIN_UPDATE>, in the second phase layouts for parent cells are
  384. * executed before layouts for child cells with <mxEvent.END_UPDATE>.
  385. */
  386. mxLayoutManager.prototype.executeLayoutForCells = function(cells)
  387. {
  388. var model = this.getGraph().getModel();
  389. model.beginUpdate();
  390. try
  391. {
  392. var sorted = mxUtils.sortCells(cells, false);
  393. this.layoutCells(sorted, true);
  394. this.layoutCells(sorted.reverse(), false);
  395. }
  396. finally
  397. {
  398. model.endUpdate();
  399. }
  400. };
  401. /**
  402. * Function: layoutCells
  403. *
  404. * Executes all layouts which have been scheduled during the changes.
  405. */
  406. mxLayoutManager.prototype.layoutCells = function(cells, bubble)
  407. {
  408. if (cells.length > 0)
  409. {
  410. // Invokes the layouts while removing duplicates
  411. var model = this.getGraph().getModel();
  412. model.beginUpdate();
  413. try
  414. {
  415. var last = null;
  416. for (var i = 0; i < cells.length; i++)
  417. {
  418. if (cells[i] != model.getRoot() && cells[i] != last)
  419. {
  420. this.executeLayout(cells[i], bubble);
  421. last = cells[i];
  422. }
  423. }
  424. this.fireEvent(new mxEventObject(mxEvent.LAYOUT_CELLS, 'cells', cells));
  425. }
  426. finally
  427. {
  428. model.endUpdate();
  429. }
  430. }
  431. };
  432. /**
  433. * Function: executeLayout
  434. *
  435. * Executes the given layout on the given parent.
  436. */
  437. mxLayoutManager.prototype.executeLayout = function(cell, bubble)
  438. {
  439. var layout = this.getLayout(cell, (bubble) ?
  440. mxEvent.BEGIN_UPDATE : mxEvent.END_UPDATE);
  441. if (layout != null)
  442. {
  443. layout.execute(cell);
  444. }
  445. };
  446. /**
  447. * Function: destroy
  448. *
  449. * Removes all handlers from the <graph> and deletes the reference to it.
  450. */
  451. mxLayoutManager.prototype.destroy = function()
  452. {
  453. this.setGraph(null);
  454. };