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