EditorUi.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904
  1. /**
  2. *$id:Action 。JS,V 2017-12-19
  3. *$author shen.zhi
  4. */
  5. /**
  6. * 构建了一种新的图形编辑器
  7. * 编辑管理 实现方法管理
  8. */
  9. EditorUi = function(editor, container)
  10. {
  11. this.editor = editor || new Editor();
  12. this.container = container || document.body;
  13. var graph = editor.graph;
  14. //禁用滚动条
  15. this.container.style.overflow = 'hidden';
  16. var textEditing = mxUtils.bind(this, function(evt)
  17. {
  18. if (evt == null)
  19. {
  20. evt = window.event;
  21. }
  22. if (this.isSelectionAllowed(evt))
  23. {
  24. return true;
  25. }
  26. return graph.isEditing() || this.dialog != null;
  27. });
  28. //禁用文本选择而不是编辑没有对话框可见
  29. if (this.container == document.body)
  30. {
  31. document.onselectstart = textEditing;
  32. document.onmousedown = textEditing;
  33. }
  34. //使用内置的上下文菜单编辑时
  35. if (mxClient.IS_IE && document.documentMode != 9)
  36. {
  37. mxEvent.addListener(this.container, 'contextmenu', textEditing);
  38. }
  39. else
  40. {
  41. this.container.oncontextmenu = textEditing;
  42. }
  43. //图像预fetches子菜单
  44. new Image().src = mxPopupMenu.prototype.submenuImage;
  45. //预取连接图像
  46. if (mxConnectionHandler.prototype.connectImage != null)
  47. {
  48. new Image().src = mxConnectionHandler.prototype.connectImage.src;
  49. }
  50. //创建用户界面
  51. this.actions = new Actions(this);
  52. this.menus = new Menus(this);
  53. this.createDivs();
  54. this.refresh();
  55. this.createUi();
  56. //contains the given the inside main图审小组
  57. graph.init(this.diagramContainer);
  58. graph.refresh();
  59. //使容器的滚动条和设置光标样式
  60. graph.container.setAttribute('tabindex', '0');
  61. graph.container.style.overflow = (touchStyle) ? 'hidden' : 'auto';
  62. graph.container.style.cursor = 'default';
  63. graph.container.style.backgroundImage = 'url(' + IMAGE_PATH + '/grid.gif)';
  64. graph.container.focus();
  65. //保持图形容器集中在鼠标按下
  66. var graphFireMouseEvent = graph.fireMouseEvent;
  67. graph.fireMouseEvent = function(evtName, me, sender)
  68. {
  69. if (evtName == mxEvent.MOUSE_DOWN)
  70. {
  71. this.container.focus();
  72. }
  73. graphFireMouseEvent.apply(this, arguments);
  74. };
  75. //在鼠标经过时自动扩展配置
  76. graph.panningHandler.autoExpand = true;
  77. //对上下文菜单
  78. graph.panningHandler.factoryMethod = mxUtils.bind(this, function(menu, cell, evt)
  79. {
  80. this.menus.createPopupMenu(menu, cell, evt);
  81. });
  82. //初始化轮廓
  83. editor.outline.init(this.outlineContainer);
  84. //隐藏菜单
  85. var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
  86. mxEvent.addListener(document, md, mxUtils.bind(this, function(evt)
  87. {
  88. graph.panningHandler.hideMenu();
  89. }));
  90. //增加了手势操作(缩放)
  91. if (mxClient.IS_TOUCH)
  92. {
  93. mxEvent.addListener(graph.container, 'gesturechange',
  94. mxUtils.bind(this, function(evt)
  95. {
  96. graph.view.getDrawPane().setAttribute('transform', 'scale(' + evt.scale + ')');
  97. graph.view.getOverlayPane().style.visibility = 'hidden';
  98. })
  99. );
  100. mxEvent.addListener(graph.container, 'gestureend',
  101. mxUtils.bind(this, function(evt)
  102. {
  103. graph.view.getDrawPane().removeAttribute('transform');
  104. graph.zoomToCenter = true;
  105. graph.zoom(evt.scale);
  106. graph.view.getOverlayPane().style.visibility = 'visible';
  107. })
  108. );
  109. }
  110. // Create handler for key events
  111. var keyHandler = this.createKeyHandler(editor);
  112. // Getter for key handler
  113. this.getKeyHandler = function()
  114. {
  115. return keyHandler;
  116. };
  117. // Shows dialog if changes are lost
  118. window.onbeforeunload = function()
  119. {
  120. if (editor.modified)
  121. {
  122. //return mxResources.get('allChangesLost');
  123. }
  124. };
  125. // Updates the editor UI after the window has been resized
  126. mxEvent.addListener(window, 'resize', mxUtils.bind(this, function()
  127. {
  128. this.refresh();
  129. graph.sizeDidChange();
  130. this.editor.outline.update(false);
  131. this.editor.outline.outline.sizeDidChange();
  132. }));
  133. // Updates action and menu states
  134. this.init();
  135. this.open();
  136. };
  137. /**
  138. * Specifies the size of the split bar.
  139. */
  140. EditorUi.prototype.splitSize = (mxClient.IS_TOUCH) ? 16 : 8;
  141. /**
  142. * Specifies the height of the menubar. Default is 34.
  143. */
  144. EditorUi.prototype.menubarHeight = 34;
  145. /**
  146. * Specifies the height of the toolbar. Default is 46.
  147. */
  148. EditorUi.prototype.toolbarHeight = 46;
  149. /**
  150. * Specifies the height of the footer. Default is 28.
  151. */
  152. EditorUi.prototype.footerHeight = 28;
  153. /**
  154. * Specifies the position of the horizontal split bar. Default is 190.
  155. */
  156. EditorUi.prototype.hsplitPosition = 190;
  157. /**
  158. * Specifies the position of the vertical split bar. Default is 190.
  159. */
  160. EditorUi.prototype.vsplitPosition = 190;
  161. /**
  162. * Installs the listeners to update the action states.
  163. */
  164. EditorUi.prototype.init = function()
  165. {
  166. // Updates action states
  167. this.addUndoListener();
  168. this.addSelectionListener();
  169. // Overrides clipboard to update paste action state
  170. var paste = this.actions.get('paste');
  171. var updatePaste = function()
  172. {
  173. paste.setEnabled(!mxClipboard.isEmpty());
  174. };
  175. var mxClipboardCut = mxClipboard.cut;
  176. mxClipboard.cut = function()
  177. {
  178. mxClipboardCut.apply(this, arguments);
  179. updatePaste();
  180. };
  181. var mxClipboardCopy = mxClipboard.copy;
  182. mxClipboard.copy = function()
  183. {
  184. mxClipboardCopy.apply(this, arguments);
  185. updatePaste();
  186. };
  187. };
  188. /**
  189. * Hook for allowing selection and context menu for certain events.
  190. */
  191. EditorUi.prototype.isSelectionAllowed = function(evt)
  192. {
  193. return false;
  194. };
  195. /**
  196. * Opens the current diagram via the window.opener if one exists.
  197. */
  198. EditorUi.prototype.open = function()
  199. {
  200. // Cross-domain window access is not allowed in FF, so if we
  201. // were opened from another domain then this will fail.
  202. try
  203. {
  204. if (window.opener != null && window.opener.openFile != null)
  205. {
  206. window.opener.openFile.setConsumer(mxUtils.bind(this, function(xml, filename)
  207. {
  208. try
  209. {
  210. var doc = mxUtils.parseXml(xml);
  211. this.editor.setGraphXml(doc.documentElement);
  212. this.editor.modified = false;
  213. this.editor.undoManager.clear();
  214. if (filename != null)
  215. {
  216. this.editor.filename = filename;
  217. }
  218. }
  219. catch (e)
  220. {
  221. mxUtils.alert(mxResources.get('invalidOrMissingFile') + ': ' + e.message);
  222. }
  223. }));
  224. }
  225. }
  226. catch(e)
  227. {
  228. // ignore
  229. }
  230. };
  231. /**
  232. * 在给定的文件名保存当前图。
  233. */
  234. EditorUi.prototype.save = function()
  235. {
  236. var xml = mxUtils.getXml(this.editor.getGraphXml());
  237. //火狐浏览器
  238. //if (navigator.userAgent.indexOf('Firefox') >= 0){
  239. //}
  240. //xml="<mxGraphModel grid=\"0\" guides=\"1\" tooltips=\"1\" connect=\"1\" fold=\"1\" page=\"0\" pageScale=\"1\" pageWidth=\"826\" pageHeight=\"1169\">"+xml+"</mxGraphModel>"
  241. //将xml代码保存至服务器文件
  242. $.post($("#path").val()+"/SaveToXmlServlet",{"tp":$("#mapTp").val(),"xml":xml,"type":"set"},function(text){
  243. if(text=="0"){
  244. alert("保存失败!");
  245. }
  246. });
  247. };
  248. /**
  249. * 返回一个拷贝没有状态这个编辑器的URL。
  250. */
  251. EditorUi.prototype.getUrl = function(pathname)
  252. {
  253. var href = (pathname != null) ? pathname : window.location.pathname;
  254. var parms = (pathname.indexOf('?') > 0) ? 1 : 0;
  255. // Removes template URL parameter for new blank diagram
  256. for (var key in urlParams)
  257. {
  258. if (parms == 0)
  259. {
  260. href += '?';
  261. }
  262. else
  263. {
  264. href += '&';
  265. }
  266. href += key + '=' + urlParams[key];
  267. parms++;
  268. }
  269. return href;
  270. };
  271. /**
  272. * 更新的撤销/重做项的状态。
  273. */
  274. EditorUi.prototype.addUndoListener = function()
  275. {
  276. var undo = this.actions.get('undo');
  277. var redo = this.actions.get('redo');
  278. var undoMgr = this.editor.undoManager;
  279. var undoListener = function()
  280. {
  281. undo.setEnabled(undoMgr.canUndo());
  282. redo.setEnabled(undoMgr.canRedo());
  283. };
  284. undoMgr.addListener(mxEvent.ADD, undoListener);
  285. undoMgr.addListener(mxEvent.UNDO, undoListener);
  286. undoMgr.addListener(mxEvent.REDO, undoListener);
  287. undoMgr.addListener(mxEvent.CLEAR, undoListener);
  288. // Updates the button states once
  289. undoListener();
  290. };
  291. /**
  292. * Updates the states of the given toolbar items based on the selection.
  293. */
  294. EditorUi.prototype.addSelectionListener = function()
  295. {
  296. var selectionListener = mxUtils.bind(this, function()
  297. {
  298. var graph = this.editor.graph;
  299. var selected = !graph.isSelectionEmpty();
  300. var vertexSelected = false;
  301. var edgeSelected = false;
  302. var cells = graph.getSelectionCells();
  303. if (cells != null)
  304. {
  305. for (var i = 0; i < cells.length; i++)
  306. {
  307. var cell = cells[i];
  308. if (graph.getModel().isEdge(cell))
  309. {
  310. edgeSelected = true;
  311. }
  312. if (graph.getModel().isVertex(cell))
  313. {
  314. vertexSelected = true;
  315. }
  316. if (edgeSelected && vertexSelected)
  317. {
  318. break;
  319. }
  320. }
  321. }
  322. // 更新动作状态
  323. var actions = ['cut', 'copy', 'delete', 'duplicate', 'bold', 'italic', 'style', 'fillColor',
  324. 'gradientColor', 'underline', 'fontColor', 'strokeColor', 'backgroundColor',
  325. 'borderColor', 'toFront', 'toBack', 'dashed', 'rounded', 'shadow', 'rotate',
  326. 'autosize'];
  327. for (var i = 0; i < actions.length; i++)
  328. {
  329. this.actions.get(actions[i]).setEnabled(selected);
  330. }
  331. this.actions.get('rotation').setEnabled(vertexSelected);
  332. this.actions.get('group').setEnabled(graph.getSelectionCount() > 1);
  333. this.actions.get('ungroup').setEnabled(graph.getSelectionCount() == 1 &&
  334. graph.getModel().getChildCount(graph.getSelectionCell()) > 0);
  335. var oneVertexSelected = vertexSelected && graph.getSelectionCount() == 1;
  336. this.actions.get('removeFromGroup').setEnabled(oneVertexSelected &&
  337. graph.getModel().isVertex(graph.getModel().getParent(graph.getSelectionCell())));
  338. //更新菜单状态
  339. var menus = ['fontFamily', 'fontSize', 'alignment', 'position', 'text', 'format',
  340. 'arrange', 'linewidth', 'spacing', 'gradient'];
  341. for (var i = 0; i < menus.length; i++)
  342. {
  343. this.menus.get(menus[i]).setEnabled(selected);
  344. }
  345. menus = ['line', 'lineend', 'linestart'];
  346. for (var i = 0; i < menus.length; i++)
  347. {
  348. this.menus.get(menus[i]).setEnabled(edgeSelected);
  349. }
  350. this.actions.get('setAsDefaultEdge').setEnabled(edgeSelected);
  351. this.menus.get('align').setEnabled(graph.getSelectionCount() > 1);
  352. this.menus.get('direction').setEnabled(vertexSelected || (edgeSelected &&
  353. graph.isLoop(graph.view.getState(graph.getSelectionCell()))));
  354. this.menus.get('navigation').setEnabled(graph.foldingEnabled && ((graph.view.currentRoot != null) ||
  355. (graph.getSelectionCount() == 1 && graph.isValidRoot(graph.getSelectionCell()))));
  356. this.actions.get('home').setEnabled(graph.view.currentRoot != null);
  357. this.actions.get('exitGroup').setEnabled(graph.view.currentRoot != null);
  358. var groupEnabled = graph.getSelectionCount() == 1 && graph.isValidRoot(graph.getSelectionCell());
  359. this.actions.get('enterGroup').setEnabled(groupEnabled);
  360. this.actions.get('expand').setEnabled(groupEnabled);
  361. this.actions.get('collapse').setEnabled(groupEnabled);
  362. this.actions.get('editLink').setEnabled(graph.getSelectionCount() == 1);
  363. this.actions.get('openLink').setEnabled(graph.getSelectionCount() == 1 &&
  364. graph.getLinkForCell(graph.getSelectionCell()) != null);
  365. });
  366. this.editor.graph.getSelectionModel().addListener(mxEvent.CHANGE, selectionListener);
  367. selectionListener();
  368. };
  369. /**
  370. * Refreshes the viewport.
  371. */
  372. EditorUi.prototype.refresh = function()
  373. {
  374. var quirks = mxClient.IS_IE && (document.documentMode == null || document.documentMode == 5);
  375. var w = this.container.clientWidth;
  376. var h = this.container.clientHeight;
  377. if (this.container == document.body)
  378. {
  379. w = document.body.clientWidth || document.documentElement.clientWidth;
  380. h = (quirks) ? document.body.clientHeight || document.documentElement.clientHeight : document.documentElement.clientHeight;
  381. }
  382. var effHsplitPosition = Math.max(0, Math.min(this.hsplitPosition, w - this.splitSize - 20));
  383. var effVsplitPosition = Math.max(0, Math.min(this.vsplitPosition, h - this.menubarHeight - this.toolbarHeight - this.footerHeight - this.splitSize - 1));
  384. this.menubarContainer.style.height = this.menubarHeight + 'px';
  385. this.toolbarContainer.style.top = this.menubarHeight + 'px';
  386. this.toolbarContainer.style.height = this.toolbarHeight + 'px';
  387. this.sidebarContainer.style.top = (this.menubarHeight + this.toolbarHeight) + 'px';
  388. this.sidebarContainer.style.width = effHsplitPosition + 'px';
  389. this.outlineContainer.style.width = effHsplitPosition + 'px';
  390. this.outlineContainer.style.height = effVsplitPosition + 'px';
  391. this.outlineContainer.style.bottom = this.footerHeight + 'px';
  392. this.diagramContainer.style.left = (effHsplitPosition + this.splitSize) + 'px';
  393. this.diagramContainer.style.top = this.sidebarContainer.style.top;
  394. this.footerContainer.style.height = this.footerHeight + 'px';
  395. this.hsplit.style.top = this.sidebarContainer.style.top;
  396. this.hsplit.style.bottom = this.outlineContainer.style.bottom;
  397. this.hsplit.style.left = effHsplitPosition + 'px';
  398. this.vsplit.style.width = this.sidebarContainer.style.width;
  399. this.vsplit.style.bottom = (effVsplitPosition + this.footerHeight) + 'px';
  400. if (quirks)
  401. {
  402. this.menubarContainer.style.width = w + 'px';
  403. this.toolbarContainer.style.width = this.menubarContainer.style.width;
  404. var sidebarHeight = (h - effVsplitPosition - this.splitSize - this.footerHeight - this.menubarHeight - this.toolbarHeight);
  405. this.sidebarContainer.style.height = sidebarHeight + 'px';
  406. this.diagramContainer.style.width = (w - effHsplitPosition - this.splitSize) + 'px';
  407. var diagramHeight = (h - this.footerHeight - this.menubarHeight - this.toolbarHeight);
  408. this.diagramContainer.style.height = diagramHeight + 'px';
  409. this.footerContainer.style.width = this.menubarContainer.style.width;
  410. this.hsplit.style.height = diagramHeight + 'px';
  411. }
  412. else
  413. {
  414. this.sidebarContainer.style.bottom = (effVsplitPosition + this.splitSize + this.footerHeight) + 'px';
  415. this.diagramContainer.style.bottom = this.outlineContainer.style.bottom;
  416. }
  417. };
  418. /**
  419. * Creates the required containers.
  420. */
  421. EditorUi.prototype.createDivs = function()
  422. {
  423. this.menubarContainer = this.createDiv('geMenubarContainer');
  424. this.toolbarContainer = this.createDiv('geToolbarContainer');
  425. this.sidebarContainer = this.createDiv('geSidebarContainer');
  426. this.outlineContainer = this.createDiv('geOutlineContainer');
  427. this.diagramContainer = this.createDiv('geDiagramContainer');
  428. this.footerContainer = this.createDiv('geFooterContainer');
  429. this.hsplit = this.createDiv('geHsplit');
  430. this.vsplit = this.createDiv('geVsplit');
  431. // Sets static style for containers
  432. this.menubarContainer.style.top = '0px';
  433. this.menubarContainer.style.left = '0px';
  434. this.menubarContainer.style.right = '0px';
  435. this.toolbarContainer.style.left = '0px';
  436. this.toolbarContainer.style.right = '0px';
  437. this.sidebarContainer.style.left = '0px';
  438. this.outlineContainer.style.left = '0px';
  439. this.diagramContainer.style.right = '0px';
  440. this.footerContainer.style.left = '0px';
  441. this.footerContainer.style.right = '0px';
  442. this.footerContainer.style.bottom = '0px';
  443. this.vsplit.style.left = '0px';
  444. this.vsplit.style.height = this.splitSize + 'px';
  445. this.hsplit.style.width = this.splitSize + 'px';
  446. };
  447. /**
  448. * Creates the required containers.
  449. */
  450. EditorUi.prototype.createUi = function()
  451. {
  452. // Creates menubar
  453. this.menubar = this.menus.createMenubar(this.createDiv('geMenubar'));
  454. this.menubarContainer.appendChild(this.menubar.container);
  455. // Creates toolbar
  456. this.toolbar = this.createToolbar(this.createDiv('geToolbar'));
  457. this.toolbarContainer.appendChild(this.toolbar.container);
  458. // Creates the sidebar
  459. this.sidebar = this.createSidebar(this.sidebarContainer);
  460. // Creates the footer
  461. this.footerContainer.appendChild(this.createFooter());
  462. // Adds status bar in menubar
  463. this.statusContainer = this.createStatusContainer();
  464. // Connects the status bar to the editor status
  465. this.editor.addListener('statusChanged', mxUtils.bind(this, function()
  466. {
  467. this.setStatusText(this.editor.getStatus());
  468. }));
  469. this.setStatusText(this.editor.getStatus());
  470. this.menubar.container.appendChild(this.statusContainer);
  471. // Inserts into DOM
  472. this.container.appendChild(this.menubarContainer);
  473. this.container.appendChild(this.toolbarContainer);
  474. this.container.appendChild(this.sidebarContainer);
  475. this.container.appendChild(this.outlineContainer);
  476. this.container.appendChild(this.diagramContainer);
  477. this.container.appendChild(this.footerContainer);
  478. this.container.appendChild(this.hsplit);
  479. this.container.appendChild(this.vsplit);
  480. // HSplit
  481. this.addSplitHandler(this.hsplit, true, 0, mxUtils.bind(this, function(value)
  482. {
  483. this.hsplitPosition = value;
  484. this.refresh();
  485. this.editor.graph.sizeDidChange();
  486. this.editor.outline.update(false);
  487. this.editor.outline.outline.sizeDidChange();
  488. }));
  489. // VSplit
  490. this.addSplitHandler(this.vsplit, false, this.footerHeight, mxUtils.bind(this, function(value)
  491. {
  492. this.vsplitPosition = value;
  493. this.refresh();
  494. this.editor.outline.update(false);
  495. this.editor.outline.outline.sizeDidChange();
  496. }));
  497. };
  498. /**
  499. * Creates a new toolbar for the given container.
  500. */
  501. EditorUi.prototype.createStatusContainer = function()
  502. {
  503. var container = document.createElement('a');
  504. container.className = 'geItem geStatus';
  505. return container;
  506. };
  507. /**
  508. * Creates a new toolbar for the given container.
  509. */
  510. EditorUi.prototype.setStatusText = function(value)
  511. {
  512. this.statusContainer.innerHTML = value;
  513. };
  514. /**
  515. * Creates a new toolbar for the given container.
  516. */
  517. EditorUi.prototype.createToolbar = function(container)
  518. {
  519. return new Toolbar(this, container);
  520. };
  521. /**
  522. * Creates a new sidebar for the given container.
  523. */
  524. EditorUi.prototype.createSidebar = function(container)
  525. {
  526. return new Sidebar(this, container);
  527. };
  528. /**
  529. * Creates and returns a new footer.
  530. */
  531. EditorUi.prototype.createFooter = function()
  532. {
  533. return this.createDiv('geFooter');
  534. };
  535. /**
  536. * Creates the actual toolbar for the toolbar container.
  537. */
  538. EditorUi.prototype.createDiv = function(classname)
  539. {
  540. var elt = document.createElement('div');
  541. elt.className = classname;
  542. return elt;
  543. };
  544. /**
  545. * Updates the states of the given undo/redo items.
  546. */
  547. EditorUi.prototype.addSplitHandler = function(elt, horizontal, dx, onChange)
  548. {
  549. var start = null;
  550. var initial = null;
  551. function getValue()
  552. {
  553. return parseInt(((horizontal) ? elt.style.left : elt.style.bottom));
  554. }
  555. var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
  556. var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
  557. var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
  558. mxEvent.addListener(elt, md, function(evt)
  559. {
  560. start = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
  561. initial = getValue();
  562. mxEvent.consume(evt);
  563. });
  564. function moveHandler(evt)
  565. {
  566. if (start != null)
  567. {
  568. var pt = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
  569. onChange(Math.max(0, initial + ((horizontal) ? (pt.x - start.x) : (start.y - pt.y)) - dx));
  570. mxEvent.consume(evt);
  571. }
  572. }
  573. mxEvent.addListener(document, mm, moveHandler);
  574. mxEvent.addListener(document, mu, function(evt)
  575. {
  576. moveHandler(evt);
  577. start = null;
  578. initial = null;
  579. });
  580. };
  581. /**
  582. * Displays a print dialog.
  583. */
  584. EditorUi.prototype.showDialog = function(elt, w, h, modal, closable, onClose)
  585. {
  586. this.hideDialog();
  587. this.dialog = new Dialog(this, elt, w, (mxClient.IS_VML) ? h - 12 : h, modal, closable, onClose);
  588. };
  589. /**
  590. * Displays a print dialog.
  591. */
  592. EditorUi.prototype.hideDialog = function()
  593. {
  594. if (this.dialog != null)
  595. {
  596. this.dialog.close();
  597. this.dialog = null;
  598. this.editor.graph.container.focus();
  599. }
  600. };
  601. /**
  602. * Adds the label menu items to the given menu and parent.
  603. */
  604. EditorUi.prototype.openFile = function()
  605. {
  606. // Closes dialog after open
  607. window.openFile = new OpenFile(mxUtils.bind(this, function()
  608. {
  609. this.hideDialog();
  610. }));
  611. // Removes openFile if dialog is closed
  612. this.showDialog(new OpenDialog(this).container, 300, 180, true, true, function()
  613. {
  614. window.openFile = null;
  615. });
  616. };
  617. /**
  618. * Adds the label menu items to the given menu and parent.
  619. */
  620. EditorUi.prototype.saveFile = function(forceDialog)
  621. {
  622. if (!forceDialog && this.editor.filename != null)
  623. {
  624. this.save(this.editor.getOrCreateFilename());
  625. }
  626. else
  627. {
  628. this.showDialog(new SaveDialog(this).container, 300, 100, true, true);
  629. }
  630. };
  631. /**
  632. * Executes the given layout.
  633. */
  634. EditorUi.prototype.executeLayout = function(layout, animate, ignoreChildCount)
  635. {
  636. var graph = this.editor.graph;
  637. var cell = graph.getSelectionCell();
  638. graph.getModel().beginUpdate();
  639. try
  640. {
  641. layout.execute(graph.getDefaultParent(), cell);
  642. }
  643. catch (e)
  644. {
  645. throw e;
  646. }
  647. finally
  648. {
  649. // Animates the changes in the graph model except
  650. // for Camino, where animation is too slow
  651. if (animate && navigator.userAgent.indexOf('Camino') < 0)
  652. {
  653. // New API for animating graph layout results asynchronously
  654. var morph = new mxMorphing(graph);
  655. morph.addListener(mxEvent.DONE, mxUtils.bind(this, function()
  656. {
  657. graph.getModel().endUpdate();
  658. }));
  659. morph.startAnimation();
  660. }
  661. else
  662. {
  663. graph.getModel().endUpdate();
  664. }
  665. }
  666. };
  667. /**
  668. * Creates the keyboard event handler for the current graph and history.
  669. */
  670. EditorUi.prototype.createKeyHandler = function(editor)
  671. {
  672. var graph = this.editor.graph;
  673. var keyHandler = new mxKeyHandler(graph);
  674. // Routes command-key to control-key on Mac
  675. keyHandler.isControlDown = function(evt)
  676. {
  677. return mxEvent.isControlDown(evt) || (mxClient.IS_MAC && evt.metaKey);
  678. };
  679. // Helper function to move cells with the cursor keys
  680. function nudge(keyCode)
  681. {
  682. if (!graph.isSelectionEmpty())
  683. {
  684. var dx = 0;
  685. var dy = 0;
  686. if (keyCode == 37)
  687. {
  688. dx = -1;
  689. }
  690. else if (keyCode == 38)
  691. {
  692. dy = -1;
  693. }
  694. else if (keyCode == 39)
  695. {
  696. dx = 1;
  697. }
  698. else if (keyCode == 40)
  699. {
  700. dy = 1;
  701. }
  702. graph.moveCells(graph.getSelectionCells(), dx, dy);
  703. graph.scrollCellVisible(graph.getSelectionCell());
  704. }
  705. };
  706. // Binds keystrokes to actions
  707. var bindAction = mxUtils.bind(this, function(code, control, key, shift)
  708. {
  709. var action = this.actions.get(key);
  710. if (action != null)
  711. {
  712. var f = function()
  713. {
  714. if (action.enabled)
  715. {
  716. action.funct();
  717. }
  718. };
  719. if (control)
  720. {
  721. if (shift)
  722. {
  723. keyHandler.bindControlShiftKey(code, f);
  724. }
  725. else
  726. {
  727. keyHandler.bindControlKey(code, f);
  728. }
  729. }
  730. else
  731. {
  732. if (shift)
  733. {
  734. keyHandler.bindShiftKey(code, f);
  735. }
  736. else
  737. {
  738. keyHandler.bindKey(code, f);
  739. }
  740. }
  741. }
  742. });
  743. var ui = this;
  744. var keyHandleEscape = keyHandler.escape;
  745. keyHandler.escape = function(evt)
  746. {
  747. ui.hideDialog();
  748. keyHandleEscape.apply(this, arguments);
  749. };
  750. // Ignores enter keystroke. Remove this line if you want the
  751. // enter keystroke to stop editing.
  752. keyHandler.enter = function() {};
  753. keyHandler.bindKey(8, function() { graph.foldCells(true); }); // Backspace
  754. keyHandler.bindKey(13, function() { graph.foldCells(false); }); // Enter
  755. keyHandler.bindKey(33, function() { graph.exitGroup(); }); // Page Up
  756. keyHandler.bindKey(34, function() { graph.enterGroup(); }); // Page Down
  757. keyHandler.bindKey(36, function() { graph.home(); }); // Home
  758. keyHandler.bindKey(35, function() { graph.refresh(); }); // End
  759. keyHandler.bindKey(37, function() { nudge(37); }); // Left arrow
  760. keyHandler.bindKey(38, function() { nudge(38); }); // Up arrow
  761. keyHandler.bindKey(39, function() { nudge(39); }); // Right arrow
  762. keyHandler.bindKey(40, function() { nudge(40); }); // Down arrow
  763. keyHandler.bindKey(113, function() { graph.startEditingAtCell(); });
  764. bindAction(46, false, 'delete'); // Delete
  765. bindAction(82, true, 'rotate'); // Ctrl+R
  766. bindAction(83, true, 'save'); // Ctrl+S
  767. bindAction(83, true, 'saveAs', true); // Ctrl+Shift+S
  768. bindAction(107, false, 'zoomIn'); // Add
  769. bindAction(109, false, 'zoomOut'); // Subtract
  770. bindAction(65, true, 'selectAll'); // Ctrl+A
  771. bindAction(86, true, 'selectVertices', true); // Ctrl+Shift+V
  772. bindAction(69, true, 'selectEdges', true); // Ctrl+Shift+E
  773. bindAction(69, true, 'export'); // Ctrl+Shift+E
  774. bindAction(66, true, 'toBack'); // Ctrl+B
  775. bindAction(70, true, 'toFront'); // Ctrl+F
  776. bindAction(68, true, 'duplicate'); // Ctrl+D
  777. bindAction(90, true, 'undo'); // Ctrl+Z
  778. bindAction(89, true, 'redo'); // Ctrl+Y
  779. bindAction(88, true, 'cut'); // Ctrl+X
  780. bindAction(67, true, 'copy'); // Ctrl+C
  781. bindAction(81, true, 'connect'); // Ctrl+Q
  782. bindAction(86, true, 'paste'); // Ctrl+V
  783. bindAction(71, true, 'group'); // Ctrl+G
  784. bindAction(71, true, 'grid', true); // Ctrl+Shift+G
  785. bindAction(85, true, 'ungroup'); // Ctrl+U
  786. bindAction(112, false, 'about'); // F1
  787. return keyHandler;
  788. };