Menus.js 32 KB

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