Built motion from commit bcd50b9.|0.0.29
[motion.git] / public / assets / plugins / square / js / Menus.js
1 /**
2  * $Id: Menus.js,v 1.13 2013-02-20 16:21:29 gaudenz Exp $
3  * Copyright (c) 2006-2012, JGraph Ltd
4  */
5 /**
6  * Constructs a new graph editor
7  */
8 Menus = function(editorUi) {
9         this.editorUi = editorUi;
10         this.menus = new Object();
11         this.init();
12
13         // Pre-fetches checkmark image
14         new Image().src = IMAGE_PATH + '/checkmark.gif';
15 };
16
17 /**
18  * Adds the label menu items to the given menu and parent.
19  */
20 Menus.prototype.init = function() {
21         var graph = this.editorUi.editor.graph;
22
23         this.put('fontFamily', new Menu(mxUtils.bind(this, function(menu, parent) {
24                 var fonts = ['Helvetica', 'Verdana', 'Times New Roman', 'Garamond',
25                         'Comic Sans MS',
26                         'Courier New', 'Georgia', 'Lucida Console', 'Tahoma'
27                 ];
28
29                 for (var i = 0; i < fonts.length; i++) {
30                         var tr = this.styleChange(menu, fonts[i], [mxConstants.STYLE_FONTFAMILY], [
31                                 fonts[i]
32                         ], null, parent);
33                         tr.firstChild.nextSibling.style.fontFamily = fonts[i];
34                 }
35
36                 menu.addSeparator(parent);
37                 this.promptChange(menu, mxResources.get('custom'), '', mxConstants.DEFAULT_FONTFAMILY,
38                         mxConstants.STYLE_FONTFAMILY, parent);
39         })));
40         this.put('fontSize', new Menu(mxUtils.bind(this, function(menu, parent) {
41                 var sizes = [6, 8, 9, 10, 11, 12, 14, 18, 24, 36, 48, 72];
42
43                 for (var i = 0; i < sizes.length; i++) {
44                         this.styleChange(menu, sizes[i], [mxConstants.STYLE_FONTSIZE], [sizes[i]],
45                                 null, parent);
46                 }
47
48                 menu.addSeparator(parent);
49                 this.promptChange(menu, mxResources.get('custom'), '(pt)', '12',
50                         mxConstants.STYLE_FONTSIZE, parent);
51         })));
52         this.put('linewidth', new Menu(mxUtils.bind(this, function(menu, parent) {
53                 var sizes = [1, 2, 3, 4, 8, 12, 16, 24];
54
55                 for (var i = 0; i < sizes.length; i++) {
56                         this.styleChange(menu, sizes[i] + 'px', [mxConstants.STYLE_STROKEWIDTH], [
57                                 sizes[i]
58                         ], null, parent);
59                 }
60
61                 menu.addSeparator(parent);
62                 this.promptChange(menu, mxResources.get('custom'), '(px)', '1',
63                         mxConstants.STYLE_STROKEWIDTH, parent);
64         })));
65         this.put('line', new Menu(mxUtils.bind(this, function(menu, parent) {
66                 this.styleChange(menu, mxResources.get('straight'), [mxConstants.STYLE_EDGE], [
67                         null
68                 ], null, parent);
69                 this.styleChange(menu, mxResources.get('entityRelation'), [mxConstants.STYLE_EDGE], [
70                         'entityRelationEdgeStyle'
71                 ], null, parent);
72                 menu.addSeparator(parent);
73                 this.styleChange(menu, mxResources.get('horizontal'), [mxConstants.STYLE_EDGE,
74                         mxConstants.STYLE_ELBOW
75                 ], ['elbowEdgeStyle', 'horizontal'], null, parent);
76                 this.styleChange(menu, mxResources.get('vertical'), [mxConstants.STYLE_EDGE,
77                         mxConstants.STYLE_ELBOW
78                 ], ['elbowEdgeStyle', 'vertical'], null, parent);
79                 menu.addSeparator(parent);
80                 this.styleChange(menu, mxResources.get('manual'), [mxConstants.STYLE_EDGE], [
81                         'segmentEdgeStyle'
82                 ], null, parent);
83                 this.styleChange(menu, mxResources.get('automatic'), [mxConstants.STYLE_EDGE], [
84                         'orthogonalEdgeStyle'
85                 ], null, parent);
86         })));
87         this.put('lineend', new Menu(mxUtils.bind(this, function(menu, parent) {
88                 this.styleChange(menu, mxResources.get('classic'), [mxConstants.STYLE_ENDARROW], [
89                         mxConstants.ARROW_CLASSIC
90                 ], null, parent);
91                 this.styleChange(menu, mxResources.get('openArrow'), [mxConstants.STYLE_ENDARROW], [
92                         mxConstants.ARROW_OPEN
93                 ], null, parent);
94                 this.styleChange(menu, mxResources.get('block'), [mxConstants.STYLE_ENDARROW], [
95                         mxConstants.ARROW_BLOCK
96                 ], null, parent);
97                 menu.addSeparator(parent);
98                 this.styleChange(menu, mxResources.get('oval'), [mxConstants.STYLE_ENDARROW], [
99                         mxConstants.ARROW_OVAL
100                 ], null, parent);
101                 this.styleChange(menu, mxResources.get('diamond'), [mxConstants.STYLE_ENDARROW], [
102                         mxConstants.ARROW_DIAMOND
103                 ], null, parent);
104                 this.styleChange(menu, mxResources.get('diamondThin'), [mxConstants.STYLE_ENDARROW], [
105                         mxConstants.ARROW_DIAMOND_THIN
106                 ], null, parent);
107                 menu.addSeparator(parent);
108                 this.styleChange(menu, mxResources.get('none'), [mxConstants.STYLE_ENDARROW], [
109                         mxConstants.NONE
110                 ], null, parent);
111                 menu.addSeparator(parent);
112                 menu.addItem(mxResources.get('transparent'), null, function() {
113                         graph.toggleCellStyles('endFill', true);
114                 }, parent, null, true);
115                 menu.addSeparator(parent);
116                 this.promptChange(menu, mxResources.get('size'), '(px)', mxConstants.DEFAULT_MARKERSIZE,
117                         mxConstants.STYLE_ENDSIZE, parent);
118         })));
119         this.put('linestart', new Menu(mxUtils.bind(this, function(menu, parent) {
120                 this.styleChange(menu, mxResources.get('classic'), [mxConstants.STYLE_STARTARROW], [
121                         mxConstants.ARROW_CLASSIC
122                 ], null, parent);
123                 this.styleChange(menu, mxResources.get('openArrow'), [mxConstants.STYLE_STARTARROW], [
124                         mxConstants.ARROW_OPEN
125                 ], null, parent);
126                 this.styleChange(menu, mxResources.get('block'), [mxConstants.STYLE_STARTARROW], [
127                         mxConstants.ARROW_BLOCK
128                 ], null, parent);
129                 menu.addSeparator(parent);
130                 this.styleChange(menu, mxResources.get('oval'), [mxConstants.STYLE_STARTARROW], [
131                         mxConstants.ARROW_OVAL
132                 ], null, parent);
133                 this.styleChange(menu, mxResources.get('diamond'), [mxConstants.STYLE_STARTARROW], [
134                         mxConstants.ARROW_DIAMOND
135                 ], null, parent);
136                 this.styleChange(menu, mxResources.get('diamondThin'), [mxConstants.STYLE_STARTARROW], [
137                         mxConstants.ARROW_DIAMOND_THIN
138                 ], null, parent);
139                 menu.addSeparator(parent);
140                 this.styleChange(menu, mxResources.get('none'), [mxConstants.STYLE_STARTARROW], [
141                         mxConstants.NONE
142                 ], null, parent);
143                 menu.addSeparator(parent);
144                 menu.addItem(mxResources.get('transparent'), null, function() {
145                         graph.toggleCellStyles('startFill', true);
146                 }, parent, null, true);
147                 menu.addSeparator(parent);
148                 this.promptChange(menu, mxResources.get('size'), '(px)', mxConstants.DEFAULT_MARKERSIZE,
149                         mxConstants.STYLE_STARTSIZE, parent);
150         })));
151         this.put('spacing', new Menu(mxUtils.bind(this, function(menu, parent) {
152                 // Uses shadow action and line menu to analyze selection
153                 var vertexSelected = this.editorUi.actions.get('shadow').enabled;
154                 var edgeSelected = this.get('line').enabled;
155
156                 if (vertexSelected || menu.showDisabled) {
157                         this.promptChange(menu, mxResources.get('top'), '(px)', '0',
158                                 mxConstants.STYLE_SPACING_TOP, parent, vertexSelected);
159                         this.promptChange(menu, mxResources.get('right'), '(px)', '0',
160                                 mxConstants.STYLE_SPACING_RIGHT, parent, vertexSelected);
161                         this.promptChange(menu, mxResources.get('bottom'), '(px)', '0',
162                                 mxConstants.STYLE_SPACING_BOTTOM, parent, vertexSelected);
163                         this.promptChange(menu, mxResources.get('left'), '(px)', '0',
164                                 mxConstants.STYLE_SPACING_LEFT, parent, vertexSelected);
165                         menu.addSeparator(parent);
166                         this.promptChange(menu, mxResources.get('global'), '(px)', '0',
167                                 mxConstants.STYLE_SPACING, parent, vertexSelected);
168                         this.promptChange(menu, mxResources.get('perimeter'), '(px)', '0',
169                                 mxConstants.STYLE_PERIMETER_SPACING, parent, vertexSelected);
170                 }
171
172                 if (edgeSelected || menu.showDisabled) {
173                         menu.addSeparator(parent);
174                         this.promptChange(menu, mxResources.get('sourceSpacing'), '(px)', '0',
175                                 mxConstants.STYLE_SOURCE_PERIMETER_SPACING, parent, edgeSelected);
176                         this.promptChange(menu, mxResources.get('targetSpacing'), '(px)', '0',
177                                 mxConstants.STYLE_TARGET_PERIMETER_SPACING, parent, edgeSelected);
178                 }
179         })));
180         this.put('format', new Menu(mxUtils.bind(this, function(menu, parent) {
181                 this.addMenuItem(menu, 'style', parent);
182         })));
183         this.put('text', new Menu(mxUtils.bind(this, function(menu, parent) {
184                 var enabled = this.get('text').enabled;
185                 menu.addSeparator(parent);
186                 this.addSubmenu('fontFamily', menu, parent);
187                 this.addSubmenu('fontSize', menu, parent);
188                 this.addMenuItems(menu, ['-', 'bold', 'italic', 'underline', '-'],
189                         parent);
190                 this.addSubmenu('alignment', menu, parent);
191                 this.addMenuItem(menu, 'wordWrap', parent);
192                 menu.addSeparator(parent);
193                 this.promptChange(menu, mxResources.get('textOpacity'), '(%)', '100',
194                         mxConstants.STYLE_TEXT_OPACITY, parent, enabled);
195                 menu.addItem(mxResources.get('hide'), null, function() {
196                         graph.toggleCellStyles(mxConstants.STYLE_NOLABEL, false);
197                 }, parent, null, enabled);
198         })));
199         this.put('alignment', new Menu(mxUtils.bind(this, function(menu, parent) {
200                 this.styleChange(menu, mxResources.get('leftAlign'), [mxConstants.STYLE_ALIGN], [
201                         mxConstants.ALIGN_LEFT
202                 ], null, parent);
203                 this.styleChange(menu, mxResources.get('center'), [mxConstants.STYLE_ALIGN], [
204                         mxConstants.ALIGN_CENTER
205                 ], null, parent);
206                 this.styleChange(menu, mxResources.get('rightAlign'), [mxConstants.STYLE_ALIGN], [
207                         mxConstants.ALIGN_RIGHT
208                 ], null, parent);
209                 menu.addSeparator(parent);
210                 this.styleChange(menu, mxResources.get('topAlign'), [mxConstants.STYLE_VERTICAL_ALIGN], [
211                         mxConstants.ALIGN_TOP
212                 ], null, parent);
213                 this.styleChange(menu, mxResources.get('middle'), [mxConstants.STYLE_VERTICAL_ALIGN], [
214                         mxConstants.ALIGN_MIDDLE
215                 ], null, parent);
216                 this.styleChange(menu, mxResources.get('bottomAlign'), [mxConstants.STYLE_VERTICAL_ALIGN], [
217                         mxConstants.ALIGN_BOTTOM
218                 ], null, parent);
219                 menu.addSeparator(parent);
220                 this.addSubmenu('position', menu, parent);
221                 this.addSubmenu('spacing', menu, parent);
222                 menu.addSeparator(parent);
223                 var enabled = this.get('text').enabled;
224                 menu.addItem(mxResources.get('vertical'), null, function() {
225                         graph.toggleCellStyles(mxConstants.STYLE_HORIZONTAL, true);
226                 }, parent, null, enabled);
227         })));
228         this.put('position', new Menu(mxUtils.bind(this, function(menu, parent) {
229                 this.styleChange(menu, mxResources.get('left'), [mxConstants.STYLE_LABEL_POSITION,
230                         mxConstants.STYLE_ALIGN
231                 ], [mxConstants.ALIGN_LEFT, mxConstants.ALIGN_RIGHT], null, parent);
232                 this.styleChange(menu, mxResources.get('center'), [mxConstants.STYLE_LABEL_POSITION,
233                         mxConstants.STYLE_ALIGN
234                 ], [mxConstants.ALIGN_CENTER, mxConstants.ALIGN_CENTER], null, parent);
235                 this.styleChange(menu, mxResources.get('right'), [mxConstants.STYLE_LABEL_POSITION,
236                         mxConstants.STYLE_ALIGN
237                 ], [mxConstants.ALIGN_RIGHT, mxConstants.ALIGN_LEFT], null, parent);
238                 menu.addSeparator(parent);
239                 this.styleChange(menu, mxResources.get('top'), [mxConstants.STYLE_VERTICAL_LABEL_POSITION,
240                         mxConstants.STYLE_VERTICAL_ALIGN
241                 ], [mxConstants.ALIGN_TOP, mxConstants.ALIGN_BOTTOM], null, parent);
242                 this.styleChange(menu, mxResources.get('middle'), [mxConstants.STYLE_VERTICAL_LABEL_POSITION,
243                         mxConstants.STYLE_VERTICAL_ALIGN
244                 ], [mxConstants.ALIGN_MIDDLE, mxConstants.ALIGN_MIDDLE], null, parent);
245                 this.styleChange(menu, mxResources.get('bottom'), [mxConstants.STYLE_VERTICAL_LABEL_POSITION,
246                         mxConstants.STYLE_VERTICAL_ALIGN
247                 ], [mxConstants.ALIGN_BOTTOM, mxConstants.ALIGN_TOP], null, parent);
248         })));
249         this.put('direction', new Menu(mxUtils.bind(this, function(menu, parent) {
250                 menu.addItem(mxResources.get('flipH'), null, function() {
251                         graph.toggleCellStyles(mxConstants.STYLE_FLIPH, false);
252                 }, parent);
253                 menu.addItem(mxResources.get('flipV'), null, function() {
254                         graph.toggleCellStyles(mxConstants.STYLE_FLIPV, false);
255                 }, parent);
256                 this.addMenuItems(menu, ['-', 'tilt', 'rotation'], parent);
257         })));
258         this.put('align', new Menu(mxUtils.bind(this, function(menu, parent) {
259                 menu.addItem(mxResources.get('leftAlign'), null, function() {
260                         graph.alignCells(mxConstants.ALIGN_LEFT);
261                 }, parent);
262                 menu.addItem(mxResources.get('center'), null, function() {
263                         graph.alignCells(mxConstants.ALIGN_CENTER);
264                 }, parent);
265                 menu.addItem(mxResources.get('rightAlign'), null, function() {
266                         graph.alignCells(mxConstants.ALIGN_RIGHT);
267                 }, parent);
268                 menu.addSeparator(parent);
269                 menu.addItem(mxResources.get('topAlign'), null, function() {
270                         graph.alignCells(mxConstants.ALIGN_TOP);
271                 }, parent);
272                 menu.addItem(mxResources.get('middle'), null, function() {
273                         graph.alignCells(mxConstants.ALIGN_MIDDLE);
274                 }, parent);
275                 menu.addItem(mxResources.get('bottomAlign'), null, function() {
276                         graph.alignCells(mxConstants.ALIGN_BOTTOM);
277                 }, parent);
278         })));
279         this.put('layout', new Menu(mxUtils.bind(this, function(menu, parent) {
280                 // menu.addItem(mxResources.get('horizontalTree'), null, mxUtils.bind(this, function()
281                 // {
282                 //      if (!graph.isSelectionEmpty())
283                 //      {
284                 //              var layout = new mxCompactTreeLayout(graph, true);
285                 //              layout.edgeRouting = false;
286                 //              layout.levelDistance = 30;
287                 //              this.editorUi.executeLayout(layout, true, true);
288                 //      }
289                 // }), parent);
290                 // menu.addItem(mxResources.get('verticalTree'), null, mxUtils.bind(this, function()
291                 // {
292                 //      if (!graph.isSelectionEmpty())
293                 //      {
294                 //              var layout = new mxCompactTreeLayout(graph, false);
295                 //              layout.edgeRouting = false;
296                 //              layout.levelDistance = 30;
297                 //              this.editorUi.executeLayout(layout, true, true);
298                 //      }
299                 // }), parent);
300                 // menu.addSeparator(parent);
301                 menu.addItem(mxResources.get('horizontalFlow'), null, mxUtils.bind(this,
302                         function() {
303                                 var layout = new mxHierarchicalLayout(graph, mxConstants.DIRECTION_WEST);
304                                 this.editorUi.executeLayout(layout, true, true);
305                         }), parent);
306                 menu.addItem(mxResources.get('verticalFlow'), null, mxUtils.bind(this,
307                         function() {
308                                 var layout = new mxHierarchicalLayout(graph, mxConstants.DIRECTION_NORTH);
309                                 this.editorUi.executeLayout(layout, true, true);
310                         }), parent);
311                 //menu.addSeparator(parent);
312                 // menu.addItem(mxResources.get('organic'), null, mxUtils.bind(this, function()
313                 // {
314                 //      var layout = new mxFastOrganicLayout(graph);
315                 //              this.editorUi.executeLayout(layout, true, true);
316                 // }), parent);
317                 // menu.addItem(mxResources.get('circle'), null, mxUtils.bind(this, function()
318                 // {
319                 //      var layout = new mxCircleLayout(graph);
320                 //              this.editorUi.executeLayout(layout, true, true, graph.getSelectionCells());
321                 // }), parent);
322         })));
323         this.put('navigation', new Menu(mxUtils.bind(this, function(menu, parent) {
324                 this.addMenuItems(menu, ['home', '-', 'exitGroup', 'enterGroup', '-',
325                         'expand', 'collapse'
326                 ], parent);
327         })));
328         this.put('arrange', new Menu(mxUtils.bind(this, function(menu, parent) {
329                 this.addMenuItems(menu, ['toFront', 'toBack', '-'], parent);
330                 //this.addSubmenu('direction', menu, parent);
331                 this.addSubmenu('layout', menu, parent);
332                 //this.addSubmenu('align', menu, parent);
333                 menu.addSeparator(parent);
334                 //this.addSubmenu('navigation', menu, parent);
335                 this.addMenuItems(menu, ['-', 'group', 'ungroup', 'removeFromGroup'],
336                         parent);
337                 //this.addMenuItems(menu, ['-', 'group', 'ungroup', 'removeFromGroup', '-', 'autosize'], parent);
338         })));
339         this.put('view', new Menu(mxUtils.bind(this, function(menu, parent) {
340                 this.addMenuItems(menu, ['actualSize'], parent);
341                 menu.addSeparator();
342                 var scales = [0.25, 0.5, 0.75, 1, 2, 4];
343
344                 for (var i = 0; i < scales.length; i++) {
345                         (function(scale) {
346                                 menu.addItem((scale * 100) + '%', null, function() {
347                                         graph.zoomTo(scale);
348                                 }, parent);
349                         })(scales[i]);
350                 }
351
352                 this.addMenuItems(menu, ['-', 'zoomIn', 'zoomOut', '-', 'fitWindow',
353                         'customZoom', '-', 'fitPage', 'fitPageWidth'
354                 ], parent);
355         })));
356         this.put('file', new Menu(mxUtils.bind(this, function(menu, parent) {
357                 this.addMenuItems(menu, ['new', 'open', '-', 'save', 'saveAs', 'publish',
358                         '-', 'variable', '-', 'import', 'export', '-', 'editFile', '-',
359                         'print'
360                 ], parent);
361         })));
362         this.put('edit', new Menu(mxUtils.bind(this, function(menu, parent) {
363                 this.addMenuItems(menu, ['undo', 'redo', '-', 'cut', 'copy', 'paste',
364                         'delete', '-', 'duplicate', '-',
365                         'selectVertices', 'selectEdges', 'selectAll'
366                 ]);
367         })));
368         this.put('help', new Menu(mxUtils.bind(this, function(menu, parent) {
369                 this.addMenuItems(menu, ['help', '-', 'about']);
370         })));
371 };
372
373 /**
374  * Adds the label menu items to the given menu and parent.
375  */
376 Menus.prototype.put = function(name, menu) {
377         this.menus[name] = menu;
378 };
379
380 /**
381  * Adds the label menu items to the given menu and parent.
382  */
383 Menus.prototype.get = function(name) {
384         return this.menus[name];
385 };
386
387 /**
388  * Adds the given submenu.
389  */
390 Menus.prototype.addSubmenu = function(name, menu, parent) {
391         var enabled = this.get(name).enabled;
392
393         if (menu.showDisabled || enabled) {
394                 var submenu = menu.addItem(mxResources.get(name), null, null, parent, null,
395                         enabled);
396                 this.addMenu(name, menu, submenu);
397         }
398 };
399
400 /**
401  * Adds the label menu items to the given menu and parent.
402  */
403 Menus.prototype.addMenu = function(name, popupMenu, parent) {
404         var menu = this.get(name);
405
406         if (menu != null && (popupMenu.showDisabled || menu.enabled)) {
407                 this.get(name).execute(popupMenu, parent);
408         }
409 };
410
411 /**
412  * Adds a style change item to the given menu.
413  */
414 Menus.prototype.styleChange = function(menu, label, keys, values, sprite,
415         parent) {
416         return menu.addItem(label, null, mxUtils.bind(this, function() {
417                 var graph = this.editorUi.editor.graph;
418
419                 graph.getModel().beginUpdate();
420                 try {
421                         for (var i = 0; i < keys.length; i++) {
422                                 graph.setCellStyles(keys[i], values[i]);
423                         }
424                 } finally {
425                         graph.getModel().endUpdate();
426                 }
427         }), parent, sprite);
428 };
429
430 /**
431  * Adds a style change item with a prompt to the given menu.
432  */
433 Menus.prototype.promptChange = function(menu, label, hint, defaultValue, key,
434         parent, enabled) {
435         return menu.addItem(label, null, mxUtils.bind(this, function() {
436                 var graph = this.editorUi.editor.graph;
437                 var value = defaultValue;
438                 var state = graph.getView().getState(graph.getSelectionCell());
439
440                 if (state != null) {
441                         value = state.style[key] || value;
442                 }
443
444                 value = mxUtils.prompt(mxResources.get('enterValue') + ((hint.length > 0) ?
445                         (' ' + hint) : ''), value);
446
447                 if (value != null && value.length > 0) {
448                         graph.setCellStyles(key, value);
449                 }
450         }), parent, null, enabled);
451 };
452
453 /**
454  * Creates the keyboard event handler for the current graph and history.
455  */
456 Menus.prototype.addMenuItem = function(menu, key, parent) {
457         var action = this.editorUi.actions.get(key);
458
459         if (action != null && (menu.showDisabled || action.enabled)) {
460                 var item = menu.addItem(action.label, null, action.funct, parent, null,
461                         action.enabled);
462
463                 // Adds checkmark image
464                 if (action.toggleAction && action.isSelected()) {
465                         this.addCheckmark(item);
466                 }
467
468                 this.addShortcut(item, action);
469
470                 return item;
471         }
472
473         return null;
474 };
475
476 /**
477  * Adds a checkmark to the given menuitem.
478  */
479 Menus.prototype.addShortcut = function(item, action) {
480         if (action.shortcut != null) {
481                 var td = item.firstChild.nextSibling.nextSibling;
482                 var span = document.createElement('span');
483                 span.style.color = 'gray';
484                 mxUtils.write(span, action.shortcut);
485                 td.appendChild(span);
486         }
487 };
488
489 /**
490  * Adds a checkmark to the given menuitem.
491  */
492 Menus.prototype.addCheckmark = function(item) {
493         var td = item.firstChild.nextSibling;
494         td.style.backgroundImage = 'url(' + IMAGE_PATH + '/checkmark.gif)';
495         td.style.backgroundRepeat = 'no-repeat';
496         td.style.backgroundPosition = '2px 50%';
497 };
498
499 /**
500  * Creates the keyboard event handler for the current graph and history.
501  */
502 Menus.prototype.addMenuItems = function(menu, keys, parent) {
503         for (var i = 0; i < keys.length; i++) {
504                 if (keys[i] == '-') {
505                         menu.addSeparator(parent);
506                 } else {
507                         this.addMenuItem(menu, keys[i], parent);
508                 }
509         }
510 };
511
512 /**
513  * Creates the keyboard event handler for the current graph and history.
514  */
515 Menus.prototype.createPopupMenu = function(menu, cell, evt) {
516         var graph = this.editorUi.editor.graph;
517         menu.smartSeparators = true;
518
519         if (graph.isSelectionEmpty()) {
520                 this.addMenuItems(menu, ['undo', 'redo', '-', 'paste', '-']);
521         } else {
522                 this.addMenuItems(menu, ['delete', '-', 'cut', 'copy', '-', 'duplicate']);
523
524                 if (graph.getSelectionCount() == 1 && graph.getModel().isEdge(graph.getSelectionCell())) {
525                         this.addMenuItems(menu, ['setAsDefaultEdge']);
526                 }
527
528                 menu.addSeparator();
529         }
530
531         if (graph.getSelectionCount() > 0) {
532                 this.addMenuItems(menu, ['toFront', 'toBack', '-']);
533
534                 if (graph.getModel().isEdge(graph.getSelectionCell())) {
535                         var isWaypoint = false;
536                         var cell = graph.getSelectionCell();
537
538                         if (cell != null && graph.getModel().isEdge(cell)) {
539                                 var handler = graph.selectionCellsHandler.getHandler(cell);
540
541                                 if (handler instanceof mxEdgeHandler && handler.bends != null && handler.bends
542                                         .length > 2) {
543                                         var index = handler.getHandleForEvent(new mxMouseEvent(evt));
544
545                                         // Configures removeWaypoint action before execution
546                                         var rmWaypointAction = this.editorUi.actions.get('removeWaypoint');
547                                         rmWaypointAction.handler = handler;
548                                         rmWaypointAction.index = index;
549
550                                         isWaypoint = index > 0 && index < handler.bends.length;
551                                 }
552                         }
553
554                         this.addMenuItems(menu, ['-', (isWaypoint) ? 'removeWaypoint' :
555                                 'addWaypoint'
556                         ]);
557                 } else if (graph.getSelectionCount() > 1) {
558                         menu.addSeparator();
559                         this.addMenuItems(menu, ['group']);
560                 }
561
562                 menu.addSeparator();
563
564                 if (graph.getSelectionCount() == 1) {
565                         var link = graph.getLinkForCell(graph.getSelectionCell());
566
567                         if (link != null) {
568                                 this.addMenuItems(menu, ['openLink']);
569                         }
570                 }
571         } else {
572                 this.addMenuItems(menu, ['-', 'selectVertices', 'selectEdges', '-',
573                         'selectAll'
574                 ]);
575         }
576 };
577
578 /**
579  * Creates the keyboard event handler for the current graph and history.
580  */
581 Menus.prototype.createMenubar = function(container) {
582         var menubar = new Menubar(this.editorUi, container);
583         var menus = ['file', 'edit', 'view', 'format', 'text', 'arrange',
584                 'help'
585         ];
586
587         for (var i = 0; i < menus.length; i++) {
588                 menubar.addMenu(mxResources.get(menus[i]), this.get(menus[i]).funct);
589         }
590
591         return menubar;
592 };
593
594 /**
595  * Construcs a new menubar for the given editor.
596  */
597 function Menubar(editorUi, container) {
598         this.editorUi = editorUi;
599         this.container = container;
600
601         // Global handler to hide the current menu
602         mxEvent.addGestureListeners(document, mxUtils.bind(this, function(evt) {
603                 this.hideMenu();
604         }));
605 };
606
607 /**
608  * Adds the menubar elements.
609  */
610 Menubar.prototype.hideMenu = function() {
611         if (this.currentMenu != null) {
612                 this.currentMenu.hideMenu();
613         }
614 };
615
616 /**
617  * Adds a submenu to this menubar.
618  */
619 Menubar.prototype.addMenu = function(label, funct) {
620         var elt = document.createElement('a');
621         elt.setAttribute('href', 'javascript:void(0);');
622         elt.className = 'geItem';
623         mxUtils.write(elt, label);
624
625         this.addMenuHandler(elt, funct);
626         this.container.appendChild(elt);
627
628         return elt;
629 };
630
631 /**
632  * Adds a handler for showing a menu in the given element.
633  */
634 Menubar.prototype.addMenuHandler = function(elt, funct) {
635         if (funct != null) {
636                 var show = true;
637
638                 var clickHandler = mxUtils.bind(this, function(evt) {
639                         if (show && elt.enabled == null || elt.enabled) {
640                                 this.editorUi.editor.graph.panningHandler.hideMenu();
641                                 var menu = new mxPopupMenu(funct);
642                                 menu.div.className += ' geMenubarMenu';
643                                 menu.smartSeparators = true;
644                                 menu.showDisabled = true;
645                                 menu.autoExpand = true;
646
647                                 // Disables autoexpand and destroys menu when hidden
648                                 menu.hideMenu = mxUtils.bind(this, function() {
649                                         mxPopupMenu.prototype.hideMenu.apply(menu, arguments);
650                                         menu.destroy();
651                                         this.currentMenu = null;
652                                         this.currentElt = null;
653                                 });
654
655                                 // Added width of the page-sidebar
656                                 var x = elt.offsetLeft + document.getElementById('page-sidebar').offsetWidth +
657                                         22;
658                                 // Added static height of the page-navbar
659                                 var y = elt.offsetTop + elt.offsetHeight + 140;
660
661                                 menu.popup(x, y, null, evt);
662
663                                 //menu.popup(elt.offsetLeft + 4, elt.offsetTop + elt.offsetHeight + 4, null, evt);
664                                 this.currentMenu = menu;
665                                 this.currentElt = elt;
666                         }
667
668                         show = true;
669                         mxEvent.consume(evt);
670                 });
671
672                 // Shows menu automatically while in expanded state
673                 mxEvent.addListener(elt, 'mousemove', mxUtils.bind(this, function(evt) {
674                         if (this.currentMenu != null && this.currentElt != elt) {
675                                 this.hideMenu();
676                                 clickHandler(evt);
677                         }
678                 }));
679
680                 // Hides menu if already showing
681                 mxEvent.addListener(elt, 'mousedown', mxUtils.bind(this, function() {
682                         show = this.currentElt != elt;
683                 }));
684
685                 mxEvent.addListener(elt, 'click', clickHandler);
686         }
687 };
688
689 /**
690  * Constructs a new action for the given parameters.
691  */
692 function Menu(funct, enabled) {
693         mxEventSource.call(this);
694         this.funct = funct;
695         this.enabled = (enabled != null) ? enabled : true;
696 };
697
698 // Menu inherits from mxEventSource
699 mxUtils.extend(Menu, mxEventSource);
700
701 /**
702  * Sets the enabled state of the action and fires a stateChanged event.
703  */
704 Menu.prototype.setEnabled = function(value) {
705         if (this.enabled != value) {
706                 this.enabled = value;
707                 this.fireEvent(new mxEventObject('stateChanged'));
708         }
709 };
710
711 /**
712  * Sets the enabled state of the action and fires a stateChanged event.
713  */
714 Menu.prototype.execute = function(menu, parent) {
715         this.funct(menu, parent);
716 };