1/** 2 * Copyright (c) 2006-2012, JGraph Ltd 3 */ 4/** 5 * Constructs a new graph editor 6 */ 7EditorUi = function(editor, container, lightbox) 8{ 9 mxEventSource.call(this); 10 11 this.destroyFunctions = []; 12 this.editor = editor || new Editor(); 13 this.container = container || document.body; 14 15 var graph = this.editor.graph; 16 graph.lightbox = lightbox; 17 18 // Overrides graph bounds to include background pages 19 var graphGetGraphBounds = graph.getGraphBounds; 20 21 graph.getGraphBounds = function(img) 22 { 23 var bounds = graphGetGraphBounds.apply(this, arguments); 24 var img = this.backgroundImage; 25 26 if (img != null) 27 { 28 var t = this.view.translate; 29 var s = this.view.scale; 30 31 bounds = mxRectangle.fromRectangle(bounds); 32 bounds.add(new mxRectangle( 33 (t.x + img.x) * s, (t.y + img.y) * s, 34 img.width * s, img.height * s)); 35 } 36 37 return bounds; 38 }; 39 40 // Faster scrollwheel zoom is possible with CSS transforms 41 if (graph.useCssTransforms) 42 { 43 this.lazyZoomDelay = 0; 44 } 45 46 // Pre-fetches submenu image or replaces with embedded image if supported 47 if (mxClient.IS_SVG) 48 { 49 mxPopupMenu.prototype.submenuImage = 'data:image/gif;base64,R0lGODlhCQAJAIAAAP///zMzMyH5BAEAAAAALAAAAAAJAAkAAAIPhI8WebHsHopSOVgb26AAADs='; 50 } 51 else 52 { 53 new Image().src = mxPopupMenu.prototype.submenuImage; 54 } 55 56 // Pre-fetches connect image 57 if (!mxClient.IS_SVG && mxConnectionHandler.prototype.connectImage != null) 58 { 59 new Image().src = mxConnectionHandler.prototype.connectImage.src; 60 } 61 62 // Disables graph and forced panning in chromeless mode 63 if (this.editor.chromeless && !this.editor.editable) 64 { 65 this.footerHeight = 0; 66 graph.isEnabled = function() { return false; }; 67 graph.panningHandler.isForcePanningEvent = function(me) 68 { 69 return !mxEvent.isPopupTrigger(me.getEvent()); 70 }; 71 } 72 73 // Creates the user interface 74 this.actions = new Actions(this); 75 this.menus = this.createMenus(); 76 77 if (!graph.standalone) 78 { 79 // Stores the current style and assigns it to new cells 80 var styles = ['rounded', 'shadow', 'glass', 'dashed', 'dashPattern', 'labelBackgroundColor', 81 'labelBorderColor', 'comic', 'sketch', 'fillWeight', 'hachureGap', 'hachureAngle', 'jiggle', 82 'disableMultiStroke', 'disableMultiStrokeFill', 'fillStyle', 'curveFitting', 83 'simplification', 'sketchStyle', 'pointerEvents']; 84 var connectStyles = ['shape', 'edgeStyle', 'curved', 'rounded', 'elbow', 'jumpStyle', 'jumpSize', 85 'comic', 'sketch', 'fillWeight', 'hachureGap', 'hachureAngle', 'jiggle', 86 'disableMultiStroke', 'disableMultiStrokeFill', 'fillStyle', 'curveFitting', 87 'simplification', 'sketchStyle']; 88 // Styles to be ignored if applyAll is false 89 var ignoredEdgeStyles = ['curved', 'sourcePerimeterSpacing', 'targetPerimeterSpacing', 90 'startArrow', 'startFill', 'startSize', 'endArrow', 'endFill', 'endSize']; 91 92 // Note: Everything that is not in styles is ignored (styles is augmented below) 93 this.setDefaultStyle = function(cell) 94 { 95 try 96 { 97 var state = graph.view.getState(cell); 98 99 if (state != null) 100 { 101 // Ignores default styles 102 var clone = cell.clone(); 103 clone.style = '' 104 var defaultStyle = graph.getCellStyle(clone); 105 var values = []; 106 var keys = []; 107 108 for (var key in state.style) 109 { 110 if (defaultStyle[key] != state.style[key]) 111 { 112 values.push(state.style[key]); 113 keys.push(key); 114 } 115 } 116 117 // Handles special case for value "none" 118 var cellStyle = graph.getModel().getStyle(state.cell); 119 var tokens = (cellStyle != null) ? cellStyle.split(';') : []; 120 121 for (var i = 0; i < tokens.length; i++) 122 { 123 var tmp = tokens[i]; 124 var pos = tmp.indexOf('='); 125 126 if (pos >= 0) 127 { 128 var key = tmp.substring(0, pos); 129 var value = tmp.substring(pos + 1); 130 131 if (defaultStyle[key] != null && value == 'none') 132 { 133 values.push(value); 134 keys.push(key); 135 } 136 } 137 } 138 139 // Resets current style 140 if (graph.getModel().isEdge(state.cell)) 141 { 142 graph.currentEdgeStyle = {}; 143 } 144 else 145 { 146 graph.currentVertexStyle = {} 147 } 148 149 this.fireEvent(new mxEventObject('styleChanged', 'keys', keys, 'values', values, 'cells', [state.cell])); 150 } 151 } 152 catch (e) 153 { 154 this.handleError(e); 155 } 156 }; 157 158 this.clearDefaultStyle = function() 159 { 160 graph.currentEdgeStyle = mxUtils.clone(graph.defaultEdgeStyle); 161 graph.currentVertexStyle = mxUtils.clone(graph.defaultVertexStyle); 162 163 // Updates UI 164 this.fireEvent(new mxEventObject('styleChanged', 'keys', [], 'values', [], 'cells', [])); 165 }; 166 167 // Keys that should be ignored if the cell has a value (known: new default for all cells is html=1 so 168 // for the html key this effecticely only works for edges inserted via the connection handler) 169 var valueStyles = ['fontFamily', 'fontSource', 'fontSize', 'fontColor']; 170 171 for (var i = 0; i < valueStyles.length; i++) 172 { 173 if (mxUtils.indexOf(styles, valueStyles[i]) < 0) 174 { 175 styles.push(valueStyles[i]); 176 } 177 } 178 179 // Keys that always update the current edge style regardless of selection 180 var alwaysEdgeStyles = ['edgeStyle', 'startArrow', 'startFill', 'startSize', 'endArrow', 181 'endFill', 'endSize']; 182 183 // Keys that are ignored together (if one appears all are ignored) 184 var keyGroups = [['startArrow', 'startFill', 'endArrow', 'endFill'], 185 ['startSize', 'endSize'], 186 ['sourcePerimeterSpacing', 'targetPerimeterSpacing'], 187 ['strokeColor', 'strokeWidth'], 188 ['fillColor', 'gradientColor', 'gradientDirection'], 189 ['align', 'verticalAlign'], 190 ['opacity'], 191 ['html']]; 192 193 // Adds all keys used above to the styles array 194 for (var i = 0; i < keyGroups.length; i++) 195 { 196 for (var j = 0; j < keyGroups[i].length; j++) 197 { 198 styles.push(keyGroups[i][j]); 199 } 200 } 201 202 for (var i = 0; i < connectStyles.length; i++) 203 { 204 if (mxUtils.indexOf(styles, connectStyles[i]) < 0) 205 { 206 styles.push(connectStyles[i]); 207 } 208 } 209 210 // Implements a global current style for edges and vertices that is applied to new cells 211 var insertHandler = function(cells, asText, model, vertexStyle, edgeStyle, applyAll, recurse) 212 { 213 vertexStyle = (vertexStyle != null) ? vertexStyle : graph.currentVertexStyle; 214 edgeStyle = (edgeStyle != null) ? edgeStyle : graph.currentEdgeStyle; 215 applyAll = (applyAll != null) ? applyAll : true; 216 217 model = (model != null) ? model : graph.getModel(); 218 219 if (recurse) 220 { 221 var temp = []; 222 223 for (var i = 0; i < cells.length; i++) 224 { 225 temp = temp.concat(model.getDescendants(cells[i])); 226 } 227 228 cells = temp; 229 } 230 231 model.beginUpdate(); 232 try 233 { 234 for (var i = 0; i < cells.length; i++) 235 { 236 var cell = cells[i]; 237 238 var appliedStyles; 239 240 if (asText) 241 { 242 // Applies only basic text styles 243 appliedStyles = ['fontSize', 'fontFamily', 'fontColor']; 244 } 245 else 246 { 247 // Removes styles defined in the cell style from the styles to be applied 248 var cellStyle = model.getStyle(cell); 249 var tokens = (cellStyle != null) ? cellStyle.split(';') : []; 250 appliedStyles = styles.slice(); 251 252 for (var j = 0; j < tokens.length; j++) 253 { 254 var tmp = tokens[j]; 255 var pos = tmp.indexOf('='); 256 257 if (pos >= 0) 258 { 259 var key = tmp.substring(0, pos); 260 var index = mxUtils.indexOf(appliedStyles, key); 261 262 if (index >= 0) 263 { 264 appliedStyles.splice(index, 1); 265 } 266 267 // Handles special cases where one defined style ignores other styles 268 for (var k = 0; k < keyGroups.length; k++) 269 { 270 var group = keyGroups[k]; 271 272 if (mxUtils.indexOf(group, key) >= 0) 273 { 274 for (var l = 0; l < group.length; l++) 275 { 276 var index2 = mxUtils.indexOf(appliedStyles, group[l]); 277 278 if (index2 >= 0) 279 { 280 appliedStyles.splice(index2, 1); 281 } 282 } 283 } 284 } 285 } 286 } 287 } 288 289 // Applies the current style to the cell 290 var edge = model.isEdge(cell); 291 var current = (edge) ? edgeStyle : vertexStyle; 292 var newStyle = model.getStyle(cell); 293 294 for (var j = 0; j < appliedStyles.length; j++) 295 { 296 var key = appliedStyles[j]; 297 var styleValue = current[key]; 298 299 if (styleValue != null && key != 'edgeStyle' && (key != 'shape' || edge)) 300 { 301 // Special case: Connect styles are not applied here but in the connection handler 302 if (!edge || applyAll || mxUtils.indexOf(ignoredEdgeStyles, key) < 0) 303 { 304 newStyle = mxUtils.setStyle(newStyle, key, styleValue); 305 } 306 } 307 } 308 309 if (Editor.simpleLabels) 310 { 311 newStyle = mxUtils.setStyle(mxUtils.setStyle( 312 newStyle, 'html', null), 'whiteSpace', null); 313 } 314 315 model.setStyle(cell, newStyle); 316 } 317 } 318 finally 319 { 320 model.endUpdate(); 321 } 322 323 return cells; 324 }; 325 326 graph.addListener('cellsInserted', function(sender, evt) 327 { 328 insertHandler(evt.getProperty('cells'), null, null, null, null, true, true); 329 }); 330 331 graph.addListener('textInserted', function(sender, evt) 332 { 333 insertHandler(evt.getProperty('cells'), true); 334 }); 335 336 this.insertHandler = insertHandler; 337 338 this.createDivs(); 339 this.createUi(); 340 this.refresh(); 341 342 // Disables HTML and text selection 343 var textEditing = mxUtils.bind(this, function(evt) 344 { 345 if (evt == null) 346 { 347 evt = window.event; 348 } 349 350 return graph.isEditing() || (evt != null && this.isSelectionAllowed(evt)); 351 }); 352 353 // Disables text selection while not editing and no dialog visible 354 if (this.container == document.body) 355 { 356 this.menubarContainer.onselectstart = textEditing; 357 this.menubarContainer.onmousedown = textEditing; 358 this.toolbarContainer.onselectstart = textEditing; 359 this.toolbarContainer.onmousedown = textEditing; 360 this.diagramContainer.onselectstart = textEditing; 361 this.diagramContainer.onmousedown = textEditing; 362 this.sidebarContainer.onselectstart = textEditing; 363 this.sidebarContainer.onmousedown = textEditing; 364 this.formatContainer.onselectstart = textEditing; 365 this.formatContainer.onmousedown = textEditing; 366 this.footerContainer.onselectstart = textEditing; 367 this.footerContainer.onmousedown = textEditing; 368 369 if (this.tabContainer != null) 370 { 371 // Mouse down is needed for drag and drop 372 this.tabContainer.onselectstart = textEditing; 373 } 374 } 375 376 // And uses built-in context menu while editing 377 if (!this.editor.chromeless || this.editor.editable) 378 { 379 // Allows context menu for links in hints 380 var linkHandler = function(evt) 381 { 382 if (evt != null) 383 { 384 var source = mxEvent.getSource(evt); 385 386 if (source.nodeName == 'A') 387 { 388 while (source != null) 389 { 390 if (source.className == 'geHint') 391 { 392 return true; 393 } 394 395 source = source.parentNode; 396 } 397 } 398 } 399 400 return textEditing(evt); 401 }; 402 403 if (mxClient.IS_IE && (typeof(document.documentMode) === 'undefined' || document.documentMode < 9)) 404 { 405 mxEvent.addListener(this.diagramContainer, 'contextmenu', linkHandler); 406 } 407 else 408 { 409 // Allows browser context menu outside of diagram and sidebar 410 this.diagramContainer.oncontextmenu = linkHandler; 411 } 412 } 413 else 414 { 415 graph.panningHandler.usePopupTrigger = false; 416 } 417 418 // Contains the main graph instance inside the given panel 419 graph.init(this.diagramContainer); 420 421 // Improves line wrapping for in-place editor 422 if (mxClient.IS_SVG && graph.view.getDrawPane() != null) 423 { 424 var root = graph.view.getDrawPane().ownerSVGElement; 425 426 if (root != null) 427 { 428 root.style.position = 'absolute'; 429 } 430 } 431 432 // Creates hover icons 433 this.hoverIcons = this.createHoverIcons(); 434 435 // Hides hover icons when cells are moved 436 if (graph.graphHandler != null) 437 { 438 var graphHandlerStart = graph.graphHandler.start; 439 440 graph.graphHandler.start = function() 441 { 442 if (ui.hoverIcons != null) 443 { 444 ui.hoverIcons.reset(); 445 } 446 447 graphHandlerStart.apply(this, arguments); 448 }; 449 } 450 451 // Adds tooltip when mouse is over scrollbars to show space-drag panning option 452 mxEvent.addListener(this.diagramContainer, 'mousemove', mxUtils.bind(this, function(evt) 453 { 454 var off = mxUtils.getOffset(this.diagramContainer); 455 456 if (mxEvent.getClientX(evt) - off.x - this.diagramContainer.clientWidth > 0 || 457 mxEvent.getClientY(evt) - off.y - this.diagramContainer.clientHeight > 0) 458 { 459 this.diagramContainer.setAttribute('title', mxResources.get('panTooltip')); 460 } 461 else 462 { 463 this.diagramContainer.removeAttribute('title'); 464 } 465 })); 466 467 // Escape key hides dialogs, adds space+drag panning 468 var spaceKeyPressed = false; 469 470 // Overrides hovericons to disable while space key is pressed 471 var hoverIconsIsResetEvent = this.hoverIcons.isResetEvent; 472 473 this.hoverIcons.isResetEvent = function(evt, allowShift) 474 { 475 return spaceKeyPressed || hoverIconsIsResetEvent.apply(this, arguments); 476 }; 477 478 this.keydownHandler = mxUtils.bind(this, function(evt) 479 { 480 if (evt.which == 32 /* Space */ && !graph.isEditing()) 481 { 482 spaceKeyPressed = true; 483 this.hoverIcons.reset(); 484 graph.container.style.cursor = 'move'; 485 486 // Disables scroll after space keystroke with scrollbars 487 if (!graph.isEditing() && mxEvent.getSource(evt) == graph.container) 488 { 489 mxEvent.consume(evt); 490 } 491 } 492 else if (!mxEvent.isConsumed(evt) && evt.keyCode == 27 /* Escape */) 493 { 494 this.hideDialog(null, true); 495 } 496 }); 497 498 mxEvent.addListener(document, 'keydown', this.keydownHandler); 499 500 this.keyupHandler = mxUtils.bind(this, function(evt) 501 { 502 graph.container.style.cursor = ''; 503 spaceKeyPressed = false; 504 }); 505 506 mxEvent.addListener(document, 'keyup', this.keyupHandler); 507 508 // Forces panning for middle and right mouse buttons 509 var panningHandlerIsForcePanningEvent = graph.panningHandler.isForcePanningEvent; 510 graph.panningHandler.isForcePanningEvent = function(me) 511 { 512 // Ctrl+left button is reported as right button in FF on Mac 513 return panningHandlerIsForcePanningEvent.apply(this, arguments) || 514 spaceKeyPressed || (mxEvent.isMouseEvent(me.getEvent()) && 515 (this.usePopupTrigger || !mxEvent.isPopupTrigger(me.getEvent())) && 516 ((!mxEvent.isControlDown(me.getEvent()) && 517 mxEvent.isRightMouseButton(me.getEvent())) || 518 mxEvent.isMiddleMouseButton(me.getEvent()))); 519 }; 520 521 // Ctrl/Cmd+Enter applies editing value except in Safari where Ctrl+Enter creates 522 // a new line (while Enter creates a new paragraph and Shift+Enter stops) 523 var cellEditorIsStopEditingEvent = graph.cellEditor.isStopEditingEvent; 524 graph.cellEditor.isStopEditingEvent = function(evt) 525 { 526 return cellEditorIsStopEditingEvent.apply(this, arguments) || 527 (evt.keyCode == 13 && ((!mxClient.IS_SF && mxEvent.isControlDown(evt)) || 528 (mxClient.IS_MAC && mxEvent.isMetaDown(evt)) || 529 (mxClient.IS_SF && mxEvent.isShiftDown(evt)))); 530 }; 531 532 // Adds space+wheel for zoom 533 var graphIsZoomWheelEvent = graph.isZoomWheelEvent; 534 535 graph.isZoomWheelEvent = function() 536 { 537 return spaceKeyPressed || graphIsZoomWheelEvent.apply(this, arguments); 538 }; 539 540 // Switches toolbar for text editing 541 var textMode = false; 542 var fontMenu = null; 543 var sizeMenu = null; 544 var nodes = null; 545 546 var updateToolbar = mxUtils.bind(this, function() 547 { 548 if (this.toolbar != null && textMode != graph.cellEditor.isContentEditing()) 549 { 550 var node = this.toolbar.container.firstChild; 551 var newNodes = []; 552 553 while (node != null) 554 { 555 var tmp = node.nextSibling; 556 557 if (mxUtils.indexOf(this.toolbar.staticElements, node) < 0) 558 { 559 node.parentNode.removeChild(node); 560 newNodes.push(node); 561 } 562 563 node = tmp; 564 } 565 566 // Saves references to special items 567 var tmp1 = this.toolbar.fontMenu; 568 var tmp2 = this.toolbar.sizeMenu; 569 570 if (nodes == null) 571 { 572 this.toolbar.createTextToolbar(); 573 } 574 else 575 { 576 for (var i = 0; i < nodes.length; i++) 577 { 578 this.toolbar.container.appendChild(nodes[i]); 579 } 580 581 // Restores references to special items 582 this.toolbar.fontMenu = fontMenu; 583 this.toolbar.sizeMenu = sizeMenu; 584 } 585 586 textMode = graph.cellEditor.isContentEditing(); 587 fontMenu = tmp1; 588 sizeMenu = tmp2; 589 nodes = newNodes; 590 } 591 }); 592 593 var ui = this; 594 595 // Overrides cell editor to update toolbar 596 var cellEditorStartEditing = graph.cellEditor.startEditing; 597 graph.cellEditor.startEditing = function() 598 { 599 cellEditorStartEditing.apply(this, arguments); 600 updateToolbar(); 601 602 if (graph.cellEditor.isContentEditing()) 603 { 604 var updating = false; 605 606 var updateCssHandler = function() 607 { 608 if (!updating) 609 { 610 updating = true; 611 612 window.setTimeout(function() 613 { 614 var node = graph.getSelectedEditingElement(); 615 616 if (node != null) 617 { 618 var css = mxUtils.getCurrentStyle(node); 619 620 if (css != null && ui.toolbar != null) 621 { 622 ui.toolbar.setFontName(Graph.stripQuotes(css.fontFamily)); 623 ui.toolbar.setFontSize(parseInt(css.fontSize)); 624 } 625 } 626 627 updating = false; 628 }, 0); 629 } 630 }; 631 632 mxEvent.addListener(graph.cellEditor.textarea, 'input', updateCssHandler) 633 mxEvent.addListener(graph.cellEditor.textarea, 'touchend', updateCssHandler); 634 mxEvent.addListener(graph.cellEditor.textarea, 'mouseup', updateCssHandler); 635 mxEvent.addListener(graph.cellEditor.textarea, 'keyup', updateCssHandler); 636 updateCssHandler(); 637 } 638 }; 639 640 // Updates toolbar and handles possible errors 641 var cellEditorStopEditing = graph.cellEditor.stopEditing; 642 graph.cellEditor.stopEditing = function(cell, trigger) 643 { 644 try 645 { 646 cellEditorStopEditing.apply(this, arguments); 647 updateToolbar(); 648 } 649 catch (e) 650 { 651 ui.handleError(e); 652 } 653 }; 654 655 // Enables scrollbars and sets cursor style for the container 656 graph.container.setAttribute('tabindex', '0'); 657 graph.container.style.cursor = 'default'; 658 659 // Workaround for page scroll if embedded via iframe 660 if (window.self === window.top && graph.container.parentNode != null) 661 { 662 try 663 { 664 graph.container.focus(); 665 } 666 catch (e) 667 { 668 // ignores error in old versions of IE 669 } 670 } 671 672 // Keeps graph container focused on mouse down 673 var graphFireMouseEvent = graph.fireMouseEvent; 674 graph.fireMouseEvent = function(evtName, me, sender) 675 { 676 if (evtName == mxEvent.MOUSE_DOWN) 677 { 678 this.container.focus(); 679 } 680 681 graphFireMouseEvent.apply(this, arguments); 682 }; 683 684 // Configures automatic expand on mouseover 685 graph.popupMenuHandler.autoExpand = true; 686 687 // Installs context menu 688 if (this.menus != null) 689 { 690 graph.popupMenuHandler.factoryMethod = mxUtils.bind(this, function(menu, cell, evt) 691 { 692 this.menus.createPopupMenu(menu, cell, evt); 693 }); 694 } 695 696 // Hides context menu 697 mxEvent.addGestureListeners(document, mxUtils.bind(this, function(evt) 698 { 699 graph.popupMenuHandler.hideMenu(); 700 })); 701 702 // Create handler for key events 703 this.keyHandler = this.createKeyHandler(editor); 704 705 // Getter for key handler 706 this.getKeyHandler = function() 707 { 708 return keyHandler; 709 }; 710 711 graph.connectionHandler.addListener(mxEvent.CONNECT, function(sender, evt) 712 { 713 var cells = [evt.getProperty('cell')]; 714 715 if (evt.getProperty('terminalInserted')) 716 { 717 cells.push(evt.getProperty('terminal')); 718 719 window.setTimeout(function() 720 { 721 if (ui.hoverIcons != null) 722 { 723 ui.hoverIcons.update(graph.view.getState(cells[cells.length - 1])); 724 } 725 }, 0); 726 } 727 728 insertHandler(cells); 729 }); 730 731 this.addListener('styleChanged', mxUtils.bind(this, function(sender, evt) 732 { 733 // Checks if edges and/or vertices were modified 734 var cells = evt.getProperty('cells'); 735 var vertex = false; 736 var edge = false; 737 738 if (cells.length > 0) 739 { 740 for (var i = 0; i < cells.length; i++) 741 { 742 vertex = graph.getModel().isVertex(cells[i]) || vertex; 743 edge = graph.getModel().isEdge(cells[i]) || edge; 744 745 if (edge && vertex) 746 { 747 break; 748 } 749 } 750 } 751 else 752 { 753 vertex = true; 754 edge = true; 755 } 756 757 var keys = evt.getProperty('keys'); 758 var values = evt.getProperty('values'); 759 760 for (var i = 0; i < keys.length; i++) 761 { 762 var common = mxUtils.indexOf(valueStyles, keys[i]) >= 0; 763 764 // Ignores transparent stroke colors 765 if (keys[i] != 'strokeColor' || values[i] != null && values[i] != 'none') 766 { 767 // Special case: Edge style and shape 768 if (mxUtils.indexOf(connectStyles, keys[i]) >= 0) 769 { 770 if (edge || mxUtils.indexOf(alwaysEdgeStyles, keys[i]) >= 0) 771 { 772 if (values[i] == null) 773 { 774 delete graph.currentEdgeStyle[keys[i]]; 775 } 776 else 777 { 778 graph.currentEdgeStyle[keys[i]] = values[i]; 779 } 780 } 781 // Uses style for vertex if defined in styles 782 else if (vertex && mxUtils.indexOf(styles, keys[i]) >= 0) 783 { 784 if (values[i] == null) 785 { 786 delete graph.currentVertexStyle[keys[i]]; 787 } 788 else 789 { 790 graph.currentVertexStyle[keys[i]] = values[i]; 791 } 792 } 793 } 794 else if (mxUtils.indexOf(styles, keys[i]) >= 0) 795 { 796 if (vertex || common) 797 { 798 if (values[i] == null) 799 { 800 delete graph.currentVertexStyle[keys[i]]; 801 } 802 else 803 { 804 graph.currentVertexStyle[keys[i]] = values[i]; 805 } 806 } 807 808 if (edge || common || mxUtils.indexOf(alwaysEdgeStyles, keys[i]) >= 0) 809 { 810 if (values[i] == null) 811 { 812 delete graph.currentEdgeStyle[keys[i]]; 813 } 814 else 815 { 816 graph.currentEdgeStyle[keys[i]] = values[i]; 817 } 818 } 819 } 820 } 821 } 822 823 if (this.toolbar != null) 824 { 825 this.toolbar.setFontName(graph.currentVertexStyle['fontFamily'] || Menus.prototype.defaultFont); 826 this.toolbar.setFontSize(graph.currentVertexStyle['fontSize'] || Menus.prototype.defaultFontSize); 827 828 if (this.toolbar.edgeStyleMenu != null) 829 { 830 // Updates toolbar icon for edge style 831 var edgeStyleDiv = this.toolbar.edgeStyleMenu.getElementsByTagName('div')[0]; 832 833 if (graph.currentEdgeStyle['edgeStyle'] == 'orthogonalEdgeStyle' && graph.currentEdgeStyle['curved'] == '1') 834 { 835 edgeStyleDiv.className = 'geSprite geSprite-curved'; 836 } 837 else if (graph.currentEdgeStyle['edgeStyle'] == 'straight' || graph.currentEdgeStyle['edgeStyle'] == 'none' || 838 graph.currentEdgeStyle['edgeStyle'] == null) 839 { 840 edgeStyleDiv.className = 'geSprite geSprite-straight'; 841 } 842 else if (graph.currentEdgeStyle['edgeStyle'] == 'entityRelationEdgeStyle') 843 { 844 edgeStyleDiv.className = 'geSprite geSprite-entity'; 845 } 846 else if (graph.currentEdgeStyle['edgeStyle'] == 'elbowEdgeStyle') 847 { 848 edgeStyleDiv.className = 'geSprite geSprite-' + ((graph.currentEdgeStyle['elbow'] == 'vertical') ? 849 'verticalelbow' : 'horizontalelbow'); 850 } 851 else if (graph.currentEdgeStyle['edgeStyle'] == 'isometricEdgeStyle') 852 { 853 edgeStyleDiv.className = 'geSprite geSprite-' + ((graph.currentEdgeStyle['elbow'] == 'vertical') ? 854 'verticalisometric' : 'horizontalisometric'); 855 } 856 else 857 { 858 edgeStyleDiv.className = 'geSprite geSprite-orthogonal'; 859 } 860 } 861 862 if (this.toolbar.edgeShapeMenu != null) 863 { 864 // Updates icon for edge shape 865 var edgeShapeDiv = this.toolbar.edgeShapeMenu.getElementsByTagName('div')[0]; 866 867 if (graph.currentEdgeStyle['shape'] == 'link') 868 { 869 edgeShapeDiv.className = 'geSprite geSprite-linkedge'; 870 } 871 else if (graph.currentEdgeStyle['shape'] == 'flexArrow') 872 { 873 edgeShapeDiv.className = 'geSprite geSprite-arrow'; 874 } 875 else if (graph.currentEdgeStyle['shape'] == 'arrow') 876 { 877 edgeShapeDiv.className = 'geSprite geSprite-simplearrow'; 878 } 879 else 880 { 881 edgeShapeDiv.className = 'geSprite geSprite-connection'; 882 } 883 } 884 885 // Updates icon for optinal line start shape 886 if (this.toolbar.lineStartMenu != null) 887 { 888 var lineStartDiv = this.toolbar.lineStartMenu.getElementsByTagName('div')[0]; 889 890 lineStartDiv.className = this.getCssClassForMarker('start', 891 graph.currentEdgeStyle['shape'], graph.currentEdgeStyle[mxConstants.STYLE_STARTARROW], 892 mxUtils.getValue(graph.currentEdgeStyle, 'startFill', '1')); 893 } 894 895 // Updates icon for optinal line end shape 896 if (this.toolbar.lineEndMenu != null) 897 { 898 var lineEndDiv = this.toolbar.lineEndMenu.getElementsByTagName('div')[0]; 899 900 lineEndDiv.className = this.getCssClassForMarker('end', 901 graph.currentEdgeStyle['shape'], graph.currentEdgeStyle[mxConstants.STYLE_ENDARROW], 902 mxUtils.getValue(graph.currentEdgeStyle, 'endFill', '1')); 903 } 904 } 905 })); 906 907 // Update font size and font family labels 908 if (this.toolbar != null) 909 { 910 var update = mxUtils.bind(this, function() 911 { 912 var ff = graph.currentVertexStyle['fontFamily'] || 'Helvetica'; 913 var fs = String(graph.currentVertexStyle['fontSize'] || '12'); 914 var state = graph.getView().getState(graph.getSelectionCell()); 915 916 if (state != null) 917 { 918 ff = state.style[mxConstants.STYLE_FONTFAMILY] || ff; 919 fs = state.style[mxConstants.STYLE_FONTSIZE] || fs; 920 921 if (ff.length > 10) 922 { 923 ff = ff.substring(0, 8) + '...'; 924 } 925 } 926 927 this.toolbar.setFontName(ff); 928 this.toolbar.setFontSize(fs); 929 }); 930 931 graph.getSelectionModel().addListener(mxEvent.CHANGE, update); 932 graph.getModel().addListener(mxEvent.CHANGE, update); 933 } 934 935 // Makes sure the current layer is visible when cells are added 936 graph.addListener(mxEvent.CELLS_ADDED, function(sender, evt) 937 { 938 var cells = evt.getProperty('cells'); 939 var parent = evt.getProperty('parent'); 940 941 if (parent != null && graph.getModel().isLayer(parent) && 942 !graph.isCellVisible(parent) && cells != null && 943 cells.length > 0) 944 { 945 graph.getModel().setVisible(parent, true); 946 } 947 }); 948 949 // Global handler to hide the current menu 950 this.gestureHandler = mxUtils.bind(this, function(evt) 951 { 952 if (this.currentMenu != null && mxEvent.getSource(evt) != this.currentMenu.div) 953 { 954 this.hideCurrentMenu(); 955 } 956 }); 957 958 mxEvent.addGestureListeners(document, this.gestureHandler); 959 960 // Updates the editor UI after the window has been resized or the orientation changes 961 // Timeout is workaround for old IE versions which have a delay for DOM client sizes. 962 // Should not use delay > 0 to avoid handle multiple repaints during window resize 963 this.resizeHandler = mxUtils.bind(this, function() 964 { 965 window.setTimeout(mxUtils.bind(this, function() 966 { 967 if (this.editor.graph != null) 968 { 969 this.refresh(); 970 } 971 }), 0); 972 }); 973 974 mxEvent.addListener(window, 'resize', this.resizeHandler); 975 976 this.orientationChangeHandler = mxUtils.bind(this, function() 977 { 978 this.refresh(); 979 }); 980 981 mxEvent.addListener(window, 'orientationchange', this.orientationChangeHandler); 982 983 // Workaround for bug on iOS see 984 // http://stackoverflow.com/questions/19012135/ios-7-ipad-safari-landscape-innerheight-outerheight-layout-issue 985 if (mxClient.IS_IOS && !window.navigator.standalone) 986 { 987 this.scrollHandler = mxUtils.bind(this, function() 988 { 989 window.scrollTo(0, 0); 990 }); 991 992 mxEvent.addListener(window, 'scroll', this.scrollHandler); 993 } 994 995 /** 996 * Sets the initial scrollbar locations after a file was loaded. 997 */ 998 this.editor.addListener('resetGraphView', mxUtils.bind(this, function() 999 { 1000 this.resetScrollbars(); 1001 })); 1002 1003 /** 1004 * Repaints the grid. 1005 */ 1006 this.addListener('gridEnabledChanged', mxUtils.bind(this, function() 1007 { 1008 graph.view.validateBackground(); 1009 })); 1010 1011 this.addListener('backgroundColorChanged', mxUtils.bind(this, function() 1012 { 1013 graph.view.validateBackground(); 1014 })); 1015 1016 /** 1017 * Repaints the grid. 1018 */ 1019 graph.addListener('gridSizeChanged', mxUtils.bind(this, function() 1020 { 1021 if (graph.isGridEnabled()) 1022 { 1023 graph.view.validateBackground(); 1024 } 1025 })); 1026 1027 // Resets UI, updates action and menu states 1028 this.editor.resetGraph(); 1029 } 1030 1031 this.init(); 1032 1033 if (!graph.standalone) 1034 { 1035 this.open(); 1036 } 1037}; 1038 1039// Extends mxEventSource 1040mxUtils.extend(EditorUi, mxEventSource); 1041 1042/** 1043 * Global config that specifies if the compact UI elements should be used. 1044 */ 1045EditorUi.compactUi = true; 1046 1047/** 1048 * Specifies the size of the split bar. 1049 */ 1050EditorUi.prototype.splitSize = (mxClient.IS_TOUCH || mxClient.IS_POINTER) ? 12 : 8; 1051 1052/** 1053 * Specifies the height of the menubar. Default is 30. 1054 */ 1055EditorUi.prototype.menubarHeight = 30; 1056 1057/** 1058 * Specifies the width of the format panel should be enabled. Default is true. 1059 */ 1060EditorUi.prototype.formatEnabled = true; 1061 1062/** 1063 * Specifies the width of the format panel. Default is 240. 1064 */ 1065EditorUi.prototype.formatWidth = 240; 1066 1067/** 1068 * Specifies the height of the toolbar. Default is 38. 1069 */ 1070EditorUi.prototype.toolbarHeight = 38; 1071 1072/** 1073 * Specifies the height of the footer. Default is 28. 1074 */ 1075EditorUi.prototype.footerHeight = 28; 1076 1077/** 1078 * Specifies the height of the optional sidebarFooterContainer. Default is 34. 1079 */ 1080EditorUi.prototype.sidebarFooterHeight = 34; 1081 1082/** 1083 * Specifies the position of the horizontal split bar. Default is 240 or 118 for 1084 * screen widths <= 640px. 1085 */ 1086EditorUi.prototype.hsplitPosition = (screen.width <= 640) ? 118 : ((urlParams['sidebar-entries'] != 'large') ? 212 : 240); 1087 1088/** 1089 * Specifies if animations are allowed in <executeLayout>. Default is true. 1090 */ 1091EditorUi.prototype.allowAnimation = true; 1092 1093/** 1094 * Default is 2. 1095 */ 1096EditorUi.prototype.lightboxMaxFitScale = 2; 1097 1098/** 1099 * Default is 4. 1100 */ 1101EditorUi.prototype.lightboxVerticalDivider = 4; 1102 1103/** 1104 * Specifies if single click on horizontal split should collapse sidebar. Default is false. 1105 */ 1106EditorUi.prototype.hsplitClickEnabled = false; 1107 1108/** 1109 * Installs the listeners to update the action states. 1110 */ 1111EditorUi.prototype.init = function() 1112{ 1113 var graph = this.editor.graph; 1114 1115 if (!graph.standalone) 1116 { 1117 if (urlParams['shape-picker'] != '0') 1118 { 1119 this.installShapePicker(); 1120 } 1121 1122 // Hides tooltips and connection points when scrolling 1123 mxEvent.addListener(graph.container, 'scroll', mxUtils.bind(this, function() 1124 { 1125 graph.tooltipHandler.hide(); 1126 1127 if (graph.connectionHandler != null && graph.connectionHandler.constraintHandler != null) 1128 { 1129 graph.connectionHandler.constraintHandler.reset(); 1130 } 1131 })); 1132 1133 // Hides tooltip on escape 1134 graph.addListener(mxEvent.ESCAPE, mxUtils.bind(this, function() 1135 { 1136 graph.tooltipHandler.hide(); 1137 var rb = graph.getRubberband(); 1138 1139 if (rb != null) 1140 { 1141 rb.cancel(); 1142 } 1143 })); 1144 1145 mxEvent.addListener(graph.container, 'keydown', mxUtils.bind(this, function(evt) 1146 { 1147 this.onKeyDown(evt); 1148 })); 1149 1150 mxEvent.addListener(graph.container, 'keypress', mxUtils.bind(this, function(evt) 1151 { 1152 this.onKeyPress(evt); 1153 })); 1154 1155 // Updates action states 1156 this.addUndoListener(); 1157 this.addBeforeUnloadListener(); 1158 1159 graph.getSelectionModel().addListener(mxEvent.CHANGE, mxUtils.bind(this, function() 1160 { 1161 this.updateActionStates(); 1162 })); 1163 1164 graph.getModel().addListener(mxEvent.CHANGE, mxUtils.bind(this, function() 1165 { 1166 this.updateActionStates(); 1167 })); 1168 1169 // Changes action states after change of default parent 1170 var graphSetDefaultParent = graph.setDefaultParent; 1171 var ui = this; 1172 1173 this.editor.graph.setDefaultParent = function() 1174 { 1175 graphSetDefaultParent.apply(this, arguments); 1176 ui.updateActionStates(); 1177 }; 1178 1179 // Hack to make editLink available in vertex handler 1180 graph.editLink = ui.actions.get('editLink').funct; 1181 1182 this.updateActionStates(); 1183 this.initClipboard(); 1184 this.initCanvas(); 1185 1186 if (this.format != null) 1187 { 1188 this.format.init(); 1189 } 1190 } 1191}; 1192 1193/** 1194 * Returns true if the given event should start editing. This implementation returns true. 1195 */ 1196EditorUi.prototype.installShapePicker = function() 1197{ 1198 var graph = this.editor.graph; 1199 var ui = this; 1200 1201 // Uses this event to process mouseDown to check the selection state before it is changed 1202 graph.addListener(mxEvent.FIRE_MOUSE_EVENT, mxUtils.bind(this, function(sender, evt) 1203 { 1204 if (evt.getProperty('eventName') == 'mouseDown') 1205 { 1206 ui.hideShapePicker(); 1207 } 1208 })); 1209 1210 var hidePicker = mxUtils.bind(this, function() 1211 { 1212 ui.hideShapePicker(true); 1213 }); 1214 1215 graph.addListener('wheel', hidePicker); 1216 graph.addListener(mxEvent.ESCAPE, hidePicker); 1217 graph.view.addListener(mxEvent.SCALE, hidePicker); 1218 graph.view.addListener(mxEvent.SCALE_AND_TRANSLATE, hidePicker); 1219 graph.getSelectionModel().addListener(mxEvent.CHANGE, hidePicker); 1220 graph.getModel().addListener(mxEvent.CHANGE, hidePicker); 1221 1222 // Counts as popup menu 1223 var popupMenuHandlerIsMenuShowing = graph.popupMenuHandler.isMenuShowing; 1224 1225 graph.popupMenuHandler.isMenuShowing = function() 1226 { 1227 return popupMenuHandlerIsMenuShowing.apply(this, arguments) || ui.shapePicker != null; 1228 }; 1229 1230 // Adds dbl click dialog for inserting shapes 1231 var graphDblClick = graph.dblClick; 1232 1233 graph.dblClick = function(evt, cell) 1234 { 1235 if (this.isEnabled()) 1236 { 1237 if (cell == null && ui.sidebar != null && !mxEvent.isShiftDown(evt) && 1238 !graph.isCellLocked(graph.getDefaultParent())) 1239 { 1240 var pt = mxUtils.convertPoint(this.container, mxEvent.getClientX(evt), mxEvent.getClientY(evt)); 1241 mxEvent.consume(evt); 1242 1243 // Asynchronous to avoid direct insert after double tap 1244 window.setTimeout(mxUtils.bind(this, function() 1245 { 1246 ui.showShapePicker(pt.x, pt.y); 1247 }), 30); 1248 } 1249 else 1250 { 1251 graphDblClick.apply(this, arguments); 1252 } 1253 } 1254 }; 1255 1256 if (this.hoverIcons != null) 1257 { 1258 this.hoverIcons.addListener('reset', hidePicker); 1259 var hoverIconsDrag = this.hoverIcons.drag; 1260 1261 this.hoverIcons.drag = function() 1262 { 1263 ui.hideShapePicker(); 1264 hoverIconsDrag.apply(this, arguments); 1265 }; 1266 1267 var hoverIconsExecute = this.hoverIcons.execute; 1268 1269 this.hoverIcons.execute = function(state, dir, me) 1270 { 1271 var evt = me.getEvent(); 1272 1273 if (!this.graph.isCloneEvent(evt) && !mxEvent.isShiftDown(evt)) 1274 { 1275 this.graph.connectVertex(state.cell, dir, this.graph.defaultEdgeLength, evt, null, null, mxUtils.bind(this, function(x, y, execute) 1276 { 1277 var temp = graph.getCompositeParent(state.cell); 1278 var geo = graph.getCellGeometry(temp); 1279 me.consume(); 1280 1281 while (temp != null && graph.model.isVertex(temp) && geo != null && geo.relative) 1282 { 1283 cell = temp; 1284 temp = graph.model.getParent(cell) 1285 geo = graph.getCellGeometry(temp); 1286 } 1287 1288 // Asynchronous to avoid direct insert after double tap 1289 window.setTimeout(mxUtils.bind(this, function() 1290 { 1291 ui.showShapePicker(me.getGraphX(), me.getGraphY(), temp, mxUtils.bind(this, function(cell) 1292 { 1293 execute(cell); 1294 1295 if (ui.hoverIcons != null) 1296 { 1297 ui.hoverIcons.update(graph.view.getState(cell)); 1298 } 1299 }), dir); 1300 }), 30); 1301 }), mxUtils.bind(this, function(result) 1302 { 1303 this.graph.selectCellsForConnectVertex(result, evt, this); 1304 })); 1305 } 1306 else 1307 { 1308 hoverIconsExecute.apply(this, arguments); 1309 } 1310 }; 1311 1312 var thread = null; 1313 1314 this.hoverIcons.addListener('focus', mxUtils.bind(this, function(sender, evt) 1315 { 1316 if (thread != null) 1317 { 1318 window.clearTimeout(thread); 1319 } 1320 1321 thread = window.setTimeout(mxUtils.bind(this, function() 1322 { 1323 var arrow = evt.getProperty('arrow'); 1324 var dir = evt.getProperty('direction'); 1325 var mouseEvent = evt.getProperty('event'); 1326 1327 var rect = arrow.getBoundingClientRect(); 1328 var offset = mxUtils.getOffset(graph.container); 1329 var x = graph.container.scrollLeft + rect.x - offset.x; 1330 var y = graph.container.scrollTop + rect.y - offset.y; 1331 1332 var temp = graph.getCompositeParent((this.hoverIcons.currentState != null) ? 1333 this.hoverIcons.currentState.cell : null); 1334 var div = ui.showShapePicker(x, y, temp, mxUtils.bind(this, function(cell) 1335 { 1336 if (cell != null) 1337 { 1338 graph.connectVertex(temp, dir, graph.defaultEdgeLength, mouseEvent, true, true, function(x, y, execute) 1339 { 1340 execute(cell); 1341 1342 if (ui.hoverIcons != null) 1343 { 1344 ui.hoverIcons.update(graph.view.getState(cell)); 1345 } 1346 }, function(cells) 1347 { 1348 graph.selectCellsForConnectVertex(cells); 1349 }, mouseEvent, this.hoverIcons); 1350 } 1351 }), dir, true); 1352 1353 this.centerShapePicker(div, rect, x, y, dir); 1354 mxUtils.setOpacity(div, 30); 1355 1356 mxEvent.addListener(div, 'mouseenter', function() 1357 { 1358 mxUtils.setOpacity(div, 100); 1359 }); 1360 1361 mxEvent.addListener(div, 'mouseleave', function() 1362 { 1363 ui.hideShapePicker(); 1364 }); 1365 }), Editor.shapePickerHoverDelay); 1366 })); 1367 1368 this.hoverIcons.addListener('blur', mxUtils.bind(this, function(sender, evt) 1369 { 1370 if (thread != null) 1371 { 1372 window.clearTimeout(thread); 1373 } 1374 })); 1375 } 1376}; 1377 1378/** 1379 * Creates a temporary graph instance for rendering off-screen content. 1380 */ 1381EditorUi.prototype.centerShapePicker = function(div, rect, x, y, dir) 1382{ 1383 if (dir == mxConstants.DIRECTION_EAST || dir == mxConstants.DIRECTION_WEST) 1384 { 1385 div.style.width = '40px'; 1386 } 1387 1388 var r2 = div.getBoundingClientRect(); 1389 1390 if (dir == mxConstants.DIRECTION_NORTH) 1391 { 1392 x -= r2.width / 2 - 10; 1393 y -= r2.height + 6; 1394 } 1395 else if (dir == mxConstants.DIRECTION_SOUTH) 1396 { 1397 x -= r2.width / 2 - 10; 1398 y += rect.height + 6; 1399 } 1400 else if (dir == mxConstants.DIRECTION_WEST) 1401 { 1402 x -= r2.width + 6; 1403 y -= r2.height / 2 - 10; 1404 } 1405 else if (dir == mxConstants.DIRECTION_EAST) 1406 { 1407 x += rect.width + 6; 1408 y -= r2.height / 2 - 10; 1409 } 1410 1411 div.style.left = x + 'px'; 1412 div.style.top = y + 'px'; 1413}; 1414 1415/** 1416 * Creates a temporary graph instance for rendering off-screen content. 1417 */ 1418EditorUi.prototype.showShapePicker = function(x, y, source, callback, direction, hovering) 1419{ 1420 var div = this.createShapePicker(x, y, source, callback, direction, mxUtils.bind(this, function() 1421 { 1422 this.hideShapePicker(); 1423 }), this.getCellsForShapePicker(source, hovering), hovering); 1424 1425 if (div != null) 1426 { 1427 if (this.hoverIcons != null && !hovering) 1428 { 1429 this.hoverIcons.reset(); 1430 } 1431 1432 var graph = this.editor.graph; 1433 graph.popupMenuHandler.hideMenu(); 1434 graph.tooltipHandler.hideTooltip(); 1435 this.hideCurrentMenu(); 1436 this.hideShapePicker(); 1437 1438 this.shapePickerCallback = callback; 1439 this.shapePicker = div; 1440 } 1441 1442 return div; 1443}; 1444 1445/** 1446 * Creates a temporary graph instance for rendering off-screen content. 1447 */ 1448EditorUi.prototype.createShapePicker = function(x, y, source, callback, direction, afterClick, cells, hovering) 1449{ 1450 var div = null; 1451 1452 if (cells != null && cells.length > 0) 1453 { 1454 var ui = this; 1455 var graph = this.editor.graph; 1456 div = document.createElement('div'); 1457 var sourceState = graph.view.getState(source); 1458 var style = (source != null && (sourceState == null || 1459 !graph.isTransparentState(sourceState))) ? 1460 graph.copyStyle(source) : null; 1461 1462 // Do not place entry under pointer for touch devices 1463 var w = (cells.length < 6) ? cells.length * 35 : 140; 1464 div.className = 'geToolbarContainer geSidebarContainer'; 1465 div.style.cssText = 'position:absolute;left:' + x + 'px;top:' + y + 1466 'px;width:' + w + 'px;border-radius:10px;padding:4px;text-align:center;' + 1467 'box-shadow:0px 0px 3px 1px #d1d1d1;padding: 6px 0 8px 0;' + 1468 'z-index: ' + mxPopupMenu.prototype.zIndex + 1 + ';'; 1469 1470 if (!hovering) 1471 { 1472 mxUtils.setPrefixedStyle(div.style, 'transform', 'translate(-22px,-22px)'); 1473 } 1474 1475 if (graph.background != null && graph.background != mxConstants.NONE) 1476 { 1477 div.style.backgroundColor = graph.background; 1478 } 1479 1480 graph.container.appendChild(div); 1481 1482 var addCell = mxUtils.bind(this, function(cell) 1483 { 1484 // Wrapper needed to catch events 1485 var node = document.createElement('a'); 1486 node.className = 'geItem'; 1487 node.style.cssText = 'position:relative;display:inline-block;position:relative;' + 1488 'width:30px;height:30px;cursor:pointer;overflow:hidden;padding:3px 0 0 3px;'; 1489 div.appendChild(node); 1490 1491 if (style != null && urlParams['sketch'] != '1') 1492 { 1493 this.sidebar.graph.pasteStyle(style, [cell]); 1494 } 1495 else 1496 { 1497 ui.insertHandler([cell], cell.value != '' && urlParams['sketch'] != '1', this.sidebar.graph.model); 1498 } 1499 1500 this.sidebar.createThumb([cell], 25, 25, node, null, true, false, cell.geometry.width, cell.geometry.height); 1501 1502 mxEvent.addListener(node, 'click', function() 1503 { 1504 var clone = graph.cloneCell(cell); 1505 1506 if (callback != null) 1507 { 1508 callback(clone); 1509 } 1510 else 1511 { 1512 clone.geometry.x = graph.snap(Math.round(x / graph.view.scale) - 1513 graph.view.translate.x - cell.geometry.width / 2); 1514 clone.geometry.y = graph.snap(Math.round(y / graph.view.scale) - 1515 graph.view.translate.y - cell.geometry.height / 2); 1516 1517 graph.model.beginUpdate(); 1518 try 1519 { 1520 graph.addCell(clone); 1521 } 1522 finally 1523 { 1524 graph.model.endUpdate(); 1525 } 1526 1527 graph.setSelectionCell(clone); 1528 graph.scrollCellToVisible(clone); 1529 graph.startEditingAtCell(clone); 1530 1531 if (ui.hoverIcons != null) 1532 { 1533 ui.hoverIcons.update(graph.view.getState(clone)); 1534 } 1535 } 1536 1537 if (afterClick != null) 1538 { 1539 afterClick(); 1540 } 1541 }); 1542 }); 1543 1544 for (var i = 0; i < (hovering ? Math.min(cells.length, 4) : cells.length); i++) 1545 { 1546 addCell(cells[i]); 1547 } 1548 1549 var b = graph.container.scrollTop + graph.container.offsetHeight; 1550 var dy = div.offsetTop + div.clientHeight - b; 1551 1552 if (dy > 0) 1553 { 1554 div.style.top = Math.max(graph.container.scrollTop + 22, y - dy) + 'px'; 1555 } 1556 1557 var r = graph.container.scrollLeft + graph.container.offsetWidth; 1558 var dx = div.offsetLeft + div.clientWidth - r; 1559 1560 if (dx > 0) 1561 { 1562 div.style.left = Math.max(graph.container.scrollLeft + 22, x - dx) + 'px'; 1563 } 1564 } 1565 1566 return div; 1567}; 1568 1569/** 1570 * Creates a temporary graph instance for rendering off-screen content. 1571 */ 1572EditorUi.prototype.getCellsForShapePicker = function(cell, hovering) 1573{ 1574 var createVertex = mxUtils.bind(this, function(style, w, h, value) 1575 { 1576 return this.editor.graph.createVertex(null, null, value || '', 0, 0, w || 120, h || 60, style, false); 1577 }); 1578 1579 return [(cell != null) ? this.editor.graph.cloneCell(cell) : 1580 createVertex('text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;', 40, 20, 'Text'), 1581 createVertex('whiteSpace=wrap;html=1;'), 1582 createVertex('ellipse;whiteSpace=wrap;html=1;'), 1583 createVertex('rhombus;whiteSpace=wrap;html=1;', 80, 80), 1584 createVertex('rounded=1;whiteSpace=wrap;html=1;'), 1585 createVertex('shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;'), 1586 createVertex('shape=trapezoid;perimeter=trapezoidPerimeter;whiteSpace=wrap;html=1;fixedSize=1;', 120, 60), 1587 createVertex('shape=hexagon;perimeter=hexagonPerimeter2;whiteSpace=wrap;html=1;fixedSize=1;', 120, 80), 1588 createVertex('shape=step;perimeter=stepPerimeter;whiteSpace=wrap;html=1;fixedSize=1;', 120, 80), 1589 createVertex('shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;'), 1590 createVertex('triangle;whiteSpace=wrap;html=1;', 60, 80), 1591 createVertex('shape=document;whiteSpace=wrap;html=1;boundedLbl=1;', 120, 80), 1592 createVertex('shape=tape;whiteSpace=wrap;html=1;', 120, 100), 1593 createVertex('ellipse;shape=cloud;whiteSpace=wrap;html=1;', 120, 80), 1594 createVertex('shape=singleArrow;whiteSpace=wrap;html=1;arrowWidth=0.4;arrowSize=0.4;', 80, 60), 1595 createVertex('shape=waypoint;sketch=0;size=6;pointerEvents=1;points=[];fillColor=none;resizable=0;rotatable=0;perimeter=centerPerimeter;snapToPoint=1;', 40, 40)]; 1596}; 1597 1598/** 1599 * Creates a temporary graph instance for rendering off-screen content. 1600 */ 1601EditorUi.prototype.hideShapePicker = function(cancel) 1602{ 1603 if (this.shapePicker != null) 1604 { 1605 this.shapePicker.parentNode.removeChild(this.shapePicker); 1606 this.shapePicker = null; 1607 1608 if (!cancel && this.shapePickerCallback != null) 1609 { 1610 this.shapePickerCallback(); 1611 } 1612 1613 this.shapePickerCallback = null; 1614 } 1615}; 1616 1617/** 1618 * Returns true if the given event should start editing. This implementation returns true. 1619 */ 1620EditorUi.prototype.onKeyDown = function(evt) 1621{ 1622 var graph = this.editor.graph; 1623 1624 // Alt+tab for task switcher in Windows, ctrl+tab for tab control in Chrome 1625 if (evt.which == 9 && graph.isEnabled() && !mxEvent.isControlDown(evt)) 1626 { 1627 if (graph.isEditing()) 1628 { 1629 if (mxEvent.isAltDown(evt)) 1630 { 1631 graph.stopEditing(false); 1632 } 1633 else 1634 { 1635 try 1636 { 1637 if (graph.cellEditor.isContentEditing() && graph.cellEditor.isTextSelected()) 1638 { 1639 // (Shift+)tab indents/outdents with text selection 1640 document.execCommand(mxEvent.isShiftDown(evt) ? 'outdent' : 'indent', false, null); 1641 } 1642 // Shift+tab applies value with cursor 1643 else if (mxEvent.isShiftDown(evt)) 1644 { 1645 graph.stopEditing(false); 1646 } 1647 else 1648 { 1649 // Inserts tab character 1650 graph.cellEditor.insertTab(!graph.cellEditor.isContentEditing() ? 4 : null); 1651 } 1652 } 1653 catch (e) 1654 { 1655 // ignore 1656 } 1657 } 1658 } 1659 else if (mxEvent.isAltDown(evt)) 1660 { 1661 graph.selectParentCell(); 1662 } 1663 else 1664 { 1665 graph.selectCell(!mxEvent.isShiftDown(evt)); 1666 } 1667 1668 mxEvent.consume(evt); 1669 } 1670}; 1671 1672/** 1673 * Returns true if the given event should start editing. This implementation returns true. 1674 */ 1675EditorUi.prototype.onKeyPress = function(evt) 1676{ 1677 var graph = this.editor.graph; 1678 1679 // KNOWN: Focus does not work if label is empty in quirks mode 1680 if (this.isImmediateEditingEvent(evt) && !graph.isEditing() && !graph.isSelectionEmpty() && evt.which !== 0 && 1681 evt.which !== 27 && !mxEvent.isAltDown(evt) && !mxEvent.isControlDown(evt) && !mxEvent.isMetaDown(evt)) 1682 { 1683 graph.escape(); 1684 graph.startEditing(); 1685 1686 // Workaround for FF where char is lost if cursor is placed before char 1687 if (mxClient.IS_FF) 1688 { 1689 var ce = graph.cellEditor; 1690 1691 if (ce.textarea != null) 1692 { 1693 ce.textarea.innerHTML = String.fromCharCode(evt.which); 1694 1695 // Moves cursor to end of textarea 1696 var range = document.createRange(); 1697 range.selectNodeContents(ce.textarea); 1698 range.collapse(false); 1699 var sel = window.getSelection(); 1700 sel.removeAllRanges(); 1701 sel.addRange(range); 1702 } 1703 } 1704 } 1705}; 1706 1707/** 1708 * Returns true if the given event should start editing. This implementation returns true. 1709 */ 1710EditorUi.prototype.isImmediateEditingEvent = function(evt) 1711{ 1712 return true; 1713}; 1714 1715/** 1716 * Private helper method. 1717 */ 1718EditorUi.prototype.getCssClassForMarker = function(prefix, shape, marker, fill) 1719{ 1720 var result = ''; 1721 1722 if (shape == 'flexArrow') 1723 { 1724 result = (marker != null && marker != mxConstants.NONE) ? 1725 'geSprite geSprite-' + prefix + 'blocktrans' : 'geSprite geSprite-noarrow'; 1726 } 1727 else 1728 { 1729 // SVG marker sprites 1730 if (marker == 'box' || marker == 'halfCircle') 1731 { 1732 result = 'geSprite geSvgSprite geSprite-' + marker + ((prefix == 'end') ? ' geFlipSprite' : ''); 1733 } 1734 else if (marker == mxConstants.ARROW_CLASSIC) 1735 { 1736 result = (fill == '1') ? 'geSprite geSprite-' + prefix + 'classic' : 'geSprite geSprite-' + prefix + 'classictrans'; 1737 } 1738 else if (marker == mxConstants.ARROW_CLASSIC_THIN) 1739 { 1740 result = (fill == '1') ? 'geSprite geSprite-' + prefix + 'classicthin' : 'geSprite geSprite-' + prefix + 'classicthintrans'; 1741 } 1742 else if (marker == mxConstants.ARROW_OPEN) 1743 { 1744 result = 'geSprite geSprite-' + prefix + 'open'; 1745 } 1746 else if (marker == mxConstants.ARROW_OPEN_THIN) 1747 { 1748 result = 'geSprite geSprite-' + prefix + 'openthin'; 1749 } 1750 else if (marker == mxConstants.ARROW_BLOCK) 1751 { 1752 result = (fill == '1') ? 'geSprite geSprite-' + prefix + 'block' : 'geSprite geSprite-' + prefix + 'blocktrans'; 1753 } 1754 else if (marker == mxConstants.ARROW_BLOCK_THIN) 1755 { 1756 result = (fill == '1') ? 'geSprite geSprite-' + prefix + 'blockthin' : 'geSprite geSprite-' + prefix + 'blockthintrans'; 1757 } 1758 else if (marker == mxConstants.ARROW_OVAL) 1759 { 1760 result = (fill == '1') ? 'geSprite geSprite-' + prefix + 'oval' : 'geSprite geSprite-' + prefix + 'ovaltrans'; 1761 } 1762 else if (marker == mxConstants.ARROW_DIAMOND) 1763 { 1764 result = (fill == '1') ? 'geSprite geSprite-' + prefix + 'diamond' : 'geSprite geSprite-' + prefix + 'diamondtrans'; 1765 } 1766 else if (marker == mxConstants.ARROW_DIAMOND_THIN) 1767 { 1768 result = (fill == '1') ? 'geSprite geSprite-' + prefix + 'thindiamond' : 'geSprite geSprite-' + prefix + 'thindiamondtrans'; 1769 } 1770 else if (marker == 'openAsync') 1771 { 1772 result = 'geSprite geSprite-' + prefix + 'openasync'; 1773 } 1774 else if (marker == 'dash') 1775 { 1776 result = 'geSprite geSprite-' + prefix + 'dash'; 1777 } 1778 else if (marker == 'cross') 1779 { 1780 result = 'geSprite geSprite-' + prefix + 'cross'; 1781 } 1782 else if (marker == 'async') 1783 { 1784 result = (fill == '1') ? 'geSprite geSprite-' + prefix + 'async' : 'geSprite geSprite-' + prefix + 'asynctrans'; 1785 } 1786 else if (marker == 'circle' || marker == 'circlePlus') 1787 { 1788 result = (fill == '1' || marker == 'circle') ? 'geSprite geSprite-' + prefix + 'circle' : 'geSprite geSprite-' + prefix + 'circleplus'; 1789 } 1790 else if (marker == 'ERone') 1791 { 1792 result = 'geSprite geSprite-' + prefix + 'erone'; 1793 } 1794 else if (marker == 'ERmandOne') 1795 { 1796 result = 'geSprite geSprite-' + prefix + 'eronetoone'; 1797 } 1798 else if (marker == 'ERmany') 1799 { 1800 result = 'geSprite geSprite-' + prefix + 'ermany'; 1801 } 1802 else if (marker == 'ERoneToMany') 1803 { 1804 result = 'geSprite geSprite-' + prefix + 'eronetomany'; 1805 } 1806 else if (marker == 'ERzeroToOne') 1807 { 1808 result = 'geSprite geSprite-' + prefix + 'eroneopt'; 1809 } 1810 else if (marker == 'ERzeroToMany') 1811 { 1812 result = 'geSprite geSprite-' + prefix + 'ermanyopt'; 1813 } 1814 else 1815 { 1816 result = 'geSprite geSprite-noarrow'; 1817 } 1818 } 1819 1820 return result; 1821}; 1822 1823/** 1824 * Overridden in Menus.js 1825 */ 1826EditorUi.prototype.createMenus = function() 1827{ 1828 return null; 1829}; 1830 1831/** 1832 * Hook for allowing selection and context menu for certain events. 1833 */ 1834EditorUi.prototype.updatePasteActionStates = function() 1835{ 1836 var graph = this.editor.graph; 1837 var paste = this.actions.get('paste'); 1838 var pasteHere = this.actions.get('pasteHere'); 1839 1840 paste.setEnabled(this.editor.graph.cellEditor.isContentEditing() || 1841 (((!mxClient.IS_FF && navigator.clipboard != null) || !mxClipboard.isEmpty()) && 1842 graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent()))); 1843 pasteHere.setEnabled(paste.isEnabled()); 1844}; 1845 1846/** 1847 * Hook for allowing selection and context menu for certain events. 1848 */ 1849EditorUi.prototype.initClipboard = function() 1850{ 1851 var ui = this; 1852 1853 var mxClipboardCut = mxClipboard.cut; 1854 mxClipboard.cut = function(graph) 1855 { 1856 if (graph.cellEditor.isContentEditing()) 1857 { 1858 document.execCommand('cut', false, null); 1859 } 1860 else 1861 { 1862 mxClipboardCut.apply(this, arguments); 1863 } 1864 1865 ui.updatePasteActionStates(); 1866 }; 1867 1868 var mxClipboardCopy = mxClipboard.copy; 1869 mxClipboard.copy = function(graph) 1870 { 1871 var result = null; 1872 1873 if (graph.cellEditor.isContentEditing()) 1874 { 1875 document.execCommand('copy', false, null); 1876 } 1877 else 1878 { 1879 result = result || graph.getSelectionCells(); 1880 result = graph.getExportableCells(graph.model.getTopmostCells(result)); 1881 1882 var cloneMap = new Object(); 1883 var lookup = graph.createCellLookup(result); 1884 var clones = graph.cloneCells(result, null, cloneMap); 1885 1886 // Uses temporary model to force new IDs to be assigned 1887 // to avoid having to carry over the mapping from object 1888 // ID to cell ID to the paste operation 1889 var model = new mxGraphModel(); 1890 var parent = model.getChildAt(model.getRoot(), 0); 1891 1892 for (var i = 0; i < clones.length; i++) 1893 { 1894 model.add(parent, clones[i]); 1895 1896 // Checks for orphaned relative children and makes absolute 1897 var state = graph.view.getState(result[i]); 1898 1899 if (state != null) 1900 { 1901 var geo = graph.getCellGeometry(clones[i]); 1902 1903 if (geo != null && geo.relative && !model.isEdge(result[i]) && 1904 lookup[mxObjectIdentity.get(model.getParent(result[i]))] == null) 1905 { 1906 geo.offset = null; 1907 geo.relative = false; 1908 geo.x = state.x / state.view.scale - state.view.translate.x; 1909 geo.y = state.y / state.view.scale - state.view.translate.y; 1910 } 1911 } 1912 } 1913 1914 graph.updateCustomLinks(graph.createCellMapping(cloneMap, lookup), clones); 1915 1916 mxClipboard.insertCount = 1; 1917 mxClipboard.setCells(clones); 1918 } 1919 1920 ui.updatePasteActionStates(); 1921 1922 return result; 1923 }; 1924 1925 var mxClipboardPaste = mxClipboard.paste; 1926 mxClipboard.paste = function(graph) 1927 { 1928 var result = null; 1929 1930 if (graph.cellEditor.isContentEditing()) 1931 { 1932 document.execCommand('paste', false, null); 1933 } 1934 else 1935 { 1936 result = mxClipboardPaste.apply(this, arguments); 1937 } 1938 1939 ui.updatePasteActionStates(); 1940 1941 return result; 1942 }; 1943 1944 // Overrides cell editor to update paste action state 1945 var cellEditorStartEditing = this.editor.graph.cellEditor.startEditing; 1946 1947 this.editor.graph.cellEditor.startEditing = function() 1948 { 1949 cellEditorStartEditing.apply(this, arguments); 1950 ui.updatePasteActionStates(); 1951 }; 1952 1953 var cellEditorStopEditing = this.editor.graph.cellEditor.stopEditing; 1954 1955 this.editor.graph.cellEditor.stopEditing = function(cell, trigger) 1956 { 1957 cellEditorStopEditing.apply(this, arguments); 1958 ui.updatePasteActionStates(); 1959 }; 1960 1961 this.updatePasteActionStates(); 1962}; 1963 1964/** 1965 * Delay between zoom steps when not using preview. 1966 */ 1967EditorUi.prototype.lazyZoomDelay = 20; 1968 1969/** 1970 * Delay before update of DOM when using preview. 1971 */ 1972EditorUi.prototype.wheelZoomDelay = 400; 1973 1974/** 1975 * Delay before update of DOM when using preview. 1976 */ 1977EditorUi.prototype.buttonZoomDelay = 600; 1978 1979/** 1980 * Initializes the infinite canvas. 1981 */ 1982EditorUi.prototype.initCanvas = function() 1983{ 1984 // Initial page layout view, scrollBuffer and timer-based scrolling 1985 var graph = this.editor.graph; 1986 graph.timerAutoScroll = true; 1987 1988 /** 1989 * Returns the padding for pages in page view with scrollbars. 1990 */ 1991 graph.getPagePadding = function() 1992 { 1993 return new mxPoint(Math.max(0, Math.round((graph.container.offsetWidth - 34) / graph.view.scale)), 1994 Math.max(0, Math.round((graph.container.offsetHeight - 34) / graph.view.scale))); 1995 }; 1996 1997 // Fits the number of background pages to the graph 1998 graph.view.getBackgroundPageBounds = function() 1999 { 2000 var layout = this.graph.getPageLayout(); 2001 var page = this.graph.getPageSize(); 2002 2003 return new mxRectangle(this.scale * (this.translate.x + layout.x * page.width), 2004 this.scale * (this.translate.y + layout.y * page.height), 2005 this.scale * layout.width * page.width, 2006 this.scale * layout.height * page.height); 2007 }; 2008 2009 graph.getPreferredPageSize = function(bounds, width, height) 2010 { 2011 var pages = this.getPageLayout(); 2012 var size = this.getPageSize(); 2013 2014 return new mxRectangle(0, 0, pages.width * size.width, pages.height * size.height); 2015 }; 2016 2017 // Scales pages/graph to fit available size 2018 var resize = null; 2019 var ui = this; 2020 2021 if (this.editor.isChromelessView()) 2022 { 2023 resize = mxUtils.bind(this, function(autoscale, maxScale, cx, cy) 2024 { 2025 if (graph.container != null && !graph.isViewer()) 2026 { 2027 cx = (cx != null) ? cx : 0; 2028 cy = (cy != null) ? cy : 0; 2029 2030 var bds = (graph.pageVisible) ? graph.view.getBackgroundPageBounds() : graph.getGraphBounds(); 2031 var scroll = mxUtils.hasScrollbars(graph.container); 2032 var tr = graph.view.translate; 2033 var s = graph.view.scale; 2034 2035 // Normalizes the bounds 2036 var b = mxRectangle.fromRectangle(bds); 2037 b.x = b.x / s - tr.x; 2038 b.y = b.y / s - tr.y; 2039 b.width /= s; 2040 b.height /= s; 2041 2042 var st = graph.container.scrollTop; 2043 var sl = graph.container.scrollLeft; 2044 var sb = (document.documentMode >= 8) ? 20 : 14; 2045 2046 if (document.documentMode == 8 || document.documentMode == 9) 2047 { 2048 sb += 3; 2049 } 2050 2051 var cw = graph.container.offsetWidth - sb; 2052 var ch = graph.container.offsetHeight - sb; 2053 2054 var ns = (autoscale) ? Math.max(0.3, Math.min(maxScale || 1, cw / b.width)) : s; 2055 var dx = ((cw - ns * b.width) / 2) / ns; 2056 var dy = (this.lightboxVerticalDivider == 0) ? 0 : ((ch - ns * b.height) / this.lightboxVerticalDivider) / ns; 2057 2058 if (scroll) 2059 { 2060 dx = Math.max(dx, 0); 2061 dy = Math.max(dy, 0); 2062 } 2063 2064 if (scroll || bds.width < cw || bds.height < ch) 2065 { 2066 graph.view.scaleAndTranslate(ns, Math.floor(dx - b.x), Math.floor(dy - b.y)); 2067 graph.container.scrollTop = st * ns / s; 2068 graph.container.scrollLeft = sl * ns / s; 2069 } 2070 else if (cx != 0 || cy != 0) 2071 { 2072 var t = graph.view.translate; 2073 graph.view.setTranslate(Math.floor(t.x + cx / s), Math.floor(t.y + cy / s)); 2074 } 2075 } 2076 }); 2077 2078 // Hack to make function available to subclassers 2079 this.chromelessResize = resize; 2080 2081 // Hook for subclassers for override 2082 this.chromelessWindowResize = mxUtils.bind(this, function() 2083 { 2084 this.chromelessResize(false); 2085 }); 2086 2087 // Removable resize listener 2088 var autoscaleResize = mxUtils.bind(this, function() 2089 { 2090 this.chromelessWindowResize(false); 2091 }); 2092 2093 mxEvent.addListener(window, 'resize', autoscaleResize); 2094 2095 this.destroyFunctions.push(function() 2096 { 2097 mxEvent.removeListener(window, 'resize', autoscaleResize); 2098 }); 2099 2100 this.editor.addListener('resetGraphView', mxUtils.bind(this, function() 2101 { 2102 this.chromelessResize(true); 2103 })); 2104 2105 this.actions.get('zoomIn').funct = mxUtils.bind(this, function(evt) 2106 { 2107 graph.zoomIn(); 2108 this.chromelessResize(false); 2109 }); 2110 this.actions.get('zoomOut').funct = mxUtils.bind(this, function(evt) 2111 { 2112 graph.zoomOut(); 2113 this.chromelessResize(false); 2114 }); 2115 2116 // Creates toolbar for viewer - do not use CSS here 2117 // as this may be used in a viewer that has no CSS 2118 if (urlParams['toolbar'] != '0') 2119 { 2120 var toolbarConfig = JSON.parse(decodeURIComponent(urlParams['toolbar-config'] || '{}')); 2121 2122 this.chromelessToolbar = document.createElement('div'); 2123 this.chromelessToolbar.style.position = 'fixed'; 2124 this.chromelessToolbar.style.overflow = 'hidden'; 2125 this.chromelessToolbar.style.boxSizing = 'border-box'; 2126 this.chromelessToolbar.style.whiteSpace = 'nowrap'; 2127 this.chromelessToolbar.style.padding = '10px 10px 8px 10px'; 2128 this.chromelessToolbar.style.left = (graph.isViewer()) ? '0' : '50%'; 2129 2130 if (!mxClient.IS_IE && !mxClient.IS_IE11) 2131 { 2132 this.chromelessToolbar.style.backgroundColor = '#000000'; 2133 } 2134 else 2135 { 2136 this.chromelessToolbar.style.backgroundColor = '#ffffff'; 2137 this.chromelessToolbar.style.border = '3px solid black'; 2138 } 2139 2140 mxUtils.setPrefixedStyle(this.chromelessToolbar.style, 'borderRadius', '16px'); 2141 mxUtils.setPrefixedStyle(this.chromelessToolbar.style, 'transition', 'opacity 600ms ease-in-out'); 2142 2143 var updateChromelessToolbarPosition = mxUtils.bind(this, function() 2144 { 2145 var css = mxUtils.getCurrentStyle(graph.container); 2146 2147 if (graph.isViewer()) 2148 { 2149 this.chromelessToolbar.style.top = '0'; 2150 } 2151 else 2152 { 2153 this.chromelessToolbar.style.bottom = ((css != null) ? parseInt(css['margin-bottom'] || 0) : 0) + 2154 ((this.tabContainer != null) ? (20 + parseInt(this.tabContainer.style.height)) : 20) + 'px'; 2155 } 2156 }); 2157 2158 this.editor.addListener('resetGraphView', updateChromelessToolbarPosition); 2159 updateChromelessToolbarPosition(); 2160 2161 var btnCount = 0; 2162 2163 var addButton = mxUtils.bind(this, function(fn, imgSrc, tip) 2164 { 2165 btnCount++; 2166 2167 var a = document.createElement('span'); 2168 a.style.paddingLeft = '8px'; 2169 a.style.paddingRight = '8px'; 2170 a.style.cursor = 'pointer'; 2171 mxEvent.addListener(a, 'click', fn); 2172 2173 if (tip != null) 2174 { 2175 a.setAttribute('title', tip); 2176 } 2177 2178 var img = document.createElement('img'); 2179 img.setAttribute('border', '0'); 2180 img.setAttribute('src', imgSrc); 2181 img.style.width = '36px'; 2182 img.style.filter = 'invert(100%)'; 2183 2184 a.appendChild(img); 2185 this.chromelessToolbar.appendChild(a); 2186 2187 return a; 2188 }); 2189 2190 if (toolbarConfig.backBtn != null) 2191 { 2192 addButton(mxUtils.bind(this, function(evt) 2193 { 2194 window.location.href = toolbarConfig.backBtn.url; 2195 mxEvent.consume(evt); 2196 }), Editor.backImage, mxResources.get('back', null, 'Back')); 2197 } 2198 2199 if (this.isPagesEnabled()) 2200 { 2201 var prevButton = addButton(mxUtils.bind(this, function(evt) 2202 { 2203 this.actions.get('previousPage').funct(); 2204 mxEvent.consume(evt); 2205 }), Editor.previousImage, mxResources.get('previousPage')); 2206 2207 var pageInfo = document.createElement('div'); 2208 pageInfo.style.fontFamily = Editor.defaultHtmlFont; 2209 pageInfo.style.display = 'inline-block'; 2210 pageInfo.style.verticalAlign = 'top'; 2211 pageInfo.style.fontWeight = 'bold'; 2212 pageInfo.style.marginTop = '8px'; 2213 pageInfo.style.fontSize = '14px'; 2214 2215 if (!mxClient.IS_IE && !mxClient.IS_IE11) 2216 { 2217 pageInfo.style.color = '#ffffff'; 2218 } 2219 else 2220 { 2221 pageInfo.style.color = '#000000'; 2222 } 2223 2224 this.chromelessToolbar.appendChild(pageInfo); 2225 2226 var nextButton = addButton(mxUtils.bind(this, function(evt) 2227 { 2228 this.actions.get('nextPage').funct(); 2229 mxEvent.consume(evt); 2230 }), Editor.nextImage, mxResources.get('nextPage')); 2231 2232 var updatePageInfo = mxUtils.bind(this, function() 2233 { 2234 if (this.pages != null && this.pages.length > 1 && this.currentPage != null) 2235 { 2236 pageInfo.innerHTML = ''; 2237 mxUtils.write(pageInfo, (mxUtils.indexOf(this.pages, this.currentPage) + 1) + ' / ' + this.pages.length); 2238 } 2239 }); 2240 2241 prevButton.style.paddingLeft = '0px'; 2242 prevButton.style.paddingRight = '4px'; 2243 nextButton.style.paddingLeft = '4px'; 2244 nextButton.style.paddingRight = '0px'; 2245 2246 var updatePageButtons = mxUtils.bind(this, function() 2247 { 2248 if (this.pages != null && this.pages.length > 1 && this.currentPage != null) 2249 { 2250 nextButton.style.display = ''; 2251 prevButton.style.display = ''; 2252 pageInfo.style.display = 'inline-block'; 2253 } 2254 else 2255 { 2256 nextButton.style.display = 'none'; 2257 prevButton.style.display = 'none'; 2258 pageInfo.style.display = 'none'; 2259 } 2260 2261 updatePageInfo(); 2262 }); 2263 2264 this.editor.addListener('resetGraphView', updatePageButtons); 2265 this.editor.addListener('pageSelected', updatePageInfo); 2266 } 2267 2268 addButton(mxUtils.bind(this, function(evt) 2269 { 2270 this.actions.get('zoomOut').funct(); 2271 mxEvent.consume(evt); 2272 }), Editor.zoomOutImage, mxResources.get('zoomOut') + ' (Alt+Mousewheel)'); 2273 2274 addButton(mxUtils.bind(this, function(evt) 2275 { 2276 this.actions.get('zoomIn').funct(); 2277 mxEvent.consume(evt); 2278 }), Editor.zoomInImage, mxResources.get('zoomIn') + ' (Alt+Mousewheel)'); 2279 2280 addButton(mxUtils.bind(this, function(evt) 2281 { 2282 if (graph.isLightboxView()) 2283 { 2284 if (graph.view.scale == 1) 2285 { 2286 this.lightboxFit(); 2287 } 2288 else 2289 { 2290 graph.zoomTo(1); 2291 } 2292 2293 this.chromelessResize(false); 2294 } 2295 else 2296 { 2297 this.chromelessResize(true); 2298 } 2299 2300 mxEvent.consume(evt); 2301 }), Editor.zoomFitImage, mxResources.get('fit')); 2302 2303 // Changes toolbar opacity on hover 2304 var fadeThread = null; 2305 var fadeThread2 = null; 2306 2307 var fadeOut = mxUtils.bind(this, function(delay) 2308 { 2309 if (fadeThread != null) 2310 { 2311 window.clearTimeout(fadeThread); 2312 fadeThread = null; 2313 } 2314 2315 if (fadeThread2 != null) 2316 { 2317 window.clearTimeout(fadeThread2); 2318 fadeThread2 = null; 2319 } 2320 2321 fadeThread = window.setTimeout(mxUtils.bind(this, function() 2322 { 2323 mxUtils.setOpacity(this.chromelessToolbar, 0); 2324 fadeThread = null; 2325 2326 fadeThread2 = window.setTimeout(mxUtils.bind(this, function() 2327 { 2328 this.chromelessToolbar.style.display = 'none'; 2329 fadeThread2 = null; 2330 }), 600); 2331 }), delay || 200); 2332 }); 2333 2334 var fadeIn = mxUtils.bind(this, function(opacity) 2335 { 2336 if (fadeThread != null) 2337 { 2338 window.clearTimeout(fadeThread); 2339 fadeThread = null; 2340 } 2341 2342 if (fadeThread2 != null) 2343 { 2344 window.clearTimeout(fadeThread2); 2345 fadeThread2 = null; 2346 } 2347 2348 this.chromelessToolbar.style.display = ''; 2349 mxUtils.setOpacity(this.chromelessToolbar, opacity || 30); 2350 }); 2351 2352 if (urlParams['layers'] == '1') 2353 { 2354 this.layersDialog = null; 2355 2356 var layersButton = addButton(mxUtils.bind(this, function(evt) 2357 { 2358 if (this.layersDialog != null) 2359 { 2360 this.layersDialog.parentNode.removeChild(this.layersDialog); 2361 this.layersDialog = null; 2362 } 2363 else 2364 { 2365 this.layersDialog = graph.createLayersDialog(null, true); 2366 2367 mxEvent.addListener(this.layersDialog, 'mouseleave', mxUtils.bind(this, function() 2368 { 2369 this.layersDialog.parentNode.removeChild(this.layersDialog); 2370 this.layersDialog = null; 2371 })); 2372 2373 var r = layersButton.getBoundingClientRect(); 2374 2375 mxUtils.setPrefixedStyle(this.layersDialog.style, 'borderRadius', '5px'); 2376 this.layersDialog.style.position = 'fixed'; 2377 this.layersDialog.style.fontFamily = Editor.defaultHtmlFont; 2378 this.layersDialog.style.width = '160px'; 2379 this.layersDialog.style.padding = '4px 2px 4px 2px'; 2380 this.layersDialog.style.left = r.left + 'px'; 2381 this.layersDialog.style.bottom = parseInt(this.chromelessToolbar.style.bottom) + 2382 this.chromelessToolbar.offsetHeight + 4 + 'px'; 2383 2384 if (!mxClient.IS_IE && !mxClient.IS_IE11) 2385 { 2386 this.layersDialog.style.backgroundColor = '#000000'; 2387 this.layersDialog.style.color = '#ffffff'; 2388 mxUtils.setOpacity(this.layersDialog, 80); 2389 } 2390 else 2391 { 2392 this.layersDialog.style.backgroundColor = '#ffffff'; 2393 this.layersDialog.style.border = '2px solid black'; 2394 this.layersDialog.style.color = '#000000'; 2395 } 2396 2397 // Puts the dialog on top of the container z-index 2398 var style = mxUtils.getCurrentStyle(this.editor.graph.container); 2399 this.layersDialog.style.zIndex = style.zIndex; 2400 2401 document.body.appendChild(this.layersDialog); 2402 this.editor.fireEvent(new mxEventObject('layersDialogShown')); 2403 } 2404 2405 mxEvent.consume(evt); 2406 }), Editor.layersImage, mxResources.get('layers')); 2407 2408 // Shows/hides layers button depending on content 2409 var model = graph.getModel(); 2410 2411 model.addListener(mxEvent.CHANGE, function() 2412 { 2413 layersButton.style.display = (model.getChildCount(model.root) > 1) ? '' : 'none'; 2414 }); 2415 } 2416 2417 if (urlParams['openInSameWin'] != '1' || navigator.standalone) 2418 { 2419 this.addChromelessToolbarItems(addButton); 2420 } 2421 2422 if (this.editor.editButtonLink != null || this.editor.editButtonFunc != null) 2423 { 2424 addButton(mxUtils.bind(this, function(evt) 2425 { 2426 if (this.editor.editButtonFunc != null) 2427 { 2428 this.editor.editButtonFunc(); 2429 } 2430 else if (this.editor.editButtonLink == '_blank') 2431 { 2432 this.editor.editAsNew(this.getEditBlankXml()); 2433 } 2434 else 2435 { 2436 graph.openLink(this.editor.editButtonLink, 'editWindow'); 2437 } 2438 2439 mxEvent.consume(evt); 2440 }), Editor.editImage, mxResources.get('edit')); 2441 } 2442 2443 if (this.lightboxToolbarActions != null) 2444 { 2445 for (var i = 0; i < this.lightboxToolbarActions.length; i++) 2446 { 2447 var lbAction = this.lightboxToolbarActions[i]; 2448 lbAction.elem = addButton(lbAction.fn, lbAction.icon, lbAction.tooltip); 2449 } 2450 } 2451 2452 if (toolbarConfig.refreshBtn != null) 2453 { 2454 addButton(mxUtils.bind(this, function(evt) 2455 { 2456 if (toolbarConfig.refreshBtn.url) 2457 { 2458 window.location.href = toolbarConfig.refreshBtn.url; 2459 } 2460 else 2461 { 2462 window.location.reload(); 2463 } 2464 2465 mxEvent.consume(evt); 2466 }), Editor.refreshImage, mxResources.get('refresh', null, 'Refresh')); 2467 } 2468 2469 if (toolbarConfig.fullscreenBtn != null && window.self !== window.top) 2470 { 2471 addButton(mxUtils.bind(this, function(evt) 2472 { 2473 if (toolbarConfig.fullscreenBtn.url) 2474 { 2475 graph.openLink(toolbarConfig.fullscreenBtn.url); 2476 } 2477 else 2478 { 2479 graph.openLink(window.location.href); 2480 } 2481 2482 mxEvent.consume(evt); 2483 }), Editor.fullscreenImage, mxResources.get('openInNewWindow', null, 'Open in New Window')); 2484 } 2485 2486 if ((toolbarConfig.closeBtn && window.self === window.top) || 2487 (graph.lightbox && (urlParams['close'] == '1' || this.container != document.body))) 2488 { 2489 addButton(mxUtils.bind(this, function(evt) 2490 { 2491 if (urlParams['close'] == '1' || toolbarConfig.closeBtn) 2492 { 2493 window.close(); 2494 } 2495 else 2496 { 2497 this.destroy(); 2498 mxEvent.consume(evt); 2499 } 2500 }), Editor.closeImage, mxResources.get('close') + ' (Escape)'); 2501 } 2502 2503 // Initial state invisible 2504 this.chromelessToolbar.style.display = 'none'; 2505 2506 if (!graph.isViewer()) 2507 { 2508 mxUtils.setPrefixedStyle(this.chromelessToolbar.style, 'transform', 'translate(-50%,0)'); 2509 } 2510 2511 graph.container.appendChild(this.chromelessToolbar); 2512 2513 mxEvent.addListener(graph.container, (mxClient.IS_POINTER) ? 'pointermove' : 'mousemove', mxUtils.bind(this, function(evt) 2514 { 2515 if (!mxEvent.isTouchEvent(evt)) 2516 { 2517 if (!mxEvent.isShiftDown(evt)) 2518 { 2519 fadeIn(30); 2520 } 2521 2522 fadeOut(); 2523 } 2524 })); 2525 2526 mxEvent.addListener(this.chromelessToolbar, (mxClient.IS_POINTER) ? 'pointermove' : 'mousemove', function(evt) 2527 { 2528 mxEvent.consume(evt); 2529 }); 2530 2531 mxEvent.addListener(this.chromelessToolbar, 'mouseenter', mxUtils.bind(this, function(evt) 2532 { 2533 graph.tooltipHandler.resetTimer(); 2534 graph.tooltipHandler.hideTooltip(); 2535 2536 if (!mxEvent.isShiftDown(evt)) 2537 { 2538 fadeIn(100); 2539 } 2540 else 2541 { 2542 fadeOut(); 2543 } 2544 })); 2545 2546 mxEvent.addListener(this.chromelessToolbar, 'mousemove', mxUtils.bind(this, function(evt) 2547 { 2548 if (!mxEvent.isShiftDown(evt)) 2549 { 2550 fadeIn(100); 2551 } 2552 else 2553 { 2554 fadeOut(); 2555 } 2556 2557 mxEvent.consume(evt); 2558 })); 2559 2560 mxEvent.addListener(this.chromelessToolbar, 'mouseleave', mxUtils.bind(this, function(evt) 2561 { 2562 if (!mxEvent.isTouchEvent(evt)) 2563 { 2564 fadeIn(30); 2565 } 2566 })); 2567 2568 // Shows/hides toolbar for touch devices 2569 var tol = graph.getTolerance(); 2570 2571 graph.addMouseListener( 2572 { 2573 startX: 0, 2574 startY: 0, 2575 scrollLeft: 0, 2576 scrollTop: 0, 2577 mouseDown: function(sender, me) 2578 { 2579 this.startX = me.getGraphX(); 2580 this.startY = me.getGraphY(); 2581 this.scrollLeft = graph.container.scrollLeft; 2582 this.scrollTop = graph.container.scrollTop; 2583 }, 2584 mouseMove: function(sender, me) {}, 2585 mouseUp: function(sender, me) 2586 { 2587 if (mxEvent.isTouchEvent(me.getEvent())) 2588 { 2589 if ((Math.abs(this.scrollLeft - graph.container.scrollLeft) < tol && 2590 Math.abs(this.scrollTop - graph.container.scrollTop) < tol) && 2591 (Math.abs(this.startX - me.getGraphX()) < tol && 2592 Math.abs(this.startY - me.getGraphY()) < tol)) 2593 { 2594 if (parseFloat(ui.chromelessToolbar.style.opacity || 0) > 0) 2595 { 2596 fadeOut(); 2597 } 2598 else 2599 { 2600 fadeIn(30); 2601 } 2602 } 2603 } 2604 } 2605 }); 2606 } // end if toolbar 2607 2608 // Installs handling of highlight and handling links to relative links and anchors 2609 if (!this.editor.editable) 2610 { 2611 this.addChromelessClickHandler(); 2612 } 2613 } 2614 else if (this.editor.extendCanvas) 2615 { 2616 /** 2617 * Guesses autoTranslate to avoid another repaint (see below). 2618 * Works if only the scale of the graph changes or if pages 2619 * are visible and the visible pages do not change. 2620 */ 2621 var graphViewValidate = graph.view.validate; 2622 graph.view.validate = function() 2623 { 2624 if (this.graph.container != null && mxUtils.hasScrollbars(this.graph.container)) 2625 { 2626 var pad = this.graph.getPagePadding(); 2627 var size = this.graph.getPageSize(); 2628 2629 // Updating scrollbars here causes flickering in quirks and is not needed 2630 // if zoom method is always used to set the current scale on the graph. 2631 var tx = this.translate.x; 2632 var ty = this.translate.y; 2633 this.translate.x = pad.x - (this.x0 || 0) * size.width; 2634 this.translate.y = pad.y - (this.y0 || 0) * size.height; 2635 } 2636 2637 graphViewValidate.apply(this, arguments); 2638 }; 2639 2640 if (!graph.isViewer()) 2641 { 2642 var graphSizeDidChange = graph.sizeDidChange; 2643 2644 graph.sizeDidChange = function() 2645 { 2646 if (this.container != null && mxUtils.hasScrollbars(this.container)) 2647 { 2648 var pages = this.getPageLayout(); 2649 var pad = this.getPagePadding(); 2650 var size = this.getPageSize(); 2651 2652 // Updates the minimum graph size 2653 var minw = Math.ceil(2 * pad.x + pages.width * size.width); 2654 var minh = Math.ceil(2 * pad.y + pages.height * size.height); 2655 2656 var min = graph.minimumGraphSize; 2657 2658 // LATER: Fix flicker of scrollbar size in IE quirks mode 2659 // after delayed call in window.resize event handler 2660 if (min == null || min.width != minw || min.height != minh) 2661 { 2662 graph.minimumGraphSize = new mxRectangle(0, 0, minw, minh); 2663 } 2664 2665 // Updates auto-translate to include padding and graph size 2666 var dx = pad.x - pages.x * size.width; 2667 var dy = pad.y - pages.y * size.height; 2668 2669 if (!this.autoTranslate && (this.view.translate.x != dx || this.view.translate.y != dy)) 2670 { 2671 this.autoTranslate = true; 2672 this.view.x0 = pages.x; 2673 this.view.y0 = pages.y; 2674 2675 // NOTE: THIS INVOKES THIS METHOD AGAIN. UNFORTUNATELY THERE IS NO WAY AROUND THIS SINCE THE 2676 // BOUNDS ARE KNOWN AFTER THE VALIDATION AND SETTING THE TRANSLATE TRIGGERS A REVALIDATION. 2677 // SHOULD MOVE TRANSLATE/SCALE TO VIEW. 2678 var tx = graph.view.translate.x; 2679 var ty = graph.view.translate.y; 2680 graph.view.setTranslate(dx, dy); 2681 2682 // LATER: Fix rounding errors for small zoom 2683 graph.container.scrollLeft += Math.round((dx - tx) * graph.view.scale); 2684 graph.container.scrollTop += Math.round((dy - ty) * graph.view.scale); 2685 2686 this.autoTranslate = false; 2687 2688 return; 2689 } 2690 2691 graphSizeDidChange.apply(this, arguments); 2692 } 2693 else 2694 { 2695 // Fires event but does not invoke superclass 2696 this.fireEvent(new mxEventObject(mxEvent.SIZE, 'bounds', this.getGraphBounds())); 2697 } 2698 }; 2699 } 2700 } 2701 2702 // Accumulates the zoom factor while the rendering is taking place 2703 // so that not the complete sequence of zoom steps must be painted 2704 var bgGroup = graph.view.getBackgroundPane(); 2705 var mainGroup = graph.view.getDrawPane(); 2706 graph.cumulativeZoomFactor = 1; 2707 var updateZoomTimeout = null; 2708 var cursorPosition = null; 2709 var scrollPosition = null; 2710 var forcedZoom = null; 2711 var filter = null; 2712 2713 var scheduleZoom = function(delay) 2714 { 2715 if (updateZoomTimeout != null) 2716 { 2717 window.clearTimeout(updateZoomTimeout); 2718 } 2719 2720 window.setTimeout(function() 2721 { 2722 if (!graph.isMouseDown || forcedZoom) 2723 { 2724 updateZoomTimeout = window.setTimeout(mxUtils.bind(this, function() 2725 { 2726 if (graph.isFastZoomEnabled()) 2727 { 2728 // Transforms background page 2729 if (graph.view.backgroundPageShape != null && graph.view.backgroundPageShape.node != null) 2730 { 2731 mxUtils.setPrefixedStyle(graph.view.backgroundPageShape.node.style, 'transform-origin', null); 2732 mxUtils.setPrefixedStyle(graph.view.backgroundPageShape.node.style, 'transform', null); 2733 } 2734 2735 // Transforms graph and background image 2736 mainGroup.style.transformOrigin = ''; 2737 bgGroup.style.transformOrigin = ''; 2738 2739 // Workaround for no reset of transform in Safari 2740 if (mxClient.IS_SF) 2741 { 2742 mainGroup.style.transform = 'scale(1)'; 2743 bgGroup.style.transform = 'scale(1)'; 2744 2745 window.setTimeout(function() 2746 { 2747 mainGroup.style.transform = ''; 2748 bgGroup.style.transform = ''; 2749 }, 0) 2750 } 2751 else 2752 { 2753 mainGroup.style.transform = ''; 2754 bgGroup.style.transform = ''; 2755 } 2756 2757 // Shows interactive elements 2758 graph.view.getDecoratorPane().style.opacity = ''; 2759 graph.view.getOverlayPane().style.opacity = ''; 2760 } 2761 2762 var sp = new mxPoint(graph.container.scrollLeft, graph.container.scrollTop); 2763 var offset = mxUtils.getOffset(graph.container); 2764 var prev = graph.view.scale; 2765 var dx = 0; 2766 var dy = 0; 2767 2768 if (cursorPosition != null) 2769 { 2770 dx = graph.container.offsetWidth / 2 - cursorPosition.x + offset.x; 2771 dy = graph.container.offsetHeight / 2 - cursorPosition.y + offset.y; 2772 } 2773 2774 graph.zoom(graph.cumulativeZoomFactor); 2775 var s = graph.view.scale; 2776 2777 if (s != prev) 2778 { 2779 if (scrollPosition != null) 2780 { 2781 dx += sp.x - scrollPosition.x; 2782 dy += sp.y - scrollPosition.y; 2783 } 2784 2785 if (resize != null) 2786 { 2787 ui.chromelessResize(false, null, dx * (graph.cumulativeZoomFactor - 1), 2788 dy * (graph.cumulativeZoomFactor - 1)); 2789 } 2790 2791 if (mxUtils.hasScrollbars(graph.container) && (dx != 0 || dy != 0)) 2792 { 2793 graph.container.scrollLeft -= dx * (graph.cumulativeZoomFactor - 1); 2794 graph.container.scrollTop -= dy * (graph.cumulativeZoomFactor - 1); 2795 } 2796 } 2797 2798 if (filter != null) 2799 { 2800 mainGroup.setAttribute('filter', filter); 2801 } 2802 2803 graph.cumulativeZoomFactor = 1; 2804 updateZoomTimeout = null; 2805 scrollPosition = null; 2806 cursorPosition = null; 2807 forcedZoom = null; 2808 filter = null; 2809 }), (delay != null) ? delay : ((graph.isFastZoomEnabled()) ? ui.wheelZoomDelay : ui.lazyZoomDelay)); 2810 } 2811 }, 0); 2812 }; 2813 2814 var lastZoomEvent = Date.now(); 2815 2816 graph.lazyZoom = function(zoomIn, ignoreCursorPosition, delay) 2817 { 2818 // TODO: Fix ignored cursor position if scrollbars are disabled 2819 ignoreCursorPosition = ignoreCursorPosition || !graph.scrollbars; 2820 2821 if (ignoreCursorPosition) 2822 { 2823 cursorPosition = new mxPoint( 2824 graph.container.offsetLeft + graph.container.clientWidth / 2, 2825 graph.container.offsetTop + graph.container.clientHeight / 2); 2826 } 2827 2828 // Ignores events to reduce touchpad and magic mouse zoom speed 2829 if (!mxClient.IS_IOS && Date.now() - lastZoomEvent < 15) 2830 { 2831 return; 2832 } 2833 2834 lastZoomEvent = Date.now(); 2835 2836 // Switches to 5% zoom steps below 15% 2837 if (zoomIn) 2838 { 2839 if (this.view.scale * this.cumulativeZoomFactor <= 0.15) 2840 { 2841 this.cumulativeZoomFactor *= (this.view.scale + 0.05) / this.view.scale; 2842 } 2843 else 2844 { 2845 // Uses to 5% zoom steps for better grid rendering in webkit 2846 // and to avoid rounding errors for zoom steps 2847 this.cumulativeZoomFactor *= this.zoomFactor; 2848 this.cumulativeZoomFactor = Math.round(this.view.scale * this.cumulativeZoomFactor * 20) / 20 / this.view.scale; 2849 } 2850 } 2851 else 2852 { 2853 if (this.view.scale * this.cumulativeZoomFactor <= 0.15) 2854 { 2855 this.cumulativeZoomFactor *= (this.view.scale - 0.05) / this.view.scale; 2856 } 2857 else 2858 { 2859 // Uses to 5% zoom steps for better grid rendering in webkit 2860 // and to avoid rounding errors for zoom steps 2861 this.cumulativeZoomFactor /= this.zoomFactor; 2862 this.cumulativeZoomFactor = Math.round(this.view.scale * this.cumulativeZoomFactor * 20) / 20 / this.view.scale; 2863 } 2864 } 2865 2866 this.cumulativeZoomFactor = Math.max(0.05, Math.min(this.view.scale * this.cumulativeZoomFactor, 160)) / this.view.scale; 2867 2868 if (graph.isFastZoomEnabled()) 2869 { 2870 if (filter == null && mainGroup.getAttribute('filter') != '') 2871 { 2872 filter = mainGroup.getAttribute('filter'); 2873 mainGroup.removeAttribute('filter'); 2874 } 2875 2876 scrollPosition = new mxPoint(graph.container.scrollLeft, graph.container.scrollTop); 2877 2878 var cx = (ignoreCursorPosition) ? graph.container.scrollLeft + graph.container.clientWidth / 2 : 2879 cursorPosition.x + graph.container.scrollLeft - graph.container.offsetLeft; 2880 var cy = (ignoreCursorPosition) ? graph.container.scrollTop + graph.container.clientHeight / 2 : 2881 cursorPosition.y + graph.container.scrollTop - graph.container.offsetTop; 2882 mainGroup.style.transformOrigin = cx + 'px ' + cy + 'px'; 2883 mainGroup.style.transform = 'scale(' + this.cumulativeZoomFactor + ')'; 2884 bgGroup.style.transformOrigin = cx + 'px ' + cy + 'px'; 2885 bgGroup.style.transform = 'scale(' + this.cumulativeZoomFactor + ')'; 2886 2887 if (graph.view.backgroundPageShape != null && graph.view.backgroundPageShape.node != null) 2888 { 2889 var page = graph.view.backgroundPageShape.node; 2890 2891 mxUtils.setPrefixedStyle(page.style, 'transform-origin', 2892 ((ignoreCursorPosition) ? ((graph.container.clientWidth / 2 + graph.container.scrollLeft - 2893 page.offsetLeft) + 'px') : ((cursorPosition.x + graph.container.scrollLeft - 2894 page.offsetLeft - graph.container.offsetLeft) + 'px')) + ' ' + 2895 ((ignoreCursorPosition) ? ((graph.container.clientHeight / 2 + graph.container.scrollTop - 2896 page.offsetTop) + 'px') : ((cursorPosition.y + graph.container.scrollTop - 2897 page.offsetTop - graph.container.offsetTop) + 'px'))); 2898 mxUtils.setPrefixedStyle(page.style, 'transform', 2899 'scale(' + this.cumulativeZoomFactor + ')'); 2900 } 2901 2902 graph.view.getDecoratorPane().style.opacity = '0'; 2903 graph.view.getOverlayPane().style.opacity = '0'; 2904 2905 if (ui.hoverIcons != null) 2906 { 2907 ui.hoverIcons.reset(); 2908 } 2909 } 2910 2911 scheduleZoom(delay); 2912 }; 2913 2914 // Holds back repaint until after mouse gestures 2915 mxEvent.addGestureListeners(graph.container, function(evt) 2916 { 2917 if (updateZoomTimeout != null) 2918 { 2919 window.clearTimeout(updateZoomTimeout); 2920 } 2921 }, null, function(evt) 2922 { 2923 if (graph.cumulativeZoomFactor != 1) 2924 { 2925 scheduleZoom(0); 2926 } 2927 }); 2928 2929 // Holds back repaint until scroll ends 2930 mxEvent.addListener(graph.container, 'scroll', function(evt) 2931 { 2932 if (updateZoomTimeout != null && !graph.isMouseDown && graph.cumulativeZoomFactor != 1) 2933 { 2934 scheduleZoom(0); 2935 } 2936 }); 2937 2938 mxEvent.addMouseWheelListener(mxUtils.bind(this, function(evt, up, force, cx, cy) 2939 { 2940 graph.fireEvent(new mxEventObject('wheel')); 2941 2942 if (this.dialogs == null || this.dialogs.length == 0) 2943 { 2944 // Scrolls with scrollbars turned off 2945 if (!graph.scrollbars && !force && graph.isScrollWheelEvent(evt)) 2946 { 2947 var t = graph.view.getTranslate(); 2948 var step = 40 / graph.view.scale; 2949 2950 if (!mxEvent.isShiftDown(evt)) 2951 { 2952 graph.view.setTranslate(t.x, t.y + ((up) ? step : -step)); 2953 } 2954 else 2955 { 2956 graph.view.setTranslate(t.x + ((up) ? -step : step), t.y); 2957 } 2958 } 2959 else if (force || graph.isZoomWheelEvent(evt)) 2960 { 2961 var source = mxEvent.getSource(evt); 2962 2963 while (source != null) 2964 { 2965 if (source == graph.container) 2966 { 2967 graph.tooltipHandler.hideTooltip(); 2968 cursorPosition = (cx != null && cy!= null) ? new mxPoint(cx, cy) : 2969 new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt)); 2970 forcedZoom = force; 2971 graph.lazyZoom(up); 2972 mxEvent.consume(evt); 2973 2974 return false; 2975 } 2976 2977 source = source.parentNode; 2978 } 2979 } 2980 } 2981 }), graph.container); 2982 2983 // Uses fast zoom for pinch gestures on iOS 2984 graph.panningHandler.zoomGraph = function(evt) 2985 { 2986 graph.cumulativeZoomFactor = evt.scale; 2987 graph.lazyZoom(evt.scale > 0, true); 2988 mxEvent.consume(evt); 2989 }; 2990}; 2991 2992/** 2993 * Creates a temporary graph instance for rendering off-screen content. 2994 */ 2995EditorUi.prototype.addChromelessToolbarItems = function(addButton) 2996{ 2997 addButton(mxUtils.bind(this, function(evt) 2998 { 2999 this.actions.get('print').funct(); 3000 mxEvent.consume(evt); 3001 }), Editor.printImage, mxResources.get('print')); 3002}; 3003 3004/** 3005 * Creates a temporary graph instance for rendering off-screen content. 3006 */ 3007EditorUi.prototype.isPagesEnabled = function() 3008{ 3009 return this.editor.editable || urlParams['hide-pages'] != '1'; 3010}; 3011 3012/** 3013 * Creates a temporary graph instance for rendering off-screen content. 3014 */ 3015EditorUi.prototype.createTemporaryGraph = function(stylesheet) 3016{ 3017 return Graph.createOffscreenGraph(stylesheet); 3018}; 3019 3020/** 3021 * 3022 */ 3023EditorUi.prototype.addChromelessClickHandler = function() 3024{ 3025 var hl = urlParams['highlight']; 3026 3027 // Adds leading # for highlight color code 3028 if (hl != null && hl.length > 0) 3029 { 3030 hl = '#' + hl; 3031 } 3032 3033 this.editor.graph.addClickHandler(hl); 3034}; 3035 3036/** 3037 * 3038 */ 3039EditorUi.prototype.toggleFormatPanel = function(visible) 3040{ 3041 visible = (visible != null) ? visible : this.formatWidth == 0; 3042 3043 if (this.format != null) 3044 { 3045 this.formatWidth = (visible) ? 240 : 0; 3046 this.formatContainer.style.display = (visible) ? '' : 'none'; 3047 this.refresh(); 3048 this.format.refresh(); 3049 this.fireEvent(new mxEventObject('formatWidthChanged')); 3050 } 3051}; 3052 3053/** 3054 * Adds support for placeholders in labels. 3055 */ 3056EditorUi.prototype.lightboxFit = function(maxHeight) 3057{ 3058 if (this.isDiagramEmpty()) 3059 { 3060 this.editor.graph.view.setScale(1); 3061 } 3062 else 3063 { 3064 var p = urlParams['border']; 3065 var border = 60; 3066 3067 if (p != null) 3068 { 3069 border = parseInt(p); 3070 } 3071 3072 // LATER: Use initial graph bounds to avoid rounding errors 3073 this.editor.graph.maxFitScale = this.lightboxMaxFitScale; 3074 this.editor.graph.fit(border, null, null, null, null, null, maxHeight); 3075 this.editor.graph.maxFitScale = null; 3076 } 3077}; 3078 3079/** 3080 * Translates this point by the given vector. 3081 * 3082 * @param {number} dx X-coordinate of the translation. 3083 * @param {number} dy Y-coordinate of the translation. 3084 */ 3085EditorUi.prototype.isDiagramEmpty = function() 3086{ 3087 var model = this.editor.graph.getModel(); 3088 3089 return model.getChildCount(model.root) == 1 && model.getChildCount(model.getChildAt(model.root, 0)) == 0; 3090}; 3091 3092/** 3093 * Hook for allowing selection and context menu for certain events. 3094 */ 3095EditorUi.prototype.isSelectionAllowed = function(evt) 3096{ 3097 return mxEvent.getSource(evt).nodeName == 'SELECT' || (mxEvent.getSource(evt).nodeName == 'INPUT' && 3098 mxUtils.isAncestorNode(this.formatContainer, mxEvent.getSource(evt))); 3099}; 3100 3101/** 3102 * Installs dialog if browser window is closed without saving 3103 * This must be disabled during save and image export. 3104 */ 3105EditorUi.prototype.addBeforeUnloadListener = function() 3106{ 3107 // Installs dialog if browser window is closed without saving 3108 // This must be disabled during save and image export 3109 window.onbeforeunload = mxUtils.bind(this, function() 3110 { 3111 if (!this.editor.isChromelessView()) 3112 { 3113 return this.onBeforeUnload(); 3114 } 3115 }); 3116}; 3117 3118/** 3119 * Sets the onbeforeunload for the application 3120 */ 3121EditorUi.prototype.onBeforeUnload = function() 3122{ 3123 if (this.editor.modified) 3124 { 3125 return mxResources.get('allChangesLost'); 3126 } 3127}; 3128 3129/** 3130 * Opens the current diagram via the window.opener if one exists. 3131 */ 3132EditorUi.prototype.open = function() 3133{ 3134 // Cross-domain window access is not allowed in FF, so if we 3135 // were opened from another domain then this will fail. 3136 try 3137 { 3138 if (window.opener != null && window.opener.openFile != null) 3139 { 3140 window.opener.openFile.setConsumer(mxUtils.bind(this, function(xml, filename) 3141 { 3142 try 3143 { 3144 var doc = mxUtils.parseXml(xml); 3145 this.editor.setGraphXml(doc.documentElement); 3146 this.editor.setModified(false); 3147 this.editor.undoManager.clear(); 3148 3149 if (filename != null) 3150 { 3151 this.editor.setFilename(filename); 3152 this.updateDocumentTitle(); 3153 } 3154 3155 return; 3156 } 3157 catch (e) 3158 { 3159 mxUtils.alert(mxResources.get('invalidOrMissingFile') + ': ' + e.message); 3160 } 3161 })); 3162 } 3163 } 3164 catch(e) 3165 { 3166 // ignore 3167 } 3168 3169 // Fires as the last step if no file was loaded 3170 this.editor.graph.view.validate(); 3171 3172 // Required only in special cases where an initial file is opened 3173 // and the minimumGraphSize changes and CSS must be updated. 3174 this.editor.graph.sizeDidChange(); 3175 this.editor.fireEvent(new mxEventObject('resetGraphView')); 3176}; 3177 3178/** 3179 * Shows the given popup menu. 3180 */ 3181EditorUi.prototype.showPopupMenu = function(fn, x, y, evt) 3182{ 3183 this.editor.graph.popupMenuHandler.hideMenu(); 3184 3185 var menu = new mxPopupMenu(fn); 3186 menu.div.className += ' geMenubarMenu'; 3187 menu.smartSeparators = true; 3188 menu.showDisabled = true; 3189 menu.autoExpand = true; 3190 3191 // Disables autoexpand and destroys menu when hidden 3192 menu.hideMenu = mxUtils.bind(this, function() 3193 { 3194 mxPopupMenu.prototype.hideMenu.apply(menu, arguments); 3195 menu.destroy(); 3196 }); 3197 3198 menu.popup(x, y, null, evt); 3199 3200 // Allows hiding by clicking on document 3201 this.setCurrentMenu(menu); 3202}; 3203 3204/** 3205 * Sets the current menu and element. 3206 */ 3207EditorUi.prototype.setCurrentMenu = function(menu, elt) 3208{ 3209 this.currentMenuElt = elt; 3210 this.currentMenu = menu; 3211}; 3212 3213/** 3214 * Resets the current menu and element. 3215 */ 3216EditorUi.prototype.resetCurrentMenu = function() 3217{ 3218 this.currentMenuElt = null; 3219 this.currentMenu = null; 3220}; 3221 3222/** 3223 * Hides and destroys the current menu. 3224 */ 3225EditorUi.prototype.hideCurrentMenu = function() 3226{ 3227 if (this.currentMenu != null) 3228 { 3229 this.currentMenu.hideMenu(); 3230 this.resetCurrentMenu(); 3231 } 3232}; 3233 3234/** 3235 * Updates the document title. 3236 */ 3237EditorUi.prototype.updateDocumentTitle = function() 3238{ 3239 var title = this.editor.getOrCreateFilename(); 3240 3241 if (this.editor.appName != null) 3242 { 3243 title += ' - ' + this.editor.appName; 3244 } 3245 3246 document.title = title; 3247}; 3248 3249/** 3250 * Updates the document title. 3251 */ 3252EditorUi.prototype.createHoverIcons = function() 3253{ 3254 return new HoverIcons(this.editor.graph); 3255}; 3256 3257/** 3258 * Returns the URL for a copy of this editor with no state. 3259 */ 3260EditorUi.prototype.redo = function() 3261{ 3262 try 3263 { 3264 var graph = this.editor.graph; 3265 3266 if (graph.isEditing()) 3267 { 3268 document.execCommand('redo', false, null); 3269 } 3270 else 3271 { 3272 this.editor.undoManager.redo(); 3273 } 3274 } 3275 catch (e) 3276 { 3277 // ignore all errors 3278 } 3279}; 3280 3281/** 3282 * Returns the URL for a copy of this editor with no state. 3283 */ 3284EditorUi.prototype.undo = function() 3285{ 3286 try 3287 { 3288 var graph = this.editor.graph; 3289 3290 if (graph.isEditing()) 3291 { 3292 // Stops editing and executes undo on graph if native undo 3293 // does not affect current editing value 3294 var value = graph.cellEditor.textarea.innerHTML; 3295 document.execCommand('undo', false, null); 3296 3297 if (value == graph.cellEditor.textarea.innerHTML) 3298 { 3299 graph.stopEditing(true); 3300 this.editor.undoManager.undo(); 3301 } 3302 } 3303 else 3304 { 3305 this.editor.undoManager.undo(); 3306 } 3307 } 3308 catch (e) 3309 { 3310 // ignore all errors 3311 } 3312}; 3313 3314/** 3315 * Returns the URL for a copy of this editor with no state. 3316 */ 3317EditorUi.prototype.canRedo = function() 3318{ 3319 return this.editor.graph.isEditing() || this.editor.undoManager.canRedo(); 3320}; 3321 3322/** 3323 * Returns the URL for a copy of this editor with no state. 3324 */ 3325EditorUi.prototype.canUndo = function() 3326{ 3327 return this.editor.graph.isEditing() || this.editor.undoManager.canUndo(); 3328}; 3329 3330/** 3331 * 3332 */ 3333EditorUi.prototype.getEditBlankXml = function() 3334{ 3335 return mxUtils.getXml(this.editor.getGraphXml()); 3336}; 3337 3338/** 3339 * Returns the URL for a copy of this editor with no state. 3340 */ 3341EditorUi.prototype.getUrl = function(pathname) 3342{ 3343 var href = (pathname != null) ? pathname : window.location.pathname; 3344 var parms = (href.indexOf('?') > 0) ? 1 : 0; 3345 3346 // Removes template URL parameter for new blank diagram 3347 for (var key in urlParams) 3348 { 3349 if (parms == 0) 3350 { 3351 href += '?'; 3352 } 3353 else 3354 { 3355 href += '&'; 3356 } 3357 3358 href += key + '=' + urlParams[key]; 3359 parms++; 3360 } 3361 3362 return href; 3363}; 3364 3365/** 3366 * Specifies if the graph has scrollbars. 3367 */ 3368EditorUi.prototype.setScrollbars = function(value) 3369{ 3370 var graph = this.editor.graph; 3371 var prev = graph.container.style.overflow; 3372 graph.scrollbars = value; 3373 this.editor.updateGraphComponents(); 3374 3375 if (prev != graph.container.style.overflow) 3376 { 3377 graph.container.scrollTop = 0; 3378 graph.container.scrollLeft = 0; 3379 graph.view.scaleAndTranslate(1, 0, 0); 3380 this.resetScrollbars(); 3381 } 3382 3383 this.fireEvent(new mxEventObject('scrollbarsChanged')); 3384}; 3385 3386/** 3387 * Returns true if the graph has scrollbars. 3388 */ 3389EditorUi.prototype.hasScrollbars = function() 3390{ 3391 return this.editor.graph.scrollbars; 3392}; 3393 3394/** 3395 * Resets the state of the scrollbars. 3396 */ 3397EditorUi.prototype.resetScrollbars = function() 3398{ 3399 var graph = this.editor.graph; 3400 3401 if (!this.editor.extendCanvas) 3402 { 3403 graph.container.scrollTop = 0; 3404 graph.container.scrollLeft = 0; 3405 3406 if (!mxUtils.hasScrollbars(graph.container)) 3407 { 3408 graph.view.setTranslate(0, 0); 3409 } 3410 } 3411 else if (!this.editor.isChromelessView()) 3412 { 3413 if (mxUtils.hasScrollbars(graph.container)) 3414 { 3415 if (graph.pageVisible) 3416 { 3417 var pad = graph.getPagePadding(); 3418 graph.container.scrollTop = Math.floor(pad.y - this.editor.initialTopSpacing) - 1; 3419 graph.container.scrollLeft = Math.floor(Math.min(pad.x, 3420 (graph.container.scrollWidth - graph.container.clientWidth) / 2)) - 1; 3421 3422 // Scrolls graph to visible area 3423 var bounds = graph.getGraphBounds(); 3424 3425 if (bounds.width > 0 && bounds.height > 0) 3426 { 3427 if (bounds.x > graph.container.scrollLeft + graph.container.clientWidth * 0.9) 3428 { 3429 graph.container.scrollLeft = Math.min(bounds.x + bounds.width - graph.container.clientWidth, bounds.x - 10); 3430 } 3431 3432 if (bounds.y > graph.container.scrollTop + graph.container.clientHeight * 0.9) 3433 { 3434 graph.container.scrollTop = Math.min(bounds.y + bounds.height - graph.container.clientHeight, bounds.y - 10); 3435 } 3436 } 3437 } 3438 else 3439 { 3440 var bounds = graph.getGraphBounds(); 3441 var width = Math.max(bounds.width, graph.scrollTileSize.width * graph.view.scale); 3442 var height = Math.max(bounds.height, graph.scrollTileSize.height * graph.view.scale); 3443 graph.container.scrollTop = Math.floor(Math.max(0, bounds.y - Math.max(20, (graph.container.clientHeight - height) / 4))); 3444 graph.container.scrollLeft = Math.floor(Math.max(0, bounds.x - Math.max(0, (graph.container.clientWidth - width) / 2))); 3445 } 3446 } 3447 else 3448 { 3449 var b = mxRectangle.fromRectangle((graph.pageVisible) ? graph.view.getBackgroundPageBounds() : graph.getGraphBounds()) 3450 var tr = graph.view.translate; 3451 var s = graph.view.scale; 3452 b.x = b.x / s - tr.x; 3453 b.y = b.y / s - tr.y; 3454 b.width /= s; 3455 b.height /= s; 3456 3457 var dy = (graph.pageVisible) ? 0 : Math.max(0, (graph.container.clientHeight - b.height) / 4); 3458 3459 graph.view.setTranslate(Math.floor(Math.max(0, 3460 (graph.container.clientWidth - b.width) / 2) - b.x + 2), 3461 Math.floor(dy - b.y + 1)); 3462 } 3463 } 3464}; 3465 3466/** 3467 * Loads the stylesheet for this graph. 3468 */ 3469EditorUi.prototype.setPageVisible = function(value) 3470{ 3471 var graph = this.editor.graph; 3472 var hasScrollbars = mxUtils.hasScrollbars(graph.container); 3473 var tx = 0; 3474 var ty = 0; 3475 3476 if (hasScrollbars) 3477 { 3478 tx = graph.view.translate.x * graph.view.scale - graph.container.scrollLeft; 3479 ty = graph.view.translate.y * graph.view.scale - graph.container.scrollTop; 3480 } 3481 3482 graph.pageVisible = value; 3483 graph.pageBreaksVisible = value; 3484 graph.preferPageSize = value; 3485 graph.view.validateBackground(); 3486 3487 // Workaround for possible handle offset 3488 if (hasScrollbars) 3489 { 3490 var cells = graph.getSelectionCells(); 3491 graph.clearSelection(); 3492 graph.setSelectionCells(cells); 3493 } 3494 3495 // Calls updatePageBreaks 3496 graph.sizeDidChange(); 3497 3498 if (hasScrollbars) 3499 { 3500 graph.container.scrollLeft = graph.view.translate.x * graph.view.scale - tx; 3501 graph.container.scrollTop = graph.view.translate.y * graph.view.scale - ty; 3502 } 3503 3504 graph.defaultPageVisible = value; 3505 this.fireEvent(new mxEventObject('pageViewChanged')); 3506}; 3507 3508/** 3509 * Class: ChangeGridColor 3510 * 3511 * Undoable change to grid color. 3512 */ 3513function ChangeGridColor(ui, color) 3514{ 3515 this.ui = ui; 3516 this.color = color; 3517}; 3518 3519/** 3520 * Executes selection of a new page. 3521 */ 3522ChangeGridColor.prototype.execute = function() 3523{ 3524 var temp = this.ui.editor.graph.view.gridColor; 3525 this.ui.setGridColor(this.color); 3526 this.color = temp; 3527}; 3528 3529// Registers codec for ChangePageSetup 3530(function() 3531{ 3532 var codec = new mxObjectCodec(new ChangeGridColor(), ['ui']); 3533 3534 mxCodecRegistry.register(codec); 3535})(); 3536 3537/** 3538 * Change types 3539 */ 3540function ChangePageSetup(ui, color, image, format, pageScale) 3541{ 3542 this.ui = ui; 3543 this.color = color; 3544 this.previousColor = color; 3545 this.image = image; 3546 this.previousImage = image; 3547 this.format = format; 3548 this.previousFormat = format; 3549 this.pageScale = pageScale; 3550 this.previousPageScale = pageScale; 3551 3552 // Needed since null are valid values for color and image 3553 this.ignoreColor = false; 3554 this.ignoreImage = false; 3555} 3556 3557/** 3558 * Implementation of the undoable page rename. 3559 */ 3560ChangePageSetup.prototype.execute = function() 3561{ 3562 var graph = this.ui.editor.graph; 3563 3564 if (!this.ignoreColor) 3565 { 3566 this.color = this.previousColor; 3567 var tmp = graph.background; 3568 this.ui.setBackgroundColor(this.previousColor); 3569 this.previousColor = tmp; 3570 } 3571 3572 if (!this.ignoreImage) 3573 { 3574 this.image = this.previousImage; 3575 var tmp = graph.backgroundImage; 3576 var img = this.previousImage; 3577 3578 if (img != null && img.src != null && img.src.substring(0, 13) == 'data:page/id,') 3579 { 3580 img = this.ui.createImageForPageLink(img.src, this.ui.currentPage); 3581 } 3582 3583 this.ui.setBackgroundImage(img); 3584 this.previousImage = tmp; 3585 } 3586 3587 if (this.previousFormat != null) 3588 { 3589 this.format = this.previousFormat; 3590 var tmp = graph.pageFormat; 3591 3592 if (this.previousFormat.width != tmp.width || 3593 this.previousFormat.height != tmp.height) 3594 { 3595 this.ui.setPageFormat(this.previousFormat); 3596 this.previousFormat = tmp; 3597 } 3598 } 3599 3600 if (this.foldingEnabled != null && this.foldingEnabled != this.ui.editor.graph.foldingEnabled) 3601 { 3602 this.ui.setFoldingEnabled(this.foldingEnabled); 3603 this.foldingEnabled = !this.foldingEnabled; 3604 } 3605 3606 if (this.previousPageScale != null) 3607 { 3608 var currentPageScale = this.ui.editor.graph.pageScale; 3609 3610 if (this.previousPageScale != currentPageScale) 3611 { 3612 this.ui.setPageScale(this.previousPageScale); 3613 this.previousPageScale = currentPageScale; 3614 } 3615 } 3616}; 3617 3618// Registers codec for ChangePageSetup 3619(function() 3620{ 3621 var codec = new mxObjectCodec(new ChangePageSetup(), ['ui', 'previousColor', 'previousImage', 'previousFormat', 'previousPageScale']); 3622 3623 codec.afterDecode = function(dec, node, obj) 3624 { 3625 obj.previousColor = obj.color; 3626 obj.previousImage = obj.image; 3627 obj.previousFormat = obj.format; 3628 obj.previousPageScale = obj.pageScale; 3629 3630 if (obj.foldingEnabled != null) 3631 { 3632 obj.foldingEnabled = !obj.foldingEnabled; 3633 } 3634 3635 return obj; 3636 }; 3637 3638 mxCodecRegistry.register(codec); 3639})(); 3640 3641/** 3642 * Loads the stylesheet for this graph. 3643 */ 3644EditorUi.prototype.setBackgroundColor = function(value) 3645{ 3646 this.editor.graph.background = value; 3647 this.editor.graph.view.validateBackground(); 3648 3649 this.fireEvent(new mxEventObject('backgroundColorChanged')); 3650}; 3651 3652/** 3653 * Loads the stylesheet for this graph. 3654 */ 3655EditorUi.prototype.setFoldingEnabled = function(value) 3656{ 3657 this.editor.graph.foldingEnabled = value; 3658 this.editor.graph.view.revalidate(); 3659 3660 this.fireEvent(new mxEventObject('foldingEnabledChanged')); 3661}; 3662 3663/** 3664 * Loads the stylesheet for this graph. 3665 */ 3666EditorUi.prototype.setPageFormat = function(value, ignorePageVisible) 3667{ 3668 ignorePageVisible = (ignorePageVisible != null) ? ignorePageVisible : urlParams['sketch'] == '1'; 3669 this.editor.graph.pageFormat = value; 3670 3671 if (!ignorePageVisible) 3672 { 3673 if (!this.editor.graph.pageVisible) 3674 { 3675 this.actions.get('pageView').funct(); 3676 } 3677 else 3678 { 3679 this.editor.graph.view.validateBackground(); 3680 this.editor.graph.sizeDidChange(); 3681 } 3682 } 3683 3684 this.fireEvent(new mxEventObject('pageFormatChanged')); 3685}; 3686 3687/** 3688 * Loads the stylesheet for this graph. 3689 */ 3690EditorUi.prototype.setPageScale = function(value) 3691{ 3692 this.editor.graph.pageScale = value; 3693 3694 if (!this.editor.graph.pageVisible) 3695 { 3696 this.actions.get('pageView').funct(); 3697 } 3698 else 3699 { 3700 this.editor.graph.view.validateBackground(); 3701 this.editor.graph.sizeDidChange(); 3702 } 3703 3704 this.fireEvent(new mxEventObject('pageScaleChanged')); 3705}; 3706 3707/** 3708 * Loads the stylesheet for this graph. 3709 */ 3710EditorUi.prototype.setGridColor = function(value) 3711{ 3712 this.editor.graph.view.gridColor = value; 3713 this.editor.graph.view.validateBackground(); 3714 this.fireEvent(new mxEventObject('gridColorChanged')); 3715}; 3716 3717/** 3718 * Updates the states of the given undo/redo items. 3719 */ 3720EditorUi.prototype.addUndoListener = function() 3721{ 3722 var undo = this.actions.get('undo'); 3723 var redo = this.actions.get('redo'); 3724 3725 var undoMgr = this.editor.undoManager; 3726 3727 var undoListener = mxUtils.bind(this, function() 3728 { 3729 undo.setEnabled(this.canUndo()); 3730 redo.setEnabled(this.canRedo()); 3731 }); 3732 3733 undoMgr.addListener(mxEvent.ADD, undoListener); 3734 undoMgr.addListener(mxEvent.UNDO, undoListener); 3735 undoMgr.addListener(mxEvent.REDO, undoListener); 3736 undoMgr.addListener(mxEvent.CLEAR, undoListener); 3737 3738 // Overrides cell editor to update action states 3739 var cellEditorStartEditing = this.editor.graph.cellEditor.startEditing; 3740 3741 this.editor.graph.cellEditor.startEditing = function() 3742 { 3743 cellEditorStartEditing.apply(this, arguments); 3744 undoListener(); 3745 }; 3746 3747 var cellEditorStopEditing = this.editor.graph.cellEditor.stopEditing; 3748 3749 this.editor.graph.cellEditor.stopEditing = function(cell, trigger) 3750 { 3751 cellEditorStopEditing.apply(this, arguments); 3752 undoListener(); 3753 }; 3754 3755 // Updates the button states once 3756 undoListener(); 3757}; 3758 3759/** 3760* Updates the states of the given toolbar items based on the selection. 3761*/ 3762EditorUi.prototype.updateActionStates = function() 3763{ 3764 var graph = this.editor.graph; 3765 var vertexSelected = false; 3766 var groupSelected = false; 3767 var edgeSelected = false; 3768 var selected = false; 3769 var editable = []; 3770 3771 var cells = graph.getSelectionCells(); 3772 3773 if (cells != null) 3774 { 3775 for (var i = 0; i < cells.length; i++) 3776 { 3777 var cell = cells[i]; 3778 3779 if (graph.isCellEditable(cell)) 3780 { 3781 editable.push(cell); 3782 selected = true; 3783 3784 if (graph.getModel().isEdge(cell)) 3785 { 3786 edgeSelected = true; 3787 } 3788 3789 if (graph.getModel().isVertex(cell)) 3790 { 3791 vertexSelected = true; 3792 3793 if (graph.getModel().getChildCount(cell) > 0 || 3794 graph.isContainer(cell)) 3795 { 3796 groupSelected = true; 3797 } 3798 } 3799 } 3800 } 3801 } 3802 3803 // Updates action states 3804 var actions = ['cut', 'copy', 'bold', 'italic', 'underline', 'delete', 'duplicate', 3805 'editStyle', 'editTooltip', 'editLink', 'backgroundColor', 'borderColor', 3806 'edit', 'toFront', 'toBack', 'solid', 'dashed', 'pasteSize', 3807 'dotted', 'fillColor', 'gradientColor', 'shadow', 'fontColor', 3808 'formattedText', 'rounded', 'toggleRounded', 'sharp', 'strokeColor']; 3809 3810 for (var i = 0; i < actions.length; i++) 3811 { 3812 this.actions.get(actions[i]).setEnabled(selected); 3813 } 3814 3815 this.actions.get('lockUnlock').setEnabled(!graph.isSelectionEmpty()); 3816 this.actions.get('setAsDefaultStyle').setEnabled(graph.getSelectionCount() == 1); 3817 this.actions.get('clearWaypoints').setEnabled(selected); 3818 this.actions.get('copySize').setEnabled(graph.getSelectionCount() == 1); 3819 this.actions.get('bringForward').setEnabled(editable.length == 1); 3820 this.actions.get('sendBackward').setEnabled(editable.length == 1); 3821 this.actions.get('turn').setEnabled(graph.getResizableCells(graph.getSelectionCells()).length > 0); 3822 this.actions.get('curved').setEnabled(edgeSelected); 3823 this.actions.get('rotation').setEnabled(vertexSelected); 3824 this.actions.get('wordWrap').setEnabled(vertexSelected); 3825 this.actions.get('autosize').setEnabled(vertexSelected); 3826 var oneVertexSelected = vertexSelected && graph.getSelectionCount() == 1; 3827 this.actions.get('group').setEnabled(graph.getSelectionCount() > 1 || 3828 (oneVertexSelected && !graph.isContainer(graph.getSelectionCell()))); 3829 this.actions.get('ungroup').setEnabled(groupSelected); 3830 this.actions.get('removeFromGroup').setEnabled(oneVertexSelected && 3831 graph.getModel().isVertex(graph.getModel().getParent(editable[0]))); 3832 3833 // Updates menu states 3834 var state = graph.view.getState(graph.getSelectionCell()); 3835 this.menus.get('navigation').setEnabled(selected || graph.view.currentRoot != null); 3836 this.actions.get('collapsible').setEnabled(vertexSelected && 3837 (graph.isContainer(graph.getSelectionCell()) || graph.model.getChildCount(graph.getSelectionCell()) > 0)); 3838 this.actions.get('home').setEnabled(graph.view.currentRoot != null); 3839 this.actions.get('exitGroup').setEnabled(graph.view.currentRoot != null); 3840 this.actions.get('enterGroup').setEnabled(graph.getSelectionCount() == 1 && graph.isValidRoot(graph.getSelectionCell())); 3841 var foldable = graph.getSelectionCount() == 1 && graph.isCellFoldable(graph.getSelectionCell()); // TODO 3842 this.actions.get('expand').setEnabled(foldable); 3843 this.actions.get('collapse').setEnabled(foldable); 3844 this.actions.get('editLink').setEnabled(editable.length == 1); 3845 this.actions.get('openLink').setEnabled(graph.getSelectionCount() == 1 && 3846 graph.getLinkForCell(graph.getSelectionCell()) != null); 3847 this.actions.get('guides').setEnabled(graph.isEnabled()); 3848 this.actions.get('grid').setEnabled(!this.editor.chromeless || this.editor.editable); 3849 3850 var unlocked = graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent()); 3851 this.menus.get('layout').setEnabled(unlocked); 3852 this.menus.get('insert').setEnabled(unlocked); 3853 this.menus.get('direction').setEnabled(unlocked && vertexSelected); 3854 this.menus.get('align').setEnabled(unlocked && vertexSelected && graph.getSelectionCount() > 1); 3855 this.menus.get('distribute').setEnabled(unlocked && vertexSelected && graph.getSelectionCount() > 1); 3856 this.actions.get('selectVertices').setEnabled(unlocked); 3857 this.actions.get('selectEdges').setEnabled(unlocked); 3858 this.actions.get('selectAll').setEnabled(unlocked); 3859 this.actions.get('selectNone').setEnabled(unlocked); 3860 3861 this.updatePasteActionStates(); 3862}; 3863 3864EditorUi.prototype.zeroOffset = new mxPoint(0, 0); 3865 3866EditorUi.prototype.getDiagramContainerOffset = function() 3867{ 3868 return this.zeroOffset; 3869}; 3870 3871/** 3872 * Refreshes the viewport. 3873 */ 3874EditorUi.prototype.refresh = function(sizeDidChange) 3875{ 3876 sizeDidChange = (sizeDidChange != null) ? sizeDidChange : true; 3877 3878 var w = this.container.clientWidth; 3879 var h = this.container.clientHeight; 3880 3881 if (this.container == document.body) 3882 { 3883 w = document.body.clientWidth || document.documentElement.clientWidth; 3884 h = document.documentElement.clientHeight; 3885 } 3886 3887 // Workaround for bug on iOS see 3888 // http://stackoverflow.com/questions/19012135/ios-7-ipad-safari-landscape-innerheight-outerheight-layout-issue 3889 // FIXME: Fix if footer visible 3890 var off = 0; 3891 3892 if (mxClient.IS_IOS && !window.navigator.standalone) 3893 { 3894 if (window.innerHeight != document.documentElement.clientHeight) 3895 { 3896 off = document.documentElement.clientHeight - window.innerHeight; 3897 window.scrollTo(0, 0); 3898 } 3899 } 3900 3901 var effHsplitPosition = Math.max(0, Math.min(this.hsplitPosition, w - this.splitSize - 20)); 3902 var tmp = 0; 3903 3904 if (this.menubar != null) 3905 { 3906 this.menubarContainer.style.height = this.menubarHeight + 'px'; 3907 tmp += this.menubarHeight; 3908 } 3909 3910 if (this.toolbar != null) 3911 { 3912 this.toolbarContainer.style.top = this.menubarHeight + 'px'; 3913 this.toolbarContainer.style.height = this.toolbarHeight + 'px'; 3914 tmp += this.toolbarHeight; 3915 } 3916 3917 if (tmp > 0) 3918 { 3919 tmp += 1; 3920 } 3921 3922 var sidebarFooterHeight = 0; 3923 3924 if (this.sidebarFooterContainer != null) 3925 { 3926 var bottom = this.footerHeight + off; 3927 sidebarFooterHeight = Math.max(0, Math.min(h - tmp - bottom, this.sidebarFooterHeight)); 3928 this.sidebarFooterContainer.style.width = effHsplitPosition + 'px'; 3929 this.sidebarFooterContainer.style.height = sidebarFooterHeight + 'px'; 3930 this.sidebarFooterContainer.style.bottom = bottom + 'px'; 3931 } 3932 3933 var fw = (this.format != null) ? this.formatWidth : 0; 3934 this.sidebarContainer.style.top = tmp + 'px'; 3935 this.sidebarContainer.style.width = effHsplitPosition + 'px'; 3936 this.formatContainer.style.top = tmp + 'px'; 3937 this.formatContainer.style.width = fw + 'px'; 3938 this.formatContainer.style.display = (this.format != null) ? '' : 'none'; 3939 3940 var diagContOffset = this.getDiagramContainerOffset(); 3941 var contLeft = (this.hsplit.parentNode != null) ? (effHsplitPosition + this.splitSize) : 0; 3942 this.footerContainer.style.height = this.footerHeight + 'px'; 3943 this.hsplit.style.top = this.sidebarContainer.style.top; 3944 this.hsplit.style.bottom = (this.footerHeight + off) + 'px'; 3945 this.hsplit.style.left = effHsplitPosition + 'px'; 3946 this.footerContainer.style.display = (this.footerHeight == 0) ? 'none' : ''; 3947 3948 if (this.tabContainer != null) 3949 { 3950 this.tabContainer.style.left = contLeft + 'px'; 3951 } 3952 3953 if (this.footerHeight > 0) 3954 { 3955 this.footerContainer.style.bottom = off + 'px'; 3956 } 3957 3958 var th = 0; 3959 3960 if (this.tabContainer != null) 3961 { 3962 this.tabContainer.style.bottom = (this.footerHeight + off) + 'px'; 3963 this.tabContainer.style.right = this.diagramContainer.style.right; 3964 th = this.tabContainer.clientHeight; 3965 } 3966 3967 this.sidebarContainer.style.bottom = (this.footerHeight + sidebarFooterHeight + off) + 'px'; 3968 this.formatContainer.style.bottom = (this.footerHeight + off) + 'px'; 3969 3970 if (urlParams['embedInline'] != '1') 3971 { 3972 this.diagramContainer.style.left = (contLeft + diagContOffset.x) + 'px'; 3973 this.diagramContainer.style.top = (tmp + diagContOffset.y) + 'px'; 3974 this.diagramContainer.style.right = fw + 'px'; 3975 this.diagramContainer.style.bottom = (this.footerHeight + off + th) + 'px'; 3976 } 3977 3978 if (sizeDidChange) 3979 { 3980 this.editor.graph.sizeDidChange(); 3981 } 3982}; 3983 3984/** 3985 * Creates the required containers. 3986 */ 3987EditorUi.prototype.createTabContainer = function() 3988{ 3989 return null; 3990}; 3991 3992/** 3993 * Creates the required containers. 3994 */ 3995EditorUi.prototype.createDivs = function() 3996{ 3997 this.menubarContainer = this.createDiv('geMenubarContainer'); 3998 this.toolbarContainer = this.createDiv('geToolbarContainer'); 3999 this.sidebarContainer = this.createDiv('geSidebarContainer'); 4000 this.formatContainer = this.createDiv('geSidebarContainer geFormatContainer'); 4001 this.diagramContainer = this.createDiv('geDiagramContainer'); 4002 this.footerContainer = this.createDiv('geFooterContainer'); 4003 this.hsplit = this.createDiv('geHsplit'); 4004 this.hsplit.setAttribute('title', mxResources.get('collapseExpand')); 4005 4006 // Sets static style for containers 4007 this.menubarContainer.style.top = '0px'; 4008 this.menubarContainer.style.left = '0px'; 4009 this.menubarContainer.style.right = '0px'; 4010 this.toolbarContainer.style.left = '0px'; 4011 this.toolbarContainer.style.right = '0px'; 4012 this.sidebarContainer.style.left = '0px'; 4013 this.formatContainer.style.right = '0px'; 4014 this.formatContainer.style.zIndex = '1'; 4015 this.diagramContainer.style.right = ((this.format != null) ? this.formatWidth : 0) + 'px'; 4016 this.footerContainer.style.left = '0px'; 4017 this.footerContainer.style.right = '0px'; 4018 this.footerContainer.style.bottom = '0px'; 4019 this.footerContainer.style.zIndex = mxPopupMenu.prototype.zIndex - 3; 4020 this.hsplit.style.width = this.splitSize + 'px'; 4021 this.sidebarFooterContainer = this.createSidebarFooterContainer(); 4022 4023 if (this.sidebarFooterContainer) 4024 { 4025 this.sidebarFooterContainer.style.left = '0px'; 4026 } 4027 4028 if (!this.editor.chromeless) 4029 { 4030 this.tabContainer = this.createTabContainer(); 4031 } 4032 else 4033 { 4034 this.diagramContainer.style.border = 'none'; 4035 } 4036}; 4037 4038/** 4039 * Hook for sidebar footer container. This implementation returns null. 4040 */ 4041EditorUi.prototype.createSidebarFooterContainer = function() 4042{ 4043 return null; 4044}; 4045 4046/** 4047 * Creates the required containers. 4048 */ 4049EditorUi.prototype.createUi = function() 4050{ 4051 // Creates menubar 4052 this.menubar = (this.editor.chromeless) ? null : this.menus.createMenubar(this.createDiv('geMenubar')); 4053 4054 if (this.menubar != null) 4055 { 4056 this.menubarContainer.appendChild(this.menubar.container); 4057 } 4058 4059 // Adds status bar in menubar 4060 if (this.menubar != null) 4061 { 4062 this.statusContainer = this.createStatusContainer(); 4063 4064 // Connects the status bar to the editor status 4065 this.editor.addListener('statusChanged', mxUtils.bind(this, function() 4066 { 4067 this.setStatusText(this.editor.getStatus()); 4068 })); 4069 4070 this.setStatusText(this.editor.getStatus()); 4071 this.menubar.container.appendChild(this.statusContainer); 4072 4073 // Inserts into DOM 4074 this.container.appendChild(this.menubarContainer); 4075 } 4076 4077 // Creates the sidebar 4078 this.sidebar = (this.editor.chromeless) ? null : this.createSidebar(this.sidebarContainer); 4079 4080 if (this.sidebar != null) 4081 { 4082 this.container.appendChild(this.sidebarContainer); 4083 } 4084 4085 // Creates the format sidebar 4086 this.format = (this.editor.chromeless || !this.formatEnabled) ? null : this.createFormat(this.formatContainer); 4087 4088 if (this.format != null) 4089 { 4090 this.container.appendChild(this.formatContainer); 4091 } 4092 4093 // Creates the footer 4094 var footer = (this.editor.chromeless) ? null : this.createFooter(); 4095 4096 if (footer != null) 4097 { 4098 this.footerContainer.appendChild(footer); 4099 this.container.appendChild(this.footerContainer); 4100 } 4101 4102 if (this.sidebar != null && this.sidebarFooterContainer) 4103 { 4104 this.container.appendChild(this.sidebarFooterContainer); 4105 } 4106 4107 this.container.appendChild(this.diagramContainer); 4108 4109 if (this.container != null && this.tabContainer != null) 4110 { 4111 this.container.appendChild(this.tabContainer); 4112 } 4113 4114 // Creates toolbar 4115 this.toolbar = (this.editor.chromeless) ? null : this.createToolbar(this.createDiv('geToolbar')); 4116 4117 if (this.toolbar != null) 4118 { 4119 this.toolbarContainer.appendChild(this.toolbar.container); 4120 this.container.appendChild(this.toolbarContainer); 4121 } 4122 4123 // HSplit 4124 if (this.sidebar != null) 4125 { 4126 this.container.appendChild(this.hsplit); 4127 4128 this.addSplitHandler(this.hsplit, true, 0, mxUtils.bind(this, function(value) 4129 { 4130 this.hsplitPosition = value; 4131 this.refresh(); 4132 })); 4133 } 4134}; 4135 4136/** 4137 * Creates a new toolbar for the given container. 4138 */ 4139EditorUi.prototype.createStatusContainer = function() 4140{ 4141 var container = document.createElement('a'); 4142 container.className = 'geItem geStatus'; 4143 4144 return container; 4145}; 4146 4147/** 4148 * Creates a new toolbar for the given container. 4149 */ 4150EditorUi.prototype.setStatusText = function(value) 4151{ 4152 this.statusContainer.innerHTML = value; 4153}; 4154 4155/** 4156 * Creates a new toolbar for the given container. 4157 */ 4158EditorUi.prototype.createToolbar = function(container) 4159{ 4160 return new Toolbar(this, container); 4161}; 4162 4163/** 4164 * Creates a new sidebar for the given container. 4165 */ 4166EditorUi.prototype.createSidebar = function(container) 4167{ 4168 return new Sidebar(this, container); 4169}; 4170 4171/** 4172 * Creates a new sidebar for the given container. 4173 */ 4174EditorUi.prototype.createFormat = function(container) 4175{ 4176 return new Format(this, container); 4177}; 4178 4179/** 4180 * Creates and returns a new footer. 4181 */ 4182EditorUi.prototype.createFooter = function() 4183{ 4184 return this.createDiv('geFooter'); 4185}; 4186 4187/** 4188 * Creates the actual toolbar for the toolbar container. 4189 */ 4190EditorUi.prototype.createDiv = function(classname) 4191{ 4192 var elt = document.createElement('div'); 4193 elt.className = classname; 4194 4195 return elt; 4196}; 4197 4198/** 4199 * Updates the states of the given undo/redo items. 4200 */ 4201EditorUi.prototype.addSplitHandler = function(elt, horizontal, dx, onChange) 4202{ 4203 var start = null; 4204 var initial = null; 4205 var ignoreClick = true; 4206 var last = null; 4207 4208 // Disables built-in pan and zoom in IE10 and later 4209 if (mxClient.IS_POINTER) 4210 { 4211 elt.style.touchAction = 'none'; 4212 } 4213 4214 var getValue = mxUtils.bind(this, function() 4215 { 4216 var result = parseInt(((horizontal) ? elt.style.left : elt.style.bottom)); 4217 4218 // Takes into account hidden footer 4219 if (!horizontal) 4220 { 4221 result = result + dx - this.footerHeight; 4222 } 4223 4224 return result; 4225 }); 4226 4227 function moveHandler(evt) 4228 { 4229 if (start != null) 4230 { 4231 var pt = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt)); 4232 onChange(Math.max(0, initial + ((horizontal) ? (pt.x - start.x) : (start.y - pt.y)) - dx)); 4233 mxEvent.consume(evt); 4234 4235 if (initial != getValue()) 4236 { 4237 ignoreClick = true; 4238 last = null; 4239 } 4240 } 4241 }; 4242 4243 function dropHandler(evt) 4244 { 4245 moveHandler(evt); 4246 initial = null; 4247 start = null; 4248 }; 4249 4250 mxEvent.addGestureListeners(elt, function(evt) 4251 { 4252 start = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt)); 4253 initial = getValue(); 4254 ignoreClick = false; 4255 mxEvent.consume(evt); 4256 }); 4257 4258 mxEvent.addListener(elt, 'click', mxUtils.bind(this, function(evt) 4259 { 4260 if (!ignoreClick && this.hsplitClickEnabled) 4261 { 4262 var next = (last != null) ? last - dx : 0; 4263 last = getValue(); 4264 onChange(next); 4265 mxEvent.consume(evt); 4266 } 4267 })); 4268 4269 mxEvent.addGestureListeners(document, null, moveHandler, dropHandler); 4270 4271 this.destroyFunctions.push(function() 4272 { 4273 mxEvent.removeGestureListeners(document, null, moveHandler, dropHandler); 4274 }); 4275}; 4276 4277/** 4278 * Translates this point by the given vector. 4279 * 4280 * @param {number} dx X-coordinate of the translation. 4281 * @param {number} dy Y-coordinate of the translation. 4282 */ 4283EditorUi.prototype.handleError = function(resp, title, fn, invokeFnOnClose, notFoundMessage) 4284{ 4285 var e = (resp != null && resp.error != null) ? resp.error : resp; 4286 4287 if (e != null || title != null) 4288 { 4289 var msg = mxUtils.htmlEntities(mxResources.get('unknownError')); 4290 var btn = mxResources.get('ok'); 4291 title = (title != null) ? title : mxResources.get('error'); 4292 4293 if (e != null && e.message != null) 4294 { 4295 msg = mxUtils.htmlEntities(e.message); 4296 } 4297 4298 this.showError(title, msg, btn, fn, null, null, null, null, null, 4299 null, null, null, (invokeFnOnClose) ? fn : null); 4300 } 4301 else if (fn != null) 4302 { 4303 fn(); 4304 } 4305}; 4306 4307/** 4308 * Translates this point by the given vector. 4309 * 4310 * @param {number} dx X-coordinate of the translation. 4311 * @param {number} dy Y-coordinate of the translation. 4312 */ 4313EditorUi.prototype.showError = function(title, msg, btn, fn, retry, btn2, fn2, btn3, fn3, w, h, hide, onClose) 4314{ 4315 var dlg = new ErrorDialog(this, title, msg, btn || mxResources.get('ok'), 4316 fn, retry, btn2, fn2, hide, btn3, fn3); 4317 var lines = Math.ceil((msg != null) ? msg.length / 50 : 1); 4318 this.showDialog(dlg.container, w || 340, h || (100 + lines * 20), true, false, onClose); 4319 dlg.init(); 4320}; 4321 4322/** 4323 * Displays a print dialog. 4324 */ 4325EditorUi.prototype.showDialog = function(elt, w, h, modal, closable, onClose, noScroll, transparent, onResize, ignoreBgClick) 4326{ 4327 this.editor.graph.tooltipHandler.resetTimer(); 4328 this.editor.graph.tooltipHandler.hideTooltip(); 4329 4330 if (this.dialogs == null) 4331 { 4332 this.dialogs = []; 4333 } 4334 4335 this.dialog = new Dialog(this, elt, w, h, modal, closable, onClose, noScroll, transparent, onResize, ignoreBgClick); 4336 this.dialogs.push(this.dialog); 4337}; 4338 4339/** 4340 * Displays a print dialog. 4341 */ 4342EditorUi.prototype.hideDialog = function(cancel, isEsc, matchContainer) 4343{ 4344 if (this.dialogs != null && this.dialogs.length > 0) 4345 { 4346 if (matchContainer != null && matchContainer != this.dialog.container.firstChild) 4347 { 4348 return; 4349 } 4350 4351 var dlg = this.dialogs.pop(); 4352 4353 if (dlg.close(cancel, isEsc) == false) 4354 { 4355 //add the dialog back if dialog closing is cancelled 4356 this.dialogs.push(dlg); 4357 return; 4358 } 4359 4360 this.dialog = (this.dialogs.length > 0) ? this.dialogs[this.dialogs.length - 1] : null; 4361 this.editor.fireEvent(new mxEventObject('hideDialog')); 4362 4363 if (this.dialog == null && this.editor.graph.container.style.visibility != 'hidden') 4364 { 4365 window.setTimeout(mxUtils.bind(this, function() 4366 { 4367 if (this.editor.graph.isEditing() && this.editor.graph.cellEditor.textarea != null) 4368 { 4369 this.editor.graph.cellEditor.textarea.focus(); 4370 } 4371 else 4372 { 4373 mxUtils.clearSelection(); 4374 this.editor.graph.container.focus(); 4375 } 4376 }), 0); 4377 } 4378 } 4379}; 4380 4381/** 4382 * Handles ctrl+enter keystroke to clone cells. 4383 */ 4384EditorUi.prototype.ctrlEnter = function() 4385{ 4386 var graph = this.editor.graph; 4387 4388 if (graph.isEnabled()) 4389 { 4390 try 4391 { 4392 var cells = graph.getSelectionCells(); 4393 var lookup = new mxDictionary(); 4394 var newCells = []; 4395 4396 for (var i = 0; i < cells.length; i++) 4397 { 4398 // Clones table rows instead of cells 4399 var cell = (graph.isTableCell(cells[i])) ? graph.model.getParent(cells[i]) : cells[i]; 4400 4401 if (cell != null && !lookup.get(cell)) 4402 { 4403 lookup.put(cell, true); 4404 newCells.push(cell); 4405 } 4406 } 4407 4408 graph.setSelectionCells(graph.duplicateCells(newCells, false)); 4409 } 4410 catch (e) 4411 { 4412 this.handleError(e); 4413 } 4414 } 4415}; 4416 4417/** 4418 * Display a color dialog. 4419 */ 4420EditorUi.prototype.pickColor = function(color, apply) 4421{ 4422 var graph = this.editor.graph; 4423 var selState = graph.cellEditor.saveSelection(); 4424 var h = 230 + ((Math.ceil(ColorDialog.prototype.presetColors.length / 12) + 4425 Math.ceil(ColorDialog.prototype.defaultColors.length / 12)) * 17); 4426 4427 var dlg = new ColorDialog(this, color || 'none', function(color) 4428 { 4429 graph.cellEditor.restoreSelection(selState); 4430 apply(color); 4431 }, function() 4432 { 4433 graph.cellEditor.restoreSelection(selState); 4434 }); 4435 this.showDialog(dlg.container, 230, h, true, false); 4436 dlg.init(); 4437}; 4438 4439/** 4440 * Adds the label menu items to the given menu and parent. 4441 */ 4442EditorUi.prototype.openFile = function() 4443{ 4444 // Closes dialog after open 4445 window.openFile = new OpenFile(mxUtils.bind(this, function(cancel) 4446 { 4447 this.hideDialog(cancel); 4448 })); 4449 4450 // Removes openFile if dialog is closed 4451 this.showDialog(new OpenDialog(this).container, (Editor.useLocalStorage) ? 640 : 320, 4452 (Editor.useLocalStorage) ? 480 : 220, true, true, function() 4453 { 4454 window.openFile = null; 4455 }); 4456}; 4457 4458/** 4459 * Extracs the graph model from the given HTML data from a data transfer event. 4460 */ 4461EditorUi.prototype.extractGraphModelFromHtml = function(data) 4462{ 4463 var result = null; 4464 4465 try 4466 { 4467 var idx = data.indexOf('<mxGraphModel '); 4468 4469 if (idx >= 0) 4470 { 4471 var idx2 = data.lastIndexOf('</mxGraphModel>'); 4472 4473 if (idx2 > idx) 4474 { 4475 result = data.substring(idx, idx2 + 21).replace(/>/g, '>'). 4476 replace(/</g, '<').replace(/\\"/g, '"').replace(/\n/g, ''); 4477 } 4478 } 4479 } 4480 catch (e) 4481 { 4482 // ignore 4483 } 4484 4485 return result; 4486}; 4487 4488/** 4489 * Opens the given files in the editor. 4490 */ 4491EditorUi.prototype.readGraphModelFromClipboard = function(fn) 4492{ 4493 this.readGraphModelFromClipboardWithType(mxUtils.bind(this, function(xml) 4494 { 4495 if (xml != null) 4496 { 4497 fn(xml); 4498 } 4499 else 4500 { 4501 this.readGraphModelFromClipboardWithType(mxUtils.bind(this, function(xml) 4502 { 4503 if (xml != null) 4504 { 4505 var tmp = decodeURIComponent(xml); 4506 4507 if (this.isCompatibleString(tmp)) 4508 { 4509 xml = tmp; 4510 } 4511 } 4512 4513 fn(xml); 4514 }), 'text'); 4515 } 4516 }), 'html'); 4517}; 4518 4519/** 4520 * Opens the given files in the editor. 4521 */ 4522EditorUi.prototype.readGraphModelFromClipboardWithType = function(fn, type) 4523{ 4524 navigator.clipboard.read().then(mxUtils.bind(this, function(data) 4525 { 4526 if (data != null && data.length > 0 && type == 'html' && 4527 mxUtils.indexOf(data[0].types, 'text/html') >= 0) 4528 { 4529 data[0].getType('text/html').then(mxUtils.bind(this, function(blob) 4530 { 4531 blob.text().then(mxUtils.bind(this, function(value) 4532 { 4533 try 4534 { 4535 var elt = this.parseHtmlData(value); 4536 var asHtml = elt.getAttribute('data-type') != 'text/plain'; 4537 4538 // KNOWN: Paste from IE11 to other browsers on Windows 4539 // seems to paste the contents of index.html 4540 var xml = (asHtml) ? elt.innerHTML : 4541 mxUtils.trim((elt.innerText == null) ? 4542 mxUtils.getTextContent(elt) : elt.innerText); 4543 4544 // Workaround for junk after XML in VM 4545 try 4546 { 4547 var idx = xml.lastIndexOf('%3E'); 4548 4549 if (idx >= 0 && idx < xml.length - 3) 4550 { 4551 xml = xml.substring(0, idx + 3); 4552 } 4553 } 4554 catch (e) 4555 { 4556 // ignore 4557 } 4558 4559 // Checks for embedded XML content 4560 try 4561 { 4562 var spans = elt.getElementsByTagName('span'); 4563 var tmp = (spans != null && spans.length > 0) ? 4564 mxUtils.trim(decodeURIComponent(spans[0].textContent)) : 4565 decodeURIComponent(xml); 4566 4567 if (this.isCompatibleString(tmp)) 4568 { 4569 xml = tmp; 4570 } 4571 } 4572 catch (e) 4573 { 4574 // ignore 4575 } 4576 } 4577 catch (e) 4578 { 4579 // ignore 4580 } 4581 4582 fn(this.isCompatibleString(xml) ? xml : null); 4583 }))['catch'](function(data) 4584 { 4585 fn(null); 4586 }); 4587 }))['catch'](function(data) 4588 { 4589 fn(null); 4590 }); 4591 } 4592 else if (data != null && data.length > 0 && type == 'text' && 4593 mxUtils.indexOf(data[0].types, 'text/plain') >= 0) 4594 { 4595 data[0].getType('text/plain').then(function(blob) 4596 { 4597 blob.text().then(function(value) 4598 { 4599 fn(value); 4600 })['catch'](function() 4601 { 4602 fn(null); 4603 }); 4604 })['catch'](function() 4605 { 4606 fn(null); 4607 }); 4608 } 4609 else 4610 { 4611 fn(null); 4612 } 4613 }))['catch'](function(data) 4614 { 4615 fn(null); 4616 }); 4617}; 4618 4619/** 4620 * Parses the given HTML data and returns a DIV. 4621 */ 4622EditorUi.prototype.parseHtmlData = function(data) 4623{ 4624 var elt = null; 4625 4626 if (data != null && data.length > 0) 4627 { 4628 var hasMeta = data.substring(0, 6) == '<meta '; 4629 elt = document.createElement('div'); 4630 elt.innerHTML = ((hasMeta) ? '<meta charset="utf-8">' : '') + 4631 this.editor.graph.sanitizeHtml(data); 4632 asHtml = true; 4633 4634 // Workaround for innerText not ignoring style elements in Chrome 4635 var styles = elt.getElementsByTagName('style'); 4636 4637 if (styles != null) 4638 { 4639 while (styles.length > 0) 4640 { 4641 styles[0].parentNode.removeChild(styles[0]); 4642 } 4643 } 4644 4645 // Special case of link pasting from Chrome 4646 if (elt.firstChild != null && elt.firstChild.nodeType == mxConstants.NODETYPE_ELEMENT && 4647 elt.firstChild.nextSibling != null && elt.firstChild.nextSibling.nodeType == mxConstants.NODETYPE_ELEMENT && 4648 elt.firstChild.nodeName == 'META' && elt.firstChild.nextSibling.nodeName == 'A' && 4649 elt.firstChild.nextSibling.nextSibling == null) 4650 { 4651 var temp = (elt.firstChild.nextSibling.innerText == null) ? 4652 mxUtils.getTextContent(elt.firstChild.nextSibling) : 4653 elt.firstChild.nextSibling.innerText; 4654 4655 if (temp == elt.firstChild.nextSibling.getAttribute('href')) 4656 { 4657 mxUtils.setTextContent(elt, temp); 4658 asHtml = false; 4659 } 4660 } 4661 4662 // Extracts single image source address with meta tag in markup 4663 var img = (hasMeta && elt.firstChild != null) ? elt.firstChild.nextSibling : elt.firstChild; 4664 4665 if (img != null && img.nextSibling == null && 4666 img.nodeType == mxConstants.NODETYPE_ELEMENT && 4667 img.nodeName == 'IMG') 4668 { 4669 var temp = img.getAttribute('src'); 4670 4671 if (temp != null) 4672 { 4673 if (temp.substring(0, 22) == 'data:image/png;base64,') 4674 { 4675 var xml = this.extractGraphModelFromPng(temp); 4676 4677 if (xml != null && xml.length > 0) 4678 { 4679 temp = xml; 4680 } 4681 } 4682 4683 mxUtils.setTextContent(elt, temp); 4684 asHtml = false; 4685 } 4686 } 4687 else 4688 { 4689 // Extracts embedded XML or image source address from single PNG image 4690 var images = elt.getElementsByTagName('img'); 4691 4692 if (images.length == 1) 4693 { 4694 var img = images[0]; 4695 var temp = img.getAttribute('src'); 4696 4697 if (temp != null && img.parentNode == elt && elt.children.length == 1) 4698 { 4699 if (temp.substring(0, 22) == 'data:image/png;base64,') 4700 { 4701 var xml = this.extractGraphModelFromPng(temp); 4702 4703 if (xml != null && xml.length > 0) 4704 { 4705 temp = xml; 4706 } 4707 } 4708 4709 mxUtils.setTextContent(elt, temp); 4710 asHtml = false; 4711 } 4712 } 4713 } 4714 4715 if (asHtml) 4716 { 4717 Graph.removePasteFormatting(elt); 4718 } 4719 } 4720 4721 if (!asHtml) 4722 { 4723 elt.setAttribute('data-type', 'text/plain'); 4724 } 4725 4726 return elt; 4727}; 4728 4729/** 4730 * Opens the given files in the editor. 4731 */ 4732EditorUi.prototype.extractGraphModelFromEvent = function(evt) 4733{ 4734 var result = null; 4735 var data = null; 4736 4737 if (evt != null) 4738 { 4739 var provider = (evt.dataTransfer != null) ? evt.dataTransfer : evt.clipboardData; 4740 4741 if (provider != null) 4742 { 4743 if (document.documentMode == 10 || document.documentMode == 11) 4744 { 4745 data = provider.getData('Text'); 4746 } 4747 else 4748 { 4749 data = (mxUtils.indexOf(provider.types, 'text/html') >= 0) ? provider.getData('text/html') : null; 4750 4751 if (mxUtils.indexOf(provider.types, 'text/plain' && (data == null || data.length == 0))) 4752 { 4753 data = provider.getData('text/plain'); 4754 } 4755 } 4756 4757 if (data != null) 4758 { 4759 data = Graph.zapGremlins(mxUtils.trim(data)); 4760 4761 // Tries parsing as HTML document with embedded XML 4762 var xml = this.extractGraphModelFromHtml(data); 4763 4764 if (xml != null) 4765 { 4766 data = xml; 4767 } 4768 } 4769 } 4770 } 4771 4772 if (data != null && this.isCompatibleString(data)) 4773 { 4774 result = data; 4775 } 4776 4777 return result; 4778}; 4779 4780/** 4781 * Hook for subclassers to return true if event data is a supported format. 4782 * This implementation always returns false. 4783 */ 4784EditorUi.prototype.isCompatibleString = function(data) 4785{ 4786 return false; 4787}; 4788 4789/** 4790 * Adds the label menu items to the given menu and parent. 4791 */ 4792EditorUi.prototype.saveFile = function(forceDialog) 4793{ 4794 if (!forceDialog && this.editor.filename != null) 4795 { 4796 this.save(this.editor.getOrCreateFilename()); 4797 } 4798 else 4799 { 4800 var dlg = new FilenameDialog(this, this.editor.getOrCreateFilename(), mxResources.get('save'), mxUtils.bind(this, function(name) 4801 { 4802 this.save(name); 4803 }), null, mxUtils.bind(this, function(name) 4804 { 4805 if (name != null && name.length > 0) 4806 { 4807 return true; 4808 } 4809 4810 mxUtils.confirm(mxResources.get('invalidName')); 4811 4812 return false; 4813 })); 4814 this.showDialog(dlg.container, 300, 100, true, true); 4815 dlg.init(); 4816 } 4817}; 4818 4819/** 4820 * Saves the current graph under the given filename. 4821 */ 4822EditorUi.prototype.save = function(name) 4823{ 4824 if (name != null) 4825 { 4826 if (this.editor.graph.isEditing()) 4827 { 4828 this.editor.graph.stopEditing(); 4829 } 4830 4831 var xml = mxUtils.getXml(this.editor.getGraphXml()); 4832 4833 try 4834 { 4835 if (Editor.useLocalStorage) 4836 { 4837 if (localStorage.getItem(name) != null && 4838 !mxUtils.confirm(mxResources.get('replaceIt', [name]))) 4839 { 4840 return; 4841 } 4842 4843 localStorage.setItem(name, xml); 4844 this.editor.setStatus(mxUtils.htmlEntities(mxResources.get('saved')) + ' ' + new Date()); 4845 } 4846 else 4847 { 4848 if (xml.length < MAX_REQUEST_SIZE) 4849 { 4850 new mxXmlRequest(SAVE_URL, 'filename=' + encodeURIComponent(name) + 4851 '&xml=' + encodeURIComponent(xml)).simulate(document, '_blank'); 4852 } 4853 else 4854 { 4855 mxUtils.alert(mxResources.get('drawingTooLarge')); 4856 mxUtils.popup(xml); 4857 4858 return; 4859 } 4860 } 4861 4862 this.editor.setModified(false); 4863 this.editor.setFilename(name); 4864 this.updateDocumentTitle(); 4865 } 4866 catch (e) 4867 { 4868 this.editor.setStatus(mxUtils.htmlEntities(mxResources.get('errorSavingFile'))); 4869 } 4870 } 4871}; 4872 4873/** 4874 * Executes the given layout. 4875 */ 4876EditorUi.prototype.executeLayout = function(exec, animate, post) 4877{ 4878 var graph = this.editor.graph; 4879 4880 if (graph.isEnabled()) 4881 { 4882 graph.getModel().beginUpdate(); 4883 try 4884 { 4885 exec(); 4886 } 4887 catch (e) 4888 { 4889 throw e; 4890 } 4891 finally 4892 { 4893 // Animates the changes in the graph model except 4894 // for Camino, where animation is too slow 4895 if (this.allowAnimation && animate && (navigator.userAgent == null || 4896 navigator.userAgent.indexOf('Camino') < 0)) 4897 { 4898 // New API for animating graph layout results asynchronously 4899 var morph = new mxMorphing(graph); 4900 morph.addListener(mxEvent.DONE, mxUtils.bind(this, function() 4901 { 4902 graph.getModel().endUpdate(); 4903 4904 if (post != null) 4905 { 4906 post(); 4907 } 4908 })); 4909 4910 morph.startAnimation(); 4911 } 4912 else 4913 { 4914 graph.getModel().endUpdate(); 4915 4916 if (post != null) 4917 { 4918 post(); 4919 } 4920 } 4921 } 4922 } 4923}; 4924 4925/** 4926 * Hides the current menu. 4927 */ 4928EditorUi.prototype.showImageDialog = function(title, value, fn, ignoreExisting) 4929{ 4930 var cellEditor = this.editor.graph.cellEditor; 4931 var selState = cellEditor.saveSelection(); 4932 var newValue = mxUtils.prompt(title, value); 4933 cellEditor.restoreSelection(selState); 4934 4935 if (newValue != null && newValue.length > 0) 4936 { 4937 var img = new Image(); 4938 4939 img.onload = function() 4940 { 4941 fn(newValue, img.width, img.height); 4942 }; 4943 img.onerror = function() 4944 { 4945 fn(null); 4946 mxUtils.alert(mxResources.get('fileNotFound')); 4947 }; 4948 4949 img.src = newValue; 4950 } 4951 else 4952 { 4953 fn(null); 4954 } 4955}; 4956 4957/** 4958 * Hides the current menu. 4959 */ 4960EditorUi.prototype.showLinkDialog = function(value, btnLabel, fn) 4961{ 4962 var dlg = new LinkDialog(this, value, btnLabel, fn); 4963 this.showDialog(dlg.container, 420, 90, true, true); 4964 dlg.init(); 4965}; 4966 4967/** 4968 * Hides the current menu. 4969 */ 4970EditorUi.prototype.showDataDialog = function(cell) 4971{ 4972 if (cell != null) 4973 { 4974 var dlg = new EditDataDialog(this, cell); 4975 this.showDialog(dlg.container, 480, 420, true, false, null, false); 4976 dlg.init(); 4977 } 4978}; 4979 4980/** 4981 * Hides the current menu. 4982 */ 4983EditorUi.prototype.showBackgroundImageDialog = function(apply, img) 4984{ 4985 apply = (apply != null) ? apply : mxUtils.bind(this, function(image) 4986 { 4987 var change = new ChangePageSetup(this, null, image); 4988 change.ignoreColor = true; 4989 4990 this.editor.graph.model.execute(change); 4991 }); 4992 4993 var newValue = mxUtils.prompt(mxResources.get('backgroundImage'), (img != null) ? img.src : ''); 4994 4995 if (newValue != null && newValue.length > 0) 4996 { 4997 var img = new Image(); 4998 4999 img.onload = function() 5000 { 5001 apply(new mxImage(newValue, img.width, img.height), false); 5002 }; 5003 img.onerror = function() 5004 { 5005 apply(null, true); 5006 mxUtils.alert(mxResources.get('fileNotFound')); 5007 }; 5008 5009 img.src = newValue; 5010 } 5011 else 5012 { 5013 apply(null); 5014 } 5015}; 5016 5017/** 5018 * Loads the stylesheet for this graph. 5019 */ 5020EditorUi.prototype.setBackgroundImage = function(image) 5021{ 5022 this.editor.graph.setBackgroundImage(image); 5023 this.editor.graph.view.validateBackgroundImage(); 5024 5025 this.fireEvent(new mxEventObject('backgroundImageChanged')); 5026}; 5027 5028/** 5029 * Creates the keyboard event handler for the current graph and history. 5030 */ 5031EditorUi.prototype.confirm = function(msg, okFn, cancelFn) 5032{ 5033 if (mxUtils.confirm(msg)) 5034 { 5035 if (okFn != null) 5036 { 5037 okFn(); 5038 } 5039 } 5040 else if (cancelFn != null) 5041 { 5042 cancelFn(); 5043 } 5044}; 5045 5046/** 5047 * Creates the keyboard event handler for the current graph and history. 5048 */ 5049EditorUi.prototype.createOutline = function(wnd) 5050{ 5051 var outline = new mxOutline(this.editor.graph); 5052 5053 mxEvent.addListener(window, 'resize', function() 5054 { 5055 outline.update(false); 5056 }); 5057 5058 return outline; 5059}; 5060 5061// Alt+Shift+Keycode mapping to action 5062EditorUi.prototype.altShiftActions = {67: 'clearWaypoints', // Alt+Shift+C 5063 65: 'connectionArrows', // Alt+Shift+A 5064 76: 'editLink', // Alt+Shift+L 5065 80: 'connectionPoints', // Alt+Shift+P 5066 84: 'editTooltip', // Alt+Shift+T 5067 86: 'pasteSize', // Alt+Shift+V 5068 88: 'copySize', // Alt+Shift+X 5069 66: 'copyData', // Alt+Shift+B 5070 69: 'pasteData' // Alt+Shift+E 5071}; 5072 5073/** 5074 * Creates the keyboard event handler for the current graph and history. 5075 */ 5076EditorUi.prototype.createKeyHandler = function(editor) 5077{ 5078 var editorUi = this; 5079 var graph = this.editor.graph; 5080 var keyHandler = new mxKeyHandler(graph); 5081 5082 var isEventIgnored = keyHandler.isEventIgnored; 5083 keyHandler.isEventIgnored = function(evt) 5084 { 5085 // Handles undo/redo/ctrl+./,/u via action and allows ctrl+b/i 5086 // only if editing value is HTML (except for FF and Safari) 5087 return !(mxEvent.isShiftDown(evt) && evt.keyCode == 9) && 5088 ((!this.isControlDown(evt) || mxEvent.isShiftDown(evt) || 5089 (evt.keyCode != 90 && evt.keyCode != 89 && evt.keyCode != 188 && 5090 evt.keyCode != 190 && evt.keyCode != 85)) && ((evt.keyCode != 66 && evt.keyCode != 73) || 5091 !this.isControlDown(evt) || (this.graph.cellEditor.isContentEditing() && 5092 !mxClient.IS_FF && !mxClient.IS_SF)) && isEventIgnored.apply(this, arguments)); 5093 }; 5094 5095 // Ignores graph enabled state but not chromeless state 5096 keyHandler.isEnabledForEvent = function(evt) 5097 { 5098 return (!mxEvent.isConsumed(evt) && this.isGraphEvent(evt) && this.isEnabled() && 5099 (editorUi.dialogs == null || editorUi.dialogs.length == 0)); 5100 }; 5101 5102 // Routes command-key to control-key on Mac 5103 keyHandler.isControlDown = function(evt) 5104 { 5105 return mxEvent.isControlDown(evt) || (mxClient.IS_MAC && evt.metaKey); 5106 }; 5107 5108 var thread = null; 5109 5110 // Helper function to move cells with the cursor keys 5111 function nudge(keyCode, stepSize, resize) 5112 { 5113 if (!graph.isSelectionEmpty() && graph.isEnabled()) 5114 { 5115 stepSize = (stepSize != null) ? stepSize : 1; 5116 5117 if (resize) 5118 { 5119 // Resizes all selected vertices 5120 graph.getModel().beginUpdate(); 5121 try 5122 { 5123 var cells = graph.getSelectionCells(); 5124 5125 for (var i = 0; i < cells.length; i++) 5126 { 5127 if (graph.getModel().isVertex(cells[i]) && graph.isCellResizable(cells[i])) 5128 { 5129 var geo = graph.getCellGeometry(cells[i]); 5130 5131 if (geo != null) 5132 { 5133 geo = geo.clone(); 5134 5135 if (keyCode == 37) 5136 { 5137 geo.width = Math.max(0, geo.width - stepSize); 5138 } 5139 else if (keyCode == 38) 5140 { 5141 geo.height = Math.max(0, geo.height - stepSize); 5142 } 5143 else if (keyCode == 39) 5144 { 5145 geo.width += stepSize; 5146 } 5147 else if (keyCode == 40) 5148 { 5149 geo.height += stepSize; 5150 } 5151 5152 graph.getModel().setGeometry(cells[i], geo); 5153 } 5154 } 5155 } 5156 } 5157 finally 5158 { 5159 graph.getModel().endUpdate(); 5160 } 5161 } 5162 else 5163 { 5164 // Moves vertices up/down in a stack layout 5165 var cell = graph.getSelectionCell(); 5166 var parent = graph.model.getParent(cell); 5167 var scale = graph.getView().scale; 5168 var layout = null; 5169 5170 if (graph.getSelectionCount() == 1 && graph.model.isVertex(cell) && 5171 graph.layoutManager != null && !graph.isCellLocked(cell)) 5172 { 5173 layout = graph.layoutManager.getLayout(parent); 5174 } 5175 5176 if (layout != null && layout.constructor == mxStackLayout) 5177 { 5178 var index = parent.getIndex(cell); 5179 5180 if (keyCode == 37 || keyCode == 38) 5181 { 5182 graph.model.add(parent, cell, Math.max(0, index - 1)); 5183 } 5184 else if (keyCode == 39 ||keyCode == 40) 5185 { 5186 graph.model.add(parent, cell, Math.min(graph.model.getChildCount(parent), index + 1)); 5187 } 5188 } 5189 else 5190 { 5191 var handler = graph.graphHandler; 5192 5193 if (handler != null) 5194 { 5195 if (handler.first == null) 5196 { 5197 handler.start(graph.getSelectionCell(), 5198 0, 0, graph.getSelectionCells()); 5199 } 5200 5201 if (handler.first != null) 5202 { 5203 var dx = 0; 5204 var dy = 0; 5205 5206 if (keyCode == 37) 5207 { 5208 dx = -stepSize; 5209 } 5210 else if (keyCode == 38) 5211 { 5212 dy = -stepSize; 5213 } 5214 else if (keyCode == 39) 5215 { 5216 dx = stepSize; 5217 } 5218 else if (keyCode == 40) 5219 { 5220 dy = stepSize; 5221 } 5222 5223 handler.currentDx += dx * scale; 5224 handler.currentDy += dy * scale; 5225 handler.checkPreview(); 5226 handler.updatePreview(); 5227 } 5228 5229 // Groups move steps in undoable change 5230 if (thread != null) 5231 { 5232 window.clearTimeout(thread); 5233 } 5234 5235 thread = window.setTimeout(function() 5236 { 5237 if (handler.first != null) 5238 { 5239 var dx = handler.roundLength(handler.currentDx / scale); 5240 var dy = handler.roundLength(handler.currentDy / scale); 5241 handler.moveCells(handler.cells, dx, dy); 5242 handler.reset(); 5243 } 5244 }, 400); 5245 } 5246 } 5247 } 5248 } 5249 }; 5250 5251 // Overridden to handle special alt+shift+cursor keyboard shortcuts 5252 var directions = {37: mxConstants.DIRECTION_WEST, 38: mxConstants.DIRECTION_NORTH, 5253 39: mxConstants.DIRECTION_EAST, 40: mxConstants.DIRECTION_SOUTH}; 5254 5255 var keyHandlerGetFunction = keyHandler.getFunction; 5256 5257 mxKeyHandler.prototype.getFunction = function(evt) 5258 { 5259 if (graph.isEnabled()) 5260 { 5261 // TODO: Add alt modified state in core API, here are some specific cases 5262 if (mxEvent.isShiftDown(evt) && mxEvent.isAltDown(evt)) 5263 { 5264 var action = editorUi.actions.get(editorUi.altShiftActions[evt.keyCode]); 5265 5266 if (action != null) 5267 { 5268 return action.funct; 5269 } 5270 } 5271 5272 if (directions[evt.keyCode] != null && !graph.isSelectionEmpty()) 5273 { 5274 // On macOS, Control+Cursor is used by Expose so allow for Alt+Control to resize 5275 if (!this.isControlDown(evt) && mxEvent.isShiftDown(evt) && mxEvent.isAltDown(evt)) 5276 { 5277 if (graph.model.isVertex(graph.getSelectionCell())) 5278 { 5279 return function() 5280 { 5281 var cells = graph.connectVertex(graph.getSelectionCell(), directions[evt.keyCode], 5282 graph.defaultEdgeLength, evt, true); 5283 5284 if (cells != null && cells.length > 0) 5285 { 5286 if (cells.length == 1 && graph.model.isEdge(cells[0])) 5287 { 5288 graph.setSelectionCell(graph.model.getTerminal(cells[0], false)); 5289 } 5290 else 5291 { 5292 graph.setSelectionCell(cells[cells.length - 1]); 5293 } 5294 5295 graph.scrollCellToVisible(graph.getSelectionCell()); 5296 5297 if (editorUi.hoverIcons != null) 5298 { 5299 editorUi.hoverIcons.update(graph.view.getState(graph.getSelectionCell())); 5300 } 5301 } 5302 }; 5303 } 5304 } 5305 else 5306 { 5307 // Avoids consuming event if no vertex is selected by returning null below 5308 // Cursor keys move and resize (ctrl) cells 5309 if (this.isControlDown(evt)) 5310 { 5311 return function() 5312 { 5313 nudge(evt.keyCode, (mxEvent.isShiftDown(evt)) ? graph.gridSize : null, true); 5314 }; 5315 } 5316 else 5317 { 5318 return function() 5319 { 5320 nudge(evt.keyCode, (mxEvent.isShiftDown(evt)) ? graph.gridSize : null); 5321 }; 5322 } 5323 } 5324 } 5325 } 5326 5327 return keyHandlerGetFunction.apply(this, arguments); 5328 }; 5329 5330 // Binds keystrokes to actions 5331 keyHandler.bindAction = mxUtils.bind(this, function(code, control, key, shift) 5332 { 5333 var action = this.actions.get(key); 5334 5335 if (action != null) 5336 { 5337 var f = function() 5338 { 5339 if (action.isEnabled()) 5340 { 5341 action.funct(); 5342 } 5343 }; 5344 5345 if (control) 5346 { 5347 if (shift) 5348 { 5349 keyHandler.bindControlShiftKey(code, f); 5350 } 5351 else 5352 { 5353 keyHandler.bindControlKey(code, f); 5354 } 5355 } 5356 else 5357 { 5358 if (shift) 5359 { 5360 keyHandler.bindShiftKey(code, f); 5361 } 5362 else 5363 { 5364 keyHandler.bindKey(code, f); 5365 } 5366 } 5367 } 5368 }); 5369 5370 var ui = this; 5371 var keyHandlerEscape = keyHandler.escape; 5372 keyHandler.escape = function(evt) 5373 { 5374 keyHandlerEscape.apply(this, arguments); 5375 }; 5376 5377 // Ignores enter keystroke. Remove this line if you want the 5378 // enter keystroke to stop editing. N, W, T are reserved. 5379 keyHandler.enter = function() {}; 5380 5381 keyHandler.bindControlShiftKey(36, function() { graph.exitGroup(); }); // Ctrl+Shift+Home 5382 keyHandler.bindControlShiftKey(35, function() { graph.enterGroup(); }); // Ctrl+Shift+End 5383 keyHandler.bindShiftKey(36, function() { graph.home(); }); // Ctrl+Shift+Home 5384 keyHandler.bindKey(35, function() { graph.refresh(); }); // End 5385 keyHandler.bindAction(107, true, 'zoomIn'); // Ctrl+Plus 5386 keyHandler.bindAction(109, true, 'zoomOut'); // Ctrl+Minus 5387 keyHandler.bindAction(80, true, 'print'); // Ctrl+P 5388 keyHandler.bindAction(79, true, 'outline', true); // Ctrl+Shift+O 5389 5390 if (!this.editor.chromeless || this.editor.editable) 5391 { 5392 keyHandler.bindControlKey(36, function() { if (graph.isEnabled()) { graph.foldCells(true); }}); // Ctrl+Home 5393 keyHandler.bindControlKey(35, function() { if (graph.isEnabled()) { graph.foldCells(false); }}); // Ctrl+End 5394 keyHandler.bindControlKey(13, function() { ui.ctrlEnter(); }); // Ctrl+Enter 5395 keyHandler.bindAction(8, false, 'delete'); // Backspace 5396 keyHandler.bindAction(8, true, 'deleteAll'); // Ctrl+Backspace 5397 keyHandler.bindAction(8, false, 'deleteLabels', true); // Shift+Backspace 5398 keyHandler.bindAction(46, false, 'delete'); // Delete 5399 keyHandler.bindAction(46, true, 'deleteAll'); // Ctrl+Delete 5400 keyHandler.bindAction(46, false, 'deleteLabels', true); // Shift+Delete 5401 keyHandler.bindAction(36, false, 'resetView'); // Home 5402 keyHandler.bindAction(72, true, 'fitWindow', true); // Ctrl+Shift+H 5403 keyHandler.bindAction(74, true, 'fitPage'); // Ctrl+J 5404 keyHandler.bindAction(74, true, 'fitTwoPages', true); // Ctrl+Shift+J 5405 keyHandler.bindAction(48, true, 'customZoom'); // Ctrl+0 5406 keyHandler.bindAction(82, true, 'turn'); // Ctrl+R 5407 keyHandler.bindAction(82, true, 'clearDefaultStyle', true); // Ctrl+Shift+R 5408 keyHandler.bindAction(83, true, 'save'); // Ctrl+S 5409 keyHandler.bindAction(83, true, 'saveAs', true); // Ctrl+Shift+S 5410 keyHandler.bindAction(65, true, 'selectAll'); // Ctrl+A 5411 keyHandler.bindAction(65, true, 'selectNone', true); // Ctrl+A 5412 keyHandler.bindAction(73, true, 'selectVertices', true); // Ctrl+Shift+I 5413 keyHandler.bindAction(69, true, 'selectEdges', true); // Ctrl+Shift+E 5414 keyHandler.bindAction(69, true, 'editStyle'); // Ctrl+E 5415 keyHandler.bindAction(66, true, 'bold'); // Ctrl+B 5416 keyHandler.bindAction(66, true, 'toBack', true); // Ctrl+Shift+B 5417 keyHandler.bindAction(70, true, 'toFront', true); // Ctrl+Shift+F 5418 keyHandler.bindAction(68, true, 'duplicate'); // Ctrl+D 5419 keyHandler.bindAction(68, true, 'setAsDefaultStyle', true); // Ctrl+Shift+D 5420 keyHandler.bindAction(90, true, 'undo'); // Ctrl+Z 5421 keyHandler.bindAction(89, true, 'autosize', true); // Ctrl+Shift+Y 5422 keyHandler.bindAction(88, true, 'cut'); // Ctrl+X 5423 keyHandler.bindAction(67, true, 'copy'); // Ctrl+C 5424 keyHandler.bindAction(86, true, 'paste'); // Ctrl+V 5425 keyHandler.bindAction(71, true, 'group'); // Ctrl+G 5426 keyHandler.bindAction(77, true, 'editData'); // Ctrl+M 5427 keyHandler.bindAction(71, true, 'grid', true); // Ctrl+Shift+G 5428 keyHandler.bindAction(73, true, 'italic'); // Ctrl+I 5429 keyHandler.bindAction(76, true, 'lockUnlock'); // Ctrl+L 5430 keyHandler.bindAction(76, true, 'layers', true); // Ctrl+Shift+L 5431 keyHandler.bindAction(80, true, 'formatPanel', true); // Ctrl+Shift+P 5432 keyHandler.bindAction(85, true, 'underline'); // Ctrl+U 5433 keyHandler.bindAction(85, true, 'ungroup', true); // Ctrl+Shift+U 5434 keyHandler.bindAction(190, true, 'superscript'); // Ctrl+. 5435 keyHandler.bindAction(188, true, 'subscript'); // Ctrl+, 5436 keyHandler.bindAction(13, false, 'keyPressEnter'); // Enter 5437 keyHandler.bindKey(113, function() { if (graph.isEnabled()) { graph.startEditingAtCell(); }}); // F2 5438 } 5439 5440 if (!mxClient.IS_WIN) 5441 { 5442 keyHandler.bindAction(90, true, 'redo', true); // Ctrl+Shift+Z 5443 } 5444 else 5445 { 5446 keyHandler.bindAction(89, true, 'redo'); // Ctrl+Y 5447 } 5448 5449 return keyHandler; 5450}; 5451 5452/** 5453 * Creates the keyboard event handler for the current graph and history. 5454 */ 5455EditorUi.prototype.destroy = function() 5456{ 5457 if (this.editor != null) 5458 { 5459 this.editor.destroy(); 5460 this.editor = null; 5461 } 5462 5463 if (this.menubar != null) 5464 { 5465 this.menubar.destroy(); 5466 this.menubar = null; 5467 } 5468 5469 if (this.toolbar != null) 5470 { 5471 this.toolbar.destroy(); 5472 this.toolbar = null; 5473 } 5474 5475 if (this.sidebar != null) 5476 { 5477 this.sidebar.destroy(); 5478 this.sidebar = null; 5479 } 5480 5481 if (this.keyHandler != null) 5482 { 5483 this.keyHandler.destroy(); 5484 this.keyHandler = null; 5485 } 5486 5487 if (this.keydownHandler != null) 5488 { 5489 mxEvent.removeListener(document, 'keydown', this.keydownHandler); 5490 this.keydownHandler = null; 5491 } 5492 5493 if (this.keyupHandler != null) 5494 { 5495 mxEvent.removeListener(document, 'keyup', this.keyupHandler); 5496 this.keyupHandler = null; 5497 } 5498 5499 if (this.resizeHandler != null) 5500 { 5501 mxEvent.removeListener(window, 'resize', this.resizeHandler); 5502 this.resizeHandler = null; 5503 } 5504 5505 if (this.gestureHandler != null) 5506 { 5507 mxEvent.removeGestureListeners(document, this.gestureHandler); 5508 this.gestureHandler = null; 5509 } 5510 5511 if (this.orientationChangeHandler != null) 5512 { 5513 mxEvent.removeListener(window, 'orientationchange', this.orientationChangeHandler); 5514 this.orientationChangeHandler = null; 5515 } 5516 5517 if (this.scrollHandler != null) 5518 { 5519 mxEvent.removeListener(window, 'scroll', this.scrollHandler); 5520 this.scrollHandler = null; 5521 } 5522 5523 if (this.destroyFunctions != null) 5524 { 5525 for (var i = 0; i < this.destroyFunctions.length; i++) 5526 { 5527 this.destroyFunctions[i](); 5528 } 5529 5530 this.destroyFunctions = null; 5531 } 5532 5533 var c = [this.menubarContainer, this.toolbarContainer, this.sidebarContainer, 5534 this.formatContainer, this.diagramContainer, this.footerContainer, 5535 this.chromelessToolbar, this.hsplit, this.sidebarFooterContainer, 5536 this.layersDialog]; 5537 5538 for (var i = 0; i < c.length; i++) 5539 { 5540 if (c[i] != null && c[i].parentNode != null) 5541 { 5542 c[i].parentNode.removeChild(c[i]); 5543 } 5544 } 5545}; 5546