Menus.js 53 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006
  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.autoPopup = true;
  39. /**
  40. * Adds the label menu items to the given menu and parent.
  41. */
  42. Menus.prototype.init = function()
  43. {
  44. var ui = this.editorUi;
  45. var graph = ui.editor.graph;
  46. var isGraphEnabled = mxUtils.bind(graph, graph.isEnabled);
  47. this.customFonts = [];
  48. this.customFontSizes = [];
  49. this.put('fontFamily', new Menu(mxUtils.bind(this, function(menu, parent)
  50. {
  51. var addItem = mxUtils.bind(this, function(fontFamily)
  52. {
  53. var tr = this.styleChange(menu, fontFamily, [mxConstants.STYLE_FONTFAMILY],
  54. [fontFamily], null, parent, function()
  55. {
  56. document.execCommand('fontname', false, fontFamily);
  57. ui.fireEvent(new mxEventObject('styleChanged',
  58. 'keys', [mxConstants.STYLE_FONTFAMILY],
  59. 'values', [fontFamily],
  60. 'cells', [graph.cellEditor.getEditingCell()]));
  61. }, function()
  62. {
  63. graph.updateLabelElements(graph.getSelectionCells(), function(elt)
  64. {
  65. elt.removeAttribute('face');
  66. elt.style.fontFamily = null;
  67. if (elt.nodeName == 'PRE')
  68. {
  69. graph.replaceElement(elt, 'div');
  70. }
  71. });
  72. });
  73. tr.firstChild.nextSibling.style.fontFamily = fontFamily;
  74. });
  75. for (var i = 0; i < this.defaultFonts.length; i++)
  76. {
  77. addItem(this.defaultFonts[i]);
  78. }
  79. menu.addSeparator(parent);
  80. if (this.customFonts.length > 0)
  81. {
  82. for (var i = 0; i < this.customFonts.length; i++)
  83. {
  84. addItem(this.customFonts[i]);
  85. }
  86. menu.addSeparator(parent);
  87. menu.addItem(mxResources.get('reset'), null, mxUtils.bind(this, function()
  88. {
  89. this.customFonts = [];
  90. this.editorUi.fireEvent(new mxEventObject('customFontsChanged'));
  91. }), parent);
  92. menu.addSeparator(parent);
  93. }
  94. this.promptChange(menu, mxResources.get('custom') + '...', '', mxConstants.DEFAULT_FONTFAMILY, mxConstants.STYLE_FONTFAMILY, parent, true, mxUtils.bind(this, function(newValue)
  95. {
  96. if (mxUtils.indexOf(this.customFonts, newValue) < 0)
  97. {
  98. this.customFonts.push(newValue);
  99. this.editorUi.fireEvent(new mxEventObject('customFontsChanged'));
  100. }
  101. }));
  102. })));
  103. this.put('formatBlock', new Menu(mxUtils.bind(this, function(menu, parent)
  104. {
  105. function addItem(label, tag)
  106. {
  107. return menu.addItem(label, null, mxUtils.bind(this, function()
  108. {
  109. // TODO: Check if visible
  110. if (graph.cellEditor.textarea != null)
  111. {
  112. graph.cellEditor.textarea.focus();
  113. document.execCommand('formatBlock', false, '<' + tag + '>');
  114. }
  115. }), parent);
  116. };
  117. addItem(mxResources.get('normal'), 'p');
  118. addItem('', 'h1').firstChild.nextSibling.innerHTML = '<h1 style="margin:0px;">' + mxResources.get('heading') + ' 1</h1>';
  119. addItem('', 'h2').firstChild.nextSibling.innerHTML = '<h2 style="margin:0px;">' + mxResources.get('heading') + ' 2</h2>';
  120. addItem('', 'h3').firstChild.nextSibling.innerHTML = '<h3 style="margin:0px;">' + mxResources.get('heading') + ' 3</h3>';
  121. addItem('', 'h4').firstChild.nextSibling.innerHTML = '<h4 style="margin:0px;">' + mxResources.get('heading') + ' 4</h4>';
  122. addItem('', 'h5').firstChild.nextSibling.innerHTML = '<h5 style="margin:0px;">' + mxResources.get('heading') + ' 5</h5>';
  123. addItem('', 'h6').firstChild.nextSibling.innerHTML = '<h6 style="margin:0px;">' + mxResources.get('heading') + ' 6</h6>';
  124. addItem('', 'pre').firstChild.nextSibling.innerHTML = '<pre style="margin:0px;">' + mxResources.get('formatted') + '</pre>';
  125. addItem('', 'blockquote').firstChild.nextSibling.innerHTML = '<blockquote style="margin-top:0px;margin-bottom:0px;">' + mxResources.get('blockquote') + '</blockquote>';
  126. })));
  127. this.put('fontSize', new Menu(mxUtils.bind(this, function(menu, parent)
  128. {
  129. var sizes = [6, 8, 9, 10, 11, 12, 14, 18, 24, 36, 48, 72];
  130. if (mxUtils.indexOf(sizes, this.defaultFontSize) < 0)
  131. {
  132. sizes.push(this.defaultFontSize);
  133. sizes.sort(function(a, b)
  134. {
  135. return a - b;
  136. });
  137. }
  138. var setFontSize = mxUtils.bind(this, function(fontSize)
  139. {
  140. if (graph.cellEditor.textarea != null)
  141. {
  142. // Creates an element with arbitrary size 3
  143. document.execCommand('fontSize', false, '3');
  144. // Changes the css font size of the first font element inside the in-place editor with size 3
  145. // hopefully the above element that we've just created. LATER: Check for new element using
  146. // previous result of getElementsByTagName (see other actions)
  147. var elts = graph.cellEditor.textarea.getElementsByTagName('font');
  148. for (var i = 0; i < elts.length; i++)
  149. {
  150. if (elts[i].getAttribute('size') == '3')
  151. {
  152. elts[i].removeAttribute('size');
  153. elts[i].style.fontSize = fontSize + 'px';
  154. break;
  155. }
  156. }
  157. ui.fireEvent(new mxEventObject('styleChanged',
  158. 'keys', [mxConstants.STYLE_FONTSIZE],
  159. 'values', [fontSize],
  160. 'cells', [graph.cellEditor.getEditingCell()]));
  161. }
  162. });
  163. var addItem = mxUtils.bind(this, function(fontSize)
  164. {
  165. this.styleChange(menu, fontSize, [mxConstants.STYLE_FONTSIZE],
  166. [fontSize], null, parent, function()
  167. {
  168. setFontSize(fontSize);
  169. });
  170. });
  171. for (var i = 0; i < sizes.length; i++)
  172. {
  173. addItem(sizes[i]);
  174. }
  175. menu.addSeparator(parent);
  176. if (this.customFontSizes.length > 0)
  177. {
  178. var counter = 0;
  179. for (var i = 0; i < this.customFontSizes.length; i++)
  180. {
  181. if (mxUtils.indexOf(sizes, this.customFontSizes[i]) < 0)
  182. {
  183. addItem(this.customFontSizes[i]);
  184. counter++;
  185. }
  186. }
  187. if (counter > 0)
  188. {
  189. menu.addSeparator(parent);
  190. }
  191. menu.addItem(mxResources.get('reset'), null, mxUtils.bind(this, function()
  192. {
  193. this.customFontSizes = [];
  194. }), parent);
  195. menu.addSeparator(parent);
  196. }
  197. var selState = null;
  198. this.promptChange(menu, mxResources.get('custom') + '...',
  199. '(' + mxResources.get('points') + ')', this.defaultFontSize,
  200. mxConstants.STYLE_FONTSIZE, parent, true,
  201. mxUtils.bind(this, function(newValue)
  202. {
  203. if (selState != null && graph.cellEditor.textarea != null)
  204. {
  205. graph.cellEditor.textarea.focus();
  206. graph.cellEditor.restoreSelection(selState);
  207. }
  208. if (newValue != null && newValue.length > 0)
  209. {
  210. this.customFontSizes.push(newValue);
  211. setFontSize(newValue);
  212. }
  213. }), null, function()
  214. {
  215. selState = graph.cellEditor.saveSelection();
  216. return false;
  217. });
  218. })));
  219. this.put('direction', new Menu(mxUtils.bind(this, function(menu, parent)
  220. {
  221. menu.addItem(mxResources.get('flipH'), null, function() { graph.toggleCellStyles(mxConstants.STYLE_FLIPH, false); }, parent);
  222. menu.addItem(mxResources.get('flipV'), null, function() { graph.toggleCellStyles(mxConstants.STYLE_FLIPV, false); }, parent);
  223. this.addMenuItems(menu, ['-', 'rotation'], parent);
  224. })));
  225. this.put('align', new Menu(mxUtils.bind(this, function(menu, parent)
  226. {
  227. var ss = this.editorUi.getSelectionState();
  228. var enabled = ss.vertices.length > 1;
  229. menu.addItem(mxResources.get('leftAlign'), null, function() { graph.alignCells(mxConstants.ALIGN_LEFT); }, parent, null, enabled);
  230. menu.addItem(mxResources.get('center'), null, function() { graph.alignCells(mxConstants.ALIGN_CENTER); }, parent, null, enabled);
  231. menu.addItem(mxResources.get('rightAlign'), null, function() { graph.alignCells(mxConstants.ALIGN_RIGHT); }, parent, null, enabled);
  232. menu.addSeparator(parent);
  233. menu.addItem(mxResources.get('topAlign'), null, function() { graph.alignCells(mxConstants.ALIGN_TOP); }, parent, null, enabled);
  234. menu.addItem(mxResources.get('middle'), null, function() { graph.alignCells(mxConstants.ALIGN_MIDDLE); }, parent, null, enabled);
  235. menu.addItem(mxResources.get('bottomAlign'), null, function() { graph.alignCells(mxConstants.ALIGN_BOTTOM); }, parent, null, enabled);
  236. this.addMenuItems(menu, ['-', 'snapToGrid'], parent);
  237. })));
  238. this.put('distribute', new Menu(mxUtils.bind(this, function(menu, parent)
  239. {
  240. menu.addItem(mxResources.get('horizontal'), null, function() { graph.distributeCells(true); }, parent);
  241. menu.addItem(mxResources.get('vertical'), null, function() { graph.distributeCells(false); }, parent);
  242. menu.addSeparator(parent);
  243. this.addSubmenu('distributeSpacing', menu, parent, mxResources.get('spacing'));
  244. })));
  245. this.put('distributeSpacing', new Menu(mxUtils.bind(this, function(menu, parent)
  246. {
  247. menu.addItem(mxResources.get('horizontal'), null, function() { graph.distributeCells(true, null, true); }, parent);
  248. menu.addItem(mxResources.get('vertical'), null, function() { graph.distributeCells(false, null, true); }, parent);
  249. })));
  250. this.put('line', new Menu(mxUtils.bind(this, function(menu, parent)
  251. {
  252. var state = graph.view.getState(graph.getSelectionCell());
  253. if (state != null)
  254. {
  255. var shape = mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE);
  256. if (shape != 'arrow')
  257. {
  258. this.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
  259. [null, null, null], 'geIcon geSprite geSprite-straight', parent, true).setAttribute('title', mxResources.get('straight'));
  260. this.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
  261. ['orthogonalEdgeStyle', null, null], 'geIcon geSprite geSprite-orthogonal', parent, true).setAttribute('title', mxResources.get('orthogonal'));
  262. this.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_ELBOW, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
  263. ['elbowEdgeStyle', null, null, null], 'geIcon geSprite geSprite-horizontalelbow', parent, true).setAttribute('title', mxResources.get('simple'));
  264. this.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_ELBOW, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
  265. ['elbowEdgeStyle', 'vertical', null, null], 'geIcon geSprite geSprite-verticalelbow', parent, true).setAttribute('title', mxResources.get('simple'));
  266. this.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_ELBOW, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
  267. ['isometricEdgeStyle', null, null, null], 'geIcon geSprite geSprite-horizontalisometric', parent, true).setAttribute('title', mxResources.get('isometric'));
  268. this.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_ELBOW, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
  269. ['isometricEdgeStyle', 'vertical', null, null], 'geIcon geSprite geSprite-verticalisometric', parent, true).setAttribute('title', mxResources.get('isometric'));
  270. if (shape == 'connector')
  271. {
  272. this.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
  273. ['orthogonalEdgeStyle', '1', null], 'geIcon geSprite geSprite-curved', parent, true).setAttribute('title', mxResources.get('curved'));
  274. }
  275. this.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
  276. ['entityRelationEdgeStyle', null, null], 'geIcon geSprite geSprite-entity', parent, true).setAttribute('title', mxResources.get('entityRelation'));
  277. }
  278. menu.addSeparator(parent);
  279. this.styleChange(menu, '', [mxConstants.STYLE_SHAPE, mxConstants.STYLE_STARTSIZE, mxConstants.STYLE_ENDSIZE, 'width'],
  280. [null, null, null, null], 'geIcon geSprite geSprite-connection', parent, null, null, true).setAttribute('title', mxResources.get('line'));
  281. this.styleChange(menu, '', [mxConstants.STYLE_SHAPE, mxConstants.STYLE_STARTSIZE, mxConstants.STYLE_ENDSIZE, 'width'],
  282. ['link', null, null, null], 'geIcon geSprite geSprite-linkedge', parent, null, null, true).setAttribute('title', mxResources.get('link'));
  283. this.styleChange(menu, '', [mxConstants.STYLE_SHAPE, mxConstants.STYLE_STARTSIZE, mxConstants.STYLE_ENDSIZE, 'width'],
  284. ['flexArrow', null, null, null], 'geIcon geSprite geSprite-arrow', parent, null, null, true).setAttribute('title', mxResources.get('arrow'));
  285. this.styleChange(menu, '', [mxConstants.STYLE_SHAPE, mxConstants.STYLE_STARTSIZE, mxConstants.STYLE_ENDSIZE, 'width'],
  286. ['arrow', null, null, null], 'geIcon geSprite geSprite-simplearrow', parent, null, null, true).setAttribute('title', mxResources.get('simpleArrow'));
  287. }
  288. })));
  289. this.put('layout', new Menu(mxUtils.bind(this, function(menu, parent)
  290. {
  291. var promptSpacing = mxUtils.bind(this, function(defaultValue, fn)
  292. {
  293. this.editorUi.prompt(mxResources.get('spacing'), defaultValue, fn);
  294. });
  295. var runTreeLayout = mxUtils.bind(this, function(layout)
  296. {
  297. this.editorUi.tryAndHandle(mxUtils.bind(this, function()
  298. {
  299. var tmp = graph.getSelectionCell();
  300. var roots = null;
  301. if (tmp == null || graph.getModel().getChildCount(tmp) == 0)
  302. {
  303. if (graph.getModel().getEdgeCount(tmp) == 0)
  304. {
  305. roots = graph.findTreeRoots(graph.getDefaultParent());
  306. }
  307. }
  308. else
  309. {
  310. roots = graph.findTreeRoots(tmp);
  311. }
  312. if (roots != null && roots.length > 0)
  313. {
  314. tmp = roots[0];
  315. }
  316. if (tmp != null)
  317. {
  318. this.editorUi.executeLayout(function()
  319. {
  320. layout.execute(graph.getDefaultParent(), tmp);
  321. if (!graph.isSelectionEmpty())
  322. {
  323. tmp = graph.getModel().getParent(tmp);
  324. if (graph.getModel().isVertex(tmp))
  325. {
  326. graph.updateGroupBounds([tmp], graph.gridSize * 2, true);
  327. }
  328. }
  329. }, true);
  330. }
  331. }));
  332. });
  333. menu.addItem(mxResources.get('horizontalFlow'), null, mxUtils.bind(this, function()
  334. {
  335. this.editorUi.tryAndHandle(mxUtils.bind(this, function()
  336. {
  337. var layout = new mxHierarchicalLayout(graph, mxConstants.DIRECTION_WEST);
  338. this.editorUi.executeLayout(function()
  339. {
  340. var selectionCells = graph.getSelectionCells();
  341. layout.execute(graph.getDefaultParent(), selectionCells.length == 0 ? null : selectionCells);
  342. }, true);
  343. }));
  344. }), parent);
  345. menu.addItem(mxResources.get('verticalFlow'), null, mxUtils.bind(this, function()
  346. {
  347. this.editorUi.tryAndHandle(mxUtils.bind(this, function()
  348. {
  349. var layout = new mxHierarchicalLayout(graph, mxConstants.DIRECTION_NORTH);
  350. this.editorUi.executeLayout(function()
  351. {
  352. var selectionCells = graph.getSelectionCells();
  353. layout.execute(graph.getDefaultParent(), selectionCells.length == 0 ? null : selectionCells);
  354. }, true);
  355. }));
  356. }), parent);
  357. menu.addSeparator(parent);
  358. menu.addItem(mxResources.get('horizontalTree'), null, mxUtils.bind(this, function()
  359. {
  360. var layout = new mxCompactTreeLayout(graph, true);
  361. layout.edgeRouting = false;
  362. layout.levelDistance = 30;
  363. promptSpacing(layout.levelDistance, mxUtils.bind(this, function(spacing)
  364. {
  365. if (!isNaN(spacing))
  366. {
  367. layout.levelDistance = spacing;
  368. runTreeLayout(layout);
  369. }
  370. }));
  371. }), parent);
  372. menu.addItem(mxResources.get('verticalTree'), null, mxUtils.bind(this, function()
  373. {
  374. var layout = new mxCompactTreeLayout(graph, false);
  375. layout.edgeRouting = false;
  376. layout.levelDistance = 30;
  377. promptSpacing(layout.levelDistance, mxUtils.bind(this, function(spacing)
  378. {
  379. if (!isNaN(spacing))
  380. {
  381. layout.levelDistance = spacing;
  382. runTreeLayout(layout);
  383. }
  384. }));
  385. }), parent);
  386. menu.addItem(mxResources.get('radialTree'), null, mxUtils.bind(this, function()
  387. {
  388. var layout = new mxRadialTreeLayout(graph);
  389. layout.levelDistance = 80;
  390. layout.autoRadius = true;
  391. promptSpacing(layout.levelDistance, mxUtils.bind(this, function(spacing)
  392. {
  393. if (!isNaN(spacing))
  394. {
  395. layout.levelDistance = spacing;
  396. runTreeLayout(layout);
  397. }
  398. }));
  399. }), parent);
  400. menu.addSeparator(parent);
  401. menu.addItem(mxResources.get('organic'), null, mxUtils.bind(this, function()
  402. {
  403. var layout = new mxFastOrganicLayout(graph);
  404. promptSpacing(layout.forceConstant, mxUtils.bind(this, function(newValue)
  405. {
  406. this.editorUi.tryAndHandle(mxUtils.bind(this, function()
  407. {
  408. layout.forceConstant = newValue;
  409. this.editorUi.executeLayout(function()
  410. {
  411. var tmp = graph.getSelectionCell();
  412. if (tmp == null || graph.getModel().getChildCount(tmp) == 0)
  413. {
  414. tmp = graph.getDefaultParent();
  415. }
  416. layout.execute(tmp);
  417. if (graph.getModel().isVertex(tmp))
  418. {
  419. graph.updateGroupBounds([tmp], graph.gridSize * 2, true);
  420. }
  421. }, true);
  422. }));
  423. }));
  424. }), parent);
  425. menu.addItem(mxResources.get('circle'), null, mxUtils.bind(this, function()
  426. {
  427. this.editorUi.tryAndHandle(mxUtils.bind(this, function()
  428. {
  429. var layout = new mxCircleLayout(graph);
  430. this.editorUi.executeLayout(function()
  431. {
  432. var tmp = graph.getSelectionCell();
  433. if (tmp == null || graph.getModel().getChildCount(tmp) == 0)
  434. {
  435. tmp = graph.getDefaultParent();
  436. }
  437. layout.execute(tmp);
  438. if (graph.getModel().isVertex(tmp))
  439. {
  440. graph.updateGroupBounds([tmp], graph.gridSize * 2, true);
  441. }
  442. }, true);
  443. }));
  444. }), parent);
  445. })));
  446. this.put('navigation', new Menu(mxUtils.bind(this, function(menu, parent)
  447. {
  448. this.addMenuItems(menu, ['home', '-', 'exitGroup', 'enterGroup', '-', 'expand', 'collapse', '-', 'collapsible'], parent);
  449. })));
  450. this.put('arrange', new Menu(mxUtils.bind(this, function(menu, parent)
  451. {
  452. this.addMenuItems(menu, ['toFront', 'toBack', 'bringForward', 'sendBackward', '-'], parent);
  453. this.addSubmenu('direction', menu, parent);
  454. this.addMenuItems(menu, ['turn', '-'], parent);
  455. this.addSubmenu('align', menu, parent);
  456. this.addSubmenu('distribute', menu, parent);
  457. menu.addSeparator(parent);
  458. this.addSubmenu('navigation', menu, parent);
  459. this.addSubmenu('insert', menu, parent);
  460. this.addSubmenu('layout', menu, parent);
  461. this.addMenuItems(menu, ['-', 'group', 'ungroup', 'removeFromGroup', '-', 'clearWaypoints', 'autosize'], parent);
  462. }))).isEnabled = isGraphEnabled;
  463. this.put('insert', new Menu(mxUtils.bind(this, function(menu, parent)
  464. {
  465. this.addMenuItems(menu, ['insertLink', 'insertImage'], parent);
  466. })));
  467. this.put('view', new Menu(mxUtils.bind(this, function(menu, parent)
  468. {
  469. this.addMenuItems(menu, ((this.editorUi.format != null) ? ['format'] : []).
  470. concat(['outline', 'layers', '-', 'pageView', 'pageScale', '-', 'tooltips',
  471. 'grid', 'guides', '-', 'connectionArrows', 'connectionPoints', '-',
  472. 'resetView', 'zoomIn', 'zoomOut'], parent));
  473. })));
  474. // Two special dropdowns that are only used in the toolbar
  475. this.put('viewPanels', new Menu(mxUtils.bind(this, function(menu, parent)
  476. {
  477. if (this.editorUi.format != null)
  478. {
  479. this.addMenuItems(menu, ['format'], parent);
  480. }
  481. this.addMenuItems(menu, ['outline', 'layers'], parent);
  482. })));
  483. this.put('viewZoom', new Menu(mxUtils.bind(this, function(menu, parent)
  484. {
  485. this.addMenuItems(menu, ['smartFit', '-'], parent);
  486. var scales = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 2, 3, 4];
  487. for (var i = 0; i < scales.length; i++)
  488. {
  489. (function(scale)
  490. {
  491. menu.addItem((scale * 100) + '%', null, function()
  492. {
  493. graph.zoomTo(scale);
  494. }, parent);
  495. })(scales[i]);
  496. }
  497. this.addMenuItems(menu, ['-', 'fitWindow', 'fitPageWidth', 'fitPage', 'fitTwoPages', '-', 'customZoom'], parent);
  498. })));
  499. this.put('file', new Menu(mxUtils.bind(this, function(menu, parent)
  500. {
  501. this.addMenuItems(menu, ['new', 'open', '-', 'save', 'saveAs', '-', 'import', 'export', '-', 'pageSetup', 'print'], parent);
  502. })));
  503. this.put('edit', new Menu(mxUtils.bind(this, function(menu, parent)
  504. {
  505. this.addMenuItems(menu, ['undo', 'redo', '-', 'cut', 'copy', 'paste', 'delete', '-', 'duplicate', '-',
  506. 'editData', 'editTooltip', '-', 'editStyle', '-', 'edit', '-', 'editLink', 'openLink', '-',
  507. 'selectVertices', 'selectEdges', 'selectAll', 'selectNone', '-', 'lockUnlock']);
  508. })));
  509. this.put('extras', new Menu(mxUtils.bind(this, function(menu, parent)
  510. {
  511. this.addMenuItems(menu, ['copyConnect', 'collapseExpand', '-', 'editDiagram']);
  512. })));
  513. this.put('help', new Menu(mxUtils.bind(this, function(menu, parent)
  514. {
  515. this.addMenuItems(menu, ['help', '-', 'about']);
  516. })));
  517. };
  518. /**
  519. * Adds the label menu items to the given menu and parent.
  520. */
  521. Menus.prototype.put = function(name, menu)
  522. {
  523. this.menus[name] = menu;
  524. return menu;
  525. };
  526. /**
  527. * Adds the label menu items to the given menu and parent.
  528. */
  529. Menus.prototype.get = function(name)
  530. {
  531. return this.menus[name];
  532. };
  533. /**
  534. * Adds the given submenu.
  535. */
  536. Menus.prototype.addSubmenu = function(name, menu, parent, label)
  537. {
  538. var entry = this.get(name);
  539. var submenu = null;
  540. if (entry != null)
  541. {
  542. var enabled = entry.isEnabled();
  543. if (menu.showDisabled || enabled)
  544. {
  545. submenu = menu.addItem(label || mxResources.get(name),
  546. null, null, parent, null, enabled);
  547. this.addMenu(name, menu, submenu);
  548. }
  549. }
  550. return submenu;
  551. };
  552. /**
  553. * Adds the label menu items to the given menu and parent.
  554. */
  555. Menus.prototype.addMenu = function(name, popupMenu, parent)
  556. {
  557. var menu = this.get(name);
  558. if (menu != null && (popupMenu.showDisabled || menu.isEnabled()))
  559. {
  560. menu.execute(popupMenu, parent);
  561. }
  562. };
  563. /**
  564. * Adds a menu item to insert a table cell.
  565. */
  566. Menus.prototype.addInsertTableCellItem = function(menu, parent)
  567. {
  568. var graph = this.editorUi.editor.graph;
  569. var cell = graph.getSelectionCell();
  570. var style = graph.getCurrentCellStyle(cell);
  571. if (graph.getSelectionCount() > 1)
  572. {
  573. if (graph.isTableCell(cell))
  574. {
  575. cell = graph.model.getParent(cell);
  576. }
  577. if (graph.isTableRow(cell))
  578. {
  579. cell = graph.model.getParent(cell);
  580. }
  581. }
  582. var isTable = graph.isTable(cell) ||
  583. graph.isTableRow(cell) ||
  584. graph.isTableCell(cell);
  585. var isStack = graph.isStack(cell) ||
  586. graph.isStackChild(cell);
  587. var showCols = isTable;
  588. var showRows = isTable;
  589. if (isStack)
  590. {
  591. var style = (graph.isStack(cell)) ? style :
  592. graph.getCellStyle(graph.model.getParent(cell));
  593. showRows = style['horizontalStack'] == '0';
  594. showCols = !showRows;
  595. }
  596. if (parent != null || (!isTable && !isStack))
  597. {
  598. this.addInsertTableItem(menu, mxUtils.bind(this, function(evt, rows, cols, title, container)
  599. {
  600. var table = (container || mxEvent.isControlDown(evt) || mxEvent.isMetaDown(evt)) ?
  601. graph.createCrossFunctionalSwimlane(rows, cols, null, null,
  602. (title || mxEvent.isShiftDown(evt)) ? 'Cross-Functional Flowchart' : null) :
  603. graph.createTable(rows, cols, null, null,
  604. (title || mxEvent.isShiftDown(evt)) ? 'Table' : null);
  605. var pt = (mxEvent.isAltDown(evt)) ? graph.getFreeInsertPoint() :
  606. graph.getCenterInsertPoint(graph.getBoundingBoxFromGeometry([table], true));
  607. var select = null;
  608. graph.getModel().beginUpdate();
  609. try
  610. {
  611. select = graph.importCells([table], pt.x, pt.y);
  612. graph.fireEvent(new mxEventObject('cellsInserted', 'cells',
  613. graph.model.getDescendants(select[0])));
  614. }
  615. finally
  616. {
  617. graph.getModel().endUpdate();
  618. }
  619. if (select != null && select.length > 0)
  620. {
  621. graph.scrollCellToVisible(select[0]);
  622. graph.setSelectionCells(select);
  623. }
  624. }), parent);
  625. }
  626. else
  627. {
  628. if (showCols)
  629. {
  630. var elt = menu.addItem(mxResources.get('insertColumnBefore'), null, mxUtils.bind(this, function()
  631. {
  632. try
  633. {
  634. if (isStack)
  635. {
  636. graph.insertLane(cell, true);
  637. }
  638. else
  639. {
  640. graph.insertTableColumn(cell, true);
  641. }
  642. }
  643. catch (e)
  644. {
  645. this.editorUi.handleError(e);
  646. }
  647. }), null, 'geIcon geSprite geSprite-insertcolumnbefore');
  648. elt.setAttribute('title', mxResources.get('insertColumnBefore'));
  649. elt = menu.addItem(mxResources.get('insertColumnAfter'), null, mxUtils.bind(this, function()
  650. {
  651. try
  652. {
  653. if (isStack)
  654. {
  655. graph.insertLane(cell, false);
  656. }
  657. else
  658. {
  659. graph.insertTableColumn(cell, false);
  660. }
  661. }
  662. catch (e)
  663. {
  664. this.editorUi.handleError(e);
  665. }
  666. }), null, 'geIcon geSprite geSprite-insertcolumnafter');
  667. elt.setAttribute('title', mxResources.get('insertColumnAfter'));
  668. elt = menu.addItem(mxResources.get('deleteColumn'), null, mxUtils.bind(this, function()
  669. {
  670. if (cell != null)
  671. {
  672. try
  673. {
  674. if (isStack)
  675. {
  676. graph.deleteLane(cell);
  677. }
  678. else
  679. {
  680. graph.deleteTableColumn(cell);
  681. }
  682. }
  683. catch (e)
  684. {
  685. this.editorUi.handleError(e);
  686. }
  687. }
  688. }), null, 'geIcon geSprite geSprite-deletecolumn');
  689. elt.setAttribute('title', mxResources.get('deleteColumn'));
  690. }
  691. if (showRows)
  692. {
  693. elt = menu.addItem(mxResources.get('insertRowBefore'), null, mxUtils.bind(this, function()
  694. {
  695. try
  696. {
  697. if (isStack)
  698. {
  699. graph.insertLane(cell, true);
  700. }
  701. else
  702. {
  703. graph.insertTableRow(cell, true);
  704. }
  705. }
  706. catch (e)
  707. {
  708. this.editorUi.handleError(e);
  709. }
  710. }), null, 'geIcon geSprite geSprite-insertrowbefore');
  711. elt.setAttribute('title', mxResources.get('insertRowBefore'));
  712. elt = menu.addItem(mxResources.get('insertRowAfter'), null, mxUtils.bind(this, function()
  713. {
  714. try
  715. {
  716. if (isStack)
  717. {
  718. graph.insertLane(cell, false);
  719. }
  720. else
  721. {
  722. graph.insertTableRow(cell, false);
  723. }
  724. }
  725. catch (e)
  726. {
  727. this.editorUi.handleError(e);
  728. }
  729. }), null, 'geIcon geSprite geSprite-insertrowafter');
  730. elt.setAttribute('title', mxResources.get('insertRowAfter'));
  731. elt = menu.addItem(mxResources.get('deleteRow'), null, mxUtils.bind(this, function()
  732. {
  733. try
  734. {
  735. if (isStack)
  736. {
  737. graph.deleteLane(cell);
  738. }
  739. else
  740. {
  741. graph.deleteTableRow(cell);
  742. }
  743. }
  744. catch (e)
  745. {
  746. this.editorUi.handleError(e);
  747. }
  748. }), null, 'geIcon geSprite geSprite-deleterow');
  749. elt.setAttribute('title', mxResources.get('deleteRow'));
  750. var ss = this.editorUi.getSelectionState();
  751. if (ss.mergeCell != null)
  752. {
  753. this.addMenuItem(menu, 'mergeCells');
  754. }
  755. else if (ss.style['colspan'] > 1 || ss.style['rowspan'] > 1)
  756. {
  757. this.addMenuItem(menu, 'unmergeCells');
  758. }
  759. }
  760. }
  761. };
  762. /**
  763. * Adds a menu item to insert a table.
  764. */
  765. Menus.prototype.addInsertTableItem = function(menu, insertFn, parent, showOptions)
  766. {
  767. showOptions = (showOptions != null) ? showOptions : true;
  768. insertFn = (insertFn != null) ? insertFn : mxUtils.bind(this, function(evt, rows, cols)
  769. {
  770. var graph = this.editorUi.editor.graph;
  771. var td = graph.getParentByName(mxEvent.getSource(evt), 'TD');
  772. if (td != null && graph.cellEditor.textarea != null)
  773. {
  774. // To find the new link, we create a list of all existing links first
  775. // LATER: Refactor for reuse with code for finding inserted image below
  776. var tmp = graph.cellEditor.textarea.getElementsByTagName('table');
  777. var oldTables = [];
  778. for (var i = 0; i < tmp.length; i++)
  779. {
  780. oldTables.push(tmp[i]);
  781. }
  782. // Finding the new table will work with insertHTML, but IE does not support that
  783. graph.container.focus();
  784. graph.pasteHtmlAtCaret(createTable(rows, cols));
  785. // Moves cursor to first table cell
  786. var newTables = graph.cellEditor.textarea.getElementsByTagName('table');
  787. if (newTables.length == oldTables.length + 1)
  788. {
  789. // Inverse order in favor of appended tables
  790. for (var i = newTables.length - 1; i >= 0; i--)
  791. {
  792. if (i == 0 || newTables[i] != oldTables[i - 1])
  793. {
  794. graph.selectNode(newTables[i].rows[0].cells[0]);
  795. break;
  796. }
  797. }
  798. }
  799. }
  800. });
  801. // KNOWN: Does not work in IE8 standards and quirks
  802. var graph = this.editorUi.editor.graph;
  803. var row2 = null;
  804. var td = null;
  805. function createTable(rows, cols)
  806. {
  807. var html = ['<table>'];
  808. for (var i = 0; i < rows; i++)
  809. {
  810. html.push('<tr>');
  811. for (var j = 0; j < cols; j++)
  812. {
  813. html.push('<td><br></td>');
  814. }
  815. html.push('</tr>');
  816. }
  817. html.push('</table>');
  818. return html.join('');
  819. };
  820. if (parent == null)
  821. {
  822. menu.div.className += ' geToolbarMenu';
  823. menu.labels = false;
  824. }
  825. var elt2 = menu.addItem('', null, null, parent, null, null, null, true);
  826. elt2.firstChild.style.fontSize = Menus.prototype.defaultFontSize + 'px';
  827. // Hide empty rows in menu item
  828. var tds = elt2.getElementsByTagName('td');
  829. if (tds.length > 1)
  830. {
  831. tds[1].style.display = 'none';
  832. tds[2].style.display = 'none';
  833. }
  834. function createPicker(rows, cols)
  835. {
  836. var table2 = document.createElement('table');
  837. table2.className = 'geInsertTablePicker';
  838. table2.setAttribute('border', '1');
  839. table2.style.borderCollapse = 'collapse';
  840. table2.style.borderStyle = 'solid';
  841. table2.style.marginBottom = '4px';
  842. table2.style.marginTop = '8px';
  843. table2.setAttribute('cellPadding', '8');
  844. for (var i = 0; i < rows; i++)
  845. {
  846. var row = table2.insertRow(i);
  847. for (var j = 0; j < cols; j++)
  848. {
  849. var cell = row.insertCell(-1);
  850. }
  851. }
  852. return table2;
  853. };
  854. function extendPicker(picker, rows, cols)
  855. {
  856. for (var i = picker.rows.length; i < rows; i++)
  857. {
  858. var row = picker.insertRow(i);
  859. for (var j = 0; j < picker.rows[0].cells.length; j++)
  860. {
  861. var cell = row.insertCell(-1);
  862. }
  863. }
  864. for (var i = 0; i < picker.rows.length; i++)
  865. {
  866. var row = picker.rows[i];
  867. for (var j = row.cells.length; j < cols; j++)
  868. {
  869. var cell = row.insertCell(-1);
  870. }
  871. }
  872. };
  873. elt2.firstChild.innerText = '';
  874. var titleOption = document.createElement('input');
  875. titleOption.setAttribute('id', 'geTitleOption');
  876. titleOption.setAttribute('type', 'checkbox');
  877. titleOption.style.verticalAlign = 'middle';
  878. var titleLbl = document.createElement('label');
  879. mxUtils.write(titleLbl, mxResources.get('title'));
  880. titleLbl.setAttribute('for', 'geTitleOption');
  881. titleLbl.style.verticalAlign = 'middle';
  882. mxEvent.addGestureListeners(titleLbl, null, null, mxUtils.bind(this, function(e)
  883. {
  884. mxEvent.consume(e);
  885. }));
  886. mxEvent.addGestureListeners(titleOption, null, null, mxUtils.bind(this, function(e)
  887. {
  888. mxEvent.consume(e);
  889. }));
  890. var containerOption = document.createElement('input');
  891. containerOption.setAttribute('id', 'geContainerOption');
  892. containerOption.setAttribute('type', 'checkbox');
  893. containerOption.style.verticalAlign = 'middle';
  894. var containerLbl = document.createElement('label');
  895. mxUtils.write(containerLbl, mxResources.get('container'));
  896. containerLbl.setAttribute('for', 'geContainerOption');
  897. containerLbl.style.verticalAlign = 'middle';
  898. mxEvent.addGestureListeners(containerLbl, null, null, mxUtils.bind(this, function(e)
  899. {
  900. mxEvent.consume(e);
  901. }));
  902. mxEvent.addGestureListeners(containerOption, null, null, mxUtils.bind(this, function(e)
  903. {
  904. mxEvent.consume(e);
  905. }));
  906. if (showOptions)
  907. {
  908. elt2.firstChild.appendChild(titleOption);
  909. elt2.firstChild.appendChild(titleLbl);
  910. mxUtils.br(elt2.firstChild);
  911. elt2.firstChild.appendChild(containerOption);
  912. elt2.firstChild.appendChild(containerLbl);
  913. mxUtils.br(elt2.firstChild);
  914. }
  915. var picker = createPicker(5, 5);
  916. elt2.firstChild.appendChild(picker);
  917. var label = document.createElement('div');
  918. label.style.textAlign = 'center';
  919. label.style.padding = '4px';
  920. label.style.width = '100%';
  921. label.innerHTML = '1x1';
  922. elt2.firstChild.appendChild(label);
  923. function mouseover(e)
  924. {
  925. td = graph.getParentByName(mxEvent.getSource(e), 'TD');
  926. var selected = false;
  927. if (td != null)
  928. {
  929. row2 = graph.getParentByName(td, 'TR');
  930. var ext = (mxEvent.isMouseEvent(e)) ? 2 : 4;
  931. extendPicker(picker, Math.min(20, row2.sectionRowIndex + ext), Math.min(20, td.cellIndex + ext));
  932. label.innerHTML = (td.cellIndex + 1) + 'x' + (row2.sectionRowIndex + 1);
  933. for (var i = 0; i < picker.rows.length; i++)
  934. {
  935. var r = picker.rows[i];
  936. for (var j = 0; j < r.cells.length; j++)
  937. {
  938. var cell = r.cells[j];
  939. if (i == row2.sectionRowIndex &&
  940. j == td.cellIndex)
  941. {
  942. selected = cell.style.backgroundColor == 'blue';
  943. }
  944. if (i <= row2.sectionRowIndex && j <= td.cellIndex)
  945. {
  946. cell.style.backgroundColor = 'blue';
  947. }
  948. else
  949. {
  950. cell.style.backgroundColor = 'transparent';
  951. }
  952. }
  953. }
  954. }
  955. mxEvent.consume(e);
  956. return selected;
  957. };
  958. mxEvent.addGestureListeners(picker, null, null, mxUtils.bind(this, function(e)
  959. {
  960. var selected = mouseover(e);
  961. if (td != null && row2 != null && selected)
  962. {
  963. insertFn(e, row2.sectionRowIndex + 1, td.cellIndex + 1,
  964. titleOption.checked, containerOption.checked);
  965. // Async required to block event for elements under menu
  966. window.setTimeout(mxUtils.bind(this, function()
  967. {
  968. this.editorUi.hideCurrentMenu();
  969. }), 0);
  970. }
  971. }));
  972. mxEvent.addListener(picker, 'mouseover', mouseover);
  973. };
  974. /**
  975. * Adds a style change item to the given menu.
  976. */
  977. Menus.prototype.edgeStyleChange = function(menu, label, keys, values, sprite, parent, reset, image)
  978. {
  979. return this.showIconOnly(menu.addItem(label, image, mxUtils.bind(this, function()
  980. {
  981. var graph = this.editorUi.editor.graph;
  982. graph.stopEditing(false);
  983. graph.getModel().beginUpdate();
  984. try
  985. {
  986. var cells = graph.getSelectionCells();
  987. var edges = [];
  988. for (var i = 0; i < cells.length; i++)
  989. {
  990. var cell = cells[i];
  991. if (graph.getModel().isEdge(cell))
  992. {
  993. if (reset)
  994. {
  995. var geo = graph.getCellGeometry(cell);
  996. // Resets all edge points
  997. if (geo != null)
  998. {
  999. geo = geo.clone();
  1000. geo.points = null;
  1001. graph.getModel().setGeometry(cell, geo);
  1002. }
  1003. }
  1004. for (var j = 0; j < keys.length; j++)
  1005. {
  1006. graph.setCellStyles(keys[j], values[j], [cell]);
  1007. }
  1008. edges.push(cell);
  1009. }
  1010. }
  1011. this.editorUi.fireEvent(new mxEventObject(
  1012. 'styleChanged', 'cells', edges,
  1013. 'keys', keys, 'values', values,
  1014. 'force', cells.length == 0));
  1015. }
  1016. finally
  1017. {
  1018. graph.getModel().endUpdate();
  1019. }
  1020. }), parent, sprite));
  1021. };
  1022. /**
  1023. * Adds a style change item to the given menu.
  1024. */
  1025. Menus.prototype.showIconOnly = function(elt)
  1026. {
  1027. var td = elt.getElementsByTagName('td');
  1028. for (i = 0; i < td.length; i++)
  1029. {
  1030. if (td[i].getAttribute('class') == 'mxPopupMenuItem')
  1031. {
  1032. td[i].style.display = 'none';
  1033. }
  1034. }
  1035. return elt;
  1036. };
  1037. /**
  1038. * Adds a style change item to the given menu.
  1039. */
  1040. Menus.prototype.styleChange = function(menu, label, keys, values, sprite, parent, fn, post, iconOnly)
  1041. {
  1042. var apply = this.createStyleChangeFunction(keys, values);
  1043. var elt = menu.addItem(label, null, mxUtils.bind(this, function()
  1044. {
  1045. var graph = this.editorUi.editor.graph;
  1046. if (fn != null && graph.cellEditor.isContentEditing())
  1047. {
  1048. fn();
  1049. }
  1050. else
  1051. {
  1052. apply(post);
  1053. }
  1054. }), parent, sprite);
  1055. if (iconOnly)
  1056. {
  1057. this.showIconOnly(elt);
  1058. }
  1059. return elt;
  1060. };
  1061. /**
  1062. *
  1063. */
  1064. Menus.prototype.createStyleChangeFunction = function(keys, values)
  1065. {
  1066. return mxUtils.bind(this, function(post)
  1067. {
  1068. var graph = this.editorUi.editor.graph;
  1069. graph.stopEditing(false);
  1070. graph.getModel().beginUpdate();
  1071. try
  1072. {
  1073. var cells = graph.getEditableCells(graph.getSelectionCells());
  1074. var autoSizeCells = false;
  1075. for (var i = 0; i < keys.length; i++)
  1076. {
  1077. graph.setCellStyles(keys[i], values[i], cells);
  1078. // Removes CSS alignment to produce consistent output
  1079. if (keys[i] == mxConstants.STYLE_ALIGN)
  1080. {
  1081. graph.updateLabelElements(cells, function(elt)
  1082. {
  1083. elt.removeAttribute('align');
  1084. elt.style.textAlign = null;
  1085. });
  1086. }
  1087. // Updates autosize after font changes
  1088. if (keys[i] == mxConstants.STYLE_FONTFAMILY ||
  1089. keys[i] == 'fontSource')
  1090. {
  1091. autoSizeCells = true;
  1092. }
  1093. }
  1094. if (autoSizeCells)
  1095. {
  1096. for (var j = 0; j < cells.length; j++)
  1097. {
  1098. if (graph.model.getChildCount(cells[j]) == 0)
  1099. {
  1100. graph.autoSizeCell(cells[j], false);
  1101. }
  1102. }
  1103. }
  1104. if (post != null)
  1105. {
  1106. post();
  1107. }
  1108. this.editorUi.fireEvent(new mxEventObject('styleChanged',
  1109. 'keys', keys, 'values', values, 'cells', cells));
  1110. }
  1111. finally
  1112. {
  1113. graph.getModel().endUpdate();
  1114. }
  1115. });
  1116. };
  1117. /**
  1118. * Adds a style change item with a prompt to the given menu.
  1119. */
  1120. Menus.prototype.promptChange = function(menu, label, hint, defaultValue, key, parent, enabled, fn, sprite, beforeFn)
  1121. {
  1122. return menu.addItem(label, null, mxUtils.bind(this, function()
  1123. {
  1124. var graph = this.editorUi.editor.graph;
  1125. var value = defaultValue;
  1126. var state = graph.getView().getState(graph.getSelectionCell());
  1127. if (state != null)
  1128. {
  1129. value = state.style[key] || value;
  1130. }
  1131. var doStopEditing = (beforeFn != null) ? beforeFn() : true;
  1132. var dlg = new FilenameDialog(this.editorUi, value, mxResources.get('apply'), mxUtils.bind(this, function(newValue)
  1133. {
  1134. if (newValue != null && newValue.length > 0)
  1135. {
  1136. if (doStopEditing)
  1137. {
  1138. graph.getModel().beginUpdate();
  1139. try
  1140. {
  1141. graph.stopEditing(false);
  1142. graph.setCellStyles(key, newValue);
  1143. }
  1144. finally
  1145. {
  1146. graph.getModel().endUpdate();
  1147. }
  1148. }
  1149. if (fn != null)
  1150. {
  1151. fn(newValue);
  1152. }
  1153. }
  1154. }), mxResources.get('enterValue') + ((hint.length > 0) ? (' ' + hint) : ''),
  1155. null, null, null, null, function()
  1156. {
  1157. if (fn != null && beforeFn != null)
  1158. {
  1159. fn(null);
  1160. }
  1161. });
  1162. this.editorUi.showDialog(dlg.container, 300, 80, true, true);
  1163. dlg.init();
  1164. }), parent, sprite, enabled);
  1165. };
  1166. /**
  1167. * Adds a handler for showing a menu in the given element.
  1168. */
  1169. Menus.prototype.pickColor = function(key, cmd, defaultValue)
  1170. {
  1171. var ui = this.editorUi;
  1172. ui.tryAndHandle(mxUtils.bind(this, function()
  1173. {
  1174. var graph = ui.editor.graph;
  1175. var h = 226 + ((Math.ceil(ColorDialog.prototype.presetColors.length / 12) +
  1176. Math.ceil(ColorDialog.prototype.defaultColors.length / 12)) * 17);
  1177. if (cmd != null && graph.cellEditor.isContentEditing())
  1178. {
  1179. // Saves and restores text selection for in-place editor
  1180. var selState = graph.cellEditor.saveSelection();
  1181. var dlg = new ColorDialog(this.editorUi, defaultValue || graph.shapeForegroundColor, mxUtils.bind(this, function(color)
  1182. {
  1183. graph.cellEditor.restoreSelection(selState);
  1184. document.execCommand(cmd, false, (color != mxConstants.NONE) ? color : 'transparent');
  1185. var cmdMapping = {
  1186. 'forecolor': mxConstants.STYLE_FONTCOLOR,
  1187. 'backcolor': mxConstants.STYLE_LABEL_BACKGROUNDCOLOR
  1188. };
  1189. var style = cmdMapping[cmd];
  1190. if (style != null)
  1191. {
  1192. ui.fireEvent(new mxEventObject('styleChanged',
  1193. 'keys', [style], 'values', [color],
  1194. 'cells', [graph.cellEditor.getEditingCell()]));
  1195. }
  1196. }), function()
  1197. {
  1198. graph.cellEditor.restoreSelection(selState);
  1199. });
  1200. this.editorUi.showDialog(dlg.container, 230, h, true, true);
  1201. dlg.init();
  1202. }
  1203. else
  1204. {
  1205. if (this.colorDialog == null)
  1206. {
  1207. this.colorDialog = new ColorDialog(this.editorUi);
  1208. }
  1209. this.colorDialog.currentColorKey = key;
  1210. var state = graph.getView().getState(graph.getSelectionCell());
  1211. var color = mxConstants.NONE;
  1212. if (state != null)
  1213. {
  1214. color = state.style[key] || color;
  1215. }
  1216. if (color == mxConstants.NONE)
  1217. {
  1218. color = graph.shapeBackgroundColor.substring(1);
  1219. this.colorDialog.picker.fromString(color);
  1220. this.colorDialog.colorInput.value = mxConstants.NONE;
  1221. }
  1222. else
  1223. {
  1224. this.colorDialog.picker.fromString(mxUtils.rgba2hex(color));
  1225. }
  1226. this.editorUi.showDialog(this.colorDialog.container, 230, h, true, true);
  1227. this.colorDialog.init();
  1228. }
  1229. }));
  1230. };
  1231. /**
  1232. * Adds a handler for showing a menu in the given element.
  1233. */
  1234. Menus.prototype.toggleStyle = function(key, defaultValue)
  1235. {
  1236. var graph = this.editorUi.editor.graph;
  1237. var value = graph.toggleCellStyles(key, defaultValue);
  1238. this.editorUi.fireEvent(new mxEventObject('styleChanged', 'keys', [key], 'values', [value],
  1239. 'cells', graph.getSelectionCells()));
  1240. };
  1241. /**
  1242. * Creates the keyboard event handler for the current graph and history.
  1243. */
  1244. Menus.prototype.addMenuItem = function(menu, key, parent, trigger, sprite, label)
  1245. {
  1246. var action = this.editorUi.actions.get(key);
  1247. if (action != null && (menu.showDisabled || action.isEnabled()) && action.visible)
  1248. {
  1249. var item = menu.addItem(label || action.label, null, mxUtils.bind(this, function(evt)
  1250. {
  1251. try
  1252. {
  1253. action.funct(trigger, evt);
  1254. }
  1255. catch (e)
  1256. {
  1257. this.editorUi.handleError(e);
  1258. }
  1259. }), parent, sprite, action.isEnabled());
  1260. // Adds checkmark image
  1261. if (action.toggleAction && action.isSelected())
  1262. {
  1263. menu.addCheckmark(item, Editor.checkmarkImage);
  1264. }
  1265. this.addShortcut(item, action, menu.hideShortcuts);
  1266. return item;
  1267. }
  1268. return null;
  1269. };
  1270. /**
  1271. * Adds a checkmark to the given menuitem.
  1272. */
  1273. Menus.prototype.addShortcut = function(item, action, asTooltip)
  1274. {
  1275. if (action.shortcut != null)
  1276. {
  1277. if (asTooltip)
  1278. {
  1279. item.setAttribute('title', action.shortcut);
  1280. }
  1281. else
  1282. {
  1283. var td = item.firstChild.nextSibling.nextSibling;
  1284. var span = document.createElement('span');
  1285. span.style.color = 'gray';
  1286. mxUtils.write(span, action.shortcut);
  1287. td.appendChild(span);
  1288. }
  1289. }
  1290. };
  1291. /**
  1292. * Creates the keyboard event handler for the current graph and history.
  1293. */
  1294. Menus.prototype.addMenuItems = function(menu, keys, parent, trigger, sprites)
  1295. {
  1296. for (var i = 0; i < keys.length; i++)
  1297. {
  1298. if (keys[i] == '-')
  1299. {
  1300. menu.addSeparator(parent);
  1301. }
  1302. else
  1303. {
  1304. this.addMenuItem(menu, keys[i], parent, trigger, (sprites != null) ? sprites[i] : null);
  1305. }
  1306. }
  1307. };
  1308. /**
  1309. * Creates the keyboard event handler for the current graph and history.
  1310. */
  1311. Menus.prototype.createPopupMenu = function(menu, cell, evt)
  1312. {
  1313. menu.smartSeparators = true;
  1314. menu.hideShortcuts = true;
  1315. this.addPopupMenuItems(menu, cell, evt);
  1316. };
  1317. /**
  1318. * Creates the keyboard event handler for the current graph and history.
  1319. */
  1320. Menus.prototype.addPopupMenuItems = function(menu, cell, evt)
  1321. {
  1322. if (this.isShowHistoryItems())
  1323. {
  1324. this.addPopupMenuHistoryItems(menu, cell, evt);
  1325. }
  1326. this.addPopupMenuEditItems(menu, cell, evt);
  1327. if (this.isShowStyleItems())
  1328. {
  1329. this.addPopupMenuStyleItems(menu, cell, evt);
  1330. }
  1331. if (this.isShowArrangeItems())
  1332. {
  1333. this.addPopupMenuArrangeItems(menu, cell, evt);
  1334. }
  1335. this.addPopupMenuCellItems(menu, cell, evt);
  1336. this.addPopupMenuSelectionItems(menu, cell, evt);
  1337. };
  1338. /**
  1339. * Creates the keyboard event handler for the current graph and history.
  1340. */
  1341. Menus.prototype.isShowHistoryItems = function()
  1342. {
  1343. return true;
  1344. };
  1345. /**
  1346. * Creates the keyboard event handler for the current graph and history.
  1347. */
  1348. Menus.prototype.addPopupMenuHistoryItems = function(menu, cell, evt)
  1349. {
  1350. if (this.editorUi.editor.graph.isSelectionEmpty())
  1351. {
  1352. this.addMenuItems(menu, ['undo', 'redo'], null, evt);
  1353. }
  1354. };
  1355. /**
  1356. * Creates the keyboard event handler for the current graph and history.
  1357. */
  1358. Menus.prototype.addPopupDeleteItem = function(menu, cell, evt)
  1359. {
  1360. var item = this.addMenuItem(menu, 'delete');
  1361. if (item != null && item.firstChild != null &&
  1362. item.firstChild.nextSibling != null)
  1363. {
  1364. item.firstChild.nextSibling.style.color = 'red';
  1365. var graph = this.editorUi.editor.graph;
  1366. if (graph.getSelectionCount() > 1)
  1367. {
  1368. item.firstChild.nextSibling.innerHTML =
  1369. mxUtils.htmlEntities(mxResources.get('delete') +
  1370. ' (' + graph.getSelectionCount() + ')');
  1371. }
  1372. }
  1373. };
  1374. /**
  1375. * Creates the keyboard event handler for the current graph and history.
  1376. */
  1377. Menus.prototype.addPopupMenuEditItems = function(menu, cell, evt)
  1378. {
  1379. if (this.editorUi.editor.graph.isSelectionEmpty())
  1380. {
  1381. this.addMenuItems(menu, ['pasteHere'], null, evt);
  1382. }
  1383. else
  1384. {
  1385. this.addMenuItems(menu, ['cut', 'copy', 'duplicate',
  1386. '-', 'delete', 'lockUnlock'], null, evt);
  1387. }
  1388. };
  1389. /**
  1390. * Creates the keyboard event handler for the current graph and history.
  1391. */
  1392. Menus.prototype.isShowStyleItems = function()
  1393. {
  1394. return true;
  1395. };
  1396. /**
  1397. * Creates the keyboard event handler for the current graph and history.
  1398. */
  1399. Menus.prototype.addPopupMenuStyleItems = function(menu, cell, evt)
  1400. {
  1401. if (this.editorUi.editor.graph.getSelectionCount() == 1)
  1402. {
  1403. this.addMenuItems(menu, ['-', 'setAsDefaultStyle'], null, evt);
  1404. }
  1405. else if (this.editorUi.editor.graph.isSelectionEmpty())
  1406. {
  1407. this.addMenuItems(menu, ['-', 'clearDefaultStyle'], null, evt);
  1408. }
  1409. };
  1410. /**
  1411. * Creates the keyboard event handler for the current graph and history.
  1412. */
  1413. Menus.prototype.isShowArrangeItems = function()
  1414. {
  1415. return true;
  1416. };
  1417. /**
  1418. * Creates the keyboard event handler for the current graph and history.
  1419. */
  1420. Menus.prototype.addPopupMenuArrangeItems = function(menu, cell, evt)
  1421. {
  1422. var graph = this.editorUi.editor.graph;
  1423. // Shows group actions
  1424. if (graph.getSelectionCount() > 1 || (graph.getSelectionCount() > 0 &&
  1425. !graph.getModel().isEdge(cell) && !graph.isSwimlane(cell) &&
  1426. graph.getModel().getChildCount(cell) > 0 && graph.isCellEditable(cell)))
  1427. {
  1428. this.addMenuItems(menu, ['group', 'ungroup'], null, evt);
  1429. }
  1430. var count = graph.getEditableCells(graph.getSelectionCells()).length;
  1431. if (count > 1)
  1432. {
  1433. menu.addSeparator();
  1434. this.addSubmenu('align', menu);
  1435. this.addSubmenu('distribute', menu);
  1436. }
  1437. if (count >= 1)
  1438. {
  1439. this.addMenuItems(menu, ['-', 'toFront', 'toBack'], null, evt);
  1440. if (this.isShowCellEditItems() && graph.getSelectionCount() == 1)
  1441. {
  1442. this.addMenuItems(menu, ['bringForward', 'sendBackward'], null, evt);
  1443. }
  1444. }
  1445. };
  1446. /**
  1447. * Creates the keyboard event handler for the current graph and history.
  1448. */
  1449. Menus.prototype.addPopupMenuCellItems = function(menu, cell, evt)
  1450. {
  1451. var graph = this.editorUi.editor.graph;
  1452. var state = graph.view.getState(cell);
  1453. if (state != null)
  1454. {
  1455. // Adds reset waypoints option if waypoints exist
  1456. var geo = graph.getModel().getGeometry(cell);
  1457. var hasWaypoints = geo != null && geo.points != null && geo.points.length > 0;
  1458. if (this.isShowStyleItems() && graph.getSelectionCount() == 1 &&
  1459. graph.getModel().isEdge(cell))
  1460. {
  1461. menu.addSeparator();
  1462. this.addSubmenu('line', menu);
  1463. }
  1464. if (graph.getModel().isEdge(cell) && mxUtils.getValue(state.style, mxConstants.STYLE_EDGE, null) != 'entityRelationEdgeStyle' &&
  1465. mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null) != 'arrow')
  1466. {
  1467. var handler = graph.selectionCellsHandler.getHandler(cell);
  1468. var isWaypoint = false;
  1469. if (hasWaypoints && handler instanceof mxEdgeHandler && handler.bends != null && handler.bends.length > 2)
  1470. {
  1471. var index = handler.getHandleForEvent(graph.updateMouseEvent(new mxMouseEvent(evt)));
  1472. // Ignores ghosted and virtual waypoints
  1473. if (index > 0 && index < handler.bends.length - 1 &&
  1474. (handler.bends[index] == null ||
  1475. handler.bends[index].node == null ||
  1476. handler.bends[index].node.style.opacity == ''))
  1477. {
  1478. // Configures removeWaypoint action before execution
  1479. // Using trigger parameter is cleaner but have to find waypoint here anyway.
  1480. var rmWaypointAction = this.editorUi.actions.get('removeWaypoint');
  1481. rmWaypointAction.handler = handler;
  1482. rmWaypointAction.index = index;
  1483. isWaypoint = true;
  1484. }
  1485. }
  1486. if (this.isShowCellEditItems())
  1487. {
  1488. this.addMenuItem(menu, 'turn', null, evt, null, mxResources.get('reverse'));
  1489. }
  1490. this.addMenuItems(menu, [(isWaypoint) ? 'removeWaypoint' : 'addWaypoint'], null, evt);
  1491. }
  1492. if (graph.getSelectionCount() == 1 && this.isShowCellEditItems() &&
  1493. (hasWaypoints || (graph.getModel().isVertex(cell) &&
  1494. graph.getModel().getEdgeCount(cell) > 0)))
  1495. {
  1496. this.addMenuItems(menu, ['clearWaypoints'], null, evt);
  1497. }
  1498. if (this.isShowCellEditItems() &&
  1499. graph.getSelectionCount() == 1 &&
  1500. graph.isCellEditable(cell))
  1501. {
  1502. this.addPopupMenuCellEditItems(menu, cell, evt);
  1503. }
  1504. }
  1505. };
  1506. /**
  1507. * Creates the keyboard event handler for the current graph and history.
  1508. */
  1509. Menus.prototype.isShowCellEditItems = function()
  1510. {
  1511. return true;
  1512. };
  1513. /**
  1514. * Creates the keyboard event handler for the current graph and history.
  1515. */
  1516. Menus.prototype.addPopupMenuCellEditItems = function(menu, cell, evt, parent)
  1517. {
  1518. var graph = this.editorUi.editor.graph;
  1519. var state = graph.view.getState(cell);
  1520. this.addMenuItems(menu, ['-', 'editStyle', 'editData', 'editLink'], parent, evt);
  1521. // Shows edit image action if there is an image in the style
  1522. if (graph.getModel().isVertex(cell) && mxUtils.getValue(state.style, mxConstants.STYLE_IMAGE, null) != null)
  1523. {
  1524. menu.addSeparator();
  1525. this.addMenuItem(menu, 'image', parent, evt).firstChild.nextSibling.innerHTML = mxResources.get('editImage') + '...';
  1526. this.addMenuItem(menu, 'crop', parent, evt);
  1527. }
  1528. if (graph.getModel().isVertex(cell) && graph.isCellConnectable(cell))
  1529. {
  1530. this.addMenuItem(menu, 'editConnectionPoints', parent, evt);
  1531. }
  1532. };
  1533. /**
  1534. * Creates the keyboard event handler for the current graph and history.
  1535. */
  1536. Menus.prototype.addPopupMenuSelectionItems = function(menu, cell, evt)
  1537. {
  1538. if (this.editorUi.editor.graph.isSelectionEmpty())
  1539. {
  1540. this.addMenuItems(menu, ['-', 'selectAll', 'selectVertices', 'selectEdges'], null, evt);
  1541. }
  1542. };
  1543. /**
  1544. * Creates the keyboard event handler for the current graph and history.
  1545. */
  1546. Menus.prototype.createMenubar = function(container)
  1547. {
  1548. var menubar = new Menubar(this.editorUi, container);
  1549. var menus = this.defaultMenuItems;
  1550. for (var i = 0; i < menus.length; i++)
  1551. {
  1552. (mxUtils.bind(this, function(menu)
  1553. {
  1554. var elt = menubar.addMenu(mxResources.get(menus[i]), mxUtils.bind(this, function()
  1555. {
  1556. // Allows extensions of menu.funct
  1557. menu.funct.apply(this, arguments);
  1558. }));
  1559. this.menuCreated(menu, elt);
  1560. }))(this.get(menus[i]));
  1561. }
  1562. return menubar;
  1563. };
  1564. /**
  1565. * Creates the keyboard event handler for the current graph and history.
  1566. */
  1567. Menus.prototype.menuCreated = function(menu, elt, className)
  1568. {
  1569. if (elt != null)
  1570. {
  1571. className = (className != null) ? className : 'geItem';
  1572. elt.className = className;
  1573. menu.addListener('stateChanged', function()
  1574. {
  1575. elt.enabled = menu.enabled;
  1576. if (!menu.enabled)
  1577. {
  1578. elt.className = className + ' mxDisabled';
  1579. if (document.documentMode == 8)
  1580. {
  1581. elt.style.color = '#c3c3c3';
  1582. }
  1583. }
  1584. else
  1585. {
  1586. elt.className = className;
  1587. if (document.documentMode == 8)
  1588. {
  1589. elt.style.color = '';
  1590. }
  1591. }
  1592. });
  1593. }
  1594. };
  1595. /**
  1596. * Construcs a new menubar for the given editor.
  1597. */
  1598. function Menubar(editorUi, container)
  1599. {
  1600. this.editorUi = editorUi;
  1601. this.container = container;
  1602. };
  1603. /**
  1604. * Adds the menubar elements.
  1605. */
  1606. Menubar.prototype.hideMenu = function()
  1607. {
  1608. this.editorUi.hideCurrentMenu();
  1609. };
  1610. /**
  1611. * Adds a submenu to this menubar.
  1612. */
  1613. Menubar.prototype.addMenu = function(label, funct, before, clickFn)
  1614. {
  1615. var elt = document.createElement('a');
  1616. elt.className = 'geItem';
  1617. mxUtils.write(elt, label);
  1618. this.addMenuHandler(elt, funct, clickFn);
  1619. if (before != null)
  1620. {
  1621. this.container.insertBefore(elt, before);
  1622. }
  1623. else
  1624. {
  1625. this.container.appendChild(elt);
  1626. }
  1627. return elt;
  1628. };
  1629. /**
  1630. * Adds a handler for showing a menu in the given element.
  1631. */
  1632. Menubar.prototype.addMenuHandler = function(elt, funct, clickFn)
  1633. {
  1634. if (funct != null)
  1635. {
  1636. var show = true;
  1637. var clickHandler = mxUtils.bind(this, function(evt)
  1638. {
  1639. if (clickFn != null)
  1640. {
  1641. clickFn(evt);
  1642. }
  1643. if (!mxEvent.isConsumed(evt) && show &&
  1644. (elt.enabled == null || elt.enabled))
  1645. {
  1646. this.editorUi.editor.graph.popupMenuHandler.hideMenu();
  1647. var menu = new mxPopupMenu(funct);
  1648. menu.div.className += ' geMenubarMenu';
  1649. menu.smartSeparators = true;
  1650. menu.showDisabled = true;
  1651. menu.autoExpand = true;
  1652. // Disables autoexpand and destroys menu when hidden
  1653. menu.hideMenu = mxUtils.bind(this, function()
  1654. {
  1655. mxPopupMenu.prototype.hideMenu.apply(menu, arguments);
  1656. this.editorUi.resetCurrentMenu();
  1657. menu.destroy();
  1658. });
  1659. var offset = mxUtils.getOffset(elt);
  1660. menu.popup(offset.x, offset.y + elt.offsetHeight, null, evt);
  1661. this.editorUi.setCurrentMenu(menu, elt);
  1662. }
  1663. mxEvent.consume(evt);
  1664. });
  1665. // Shows menu automatically while in expanded state
  1666. mxEvent.addListener(elt, 'mousemove', mxUtils.bind(this, function(evt)
  1667. {
  1668. if (this.editorUi.menus.autoPopup && this.editorUi.currentMenu != null &&
  1669. this.editorUi.currentMenuElt != elt)
  1670. {
  1671. this.editorUi.hideCurrentMenu();
  1672. clickHandler(evt);
  1673. }
  1674. }));
  1675. // Hides menu if already showing and prevents focus
  1676. mxEvent.addListener(elt, (mxClient.IS_POINTER) ? 'pointerdown' : 'mousedown',
  1677. mxUtils.bind(this, function(evt)
  1678. {
  1679. if (!this.editorUi.menus.autoPopup && this.editorUi.currentMenu != null &&
  1680. this.editorUi.currentMenuElt != elt && mxEvent.isMouseEvent(evt))
  1681. {
  1682. this.editorUi.hideCurrentMenu();
  1683. }
  1684. show = this.editorUi.currentMenu == null;
  1685. evt.preventDefault();
  1686. }));
  1687. mxEvent.addListener(elt, 'click', mxUtils.bind(this, function(evt)
  1688. {
  1689. clickHandler(evt);
  1690. show = true;
  1691. }));
  1692. }
  1693. };
  1694. /**
  1695. * Creates the keyboard event handler for the current graph and history.
  1696. */
  1697. Menubar.prototype.destroy = function()
  1698. {
  1699. // do nothing
  1700. };
  1701. /**
  1702. * Constructs a new action for the given parameters.
  1703. */
  1704. function Menu(funct, enabled)
  1705. {
  1706. mxEventSource.call(this);
  1707. this.funct = funct;
  1708. this.enabled = (enabled != null) ? enabled : true;
  1709. };
  1710. // Menu inherits from mxEventSource
  1711. mxUtils.extend(Menu, mxEventSource);
  1712. /**
  1713. * Sets the enabled state of the action and fires a stateChanged event.
  1714. */
  1715. Menu.prototype.isEnabled = function()
  1716. {
  1717. return this.enabled;
  1718. };
  1719. /**
  1720. * Sets the enabled state of the action and fires a stateChanged event.
  1721. */
  1722. Menu.prototype.setEnabled = function(value)
  1723. {
  1724. if (this.enabled != value)
  1725. {
  1726. this.enabled = value;
  1727. this.fireEvent(new mxEventObject('stateChanged'));
  1728. }
  1729. };
  1730. /**
  1731. * Sets the enabled state of the action and fires a stateChanged event.
  1732. */
  1733. Menu.prototype.execute = function(menu, parent)
  1734. {
  1735. this.funct(menu, parent);
  1736. };
  1737. /**
  1738. * "Installs" menus in EditorUi.
  1739. */
  1740. EditorUi.prototype.createMenus = function()
  1741. {
  1742. return new Menus(this);
  1743. };