Editor.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983
  1. /**
  2. *$id:Action 。JS,V 2017-12-19
  3. *$author shen.zhi
  4. */
  5. //指定是否应使用本地存储(如在ipad没有文件系统)
  6. var useLocalStorage = (mxClient.IS_TOUCH || urlParams['storage'] == 'local') && typeof(localStorage) != 'undefined';
  7. var fileSupport = window.File != null && window.FileReader != null && window.FileList != null;
  8. //指定是否应在选定的单元格中显示的连接器
  9. var touchStyle = mxClient.IS_TOUCH || urlParams['touch'] == '1';
  10. //计数打开编辑器标签(必须是全球性的跨窗口访问)
  11. var counter = 0;
  12. //跨域窗口访问是不允许在FF,所以如果我们
  13. //打开了从另一个领域则会失败。
  14. try
  15. {
  16. var op = window;
  17. while (op.opener != null && !isNaN(op.opener.counter))
  18. {
  19. op = op.opener;
  20. }
  21. //增量在链中的第一刀计数器
  22. if (op != null)
  23. {
  24. op.counter++;
  25. counter = op.counter;
  26. }
  27. }
  28. catch (e)
  29. {
  30. //忽略
  31. }
  32. /**
  33. *构造函数执行编辑页面加载。
  34. */
  35. Editor = function()
  36. {
  37. mxEventSource.call(this);
  38. this.init();
  39. this.initStencilRegistry();
  40. this.graph = new Graph();
  41. this.outline = new mxOutline(this.graph);
  42. this.outline.updateOnPan = true;
  43. this.undoManager = this.createUndoManager();
  44. this.status = '';
  45. // Contains the name which was used for the last save. Default value is null.
  46. this.filename = null;
  47. this.getOrCreateFilename = function()
  48. {
  49. return this.filename || mxResources.get('drawing', [counter]) + '.xml';
  50. };
  51. this.getFilename = function()
  52. {
  53. return this.filename;
  54. };
  55. // Sets the status and fires a statusChanged event
  56. this.setStatus = function(value)
  57. {
  58. this.status = value;
  59. this.fireEvent(new mxEventObject('statusChanged'));
  60. };
  61. // Returns the current status
  62. this.getStatus = function()
  63. {
  64. return this.status;
  65. };
  66. // Contains the current modified state of the diagram. This is false for
  67. // new diagrams and after the diagram was saved.
  68. this.modified = false;
  69. // Updates modified state if graph changes
  70. this.graph.getModel().addListener(mxEvent.CHANGE, mxUtils.bind(this, function()
  71. {
  72. this.modified = true;
  73. }));
  74. // Installs dialog if browser window is closed without saving
  75. // This must be disabled during save and image export
  76. window.onbeforeunload = mxUtils.bind(this, function()
  77. {
  78. if (this.modified)
  79. {
  80. return mxResources.get('allChangesLost');
  81. }
  82. });
  83. // Sets persistent graph state defaults
  84. this.graph.resetViewOnRootChange = false;
  85. this.graph.scrollbars = true;
  86. this.graph.background = null;
  87. };
  88. // Editor inherits from mxEventSource
  89. mxUtils.extend(Editor, mxEventSource);
  90. /**
  91. * Specifies the image URL to be used for the grid.
  92. */
  93. Editor.prototype.gridImage = IMAGE_PATH + '/grid.gif';
  94. /**
  95. * Specifies the image URL to be used for the transparent background.
  96. */
  97. Editor.prototype.transparentImage = IMAGE_PATH + '/transparent.gif';
  98. /**
  99. * Sets the XML node for the current diagram.
  100. */
  101. Editor.prototype.setGraphXml = function(node)
  102. {
  103. var dec = new mxCodec(node.ownerDocument);
  104. if (node.nodeName == 'mxGraphModel')
  105. {
  106. this.graph.view.scale = Number(node.getAttribute('scale') || 1);
  107. this.graph.gridEnabled = node.getAttribute('grid') != '0';
  108. this.graph.graphHandler.guidesEnabled = node.getAttribute('guides') != '0';
  109. this.graph.setTooltips(node.getAttribute('tooltips') != '0');
  110. this.graph.setConnectable(node.getAttribute('connect') != '0');
  111. this.graph.foldingEnabled = node.getAttribute('fold') != '0';
  112. this.graph.scrollbars = node.getAttribute('scrollbars') != '0';
  113. if (!this.graph.scrollbars)
  114. {
  115. this.graph.container.scrollLeft = 0;
  116. this.graph.container.scrollTop = 0;
  117. this.graph.view.translate.x = Number(node.getAttribute('dx') || 0);
  118. this.graph.view.translate.y = Number(node.getAttribute('dy') || 0);
  119. }
  120. this.graph.pageVisible = node.getAttribute('page') == '1';
  121. this.graph.pageBreaksVisible = this.graph.pageVisible;
  122. this.graph.preferPageSize = this.graph.pageBreaksVisible;
  123. // Loads the persistent state settings
  124. var ps = node.getAttribute('pageScale');
  125. if (ps != null)
  126. {
  127. this.graph.pageScale = ps;
  128. }
  129. else
  130. {
  131. this.graph.pageScale = 1.5;
  132. }
  133. var pw = node.getAttribute('pageWidth');
  134. var ph = node.getAttribute('pageHeight');
  135. if (pw != null && ph != null)
  136. {
  137. this.graph.pageFormat = new mxRectangle(0, 0, parseFloat(pw), parseFloat(ph));
  138. }
  139. // Loads the persistent state settings
  140. var bg = node.getAttribute('background');
  141. if (bg != null && bg.length > 0)
  142. {
  143. this.graph.background = bg;
  144. }
  145. dec.decode(node, this.graph.getModel());
  146. this.updateGraphComponents();
  147. }
  148. };
  149. /**
  150. * Returns the XML node that represents the current diagram.
  151. */
  152. Editor.prototype.getGraphXml = function()
  153. {
  154. var enc = new mxCodec(mxUtils.createXmlDocument());
  155. var node = enc.encode(this.graph.getModel());
  156. if (this.graph.view.translate.x != 0 || this.graph.view.translate.y != 0)
  157. {
  158. node.setAttribute('dx', Math.round(this.graph.view.translate.x * 100) / 100);
  159. node.setAttribute('dy', Math.round(this.graph.view.translate.y * 100) / 100);
  160. }
  161. if (this.graph.view.scale != 1)
  162. {
  163. node.setAttribute('scale', Math.round(this.graph.view.scale * 1000) / 1000);
  164. }
  165. node.setAttribute('grid', (this.graph.isGridEnabled()) ? '1' : '0');
  166. node.setAttribute('guides', (this.graph.graphHandler.guidesEnabled) ? '1' : '0');
  167. node.setAttribute('guides', (this.graph.graphHandler.guidesEnabled) ? '1' : '0');
  168. node.setAttribute('tooltips', (this.graph.tooltipHandler.isEnabled()) ? '1' : '0');
  169. node.setAttribute('connect', (this.graph.connectionHandler.isEnabled()) ? '1' : '0');
  170. node.setAttribute('fold', (this.graph.foldingEnabled) ? '1' : '0');
  171. node.setAttribute('page', (this.graph.pageVisible) ? '1' : '0');
  172. node.setAttribute('pageScale', this.graph.pageScale);
  173. node.setAttribute('pageWidth', this.graph.pageFormat.width);
  174. node.setAttribute('pageHeight', this.graph.pageFormat.height);
  175. if (!this.graph.scrollbars)
  176. {
  177. node.setAttribute('scrollbars', '0');
  178. }
  179. if (this.graph.background != null)
  180. {
  181. node.setAttribute('background', this.graph.background);
  182. }
  183. return node;
  184. };
  185. /**
  186. * Keeps the graph container in sync with the persistent graph state
  187. */
  188. Editor.prototype.updateGraphComponents = function()
  189. {
  190. var graph = this.graph;
  191. var outline = this.outline;
  192. if (graph.container != null && outline.outline.container != null)
  193. {
  194. if (graph.background != null)
  195. {
  196. if (graph.background == 'none')
  197. {
  198. graph.container.style.backgroundColor = 'transparent';
  199. }
  200. else
  201. {
  202. if (graph.view.backgroundPageShape != null)
  203. {
  204. graph.view.backgroundPageShape.fill = graph.background;
  205. graph.view.backgroundPageShape.reconfigure();
  206. }
  207. graph.container.style.backgroundColor = graph.background;
  208. }
  209. }
  210. else
  211. {
  212. graph.container.style.backgroundColor = '';
  213. }
  214. if (graph.pageVisible)
  215. {
  216. graph.container.style.backgroundColor = '#ebebeb';
  217. graph.container.style.borderStyle = 'solid';
  218. graph.container.style.borderColor = '#e5e5e5';
  219. graph.container.style.borderTopWidth = '1px';
  220. graph.container.style.borderLeftWidth = '1px';
  221. graph.container.style.borderRightWidth = '0px';
  222. graph.container.style.borderBottomWidth = '0px';
  223. }
  224. else
  225. {
  226. graph.container.style.border = '';
  227. }
  228. outline.outline.container.style.backgroundColor = graph.container.style.backgroundColor;
  229. if (outline.outline.pageVisible != graph.pageVisible ||
  230. outline.outline.pageScale != graph.pageScale)
  231. {
  232. outline.outline.pageScale = graph.pageScale;
  233. outline.outline.pageVisible = graph.pageVisible;
  234. outline.outline.view.validate();
  235. }
  236. if (graph.scrollbars && graph.container.style.overflow == 'hidden' && !touchStyle)
  237. {
  238. graph.container.style.overflow = 'auto';
  239. }
  240. else if (!graph.scrollbars || touchStyle)
  241. {
  242. graph.container.style.overflow = 'hidden';
  243. }
  244. // Transparent.gif is a workaround for focus repaint problems in IE
  245. var noBackground = (mxClient.IS_IE && document.documentMode >= 9) ? 'url(' + this.transparentImage + ')' : 'none';
  246. graph.container.style.backgroundImage = (!graph.pageVisible && graph.isGridEnabled()) ? 'url(' + this.gridImage + ')' : noBackground;
  247. if (graph.view.backgroundPageShape != null)
  248. {
  249. graph.view.backgroundPageShape.node.style.backgroundImage = (this.graph.isGridEnabled()) ? 'url(' + this.gridImage + ')' : 'none';
  250. }
  251. }
  252. };
  253. /**
  254. * Initializes the environment.
  255. */
  256. Editor.prototype.init = function()
  257. {
  258. // Adds stylesheet for IE6
  259. if (mxClient.IS_IE6)
  260. {
  261. mxClient.link('stylesheet', CSS_PATH + '/grapheditor-ie6.css');
  262. }
  263. // Adds required resources (disables loading of fallback properties, this can only
  264. // be used if we know that all keys are defined in the language specific file)
  265. mxResources.loadDefaultBundle = false;
  266. mxResources.add(RESOURCE_BASE);
  267. // Makes the connection hotspot smaller
  268. mxConstants.DEFAULT_HOTSPOT = 0.3;
  269. var mxConnectionHandlerCreateMarker = mxConnectionHandler.prototype.createMarker;
  270. mxConnectionHandler.prototype.createMarker = function()
  271. {
  272. var marker = mxConnectionHandlerCreateMarker.apply(this, arguments);
  273. // Overrides to ignore hotspot only for target terminal
  274. marker.intersects = mxUtils.bind(this, function(state, evt)
  275. {
  276. if (this.isConnecting())
  277. {
  278. return true;
  279. }
  280. return mxCellMarker.prototype.intersects.apply(marker, arguments);
  281. });
  282. return marker;
  283. };
  284. // Makes the shadow brighter
  285. mxConstants.SHADOWCOLOR = '#d0d0d0';
  286. // Changes some default colors
  287. mxConstants.HANDLE_FILLCOLOR = '#99ccff';
  288. mxConstants.HANDLE_STROKECOLOR = '#0088cf';
  289. mxConstants.VERTEX_SELECTION_COLOR = '#00a8ff';
  290. mxConstants.OUTLINE_COLOR = '#00a8ff';
  291. mxConstants.OUTLINE_HANDLE_FILLCOLOR = '#99ccff';
  292. mxConstants.OUTLINE_HANDLE_STROKECOLOR = '#00a8ff';
  293. mxConstants.CONNECT_HANDLE_FILLCOLOR = '#cee7ff';
  294. mxConstants.EDGE_SELECTION_COLOR = '#00a8ff';
  295. mxConstants.DEFAULT_VALID_COLOR = '#00a8ff';
  296. mxConstants.LABEL_HANDLE_FILLCOLOR = '#cee7ff';
  297. mxConstants.GUIDE_COLOR = '#0088cf';
  298. // TODO: Add option for setting pageScale
  299. mxGraph.prototype.pageBreakColor = '#c0c0c0';
  300. mxGraph.prototype.pageScale = 1;
  301. // Increases default rubberband opacity (default is 20)
  302. mxRubberband.prototype.defaultOpacity = 30;
  303. // Changes border color of background page shape
  304. mxGraphView.prototype.createBackgroundPageShape = function(bounds)
  305. {
  306. return new mxRectangleShape(bounds, this.graph.background || 'white', '#cacaca');
  307. };
  308. // Fits the number of background pages to the graph
  309. mxGraphView.prototype.getBackgroundPageBounds = function()
  310. {
  311. var gb = this.getGraphBounds();
  312. // Computes unscaled, untranslated graph bounds
  313. var x = (gb.width > 0) ? gb.x / this.scale - this.translate.x : 0;
  314. var y = (gb.height > 0) ? gb.y / this.scale - this.translate.y : 0;
  315. var w = gb.width / this.scale;
  316. var h = gb.height / this.scale;
  317. var fmt = this.graph.pageFormat;
  318. var ps = this.graph.pageScale;
  319. var pw = fmt.width * ps;
  320. var ph = fmt.height * ps;
  321. var x0 = Math.floor(Math.min(0, x) / pw);
  322. var y0 = Math.floor(Math.min(0, y) / ph);
  323. var xe = Math.ceil(Math.max(1, x + w) / pw);
  324. var ye = Math.ceil(Math.max(1, y + h) / ph);
  325. var rows = xe - x0;
  326. var cols = ye - y0;
  327. var bounds = new mxRectangle(this.scale * (this.translate.x + x0 * pw), this.scale *
  328. (this.translate.y + y0 * ph), this.scale * rows * pw, this.scale * cols * ph);
  329. return bounds;
  330. };
  331. // Add panning for background page in VML
  332. var graphPanGraph = mxGraph.prototype.panGraph;
  333. mxGraph.prototype.panGraph = function(dx, dy)
  334. {
  335. graphPanGraph.apply(this, arguments);
  336. if ((this.dialect != mxConstants.DIALECT_SVG && this.view.backgroundPageShape != null) &&
  337. (!this.useScrollbarsForPanning || !mxUtils.hasScrollbars(this.container)))
  338. {
  339. this.view.backgroundPageShape.node.style.marginLeft = dx + 'px';
  340. this.view.backgroundPageShape.node.style.marginTop = dy + 'px';
  341. }
  342. };
  343. // Uses HTML for background pages (to support grid background image)
  344. mxGraphView.prototype.validateBackground = function()
  345. {
  346. var bg = this.graph.getBackgroundImage();
  347. if (bg != null)
  348. {
  349. if (this.backgroundImage == null || this.backgroundImage.image != bg.src)
  350. {
  351. if (this.backgroundImage != null)
  352. {
  353. this.backgroundImage.destroy();
  354. }
  355. var bounds = new mxRectangle(0, 0, 1, 1);
  356. this.backgroundImage = new mxImageShape(bounds, bg.src);
  357. this.backgroundImage.dialect = this.graph.dialect;
  358. this.backgroundImage.init(this.backgroundPane);
  359. this.backgroundImage.redraw();
  360. }
  361. this.redrawBackgroundImage(this.backgroundImage, bg);
  362. }
  363. else if (this.backgroundImage != null)
  364. {
  365. this.backgroundImage.destroy();
  366. this.backgroundImage = null;
  367. }
  368. if (this.graph.pageVisible)
  369. {
  370. var bounds = this.getBackgroundPageBounds();
  371. if (this.backgroundPageShape == null)
  372. {
  373. this.backgroundPageShape = this.createBackgroundPageShape(bounds);
  374. this.backgroundPageShape.scale = 1;
  375. this.backgroundPageShape.isShadow = true;
  376. this.backgroundPageShape.dialect = mxConstants.DIALECT_STRICTHTML;
  377. this.backgroundPageShape.init(this.graph.container);
  378. // Required for the browser to render the background page in correct order
  379. this.graph.container.firstChild.style.position = 'absolute';
  380. this.graph.container.insertBefore(this.backgroundPageShape.node, this.graph.container.firstChild);
  381. this.backgroundPageShape.redraw();
  382. this.backgroundPageShape.node.className = 'geBackgroundPage';
  383. // Adds listener for double click handling on background
  384. mxEvent.addListener(this.backgroundPageShape.node, 'dblclick',
  385. mxUtils.bind(this, function(evt)
  386. {
  387. this.graph.dblClick(evt);
  388. })
  389. );
  390. var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
  391. var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
  392. var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
  393. // Adds basic listeners for graph event dispatching outside of the
  394. // container and finishing the handling of a single gesture
  395. mxEvent.addListener(this.backgroundPageShape.node, md,
  396. mxUtils.bind(this, function(evt)
  397. {
  398. this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
  399. })
  400. );
  401. mxEvent.addListener(this.backgroundPageShape.node, mm,
  402. mxUtils.bind(this, function(evt)
  403. {
  404. // Hides the tooltip if mouse is outside container
  405. if (this.graph.tooltipHandler != null &&
  406. this.graph.tooltipHandler.isHideOnHover())
  407. {
  408. this.graph.tooltipHandler.hide();
  409. }
  410. if (this.graph.isMouseDown &&
  411. !mxEvent.isConsumed(evt))
  412. {
  413. this.graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
  414. new mxMouseEvent(evt));
  415. }
  416. })
  417. );
  418. mxEvent.addListener(this.backgroundPageShape.node, mu,
  419. mxUtils.bind(this, function(evt)
  420. {
  421. this.graph.fireMouseEvent(mxEvent.MOUSE_UP,
  422. new mxMouseEvent(evt));
  423. })
  424. );
  425. }
  426. else
  427. {
  428. this.backgroundPageShape.scale = 1;
  429. this.backgroundPageShape.bounds = bounds;
  430. this.backgroundPageShape.redraw();
  431. }
  432. }
  433. else if (this.backgroundPageShape != null)
  434. {
  435. this.backgroundPageShape.destroy();
  436. this.backgroundPageShape = null;
  437. }
  438. };
  439. // Draws page breaks only within the page
  440. mxGraph.prototype.updatePageBreaks = function(visible, width, height)
  441. {
  442. var scale = this.view.scale;
  443. var tr = this.view.translate;
  444. var fmt = this.pageFormat;
  445. var ps = scale * this.pageScale;
  446. var bounds2 = this.view.getBackgroundPageBounds();
  447. width = bounds2.width;
  448. height = bounds2.height;
  449. var bounds = new mxRectangle(scale * tr.x, scale * tr.y,
  450. fmt.width * ps, fmt.height * ps);
  451. // Does not show page breaks if the scale is too small
  452. visible = visible && Math.min(bounds.width, bounds.height) > this.minPageBreakDist;
  453. var horizontalCount = (visible) ? Math.ceil(width / bounds.width) - 1 : 0;
  454. var verticalCount = (visible) ? Math.ceil(height / bounds.height) - 1 : 0;
  455. var right = bounds2.x + width;
  456. var bottom = bounds2.y + height;
  457. if (this.horizontalPageBreaks == null && horizontalCount > 0)
  458. {
  459. this.horizontalPageBreaks = [];
  460. }
  461. if (this.horizontalPageBreaks != null)
  462. {
  463. for (var i = 0; i <= horizontalCount; i++)
  464. {
  465. var pts = [new mxPoint(bounds2.x + (i + 1) * bounds.width, bounds2.y),
  466. new mxPoint(bounds2.x + (i + 1) * bounds.width, bottom)];
  467. if (this.horizontalPageBreaks[i] != null)
  468. {
  469. this.horizontalPageBreaks[i].scale = 1;
  470. this.horizontalPageBreaks[i].points = pts;
  471. this.horizontalPageBreaks[i].redraw();
  472. }
  473. else
  474. {
  475. var pageBreak = new mxPolyline(pts, this.pageBreakColor, this.scale);
  476. pageBreak.dialect = this.dialect;
  477. pageBreak.isDashed = this.pageBreakDashed;
  478. pageBreak.addPipe = false;
  479. pageBreak.scale = scale;
  480. pageBreak.crisp = true;
  481. pageBreak.init(this.view.backgroundPane);
  482. pageBreak.redraw();
  483. this.horizontalPageBreaks[i] = pageBreak;
  484. }
  485. }
  486. for (var i = horizontalCount; i < this.horizontalPageBreaks.length; i++)
  487. {
  488. this.horizontalPageBreaks[i].destroy();
  489. }
  490. this.horizontalPageBreaks.splice(horizontalCount, this.horizontalPageBreaks.length - horizontalCount);
  491. }
  492. if (this.verticalPageBreaks == null && verticalCount > 0)
  493. {
  494. this.verticalPageBreaks = [];
  495. }
  496. if (this.verticalPageBreaks != null)
  497. {
  498. for (var i = 0; i <= verticalCount; i++)
  499. {
  500. var pts = [new mxPoint(bounds2.x, bounds2.y + (i + 1) * bounds.height),
  501. new mxPoint(right, bounds2.y + (i + 1) * bounds.height)];
  502. if (this.verticalPageBreaks[i] != null)
  503. {
  504. this.verticalPageBreaks[i].scale = 1; //scale;
  505. this.verticalPageBreaks[i].points = pts;
  506. this.verticalPageBreaks[i].redraw();
  507. }
  508. else
  509. {
  510. var pageBreak = new mxPolyline(pts, this.pageBreakColor, scale);
  511. pageBreak.dialect = this.dialect;
  512. pageBreak.isDashed = this.pageBreakDashed;
  513. pageBreak.addPipe = false;
  514. pageBreak.scale = scale;
  515. pageBreak.crisp = true;
  516. pageBreak.init(this.view.backgroundPane);
  517. pageBreak.redraw();
  518. this.verticalPageBreaks[i] = pageBreak;
  519. }
  520. }
  521. for (var i = verticalCount; i < this.verticalPageBreaks.length; i++)
  522. {
  523. this.verticalPageBreaks[i].destroy();
  524. }
  525. this.verticalPageBreaks.splice(verticalCount, this.verticalPageBreaks.length - verticalCount);
  526. }
  527. };
  528. // Enables snapping to off-grid terminals for edge waypoints
  529. mxEdgeHandler.prototype.snapToTerminals = true;
  530. // Enables guides
  531. mxGraphHandler.prototype.guidesEnabled = true;
  532. // Disables removing relative children from parents
  533. var mxGraphHandlerShouldRemoveCellsFromParent = mxGraphHandler.prototype.shouldRemoveCellsFromParent;
  534. mxGraphHandler.prototype.shouldRemoveCellsFromParent = function(parent, cells, evt)
  535. {
  536. for (var i = 0; i < cells.length; i++)
  537. {
  538. if (this.graph.getModel().isVertex(cells[i]))
  539. {
  540. var geo = this.graph.getCellGeometry(cells[i]);
  541. if (geo != null && geo.relative)
  542. {
  543. return false;
  544. }
  545. }
  546. }
  547. return mxGraphHandlerShouldRemoveCellsFromParent.apply(this, arguments);
  548. };
  549. // Alt-move disables guides
  550. mxGuide.prototype.isEnabledForEvent = function(evt)
  551. {
  552. return !mxEvent.isAltDown(evt);
  553. };
  554. // Consumes click events for disabled menu items
  555. mxPopupMenuAddItem = mxPopupMenu.prototype.addItem;
  556. mxPopupMenu.prototype.addItem = function(title, image, funct, parent, iconCls, enabled)
  557. {
  558. var result = mxPopupMenuAddItem.apply(this, arguments);
  559. if (enabled != null && !enabled)
  560. {
  561. mxEvent.addListener(result, 'mousedown', function(evt)
  562. {
  563. mxEvent.consume(evt);
  564. });
  565. }
  566. return result;
  567. };
  568. // Experimental add/remove waypoints
  569. mxEdgeHandler.prototype.addEnabled = true;
  570. mxEdgeHandler.prototype.removeEnabled = true;
  571. // Selects descendants before children selection mode
  572. var graphHandlerGetInitialCellForEvent = mxGraphHandler.prototype.getInitialCellForEvent;
  573. mxGraphHandler.prototype.getInitialCellForEvent = function(me)
  574. {
  575. var model = this.graph.getModel();
  576. var psel = model.getParent(this.graph.getSelectionCell());
  577. var cell = graphHandlerGetInitialCellForEvent.apply(this, arguments);
  578. var parent = model.getParent(cell);
  579. if (psel == null || (psel != cell && psel != parent))
  580. {
  581. while (!this.graph.isCellSelected(cell) && !this.graph.isCellSelected(parent) &&
  582. model.isVertex(parent) && !this.graph.isValidRoot(parent))
  583. {
  584. cell = parent;
  585. parent = this.graph.getModel().getParent(cell);
  586. }
  587. }
  588. return cell;
  589. };
  590. // Selection is delayed to mouseup if child selected
  591. var graphHandlerIsDelayedSelection = mxGraphHandler.prototype.isDelayedSelection;
  592. mxGraphHandler.prototype.isDelayedSelection = function(cell)
  593. {
  594. var result = graphHandlerIsDelayedSelection.apply(this, arguments);
  595. var model = this.graph.getModel();
  596. var psel = model.getParent(this.graph.getSelectionCell());
  597. var parent = model.getParent(cell);
  598. if (psel == null || (psel != cell && psel != parent))
  599. {
  600. if (!this.graph.isCellSelected(cell) && model.isVertex(parent) && !this.graph.isValidRoot(parent))
  601. {
  602. result = true;
  603. }
  604. }
  605. return result;
  606. };
  607. // Delayed selection of parent group
  608. mxGraphHandler.prototype.selectDelayed = function(me)
  609. {
  610. var cell = me.getCell();
  611. if (cell == null)
  612. {
  613. cell = this.cell;
  614. }
  615. var model = this.graph.getModel();
  616. var parent = model.getParent(cell);
  617. while (this.graph.isCellSelected(cell) && model.isVertex(parent) && !this.graph.isValidRoot(parent))
  618. {
  619. cell = parent;
  620. parent = model.getParent(cell);
  621. }
  622. this.graph.selectCellForEvent(cell, me.getEvent());
  623. };
  624. // Returns last selected ancestor
  625. mxPanningHandler.prototype.getCellForPopupEvent = function(me)
  626. {
  627. var cell = me.getCell();
  628. var model = this.graph.getModel();
  629. var parent = model.getParent(cell);
  630. while (model.isVertex(parent) && !this.graph.isValidRoot(parent))
  631. {
  632. if (this.graph.isCellSelected(parent))
  633. {
  634. cell = parent;
  635. }
  636. parent = model.getParent(parent);
  637. }
  638. return cell;
  639. };
  640. };
  641. /**
  642. * Creates and returns a new undo manager.
  643. */
  644. Editor.prototype.createUndoManager = function()
  645. {
  646. var graph = this.graph;
  647. var undoMgr = new mxUndoManager();
  648. // Installs the command history
  649. var listener = function(sender, evt)
  650. {
  651. undoMgr.undoableEditHappened(evt.getProperty('edit'));
  652. };
  653. graph.getModel().addListener(mxEvent.UNDO, listener);
  654. graph.getView().addListener(mxEvent.UNDO, listener);
  655. // Keeps the selection in sync with the history
  656. var undoHandler = function(sender, evt)
  657. {
  658. var cand = graph.getSelectionCellsForChanges(evt.getProperty('edit').changes);
  659. var cells = [];
  660. for (var i = 0; i < cand.length; i++)
  661. {
  662. if (graph.view.getState(cand[i]) != null)
  663. {
  664. cells.push(cand[i]);
  665. }
  666. }
  667. graph.setSelectionCells(cells);
  668. };
  669. undoMgr.addListener(mxEvent.UNDO, undoHandler);
  670. undoMgr.addListener(mxEvent.REDO, undoHandler);
  671. return undoMgr;
  672. };
  673. /**
  674. * Overrides stencil registry to add dynamic loading.
  675. */
  676. Editor.prototype.initStencilRegistry = function()
  677. {
  678. // Loads default stencils
  679. mxStencilRegistry.loadStencilSet(STENCIL_PATH + '/general.xml');
  680. };
  681. // Overrides stencil registry for dynamic loading
  682. (function()
  683. {
  684. mxStencilRegistry.packages = [];
  685. // Extends the default stencil registry to add dynamic loading
  686. mxStencilRegistry.getStencil = function(name)
  687. {
  688. var result = mxStencilRegistry.stencils[name];
  689. if (result == null)
  690. {
  691. var basename = mxStencilRegistry.getBasenameForStencil(name);
  692. // Loads stencil files and tries again
  693. if (basename != null)
  694. {
  695. mxStencilRegistry.loadStencilSet(STENCIL_PATH + '/' + basename + '.xml', null);
  696. result = mxStencilRegistry.stencils[name];
  697. }
  698. }
  699. return result;
  700. };
  701. // Returns the basename for the given stencil or null if no file must be
  702. // loaded to render the given stencil.
  703. mxStencilRegistry.getBasenameForStencil = function(name)
  704. {
  705. var parts = name.split('.');
  706. var tmp = null;
  707. if (parts.length > 0 && parts[0] == 'mxgraph')
  708. {
  709. tmp = parts[1];
  710. for (var i = 2; i < parts.length - 1; i++)
  711. {
  712. tmp += '/' + parts[i];
  713. }
  714. }
  715. return tmp;
  716. };
  717. // Loads the given stencil set
  718. mxStencilRegistry.loadStencilSet = function(stencilFile, postStencilLoad, force)
  719. {
  720. force = (force != null) ? force : false;
  721. // Uses additional cache for detecting previous load attempts
  722. var installed = mxStencilRegistry.packages[stencilFile] != null;
  723. if (force || !installed)
  724. {
  725. mxStencilRegistry.packages[stencilFile] = 1;
  726. var req = mxUtils.load(stencilFile);
  727. mxStencilRegistry.parseStencilSet(req.getXml(), postStencilLoad, !installed);
  728. }
  729. };
  730. // Parses the given stencil set
  731. mxStencilRegistry.parseStencilSet = function(xmlDocument, postStencilLoad, install)
  732. {
  733. install = (install != null) ? install : true;
  734. var root = xmlDocument.documentElement;
  735. var shape = root.firstChild;
  736. var packageName = '';
  737. var name = root.getAttribute('name');
  738. if (name != null)
  739. {
  740. packageName = name + '.';
  741. }
  742. while (shape != null)
  743. {
  744. if (shape.nodeType == mxConstants.NODETYPE_ELEMENT)
  745. {
  746. name = shape.getAttribute('name');
  747. if (name != null)
  748. {
  749. var w = shape.getAttribute('w');
  750. var h = shape.getAttribute('h');
  751. w = (w == null) ? 80 : parseInt(w, 10);
  752. h = (h == null) ? 80 : parseInt(h, 10);
  753. packageName = packageName.toLowerCase();
  754. var stencilName = name.replace(/ /g,"_");
  755. if (install)
  756. {
  757. mxStencilRegistry.addStencil(packageName + stencilName.toLowerCase(), new mxStencil(shape));
  758. }
  759. if (postStencilLoad != null)
  760. {
  761. postStencilLoad(packageName, stencilName, name, w, h);
  762. }
  763. }
  764. }
  765. shape = shape.nextSibling;
  766. }
  767. };
  768. })();
  769. /**
  770. * Class for asynchronously opening a new window and loading a file at the same
  771. * time. This acts as a bridge between the open dialog and the new editor.
  772. */
  773. OpenFile = function(done)
  774. {
  775. this.producer = null;
  776. this.consumer = null;
  777. this.done = done;
  778. };
  779. /**
  780. * Registers the editor from the new window.
  781. */
  782. OpenFile.prototype.setConsumer = function(value)
  783. {
  784. this.consumer = value;
  785. this.execute();
  786. };
  787. /**
  788. * Sets the data from the loaded file.
  789. */
  790. OpenFile.prototype.setData = function(value, filename)
  791. {
  792. this.data = value;
  793. this.filename = filename;
  794. this.execute();
  795. };
  796. /**
  797. * Displays an error message.
  798. */
  799. OpenFile.prototype.error = function(msg)
  800. {
  801. this.cancel();
  802. mxUtils.alert(msg);
  803. };
  804. /**
  805. * Consumes the data.
  806. */
  807. OpenFile.prototype.execute = function()
  808. {
  809. if (this.consumer != null && this.data != null)
  810. {
  811. this.consumer(this.data, this.filename);
  812. this.cancel();
  813. }
  814. };
  815. /**
  816. * Cancels the operation.
  817. */
  818. OpenFile.prototype.cancel = function()
  819. {
  820. if (this.done != null)
  821. {
  822. this.done();
  823. }
  824. };