Menus.js 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283
  1. /**
  2. * Copyright (c) 2006-2012, JGraph Ltd
  3. */
  4. /**
  5. * Constructs a new graph editor
  6. */
  7. Menus = function(editorUi)
  8. {
  9. this.editorUi = editorUi;
  10. this.menus = new Object();
  11. this.init();
  12. // Pre-fetches checkmark image
  13. if (!mxClient.IS_SVG)
  14. {
  15. new Image().src = this.checkmarkImage;
  16. }
  17. };
  18. /**
  19. * Sets the default font family.
  20. */
  21. Menus.prototype.defaultFont = 'Helvetica';
  22. /**
  23. * Sets the default font size.
  24. */
  25. Menus.prototype.defaultFontSize = '12';
  26. /**
  27. * Sets the default font size.
  28. */
  29. Menus.prototype.defaultMenuItems = ['file', 'edit', 'view', 'arrange', 'extras', 'help'];
  30. /**
  31. * Adds the label menu items to the given menu and parent.
  32. */
  33. Menus.prototype.defaultFonts = ['Helvetica', 'Verdana', 'Times New Roman', 'Garamond', 'Comic Sans MS',
  34. 'Courier New', 'Georgia', 'Lucida Console', 'Tahoma'];
  35. /**
  36. * Adds the label menu items to the given menu and parent.
  37. */
  38. Menus.prototype.init = function()
  39. {
  40. var graph = this.editorUi.editor.graph;
  41. var isGraphEnabled = mxUtils.bind(graph, graph.isEnabled);
  42. this.customFonts = [];
  43. this.customFontSizes = [];
  44. this.put('fontFamily', new Menu(mxUtils.bind(this, function(menu, parent)
  45. {
  46. var addItem = mxUtils.bind(this, function(fontname)
  47. {
  48. var tr = this.styleChange(menu, fontname, [mxConstants.STYLE_FONTFAMILY], [fontname], null, parent, function()
  49. {
  50. document.execCommand('fontname', false, fontname);
  51. });
  52. tr.firstChild.nextSibling.style.fontFamily = fontname;
  53. });
  54. for (var i = 0; i < this.defaultFonts.length; i++)
  55. {
  56. addItem(this.defaultFonts[i]);
  57. }
  58. menu.addSeparator(parent);
  59. if (this.customFonts.length > 0)
  60. {
  61. for (var i = 0; i < this.customFonts.length; i++)
  62. {
  63. addItem(this.customFonts[i]);
  64. }
  65. menu.addSeparator(parent);
  66. menu.addItem(mxResources.get('reset'), null, mxUtils.bind(this, function()
  67. {
  68. this.customFonts = [];
  69. }), parent);
  70. menu.addSeparator(parent);
  71. }
  72. this.promptChange(menu, mxResources.get('custom') + '...', '', mxConstants.DEFAULT_FONTFAMILY, mxConstants.STYLE_FONTFAMILY, parent, true, mxUtils.bind(this, function(newValue)
  73. {
  74. this.customFonts.push(newValue);
  75. }));
  76. })));
  77. this.put('formatBlock', new Menu(mxUtils.bind(this, function(menu, parent)
  78. {
  79. function addItem(label, tag)
  80. {
  81. return menu.addItem(label, null, mxUtils.bind(this, function()
  82. {
  83. // TODO: Check if visible
  84. graph.cellEditor.textarea.focus();
  85. document.execCommand('formatBlock', false, '<' + tag + '>');
  86. }), parent);
  87. };
  88. addItem(mxResources.get('normal'), 'p');
  89. addItem('', 'h1').firstChild.nextSibling.innerHTML = '<h1 style="margin:0px;">' + mxResources.get('heading') + ' 1</h1>';
  90. addItem('', 'h2').firstChild.nextSibling.innerHTML = '<h2 style="margin:0px;">' + mxResources.get('heading') + ' 2</h2>';
  91. addItem('', 'h3').firstChild.nextSibling.innerHTML = '<h3 style="margin:0px;">' + mxResources.get('heading') + ' 3</h3>';
  92. addItem('', 'h4').firstChild.nextSibling.innerHTML = '<h4 style="margin:0px;">' + mxResources.get('heading') + ' 4</h4>';
  93. addItem('', 'h5').firstChild.nextSibling.innerHTML = '<h5 style="margin:0px;">' + mxResources.get('heading') + ' 5</h5>';
  94. addItem('', 'h6').firstChild.nextSibling.innerHTML = '<h6 style="margin:0px;">' + mxResources.get('heading') + ' 6</h6>';
  95. addItem('', 'pre').firstChild.nextSibling.innerHTML = '<pre style="margin:0px;">' + mxResources.get('formatted') + '</pre>';
  96. addItem('', 'blockquote').firstChild.nextSibling.innerHTML = '<blockquote style="margin-top:0px;margin-bottom:0px;">' + mxResources.get('blockquote') + '</blockquote>';
  97. })));
  98. this.put('fontSize', new Menu(mxUtils.bind(this, function(menu, parent)
  99. {
  100. var sizes = [6, 8, 9, 10, 11, 12, 14, 18, 24, 36, 48, 72];
  101. var addItem = mxUtils.bind(this, function(fontsize)
  102. {
  103. this.styleChange(menu, fontsize, [mxConstants.STYLE_FONTSIZE], [fontsize], null, parent, function()
  104. {
  105. // Creates an element with arbitrary size 3
  106. document.execCommand('fontSize', false, '3');
  107. // Changes the css font size of the first font element inside the in-place editor with size 3
  108. // hopefully the above element that we've just created. LATER: Check for new element using
  109. // previous result of getElementsByTagName (see other actions)
  110. var elts = graph.cellEditor.textarea.getElementsByTagName('font');
  111. for (var i = 0; i < elts.length; i++)
  112. {
  113. if (elts[i].getAttribute('size') == '3')
  114. {
  115. elts[i].removeAttribute('size');
  116. elts[i].style.fontSize = fontsize + 'px';
  117. break;
  118. }
  119. }
  120. });
  121. });
  122. for (var i = 0; i < sizes.length; i++)
  123. {
  124. addItem(sizes[i]);
  125. }
  126. menu.addSeparator(parent);
  127. if (this.customFontSizes.length > 0)
  128. {
  129. for (var i = 0; i < this.customFontSizes.length; i++)
  130. {
  131. addItem(this.customFontSizes[i]);
  132. }
  133. menu.addSeparator(parent);
  134. menu.addItem(mxResources.get('reset'), null, mxUtils.bind(this, function()
  135. {
  136. this.customFontSizes = [];
  137. }), parent);
  138. menu.addSeparator(parent);
  139. }
  140. this.promptChange(menu, mxResources.get('custom') + '...', '(pt)', '12', mxConstants.STYLE_FONTSIZE, parent, true, mxUtils.bind(this, function(newValue)
  141. {
  142. this.customFontSizes.push(newValue);
  143. }));
  144. })));
  145. this.put('direction', new Menu(mxUtils.bind(this, function(menu, parent)
  146. {
  147. menu.addItem(mxResources.get('flipH'), null, function() { graph.toggleCellStyles(mxConstants.STYLE_FLIPH, false); }, parent);
  148. menu.addItem(mxResources.get('flipV'), null, function() { graph.toggleCellStyles(mxConstants.STYLE_FLIPV, false); }, parent);
  149. this.addMenuItems(menu, ['-', 'rotation'], parent);
  150. })));
  151. this.put('align', new Menu(mxUtils.bind(this, function(menu, parent)
  152. {
  153. menu.addItem(mxResources.get('leftAlign'), null, function() { graph.alignCells(mxConstants.ALIGN_LEFT); }, parent);
  154. menu.addItem(mxResources.get('center'), null, function() { graph.alignCells(mxConstants.ALIGN_CENTER); }, parent);
  155. menu.addItem(mxResources.get('rightAlign'), null, function() { graph.alignCells(mxConstants.ALIGN_RIGHT); }, parent);
  156. menu.addSeparator(parent);
  157. menu.addItem(mxResources.get('topAlign'), null, function() { graph.alignCells(mxConstants.ALIGN_TOP); }, parent);
  158. menu.addItem(mxResources.get('middle'), null, function() { graph.alignCells(mxConstants.ALIGN_MIDDLE); }, parent);
  159. menu.addItem(mxResources.get('bottomAlign'), null, function() { graph.alignCells(mxConstants.ALIGN_BOTTOM); }, parent);
  160. })));
  161. this.put('distribute', new Menu(mxUtils.bind(this, function(menu, parent)
  162. {
  163. menu.addItem(mxResources.get('horizontal'), null, function() { graph.distributeCells(true); }, parent);
  164. menu.addItem(mxResources.get('vertical'), null, function() { graph.distributeCells(false); }, parent);
  165. })));
  166. this.put('layout', new Menu(mxUtils.bind(this, function(menu, parent)
  167. {
  168. var promptSpacing = mxUtils.bind(this, function(defaultValue, fn)
  169. {
  170. var dlg = new FilenameDialog(this.editorUi, defaultValue, mxResources.get('apply'), function(newValue)
  171. {
  172. fn(parseFloat(newValue));
  173. }, mxResources.get('spacing'));
  174. this.editorUi.showDialog(dlg.container, 300, 80, true, true);
  175. dlg.init();
  176. });
  177. menu.addItem(mxResources.get('horizontalFlow'), null, mxUtils.bind(this, function()
  178. {
  179. var layout = new mxHierarchicalLayout(graph, mxConstants.DIRECTION_WEST);
  180. this.editorUi.executeLayout(function()
  181. {
  182. var selectionCells = graph.getSelectionCells();
  183. layout.execute(graph.getDefaultParent(), selectionCells.length == 0 ? null : selectionCells);
  184. }, true);
  185. }), parent);
  186. menu.addItem(mxResources.get('verticalFlow'), null, mxUtils.bind(this, function()
  187. {
  188. var layout = new mxHierarchicalLayout(graph, mxConstants.DIRECTION_NORTH);
  189. this.editorUi.executeLayout(function()
  190. {
  191. var selectionCells = graph.getSelectionCells();
  192. layout.execute(graph.getDefaultParent(), selectionCells.length == 0 ? null : selectionCells);
  193. }, true);
  194. }), parent);
  195. menu.addSeparator(parent);
  196. menu.addItem(mxResources.get('horizontalTree'), null, mxUtils.bind(this, function()
  197. {
  198. var tmp = graph.getSelectionCell();
  199. var roots = null;
  200. if (tmp == null || graph.getModel().getChildCount(tmp) == 0)
  201. {
  202. if (graph.getModel().getEdgeCount(tmp) == 0)
  203. {
  204. roots = graph.findTreeRoots(graph.getDefaultParent());
  205. }
  206. }
  207. else
  208. {
  209. roots = graph.findTreeRoots(tmp);
  210. }
  211. if (roots != null && roots.length > 0)
  212. {
  213. tmp = roots[0];
  214. }
  215. if (tmp != null)
  216. {
  217. var layout = new mxCompactTreeLayout(graph, true);
  218. layout.edgeRouting = false;
  219. layout.levelDistance = 30;
  220. promptSpacing(layout.levelDistance, mxUtils.bind(this, function(newValue)
  221. {
  222. layout.levelDistance = newValue;
  223. this.editorUi.executeLayout(function()
  224. {
  225. layout.execute(graph.getDefaultParent(), tmp);
  226. }, true);
  227. }));
  228. }
  229. }), parent);
  230. menu.addItem(mxResources.get('verticalTree'), null, mxUtils.bind(this, function()
  231. {
  232. var tmp = graph.getSelectionCell();
  233. var roots = null;
  234. if (tmp == null || graph.getModel().getChildCount(tmp) == 0)
  235. {
  236. if (graph.getModel().getEdgeCount(tmp) == 0)
  237. {
  238. roots = graph.findTreeRoots(graph.getDefaultParent());
  239. }
  240. }
  241. else
  242. {
  243. roots = graph.findTreeRoots(tmp);
  244. }
  245. if (roots != null && roots.length > 0)
  246. {
  247. tmp = roots[0];
  248. }
  249. if (tmp != null)
  250. {
  251. var layout = new mxCompactTreeLayout(graph, false);
  252. layout.edgeRouting = false;
  253. layout.levelDistance = 30;
  254. promptSpacing(layout.levelDistance, mxUtils.bind(this, function(newValue)
  255. {
  256. layout.levelDistance = newValue;
  257. this.editorUi.executeLayout(function()
  258. {
  259. layout.execute(graph.getDefaultParent(), tmp);
  260. }, true);
  261. }));
  262. }
  263. }), parent);
  264. menu.addItem(mxResources.get('radialTree'), null, mxUtils.bind(this, function()
  265. {
  266. var tmp = graph.getSelectionCell();
  267. var roots = null;
  268. if (tmp == null || graph.getModel().getChildCount(tmp) == 0)
  269. {
  270. if (graph.getModel().getEdgeCount(tmp) == 0)
  271. {
  272. roots = graph.findTreeRoots(graph.getDefaultParent());
  273. }
  274. }
  275. else
  276. {
  277. roots = graph.findTreeRoots(tmp);
  278. }
  279. if (roots != null && roots.length > 0)
  280. {
  281. tmp = roots[0];
  282. }
  283. if (tmp != null)
  284. {
  285. var layout = new mxRadialTreeLayout(graph, false);
  286. layout.levelDistance = 80;
  287. layout.autoRadius = true;
  288. promptSpacing(layout.levelDistance, mxUtils.bind(this, function(newValue)
  289. {
  290. layout.levelDistance = newValue;
  291. this.editorUi.executeLayout(function()
  292. {
  293. layout.execute(graph.getDefaultParent(), tmp);
  294. if (!graph.isSelectionEmpty())
  295. {
  296. tmp = graph.getModel().getParent(tmp);
  297. if (graph.getModel().isVertex(tmp))
  298. {
  299. graph.updateGroupBounds([tmp], graph.gridSize * 2, true);
  300. }
  301. }
  302. }, true);
  303. }));
  304. }
  305. }), parent);
  306. menu.addSeparator(parent);
  307. menu.addItem(mxResources.get('organic'), null, mxUtils.bind(this, function()
  308. {
  309. var layout = new mxFastOrganicLayout(graph);
  310. promptSpacing(layout.forceConstant, mxUtils.bind(this, function(newValue)
  311. {
  312. layout.forceConstant = newValue;
  313. this.editorUi.executeLayout(function()
  314. {
  315. var tmp = graph.getSelectionCell();
  316. if (tmp == null || graph.getModel().getChildCount(tmp) == 0)
  317. {
  318. tmp = graph.getDefaultParent();
  319. }
  320. layout.execute(tmp);
  321. if (graph.getModel().isVertex(tmp))
  322. {
  323. graph.updateGroupBounds([tmp], graph.gridSize * 2, true);
  324. }
  325. }, true);
  326. }));
  327. }), parent);
  328. menu.addItem(mxResources.get('circle'), null, mxUtils.bind(this, function()
  329. {
  330. var layout = new mxCircleLayout(graph);
  331. this.editorUi.executeLayout(function()
  332. {
  333. var tmp = graph.getSelectionCell();
  334. if (tmp == null || graph.getModel().getChildCount(tmp) == 0)
  335. {
  336. tmp = graph.getDefaultParent();
  337. }
  338. layout.execute(tmp);
  339. if (graph.getModel().isVertex(tmp))
  340. {
  341. graph.updateGroupBounds([tmp], graph.gridSize * 2, true);
  342. }
  343. }, true);
  344. }), parent);
  345. })));
  346. this.put('navigation', new Menu(mxUtils.bind(this, function(menu, parent)
  347. {
  348. this.addMenuItems(menu, ['home', '-', 'exitGroup', 'enterGroup', '-', 'expand', 'collapse', '-', 'collapsible'], parent);
  349. })));
  350. this.put('arrange', new Menu(mxUtils.bind(this, function(menu, parent)
  351. {
  352. this.addMenuItems(menu, ['toFront', 'toBack', '-'], parent);
  353. this.addSubmenu('direction', menu, parent);
  354. this.addMenuItems(menu, ['turn', '-'], parent);
  355. this.addSubmenu('align', menu, parent);
  356. this.addSubmenu('distribute', menu, parent);
  357. menu.addSeparator(parent);
  358. this.addSubmenu('navigation', menu, parent);
  359. this.addSubmenu('insert', menu, parent);
  360. this.addSubmenu('layout', menu, parent);
  361. this.addMenuItems(menu, ['-', 'group', 'ungroup', 'removeFromGroup', '-', 'clearWaypoints', 'autosize'], parent);
  362. }))).isEnabled = isGraphEnabled;
  363. this.put('insert', new Menu(mxUtils.bind(this, function(menu, parent)
  364. {
  365. this.addMenuItems(menu, ['insertLink', 'insertImage'], parent);
  366. })));
  367. this.put('view', new Menu(mxUtils.bind(this, function(menu, parent)
  368. {
  369. this.addMenuItems(menu, ((this.editorUi.format != null) ? ['formatPanel'] : []).
  370. concat(['outline', 'layers', '-', 'pageView', 'pageScale', '-', 'scrollbars', 'tooltips', '-',
  371. 'grid', 'guides', '-', 'connectionArrows', 'connectionPoints', '-',
  372. 'resetView', 'zoomIn', 'zoomOut'], parent));
  373. })));
  374. // Two special dropdowns that are only used in the toolbar
  375. this.put('viewPanels', new Menu(mxUtils.bind(this, function(menu, parent)
  376. {
  377. if (this.editorUi.format != null)
  378. {
  379. this.addMenuItems(menu, ['formatPanel'], parent);
  380. }
  381. this.addMenuItems(menu, ['outline', 'layers'], parent);
  382. })));
  383. this.put('viewZoom', new Menu(mxUtils.bind(this, function(menu, parent)
  384. {
  385. this.addMenuItems(menu, ['resetView', '-'], parent);
  386. var scales = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 2, 3, 4];
  387. for (var i = 0; i < scales.length; i++)
  388. {
  389. (function(scale)
  390. {
  391. menu.addItem((scale * 100) + '%', null, function()
  392. {
  393. graph.zoomTo(scale);
  394. }, parent);
  395. })(scales[i]);
  396. }
  397. this.addMenuItems(menu, ['-', 'fitWindow', 'fitPageWidth', 'fitPage', 'fitTwoPages', '-', 'customZoom'], parent);
  398. })));
  399. this.put('file', new Menu(mxUtils.bind(this, function(menu, parent)
  400. {
  401. this.addMenuItems(menu, ['new', 'open', '-', 'save', 'saveAs', '-', 'import', 'export', '-', 'pageSetup', 'print'], parent);
  402. })));
  403. this.put('edit', new Menu(mxUtils.bind(this, function(menu, parent)
  404. {
  405. this.addMenuItems(menu, ['undo', 'redo', '-', 'cut', 'copy', 'paste', 'delete', '-', 'duplicate', '-',
  406. 'editData', 'editTooltip', 'editStyle', '-', 'edit', '-', 'editLink', 'openLink', '-',
  407. 'selectVertices', 'selectEdges', 'selectAll', 'selectNone', '-', 'lockUnlock']);
  408. })));
  409. this.put('extras', new Menu(mxUtils.bind(this, function(menu, parent)
  410. {
  411. this.addMenuItems(menu, ['copyConnect', 'collapseExpand', '-', 'editDiagram']);
  412. })));
  413. this.put('help', new Menu(mxUtils.bind(this, function(menu, parent)
  414. {
  415. this.addMenuItems(menu, ['help', '-', 'about']);
  416. })));
  417. };
  418. /**
  419. * Adds the label menu items to the given menu and parent.
  420. */
  421. Menus.prototype.put = function(name, menu)
  422. {
  423. this.menus[name] = menu;
  424. return menu;
  425. };
  426. /**
  427. * Adds the label menu items to the given menu and parent.
  428. */
  429. Menus.prototype.get = function(name)
  430. {
  431. return this.menus[name];
  432. };
  433. /**
  434. * Adds the given submenu.
  435. */
  436. Menus.prototype.addSubmenu = function(name, menu, parent)
  437. {
  438. var enabled = this.get(name).isEnabled();
  439. if (menu.showDisabled || enabled)
  440. {
  441. var submenu = menu.addItem(mxResources.get(name), null, null, parent, null, enabled);
  442. this.addMenu(name, menu, submenu);
  443. }
  444. };
  445. /**
  446. * Adds the label menu items to the given menu and parent.
  447. */
  448. Menus.prototype.addMenu = function(name, popupMenu, parent)
  449. {
  450. var menu = this.get(name);
  451. if (menu != null && (popupMenu.showDisabled || menu.isEnabled()))
  452. {
  453. this.get(name).execute(popupMenu, parent);
  454. }
  455. };
  456. /**
  457. * Adds a menu item to insert a table.
  458. */
  459. Menus.prototype.addInsertTableItem = function(menu)
  460. {
  461. // KNOWN: Does not work in IE8 standards and quirks
  462. var graph = this.editorUi.editor.graph;
  463. function createTable(rows, cols)
  464. {
  465. var html = ['<table>'];
  466. for (var i = 0; i < rows; i++)
  467. {
  468. html.push('<tr>');
  469. for (var j = 0; j < cols; j++)
  470. {
  471. html.push('<td><br></td>');
  472. }
  473. html.push('</tr>');
  474. }
  475. html.push('</table>');
  476. return html.join('');
  477. };
  478. // Show table size dialog
  479. var elt2 = menu.addItem('', null, mxUtils.bind(this, function(evt)
  480. {
  481. var td = graph.getParentByName(mxEvent.getSource(evt), 'TD');
  482. if (td != null)
  483. {
  484. var row2 = graph.getParentByName(td, 'TR');
  485. // To find the new link, we create a list of all existing links first
  486. // LATER: Refactor for reuse with code for finding inserted image below
  487. var tmp = graph.cellEditor.textarea.getElementsByTagName('table');
  488. var oldTables = [];
  489. for (var i = 0; i < tmp.length; i++)
  490. {
  491. oldTables.push(tmp[i]);
  492. }
  493. // Finding the new table will work with insertHTML, but IE does not support that
  494. graph.container.focus();
  495. graph.pasteHtmlAtCaret(createTable(row2.sectionRowIndex + 1, td.cellIndex + 1));
  496. // Moves cursor to first table cell
  497. var newTables = graph.cellEditor.textarea.getElementsByTagName('table');
  498. if (newTables.length == oldTables.length + 1)
  499. {
  500. // Inverse order in favor of appended tables
  501. for (var i = newTables.length - 1; i >= 0; i--)
  502. {
  503. if (i == 0 || newTables[i] != oldTables[i - 1])
  504. {
  505. graph.selectNode(newTables[i].rows[0].cells[0]);
  506. break;
  507. }
  508. }
  509. }
  510. }
  511. }));
  512. // Quirks mode does not add cell padding if cell is empty, needs good old spacer solution
  513. var quirksCellHtml = '<img src="' + mxClient.imageBasePath + '/transparent.gif' + '" width="16" height="16"/>';
  514. function createPicker(rows, cols)
  515. {
  516. var table2 = document.createElement('table');
  517. table2.setAttribute('border', '1');
  518. table2.style.borderCollapse = 'collapse';
  519. if (!mxClient.IS_QUIRKS)
  520. {
  521. table2.setAttribute('cellPadding', '8');
  522. }
  523. for (var i = 0; i < rows; i++)
  524. {
  525. var row = table2.insertRow(i);
  526. for (var j = 0; j < cols; j++)
  527. {
  528. var cell = row.insertCell(-1);
  529. if (mxClient.IS_QUIRKS)
  530. {
  531. cell.innerHTML = quirksCellHtml;
  532. }
  533. }
  534. }
  535. return table2;
  536. };
  537. function extendPicker(picker, rows, cols)
  538. {
  539. for (var i = picker.rows.length; i < rows; i++)
  540. {
  541. var row = picker.insertRow(i);
  542. for (var j = 0; j < picker.rows[0].cells.length; j++)
  543. {
  544. var cell = row.insertCell(-1);
  545. if (mxClient.IS_QUIRKS)
  546. {
  547. cell.innerHTML = quirksCellHtml;
  548. }
  549. }
  550. }
  551. for (var i = 0; i < picker.rows.length; i++)
  552. {
  553. var row = picker.rows[i];
  554. for (var j = row.cells.length; j < cols; j++)
  555. {
  556. var cell = row.insertCell(-1);
  557. if (mxClient.IS_QUIRKS)
  558. {
  559. cell.innerHTML = quirksCellHtml;
  560. }
  561. }
  562. }
  563. };
  564. elt2.firstChild.innerHTML = '';
  565. var picker = createPicker(5, 5);
  566. elt2.firstChild.appendChild(picker);
  567. var label = document.createElement('div');
  568. label.style.padding = '4px';
  569. label.style.fontSize = Menus.prototype.defaultFontSize + 'px';
  570. label.innerHTML = '1x1';
  571. elt2.firstChild.appendChild(label);
  572. mxEvent.addListener(picker, 'mouseover', function(e)
  573. {
  574. var td = graph.getParentByName(mxEvent.getSource(e), 'TD');
  575. if (td != null)
  576. {
  577. var row2 = graph.getParentByName(td, 'TR');
  578. extendPicker(picker, Math.min(20, row2.sectionRowIndex + 2), Math.min(20, td.cellIndex + 2));
  579. label.innerHTML = (td.cellIndex + 1) + 'x' + (row2.sectionRowIndex + 1);
  580. for (var i = 0; i < picker.rows.length; i++)
  581. {
  582. var r = picker.rows[i];
  583. for (var j = 0; j < r.cells.length; j++)
  584. {
  585. var cell = r.cells[j];
  586. if (i <= row2.sectionRowIndex && j <= td.cellIndex)
  587. {
  588. cell.style.backgroundColor = 'blue';
  589. }
  590. else
  591. {
  592. cell.style.backgroundColor = 'white';
  593. }
  594. }
  595. }
  596. mxEvent.consume(e);
  597. }
  598. });
  599. };
  600. /**
  601. * Adds a style change item to the given menu.
  602. */
  603. Menus.prototype.edgeStyleChange = function(menu, label, keys, values, sprite, parent, reset)
  604. {
  605. return menu.addItem(label, null, mxUtils.bind(this, function()
  606. {
  607. var graph = this.editorUi.editor.graph;
  608. graph.stopEditing(false);
  609. graph.getModel().beginUpdate();
  610. try
  611. {
  612. var cells = graph.getSelectionCells();
  613. var edges = [];
  614. for (var i = 0; i < cells.length; i++)
  615. {
  616. var cell = cells[i];
  617. if (graph.getModel().isEdge(cell))
  618. {
  619. if (reset)
  620. {
  621. var geo = graph.getCellGeometry(cell);
  622. // Resets all edge points
  623. if (geo != null)
  624. {
  625. geo = geo.clone();
  626. geo.points = null;
  627. graph.getModel().setGeometry(cell, geo);
  628. }
  629. }
  630. for (var j = 0; j < keys.length; j++)
  631. {
  632. graph.setCellStyles(keys[j], values[j], [cell]);
  633. }
  634. edges.push(cell);
  635. }
  636. }
  637. this.editorUi.fireEvent(new mxEventObject('styleChanged', 'keys', keys,
  638. 'values', values, 'cells', edges));
  639. }
  640. finally
  641. {
  642. graph.getModel().endUpdate();
  643. }
  644. }), parent, sprite);
  645. };
  646. /**
  647. * Adds a style change item to the given menu.
  648. */
  649. Menus.prototype.styleChange = function(menu, label, keys, values, sprite, parent, fn)
  650. {
  651. var apply = this.createStyleChangeFunction(keys, values);
  652. return menu.addItem(label, null, mxUtils.bind(this, function()
  653. {
  654. var graph = this.editorUi.editor.graph;
  655. if (fn != null && graph.cellEditor.isContentEditing())
  656. {
  657. fn();
  658. }
  659. else
  660. {
  661. apply();
  662. }
  663. }), parent, sprite);
  664. };
  665. /**
  666. *
  667. */
  668. Menus.prototype.createStyleChangeFunction = function(keys, values)
  669. {
  670. return mxUtils.bind(this, function()
  671. {
  672. var graph = this.editorUi.editor.graph;
  673. graph.stopEditing(false);
  674. graph.getModel().beginUpdate();
  675. try
  676. {
  677. for (var i = 0; i < keys.length; i++)
  678. {
  679. graph.setCellStyles(keys[i], values[i]);
  680. }
  681. this.editorUi.fireEvent(new mxEventObject('styleChanged', 'keys', keys, 'values', values,
  682. 'cells', graph.getSelectionCells()));
  683. }
  684. finally
  685. {
  686. graph.getModel().endUpdate();
  687. }
  688. });
  689. };
  690. /**
  691. * Adds a style change item with a prompt to the given menu.
  692. */
  693. Menus.prototype.promptChange = function(menu, label, hint, defaultValue, key, parent, enabled, fn, sprite)
  694. {
  695. return menu.addItem(label, null, mxUtils.bind(this, function()
  696. {
  697. var graph = this.editorUi.editor.graph;
  698. var value = defaultValue;
  699. var state = graph.getView().getState(graph.getSelectionCell());
  700. if (state != null)
  701. {
  702. value = state.style[key] || value;
  703. }
  704. var dlg = new FilenameDialog(this.editorUi, value, mxResources.get('apply'), mxUtils.bind(this, function(newValue)
  705. {
  706. if (newValue != null && newValue.length > 0)
  707. {
  708. graph.getModel().beginUpdate();
  709. try
  710. {
  711. graph.stopEditing(false);
  712. graph.setCellStyles(key, newValue);
  713. }
  714. finally
  715. {
  716. graph.getModel().endUpdate();
  717. }
  718. if (fn != null)
  719. {
  720. fn(newValue);
  721. }
  722. }
  723. }), mxResources.get('enterValue') + ((hint.length > 0) ? (' ' + hint) : ''));
  724. this.editorUi.showDialog(dlg.container, 300, 80, true, true);
  725. dlg.init();
  726. }), parent, sprite, enabled);
  727. };
  728. /**
  729. * Adds a handler for showing a menu in the given element.
  730. */
  731. Menus.prototype.pickColor = function(key, cmd, defaultValue)
  732. {
  733. var graph = this.editorUi.editor.graph;
  734. if (cmd != null && graph.cellEditor.isContentEditing())
  735. {
  736. // Saves and restores text selection for in-place editor
  737. var selState = graph.cellEditor.saveSelection();
  738. var dlg = new ColorDialog(this.editorUi, defaultValue || '000000', mxUtils.bind(this, function(color)
  739. {
  740. graph.cellEditor.restoreSelection(selState);
  741. document.execCommand(cmd, false, (color != mxConstants.NONE) ? color : 'transparent');
  742. }), function()
  743. {
  744. graph.cellEditor.restoreSelection(selState);
  745. });
  746. this.editorUi.showDialog(dlg.container, 220, 430, true, true);
  747. dlg.init();
  748. }
  749. else
  750. {
  751. if (this.colorDialog == null)
  752. {
  753. this.colorDialog = new ColorDialog(this.editorUi);
  754. }
  755. this.colorDialog.currentColorKey = key;
  756. var state = graph.getView().getState(graph.getSelectionCell());
  757. var color = 'none';
  758. if (state != null)
  759. {
  760. color = state.style[key] || color;
  761. }
  762. if (color == 'none')
  763. {
  764. color = 'ffffff';
  765. this.colorDialog.picker.fromString('ffffff');
  766. this.colorDialog.colorInput.value = 'none';
  767. }
  768. else
  769. {
  770. this.colorDialog.picker.fromString(color);
  771. }
  772. this.editorUi.showDialog(this.colorDialog.container, 220, 430, true, true);
  773. this.colorDialog.init();
  774. }
  775. };
  776. /**
  777. * Adds a handler for showing a menu in the given element.
  778. */
  779. Menus.prototype.toggleStyle = function(key, defaultValue)
  780. {
  781. var graph = this.editorUi.editor.graph;
  782. var value = graph.toggleCellStyles(key, defaultValue);
  783. this.editorUi.fireEvent(new mxEventObject('styleChanged', 'keys', [key], 'values', [value],
  784. 'cells', graph.getSelectionCells()));
  785. };
  786. /**
  787. * Creates the keyboard event handler for the current graph and history.
  788. */
  789. Menus.prototype.addMenuItem = function(menu, key, parent, trigger, sprite)
  790. {
  791. var action = this.editorUi.actions.get(key);
  792. if (action != null && (menu.showDisabled || action.isEnabled()) && action.visible)
  793. {
  794. var item = menu.addItem(action.label, null, function()
  795. {
  796. action.funct(trigger);
  797. }, parent, sprite, action.isEnabled());
  798. // Adds checkmark image
  799. if (action.toggleAction && action.isSelected())
  800. {
  801. menu.addCheckmark(item, Editor.checkmarkImage);
  802. }
  803. this.addShortcut(item, action);
  804. return item;
  805. }
  806. return null;
  807. };
  808. /**
  809. * Adds a checkmark to the given menuitem.
  810. */
  811. Menus.prototype.addShortcut = function(item, action)
  812. {
  813. if (action.shortcut != null)
  814. {
  815. var td = item.firstChild.nextSibling.nextSibling;
  816. var span = document.createElement('span');
  817. span.style.color = 'gray';
  818. mxUtils.write(span, action.shortcut);
  819. td.appendChild(span);
  820. }
  821. };
  822. /**
  823. * Creates the keyboard event handler for the current graph and history.
  824. */
  825. Menus.prototype.addMenuItems = function(menu, keys, parent, trigger, sprites)
  826. {
  827. for (var i = 0; i < keys.length; i++)
  828. {
  829. if (keys[i] == '-')
  830. {
  831. menu.addSeparator(parent);
  832. }
  833. else
  834. {
  835. this.addMenuItem(menu, keys[i], parent, trigger, (sprites != null) ? sprites[i] : null);
  836. }
  837. }
  838. };
  839. /**
  840. * Creates the keyboard event handler for the current graph and history.
  841. */
  842. Menus.prototype.createPopupMenu = function(menu, cell, evt)
  843. {
  844. var graph = this.editorUi.editor.graph;
  845. menu.smartSeparators = true;
  846. if (graph.isSelectionEmpty())
  847. {
  848. this.addMenuItems(menu, ['undo', 'redo', '-', 'pasteHere'], null, evt);
  849. }
  850. else
  851. {
  852. this.addMenuItems(menu, ['delete', '-', 'cut', 'copy', '-', 'duplicate'], null, evt);
  853. }
  854. if (!graph.isSelectionEmpty())
  855. {
  856. if (graph.getSelectionCount() == 1)
  857. {
  858. this.addMenuItems(menu, ['setAsDefaultStyle'], null, evt);
  859. }
  860. menu.addSeparator();
  861. cell = graph.getSelectionCell();
  862. var state = graph.view.getState(cell);
  863. if (state != null)
  864. {
  865. var hasWaypoints = false;
  866. this.addMenuItems(menu, ['toFront', 'toBack', '-'], null, evt);
  867. if (graph.getModel().isEdge(cell) && mxUtils.getValue(state.style, mxConstants.STYLE_EDGE, null) != 'entityRelationEdgeStyle' &&
  868. mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null) != 'arrow')
  869. {
  870. var handler = graph.selectionCellsHandler.getHandler(cell);
  871. var isWaypoint = false;
  872. if (handler instanceof mxEdgeHandler && handler.bends != null && handler.bends.length > 2)
  873. {
  874. var index = handler.getHandleForEvent(graph.updateMouseEvent(new mxMouseEvent(evt)));
  875. // Configures removeWaypoint action before execution
  876. // Using trigger parameter is cleaner but have to find waypoint here anyway.
  877. var rmWaypointAction = this.editorUi.actions.get('removeWaypoint');
  878. rmWaypointAction.handler = handler;
  879. rmWaypointAction.index = index;
  880. isWaypoint = index > 0 && index < handler.bends.length - 1;
  881. }
  882. this.addMenuItems(menu, ['-', (isWaypoint) ? 'removeWaypoint' : 'addWaypoint'], null, evt);
  883. // Adds reset waypoints option if waypoints exist
  884. var geo = graph.getModel().getGeometry(cell);
  885. hasWaypoints = geo != null && geo.points != null && geo.points.length > 0;
  886. }
  887. if (graph.getSelectionCount() == 1 && (hasWaypoints || (graph.getModel().isVertex(cell) &&
  888. graph.getModel().getEdgeCount(cell) > 0)))
  889. {
  890. this.addMenuItems(menu, ['clearWaypoints'], null, evt);
  891. }
  892. if (graph.getSelectionCount() > 1)
  893. {
  894. menu.addSeparator();
  895. this.addMenuItems(menu, ['group'], null, evt);
  896. }
  897. else if (graph.getSelectionCount() == 1 && !graph.getModel().isEdge(cell) && !graph.isSwimlane(cell) &&
  898. graph.getModel().getChildCount(cell) > 0)
  899. {
  900. menu.addSeparator();
  901. this.addMenuItems(menu, ['ungroup'], null, evt);
  902. }
  903. if (graph.getSelectionCount() == 1)
  904. {
  905. menu.addSeparator();
  906. this.addMenuItems(menu, ['edit', '-', 'editData', 'editLink'], null, evt);
  907. // Shows edit image action if there is an image in the style
  908. if (graph.getModel().isVertex(cell) && mxUtils.getValue(state.style, mxConstants.STYLE_IMAGE, null) != null)
  909. {
  910. menu.addSeparator();
  911. this.addMenuItem(menu, 'image', null, evt).firstChild.nextSibling.innerHTML = mxResources.get('editImage') + '...';
  912. }
  913. }
  914. }
  915. }
  916. else
  917. {
  918. this.addMenuItems(menu, ['-', 'selectVertices', 'selectEdges', '-', 'selectAll'], null, evt);
  919. }
  920. };
  921. /**
  922. * Creates the keyboard event handler for the current graph and history.
  923. */
  924. Menus.prototype.createMenubar = function(container)
  925. {
  926. var menubar = new Menubar(this.editorUi, container);
  927. var menus = this.defaultMenuItems;
  928. for (var i = 0; i < menus.length; i++)
  929. {
  930. (mxUtils.bind(this, function(menu)
  931. {
  932. var elt = menubar.addMenu(mxResources.get(menus[i]), mxUtils.bind(this, function()
  933. {
  934. // Allows extensions of menu.funct
  935. menu.funct.apply(this, arguments);
  936. }));
  937. this.menuCreated(menu, elt);
  938. }))(this.get(menus[i]));
  939. }
  940. return menubar;
  941. };
  942. /**
  943. * Creates the keyboard event handler for the current graph and history.
  944. */
  945. Menus.prototype.menuCreated = function(menu, elt)
  946. {
  947. if (elt != null)
  948. {
  949. menu.addListener('stateChanged', function()
  950. {
  951. elt.enabled = menu.enabled;
  952. if (!menu.enabled)
  953. {
  954. elt.className = 'geItem mxDisabled';
  955. if (document.documentMode == 8)
  956. {
  957. elt.style.color = '#c3c3c3';
  958. }
  959. }
  960. else
  961. {
  962. elt.className = 'geItem';
  963. if (document.documentMode == 8)
  964. {
  965. elt.style.color = '';
  966. }
  967. }
  968. });
  969. }
  970. };
  971. /**
  972. * Construcs a new menubar for the given editor.
  973. */
  974. function Menubar(editorUi, container)
  975. {
  976. this.editorUi = editorUi;
  977. this.container = container;
  978. };
  979. /**
  980. * Adds the menubar elements.
  981. */
  982. Menubar.prototype.hideMenu = function()
  983. {
  984. this.editorUi.hideCurrentMenu();
  985. };
  986. /**
  987. * Adds a submenu to this menubar.
  988. */
  989. Menubar.prototype.addMenu = function(label, funct)
  990. {
  991. var elt = document.createElement('a');
  992. elt.setAttribute('href', 'javascript:void(0);');
  993. elt.className = 'geItem';
  994. mxUtils.write(elt, label);
  995. this.addMenuHandler(elt, funct);
  996. this.container.appendChild(elt);
  997. return elt;
  998. };
  999. /**
  1000. * Adds a handler for showing a menu in the given element.
  1001. */
  1002. Menubar.prototype.addMenuHandler = function(elt, funct)
  1003. {
  1004. if (funct != null)
  1005. {
  1006. var show = true;
  1007. var clickHandler = mxUtils.bind(this, function(evt)
  1008. {
  1009. if (show && elt.enabled == null || elt.enabled)
  1010. {
  1011. this.editorUi.editor.graph.popupMenuHandler.hideMenu();
  1012. var menu = new mxPopupMenu(funct);
  1013. menu.div.className += ' geMenubarMenu';
  1014. menu.smartSeparators = true;
  1015. menu.showDisabled = true;
  1016. menu.autoExpand = true;
  1017. // Disables autoexpand and destroys menu when hidden
  1018. menu.hideMenu = mxUtils.bind(this, function()
  1019. {
  1020. mxPopupMenu.prototype.hideMenu.apply(menu, arguments);
  1021. this.editorUi.resetCurrentMenu();
  1022. menu.destroy();
  1023. });
  1024. var offset = mxUtils.getOffset(elt);
  1025. menu.popup(offset.x, offset.y + elt.offsetHeight, null, evt);
  1026. this.editorUi.setCurrentMenu(menu, elt);
  1027. }
  1028. mxEvent.consume(evt);
  1029. });
  1030. // Shows menu automatically while in expanded state
  1031. mxEvent.addListener(elt, 'mousemove', mxUtils.bind(this, function(evt)
  1032. {
  1033. if (this.editorUi.currentMenu != null && this.editorUi.currentMenuElt != elt)
  1034. {
  1035. this.editorUi.hideCurrentMenu();
  1036. clickHandler(evt);
  1037. }
  1038. }));
  1039. // Hides menu if already showing
  1040. mxEvent.addListener(elt, 'mousedown', mxUtils.bind(this, function()
  1041. {
  1042. show = this.currentElt != elt;
  1043. }));
  1044. mxEvent.addListener(elt, 'click', mxUtils.bind(this, function(evt)
  1045. {
  1046. clickHandler(evt);
  1047. show = true;
  1048. }));
  1049. }
  1050. };
  1051. /**
  1052. * Creates the keyboard event handler for the current graph and history.
  1053. */
  1054. Menubar.prototype.destroy = function()
  1055. {
  1056. // do nothing
  1057. };
  1058. /**
  1059. * Constructs a new action for the given parameters.
  1060. */
  1061. function Menu(funct, enabled)
  1062. {
  1063. mxEventSource.call(this);
  1064. this.funct = funct;
  1065. this.enabled = (enabled != null) ? enabled : true;
  1066. };
  1067. // Menu inherits from mxEventSource
  1068. mxUtils.extend(Menu, mxEventSource);
  1069. /**
  1070. * Sets the enabled state of the action and fires a stateChanged event.
  1071. */
  1072. Menu.prototype.isEnabled = function()
  1073. {
  1074. return this.enabled;
  1075. };
  1076. /**
  1077. * Sets the enabled state of the action and fires a stateChanged event.
  1078. */
  1079. Menu.prototype.setEnabled = function(value)
  1080. {
  1081. if (this.enabled != value)
  1082. {
  1083. this.enabled = value;
  1084. this.fireEvent(new mxEventObject('stateChanged'));
  1085. }
  1086. };
  1087. /**
  1088. * Sets the enabled state of the action and fires a stateChanged event.
  1089. */
  1090. Menu.prototype.execute = function(menu, parent)
  1091. {
  1092. this.funct(menu, parent);
  1093. };
  1094. /**
  1095. * "Installs" menus in EditorUi.
  1096. */
  1097. EditorUi.prototype.createMenus = function()
  1098. {
  1099. return new Menus(this);
  1100. };