1/** 2 * Copyright (c) 2006-2012, JGraph Ltd 3 */ 4// Workaround for allowing target="_blank" in HTML sanitizer 5// see https://code.google.com/p/google-caja/issues/detail?can=2&q=&colspec=ID%20Type%20Status%20Priority%20Owner%20Summary&groupby=&sort=&id=1296 6if (typeof html4 !== 'undefined') 7{ 8 html4.ATTRIBS['a::target'] = 0; 9 html4.ATTRIBS['source::src'] = 0; 10 html4.ATTRIBS['video::src'] = 0; 11 // Would be nice for tooltips but probably a security risk... 12 //html4.ATTRIBS['video::autoplay'] = 0; 13 //html4.ATTRIBS['video::autobuffer'] = 0; 14} 15 16// Workaround for handling named HTML entities in mxUtils.parseXml 17// LATER: How to configure DOMParser to just ignore all entities? 18(function() 19{ 20 var entities = [ 21 ['nbsp', '160'], 22 ['shy', '173'] 23 ]; 24 25 var parseXml = mxUtils.parseXml; 26 27 mxUtils.parseXml = function(text) 28 { 29 for (var i = 0; i < entities.length; i++) 30 { 31 text = text.replace(new RegExp( 32 '&' + entities[i][0] + ';', 'g'), 33 '&#' + entities[i][1] + ';'); 34 } 35 36 return parseXml(text); 37 }; 38})(); 39 40// Shim for missing toISOString in older versions of IE 41// See https://stackoverflow.com/questions/12907862 42if (!Date.prototype.toISOString) 43{ 44 (function() 45 { 46 function pad(number) 47 { 48 var r = String(number); 49 50 if (r.length === 1) 51 { 52 r = '0' + r; 53 } 54 55 return r; 56 }; 57 58 Date.prototype.toISOString = function() 59 { 60 return this.getUTCFullYear() 61 + '-' + pad( this.getUTCMonth() + 1 ) 62 + '-' + pad( this.getUTCDate() ) 63 + 'T' + pad( this.getUTCHours() ) 64 + ':' + pad( this.getUTCMinutes() ) 65 + ':' + pad( this.getUTCSeconds() ) 66 + '.' + String( (this.getUTCMilliseconds()/1000).toFixed(3) ).slice( 2, 5 ) 67 + 'Z'; 68 }; 69 }()); 70} 71 72// Shim for Date.now() 73if (!Date.now) 74{ 75 Date.now = function() 76 { 77 return new Date().getTime(); 78 }; 79} 80 81// Polyfill for Uint8Array.from in IE11 used in Graph.decompress 82// See https://stackoverflow.com/questions/36810940/alternative-or-polyfill-for-array-from-on-the-internet-explorer 83if (!Uint8Array.from) { 84 Uint8Array.from = (function () { 85 var toStr = Object.prototype.toString; 86 var isCallable = function (fn) { 87 return typeof fn === 'function' || toStr.call(fn) === '[object Function]'; 88 }; 89 var toInteger = function (value) { 90 var number = Number(value); 91 if (isNaN(number)) { return 0; } 92 if (number === 0 || !isFinite(number)) { return number; } 93 return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number)); 94 }; 95 var maxSafeInteger = Math.pow(2, 53) - 1; 96 var toLength = function (value) { 97 var len = toInteger(value); 98 return Math.min(Math.max(len, 0), maxSafeInteger); 99 }; 100 101 // The length property of the from method is 1. 102 return function from(arrayLike/*, mapFn, thisArg */) { 103 // 1. Let C be the this value. 104 var C = this; 105 106 // 2. Let items be ToObject(arrayLike). 107 var items = Object(arrayLike); 108 109 // 3. ReturnIfAbrupt(items). 110 if (arrayLike == null) { 111 throw new TypeError("Array.from requires an array-like object - not null or undefined"); 112 } 113 114 // 4. If mapfn is undefined, then let mapping be false. 115 var mapFn = arguments.length > 1 ? arguments[1] : void undefined; 116 var T; 117 if (typeof mapFn !== 'undefined') { 118 // 5. else 119 // 5. a If IsCallable(mapfn) is false, throw a TypeError exception. 120 if (!isCallable(mapFn)) { 121 throw new TypeError('Array.from: when provided, the second argument must be a function'); 122 } 123 124 // 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined. 125 if (arguments.length > 2) { 126 T = arguments[2]; 127 } 128 } 129 130 // 10. Let lenValue be Get(items, "length"). 131 // 11. Let len be ToLength(lenValue). 132 var len = toLength(items.length); 133 134 // 13. If IsConstructor(C) is true, then 135 // 13. a. Let A be the result of calling the [[Construct]] internal method of C with an argument list containing the single item len. 136 // 14. a. Else, Let A be ArrayCreate(len). 137 var A = isCallable(C) ? Object(new C(len)) : new Array(len); 138 139 // 16. Let k be 0. 140 var k = 0; 141 // 17. Repeat, while k < len… (also steps a - h) 142 var kValue; 143 while (k < len) { 144 kValue = items[k]; 145 if (mapFn) { 146 A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k); 147 } else { 148 A[k] = kValue; 149 } 150 k += 1; 151 } 152 // 18. Let putStatus be Put(A, "length", len, true). 153 A.length = len; 154 // 20. Return A. 155 return A; 156 }; 157 }()); 158} 159 160// Changes default colors 161/** 162 * Measurements Units 163 */ 164mxConstants.POINTS = 1; 165mxConstants.MILLIMETERS = 2; 166mxConstants.INCHES = 3; 167mxConstants.METERS = 4; 168/** 169 * This ratio is with page scale 1 170 */ 171mxConstants.PIXELS_PER_MM = 3.937; 172mxConstants.PIXELS_PER_INCH = 100; 173 174mxConstants.SHADOW_OPACITY = 0.25; 175mxConstants.SHADOWCOLOR = '#000000'; 176mxConstants.VML_SHADOWCOLOR = '#d0d0d0'; 177mxGraph.prototype.pageBreakColor = '#c0c0c0'; 178mxGraph.prototype.pageScale = 1; 179 180// Letter page format is default in US, Canada and Mexico 181(function() 182{ 183 try 184 { 185 if (navigator != null && navigator.language != null) 186 { 187 var lang = navigator.language.toLowerCase(); 188 mxGraph.prototype.pageFormat = (lang === 'en-us' || lang === 'en-ca' || lang === 'es-mx') ? 189 mxConstants.PAGE_FORMAT_LETTER_PORTRAIT : mxConstants.PAGE_FORMAT_A4_PORTRAIT; 190 } 191 } 192 catch (e) 193 { 194 // ignore 195 } 196})(); 197 198// Matches label positions of mxGraph 1.x 199mxText.prototype.baseSpacingTop = 5; 200mxText.prototype.baseSpacingBottom = 1; 201 202// Keeps edges between relative child cells inside parent 203mxGraphModel.prototype.ignoreRelativeEdgeParent = false; 204 205// Defines grid properties 206mxGraphView.prototype.gridImage = (mxClient.IS_SVG) ? 'data:image/gif;base64,R0lGODlhCgAKAJEAAAAAAP///8zMzP///yH5BAEAAAMALAAAAAAKAAoAAAIJ1I6py+0Po2wFADs=' : 207 IMAGE_PATH + '/grid.gif'; 208mxGraphView.prototype.gridSteps = 4; 209mxGraphView.prototype.minGridSize = 4; 210 211// UrlParams is null in embed mode 212mxGraphView.prototype.defaultGridColor = '#d0d0d0'; 213mxGraphView.prototype.defaultDarkGridColor = '#6e6e6e'; 214mxGraphView.prototype.gridColor = mxGraphView.prototype.defaultGridColor; 215 216//Units 217mxGraphView.prototype.unit = mxConstants.POINTS; 218 219mxGraphView.prototype.setUnit = function(unit) 220{ 221 if (this.unit != unit) 222 { 223 this.unit = unit; 224 225 this.fireEvent(new mxEventObject('unitChanged', 'unit', unit)); 226 } 227}; 228 229// Alternative text for unsupported foreignObjects 230mxSvgCanvas2D.prototype.foAltText = '[Not supported by viewer]'; 231 232// Hook for custom constraints 233mxShape.prototype.getConstraints = function(style, w, h) 234{ 235 return null; 236}; 237 238// Override for clipSvg style. 239mxImageShape.prototype.getImageDataUri = function() 240{ 241 var src = this.image; 242 243 if (src.substring(0, 26) == 'data:image/svg+xml;base64,' && this.style != null && 244 mxUtils.getValue(this.style, 'clipSvg', '0') == '1') 245 { 246 if (this.clippedSvg == null || this.clippedImage != src) 247 { 248 this.clippedSvg = Graph.clipSvgDataUri(src); 249 this.clippedImage = src; 250 } 251 252 src = this.clippedSvg; 253 } 254 255 return src; 256}; 257 258 259/** 260 * Constructs a new graph instance. Note that the constructor does not take a 261 * container because the graph instance is needed for creating the UI, which 262 * in turn will create the container for the graph. Hence, the container is 263 * assigned later in EditorUi. 264 */ 265/** 266 * Defines graph class. 267 */ 268Graph = function(container, model, renderHint, stylesheet, themes, standalone) 269{ 270 mxGraph.call(this, container, model, renderHint, stylesheet); 271 272 this.themes = themes || this.defaultThemes; 273 this.currentEdgeStyle = mxUtils.clone(this.defaultEdgeStyle); 274 this.currentVertexStyle = mxUtils.clone(this.defaultVertexStyle); 275 this.standalone = (standalone != null) ? standalone : false; 276 277 // Sets the base domain URL and domain path URL for relative links. 278 var b = this.baseUrl; 279 var p = b.indexOf('//'); 280 this.domainUrl = ''; 281 this.domainPathUrl = ''; 282 283 if (p > 0) 284 { 285 var d = b.indexOf('/', p + 2); 286 287 if (d > 0) 288 { 289 this.domainUrl = b.substring(0, d); 290 } 291 292 d = b.lastIndexOf('/'); 293 294 if (d > 0) 295 { 296 this.domainPathUrl = b.substring(0, d + 1); 297 } 298 } 299 300 // Adds support for HTML labels via style. Note: Currently, only the Java 301 // backend supports HTML labels but CSS support is limited to the following: 302 // http://docs.oracle.com/javase/6/docs/api/index.html?javax/swing/text/html/CSS.html 303 // TODO: Wrap should not affect isHtmlLabel output (should be handled later) 304 this.isHtmlLabel = function(cell) 305 { 306 var style = this.getCurrentCellStyle(cell); 307 308 return (style != null) ? (style['html'] == '1' || style[mxConstants.STYLE_WHITE_SPACE] == 'wrap') : false; 309 }; 310 311 // Implements a listener for hover and click handling on edges 312 if (this.edgeMode) 313 { 314 var start = { 315 point: null, 316 event: null, 317 state: null, 318 handle: null, 319 selected: false 320 }; 321 322 // Uses this event to process mouseDown to check the selection state before it is changed 323 this.addListener(mxEvent.FIRE_MOUSE_EVENT, mxUtils.bind(this, function(sender, evt) 324 { 325 if (evt.getProperty('eventName') == 'mouseDown' && this.isEnabled()) 326 { 327 var me = evt.getProperty('event'); 328 var state = me.getState(); 329 var s = this.view.scale; 330 331 if (!mxEvent.isAltDown(me.getEvent()) && state != null) 332 { 333 // Checks if state was removed in call to stopEditing above 334 if (this.model.isEdge(state.cell)) 335 { 336 start.point = new mxPoint(me.getGraphX(), me.getGraphY()); 337 start.selected = this.isCellSelected(state.cell); 338 start.state = state; 339 start.event = me; 340 341 if (state.text != null && state.text.boundingBox != null && 342 mxUtils.contains(state.text.boundingBox, me.getGraphX(), me.getGraphY())) 343 { 344 start.handle = mxEvent.LABEL_HANDLE; 345 } 346 else 347 { 348 var handler = this.selectionCellsHandler.getHandler(state.cell); 349 350 if (handler != null && handler.bends != null && handler.bends.length > 0) 351 { 352 start.handle = handler.getHandleForEvent(me); 353 } 354 } 355 } 356 else if (!this.panningHandler.isActive() && !mxEvent.isControlDown(me.getEvent())) 357 { 358 var handler = this.selectionCellsHandler.getHandler(state.cell); 359 360 // Cell handles have precedence over row and col resize 361 if (handler == null || handler.getHandleForEvent(me) == null) 362 { 363 var box = new mxRectangle(me.getGraphX() - 1, me.getGraphY() - 1); 364 box.grow(mxEvent.isTouchEvent(me.getEvent()) ? 365 mxShape.prototype.svgStrokeTolerance - 1 : 366 (mxShape.prototype.svgStrokeTolerance + 1) / 2); 367 368 if (this.isTableCell(state.cell) && !this.isCellSelected(state.cell)) 369 { 370 var row = this.model.getParent(state.cell); 371 var table = this.model.getParent(row); 372 373 if (!this.isCellSelected(table)) 374 { 375 var geo = this.getCellGeometry(state.cell); 376 var g = (geo.alternateBounds != null) ? geo.alternateBounds : geo; 377 378 if ((mxUtils.intersects(box, new mxRectangle(state.x, state.y - 2, g.width * s, 3)) && 379 this.model.getChildAt(table, 0) != row) || mxUtils.intersects(box, new mxRectangle( 380 state.x, state.y + g.height - 2, g.width, 3)) || 381 (mxUtils.intersects(box, new mxRectangle(state.x - 2, state.y, 2, g.height * s)) && 382 this.model.getChildAt(row, 0) != state.cell) || mxUtils.intersects(box, new mxRectangle( 383 state.x + g.width * s - 2, state.y, 2, g.height * s))) 384 { 385 var wasSelected = this.selectionCellsHandler.isHandled(table); 386 this.selectCellForEvent(table, me.getEvent()); 387 handler = this.selectionCellsHandler.getHandler(table); 388 389 if (handler != null) 390 { 391 var handle = handler.getHandleForEvent(me); 392 393 if (handle != null) 394 { 395 handler.start(me.getGraphX(), me.getGraphY(), handle); 396 handler.blockDelayedSelection = !wasSelected; 397 me.consume(); 398 } 399 } 400 } 401 } 402 } 403 404 // Hover for swimlane start sizes inside tables 405 var current = state; 406 407 while (!me.isConsumed() && current != null && (this.isTableCell(current.cell) || 408 this.isTableRow(current.cell) || this.isTable(current.cell))) 409 { 410 if (this.isSwimlane(current.cell)) 411 { 412 var offset = this.getActualStartSize(current.cell); 413 414 if (((offset.x > 0 || offset.width > 0) && mxUtils.intersects(box, new mxRectangle( 415 current.x + (offset.x - offset.width - 1) * s + ((offset.x == 0) ? current.width : 0), 416 current.y, 1, current.height))) || ((offset.y > 0 || offset.height > 0) && 417 mxUtils.intersects(box, new mxRectangle(current.x, current.y + (offset.y - 418 offset.height - 1) * s + ((offset.y == 0) ? current.height : 0), current.width, 1)))) 419 { 420 this.selectCellForEvent(current.cell, me.getEvent()); 421 handler = this.selectionCellsHandler.getHandler(current.cell); 422 423 if (handler != null) 424 { 425 // Swimlane start size handle is last custom handle 426 var handle = mxEvent.CUSTOM_HANDLE - handler.customHandles.length + 1; 427 handler.start(me.getGraphX(), me.getGraphY(), handle); 428 me.consume(); 429 } 430 } 431 } 432 433 current = this.view.getState(this.model.getParent(current.cell)); 434 } 435 } 436 } 437 } 438 } 439 })); 440 441 var mouseDown = null; 442 443 this.addMouseListener( 444 { 445 mouseDown: function(sender, me) {}, 446 mouseMove: mxUtils.bind(this, function(sender, me) 447 { 448 // Checks if any other handler is active 449 var handlerMap = this.selectionCellsHandler.handlers.map; 450 451 for (var key in handlerMap) 452 { 453 if (handlerMap[key].index != null) 454 { 455 return; 456 } 457 } 458 459 if (this.isEnabled() && !this.panningHandler.isActive() && !mxEvent.isAltDown(me.getEvent())) 460 { 461 var tol = this.tolerance; 462 463 if (start.point != null && start.state != null && start.event != null) 464 { 465 var state = start.state; 466 467 if (Math.abs(start.point.x - me.getGraphX()) > tol || 468 Math.abs(start.point.y - me.getGraphY()) > tol) 469 { 470 var handler = this.selectionCellsHandler.getHandler(state.cell); 471 472 if (handler == null && this.model.isEdge(state.cell)) 473 { 474 handler = this.createHandler(state); 475 } 476 477 if (handler != null && handler.bends != null && handler.bends.length > 0) 478 { 479 var handle = handler.getHandleForEvent(start.event); 480 var edgeStyle = this.view.getEdgeStyle(state); 481 var entity = edgeStyle == mxEdgeStyle.EntityRelation; 482 483 // Handles special case where label was clicked on unselected edge in which 484 // case the label will be moved regardless of the handle that is returned 485 if (!start.selected && start.handle == mxEvent.LABEL_HANDLE) 486 { 487 handle = start.handle; 488 } 489 490 if (!entity || handle == 0 || handle == handler.bends.length - 1 || handle == mxEvent.LABEL_HANDLE) 491 { 492 // Source or target handle or connected for direct handle access or orthogonal line 493 // with just two points where the central handle is moved regardless of mouse position 494 if (handle == mxEvent.LABEL_HANDLE || handle == 0 || state.visibleSourceState != null || 495 handle == handler.bends.length - 1 || state.visibleTargetState != null) 496 { 497 if (!entity && handle != mxEvent.LABEL_HANDLE) 498 { 499 var pts = state.absolutePoints; 500 501 // Default case where handles are at corner points handles 502 // drag of corner as drag of existing point 503 if (pts != null && ((edgeStyle == null && handle == null) || 504 edgeStyle == mxEdgeStyle.OrthConnector)) 505 { 506 // Does not use handles if they were not initially visible 507 handle = start.handle; 508 509 if (handle == null) 510 { 511 var box = new mxRectangle(start.point.x, start.point.y); 512 box.grow(mxEdgeHandler.prototype.handleImage.width / 2); 513 514 if (mxUtils.contains(box, pts[0].x, pts[0].y)) 515 { 516 // Moves source terminal handle 517 handle = 0; 518 } 519 else if (mxUtils.contains(box, pts[pts.length - 1].x, pts[pts.length - 1].y)) 520 { 521 // Moves target terminal handle 522 handle = handler.bends.length - 1; 523 } 524 else 525 { 526 // Checks if edge has no bends 527 var nobends = edgeStyle != null && (pts.length == 2 || (pts.length == 3 && 528 ((Math.round(pts[0].x - pts[1].x) == 0 && Math.round(pts[1].x - pts[2].x) == 0) || 529 (Math.round(pts[0].y - pts[1].y) == 0 && Math.round(pts[1].y - pts[2].y) == 0)))); 530 531 if (nobends) 532 { 533 // Moves central handle for straight orthogonal edges 534 handle = 2; 535 } 536 else 537 { 538 // Finds and moves vertical or horizontal segment 539 handle = mxUtils.findNearestSegment(state, start.point.x, start.point.y); 540 541 // Converts segment to virtual handle index 542 if (edgeStyle == null) 543 { 544 handle = mxEvent.VIRTUAL_HANDLE - handle; 545 } 546 // Maps segment to handle 547 else 548 { 549 handle += 1; 550 } 551 } 552 } 553 } 554 } 555 556 // Creates a new waypoint and starts moving it 557 if (handle == null) 558 { 559 handle = mxEvent.VIRTUAL_HANDLE; 560 } 561 } 562 563 handler.start(me.getGraphX(), me.getGraphX(), handle); 564 me.consume(); 565 566 // Removes preview rectangle in graph handler 567 this.graphHandler.reset(); 568 } 569 } 570 else if (entity && (state.visibleSourceState != null || state.visibleTargetState != null)) 571 { 572 // Disables moves on entity to make it consistent 573 this.graphHandler.reset(); 574 me.consume(); 575 } 576 } 577 578 if (handler != null) 579 { 580 // Lazy selection for edges inside groups 581 if (this.selectionCellsHandler.isHandlerActive(handler)) 582 { 583 if (!this.isCellSelected(state.cell)) 584 { 585 this.selectionCellsHandler.handlers.put(state.cell, handler); 586 this.selectCellForEvent(state.cell, me.getEvent()); 587 } 588 } 589 else if (!this.isCellSelected(state.cell)) 590 { 591 // Destroy temporary handler 592 handler.destroy(); 593 } 594 } 595 596 // Reset start state 597 start.selected = false; 598 start.handle = null; 599 start.state = null; 600 start.event = null; 601 start.point = null; 602 } 603 } 604 else 605 { 606 // Updates cursor for unselected edges under the mouse 607 var state = me.getState(); 608 609 if (state != null && this.isCellEditable(state.cell)) 610 { 611 var cursor = null; 612 613 // Checks if state was removed in call to stopEditing above 614 if (this.model.isEdge(state.cell)) 615 { 616 var box = new mxRectangle(me.getGraphX(), me.getGraphY()); 617 box.grow(mxEdgeHandler.prototype.handleImage.width / 2); 618 var pts = state.absolutePoints; 619 620 if (pts != null) 621 { 622 if (state.text != null && state.text.boundingBox != null && 623 mxUtils.contains(state.text.boundingBox, me.getGraphX(), me.getGraphY())) 624 { 625 cursor = 'move'; 626 } 627 else if (mxUtils.contains(box, pts[0].x, pts[0].y) || 628 mxUtils.contains(box, pts[pts.length - 1].x, pts[pts.length - 1].y)) 629 { 630 cursor = 'pointer'; 631 } 632 else if (state.visibleSourceState != null || state.visibleTargetState != null) 633 { 634 // Moving is not allowed for entity relation but still indicate hover state 635 var tmp = this.view.getEdgeStyle(state); 636 cursor = 'crosshair'; 637 638 if (tmp != mxEdgeStyle.EntityRelation && this.isOrthogonal(state)) 639 { 640 var idx = mxUtils.findNearestSegment(state, me.getGraphX(), me.getGraphY()); 641 642 if (idx < pts.length - 1 && idx >= 0) 643 { 644 cursor = (Math.round(pts[idx].x - pts[idx + 1].x) == 0) ? 645 'col-resize' : 'row-resize'; 646 } 647 } 648 } 649 } 650 } 651 else if (!mxEvent.isControlDown(me.getEvent())) 652 { 653 var box = new mxRectangle(me.getGraphX() - 1, me.getGraphY() - 1); 654 box.grow(mxShape.prototype.svgStrokeTolerance / 2); 655 656 if (this.isTableCell(state.cell)) 657 { 658 var row = this.model.getParent(state.cell); 659 var table = this.model.getParent(row); 660 661 if (!this.isCellSelected(table)) 662 { 663 if ((mxUtils.intersects(box, new mxRectangle(state.x - 2, state.y, 2, state.height)) && 664 this.model.getChildAt(row, 0) != state.cell) || mxUtils.intersects(box, 665 new mxRectangle(state.x + state.width - 2, state.y, 2, state.height))) 666 { 667 cursor ='col-resize'; 668 } 669 else if ((mxUtils.intersects(box, new mxRectangle(state.x, state.y - 2, state.width, 3)) && 670 this.model.getChildAt(table, 0) != row) || mxUtils.intersects(box, 671 new mxRectangle(state.x, state.y + state.height - 2, state.width, 3))) 672 { 673 cursor ='row-resize'; 674 } 675 } 676 } 677 678 // Hover for swimlane start sizes inside tables 679 var current = state; 680 681 while (cursor == null && current != null && (this.isTableCell(current.cell) || 682 this.isTableRow(current.cell) || this.isTable(current.cell))) 683 { 684 if (this.isSwimlane(current.cell)) 685 { 686 var offset = this.getActualStartSize(current.cell); 687 var s = this.view.scale; 688 689 if ((offset.x > 0 || offset.width > 0) && mxUtils.intersects(box, new mxRectangle( 690 current.x + (offset.x - offset.width - 1) * s + ((offset.x == 0) ? current.width * s : 0), 691 current.y, 1, current.height))) 692 { 693 cursor ='col-resize'; 694 } 695 else if ((offset.y > 0 || offset.height > 0) && mxUtils.intersects(box, new mxRectangle( 696 current.x, current.y + (offset.y - offset.height - 1) * s + ((offset.y == 0) ? current.height : 0), 697 current.width, 1))) 698 { 699 cursor ='row-resize'; 700 } 701 } 702 703 current = this.view.getState(this.model.getParent(current.cell)); 704 } 705 } 706 707 if (cursor != null) 708 { 709 state.setCursor(cursor); 710 } 711 } 712 } 713 } 714 }), 715 mouseUp: mxUtils.bind(this, function(sender, me) 716 { 717 start.state = null; 718 start.event = null; 719 start.point = null; 720 start.handle = null; 721 }) 722 }); 723 } 724 725 this.cellRenderer.minSvgStrokeWidth = 0.1; 726 727 // HTML entities are displayed as plain text in wrapped plain text labels 728 this.cellRenderer.getLabelValue = function(state) 729 { 730 var result = mxCellRenderer.prototype.getLabelValue.apply(this, arguments); 731 732 if (state.view.graph.isHtmlLabel(state.cell)) 733 { 734 if (state.style['html'] != 1) 735 { 736 result = mxUtils.htmlEntities(result, false); 737 } 738 else 739 { 740 result = state.view.graph.sanitizeHtml(result); 741 } 742 } 743 744 return result; 745 }; 746 747 // All code below not available and not needed in embed mode 748 if (typeof mxVertexHandler !== 'undefined') 749 { 750 this.setConnectable(true); 751 this.setDropEnabled(true); 752 this.setPanning(true); 753 this.setTooltips(true); 754 this.setAllowLoops(true); 755 this.allowAutoPanning = true; 756 this.resetEdgesOnConnect = false; 757 this.constrainChildren = false; 758 this.constrainRelativeChildren = true; 759 760 // Do not scroll after moving cells 761 this.graphHandler.scrollOnMove = false; 762 this.graphHandler.scaleGrid = true; 763 764 // Disables cloning of connection sources by default 765 this.connectionHandler.setCreateTarget(false); 766 this.connectionHandler.insertBeforeSource = true; 767 768 // Disables built-in connection starts 769 this.connectionHandler.isValidSource = function(cell, me) 770 { 771 return false; 772 }; 773 774 // Sets the style to be used when an elbow edge is double clicked 775 this.alternateEdgeStyle = 'vertical'; 776 777 if (stylesheet == null) 778 { 779 this.loadStylesheet(); 780 } 781 782 // Adds page centers to the guides for moving cells 783 var graphHandlerGetGuideStates = this.graphHandler.getGuideStates; 784 this.graphHandler.getGuideStates = function() 785 { 786 var result = graphHandlerGetGuideStates.apply(this, arguments); 787 788 // Create virtual cell state for page centers 789 if (this.graph.pageVisible) 790 { 791 var guides = []; 792 793 var pf = this.graph.pageFormat; 794 var ps = this.graph.pageScale; 795 var pw = pf.width * ps; 796 var ph = pf.height * ps; 797 var t = this.graph.view.translate; 798 var s = this.graph.view.scale; 799 800 var layout = this.graph.getPageLayout(); 801 802 for (var i = 0; i < layout.width; i++) 803 { 804 guides.push(new mxRectangle(((layout.x + i) * pw + t.x) * s, 805 (layout.y * ph + t.y) * s, pw * s, ph * s)); 806 } 807 808 for (var j = 1; j < layout.height; j++) 809 { 810 guides.push(new mxRectangle((layout.x * pw + t.x) * s, 811 ((layout.y + j) * ph + t.y) * s, pw * s, ph * s)); 812 } 813 814 // Page center guides have precedence over normal guides 815 result = guides.concat(result); 816 } 817 818 return result; 819 }; 820 821 // Overrides zIndex for dragElement 822 mxDragSource.prototype.dragElementZIndex = mxPopupMenu.prototype.zIndex; 823 824 // Overrides color for virtual guides for page centers 825 mxGuide.prototype.getGuideColor = function(state, horizontal) 826 { 827 return (state.cell == null) ? '#ffa500' /* orange */ : mxConstants.GUIDE_COLOR; 828 }; 829 830 // Changes color of move preview for black backgrounds 831 this.graphHandler.createPreviewShape = function(bounds) 832 { 833 this.previewColor = (this.graph.background == '#000000') ? '#ffffff' : mxGraphHandler.prototype.previewColor; 834 835 return mxGraphHandler.prototype.createPreviewShape.apply(this, arguments); 836 }; 837 838 // Handles parts of cells by checking if part=1 is in the style and returning the parent 839 // if the parent is not already in the list of cells. container style is used to disable 840 // step into swimlanes and dropTarget style is used to disable acting as a drop target. 841 // LATER: Handle recursive parts 842 var graphHandlerGetCells = this.graphHandler.getCells; 843 844 this.graphHandler.getCells = function(initialCell) 845 { 846 var cells = graphHandlerGetCells.apply(this, arguments); 847 var lookup = new mxDictionary(); 848 var newCells = []; 849 850 for (var i = 0; i < cells.length; i++) 851 { 852 // Propagates to composite parents or moves selected table rows 853 var cell = (this.graph.isTableCell(initialCell) && 854 this.graph.isTableCell(cells[i]) && 855 this.graph.isCellSelected(cells[i])) ? 856 this.graph.model.getParent(cells[i]) : 857 ((this.graph.isTableRow(initialCell) && 858 this.graph.isTableRow(cells[i]) && 859 this.graph.isCellSelected(cells[i])) ? 860 cells[i] : this.graph.getCompositeParent(cells[i])); 861 862 if (cell != null && !lookup.get(cell)) 863 { 864 lookup.put(cell, true); 865 newCells.push(cell); 866 } 867 } 868 869 return newCells; 870 }; 871 872 // Handles parts and selected rows in tables of cells for drag and drop 873 var graphHandlerStart = this.graphHandler.start; 874 875 this.graphHandler.start = function(cell, x, y, cells) 876 { 877 // Propagates to selected table row to start move 878 var ignoreParent = false; 879 880 if (this.graph.isTableCell(cell)) 881 { 882 if (!this.graph.isCellSelected(cell)) 883 { 884 cell = this.graph.model.getParent(cell); 885 } 886 else 887 { 888 ignoreParent = true; 889 } 890 } 891 892 if (!ignoreParent && (!this.graph.isTableRow(cell) || !this.graph.isCellSelected(cell))) 893 { 894 cell = this.graph.getCompositeParent(cell); 895 } 896 897 graphHandlerStart.apply(this, arguments); 898 }; 899 900 // Handles parts of cells when cloning the source for new connections 901 this.connectionHandler.createTargetVertex = function(evt, source) 902 { 903 source = this.graph.getCompositeParent(source); 904 905 return mxConnectionHandler.prototype.createTargetVertex.apply(this, arguments); 906 }; 907 908 var rubberband = new mxRubberband(this); 909 910 this.getRubberband = function() 911 { 912 return rubberband; 913 }; 914 915 // Timer-based activation of outline connect in connection handler 916 var startTime = new Date().getTime(); 917 var timeOnTarget = 0; 918 919 var connectionHandlerMouseMove = this.connectionHandler.mouseMove; 920 921 this.connectionHandler.mouseMove = function() 922 { 923 var prev = this.currentState; 924 connectionHandlerMouseMove.apply(this, arguments); 925 926 if (prev != this.currentState) 927 { 928 startTime = new Date().getTime(); 929 timeOnTarget = 0; 930 } 931 else 932 { 933 timeOnTarget = new Date().getTime() - startTime; 934 } 935 }; 936 937 // Activates outline connect after 1500ms with touch event or if alt is pressed inside the shape 938 // outlineConnect=0 is a custom style that means do not connect to strokes inside the shape, 939 // or in other words, connect to the shape's perimeter if the highlight is under the mouse 940 // (the name is because the highlight, including all strokes, is called outline in the code) 941 var connectionHandleIsOutlineConnectEvent = this.connectionHandler.isOutlineConnectEvent; 942 943 this.connectionHandler.isOutlineConnectEvent = function(me) 944 { 945 return (this.currentState != null && me.getState() == this.currentState && timeOnTarget > 2000) || 946 ((this.currentState == null || mxUtils.getValue(this.currentState.style, 'outlineConnect', '1') != '0') && 947 connectionHandleIsOutlineConnectEvent.apply(this, arguments)); 948 }; 949 950 // Adds shift+click to toggle selection state 951 var isToggleEvent = this.isToggleEvent; 952 this.isToggleEvent = function(evt) 953 { 954 return isToggleEvent.apply(this, arguments) || (!mxClient.IS_CHROMEOS && mxEvent.isShiftDown(evt)); 955 }; 956 957 // Workaround for Firefox where first mouse down is received 958 // after tap and hold if scrollbars are visible, which means 959 // start rubberband immediately if no cell is under mouse. 960 var isForceRubberBandEvent = rubberband.isForceRubberbandEvent; 961 rubberband.isForceRubberbandEvent = function(me) 962 { 963 return (isForceRubberBandEvent.apply(this, arguments) && !mxEvent.isShiftDown(me.getEvent()) && 964 !mxEvent.isControlDown(me.getEvent())) || (mxClient.IS_CHROMEOS && mxEvent.isShiftDown(me.getEvent())) || 965 (mxUtils.hasScrollbars(this.graph.container) && mxClient.IS_FF && 966 mxClient.IS_WIN && me.getState() == null && mxEvent.isTouchEvent(me.getEvent())); 967 }; 968 969 // Shows hand cursor while panning 970 var prevCursor = null; 971 972 this.panningHandler.addListener(mxEvent.PAN_START, mxUtils.bind(this, function() 973 { 974 if (this.isEnabled()) 975 { 976 prevCursor = this.container.style.cursor; 977 this.container.style.cursor = 'move'; 978 } 979 })); 980 981 this.panningHandler.addListener(mxEvent.PAN_END, mxUtils.bind(this, function() 982 { 983 if (this.isEnabled()) 984 { 985 this.container.style.cursor = prevCursor; 986 } 987 })); 988 989 this.popupMenuHandler.autoExpand = true; 990 991 this.popupMenuHandler.isSelectOnPopup = function(me) 992 { 993 return mxEvent.isMouseEvent(me.getEvent()); 994 }; 995 996 // Handles links if graph is read-only or cell is locked 997 var click = this.click; 998 this.click = function(me) 999 { 1000 var locked = me.state == null && me.sourceState != null && 1001 this.isCellLocked(me.sourceState.cell); 1002 1003 if ((!this.isEnabled() || locked) && !me.isConsumed()) 1004 { 1005 var cell = (locked) ? me.sourceState.cell : me.getCell(); 1006 1007 if (cell != null) 1008 { 1009 var link = this.getClickableLinkForCell(cell); 1010 1011 if (link != null) 1012 { 1013 if (this.isCustomLink(link)) 1014 { 1015 this.customLinkClicked(link); 1016 } 1017 else 1018 { 1019 this.openLink(link); 1020 } 1021 } 1022 } 1023 1024 if (this.isEnabled() && locked) 1025 { 1026 this.clearSelection(); 1027 } 1028 } 1029 else 1030 { 1031 return click.apply(this, arguments); 1032 } 1033 }; 1034 1035 // Redirects tooltips for locked cells 1036 this.tooltipHandler.getStateForEvent = function(me) 1037 { 1038 return me.sourceState; 1039 }; 1040 1041 // Opens links in tooltips in new windows 1042 var tooltipHandlerShow = this.tooltipHandler.show; 1043 this.tooltipHandler.show = function() 1044 { 1045 tooltipHandlerShow.apply(this, arguments); 1046 1047 if (this.div != null) 1048 { 1049 var links = this.div.getElementsByTagName('a'); 1050 1051 for (var i = 0; i < links.length; i++) 1052 { 1053 if (links[i].getAttribute('href') != null && 1054 links[i].getAttribute('target') == null) 1055 { 1056 links[i].setAttribute('target', '_blank'); 1057 } 1058 } 1059 } 1060 }; 1061 1062 // Redirects tooltips for locked cells 1063 this.tooltipHandler.getStateForEvent = function(me) 1064 { 1065 return me.sourceState; 1066 }; 1067 1068 // Redirects cursor for locked cells 1069 var getCursorForMouseEvent = this.getCursorForMouseEvent; 1070 this.getCursorForMouseEvent = function(me) 1071 { 1072 var locked = me.state == null && me.sourceState != null && this.isCellLocked(me.sourceState.cell); 1073 1074 return this.getCursorForCell((locked) ? me.sourceState.cell : me.getCell()); 1075 }; 1076 1077 // Shows pointer cursor for clickable cells with links 1078 // ie. if the graph is disabled and cells cannot be selected 1079 var getCursorForCell = this.getCursorForCell; 1080 this.getCursorForCell = function(cell) 1081 { 1082 if (!this.isEnabled() || this.isCellLocked(cell)) 1083 { 1084 var link = this.getClickableLinkForCell(cell); 1085 1086 if (link != null) 1087 { 1088 return 'pointer'; 1089 } 1090 else if (this.isCellLocked(cell)) 1091 { 1092 return 'default'; 1093 } 1094 } 1095 1096 return getCursorForCell.apply(this, arguments); 1097 }; 1098 1099 // Changes rubberband selection ignore locked cells 1100 this.selectRegion = function(rect, evt) 1101 { 1102 var isect = (mxEvent.isAltDown(evt)) ? rect : null; 1103 1104 var cells = this.getCells(rect.x, rect.y, rect.width, rect.height, null, null, isect, function(state) 1105 { 1106 return mxUtils.getValue(state.style, 'locked', '0') == '1'; 1107 }, true); 1108 1109 if (this.isToggleEvent(evt)) 1110 { 1111 for (var i = 0; i < cells.length; i++) 1112 { 1113 this.selectCellForEvent(cells[i], evt); 1114 } 1115 } 1116 else 1117 { 1118 this.selectCellsForEvent(cells, evt); 1119 } 1120 1121 return cells; 1122 }; 1123 1124 // Never removes cells from parents that are being moved 1125 var graphHandlerShouldRemoveCellsFromParent = this.graphHandler.shouldRemoveCellsFromParent; 1126 this.graphHandler.shouldRemoveCellsFromParent = function(parent, cells, evt) 1127 { 1128 if (this.graph.isCellSelected(parent)) 1129 { 1130 return false; 1131 } 1132 1133 return graphHandlerShouldRemoveCellsFromParent.apply(this, arguments); 1134 }; 1135 1136 // Unlocks all cells 1137 this.isCellLocked = function(cell) 1138 { 1139 while (cell != null) 1140 { 1141 if (mxUtils.getValue(this.getCurrentCellStyle(cell), 'locked', '0') == '1') 1142 { 1143 return true; 1144 } 1145 1146 cell = this.model.getParent(cell); 1147 } 1148 1149 return false; 1150 }; 1151 1152 var tapAndHoldSelection = null; 1153 1154 // Uses this event to process mouseDown to check the selection state before it is changed 1155 this.addListener(mxEvent.FIRE_MOUSE_EVENT, mxUtils.bind(this, function(sender, evt) 1156 { 1157 if (evt.getProperty('eventName') == 'mouseDown') 1158 { 1159 var me = evt.getProperty('event'); 1160 var state = me.getState(); 1161 1162 if (state != null && !this.isSelectionEmpty() && !this.isCellSelected(state.cell)) 1163 { 1164 tapAndHoldSelection = this.getSelectionCells(); 1165 } 1166 else 1167 { 1168 tapAndHoldSelection = null; 1169 } 1170 } 1171 })); 1172 1173 // Tap and hold on background starts rubberband for multiple selected 1174 // cells the cell associated with the event is deselected 1175 this.addListener(mxEvent.TAP_AND_HOLD, mxUtils.bind(this, function(sender, evt) 1176 { 1177 if (!mxEvent.isMultiTouchEvent(evt)) 1178 { 1179 var me = evt.getProperty('event'); 1180 var cell = evt.getProperty('cell'); 1181 1182 if (cell == null) 1183 { 1184 var pt = mxUtils.convertPoint(this.container, 1185 mxEvent.getClientX(me), mxEvent.getClientY(me)); 1186 rubberband.start(pt.x, pt.y); 1187 } 1188 else if (tapAndHoldSelection != null) 1189 { 1190 this.addSelectionCells(tapAndHoldSelection); 1191 } 1192 else if (this.getSelectionCount() > 1 && this.isCellSelected(cell)) 1193 { 1194 this.removeSelectionCell(cell); 1195 } 1196 1197 // Blocks further processing of the event 1198 tapAndHoldSelection = null; 1199 evt.consume(); 1200 } 1201 })); 1202 1203 // On connect the target is selected and we clone the cell of the preview edge for insert 1204 this.connectionHandler.selectCells = function(edge, target) 1205 { 1206 this.graph.setSelectionCell(target || edge); 1207 }; 1208 1209 // Shows connection points only if cell not selected and parent table not handled 1210 this.connectionHandler.constraintHandler.isStateIgnored = function(state, source) 1211 { 1212 var graph = state.view.graph; 1213 1214 return source && (graph.isCellSelected(state.cell) || (graph.isTableRow(state.cell) && 1215 graph.selectionCellsHandler.isHandled(graph.model.getParent(state.cell)))); 1216 }; 1217 1218 // Updates constraint handler if the selection changes 1219 this.selectionModel.addListener(mxEvent.CHANGE, mxUtils.bind(this, function() 1220 { 1221 var ch = this.connectionHandler.constraintHandler; 1222 1223 if (ch.currentFocus != null && ch.isStateIgnored(ch.currentFocus, true)) 1224 { 1225 ch.currentFocus = null; 1226 ch.constraints = null; 1227 ch.destroyIcons(); 1228 } 1229 1230 ch.destroyFocusHighlight(); 1231 })); 1232 1233 // Initializes touch interface 1234 if (Graph.touchStyle) 1235 { 1236 this.initTouch(); 1237 } 1238 1239 /** 1240 * Adds locking 1241 */ 1242 var graphUpdateMouseEvent = this.updateMouseEvent; 1243 this.updateMouseEvent = function(me) 1244 { 1245 me = graphUpdateMouseEvent.apply(this, arguments); 1246 1247 if (me.state != null && this.isCellLocked(me.getCell())) 1248 { 1249 me.state = null; 1250 } 1251 1252 return me; 1253 }; 1254 } 1255 1256 //Create a unique offset object for each graph instance. 1257 this.currentTranslate = new mxPoint(0, 0); 1258}; 1259 1260/** 1261 * Specifies if the touch UI should be used (cannot detect touch in FF so always on for Windows/Linux) 1262 */ 1263Graph.touchStyle = mxClient.IS_TOUCH || (mxClient.IS_FF && mxClient.IS_WIN) || navigator.maxTouchPoints > 0 || 1264 navigator.msMaxTouchPoints > 0 || window.urlParams == null || urlParams['touch'] == '1'; 1265 1266/** 1267 * Shortcut for capability check. 1268 */ 1269Graph.fileSupport = window.File != null && window.FileReader != null && window.FileList != null && 1270 (window.urlParams == null || urlParams['filesupport'] != '0'); 1271 1272/** 1273 * Shortcut for capability check. 1274 */ 1275Graph.translateDiagram = urlParams['translate-diagram'] == '1'; 1276 1277/** 1278 * Shortcut for capability check. 1279 */ 1280Graph.diagramLanguage = (urlParams['diagram-language'] != null) ? urlParams['diagram-language'] : mxClient.language; 1281 1282/** 1283 * Default size for line jumps. 1284 */ 1285Graph.lineJumpsEnabled = true; 1286 1287/** 1288 * Default size for line jumps. 1289 */ 1290Graph.defaultJumpSize = 6; 1291 1292/** 1293 * Specifies if the mouse wheel is used for zoom without any modifiers. 1294 */ 1295Graph.zoomWheel = false; 1296 1297/** 1298 * Minimum width for table columns. 1299 */ 1300Graph.minTableColumnWidth = 20; 1301 1302/** 1303 * Minimum height for table rows. 1304 */ 1305Graph.minTableRowHeight = 20; 1306 1307/** 1308 * Text for foreign object warning. 1309 */ 1310Graph.foreignObjectWarningText = 'Viewer does not support full SVG 1.1'; 1311 1312/** 1313 * Link for foreign object warning. 1314 */ 1315Graph.foreignObjectWarningLink = 'https://www.diagrams.net/doc/faq/svg-export-text-problems'; 1316 1317/** 1318 * 1319 */ 1320Graph.xmlDeclaration = '<?xml version="1.0" encoding="UTF-8"?>'; 1321 1322/** 1323 * 1324 */ 1325Graph.svgDoctype = '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" ' + 1326 '"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'; 1327 1328/** 1329 * 1330 */ 1331Graph.svgFileComment = '<!-- Do not edit this file with editors other than diagrams.net -->' 1332 1333/** 1334 * Minimum height for table rows. 1335 */ 1336Graph.pasteStyles = ['rounded', 'shadow', 'dashed', 'dashPattern', 'fontFamily', 'fontSource', 'fontSize', 'fontColor', 'fontStyle', 1337 'align', 'verticalAlign', 'strokeColor', 'strokeWidth', 'fillColor', 'gradientColor', 'swimlaneFillColor', 1338 'textOpacity', 'gradientDirection', 'glass', 'labelBackgroundColor', 'labelBorderColor', 'opacity', 1339 'spacing', 'spacingTop', 'spacingLeft', 'spacingBottom', 'spacingRight', 'endFill', 'endArrow', 1340 'endSize', 'targetPerimeterSpacing', 'startFill', 'startArrow', 'startSize', 'sourcePerimeterSpacing', 1341 'arcSize', 'comic', 'sketch', 'fillWeight', 'hachureGap', 'hachureAngle', 'jiggle', 'disableMultiStroke', 1342 'disableMultiStrokeFill', 'fillStyle', 'curveFitting', 'simplification', 'comicStyle']; 1343 1344/** 1345 * Creates a temporary graph instance for rendering off-screen content. 1346 */ 1347Graph.createOffscreenGraph = function(stylesheet) 1348{ 1349 var graph = new Graph(document.createElement('div')); 1350 graph.stylesheet.styles = mxUtils.clone(stylesheet.styles); 1351 graph.resetViewOnRootChange = false; 1352 graph.setConnectable(false); 1353 graph.gridEnabled = false; 1354 graph.autoScroll = false; 1355 graph.setTooltips(false); 1356 graph.setEnabled(false); 1357 1358 // Container must be in the DOM for correct HTML rendering 1359 graph.container.style.visibility = 'hidden'; 1360 graph.container.style.position = 'absolute'; 1361 graph.container.style.overflow = 'hidden'; 1362 graph.container.style.height = '1px'; 1363 graph.container.style.width = '1px'; 1364 1365 return graph; 1366}; 1367 1368/** 1369 * Helper function for creating SVG data URI. 1370 */ 1371Graph.createSvgImage = function(w, h, data, coordWidth, coordHeight) 1372{ 1373 var tmp = unescape(encodeURIComponent(Graph.svgDoctype + 1374 '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="' + w + 'px" height="' + h + 'px" ' + 1375 ((coordWidth != null && coordHeight != null) ? 'viewBox="0 0 ' + coordWidth + ' ' + coordHeight + '" ' : '') + 1376 'version="1.1">' + data + '</svg>')); 1377 1378 return new mxImage('data:image/svg+xml;base64,' + ((window.btoa) ? btoa(tmp) : Base64.encode(tmp, true)), w, h) 1379}; 1380 1381/** 1382 * Removes all illegal control characters with ASCII code <32 except TAB, LF 1383 * and CR. 1384 */ 1385Graph.zapGremlins = function(text) 1386{ 1387 var lastIndex = 0; 1388 var checked = []; 1389 1390 for (var i = 0; i < text.length; i++) 1391 { 1392 var code = text.charCodeAt(i); 1393 1394 // Removes all control chars except TAB, LF and CR 1395 if (!((code >= 32 || code == 9 || code == 10 || code == 13) && 1396 code != 0xFFFF && code != 0xFFFE)) 1397 { 1398 checked.push(text.substring(lastIndex, i)); 1399 lastIndex = i + 1; 1400 } 1401 } 1402 1403 if (lastIndex > 0 && lastIndex < text.length) 1404 { 1405 checked.push(text.substring(lastIndex)); 1406 } 1407 1408 return (checked.length == 0) ? text : checked.join(''); 1409}; 1410 1411/** 1412 * Turns the given string into an array. 1413 */ 1414Graph.stringToBytes = function(str) 1415{ 1416 var arr = new Array(str.length); 1417 1418 for (var i = 0; i < str.length; i++) 1419 { 1420 arr[i] = str.charCodeAt(i); 1421 } 1422 1423 return arr; 1424}; 1425 1426/** 1427 * Turns the given array into a string. 1428 */ 1429Graph.bytesToString = function(arr) 1430{ 1431 var result = new Array(arr.length); 1432 1433 for (var i = 0; i < arr.length; i++) 1434 { 1435 result[i] = String.fromCharCode(arr[i]); 1436 } 1437 1438 return result.join(''); 1439}; 1440 1441/** 1442 * Turns the given array into a string. 1443 */ 1444Graph.base64EncodeUnicode = function(str) 1445{ 1446 return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) { 1447 return String.fromCharCode(parseInt(p1, 16)) 1448 })); 1449}; 1450 1451/** 1452 * Turns the given array into a string. 1453 */ 1454Graph.base64DecodeUnicode = function(str) 1455{ 1456 return decodeURIComponent(Array.prototype.map.call(atob(str), function(c) { 1457 return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2) 1458 }).join('')); 1459}; 1460 1461/** 1462 * Returns a base64 encoded version of the compressed outer XML of the given node. 1463 */ 1464Graph.compressNode = function(node, checked) 1465{ 1466 var xml = mxUtils.getXml(node); 1467 1468 return Graph.compress((checked) ? xml : Graph.zapGremlins(xml)); 1469}; 1470 1471/** 1472 * Returns a string for the given array buffer. 1473 */ 1474Graph.arrayBufferToString = function(buffer) 1475{ 1476 var binary = ''; 1477 var bytes = new Uint8Array(buffer); 1478 var len = bytes.byteLength; 1479 1480 for (var i = 0; i < len; i++) 1481 { 1482 binary += String.fromCharCode(bytes[i]); 1483 } 1484 1485 return binary; 1486}; 1487 1488/** 1489 * Returns an array buffer for the given string. 1490 */ 1491Graph.stringToArrayBuffer = function(data) 1492{ 1493 return Uint8Array.from(data, function (c) 1494 { 1495 return c.charCodeAt(0); 1496 }); 1497}; 1498 1499/** 1500 * Returns index of a string in an array buffer (UInt8Array) 1501 */ 1502Graph.arrayBufferIndexOfString = function (uint8Array, str, start) 1503{ 1504 var c0 = str.charCodeAt(0), j = 1, p = -1; 1505 1506 //Index of first char 1507 for (var i = start || 0; i < uint8Array.byteLength; i++) 1508 { 1509 if (uint8Array[i] == c0) 1510 { 1511 p = i; 1512 break; 1513 } 1514 } 1515 1516 for (var i = p + 1; p > -1 && i < uint8Array.byteLength && i < p + str.length - 1; i++) 1517 { 1518 if (uint8Array[i] != str.charCodeAt(j)) 1519 { 1520 return Graph.arrayBufferIndexOfString(uint8Array, str, p + 1); 1521 } 1522 1523 j++; 1524 } 1525 1526 return j == str.length - 1? p : -1; 1527}; 1528 1529/** 1530 * Returns a base64 encoded version of the compressed string. 1531 */ 1532Graph.compress = function(data, deflate) 1533{ 1534 if (data == null || data.length == 0 || typeof(pako) === 'undefined') 1535 { 1536 return data; 1537 } 1538 else 1539 { 1540 var tmp = (deflate) ? pako.deflate(encodeURIComponent(data)) : 1541 pako.deflateRaw(encodeURIComponent(data)); 1542 1543 return btoa(Graph.arrayBufferToString(new Uint8Array(tmp))); 1544 } 1545}; 1546 1547/** 1548 * Returns a decompressed version of the base64 encoded string. 1549 */ 1550Graph.decompress = function(data, inflate, checked) 1551{ 1552 if (data == null || data.length == 0 || typeof(pako) === 'undefined') 1553 { 1554 return data; 1555 } 1556 else 1557 { 1558 var tmp = Graph.stringToArrayBuffer(atob(data)); 1559 var inflated = decodeURIComponent((inflate) ? 1560 pako.inflate(tmp, {to: 'string'}) : 1561 pako.inflateRaw(tmp, {to: 'string'})); 1562 1563 return (checked) ? inflated : Graph.zapGremlins(inflated); 1564 } 1565}; 1566 1567/** 1568 * Fades the given nodes in or out. 1569 */ 1570Graph.fadeNodes = function(nodes, start, end, done, delay) 1571{ 1572 delay = (delay != null) ? delay : 1000; 1573 Graph.setTransitionForNodes(nodes, null); 1574 Graph.setOpacityForNodes(nodes, start); 1575 1576 window.setTimeout(function() 1577 { 1578 Graph.setTransitionForNodes(nodes, 1579 'all ' + delay + 'ms ease-in-out'); 1580 Graph.setOpacityForNodes(nodes, end); 1581 1582 window.setTimeout(function() 1583 { 1584 Graph.setTransitionForNodes(nodes, null); 1585 1586 if (done != null) 1587 { 1588 done(); 1589 } 1590 }, delay); 1591 }, 0); 1592}; 1593 1594/** 1595 * Sets the transition for the given nodes. 1596 */ 1597Graph.setTransitionForNodes = function(nodes, transition) 1598{ 1599 for (var i = 0; i < nodes.length; i++) 1600 { 1601 mxUtils.setPrefixedStyle(nodes[i].style, 'transition', transition); 1602 } 1603}; 1604 1605/** 1606 * Sets the opacity for the given nodes. 1607 */ 1608Graph.setOpacityForNodes = function(nodes, opacity) 1609{ 1610 for (var i = 0; i < nodes.length; i++) 1611 { 1612 nodes[i].style.opacity = opacity; 1613 } 1614}; 1615 1616/** 1617 * Removes formatting from pasted HTML. 1618 */ 1619Graph.removePasteFormatting = function(elt) 1620{ 1621 while (elt != null) 1622 { 1623 if (elt.firstChild != null) 1624 { 1625 Graph.removePasteFormatting(elt.firstChild); 1626 } 1627 1628 if (elt.nodeType == mxConstants.NODETYPE_ELEMENT && elt.style != null) 1629 { 1630 elt.style.whiteSpace = ''; 1631 1632 if (elt.style.color == '#000000') 1633 { 1634 elt.style.color = ''; 1635 } 1636 } 1637 1638 elt = elt.nextSibling; 1639 } 1640}; 1641 1642/** 1643 * Sanitizes the given HTML markup. 1644 */ 1645Graph.sanitizeHtml = function(value, editing) 1646{ 1647 // Uses https://code.google.com/p/google-caja/wiki/JsHtmlSanitizer 1648 // NOTE: Original minimized sanitizer was modified to support 1649 // data URIs for images, mailto and special data:-links. 1650 // LATER: Add MathML to whitelisted tags 1651 function urlX(link) 1652 { 1653 if (link != null && link.toString().toLowerCase().substring(0, 11) !== 'javascript:') 1654 { 1655 return link; 1656 } 1657 1658 return null; 1659 }; 1660 function idX(id) { return id }; 1661 1662 return html_sanitize(value, urlX, idX); 1663}; 1664 1665/** 1666 * Removes all script tags and attributes starting with on. 1667 */ 1668Graph.sanitizeSvg = function(div) 1669{ 1670 // Removes all attributes starting with on 1671 var all = div.getElementsByTagName('*'); 1672 1673 for (var i = 0; i < all.length; i++) 1674 { 1675 for (var j = 0; j < all[i].attributes.length; j++) 1676 { 1677 var attr = all[i].attributes[j]; 1678 1679 if (attr.name.length > 2 && attr.name.toLowerCase().substring(0, 2) == 'on') 1680 { 1681 all[i].removeAttribute(attr.name); 1682 } 1683 } 1684 } 1685 1686 function removeAllTags(tagName) 1687 { 1688 var nodes = div.getElementsByTagName(tagName); 1689 1690 while (nodes.length > 0) 1691 { 1692 nodes[0].parentNode.removeChild(nodes[0]); 1693 } 1694 }; 1695 1696 removeAllTags('meta'); 1697 removeAllTags('script'); 1698 removeAllTags('metadata'); 1699}; 1700 1701/** 1702 * Updates the viewbox, width and height in the given SVG data URI 1703 * and returns the updated data URI with all script tags and event 1704 * handlers removed. 1705 */ 1706Graph.clipSvgDataUri = function(dataUri) 1707{ 1708 // LATER Add workaround for non-default NS declarations with empty URI not allowed in IE11 1709 if (!mxClient.IS_IE && !mxClient.IS_IE11 && dataUri != null && 1710 dataUri.substring(0, 26) == 'data:image/svg+xml;base64,') 1711 { 1712 try 1713 { 1714 var div = document.createElement('div'); 1715 div.style.position = 'absolute'; 1716 div.style.visibility = 'hidden'; 1717 1718 // Adds the text and inserts into DOM for updating of size 1719 var data = decodeURIComponent(escape(atob(dataUri.substring(26)))); 1720 var idx = data.indexOf('<svg'); 1721 1722 if (idx >= 0) 1723 { 1724 // Strips leading XML declaration and doctypes 1725 div.innerHTML = data.substring(idx); 1726 1727 // Removes all attributes starting with on 1728 Graph.sanitizeSvg(div); 1729 1730 // Gets the size and removes from DOM 1731 var svgs = div.getElementsByTagName('svg'); 1732 1733 if (svgs.length > 0) 1734 { 1735 document.body.appendChild(div); 1736 1737 try 1738 { 1739 var fx = 1; 1740 var fy = 1; 1741 var w = svgs[0].getAttribute('width'); 1742 var h = svgs[0].getAttribute('height'); 1743 1744 if (w != null && w.charAt(w.length - 1) != '%') 1745 { 1746 w = parseFloat(w); 1747 } 1748 else 1749 { 1750 w = NaN; 1751 } 1752 1753 if (h != null && h.charAt(h.length - 1) != '%') 1754 { 1755 h = parseFloat(h); 1756 } 1757 else 1758 { 1759 h = NaN; 1760 } 1761 1762 var vb = svgs[0].getAttribute('viewBox'); 1763 1764 if (vb != null && !isNaN(w) && !isNaN(h)) 1765 { 1766 var tokens = vb.split(' '); 1767 1768 if (vb.length >= 4) 1769 { 1770 fx = parseFloat(tokens[2]) / w; 1771 fy = parseFloat(tokens[3]) / h; 1772 } 1773 } 1774 1775 var size = svgs[0].getBBox(); 1776 1777 if (size.width > 0 && size.height > 0) 1778 { 1779 div.getElementsByTagName('svg')[0].setAttribute('viewBox', size.x + 1780 ' ' + size.y + ' ' + size.width + ' ' + size.height); 1781 div.getElementsByTagName('svg')[0].setAttribute('width', size.width / fx); 1782 div.getElementsByTagName('svg')[0].setAttribute('height', size.height / fy); 1783 } 1784 } 1785 catch (e) 1786 { 1787 // ignore 1788 } 1789 finally 1790 { 1791 document.body.removeChild(div); 1792 } 1793 1794 dataUri = Editor.createSvgDataUri(mxUtils.getXml(svgs[0])); 1795 } 1796 } 1797 } 1798 catch (e) 1799 { 1800 // ignore 1801 } 1802 } 1803 1804 return dataUri; 1805}; 1806 1807/** 1808 * Returns the CSS font family from the given computed style. 1809 */ 1810Graph.stripQuotes = function(text) 1811{ 1812 if (text != null) 1813 { 1814 if (text.charAt(0) == '\'') 1815 { 1816 text = text.substring(1); 1817 } 1818 1819 if (text.charAt(text.length - 1) == '\'') 1820 { 1821 text = text.substring(0, text.length - 1); 1822 } 1823 1824 if (text.charAt(0) == '"') 1825 { 1826 text = text.substring(1); 1827 } 1828 1829 if (text.charAt(text.length - 1) == '"') 1830 { 1831 text = text.substring(0, text.length - 1); 1832 } 1833 } 1834 1835 return text; 1836}; 1837 1838/** 1839 * Create remove icon. 1840 */ 1841Graph.createRemoveIcon = function(title, onclick) 1842{ 1843 var removeLink = document.createElement('img'); 1844 removeLink.setAttribute('src', Dialog.prototype.clearImage); 1845 removeLink.setAttribute('title', title); 1846 removeLink.setAttribute('width', '13'); 1847 removeLink.setAttribute('height', '10'); 1848 removeLink.style.marginLeft = '4px'; 1849 removeLink.style.marginBottom = '-1px'; 1850 removeLink.style.cursor = 'pointer'; 1851 1852 mxEvent.addListener(removeLink, 'click', onclick); 1853 1854 return removeLink; 1855}; 1856 1857/** 1858 * Returns true if the given string is a page link. 1859 */ 1860Graph.isPageLink = function(text) 1861{ 1862 return text != null && text.substring(0, 13) == 'data:page/id,'; 1863}; 1864 1865/** 1866 * Returns true if the given string is a link. 1867 * 1868 * See https://stackoverflow.com/questions/5717093/check-if-a-javascript-string-is-a-url 1869 */ 1870Graph.isLink = function(text) 1871{ 1872 return text != null && Graph.linkPattern.test(text); 1873}; 1874 1875/** 1876 * Regular expression for links. 1877 */ 1878Graph.linkPattern = new RegExp('^(https?:\\/\\/)?'+ // protocol 1879 '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name 1880 '((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address 1881 '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path 1882 '(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string 1883 '(\\#[-a-z\\d_]*)?$','i'); // fragment locator 1884 1885/** 1886 * Graph inherits from mxGraph. 1887 */ 1888mxUtils.extend(Graph, mxGraph); 1889 1890/** 1891 * Allows all values in fit. 1892 */ 1893Graph.prototype.minFitScale = null; 1894 1895/** 1896 * Allows all values in fit. 1897 */ 1898Graph.prototype.maxFitScale = null; 1899 1900/** 1901 * Sets the policy for links. Possible values are "self" to replace any framesets, 1902 * "blank" to load the URL in <linkTarget> and "auto" (default). 1903 */ 1904Graph.prototype.linkPolicy = (urlParams['target'] == 'frame') ? 'blank' : (urlParams['target'] || 'auto'); 1905 1906/** 1907 * Target for links that open in a new window. Default is _blank. 1908 */ 1909Graph.prototype.linkTarget = (urlParams['target'] == 'frame') ? '_self' : '_blank'; 1910 1911/** 1912 * Value to the rel attribute of links. Default is 'nofollow noopener noreferrer'. 1913 * NOTE: There are security implications when this is changed and if noopener is removed, 1914 * then <openLink> must be overridden to allow for the opener to be set by default. 1915 */ 1916Graph.prototype.linkRelation = 'nofollow noopener noreferrer'; 1917 1918/** 1919 * Scrollbars are enabled on non-touch devices (not including Firefox because touch events 1920 * cannot be detected in Firefox, see above). 1921 */ 1922Graph.prototype.defaultScrollbars = !mxClient.IS_IOS; 1923 1924/** 1925 * Specifies if the page should be visible for new files. Default is true. 1926 */ 1927Graph.prototype.defaultPageVisible = true; 1928 1929/** 1930 * Specifies if the page should be visible for new files. Default is true. 1931 */ 1932Graph.prototype.defaultGridEnabled = urlParams['grid'] != '0'; 1933 1934/** 1935 * Specifies if the app should run in chromeless mode. Default is false. 1936 * This default is only used if the contructor argument is null. 1937 */ 1938Graph.prototype.lightbox = false; 1939 1940/** 1941 * 1942 */ 1943Graph.prototype.defaultPageBackgroundColor = '#ffffff'; 1944 1945/** 1946 * 1947 */ 1948Graph.prototype.defaultPageBorderColor = '#ffffff'; 1949 1950/** 1951 * 1952 */ 1953Graph.prototype.shapeForegroundColor = '#000000'; 1954 1955/** 1956 * 1957 */ 1958Graph.prototype.shapeBackgroundColor = '#ffffff'; 1959 1960/** 1961 * Specifies the size of the size for "tiles" to be used for a graph with 1962 * scrollbars but no visible background page. A good value is large 1963 * enough to reduce the number of repaints that is caused for auto- 1964 * translation, which depends on this value, and small enough to give 1965 * a small empty buffer around the graph. Default is 400x400. 1966 */ 1967Graph.prototype.scrollTileSize = new mxRectangle(0, 0, 400, 400); 1968 1969/** 1970 * Overrides the background color and paints a transparent background. 1971 */ 1972Graph.prototype.transparentBackground = true; 1973 1974/** 1975 * Sets global constants. 1976 */ 1977Graph.prototype.selectParentAfterDelete = false; 1978 1979/** 1980 * Sets the default target for all links in cells. 1981 */ 1982Graph.prototype.defaultEdgeLength = 80; 1983 1984/** 1985 * Disables move of bends/segments without selecting. 1986 */ 1987Graph.prototype.edgeMode = false; 1988 1989/** 1990 * Allows all values in fit. 1991 */ 1992Graph.prototype.connectionArrowsEnabled = true; 1993 1994/** 1995 * Specifies the regular expression for matching placeholders. 1996 */ 1997Graph.prototype.placeholderPattern = new RegExp('%(date\{.*\}|[^%^\{^\}^ ^"^ \'^=^;]+)%', 'g'); 1998 1999/** 2000 * Specifies the regular expression for matching placeholders. 2001 */ 2002Graph.prototype.absoluteUrlPattern = new RegExp('^(?:[a-z]+:)?//', 'i'); 2003 2004/** 2005 * Specifies the default name for the theme. Default is 'default'. 2006 */ 2007Graph.prototype.defaultThemeName = 'default'; 2008 2009/** 2010 * Specifies the default name for the theme. Default is 'default'. 2011 */ 2012Graph.prototype.defaultThemes = {}; 2013 2014/** 2015 * Base URL for relative links. 2016 */ 2017Graph.prototype.baseUrl = (urlParams['base'] != null) ? 2018 decodeURIComponent(urlParams['base']) : 2019 (((window != window.top) ? document.referrer : 2020 document.location.toString()).split('#')[0]); 2021 2022/** 2023 * Specifies if the label should be edited after an insert. 2024 */ 2025Graph.prototype.editAfterInsert = false; 2026 2027/** 2028 * Defines the built-in properties to be ignored in tooltips. 2029 */ 2030Graph.prototype.builtInProperties = ['label', 'tooltip', 'placeholders', 'placeholder']; 2031 2032/** 2033 * Defines if the graph is part of an EditorUi. If this is false the graph can 2034 * be used in an EditorUi instance but will not have a UI added, functions 2035 * overridden or event handlers added. 2036 */ 2037Graph.prototype.standalone = false; 2038 2039/** 2040 * Enables move of bends/segments without selecting. 2041 */ 2042Graph.prototype.enableFlowAnimation = false; 2043 2044/** 2045 * Installs child layout styles. 2046 */ 2047Graph.prototype.init = function(container) 2048{ 2049 mxGraph.prototype.init.apply(this, arguments); 2050 2051 // Intercepts links with no target attribute and opens in new window 2052 this.cellRenderer.initializeLabel = function(state, shape) 2053 { 2054 mxCellRenderer.prototype.initializeLabel.apply(this, arguments); 2055 2056 // Checks tolerance for clicks on links 2057 var tol = state.view.graph.tolerance; 2058 var handleClick = true; 2059 var first = null; 2060 2061 var down = mxUtils.bind(this, function(evt) 2062 { 2063 handleClick = true; 2064 first = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt)); 2065 }); 2066 2067 var move = mxUtils.bind(this, function(evt) 2068 { 2069 handleClick = handleClick && first != null && 2070 Math.abs(first.x - mxEvent.getClientX(evt)) < tol && 2071 Math.abs(first.y - mxEvent.getClientY(evt)) < tol; 2072 }); 2073 2074 var up = mxUtils.bind(this, function(evt) 2075 { 2076 if (handleClick) 2077 { 2078 var elt = mxEvent.getSource(evt) 2079 2080 while (elt != null && elt != shape.node) 2081 { 2082 if (elt.nodeName.toLowerCase() == 'a') 2083 { 2084 state.view.graph.labelLinkClicked(state, elt, evt); 2085 break; 2086 } 2087 2088 elt = elt.parentNode; 2089 } 2090 } 2091 }); 2092 2093 mxEvent.addGestureListeners(shape.node, down, move, up); 2094 mxEvent.addListener(shape.node, 'click', function(evt) 2095 { 2096 mxEvent.consume(evt); 2097 }); 2098 }; 2099 2100 // Handles custom links in tooltips 2101 if (this.tooltipHandler != null) 2102 { 2103 var tooltipHandlerInit = this.tooltipHandler.init; 2104 2105 this.tooltipHandler.init = function() 2106 { 2107 tooltipHandlerInit.apply(this, arguments); 2108 2109 if (this.div != null) 2110 { 2111 mxEvent.addListener(this.div, 'click', mxUtils.bind(this, function(evt) 2112 { 2113 var source = mxEvent.getSource(evt); 2114 2115 if (source.nodeName == 'A') 2116 { 2117 var href = source.getAttribute('href'); 2118 2119 if (href != null && this.graph.isCustomLink(href) && 2120 (mxEvent.isTouchEvent(evt) || !mxEvent.isPopupTrigger(evt)) && 2121 this.graph.customLinkClicked(href)) 2122 { 2123 mxEvent.consume(evt); 2124 } 2125 } 2126 })); 2127 } 2128 }; 2129 } 2130 2131 2132 // Adds or updates CSS for flowAnimation style 2133 this.addListener(mxEvent.SIZE, mxUtils.bind(this, function(sender, evt) 2134 { 2135 if (this.container != null && this.flowAnimationStyle) 2136 { 2137 var id = this.flowAnimationStyle.getAttribute('id'); 2138 this.flowAnimationStyle.innerHTML = this.getFlowAnimationStyleCss(id); 2139 } 2140 })); 2141 2142 this.initLayoutManager(); 2143}; 2144 2145/** 2146 * Implements zoom and offset via CSS transforms. This is currently only used 2147 * in read-only as there are fewer issues with the mxCellState not being scaled 2148 * and translated. 2149 * 2150 * KNOWN ISSUES TO FIX: 2151 * - Apply CSS transforms to HTML labels in IE11 2152 */ 2153(function() 2154{ 2155 /** 2156 * Uses CSS transforms for scale and translate. 2157 */ 2158 Graph.prototype.useCssTransforms = false; 2159 2160 /** 2161 * Contains the scale. 2162 */ 2163 Graph.prototype.currentScale = 1; 2164 2165 /** 2166 * Contains the offset. 2167 */ 2168 Graph.prototype.currentTranslate = new mxPoint(0, 0); 2169 2170 /** 2171 * 2172 */ 2173 Graph.prototype.getVerticesAndEdges = function(vertices, edges) 2174 { 2175 vertices = (vertices != null) ? vertices : true; 2176 edges = (edges != null) ? edges : true; 2177 var model = this.model; 2178 2179 return model.filterDescendants(function(cell) 2180 { 2181 return (vertices && model.isVertex(cell)) || (edges && model.isEdge(cell)); 2182 }, model.getRoot()); 2183 }; 2184 2185 /** 2186 * Returns information about the current selection. 2187 */ 2188 Graph.prototype.getCommonStyle = function(cells) 2189 { 2190 var style = {}; 2191 2192 for (var i = 0; i < cells.length; i++) 2193 { 2194 var state = this.view.getState(cells[i]); 2195 this.mergeStyle(state.style, style, i == 0); 2196 } 2197 2198 return style; 2199 }; 2200 2201 /** 2202 * Returns information about the current selection. 2203 */ 2204 Graph.prototype.mergeStyle = function(style, into, initial) 2205 { 2206 if (style != null) 2207 { 2208 var keys = {}; 2209 2210 for (var key in style) 2211 { 2212 var value = style[key]; 2213 2214 if (value != null) 2215 { 2216 keys[key] = true; 2217 2218 if (into[key] == null && initial) 2219 { 2220 into[key] = value; 2221 } 2222 else if (into[key] != value) 2223 { 2224 delete into[key]; 2225 } 2226 } 2227 } 2228 2229 for (var key in into) 2230 { 2231 if (!keys[key]) 2232 { 2233 delete into[key]; 2234 } 2235 } 2236 } 2237 }; 2238 2239 /** 2240 * Returns the cell for editing the given cell. 2241 */ 2242 Graph.prototype.getStartEditingCell = function(cell, trigger) 2243 { 2244 // Redirect editing for tables 2245 var style = this.getCellStyle(cell); 2246 var size = parseInt(mxUtils.getValue(style, mxConstants.STYLE_STARTSIZE, 0)); 2247 2248 if (this.isTable(cell) && (!this.isSwimlane(cell) || 2249 size == 0) && this.getLabel(cell) == '' && 2250 this.model.getChildCount(cell) > 0) 2251 { 2252 cell = this.model.getChildAt(cell, 0); 2253 2254 style = this.getCellStyle(cell); 2255 size = parseInt(mxUtils.getValue(style, mxConstants.STYLE_STARTSIZE, 0)); 2256 } 2257 2258 // Redirect editing for table rows 2259 if (this.isTableRow(cell) && (!this.isSwimlane(cell) || 2260 size == 0) && this.getLabel(cell) == '' && 2261 this.model.getChildCount(cell) > 0) 2262 { 2263 for (var i = 0; i < this.model.getChildCount(cell); i++) 2264 { 2265 var temp = this.model.getChildAt(cell, i); 2266 2267 if (this.isCellEditable(temp)) 2268 { 2269 cell = temp; 2270 break; 2271 } 2272 } 2273 } 2274 2275 return cell; 2276 }; 2277 2278 /** 2279 * Returns true if fast zoom preview should be used. 2280 */ 2281 Graph.prototype.copyStyle = function(cell) 2282 { 2283 var style = null; 2284 2285 if (cell != null) 2286 { 2287 style = mxUtils.clone(this.getCurrentCellStyle(cell)); 2288 2289 // Handles special case for value "none" 2290 var cellStyle = this.model.getStyle(cell); 2291 var tokens = (cellStyle != null) ? cellStyle.split(';') : []; 2292 2293 for (var j = 0; j < tokens.length; j++) 2294 { 2295 var tmp = tokens[j]; 2296 var pos = tmp.indexOf('='); 2297 2298 if (pos >= 0) 2299 { 2300 var key = tmp.substring(0, pos); 2301 var value = tmp.substring(pos + 1); 2302 2303 if (style[key] == null && value == mxConstants.NONE) 2304 { 2305 style[key] = mxConstants.NONE; 2306 } 2307 } 2308 } 2309 } 2310 2311 return style; 2312 }; 2313 2314 /** 2315 * Returns true if fast zoom preview should be used. 2316 */ 2317 Graph.prototype.pasteStyle = function(style, cells, keys) 2318 { 2319 keys = (keys != null) ? keys : Graph.pasteStyles; 2320 2321 this.model.beginUpdate(); 2322 try 2323 { 2324 for (var i = 0; i < cells.length; i++) 2325 { 2326 var temp = this.getCurrentCellStyle(cells[i]); 2327 2328 for (var j = 0; j < keys.length; j++) 2329 { 2330 var current = temp[keys[j]]; 2331 var value = style[keys[j]]; 2332 2333 if (current != value && (current != null || value != mxConstants.NONE)) 2334 { 2335 this.setCellStyles(keys[j], value, [cells[i]]); 2336 } 2337 } 2338 } 2339 } 2340 finally 2341 { 2342 this.model.endUpdate(); 2343 } 2344 }; 2345 2346 /** 2347 * Returns true if fast zoom preview should be used. 2348 */ 2349 Graph.prototype.isFastZoomEnabled = function() 2350 { 2351 return urlParams['zoom'] != 'nocss' && !mxClient.NO_FO && !mxClient.IS_EDGE && 2352 !this.useCssTransforms && (this.isCssTransformsSupported() || mxClient.IS_IOS); 2353 }; 2354 2355 /** 2356 * Only foreignObject supported for now (no IE11). Safari disabled as it ignores 2357 * overflow visible on foreignObject in negative space (lightbox and viewer). 2358 * Check the following test case on page 1 before enabling this in production: 2359 * https://devhost.jgraph.com/git/drawio/etc/embed/sf-math-fo-clipping.html?dev=1 2360 */ 2361 Graph.prototype.isCssTransformsSupported = function() 2362 { 2363 return this.dialect == mxConstants.DIALECT_SVG && !mxClient.NO_FO && 2364 (!this.lightbox || !mxClient.IS_SF); 2365 }; 2366 2367 /** 2368 * Function: getCellAt 2369 * 2370 * Needs to modify original method for recursive call. 2371 */ 2372 Graph.prototype.getCellAt = function(x, y, parent, vertices, edges, ignoreFn) 2373 { 2374 if (this.useCssTransforms) 2375 { 2376 x = x / this.currentScale - this.currentTranslate.x; 2377 y = y / this.currentScale - this.currentTranslate.y; 2378 } 2379 2380 return this.getScaledCellAt.apply(this, arguments); 2381 }; 2382 2383 /** 2384 * Function: getScaledCellAt 2385 * 2386 * Overridden for recursion. 2387 */ 2388 Graph.prototype.getScaledCellAt = function(x, y, parent, vertices, edges, ignoreFn) 2389 { 2390 vertices = (vertices != null) ? vertices : true; 2391 edges = (edges != null) ? edges : true; 2392 2393 if (parent == null) 2394 { 2395 parent = this.getCurrentRoot(); 2396 2397 if (parent == null) 2398 { 2399 parent = this.getModel().getRoot(); 2400 } 2401 } 2402 2403 if (parent != null) 2404 { 2405 var childCount = this.model.getChildCount(parent); 2406 2407 for (var i = childCount - 1; i >= 0; i--) 2408 { 2409 var cell = this.model.getChildAt(parent, i); 2410 var result = this.getScaledCellAt(x, y, cell, vertices, edges, ignoreFn); 2411 2412 if (result != null) 2413 { 2414 return result; 2415 } 2416 else if (this.isCellVisible(cell) && (edges && this.model.isEdge(cell) || 2417 vertices && this.model.isVertex(cell))) 2418 { 2419 var state = this.view.getState(cell); 2420 2421 if (state != null && (ignoreFn == null || !ignoreFn(state, x, y)) && 2422 this.intersects(state, x, y)) 2423 { 2424 return cell; 2425 } 2426 } 2427 } 2428 } 2429 2430 return null; 2431 }; 2432 2433 /** 2434 * Returns if the child cells of the given vertex cell state should be resized. 2435 */ 2436 Graph.prototype.isRecursiveVertexResize = function(state) 2437 { 2438 return !this.isSwimlane(state.cell) && this.model.getChildCount(state.cell) > 0 && 2439 !this.isCellCollapsed(state.cell) && mxUtils.getValue(state.style, 'recursiveResize', '1') == '1' && 2440 mxUtils.getValue(state.style, 'childLayout', null) == null; 2441 } 2442 2443 /** 2444 * Returns the first parent with an absolute or no geometry. 2445 */ 2446 Graph.prototype.getAbsoluteParent = function(cell) 2447 { 2448 var result = cell; 2449 var geo = this.getCellGeometry(result); 2450 2451 while (geo != null && geo.relative) 2452 { 2453 result = this.getModel().getParent(result); 2454 geo = this.getCellGeometry(result); 2455 } 2456 2457 return result; 2458 }; 2459 2460 /** 2461 * Returns the first parent that is not a part. 2462 */ 2463 Graph.prototype.isPart = function(cell) 2464 { 2465 return mxUtils.getValue(this.getCurrentCellStyle(cell), 'part', '0') == '1' || 2466 this.isTableCell(cell) || this.isTableRow(cell); 2467 }; 2468 2469 /** 2470 * Returns the first parent that is not a part. 2471 */ 2472 Graph.prototype.getCompositeParent = function(cell) 2473 { 2474 while (this.isPart(cell)) 2475 { 2476 var temp = this.model.getParent(cell); 2477 2478 if (!this.model.isVertex(temp)) 2479 { 2480 break; 2481 } 2482 2483 cell = temp; 2484 } 2485 2486 return cell; 2487 }; 2488 2489 /** 2490 * Returns the selection cells where the given function returns false. 2491 */ 2492 Graph.prototype.filterSelectionCells = function(ignoreFn) 2493 { 2494 var cells = this.getSelectionCells(); 2495 2496 if (ignoreFn != null) 2497 { 2498 var temp = []; 2499 2500 for (var i = 0; i < cells.length; i++) 2501 { 2502 if (!ignoreFn(cells[i])) 2503 { 2504 temp.push(cells[i]); 2505 } 2506 } 2507 2508 cells = temp; 2509 } 2510 2511 return cells; 2512 }; 2513 2514 /** 2515 * Overrides scrollRectToVisible to fix ignored transform. 2516 */ 2517 var graphScrollRectToVisible = mxGraph.prototype.scrollRectToVisible; 2518 Graph.prototype.scrollRectToVisible = function(r) 2519 { 2520 if (this.useCssTransforms) 2521 { 2522 var s = this.currentScale; 2523 var t = this.currentTranslate; 2524 r = new mxRectangle((r.x + 2 * t.x) * s - t.x, 2525 (r.y + 2 * t.y) * s - t.y, 2526 r.width * s, r.height * s); 2527 } 2528 2529 graphScrollRectToVisible.apply(this, arguments); 2530 }; 2531 2532 /** 2533 * Function: repaint 2534 * 2535 * Updates the highlight after a change of the model or view. 2536 */ 2537 mxCellHighlight.prototype.getStrokeWidth = function(state) 2538 { 2539 var s = this.strokeWidth; 2540 2541 if (this.graph.useCssTransforms) 2542 { 2543 s /= this.graph.currentScale; 2544 } 2545 2546 return s; 2547 }; 2548 2549 /** 2550 * Function: getGraphBounds 2551 * 2552 * Overrides getGraphBounds to use bounding box from SVG. 2553 */ 2554 mxGraphView.prototype.getGraphBounds = function() 2555 { 2556 var b = this.graphBounds; 2557 2558 if (this.graph.useCssTransforms) 2559 { 2560 var t = this.graph.currentTranslate; 2561 var s = this.graph.currentScale; 2562 2563 b = new mxRectangle( 2564 (b.x + t.x) * s, (b.y + t.y) * s, 2565 b.width * s, b.height * s); 2566 } 2567 2568 return b; 2569 }; 2570 2571 /** 2572 * Overrides to bypass full cell tree validation. 2573 * TODO: Check if this improves performance 2574 */ 2575 mxGraphView.prototype.viewStateChanged = function() 2576 { 2577 if (this.graph.useCssTransforms) 2578 { 2579 this.validate(); 2580 this.graph.sizeDidChange(); 2581 } 2582 else 2583 { 2584 this.revalidate(); 2585 this.graph.sizeDidChange(); 2586 } 2587 }; 2588 2589 /** 2590 * Overrides validate to normalize validation view state and pass 2591 * current state to CSS transform. 2592 */ 2593 var graphViewValidate = mxGraphView.prototype.validate; 2594 mxGraphView.prototype.validate = function(cell) 2595 { 2596 if (this.graph.useCssTransforms) 2597 { 2598 this.graph.currentScale = this.scale; 2599 this.graph.currentTranslate.x = this.translate.x; 2600 this.graph.currentTranslate.y = this.translate.y; 2601 2602 this.scale = 1; 2603 this.translate.x = 0; 2604 this.translate.y = 0; 2605 } 2606 2607 graphViewValidate.apply(this, arguments); 2608 2609 if (this.graph.useCssTransforms) 2610 { 2611 this.graph.updateCssTransform(); 2612 2613 this.scale = this.graph.currentScale; 2614 this.translate.x = this.graph.currentTranslate.x; 2615 this.translate.y = this.graph.currentTranslate.y; 2616 } 2617 }; 2618 2619 /** 2620 * Overrides function to exclude table cells and rows from groups. 2621 */ 2622 var graphGetCellsForGroup = mxGraph.prototype.getCellsForGroup; 2623 Graph.prototype.getCellsForGroup = function(cells) 2624 { 2625 cells = graphGetCellsForGroup.apply(this, arguments); 2626 var result = []; 2627 2628 // Filters selection cells with the same parent 2629 for (var i = 0; i < cells.length; i++) 2630 { 2631 if (!this.isTableRow(cells[i]) && 2632 !this.isTableCell(cells[i])) 2633 { 2634 result.push(cells[i]); 2635 } 2636 } 2637 2638 return result; 2639 }; 2640 2641 /** 2642 * Overrides function to exclude tables, rows and cells from ungrouping. 2643 */ 2644 var graphGetCellsForUngroup = mxGraph.prototype.getCellsForUngroup; 2645 Graph.prototype.getCellsForUngroup = function(cells) 2646 { 2647 cells = graphGetCellsForUngroup.apply(this, arguments); 2648 var result = []; 2649 2650 // Filters selection cells with the same parent 2651 for (var i = 0; i < cells.length; i++) 2652 { 2653 if (!this.isTable(cells[i]) && 2654 !this.isTableRow(cells[i]) && 2655 !this.isTableCell(cells[i])) 2656 { 2657 result.push(cells[i]); 2658 } 2659 } 2660 2661 return result; 2662 }; 2663 2664 /** 2665 * Function: updateCssTransform 2666 * 2667 * Zooms out of the graph by <zoomFactor>. 2668 */ 2669 Graph.prototype.updateCssTransform = function() 2670 { 2671 var temp = this.view.getDrawPane(); 2672 2673 if (temp != null) 2674 { 2675 var g = temp.parentNode; 2676 2677 if (!this.useCssTransforms) 2678 { 2679 g.removeAttribute('transformOrigin'); 2680 g.removeAttribute('transform'); 2681 } 2682 else 2683 { 2684 var prev = g.getAttribute('transform'); 2685 g.setAttribute('transformOrigin', '0 0'); 2686 var s = Math.round(this.currentScale * 100) / 100; 2687 var dx = Math.round(this.currentTranslate.x * 100) / 100; 2688 var dy = Math.round(this.currentTranslate.y * 100) / 100; 2689 g.setAttribute('transform', 'scale(' + s + ',' + s + ')' + 2690 'translate(' + dx + ',' + dy + ')'); 2691 2692 // Applies workarounds only if translate has changed 2693 if (prev != g.getAttribute('transform')) 2694 { 2695 this.fireEvent(new mxEventObject('cssTransformChanged'), 2696 'transform', g.getAttribute('transform')); 2697 } 2698 } 2699 } 2700 }; 2701 2702 var graphViewValidateBackgroundPage = mxGraphView.prototype.validateBackgroundPage; 2703 mxGraphView.prototype.validateBackgroundPage = function() 2704 { 2705 var useCssTranforms = this.graph.useCssTransforms, scale = this.scale, 2706 translate = this.translate; 2707 2708 if (useCssTranforms) 2709 { 2710 this.scale = this.graph.currentScale; 2711 this.translate = this.graph.currentTranslate; 2712 } 2713 2714 graphViewValidateBackgroundPage.apply(this, arguments); 2715 2716 if (useCssTranforms) 2717 { 2718 this.scale = scale; 2719 this.translate = translate; 2720 } 2721 }; 2722 2723 var graphUpdatePageBreaks = mxGraph.prototype.updatePageBreaks; 2724 mxGraph.prototype.updatePageBreaks = function(visible, width, height) 2725 { 2726 var useCssTranforms = this.useCssTransforms, scale = this.view.scale, 2727 translate = this.view.translate; 2728 2729 if (useCssTranforms) 2730 { 2731 this.view.scale = 1; 2732 this.view.translate = new mxPoint(0, 0); 2733 this.useCssTransforms = false; 2734 } 2735 2736 graphUpdatePageBreaks.apply(this, arguments); 2737 2738 if (useCssTranforms) 2739 { 2740 this.view.scale = scale; 2741 this.view.translate = translate; 2742 this.useCssTransforms = true; 2743 } 2744 }; 2745})(); 2746 2747/** 2748 * Sets the XML node for the current diagram. 2749 */ 2750Graph.prototype.isLightboxView = function() 2751{ 2752 return this.lightbox; 2753}; 2754 2755/** 2756 * Sets the XML node for the current diagram. 2757 */ 2758Graph.prototype.isViewer = function() 2759{ 2760 return false; 2761}; 2762 2763/** 2764 * Installs automatic layout via styles 2765 */ 2766Graph.prototype.labelLinkClicked = function(state, elt, evt) 2767{ 2768 var href = elt.getAttribute('href'); 2769 2770 if (href != null && !this.isCustomLink(href) && ((mxEvent.isLeftMouseButton(evt) && 2771 !mxEvent.isPopupTrigger(evt)) || mxEvent.isTouchEvent(evt))) 2772 { 2773 if (!this.isEnabled() || this.isCellLocked(state.cell)) 2774 { 2775 var target = this.isBlankLink(href) ? this.linkTarget : '_top'; 2776 this.openLink(this.getAbsoluteUrl(href), target); 2777 } 2778 2779 mxEvent.consume(evt); 2780 } 2781}; 2782 2783/** 2784 * Returns the size of the page format scaled with the page size. 2785 */ 2786Graph.prototype.openLink = function(href, target, allowOpener) 2787{ 2788 var result = window; 2789 2790 try 2791 { 2792 // Workaround for blocking in same iframe 2793 if (target == '_self' && window != window.top) 2794 { 2795 window.location.href = href; 2796 } 2797 else 2798 { 2799 // Avoids page reload for anchors (workaround for IE but used everywhere) 2800 if (href.substring(0, this.baseUrl.length) == this.baseUrl && 2801 href.charAt(this.baseUrl.length) == '#' && 2802 target == '_top' && window == window.top) 2803 { 2804 var hash = href.split('#')[1]; 2805 2806 // Forces navigation if on same hash 2807 if (window.location.hash == '#' + hash) 2808 { 2809 window.location.hash = ''; 2810 } 2811 2812 window.location.hash = hash; 2813 } 2814 else 2815 { 2816 result = window.open(href, (target != null) ? target : '_blank'); 2817 2818 if (result != null && !allowOpener) 2819 { 2820 result.opener = null; 2821 } 2822 } 2823 } 2824 } 2825 catch (e) 2826 { 2827 // ignores permission denied 2828 } 2829 2830 return result; 2831}; 2832 2833/** 2834 * Adds support for page links. 2835 */ 2836Graph.prototype.getLinkTitle = function(href) 2837{ 2838 return href.substring(href.lastIndexOf('/') + 1); 2839}; 2840 2841/** 2842 * Adds support for page links. 2843 */ 2844Graph.prototype.isCustomLink = function(href) 2845{ 2846 return href.substring(0, 5) == 'data:'; 2847}; 2848 2849/** 2850 * Adds support for page links. 2851 */ 2852Graph.prototype.customLinkClicked = function(link) 2853{ 2854 return false; 2855}; 2856 2857/** 2858 * Returns true if the given href references an external protocol that 2859 * should never open in a new window. Default returns true for mailto. 2860 */ 2861Graph.prototype.isExternalProtocol = function(href) 2862{ 2863 return href.substring(0, 7) === 'mailto:'; 2864}; 2865 2866/** 2867 * Hook for links to open in same window. Default returns true for anchors, 2868 * links to same domain or if target == 'self' in the config. 2869 */ 2870Graph.prototype.isBlankLink = function(href) 2871{ 2872 return !this.isExternalProtocol(href) && 2873 (this.linkPolicy === 'blank' || 2874 (this.linkPolicy !== 'self' && 2875 !this.isRelativeUrl(href) && 2876 href.substring(0, this.domainUrl.length) !== this.domainUrl)); 2877}; 2878 2879/** 2880 * 2881 */ 2882Graph.prototype.isRelativeUrl = function(url) 2883{ 2884 return url != null && !this.absoluteUrlPattern.test(url) && 2885 url.substring(0, 5) !== 'data:' && 2886 !this.isExternalProtocol(url); 2887}; 2888 2889/** 2890 * 2891 */ 2892Graph.prototype.getAbsoluteUrl = function(url) 2893{ 2894 if (url != null && this.isRelativeUrl(url)) 2895 { 2896 if (url.charAt(0) == '#') 2897 { 2898 url = this.baseUrl + url; 2899 } 2900 else if (url.charAt(0) == '/') 2901 { 2902 url = this.domainUrl + url; 2903 } 2904 else 2905 { 2906 url = this.domainPathUrl + url; 2907 } 2908 } 2909 2910 return url; 2911}; 2912 2913/** 2914 * Installs automatic layout via styles 2915 */ 2916Graph.prototype.initLayoutManager = function() 2917{ 2918 this.layoutManager = new mxLayoutManager(this); 2919 2920 this.layoutManager.hasLayout = function(cell, eventName) 2921 { 2922 return this.graph.getCellStyle(cell)['childLayout'] != null; 2923 }; 2924 2925 this.layoutManager.getLayout = function(cell, eventName) 2926 { 2927 var parent = this.graph.model.getParent(cell); 2928 2929 // Executes layouts from top to bottom except for nested layouts where 2930 // child layouts are executed before and after the parent layout runs 2931 // in case the layout changes the size of the child cell 2932 if (eventName != mxEvent.BEGIN_UPDATE || this.hasLayout(parent, eventName)) 2933 { 2934 var style = this.graph.getCellStyle(cell); 2935 2936 if (style['childLayout'] == 'stackLayout') 2937 { 2938 var stackLayout = new mxStackLayout(this.graph, true); 2939 stackLayout.resizeParentMax = mxUtils.getValue(style, 'resizeParentMax', '1') == '1'; 2940 stackLayout.horizontal = mxUtils.getValue(style, 'horizontalStack', '1') == '1'; 2941 stackLayout.resizeParent = mxUtils.getValue(style, 'resizeParent', '1') == '1'; 2942 stackLayout.resizeLast = mxUtils.getValue(style, 'resizeLast', '0') == '1'; 2943 stackLayout.spacing = style['stackSpacing'] || stackLayout.spacing; 2944 stackLayout.border = style['stackBorder'] || stackLayout.border; 2945 stackLayout.marginLeft = style['marginLeft'] || 0; 2946 stackLayout.marginRight = style['marginRight'] || 0; 2947 stackLayout.marginTop = style['marginTop'] || 0; 2948 stackLayout.marginBottom = style['marginBottom'] || 0; 2949 stackLayout.allowGaps = style['allowGaps'] || 0; 2950 stackLayout.fill = true; 2951 2952 if (stackLayout.allowGaps) 2953 { 2954 stackLayout.gridSize = parseFloat(mxUtils.getValue(style, 'stackUnitSize', 20)); 2955 } 2956 2957 return stackLayout; 2958 } 2959 else if (style['childLayout'] == 'treeLayout') 2960 { 2961 var treeLayout = new mxCompactTreeLayout(this.graph); 2962 treeLayout.horizontal = mxUtils.getValue(style, 'horizontalTree', '1') == '1'; 2963 treeLayout.resizeParent = mxUtils.getValue(style, 'resizeParent', '1') == '1'; 2964 treeLayout.groupPadding = mxUtils.getValue(style, 'parentPadding', 20); 2965 treeLayout.levelDistance = mxUtils.getValue(style, 'treeLevelDistance', 30); 2966 treeLayout.maintainParentLocation = true; 2967 treeLayout.edgeRouting = false; 2968 treeLayout.resetEdges = false; 2969 2970 return treeLayout; 2971 } 2972 else if (style['childLayout'] == 'flowLayout') 2973 { 2974 var flowLayout = new mxHierarchicalLayout(this.graph, mxUtils.getValue(style, 2975 'flowOrientation', mxConstants.DIRECTION_EAST)); 2976 flowLayout.resizeParent = mxUtils.getValue(style, 'resizeParent', '1') == '1'; 2977 flowLayout.parentBorder = mxUtils.getValue(style, 'parentPadding', 20); 2978 flowLayout.maintainParentLocation = true; 2979 2980 // Special undocumented styles for changing the hierarchical 2981 flowLayout.intraCellSpacing = mxUtils.getValue(style, 'intraCellSpacing', 2982 mxHierarchicalLayout.prototype.intraCellSpacing); 2983 flowLayout.interRankCellSpacing = mxUtils.getValue(style, 'interRankCellSpacing', 2984 mxHierarchicalLayout.prototype.interRankCellSpacing); 2985 flowLayout.interHierarchySpacing = mxUtils.getValue(style, 'interHierarchySpacing', 2986 mxHierarchicalLayout.prototype.interHierarchySpacing); 2987 flowLayout.parallelEdgeSpacing = mxUtils.getValue(style, 'parallelEdgeSpacing', 2988 mxHierarchicalLayout.prototype.parallelEdgeSpacing); 2989 2990 return flowLayout; 2991 } 2992 else if (style['childLayout'] == 'circleLayout') 2993 { 2994 return new mxCircleLayout(this.graph); 2995 } 2996 else if (style['childLayout'] == 'organicLayout') 2997 { 2998 return new mxFastOrganicLayout(this.graph); 2999 } 3000 else if (style['childLayout'] == 'tableLayout') 3001 { 3002 return new TableLayout(this.graph); 3003 } 3004 } 3005 3006 return null; 3007 }; 3008}; 3009 3010/** 3011 * Returns the metadata of the given cells as a JSON object. 3012 */ 3013Graph.prototype.getDataForCells = function(cells) 3014{ 3015 var result = []; 3016 3017 for (var i = 0; i < cells.length; i++) 3018 { 3019 var attrs = (cells[i].value != null) ? cells[i].value.attributes : null; 3020 var row = {}; 3021 row.id = cells[i].id; 3022 3023 if (attrs != null) 3024 { 3025 for (var j = 0; j < attrs.length; j++) 3026 { 3027 row[attrs[j].nodeName] = attrs[j].nodeValue; 3028 } 3029 } 3030 else 3031 { 3032 row.label = this.convertValueToString(cells[i]); 3033 } 3034 3035 result.push(row); 3036 } 3037 3038 return result; 3039}; 3040 3041/** 3042 * Returns the DOM nodes for the given cells. 3043 */ 3044Graph.prototype.getNodesForCells = function(cells) 3045{ 3046 var nodes = []; 3047 3048 for (var i = 0; i < cells.length; i++) 3049 { 3050 var state = this.view.getState(cells[i]); 3051 3052 if (state != null) 3053 { 3054 var shapes = this.cellRenderer.getShapesForState(state); 3055 3056 for (var j = 0; j < shapes.length; j++) 3057 { 3058 if (shapes[j] != null && shapes[j].node != null) 3059 { 3060 nodes.push(shapes[j].node); 3061 } 3062 } 3063 3064 // Adds folding icon 3065 if (state.control != null && state.control.node != null) 3066 { 3067 nodes.push(state.control.node); 3068 } 3069 } 3070 } 3071 3072 return nodes; 3073}; 3074 3075/** 3076 * Creates animations for the given cells. 3077 */ 3078 Graph.prototype.createWipeAnimations = function(cells, wipeIn) 3079 { 3080 var animations = []; 3081 3082 for (var i = 0; i < cells.length; i++) 3083 { 3084 var state = this.view.getState(cells[i]); 3085 3086 if (state != null && state.shape != null) 3087 { 3088 // TODO: include descendants 3089 if (this.model.isEdge(state.cell) && 3090 state.absolutePoints != null && 3091 state.absolutePoints.length > 1) 3092 { 3093 animations.push(this.createEdgeWipeAnimation(state, wipeIn)); 3094 } 3095 else if (this.model.isVertex(state.cell) && 3096 state.shape.bounds != null) 3097 { 3098 animations.push(this.createVertexWipeAnimation(state, wipeIn)); 3099 } 3100 } 3101 } 3102 3103 return animations; 3104}; 3105 3106/** 3107 * Creates an object to show the given edge cell state. 3108 */ 3109Graph.prototype.createEdgeWipeAnimation = function(state, wipeIn) 3110{ 3111 var pts = state.absolutePoints.slice(); 3112 var segs = state.segments; 3113 var total = state.length; 3114 var n = pts.length; 3115 3116 return { 3117 execute: mxUtils.bind(this, function(step, steps) 3118 { 3119 if (state.shape != null) 3120 { 3121 var pts2 = [pts[0]]; 3122 var f = step / steps; 3123 3124 if (!wipeIn) 3125 { 3126 f = 1 - f; 3127 } 3128 3129 var dist = total * f; 3130 3131 for (var i = 1; i < n; i++) 3132 { 3133 if (dist <= segs[i - 1]) 3134 { 3135 pts2.push(new mxPoint(pts[i - 1].x + (pts[i].x - pts[i - 1].x) * dist / segs[i - 1], 3136 pts[i - 1].y + (pts[i].y - pts[i - 1].y) * dist / segs[i - 1])); 3137 3138 break; 3139 } 3140 else 3141 { 3142 dist -= segs[i - 1]; 3143 pts2.push(pts[i]); 3144 } 3145 } 3146 3147 state.shape.points = pts2; 3148 state.shape.redraw(); 3149 3150 if (step == 0) 3151 { 3152 Graph.setOpacityForNodes(this.getNodesForCells([state.cell]), 1); 3153 } 3154 3155 if (state.text != null && state.text.node != null) 3156 { 3157 state.text.node.style.opacity = f; 3158 } 3159 } 3160 }), 3161 stop: mxUtils.bind(this, function() 3162 { 3163 if (state.shape != null) 3164 { 3165 state.shape.points = pts; 3166 state.shape.redraw(); 3167 3168 if (state.text != null && state.text.node != null) 3169 { 3170 state.text.node.style.opacity = '' 3171 } 3172 3173 Graph.setOpacityForNodes(this.getNodesForCells([state.cell]), (wipeIn) ? 1 : 0); 3174 } 3175 }) 3176 }; 3177}; 3178 3179 /** 3180 * Creates an object to show the given vertex cell state. 3181 */ 3182Graph.prototype.createVertexWipeAnimation = function(state, wipeIn) 3183{ 3184 var bds = new mxRectangle.fromRectangle(state.shape.bounds); 3185 3186 return { 3187 execute: mxUtils.bind(this, function(step, steps) 3188 { 3189 if (state.shape != null) 3190 { 3191 var f = step / steps; 3192 3193 if (!wipeIn) 3194 { 3195 f = 1 - f; 3196 } 3197 3198 state.shape.bounds = new mxRectangle(bds.x, bds.y, bds.width * f, bds.height); 3199 state.shape.redraw(); 3200 3201 if (step == 0) 3202 { 3203 Graph.setOpacityForNodes(this.getNodesForCells([state.cell]), 1); 3204 } 3205 3206 if (state.text != null && state.text.node != null) 3207 { 3208 state.text.node.style.opacity = f; 3209 } 3210 } 3211 }), 3212 stop: mxUtils.bind(this, function() 3213 { 3214 if (state.shape != null) 3215 { 3216 state.shape.bounds = bds; 3217 state.shape.redraw(); 3218 3219 if (state.text != null && state.text.node != null) 3220 { 3221 state.text.node.style.opacity = '' 3222 } 3223 3224 Graph.setOpacityForNodes(this.getNodesForCells([state.cell]), (wipeIn) ? 1 : 0); 3225 } 3226 }) 3227 }; 3228}; 3229 3230/** 3231 * Runs the animations for the given cells. 3232 */ 3233 Graph.prototype.executeAnimations = function(animations, done, steps, delay) 3234 { 3235 steps = (steps != null) ? steps : 30; 3236 delay = (delay != null) ? delay : 30; 3237 var thread = null; 3238 var step = 0; 3239 3240 var animate = mxUtils.bind(this, function() 3241 { 3242 if (step == steps || this.stoppingCustomActions) 3243 { 3244 window.clearInterval(thread); 3245 3246 for (var i = 0; i < animations.length; i++) 3247 { 3248 animations[i].stop(); 3249 } 3250 3251 if (done != null) 3252 { 3253 done(); 3254 } 3255 } 3256 else 3257 { 3258 for (var i = 0; i < animations.length; i++) 3259 { 3260 animations[i].execute(step, steps); 3261 } 3262 } 3263 3264 step++; 3265 }); 3266 3267 thread = window.setInterval(animate, delay); 3268 animate(); 3269}; 3270 3271/** 3272 * Returns the size of the page format scaled with the page size. 3273 */ 3274Graph.prototype.getPageSize = function() 3275{ 3276 return (this.pageVisible) ? new mxRectangle(0, 0, this.pageFormat.width * this.pageScale, 3277 this.pageFormat.height * this.pageScale) : this.scrollTileSize; 3278}; 3279 3280/** 3281 * Returns a rectangle describing the position and count of the 3282 * background pages, where x and y are the position of the top, 3283 * left page and width and height are the vertical and horizontal 3284 * page count. 3285 */ 3286Graph.prototype.getPageLayout = function() 3287{ 3288 var size = this.getPageSize(); 3289 var bounds = this.getGraphBounds(); 3290 3291 if (bounds.width == 0 || bounds.height == 0) 3292 { 3293 return new mxRectangle(0, 0, 1, 1); 3294 } 3295 else 3296 { 3297 var x0 = Math.floor(Math.ceil(bounds.x / this.view.scale - 3298 this.view.translate.x) / size.width); 3299 var y0 = Math.floor(Math.ceil(bounds.y / this.view.scale - 3300 this.view.translate.y) / size.height); 3301 var w0 = Math.ceil((Math.floor((bounds.x + bounds.width) / this.view.scale) - 3302 this.view.translate.x) / size.width) - x0; 3303 var h0 = Math.ceil((Math.floor((bounds.y + bounds.height) / this.view.scale) - 3304 this.view.translate.y) / size.height) - y0; 3305 3306 return new mxRectangle(x0, y0, w0, h0); 3307 } 3308}; 3309 3310/** 3311 * Sanitizes the given HTML markup. 3312 */ 3313Graph.prototype.sanitizeHtml = function(value, editing) 3314{ 3315 return Graph.sanitizeHtml(value, editing); 3316}; 3317 3318/** 3319 * Revalidates all cells with placeholders in the current graph model. 3320 */ 3321Graph.prototype.updatePlaceholders = function() 3322{ 3323 var model = this.model; 3324 var validate = false; 3325 3326 for (var key in this.model.cells) 3327 { 3328 var cell = this.model.cells[key]; 3329 3330 if (this.isReplacePlaceholders(cell)) 3331 { 3332 this.view.invalidate(cell, false, false); 3333 validate = true; 3334 } 3335 } 3336 3337 if (validate) 3338 { 3339 this.view.validate(); 3340 } 3341}; 3342 3343/** 3344 * Adds support for placeholders in labels. 3345 */ 3346Graph.prototype.isReplacePlaceholders = function(cell) 3347{ 3348 return cell.value != null && typeof(cell.value) == 'object' && 3349 cell.value.getAttribute('placeholders') == '1'; 3350}; 3351 3352/** 3353 * Returns true if the given mouse wheel event should be used for zooming. This 3354 * is invoked if no dialogs are showing and returns true with Alt or Control 3355 * (or cmd in macOS only) is pressed. 3356 */ 3357Graph.prototype.isZoomWheelEvent = function(evt) 3358{ 3359 return (Graph.zoomWheel && !mxEvent.isShiftDown(evt) && !mxEvent.isMetaDown(evt) && 3360 !mxEvent.isAltDown(evt) && (!mxEvent.isControlDown(evt) || mxClient.IS_MAC)) || 3361 (!Graph.zoomWheel && (mxEvent.isAltDown(evt) || mxEvent.isControlDown(evt))); 3362}; 3363 3364/** 3365 * Returns true if the given scroll wheel event should be used for scrolling. 3366 */ 3367Graph.prototype.isScrollWheelEvent = function(evt) 3368{ 3369 return !this.isZoomWheelEvent(evt); 3370}; 3371 3372/** 3373 * Adds Alt+click to select cells behind cells (Shift+Click on Chrome OS). 3374 */ 3375Graph.prototype.isTransparentClickEvent = function(evt) 3376{ 3377 return mxEvent.isAltDown(evt) || (mxClient.IS_CHROMEOS && mxEvent.isShiftDown(evt)); 3378}; 3379 3380/** 3381 * Adds ctrl+shift+connect to disable connections. 3382 */ 3383Graph.prototype.isIgnoreTerminalEvent = function(evt) 3384{ 3385 return mxEvent.isAltDown(evt) && !mxEvent.isShiftDown(evt) && 3386 !mxEvent.isControlDown(evt) && !mxEvent.isMetaDown(evt); 3387}; 3388 3389/** 3390 * Returns true if the given edge should be ignored. 3391 */ 3392Graph.prototype.isEdgeIgnored = function(cell) 3393{ 3394 var result = false; 3395 3396 if (cell != null) 3397 { 3398 var style = this.getCurrentCellStyle(cell); 3399 3400 result = mxUtils.getValue(style, 'ignoreEdge', '0') == '1'; 3401 } 3402 3403 return result; 3404}; 3405 3406/** 3407 * Adds support for placeholders in labels. 3408 */ 3409Graph.prototype.isSplitTarget = function(target, cells, evt) 3410{ 3411 return !this.model.isEdge(cells[0]) && 3412 !mxEvent.isAltDown(evt) && !mxEvent.isShiftDown(evt) && 3413 mxGraph.prototype.isSplitTarget.apply(this, arguments); 3414}; 3415 3416/** 3417 * Adds support for placeholders in labels. 3418 */ 3419Graph.prototype.getLabel = function(cell) 3420{ 3421 var result = mxGraph.prototype.getLabel.apply(this, arguments); 3422 3423 if (result != null && this.isReplacePlaceholders(cell) && cell.getAttribute('placeholder') == null) 3424 { 3425 result = this.replacePlaceholders(cell, result); 3426 } 3427 3428 return result; 3429}; 3430 3431/** 3432 * Adds labelMovable style. 3433 */ 3434Graph.prototype.isLabelMovable = function(cell) 3435{ 3436 var style = this.getCurrentCellStyle(cell); 3437 3438 return !this.isCellLocked(cell) && 3439 ((this.model.isEdge(cell) && this.edgeLabelsMovable) || 3440 (this.model.isVertex(cell) && (this.vertexLabelsMovable || 3441 mxUtils.getValue(style, 'labelMovable', '0') == '1'))); 3442}; 3443 3444/** 3445 * Adds event if grid size is changed. 3446 */ 3447Graph.prototype.setGridSize = function(value) 3448{ 3449 this.gridSize = value; 3450 this.fireEvent(new mxEventObject('gridSizeChanged')); 3451}; 3452 3453/** 3454 * Adds event if default parent is changed. 3455 */ 3456Graph.prototype.setDefaultParent = function(cell) 3457{ 3458 this.defaultParent = cell; 3459 this.fireEvent(new mxEventObject('defaultParentChanged')); 3460}; 3461 3462/** 3463 * Function: getClickableLinkForCell 3464 * 3465 * Returns the first non-null link for the cell or its ancestors. 3466 * 3467 * Parameters: 3468 * 3469 * cell - <mxCell> whose link should be returned. 3470 */ 3471Graph.prototype.getClickableLinkForCell = function(cell) 3472{ 3473 do 3474 { 3475 var link = this.getLinkForCell(cell); 3476 3477 if (link != null) 3478 { 3479 return link; 3480 } 3481 3482 cell = this.model.getParent(cell); 3483 } while (cell != null); 3484 3485 return null; 3486}; 3487 3488/** 3489 * Private helper method. 3490 */ 3491Graph.prototype.getGlobalVariable = function(name) 3492{ 3493 var val = null; 3494 3495 if (name == 'date') 3496 { 3497 val = new Date().toLocaleDateString(); 3498 } 3499 else if (name == 'time') 3500 { 3501 val = new Date().toLocaleTimeString(); 3502 } 3503 else if (name == 'timestamp') 3504 { 3505 val = new Date().toLocaleString(); 3506 } 3507 else if (name.substring(0, 5) == 'date{') 3508 { 3509 var fmt = name.substring(5, name.length - 1); 3510 val = this.formatDate(new Date(), fmt); 3511 } 3512 3513 return val; 3514}; 3515 3516/** 3517 * Formats a date, see http://blog.stevenlevithan.com/archives/date-time-format 3518 */ 3519Graph.prototype.formatDate = function(date, mask, utc) 3520{ 3521 // LATER: Cache regexs 3522 if (this.dateFormatCache == null) 3523 { 3524 this.dateFormatCache = { 3525 i18n: { 3526 dayNames: [ 3527 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", 3528 "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" 3529 ], 3530 monthNames: [ 3531 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 3532 "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" 3533 ] 3534 }, 3535 3536 masks: { 3537 "default": "ddd mmm dd yyyy HH:MM:ss", 3538 shortDate: "m/d/yy", 3539 mediumDate: "mmm d, yyyy", 3540 longDate: "mmmm d, yyyy", 3541 fullDate: "dddd, mmmm d, yyyy", 3542 shortTime: "h:MM TT", 3543 mediumTime: "h:MM:ss TT", 3544 longTime: "h:MM:ss TT Z", 3545 isoDate: "yyyy-mm-dd", 3546 isoTime: "HH:MM:ss", 3547 isoDateTime: "yyyy-mm-dd'T'HH:MM:ss", 3548 isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'" 3549 } 3550 }; 3551 } 3552 3553 var dF = this.dateFormatCache; 3554 var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g, 3555 timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g, 3556 timezoneClip = /[^-+\dA-Z]/g, 3557 pad = function (val, len) { 3558 val = String(val); 3559 len = len || 2; 3560 while (val.length < len) val = "0" + val; 3561 return val; 3562 }; 3563 3564 // You can't provide utc if you skip other args (use the "UTC:" mask prefix) 3565 if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) { 3566 mask = date; 3567 date = undefined; 3568 } 3569 3570 // Passing date through Date applies Date.parse, if necessary 3571 date = date ? new Date(date) : new Date; 3572 if (isNaN(date)) throw SyntaxError("invalid date"); 3573 3574 mask = String(dF.masks[mask] || mask || dF.masks["default"]); 3575 3576 // Allow setting the utc argument via the mask 3577 if (mask.slice(0, 4) == "UTC:") { 3578 mask = mask.slice(4); 3579 utc = true; 3580 } 3581 3582 var _ = utc ? "getUTC" : "get", 3583 d = date[_ + "Date"](), 3584 D = date[_ + "Day"](), 3585 m = date[_ + "Month"](), 3586 y = date[_ + "FullYear"](), 3587 H = date[_ + "Hours"](), 3588 M = date[_ + "Minutes"](), 3589 s = date[_ + "Seconds"](), 3590 L = date[_ + "Milliseconds"](), 3591 o = utc ? 0 : date.getTimezoneOffset(), 3592 flags = { 3593 d: d, 3594 dd: pad(d), 3595 ddd: dF.i18n.dayNames[D], 3596 dddd: dF.i18n.dayNames[D + 7], 3597 m: m + 1, 3598 mm: pad(m + 1), 3599 mmm: dF.i18n.monthNames[m], 3600 mmmm: dF.i18n.monthNames[m + 12], 3601 yy: String(y).slice(2), 3602 yyyy: y, 3603 h: H % 12 || 12, 3604 hh: pad(H % 12 || 12), 3605 H: H, 3606 HH: pad(H), 3607 M: M, 3608 MM: pad(M), 3609 s: s, 3610 ss: pad(s), 3611 l: pad(L, 3), 3612 L: pad(L > 99 ? Math.round(L / 10) : L), 3613 t: H < 12 ? "a" : "p", 3614 tt: H < 12 ? "am" : "pm", 3615 T: H < 12 ? "A" : "P", 3616 TT: H < 12 ? "AM" : "PM", 3617 Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""), 3618 o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4), 3619 S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10] 3620 }; 3621 3622 return mask.replace(token, function ($0) 3623 { 3624 return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1); 3625 }); 3626}; 3627 3628/** 3629 * 3630 */ 3631Graph.prototype.getLayerForCells = function(cells) 3632{ 3633 var result = null; 3634 3635 if (cells.length > 0) 3636 { 3637 result = cells[0]; 3638 3639 while (!this.model.isLayer(result)) 3640 { 3641 result = this.model.getParent(result); 3642 } 3643 3644 for (var i = 1; i < cells.length; i++) 3645 { 3646 if (!this.model.isAncestor(result, cells[i])) 3647 { 3648 result = null; 3649 break; 3650 } 3651 } 3652 } 3653 3654 return result; 3655}; 3656 3657/** 3658 * 3659 */ 3660Graph.prototype.createLayersDialog = function(onchange, inverted) 3661{ 3662 var div = document.createElement('div'); 3663 div.style.position = 'absolute'; 3664 3665 var model = this.getModel(); 3666 var childCount = model.getChildCount(model.root); 3667 3668 for (var i = 0; i < childCount; i++) 3669 { 3670 (mxUtils.bind(this, function(layer) 3671 { 3672 var title = this.convertValueToString(layer) || 3673 (mxResources.get('background') || 'Background'); 3674 3675 var span = document.createElement('div'); 3676 span.style.overflow = 'hidden'; 3677 span.style.textOverflow = 'ellipsis'; 3678 span.style.padding = '2px'; 3679 span.style.whiteSpace = 'nowrap'; 3680 span.style.cursor = 'pointer'; 3681 span.setAttribute('title', mxResources.get( 3682 model.isVisible(layer) ? 3683 'hideIt' : 'show', [title])); 3684 3685 var inp = document.createElement('img'); 3686 inp.setAttribute('draggable', 'false'); 3687 inp.setAttribute('align', 'absmiddle'); 3688 inp.setAttribute('border', '0'); 3689 inp.style.position = 'relative'; 3690 inp.style.width = '16px'; 3691 inp.style.padding = '0px 6px 0 4px'; 3692 3693 if (inverted) 3694 { 3695 inp.style.filter = 'invert(100%)'; 3696 inp.style.top = '-2px'; 3697 } 3698 3699 span.appendChild(inp); 3700 3701 mxUtils.write(span, title); 3702 div.appendChild(span); 3703 3704 function update() 3705 { 3706 if (model.isVisible(layer)) 3707 { 3708 inp.setAttribute('src', Editor.visibleImage); 3709 mxUtils.setOpacity(span, 75); 3710 } 3711 else 3712 { 3713 inp.setAttribute('src', Editor.hiddenImage); 3714 mxUtils.setOpacity(span, 25); 3715 } 3716 }; 3717 3718 mxEvent.addListener(span, 'click', function() 3719 { 3720 model.setVisible(layer, !model.isVisible(layer)); 3721 update(); 3722 3723 if (onchange != null) 3724 { 3725 onchange(layer); 3726 } 3727 }); 3728 3729 update(); 3730 })(model.getChildAt(model.root, i))); 3731 } 3732 3733 return div; 3734}; 3735 3736/** 3737 * Private helper method. 3738 */ 3739Graph.prototype.replacePlaceholders = function(cell, str, vars, translate) 3740{ 3741 var result = []; 3742 3743 if (str != null) 3744 { 3745 var last = 0; 3746 3747 while (match = this.placeholderPattern.exec(str)) 3748 { 3749 var val = match[0]; 3750 3751 if (val.length > 2 && val != '%label%' && val != '%tooltip%') 3752 { 3753 var tmp = null; 3754 3755 if (match.index > last && str.charAt(match.index - 1) == '%') 3756 { 3757 tmp = val.substring(1); 3758 } 3759 else 3760 { 3761 var name = val.substring(1, val.length - 1); 3762 3763 // Workaround for invalid char for getting attribute in older versions of IE 3764 if (name == 'id') 3765 { 3766 tmp = cell.id; 3767 } 3768 else if (name.indexOf('{') < 0) 3769 { 3770 var current = cell; 3771 3772 while (tmp == null && current != null) 3773 { 3774 if (current.value != null && typeof(current.value) == 'object') 3775 { 3776 if (Graph.translateDiagram && Graph.diagramLanguage != null) 3777 { 3778 tmp = current.getAttribute(name + '_' + Graph.diagramLanguage); 3779 } 3780 3781 if (tmp == null) 3782 { 3783 tmp = (current.hasAttribute(name)) ? ((current.getAttribute(name) != null) ? 3784 current.getAttribute(name) : '') : null; 3785 } 3786 } 3787 3788 current = this.model.getParent(current); 3789 } 3790 } 3791 3792 if (tmp == null) 3793 { 3794 tmp = this.getGlobalVariable(name); 3795 } 3796 3797 if (tmp == null && vars != null) 3798 { 3799 tmp = vars[name]; 3800 } 3801 } 3802 3803 result.push(str.substring(last, match.index) + ((tmp != null) ? tmp : val)); 3804 last = match.index + val.length; 3805 } 3806 } 3807 3808 result.push(str.substring(last)); 3809 } 3810 3811 return result.join(''); 3812}; 3813 3814/** 3815 * Resolves the given cells in the model and selects them. 3816 */ 3817Graph.prototype.restoreSelection = function(cells) 3818{ 3819 if (cells != null && cells.length > 0) 3820 { 3821 var temp = []; 3822 3823 for (var i = 0; i < cells.length; i++) 3824 { 3825 var newCell = this.model.getCell(cells[i].id); 3826 3827 if (newCell != null) 3828 { 3829 temp.push(newCell); 3830 } 3831 } 3832 3833 this.setSelectionCells(temp); 3834 } 3835 else 3836 { 3837 this.clearSelection(); 3838 } 3839}; 3840 3841/** 3842 * Selects cells for connect vertex return value. 3843 */ 3844Graph.prototype.selectCellsForConnectVertex = function(cells, evt, hoverIcons) 3845{ 3846 // Selects only target vertex if one exists 3847 if (cells.length == 2 && this.model.isVertex(cells[1])) 3848 { 3849 this.setSelectionCell(cells[1]); 3850 this.scrollCellToVisible(cells[1]); 3851 3852 if (hoverIcons != null) 3853 { 3854 // Adds hover icons for cloned vertex or hides icons 3855 if (mxEvent.isTouchEvent(evt)) 3856 { 3857 hoverIcons.update(hoverIcons.getState(this.view.getState(cells[1]))); 3858 } 3859 else 3860 { 3861 hoverIcons.reset(); 3862 } 3863 } 3864 } 3865 else 3866 { 3867 this.setSelectionCells(cells); 3868 } 3869}; 3870 3871/** 3872 * Never connects children in stack layouts or tables. 3873 */ 3874Graph.prototype.isCloneConnectSource = function(source) 3875{ 3876 var layout = null; 3877 3878 if (this.layoutManager != null) 3879 { 3880 layout = this.layoutManager.getLayout(this.model.getParent(source)); 3881 } 3882 3883 return this.isTableRow(source) || this.isTableCell(source) || 3884 (layout != null && layout.constructor == mxStackLayout); 3885}; 3886 3887/** 3888 * Adds a connection to the given vertex or clones the vertex in special layout 3889 * containers without creating a connection. 3890 */ 3891Graph.prototype.connectVertex = function(source, direction, length, evt, forceClone, ignoreCellAt, createTarget, done) 3892{ 3893 ignoreCellAt = (ignoreCellAt) ? ignoreCellAt : false; 3894 3895 // Ignores relative edge labels 3896 if (source.geometry.relative && this.model.isEdge(source.parent)) 3897 { 3898 return []; 3899 } 3900 3901 // Uses parent for relative child cells 3902 while (source.geometry.relative && this.model.isVertex(source.parent)) 3903 { 3904 source = source.parent; 3905 } 3906 3907 // Handles clone connect sources 3908 var cloneSource = this.isCloneConnectSource(source); 3909 var composite = (cloneSource) ? source : this.getCompositeParent(source); 3910 3911 var pt = (source.geometry.relative && source.parent.geometry != null) ? 3912 new mxPoint(source.parent.geometry.width * source.geometry.x, 3913 source.parent.geometry.height * source.geometry.y) : 3914 new mxPoint(composite.geometry.x, composite.geometry.y); 3915 3916 if (direction == mxConstants.DIRECTION_NORTH) 3917 { 3918 pt.x += composite.geometry.width / 2; 3919 pt.y -= length ; 3920 } 3921 else if (direction == mxConstants.DIRECTION_SOUTH) 3922 { 3923 pt.x += composite.geometry.width / 2; 3924 pt.y += composite.geometry.height + length; 3925 } 3926 else if (direction == mxConstants.DIRECTION_WEST) 3927 { 3928 pt.x -= length; 3929 pt.y += composite.geometry.height / 2; 3930 } 3931 else 3932 { 3933 pt.x += composite.geometry.width + length; 3934 pt.y += composite.geometry.height / 2; 3935 } 3936 3937 var parentState = this.view.getState(this.model.getParent(source)); 3938 var s = this.view.scale; 3939 var t = this.view.translate; 3940 var dx = t.x * s; 3941 var dy = t.y * s; 3942 3943 if (parentState != null && this.model.isVertex(parentState.cell)) 3944 { 3945 dx = parentState.x; 3946 dy = parentState.y; 3947 } 3948 3949 // Workaround for relative child cells 3950 if (this.model.isVertex(source.parent) && source.geometry.relative) 3951 { 3952 pt.x += source.parent.geometry.x; 3953 pt.y += source.parent.geometry.y; 3954 } 3955 3956 // Checks end point for target cell and container 3957 var rect = (!ignoreCellAt) ? new mxRectangle(dx + pt.x * s, dy + pt.y * s).grow(40 * s) : null; 3958 var tempCells = (rect != null) ? this.getCells(0, 0, 0, 0, null, null, rect, null, true) : null; 3959 var sourceState = this.view.getState(source); 3960 var container = null; 3961 var target = null; 3962 3963 if (tempCells != null) 3964 { 3965 tempCells = tempCells.reverse(); 3966 3967 for (var i = 0; i < tempCells.length; i++) 3968 { 3969 if (!this.isCellLocked(tempCells[i]) && !this.model.isEdge(tempCells[i]) && tempCells[i] != source) 3970 { 3971 // Direct parent overrides all possible containers 3972 if (!this.model.isAncestor(source, tempCells[i]) && this.isContainer(tempCells[i]) && 3973 (container == null || tempCells[i] == this.model.getParent(source))) 3974 { 3975 container = tempCells[i]; 3976 } 3977 // Containers are used as target cells but swimlanes are used as parents 3978 else if (target == null && this.isCellConnectable(tempCells[i]) && 3979 !this.model.isAncestor(tempCells[i], source) && 3980 !this.isSwimlane(tempCells[i])) 3981 { 3982 var targetState = this.view.getState(tempCells[i]); 3983 3984 if (sourceState != null && targetState != null && !mxUtils.intersects(sourceState, targetState)) 3985 { 3986 target = tempCells[i]; 3987 } 3988 } 3989 } 3990 } 3991 } 3992 3993 var duplicate = (!mxEvent.isShiftDown(evt) || mxEvent.isControlDown(evt)) || forceClone; 3994 3995 if (duplicate && (urlParams['sketch'] != '1' || forceClone)) 3996 { 3997 if (direction == mxConstants.DIRECTION_NORTH) 3998 { 3999 pt.y -= source.geometry.height / 2; 4000 } 4001 else if (direction == mxConstants.DIRECTION_SOUTH) 4002 { 4003 pt.y += source.geometry.height / 2; 4004 } 4005 else if (direction == mxConstants.DIRECTION_WEST) 4006 { 4007 pt.x -= source.geometry.width / 2; 4008 } 4009 else 4010 { 4011 pt.x += source.geometry.width / 2; 4012 } 4013 } 4014 4015 var result = []; 4016 var realTarget = target; 4017 target = container; 4018 4019 var execute = mxUtils.bind(this, function(targetCell) 4020 { 4021 if (createTarget == null || targetCell != null || (target == null && cloneSource)) 4022 { 4023 this.model.beginUpdate(); 4024 try 4025 { 4026 if (realTarget == null && duplicate) 4027 { 4028 // Handles relative and composite cells 4029 var cellToClone = this.getAbsoluteParent((targetCell != null) ? targetCell : source); 4030 cellToClone = (cloneSource) ? source : this.getCompositeParent(cellToClone); 4031 realTarget = (targetCell != null) ? targetCell : this.duplicateCells([cellToClone], false)[0]; 4032 4033 if (targetCell != null) 4034 { 4035 this.addCells([realTarget], this.model.getParent(source), null, null, null, true); 4036 } 4037 4038 var geo = this.getCellGeometry(realTarget); 4039 4040 if (geo != null) 4041 { 4042 if (targetCell != null && urlParams['sketch'] == '1') 4043 { 4044 if (direction == mxConstants.DIRECTION_NORTH) 4045 { 4046 pt.y -= geo.height / 2; 4047 } 4048 else if (direction == mxConstants.DIRECTION_SOUTH) 4049 { 4050 pt.y += geo.height / 2; 4051 } 4052 else if (direction == mxConstants.DIRECTION_WEST) 4053 { 4054 pt.x -= geo.width / 2; 4055 } 4056 else 4057 { 4058 pt.x += geo.width / 2; 4059 } 4060 } 4061 4062 geo.x = pt.x - geo.width / 2; 4063 geo.y = pt.y - geo.height / 2; 4064 } 4065 4066 if (container != null) 4067 { 4068 this.addCells([realTarget], container, null, null, null, true); 4069 target = null; 4070 } 4071 else if (duplicate && !cloneSource) 4072 { 4073 this.addCells([realTarget], this.getDefaultParent(), null, null, null, true); 4074 } 4075 } 4076 4077 var edge = ((mxEvent.isControlDown(evt) && mxEvent.isShiftDown(evt) && duplicate) || 4078 (target == null && cloneSource)) ? null : this.insertEdge(this.model.getParent(source), 4079 null, '', source, realTarget, this.createCurrentEdgeStyle()); 4080 4081 // Inserts edge before source 4082 if (edge != null && this.connectionHandler.insertBeforeSource) 4083 { 4084 var index = null; 4085 var tmp = source; 4086 4087 while (tmp.parent != null && tmp.geometry != null && 4088 tmp.geometry.relative && tmp.parent != edge.parent) 4089 { 4090 tmp = this.model.getParent(tmp); 4091 } 4092 4093 if (tmp != null && tmp.parent != null && tmp.parent == edge.parent) 4094 { 4095 var index = tmp.parent.getIndex(tmp); 4096 this.model.add(tmp.parent, edge, index); 4097 } 4098 } 4099 4100 // Special case: Click on west icon puts clone before cell 4101 if (target == null && realTarget != null && source.parent != null && 4102 cloneSource && direction == mxConstants.DIRECTION_WEST) 4103 { 4104 var index = source.parent.getIndex(source); 4105 this.model.add(source.parent, realTarget, index); 4106 } 4107 4108 if (edge != null) 4109 { 4110 result.push(edge); 4111 } 4112 4113 if (target == null && realTarget != null) 4114 { 4115 result.push(realTarget); 4116 } 4117 4118 if (realTarget == null && edge != null) 4119 { 4120 edge.geometry.setTerminalPoint(pt, false); 4121 } 4122 4123 if (edge != null) 4124 { 4125 this.fireEvent(new mxEventObject('cellsInserted', 'cells', [edge])); 4126 } 4127 } 4128 finally 4129 { 4130 this.model.endUpdate(); 4131 } 4132 } 4133 4134 if (done != null) 4135 { 4136 done(result); 4137 } 4138 else 4139 { 4140 return result; 4141 } 4142 }); 4143 4144 if (createTarget != null && realTarget == null && duplicate && 4145 (target != null || !cloneSource)) 4146 { 4147 createTarget(dx + pt.x * s, dy + pt.y * s, execute); 4148 } 4149 else 4150 { 4151 return execute(realTarget); 4152 } 4153}; 4154 4155/** 4156 * Returns all labels in the diagram as a string. 4157 */ 4158Graph.prototype.getIndexableText = function() 4159{ 4160 var tmp = document.createElement('div'); 4161 var labels = []; 4162 var label = ''; 4163 4164 for (var key in this.model.cells) 4165 { 4166 var cell = this.model.cells[key]; 4167 4168 if (this.model.isVertex(cell) || this.model.isEdge(cell)) 4169 { 4170 if (this.isHtmlLabel(cell)) 4171 { 4172 tmp.innerHTML = this.sanitizeHtml(this.getLabel(cell)); 4173 label = mxUtils.extractTextWithWhitespace([tmp]); 4174 } 4175 else 4176 { 4177 label = this.getLabel(cell); 4178 } 4179 4180 label = mxUtils.trim(label.replace(/[\x00-\x1F\x7F-\x9F]|\s+/g, ' ')); 4181 4182 if (label.length > 0) 4183 { 4184 labels.push(label); 4185 } 4186 } 4187 } 4188 4189 return labels.join(' '); 4190}; 4191 4192/** 4193 * Returns the label for the given cell. 4194 */ 4195Graph.prototype.convertValueToString = function(cell) 4196{ 4197 var value = this.model.getValue(cell); 4198 4199 if (value != null && typeof(value) == 'object') 4200 { 4201 var result = null; 4202 4203 if (this.isReplacePlaceholders(cell) && cell.getAttribute('placeholder') != null) 4204 { 4205 var name = cell.getAttribute('placeholder'); 4206 var current = cell; 4207 4208 while (result == null && current != null) 4209 { 4210 if (current.value != null && typeof(current.value) == 'object') 4211 { 4212 result = (current.hasAttribute(name)) ? ((current.getAttribute(name) != null) ? 4213 current.getAttribute(name) : '') : null; 4214 } 4215 4216 current = this.model.getParent(current); 4217 } 4218 } 4219 else 4220 { 4221 var result = null; 4222 4223 if (Graph.translateDiagram && Graph.diagramLanguage != null) 4224 { 4225 result = value.getAttribute('label_' + Graph.diagramLanguage); 4226 } 4227 4228 if (result == null) 4229 { 4230 result = value.getAttribute('label') || ''; 4231 } 4232 } 4233 4234 return result || ''; 4235 } 4236 4237 return mxGraph.prototype.convertValueToString.apply(this, arguments); 4238}; 4239 4240/** 4241 * Returns the link for the given cell. 4242 */ 4243Graph.prototype.getLinksForState = function(state) 4244{ 4245 if (state != null && state.text != null && state.text.node != null) 4246 { 4247 return state.text.node.getElementsByTagName('a'); 4248 } 4249 4250 return null; 4251}; 4252 4253/** 4254 * Returns the link for the given cell. 4255 */ 4256Graph.prototype.getLinkForCell = function(cell) 4257{ 4258 if (cell.value != null && typeof(cell.value) == 'object') 4259 { 4260 var link = cell.value.getAttribute('link'); 4261 4262 // Removes links with leading javascript: protocol 4263 // TODO: Check more possible attack vectors 4264 if (link != null && link.toLowerCase().substring(0, 11) === 'javascript:') 4265 { 4266 link = link.substring(11); 4267 } 4268 4269 return link; 4270 } 4271 4272 return null; 4273}; 4274 4275/** 4276 * Returns the link target for the given cell. 4277 */ 4278Graph.prototype.getLinkTargetForCell = function(cell) 4279{ 4280 if (cell.value != null && typeof(cell.value) == 'object') 4281 { 4282 return cell.value.getAttribute('linkTarget'); 4283 } 4284 4285 return null; 4286}; 4287 4288/** 4289 * Overrides label orientation for collapsed swimlanes inside stack and 4290 * for partial rectangles inside tables. 4291 */ 4292Graph.prototype.getCellStyle = function(cell) 4293{ 4294 var style = mxGraph.prototype.getCellStyle.apply(this, arguments); 4295 4296 if (cell != null && this.layoutManager != null) 4297 { 4298 var parent = this.model.getParent(cell); 4299 4300 if (this.model.isVertex(parent) && this.isCellCollapsed(cell)) 4301 { 4302 var layout = this.layoutManager.getLayout(parent); 4303 4304 if (layout != null && layout.constructor == mxStackLayout) 4305 { 4306 style[mxConstants.STYLE_HORIZONTAL] = !layout.horizontal; 4307 } 4308 } 4309 } 4310 4311 return style; 4312}; 4313 4314/** 4315 * Disables alternate width persistence for stack layout parents 4316 */ 4317Graph.prototype.updateAlternateBounds = function(cell, geo, willCollapse) 4318{ 4319 if (cell != null && geo != null && this.layoutManager != null && geo.alternateBounds != null) 4320 { 4321 var layout = this.layoutManager.getLayout(this.model.getParent(cell)); 4322 4323 if (layout != null && layout.constructor == mxStackLayout) 4324 { 4325 if (layout.horizontal) 4326 { 4327 geo.alternateBounds.height = 0; 4328 } 4329 else 4330 { 4331 geo.alternateBounds.width = 0; 4332 } 4333 } 4334 } 4335 4336 mxGraph.prototype.updateAlternateBounds.apply(this, arguments); 4337}; 4338 4339/** 4340 * Adds Shift+collapse/expand and size management for folding inside stack 4341 */ 4342Graph.prototype.isMoveCellsEvent = function(evt, state) 4343{ 4344 return mxEvent.isShiftDown(evt) || mxUtils.getValue(state.style, 'moveCells', '0') == '1'; 4345}; 4346 4347/** 4348 * Adds Shift+collapse/expand and size management for folding inside stack 4349 */ 4350Graph.prototype.foldCells = function(collapse, recurse, cells, checkFoldable, evt) 4351{ 4352 recurse = (recurse != null) ? recurse : false; 4353 4354 if (cells == null) 4355 { 4356 cells = this.getFoldableCells(this.getSelectionCells(), collapse); 4357 } 4358 4359 if (cells != null) 4360 { 4361 this.model.beginUpdate(); 4362 4363 try 4364 { 4365 mxGraph.prototype.foldCells.apply(this, arguments); 4366 4367 // Resizes all parent stacks if alt is not pressed 4368 if (this.layoutManager != null) 4369 { 4370 for (var i = 0; i < cells.length; i++) 4371 { 4372 var state = this.view.getState(cells[i]); 4373 var geo = this.getCellGeometry(cells[i]); 4374 4375 if (state != null && geo != null) 4376 { 4377 var dx = Math.round(geo.width - state.width / this.view.scale); 4378 var dy = Math.round(geo.height - state.height / this.view.scale); 4379 4380 if (dy != 0 || dx != 0) 4381 { 4382 var parent = this.model.getParent(cells[i]); 4383 var layout = this.layoutManager.getLayout(parent); 4384 4385 if (layout == null) 4386 { 4387 // Moves cells to the right and down after collapse/expand 4388 if (evt != null && this.isMoveCellsEvent(evt, state)) 4389 { 4390 this.moveSiblings(state, parent, dx, dy); 4391 } 4392 } 4393 else if ((evt == null || !mxEvent.isAltDown(evt)) && 4394 layout.constructor == mxStackLayout && !layout.resizeLast) 4395 { 4396 this.resizeParentStacks(parent, layout, dx, dy); 4397 } 4398 } 4399 } 4400 } 4401 } 4402 } 4403 finally 4404 { 4405 this.model.endUpdate(); 4406 } 4407 4408 // Selects cells after folding 4409 if (this.isEnabled()) 4410 { 4411 this.setSelectionCells(cells); 4412 } 4413 } 4414}; 4415 4416/** 4417 * Overrides label orientation for collapsed swimlanes inside stack. 4418 */ 4419Graph.prototype.moveSiblings = function(state, parent, dx, dy) 4420{ 4421 this.model.beginUpdate(); 4422 try 4423 { 4424 var cells = this.getCellsBeyond(state.x, state.y, parent, true, true); 4425 4426 for (var i = 0; i < cells.length; i++) 4427 { 4428 if (cells[i] != state.cell) 4429 { 4430 var tmp = this.view.getState(cells[i]); 4431 var geo = this.getCellGeometry(cells[i]); 4432 4433 if (tmp != null && geo != null) 4434 { 4435 geo = geo.clone(); 4436 geo.translate(Math.round(dx * Math.max(0, Math.min(1, (tmp.x - state.x) / state.width))), 4437 Math.round(dy * Math.max(0, Math.min(1, (tmp.y - state.y) / state.height)))); 4438 this.model.setGeometry(cells[i], geo); 4439 } 4440 } 4441 } 4442 } 4443 finally 4444 { 4445 this.model.endUpdate(); 4446 } 4447}; 4448 4449/** 4450 * Overrides label orientation for collapsed swimlanes inside stack. 4451 */ 4452Graph.prototype.resizeParentStacks = function(parent, layout, dx, dy) 4453{ 4454 if (this.layoutManager != null && layout != null && layout.constructor == mxStackLayout && !layout.resizeLast) 4455 { 4456 this.model.beginUpdate(); 4457 try 4458 { 4459 var dir = layout.horizontal; 4460 4461 // Bubble resize up for all parent stack layouts with same orientation 4462 while (parent != null && layout != null && layout.constructor == mxStackLayout && 4463 layout.horizontal == dir && !layout.resizeLast) 4464 { 4465 var pgeo = this.getCellGeometry(parent); 4466 var pstate = this.view.getState(parent); 4467 4468 if (pstate != null && pgeo != null) 4469 { 4470 pgeo = pgeo.clone(); 4471 4472 if (layout.horizontal) 4473 { 4474 pgeo.width += dx + Math.min(0, pstate.width / this.view.scale - pgeo.width); 4475 } 4476 else 4477 { 4478 pgeo.height += dy + Math.min(0, pstate.height / this.view.scale - pgeo.height); 4479 } 4480 4481 this.model.setGeometry(parent, pgeo); 4482 } 4483 4484 parent = this.model.getParent(parent); 4485 layout = this.layoutManager.getLayout(parent); 4486 } 4487 } 4488 finally 4489 { 4490 this.model.endUpdate(); 4491 } 4492 } 4493}; 4494 4495/** 4496 * Disables drill-down for non-swimlanes. 4497 */ 4498Graph.prototype.isContainer = function(cell) 4499{ 4500 var style = this.getCurrentCellStyle(cell); 4501 4502 if (this.isSwimlane(cell)) 4503 { 4504 return style['container'] != '0'; 4505 } 4506 else 4507 { 4508 return style['container'] == '1'; 4509 } 4510}; 4511 4512/** 4513 * Adds a connectable style. 4514 */ 4515Graph.prototype.isCellConnectable = function(cell) 4516{ 4517 var style = this.getCurrentCellStyle(cell); 4518 4519 return (style['connectable'] != null) ? style['connectable'] != '0' : 4520 mxGraph.prototype.isCellConnectable.apply(this, arguments); 4521}; 4522 4523/** 4524 * Adds labelMovable style. 4525 */ 4526Graph.prototype.isLabelMovable = function(cell) 4527{ 4528 var style = this.getCurrentCellStyle(cell); 4529 4530 return (style['movableLabel'] != null) ? style['movableLabel'] != '0' : 4531 mxGraph.prototype.isLabelMovable.apply(this, arguments); 4532}; 4533 4534/** 4535 * Function: selectAll 4536 * 4537 * Selects all children of the given parent cell or the children of the 4538 * default parent if no parent is specified. To select leaf vertices and/or 4539 * edges use <selectCells>. 4540 * 4541 * Parameters: 4542 * 4543 * parent - Optional <mxCell> whose children should be selected. 4544 * Default is <defaultParent>. 4545 */ 4546Graph.prototype.selectAll = function(parent) 4547{ 4548 parent = parent || this.getDefaultParent(); 4549 4550 if (!this.isCellLocked(parent)) 4551 { 4552 mxGraph.prototype.selectAll.apply(this, arguments); 4553 } 4554}; 4555 4556/** 4557 * Function: selectCells 4558 * 4559 * Selects all vertices and/or edges depending on the given boolean 4560 * arguments recursively, starting at the given parent or the default 4561 * parent if no parent is specified. Use <selectAll> to select all cells. 4562 * For vertices, only cells with no children are selected. 4563 * 4564 * Parameters: 4565 * 4566 * vertices - Boolean indicating if vertices should be selected. 4567 * edges - Boolean indicating if edges should be selected. 4568 * parent - Optional <mxCell> that acts as the root of the recursion. 4569 * Default is <defaultParent>. 4570 */ 4571Graph.prototype.selectCells = function(vertices, edges, parent) 4572{ 4573 parent = parent || this.getDefaultParent(); 4574 4575 if (!this.isCellLocked(parent)) 4576 { 4577 mxGraph.prototype.selectCells.apply(this, arguments); 4578 } 4579}; 4580 4581/** 4582 * Function: getSwimlaneAt 4583 * 4584 * Returns the bottom-most swimlane that intersects the given point (x, y) 4585 * in the cell hierarchy that starts at the given parent. 4586 * 4587 * Parameters: 4588 * 4589 * x - X-coordinate of the location to be checked. 4590 * y - Y-coordinate of the location to be checked. 4591 * parent - <mxCell> that should be used as the root of the recursion. 4592 * Default is <defaultParent>. 4593 */ 4594Graph.prototype.getSwimlaneAt = function (x, y, parent) 4595{ 4596 var result = mxGraph.prototype.getSwimlaneAt.apply(this, arguments); 4597 4598 if (this.isCellLocked(result)) 4599 { 4600 result = null; 4601 } 4602 4603 return result; 4604}; 4605 4606/** 4607 * Disables folding for non-swimlanes. 4608 */ 4609Graph.prototype.isCellFoldable = function(cell) 4610{ 4611 var style = this.getCurrentCellStyle(cell); 4612 4613 return this.foldingEnabled && mxUtils.getValue(style, 4614 mxConstants.STYLE_RESIZABLE, '1') != '0' && 4615 (style['treeFolding'] == '1' || 4616 (!this.isCellLocked(cell) && 4617 ((this.isContainer(cell) && style['collapsible'] != '0') || 4618 (!this.isContainer(cell) && style['collapsible'] == '1')))); 4619}; 4620 4621/** 4622 * Stops all interactions and clears the selection. 4623 */ 4624Graph.prototype.reset = function() 4625{ 4626 if (this.isEditing()) 4627 { 4628 this.stopEditing(true); 4629 } 4630 4631 this.escape(); 4632 4633 if (!this.isSelectionEmpty()) 4634 { 4635 this.clearSelection(); 4636 } 4637}; 4638 4639/** 4640 * Overridden to limit zoom to 1% - 16.000%. 4641 */ 4642Graph.prototype.zoom = function(factor, center) 4643{ 4644 factor = Math.max(0.01, Math.min(this.view.scale * factor, 160)) / this.view.scale; 4645 4646 mxGraph.prototype.zoom.apply(this, arguments); 4647}; 4648 4649/** 4650 * Function: zoomIn 4651 * 4652 * Zooms into the graph by <zoomFactor>. 4653 */ 4654Graph.prototype.zoomIn = function() 4655{ 4656 // Switches to 1% zoom steps below 15% 4657 if (this.view.scale < 0.15) 4658 { 4659 this.zoom((this.view.scale + 0.01) / this.view.scale); 4660 } 4661 else 4662 { 4663 // Uses to 5% zoom steps for better grid rendering in webkit 4664 // and to avoid rounding errors for zoom steps 4665 this.zoom((Math.round(this.view.scale * this.zoomFactor * 20) / 20) / this.view.scale); 4666 } 4667}; 4668 4669/** 4670 * Function: zoomOut 4671 * 4672 * Zooms out of the graph by <zoomFactor>. 4673 */ 4674Graph.prototype.zoomOut = function() 4675{ 4676 // Switches to 1% zoom steps below 15% 4677 if (this.view.scale <= 0.15) 4678 { 4679 this.zoom((this.view.scale - 0.01) / this.view.scale); 4680 } 4681 else 4682 { 4683 // Uses to 5% zoom steps for better grid rendering in webkit 4684 // and to avoid rounding errors for zoom steps 4685 this.zoom((Math.round(this.view.scale * (1 / this.zoomFactor) * 20) / 20) / this.view.scale); 4686 } 4687}; 4688 4689/** 4690 * Function: fitWindow 4691 * 4692 * Sets the current visible rectangle of the window in graph coordinates. 4693 */ 4694Graph.prototype.fitWindow = function(bounds, border) 4695{ 4696 border = (border != null) ? border : 10; 4697 4698 var cw = this.container.clientWidth - border; 4699 var ch = this.container.clientHeight - border; 4700 var scale = Math.floor(20 * Math.min(cw / bounds.width, ch / bounds.height)) / 20; 4701 this.zoomTo(scale); 4702 4703 if (mxUtils.hasScrollbars(this.container)) 4704 { 4705 var t = this.view.translate; 4706 this.container.scrollTop = (bounds.y + t.y) * scale - 4707 Math.max((ch - bounds.height * scale) / 2 + border / 2, 0); 4708 this.container.scrollLeft = (bounds.x + t.x) * scale - 4709 Math.max((cw - bounds.width * scale) / 2 + border / 2, 0); 4710 } 4711}; 4712 4713/** 4714 * Overrides tooltips to show custom tooltip or metadata. 4715 */ 4716Graph.prototype.getTooltipForCell = function(cell) 4717{ 4718 var tip = ''; 4719 4720 if (mxUtils.isNode(cell.value)) 4721 { 4722 var tmp = null; 4723 4724 if (Graph.translateDiagram && Graph.diagramLanguage != null) 4725 { 4726 tmp = cell.value.getAttribute('tooltip_' + Graph.diagramLanguage); 4727 } 4728 4729 if (tmp == null) 4730 { 4731 tmp = cell.value.getAttribute('tooltip'); 4732 } 4733 4734 if (tmp != null) 4735 { 4736 if (tmp != null && this.isReplacePlaceholders(cell)) 4737 { 4738 tmp = this.replacePlaceholders(cell, tmp); 4739 } 4740 4741 tip = this.sanitizeHtml(tmp); 4742 } 4743 else 4744 { 4745 var ignored = this.builtInProperties; 4746 var attrs = cell.value.attributes; 4747 var temp = []; 4748 4749 // Hides links in edit mode 4750 if (this.isEnabled()) 4751 { 4752 ignored.push('linkTarget'); 4753 ignored.push('link'); 4754 } 4755 4756 for (var i = 0; i < attrs.length; i++) 4757 { 4758 if (mxUtils.indexOf(ignored, attrs[i].nodeName) < 0 && attrs[i].nodeValue.length > 0) 4759 { 4760 temp.push({name: attrs[i].nodeName, value: attrs[i].nodeValue}); 4761 } 4762 } 4763 4764 // Sorts by name 4765 temp.sort(function(a, b) 4766 { 4767 if (a.name < b.name) 4768 { 4769 return -1; 4770 } 4771 else if (a.name > b.name) 4772 { 4773 return 1; 4774 } 4775 else 4776 { 4777 return 0; 4778 } 4779 }); 4780 4781 for (var i = 0; i < temp.length; i++) 4782 { 4783 if (temp[i].name != 'link' || !this.isCustomLink(temp[i].value)) 4784 { 4785 tip += ((temp[i].name != 'link') ? '<b>' + temp[i].name + ':</b> ' : '') + 4786 mxUtils.htmlEntities(temp[i].value) + '\n'; 4787 } 4788 } 4789 4790 if (tip.length > 0) 4791 { 4792 tip = tip.substring(0, tip.length - 1); 4793 4794 if (mxClient.IS_SVG) 4795 { 4796 tip = '<div style="max-width:360px;text-overflow:ellipsis;overflow:hidden;">' + 4797 tip + '</div>'; 4798 } 4799 } 4800 } 4801 } 4802 4803 return tip; 4804}; 4805 4806/** 4807 * Adds rack child layout style. 4808 */ 4809Graph.prototype.getFlowAnimationStyle = function() 4810{ 4811 var head = document.getElementsByTagName('head')[0]; 4812 4813 if (head != null && this.flowAnimationStyle == null) 4814 { 4815 this.flowAnimationStyle = document.createElement('style') 4816 this.flowAnimationStyle.setAttribute('id', 4817 'geEditorFlowAnimation-' + Editor.guid()); 4818 this.flowAnimationStyle.type = 'text/css'; 4819 var id = this.flowAnimationStyle.getAttribute('id'); 4820 this.flowAnimationStyle.innerHTML = this.getFlowAnimationStyleCss(id); 4821 4822 head.appendChild(this.flowAnimationStyle); 4823 } 4824 4825 return this.flowAnimationStyle; 4826}; 4827 4828/** 4829 * Adds rack child layout style. 4830 */ 4831Graph.prototype.getFlowAnimationStyleCss = function(id) 4832{ 4833 return '.' + id + ' {\n' + 4834 'animation: ' + id + ' 0.5s linear;\n' + 4835 'animation-iteration-count: infinite;\n' + 4836 '}\n' + 4837 '@keyframes ' + id + ' {\n' + 4838 'to {\n' + 4839 'stroke-dashoffset: ' + (this.view.scale * -16) + ';\n' + 4840 '}\n' + 4841 '}'; 4842}; 4843 4844/** 4845 * Turns the given string into an array. 4846 */ 4847Graph.prototype.stringToBytes = function(str) 4848{ 4849 return Graph.stringToBytes(str); 4850}; 4851 4852/** 4853 * Turns the given array into a string. 4854 */ 4855Graph.prototype.bytesToString = function(arr) 4856{ 4857 return Graph.bytesToString(arr); 4858}; 4859 4860/** 4861 * Returns a base64 encoded version of the compressed outer XML of the given node. 4862 */ 4863Graph.prototype.compressNode = function(node) 4864{ 4865 return Graph.compressNode(node); 4866}; 4867 4868/** 4869 * Returns a base64 encoded version of the compressed string. 4870 */ 4871Graph.prototype.compress = function(data, deflate) 4872{ 4873 return Graph.compress(data, deflate); 4874}; 4875 4876/** 4877 * Returns a decompressed version of the base64 encoded string. 4878 */ 4879Graph.prototype.decompress = function(data, inflate) 4880{ 4881 return Graph.decompress(data, inflate); 4882}; 4883 4884/** 4885 * Redirects to Graph.zapGremlins. 4886 */ 4887Graph.prototype.zapGremlins = function(text) 4888{ 4889 return Graph.zapGremlins(text); 4890}; 4891 4892/** 4893 * Hover icons are used for hover, vertex handler and drag from sidebar. 4894 */ 4895HoverIcons = function(graph) 4896{ 4897 mxEventSource.call(this); 4898 this.graph = graph; 4899 this.init(); 4900}; 4901 4902// Extends mxEventSource 4903mxUtils.extend(HoverIcons, mxEventSource); 4904 4905/** 4906 * Up arrow. 4907 */ 4908HoverIcons.prototype.arrowSpacing = 2; 4909 4910/** 4911 * Delay to switch to another state for overlapping bbox. Default is 500ms. 4912 */ 4913HoverIcons.prototype.updateDelay = 500; 4914 4915/** 4916 * Delay to switch between states. Default is 140ms. 4917 */ 4918HoverIcons.prototype.activationDelay = 140; 4919 4920/** 4921 * Up arrow. 4922 */ 4923HoverIcons.prototype.currentState = null; 4924 4925/** 4926 * Up arrow. 4927 */ 4928HoverIcons.prototype.activeArrow = null; 4929 4930/** 4931 * Up arrow. 4932 */ 4933HoverIcons.prototype.inactiveOpacity = 15; 4934 4935/** 4936 * Up arrow. 4937 */ 4938HoverIcons.prototype.cssCursor = 'copy'; 4939 4940/** 4941 * Whether to hide arrows that collide with vertices. 4942 * LATER: Add keyboard override, touch support. 4943 */ 4944HoverIcons.prototype.checkCollisions = true; 4945 4946/** 4947 * Up arrow. 4948 */ 4949HoverIcons.prototype.arrowFill = '#29b6f2'; 4950 4951/** 4952 * Up arrow. 4953 */ 4954HoverIcons.prototype.triangleUp = (!mxClient.IS_SVG) ? new mxImage(IMAGE_PATH + '/triangle-up.png', 26, 14) : 4955 Graph.createSvgImage(18, 28, '<path d="m 6 26 L 12 26 L 12 12 L 18 12 L 9 1 L 1 12 L 6 12 z" ' + 4956 'stroke="#fff" fill="' + HoverIcons.prototype.arrowFill + '"/>'); 4957 4958/** 4959 * Right arrow. 4960 */ 4961HoverIcons.prototype.triangleRight = (!mxClient.IS_SVG) ? new mxImage(IMAGE_PATH + '/triangle-right.png', 14, 26) : 4962 Graph.createSvgImage(26, 18, '<path d="m 1 6 L 14 6 L 14 1 L 26 9 L 14 18 L 14 12 L 1 12 z" ' + 4963 'stroke="#fff" fill="' + HoverIcons.prototype.arrowFill + '"/>'); 4964 4965/** 4966 * Down arrow. 4967 */ 4968HoverIcons.prototype.triangleDown = (!mxClient.IS_SVG) ? new mxImage(IMAGE_PATH + '/triangle-down.png', 26, 14) : 4969 Graph.createSvgImage(18, 26, '<path d="m 6 1 L 6 14 L 1 14 L 9 26 L 18 14 L 12 14 L 12 1 z" ' + 4970 'stroke="#fff" fill="' + HoverIcons.prototype.arrowFill + '"/>'); 4971 4972/** 4973 * Left arrow. 4974 */ 4975HoverIcons.prototype.triangleLeft = (!mxClient.IS_SVG) ? new mxImage(IMAGE_PATH + '/triangle-left.png', 14, 26) : 4976 Graph.createSvgImage(28, 18, '<path d="m 1 9 L 12 1 L 12 6 L 26 6 L 26 12 L 12 12 L 12 18 z" ' + 4977 'stroke="#fff" fill="' + HoverIcons.prototype.arrowFill + '"/>'); 4978 4979/** 4980 * Round target. 4981 */ 4982HoverIcons.prototype.roundDrop = (!mxClient.IS_SVG) ? new mxImage(IMAGE_PATH + '/round-drop.png', 26, 26) : 4983 Graph.createSvgImage(26, 26, '<circle cx="13" cy="13" r="12" ' + 4984 'stroke="#fff" fill="' + HoverIcons.prototype.arrowFill + '"/>'); 4985 4986/** 4987 * Refresh target. 4988 */ 4989HoverIcons.prototype.refreshTarget = new mxImage((mxClient.IS_SVG) ? 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjM2cHgiIGhlaWdodD0iMzZweCI+PGVsbGlwc2UgZmlsbD0iIzI5YjZmMiIgY3g9IjEyIiBjeT0iMTIiIHJ4PSIxMiIgcnk9IjEyIi8+PHBhdGggdHJhbnNmb3JtPSJzY2FsZSgwLjgpIHRyYW5zbGF0ZSgyLjQsIDIuNCkiIHN0cm9rZT0iI2ZmZiIgZmlsbD0iI2ZmZiIgZD0iTTEyIDZ2M2w0LTQtNC00djNjLTQuNDIgMC04IDMuNTgtOCA4IDAgMS41Ny40NiAzLjAzIDEuMjQgNC4yNkw2LjcgMTQuOGMtLjQ1LS44My0uNy0xLjc5LS43LTIuOCAwLTMuMzEgMi42OS02IDYtNnptNi43NiAxLjc0TDE3LjMgOS4yYy40NC44NC43IDEuNzkuNyAyLjggMCAzLjMxLTIuNjkgNi02IDZ2LTNsLTQgNCA0IDR2LTNjNC40MiAwIDgtMy41OCA4LTggMC0xLjU3LS40Ni0zLjAzLTEuMjQtNC4yNnoiLz48cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIi8+PC9zdmc+Cg==' : 4990 IMAGE_PATH + '/refresh.png', 38, 38); 4991 4992/** 4993 * Tolerance for hover icon clicks. 4994 */ 4995HoverIcons.prototype.tolerance = (mxClient.IS_TOUCH) ? 6 : 0; 4996 4997/** 4998 * 4999 */ 5000HoverIcons.prototype.init = function() 5001{ 5002 this.arrowUp = this.createArrow(this.triangleUp, mxResources.get('plusTooltip'), mxConstants.DIRECTION_NORTH); 5003 this.arrowRight = this.createArrow(this.triangleRight, mxResources.get('plusTooltip'), mxConstants.DIRECTION_EAST); 5004 this.arrowDown = this.createArrow(this.triangleDown, mxResources.get('plusTooltip'), mxConstants.DIRECTION_SOUTH); 5005 this.arrowLeft = this.createArrow(this.triangleLeft, mxResources.get('plusTooltip'), mxConstants.DIRECTION_WEST); 5006 5007 this.elts = [this.arrowUp, this.arrowRight, this.arrowDown, this.arrowLeft]; 5008 5009 this.resetHandler = mxUtils.bind(this, function() 5010 { 5011 this.reset(); 5012 }); 5013 5014 this.repaintHandler = mxUtils.bind(this, function() 5015 { 5016 this.repaint(); 5017 }); 5018 5019 this.graph.selectionModel.addListener(mxEvent.CHANGE, this.resetHandler); 5020 this.graph.model.addListener(mxEvent.CHANGE, this.repaintHandler); 5021 this.graph.view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.repaintHandler); 5022 this.graph.view.addListener(mxEvent.TRANSLATE, this.repaintHandler); 5023 this.graph.view.addListener(mxEvent.SCALE, this.repaintHandler); 5024 this.graph.view.addListener(mxEvent.DOWN, this.repaintHandler); 5025 this.graph.view.addListener(mxEvent.UP, this.repaintHandler); 5026 this.graph.addListener(mxEvent.ROOT, this.repaintHandler); 5027 this.graph.addListener(mxEvent.ESCAPE, this.resetHandler); 5028 mxEvent.addListener(this.graph.container, 'scroll', this.resetHandler); 5029 5030 // Resets the mouse point on escape 5031 this.graph.addListener(mxEvent.ESCAPE, mxUtils.bind(this, function() 5032 { 5033 this.mouseDownPoint = null; 5034 })); 5035 5036 // Removes hover icons if mouse leaves the container 5037 mxEvent.addListener(this.graph.container, 'mouseleave', mxUtils.bind(this, function(evt) 5038 { 5039 // Workaround for IE11 firing mouseleave for touch in diagram 5040 if (evt.relatedTarget != null && mxEvent.getSource(evt) == this.graph.container) 5041 { 5042 this.setDisplay('none'); 5043 } 5044 })); 5045 5046 // Resets current state when in-place editor starts 5047 this.graph.addListener(mxEvent.START_EDITING, mxUtils.bind(this, function(evt) 5048 { 5049 this.reset(); 5050 })); 5051 5052 // Resets current state after update of selection state for touch events 5053 var graphClick = this.graph.click; 5054 this.graph.click = mxUtils.bind(this, function(me) 5055 { 5056 graphClick.apply(this.graph, arguments); 5057 5058 if (this.currentState != null && !this.graph.isCellSelected(this.currentState.cell) && 5059 mxEvent.isTouchEvent(me.getEvent()) && !this.graph.model.isVertex(me.getCell())) 5060 { 5061 this.reset(); 5062 } 5063 }); 5064 5065 // Checks if connection handler was active in mouse move 5066 // as workaround for possible double connection inserted 5067 var connectionHandlerActive = false; 5068 5069 // Implements a listener for hover and click handling 5070 this.graph.addMouseListener( 5071 { 5072 mouseDown: mxUtils.bind(this, function(sender, me) 5073 { 5074 connectionHandlerActive = false; 5075 var evt = me.getEvent(); 5076 5077 if (this.isResetEvent(evt)) 5078 { 5079 this.reset(); 5080 } 5081 else if (!this.isActive()) 5082 { 5083 var state = this.getState(me.getState()); 5084 5085 if (state != null || !mxEvent.isTouchEvent(evt)) 5086 { 5087 this.update(state); 5088 } 5089 } 5090 5091 this.setDisplay('none'); 5092 }), 5093 mouseMove: mxUtils.bind(this, function(sender, me) 5094 { 5095 var evt = me.getEvent(); 5096 5097 if (this.isResetEvent(evt)) 5098 { 5099 this.reset(); 5100 } 5101 else if (!this.graph.isMouseDown && !mxEvent.isTouchEvent(evt)) 5102 { 5103 this.update(this.getState(me.getState()), 5104 me.getGraphX(), me.getGraphY()); 5105 } 5106 5107 if (this.graph.connectionHandler != null && 5108 this.graph.connectionHandler.shape != null) 5109 { 5110 connectionHandlerActive = true; 5111 } 5112 }), 5113 mouseUp: mxUtils.bind(this, function(sender, me) 5114 { 5115 var evt = me.getEvent(); 5116 var pt = mxUtils.convertPoint(this.graph.container, 5117 mxEvent.getClientX(evt), mxEvent.getClientY(evt)) 5118 5119 if (this.isResetEvent(evt)) 5120 { 5121 this.reset(); 5122 } 5123 else if (this.isActive() && !connectionHandlerActive && 5124 this.mouseDownPoint != null) 5125 { 5126 this.click(this.currentState, this.getDirection(), me); 5127 } 5128 else if (this.isActive()) 5129 { 5130 // Selects target vertex after drag and clone if not only new edge was inserted 5131 if (this.graph.getSelectionCount() != 1 || !this.graph.model.isEdge( 5132 this.graph.getSelectionCell())) 5133 { 5134 this.update(this.getState(this.graph.view.getState( 5135 this.graph.getCellAt(me.getGraphX(), me.getGraphY())))); 5136 } 5137 else 5138 { 5139 this.reset(); 5140 } 5141 } 5142 else if (mxEvent.isTouchEvent(evt) || (this.bbox != null && 5143 mxUtils.contains(this.bbox, me.getGraphX(), me.getGraphY()))) 5144 { 5145 // Shows existing hover icons if inside bounding box 5146 this.setDisplay(''); 5147 this.repaint(); 5148 } 5149 else if (!mxEvent.isTouchEvent(evt)) 5150 { 5151 this.reset(); 5152 } 5153 5154 connectionHandlerActive = false; 5155 this.resetActiveArrow(); 5156 }) 5157 }); 5158}; 5159 5160/** 5161 * 5162 */ 5163HoverIcons.prototype.isResetEvent = function(evt, allowShift) 5164{ 5165 return mxEvent.isAltDown(evt) || (this.activeArrow == null && mxEvent.isShiftDown(evt)) || 5166 (mxEvent.isPopupTrigger(evt) && !this.graph.isCloneEvent(evt)); 5167}; 5168 5169/** 5170 * 5171 */ 5172HoverIcons.prototype.createArrow = function(img, tooltip, direction) 5173{ 5174 var arrow = null; 5175 arrow = mxUtils.createImage(img.src); 5176 arrow.style.width = img.width + 'px'; 5177 arrow.style.height = img.height + 'px'; 5178 arrow.style.padding = this.tolerance + 'px'; 5179 5180 if (tooltip != null) 5181 { 5182 arrow.setAttribute('title', tooltip); 5183 } 5184 5185 arrow.style.position = 'absolute'; 5186 arrow.style.cursor = this.cssCursor; 5187 5188 mxEvent.addGestureListeners(arrow, mxUtils.bind(this, function(evt) 5189 { 5190 if (this.currentState != null && !this.isResetEvent(evt)) 5191 { 5192 this.mouseDownPoint = mxUtils.convertPoint(this.graph.container, 5193 mxEvent.getClientX(evt), mxEvent.getClientY(evt)); 5194 this.drag(evt, this.mouseDownPoint.x, this.mouseDownPoint.y); 5195 this.activeArrow = arrow; 5196 this.setDisplay('none'); 5197 mxEvent.consume(evt); 5198 } 5199 })); 5200 5201 // Captures mouse events as events on graph 5202 mxEvent.redirectMouseEvents(arrow, this.graph, this.currentState); 5203 5204 mxEvent.addListener(arrow, 'mouseenter', mxUtils.bind(this, function(evt) 5205 { 5206 // Workaround for Firefox firing mouseenter on touchend 5207 if (mxEvent.isMouseEvent(evt)) 5208 { 5209 if (this.activeArrow != null && this.activeArrow != arrow) 5210 { 5211 mxUtils.setOpacity(this.activeArrow, this.inactiveOpacity); 5212 } 5213 5214 this.graph.connectionHandler.constraintHandler.reset(); 5215 mxUtils.setOpacity(arrow, 100); 5216 this.activeArrow = arrow; 5217 5218 this.fireEvent(new mxEventObject('focus', 'arrow', arrow, 5219 'direction', direction, 'event', evt)); 5220 } 5221 })); 5222 5223 mxEvent.addListener(arrow, 'mouseleave', mxUtils.bind(this, function(evt) 5224 { 5225 if (mxEvent.isMouseEvent(evt)) 5226 { 5227 this.fireEvent(new mxEventObject('blur', 'arrow', arrow, 5228 'direction', direction, 'event', evt)); 5229 } 5230 5231 // Workaround for IE11 firing this event on touch 5232 if (!this.graph.isMouseDown) 5233 { 5234 this.resetActiveArrow(); 5235 } 5236 })); 5237 5238 return arrow; 5239}; 5240 5241/** 5242 * 5243 */ 5244HoverIcons.prototype.resetActiveArrow = function() 5245{ 5246 if (this.activeArrow != null) 5247 { 5248 mxUtils.setOpacity(this.activeArrow, this.inactiveOpacity); 5249 this.activeArrow = null; 5250 } 5251}; 5252 5253/** 5254 * 5255 */ 5256HoverIcons.prototype.getDirection = function() 5257{ 5258 var dir = mxConstants.DIRECTION_EAST; 5259 5260 if (this.activeArrow == this.arrowUp) 5261 { 5262 dir = mxConstants.DIRECTION_NORTH; 5263 } 5264 else if (this.activeArrow == this.arrowDown) 5265 { 5266 dir = mxConstants.DIRECTION_SOUTH; 5267 } 5268 else if (this.activeArrow == this.arrowLeft) 5269 { 5270 dir = mxConstants.DIRECTION_WEST; 5271 } 5272 5273 return dir; 5274}; 5275 5276/** 5277 * 5278 */ 5279HoverIcons.prototype.visitNodes = function(visitor) 5280{ 5281 for (var i = 0; i < this.elts.length; i++) 5282 { 5283 if (this.elts[i] != null) 5284 { 5285 visitor(this.elts[i]); 5286 } 5287 } 5288}; 5289 5290/** 5291 * 5292 */ 5293HoverIcons.prototype.removeNodes = function() 5294{ 5295 this.visitNodes(function(elt) 5296 { 5297 if (elt.parentNode != null) 5298 { 5299 elt.parentNode.removeChild(elt); 5300 } 5301 }); 5302}; 5303 5304/** 5305 * 5306 */ 5307HoverIcons.prototype.setDisplay = function(display) 5308{ 5309 this.visitNodes(function(elt) 5310 { 5311 elt.style.display = display; 5312 }); 5313}; 5314 5315/** 5316 * 5317 */ 5318HoverIcons.prototype.isActive = function() 5319{ 5320 return this.activeArrow != null && this.currentState != null; 5321}; 5322 5323/** 5324 * 5325 */ 5326HoverIcons.prototype.drag = function(evt, x, y) 5327{ 5328 this.graph.popupMenuHandler.hideMenu(); 5329 this.graph.stopEditing(false); 5330 5331 // Checks if state was removed in call to stopEditing above 5332 if (this.currentState != null) 5333 { 5334 this.graph.connectionHandler.start(this.currentState, x, y); 5335 this.graph.isMouseTrigger = mxEvent.isMouseEvent(evt); 5336 this.graph.isMouseDown = true; 5337 5338 // Hides handles for selection cell 5339 var handler = this.graph.selectionCellsHandler.getHandler(this.currentState.cell); 5340 5341 if (handler != null) 5342 { 5343 handler.setHandlesVisible(false); 5344 } 5345 5346 // Ctrl+shift drag sets source constraint 5347 var es = this.graph.connectionHandler.edgeState; 5348 5349 if (evt != null && mxEvent.isShiftDown(evt) && mxEvent.isControlDown(evt) && es != null && 5350 mxUtils.getValue(es.style, mxConstants.STYLE_EDGE, null) === 'orthogonalEdgeStyle') 5351 { 5352 var direction = this.getDirection(); 5353 es.cell.style = mxUtils.setStyle(es.cell.style, 'sourcePortConstraint', direction); 5354 es.style['sourcePortConstraint'] = direction; 5355 } 5356 } 5357}; 5358 5359/** 5360 * 5361 */ 5362HoverIcons.prototype.getStateAt = function(state, x, y) 5363{ 5364 return this.graph.view.getState(this.graph.getCellAt(x, y)); 5365}; 5366 5367/** 5368 * 5369 */ 5370HoverIcons.prototype.click = function(state, dir, me) 5371{ 5372 var evt = me.getEvent(); 5373 var x = me.getGraphX(); 5374 var y = me.getGraphY(); 5375 5376 var tmp = this.getStateAt(state, x, y); 5377 5378 if (tmp != null && this.graph.model.isEdge(tmp.cell) && !this.graph.isCloneEvent(evt) && 5379 (tmp.getVisibleTerminalState(true) == state || tmp.getVisibleTerminalState(false) == state)) 5380 { 5381 this.graph.setSelectionCell(tmp.cell); 5382 this.reset(); 5383 } 5384 else if (state != null) 5385 { 5386 this.execute(state, dir, me); 5387 } 5388 5389 me.consume(); 5390}; 5391 5392/** 5393 * 5394 */ 5395HoverIcons.prototype.execute = function(state, dir, me) 5396{ 5397 var evt = me.getEvent(); 5398 5399 this.graph.selectCellsForConnectVertex(this.graph.connectVertex( 5400 state.cell, dir, this.graph.defaultEdgeLength, evt, this.graph.isCloneEvent(evt), 5401 this.graph.isCloneEvent(evt)), evt, this); 5402}; 5403 5404/** 5405 * 5406 */ 5407HoverIcons.prototype.reset = function(clearTimeout) 5408{ 5409 clearTimeout = (clearTimeout == null) ? true : clearTimeout; 5410 5411 if (clearTimeout && this.updateThread != null) 5412 { 5413 window.clearTimeout(this.updateThread); 5414 } 5415 5416 this.mouseDownPoint = null; 5417 this.currentState = null; 5418 this.activeArrow = null; 5419 this.removeNodes(); 5420 this.bbox = null; 5421 5422 this.fireEvent(new mxEventObject('reset')); 5423}; 5424 5425/** 5426 * 5427 */ 5428HoverIcons.prototype.repaint = function() 5429{ 5430 this.bbox = null; 5431 5432 if (this.currentState != null) 5433 { 5434 // Checks if cell was deleted 5435 this.currentState = this.getState(this.currentState); 5436 5437 // Cell was deleted 5438 if (this.currentState != null && 5439 this.graph.model.isVertex(this.currentState.cell) && 5440 this.graph.isCellConnectable(this.currentState.cell)) 5441 { 5442 var bds = mxRectangle.fromRectangle(this.currentState); 5443 5444 // Uses outer bounding box to take rotation into account 5445 if (this.currentState.shape != null && this.currentState.shape.boundingBox != null) 5446 { 5447 bds = mxRectangle.fromRectangle(this.currentState.shape.boundingBox); 5448 } 5449 5450 bds.grow(this.graph.tolerance); 5451 bds.grow(this.arrowSpacing); 5452 5453 var handler = this.graph.selectionCellsHandler.getHandler(this.currentState.cell); 5454 5455 if (this.graph.isTableRow(this.currentState.cell)) 5456 { 5457 handler = this.graph.selectionCellsHandler.getHandler( 5458 this.graph.model.getParent(this.currentState.cell)); 5459 } 5460 5461 var rotationBbox = null; 5462 5463 if (handler != null) 5464 { 5465 bds.x -= handler.horizontalOffset / 2; 5466 bds.y -= handler.verticalOffset / 2; 5467 bds.width += handler.horizontalOffset; 5468 bds.height += handler.verticalOffset; 5469 5470 // Adds bounding box of rotation handle to avoid overlap 5471 if (handler.rotationShape != null && handler.rotationShape.node != null && 5472 handler.rotationShape.node.style.visibility != 'hidden' && 5473 handler.rotationShape.node.style.display != 'none' && 5474 handler.rotationShape.boundingBox != null) 5475 { 5476 rotationBbox = handler.rotationShape.boundingBox; 5477 } 5478 } 5479 5480 // Positions arrows avoid collisions with rotation handle 5481 var positionArrow = mxUtils.bind(this, function(arrow, x, y) 5482 { 5483 if (rotationBbox != null) 5484 { 5485 var bbox = new mxRectangle(x, y, arrow.clientWidth, arrow.clientHeight); 5486 5487 if (mxUtils.intersects(bbox, rotationBbox)) 5488 { 5489 if (arrow == this.arrowUp) 5490 { 5491 y -= bbox.y + bbox.height - rotationBbox.y; 5492 } 5493 else if (arrow == this.arrowRight) 5494 { 5495 x += rotationBbox.x + rotationBbox.width - bbox.x; 5496 } 5497 else if (arrow == this.arrowDown) 5498 { 5499 y += rotationBbox.y + rotationBbox.height - bbox.y; 5500 } 5501 else if (arrow == this.arrowLeft) 5502 { 5503 x -= bbox.x + bbox.width - rotationBbox.x; 5504 } 5505 } 5506 } 5507 5508 arrow.style.left = x + 'px'; 5509 arrow.style.top = y + 'px'; 5510 mxUtils.setOpacity(arrow, this.inactiveOpacity); 5511 }); 5512 5513 positionArrow(this.arrowUp, 5514 Math.round(this.currentState.getCenterX() - this.triangleUp.width / 2 - this.tolerance), 5515 Math.round(bds.y - this.triangleUp.height - this.tolerance)); 5516 5517 positionArrow(this.arrowRight, Math.round(bds.x + bds.width - this.tolerance), 5518 Math.round(this.currentState.getCenterY() - this.triangleRight.height / 2 - this.tolerance)); 5519 5520 positionArrow(this.arrowDown, parseInt(this.arrowUp.style.left), 5521 Math.round(bds.y + bds.height - this.tolerance)); 5522 5523 positionArrow(this.arrowLeft, Math.round(bds.x - this.triangleLeft.width - this.tolerance), 5524 parseInt(this.arrowRight.style.top)); 5525 5526 if (this.checkCollisions) 5527 { 5528 var right = this.graph.getCellAt(bds.x + bds.width + 5529 this.triangleRight.width / 2, this.currentState.getCenterY()); 5530 var left = this.graph.getCellAt(bds.x - this.triangleLeft.width / 2, this.currentState.getCenterY()); 5531 var top = this.graph.getCellAt(this.currentState.getCenterX(), bds.y - this.triangleUp.height / 2); 5532 var bottom = this.graph.getCellAt(this.currentState.getCenterX(), bds.y + bds.height + this.triangleDown.height / 2); 5533 5534 // Shows hover icons large cell is behind all directions of current cell 5535 if (right != null && right == left && left == top && top == bottom) 5536 { 5537 right = null; 5538 left = null; 5539 top = null; 5540 bottom = null; 5541 } 5542 5543 var currentGeo = this.graph.getCellGeometry(this.currentState.cell); 5544 5545 var checkCollision = mxUtils.bind(this, function(cell, arrow) 5546 { 5547 var geo = this.graph.model.isVertex(cell) && this.graph.getCellGeometry(cell); 5548 5549 // Ignores collision if vertex is more than 3 times the size of this vertex 5550 if (cell != null && !this.graph.model.isAncestor(cell, this.currentState.cell) && 5551 !this.graph.isSwimlane(cell) && (geo == null || currentGeo == null || 5552 (geo.height < 3 * currentGeo.height && geo.width < 3 * currentGeo.width))) 5553 { 5554 arrow.style.visibility = 'hidden'; 5555 } 5556 else 5557 { 5558 arrow.style.visibility = 'visible'; 5559 } 5560 }); 5561 5562 checkCollision(right, this.arrowRight); 5563 checkCollision(left, this.arrowLeft); 5564 checkCollision(top, this.arrowUp); 5565 checkCollision(bottom, this.arrowDown); 5566 } 5567 else 5568 { 5569 this.arrowLeft.style.visibility = 'visible'; 5570 this.arrowRight.style.visibility = 'visible'; 5571 this.arrowUp.style.visibility = 'visible'; 5572 this.arrowDown.style.visibility = 'visible'; 5573 } 5574 5575 if (this.graph.tooltipHandler.isEnabled()) 5576 { 5577 this.arrowLeft.setAttribute('title', mxResources.get('plusTooltip')); 5578 this.arrowRight.setAttribute('title', mxResources.get('plusTooltip')); 5579 this.arrowUp.setAttribute('title', mxResources.get('plusTooltip')); 5580 this.arrowDown.setAttribute('title', mxResources.get('plusTooltip')); 5581 } 5582 else 5583 { 5584 this.arrowLeft.removeAttribute('title'); 5585 this.arrowRight.removeAttribute('title'); 5586 this.arrowUp.removeAttribute('title'); 5587 this.arrowDown.removeAttribute('title'); 5588 } 5589 } 5590 else 5591 { 5592 this.reset(); 5593 } 5594 5595 // Updates bounding box 5596 if (this.currentState != null) 5597 { 5598 this.bbox = this.computeBoundingBox(); 5599 5600 // Adds tolerance for hover 5601 if (this.bbox != null) 5602 { 5603 this.bbox.grow(10); 5604 } 5605 } 5606 } 5607}; 5608 5609/** 5610 * 5611 */ 5612HoverIcons.prototype.computeBoundingBox = function() 5613{ 5614 var bbox = (!this.graph.model.isEdge(this.currentState.cell)) ? mxRectangle.fromRectangle(this.currentState) : null; 5615 5616 this.visitNodes(function(elt) 5617 { 5618 if (elt.parentNode != null) 5619 { 5620 var tmp = new mxRectangle(elt.offsetLeft, elt.offsetTop, elt.offsetWidth, elt.offsetHeight); 5621 5622 if (bbox == null) 5623 { 5624 bbox = tmp; 5625 } 5626 else 5627 { 5628 bbox.add(tmp); 5629 } 5630 } 5631 }); 5632 5633 return bbox; 5634}; 5635 5636/** 5637 * 5638 */ 5639HoverIcons.prototype.getState = function(state) 5640{ 5641 if (state != null) 5642 { 5643 var cell = state.cell; 5644 5645 if (!this.graph.getModel().contains(cell)) 5646 { 5647 state = null; 5648 } 5649 else 5650 { 5651 // Uses connectable parent vertex if child is not connectable 5652 if (this.graph.getModel().isVertex(cell) && !this.graph.isCellConnectable(cell)) 5653 { 5654 var parent = this.graph.getModel().getParent(cell); 5655 5656 if (this.graph.getModel().isVertex(parent) && this.graph.isCellConnectable(parent)) 5657 { 5658 cell = parent; 5659 } 5660 } 5661 5662 // Ignores locked cells and edges 5663 if (this.graph.isCellLocked(cell) || this.graph.model.isEdge(cell)) 5664 { 5665 cell = null; 5666 } 5667 5668 state = this.graph.view.getState(cell); 5669 5670 if (state != null && state.style == null) 5671 { 5672 state = null; 5673 } 5674 } 5675 } 5676 5677 return state; 5678}; 5679 5680/** 5681 * 5682 */ 5683HoverIcons.prototype.update = function(state, x, y) 5684{ 5685 if (!this.graph.connectionArrowsEnabled || (state != null && 5686 mxUtils.getValue(state.style, 'allowArrows', '1') == '0')) 5687 { 5688 this.reset(); 5689 } 5690 else 5691 { 5692 if (state != null && state.cell.geometry != null && state.cell.geometry.relative && 5693 this.graph.model.isEdge(state.cell.parent)) 5694 { 5695 state = null; 5696 } 5697 5698 var timeOnTarget = null; 5699 5700 // Time on target 5701 if (this.prev != state || this.isActive()) 5702 { 5703 this.startTime = new Date().getTime(); 5704 this.prev = state; 5705 timeOnTarget = 0; 5706 5707 if (this.updateThread != null) 5708 { 5709 window.clearTimeout(this.updateThread); 5710 } 5711 5712 if (state != null) 5713 { 5714 // Starts timer to update current state with no mouse events 5715 this.updateThread = window.setTimeout(mxUtils.bind(this, function() 5716 { 5717 if (!this.isActive() && !this.graph.isMouseDown && 5718 !this.graph.panningHandler.isActive()) 5719 { 5720 this.prev = state; 5721 this.update(state, x, y); 5722 } 5723 }), this.updateDelay + 10); 5724 } 5725 } 5726 else if (this.startTime != null) 5727 { 5728 timeOnTarget = new Date().getTime() - this.startTime; 5729 } 5730 5731 this.setDisplay(''); 5732 5733 if (this.currentState != null && this.currentState != state && timeOnTarget < this.activationDelay && 5734 this.bbox != null && !mxUtils.contains(this.bbox, x, y)) 5735 { 5736 this.reset(false); 5737 } 5738 else if (this.currentState != null || timeOnTarget > this.activationDelay) 5739 { 5740 if (this.currentState != state && ((timeOnTarget > this.updateDelay && state != null) || 5741 this.bbox == null || x == null || y == null || !mxUtils.contains(this.bbox, x, y))) 5742 { 5743 if (state != null && this.graph.isEnabled()) 5744 { 5745 this.removeNodes(); 5746 this.setCurrentState(state); 5747 this.repaint(); 5748 5749 // Resets connection points on other focused cells 5750 if (this.graph.connectionHandler.constraintHandler.currentFocus != state) 5751 { 5752 this.graph.connectionHandler.constraintHandler.reset(); 5753 } 5754 } 5755 else 5756 { 5757 this.reset(); 5758 } 5759 } 5760 } 5761 } 5762}; 5763 5764/** 5765 * 5766 */ 5767HoverIcons.prototype.setCurrentState = function(state) 5768{ 5769 if (state.style['portConstraint'] != 'eastwest') 5770 { 5771 this.graph.container.appendChild(this.arrowUp); 5772 this.graph.container.appendChild(this.arrowDown); 5773 } 5774 5775 this.graph.container.appendChild(this.arrowRight); 5776 this.graph.container.appendChild(this.arrowLeft); 5777 this.currentState = state; 5778}; 5779 5780/** 5781 * Returns true if the given cell is a table. 5782 */ 5783Graph.prototype.createParent = function(parent, child, childCount, dx, dy) 5784{ 5785 parent = this.cloneCell(parent); 5786 5787 for (var i = 0; i < childCount; i++) 5788 { 5789 var clone = this.cloneCell(child); 5790 var geo = this.getCellGeometry(clone) 5791 5792 if (geo != null) 5793 { 5794 geo.x += i * dx; 5795 geo.y += i * dy; 5796 } 5797 5798 parent.insert(clone); 5799 } 5800 5801 return parent; 5802}; 5803 5804/** 5805 * Returns true if the given cell is a table. 5806 */ 5807Graph.prototype.createTable = function(rowCount, colCount, w, h, title, startSize, tableStyle, rowStyle, cellStyle) 5808{ 5809 w = (w != null) ? w : 60; 5810 h = (h != null) ? h : 40; 5811 startSize = (startSize != null) ? startSize : 30; 5812 tableStyle = (tableStyle != null) ? tableStyle : 'shape=table;startSize=' + 5813 ((title != null) ? startSize : '0') + ';container=1;collapsible=0;childLayout=tableLayout;'; 5814 rowStyle = (rowStyle != null) ? rowStyle : 'shape=partialRectangle;collapsible=0;dropTarget=0;' + 5815 'pointerEvents=0;fillColor=none;top=0;left=0;bottom=0;right=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;'; 5816 cellStyle = (cellStyle != null) ? cellStyle : 'shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;' + 5817 'overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;'; 5818 5819 return this.createParent(this.createVertex(null, null, (title != null) ? title : '', 5820 0, 0, colCount * w, rowCount * h + ((title != null) ? startSize : 0), tableStyle), 5821 this.createParent(this.createVertex(null, null, '', 0, 0, colCount * w, h, rowStyle), 5822 this.createVertex(null, null, '', 0, 0, w, h, cellStyle), 5823 colCount, w, 0), rowCount, 0, h); 5824}; 5825 5826/** 5827 * Sets the values for the cells and rows in the given table and returns the table. 5828 */ 5829Graph.prototype.setTableValues = function(table, values, rowValues) 5830{ 5831 var rows = this.model.getChildCells(table, true); 5832 5833 for (var i = 0; i < rows.length; i++) 5834 { 5835 if (rowValues != null) 5836 { 5837 rows[i].value = rowValues[i]; 5838 } 5839 5840 if (values != null) 5841 { 5842 var cells = this.model.getChildCells(rows[i], true); 5843 5844 for (var j = 0; j < cells.length; j++) 5845 { 5846 if (values[i][j] != null) 5847 { 5848 cells[j].value = values[i][j]; 5849 } 5850 } 5851 } 5852 } 5853 5854 return table; 5855}; 5856 5857/** 5858 * 5859 */ 5860Graph.prototype.createCrossFunctionalSwimlane = function(rowCount, colCount, w, h, title, tableStyle, rowStyle, firstCellStyle, cellStyle) 5861{ 5862 w = (w != null) ? w : 120; 5863 h = (h != null) ? h : 120; 5864 var startSize = (title == null) ? 0 : 40; 5865 5866 var s = 'collapsible=0;recursiveResize=0;expand=0;pointerEvents=0;'; 5867 tableStyle = (tableStyle != null) ? tableStyle : 'shape=table;childLayout=tableLayout;' + 5868 'rowLines=0;columnLines=0;startSize=' + startSize + ';' + 5869 ((title == null) ? 'fillColor=none;' : '') + s; 5870 5871 rowStyle = (rowStyle != null) ? rowStyle : 'swimlane;horizontal=0;points=[[0,0.5],[1,0.5]];' + 5872 'portConstraint=eastwest;startSize=' + startSize + ';' + s; 5873 firstCellStyle = (firstCellStyle != null) ? firstCellStyle : 'swimlane;connectable=0;startSize=40;' + s; 5874 cellStyle = (cellStyle != null) ? cellStyle : 'swimlane;connectable=0;startSize=' + 5875 ((title == null) ? '40' : '0') + ';' + s; 5876 5877 var table = this.createVertex(null, null, (title != null) ? title : '', 0, 0, 5878 colCount * w, rowCount * h, tableStyle); 5879 var t = mxUtils.getValue(this.getCellStyle(table), mxConstants.STYLE_STARTSIZE, 5880 mxConstants.DEFAULT_STARTSIZE); 5881 table.geometry.width += t; 5882 table.geometry.height += t; 5883 5884 var row = this.createVertex(null, null, '', 0, t, colCount * w + t, h, rowStyle); 5885 table.insert(this.createParent(row, this.createVertex(null, null, 5886 '', t, 0, w, h, firstCellStyle), colCount, w, 0)); 5887 5888 if (rowCount > 1) 5889 { 5890 row.geometry.y = h + t; 5891 5892 return this.createParent(table, this.createParent(row, 5893 this.createVertex(null, null, '', t, 0, w, h, cellStyle), 5894 colCount, w, 0), rowCount - 1, 0, h); 5895 } 5896 else 5897 { 5898 return table; 5899 } 5900}; 5901 5902/** 5903 * Returns true if the given cell is a table cell. 5904 */ 5905Graph.prototype.isTableCell = function(cell) 5906{ 5907 return this.model.isVertex(cell) && this.isTableRow(this.model.getParent(cell)); 5908}; 5909 5910/** 5911 * Returns true if the given cell is a table row. 5912 */ 5913Graph.prototype.isTableRow = function(cell) 5914{ 5915 return this.model.isVertex(cell) && this.isTable(this.model.getParent(cell)); 5916}; 5917 5918/** 5919 * Returns true if the given cell is a table. 5920 */ 5921Graph.prototype.isTable = function(cell) 5922{ 5923 var style = this.getCellStyle(cell); 5924 5925 return style != null && style['childLayout'] == 'tableLayout'; 5926}; 5927 5928/** 5929 * Returns true if the given cell is a table. 5930 */ 5931Graph.prototype.isStack = function(cell) 5932{ 5933 var style = this.getCellStyle(cell); 5934 5935 return style != null && style['childLayout'] == 'stackLayout'; 5936}; 5937 5938/** 5939 * Returns true if the given cell is a table row. 5940 */ 5941Graph.prototype.isStackChild = function(cell) 5942{ 5943 return this.model.isVertex(cell) && this.isStack(this.model.getParent(cell)); 5944}; 5945 5946/** 5947 * Updates the row and table heights. 5948 */ 5949Graph.prototype.setTableRowHeight = function(row, dy, extend) 5950{ 5951 extend = (extend != null) ? extend : true; 5952 var model = this.getModel(); 5953 5954 model.beginUpdate(); 5955 try 5956 { 5957 var rgeo = this.getCellGeometry(row); 5958 5959 // Sets height of row 5960 if (rgeo != null) 5961 { 5962 rgeo = rgeo.clone(); 5963 rgeo.height += dy; 5964 model.setGeometry(row, rgeo); 5965 5966 var table = model.getParent(row); 5967 var rows = model.getChildCells(table, true); 5968 5969 // Shifts and resizes neighbor row 5970 if (!extend) 5971 { 5972 var index = mxUtils.indexOf(rows, row); 5973 5974 if (index < rows.length - 1) 5975 { 5976 var nextRow = rows[index + 1]; 5977 var geo = this.getCellGeometry(nextRow); 5978 5979 if (geo != null) 5980 { 5981 geo = geo.clone(); 5982 geo.y += dy; 5983 geo.height -= dy; 5984 5985 model.setGeometry(nextRow, geo); 5986 } 5987 } 5988 } 5989 5990 // Updates height of table 5991 var tgeo = this.getCellGeometry(table); 5992 5993 if (tgeo != null) 5994 { 5995 // Always extends for last row 5996 if (!extend) 5997 { 5998 extend = row == rows[rows.length - 1]; 5999 } 6000 6001 if (extend) 6002 { 6003 tgeo = tgeo.clone(); 6004 tgeo.height += dy; 6005 model.setGeometry(table, tgeo); 6006 } 6007 } 6008 6009 if (this.layoutManager != null) 6010 { 6011 this.layoutManager.executeLayout(table, true); 6012 } 6013 } 6014 } 6015 finally 6016 { 6017 model.endUpdate(); 6018 } 6019}; 6020 6021/** 6022 * Updates column width and row height. 6023 */ 6024Graph.prototype.setTableColumnWidth = function(col, dx, extend) 6025{ 6026 extend = (extend != null) ? extend : false; 6027 6028 var model = this.getModel(); 6029 var row = model.getParent(col); 6030 var table = model.getParent(row); 6031 var cells = model.getChildCells(row, true); 6032 var index = mxUtils.indexOf(cells, col); 6033 var lastColumn = index == cells.length - 1; 6034 6035 model.beginUpdate(); 6036 try 6037 { 6038 // Sets width of child cell 6039 var rows = model.getChildCells(table, true); 6040 6041 for (var i = 0; i < rows.length; i++) 6042 { 6043 row = rows[i]; 6044 cells = model.getChildCells(row, true); 6045 var cell = cells[index]; 6046 var geo = this.getCellGeometry(cell); 6047 6048 if (geo != null) 6049 { 6050 geo = geo.clone(); 6051 geo.width += dx; 6052 6053 if (geo.alternateBounds != null) 6054 { 6055 geo.alternateBounds.width += dx; 6056 } 6057 6058 model.setGeometry(cell, geo); 6059 } 6060 6061 // Shifts and resizes neighbor column 6062 if (index < cells.length - 1) 6063 { 6064 cell = cells[index + 1]; 6065 var geo = this.getCellGeometry(cell); 6066 6067 if (geo != null) 6068 { 6069 geo = geo.clone(); 6070 geo.x += dx; 6071 6072 if (!extend) 6073 { 6074 geo.width -= dx; 6075 6076 if (geo.alternateBounds != null) 6077 { 6078 geo.alternateBounds.width -= dx; 6079 } 6080 } 6081 6082 model.setGeometry(cell, geo); 6083 } 6084 } 6085 } 6086 6087 if (lastColumn || extend) 6088 { 6089 // Updates width of table 6090 var tgeo = this.getCellGeometry(table); 6091 6092 if (tgeo != null) 6093 { 6094 tgeo = tgeo.clone(); 6095 tgeo.width += dx; 6096 model.setGeometry(table, tgeo); 6097 } 6098 } 6099 6100 if (this.layoutManager != null) 6101 { 6102 this.layoutManager.executeLayout(table, true); 6103 } 6104 } 6105 finally 6106 { 6107 model.endUpdate(); 6108 } 6109}; 6110 6111/** 6112 * Special Layout for tables. 6113 */ 6114function TableLayout(graph) 6115{ 6116 mxGraphLayout.call(this, graph); 6117}; 6118 6119/** 6120 * Extends mxGraphLayout. 6121 */ 6122TableLayout.prototype = new mxStackLayout(); 6123TableLayout.prototype.constructor = TableLayout; 6124 6125/** 6126 * Function: isHorizontal 6127 * 6128 * Overrides stack layout to handle row reorder. 6129 */ 6130TableLayout.prototype.isHorizontal = function() 6131{ 6132 return false; 6133}; 6134 6135/** 6136 * Function: isVertexIgnored 6137 * 6138 * Overrides to allow for table rows and cells. 6139 */ 6140TableLayout.prototype.isVertexIgnored = function(vertex) 6141{ 6142 return !this.graph.getModel().isVertex(vertex) || 6143 !this.graph.isCellVisible(vertex); 6144}; 6145 6146/** 6147 * Function: getSize 6148 * 6149 * Returns the total vertical or horizontal size of the given cells. 6150 */ 6151TableLayout.prototype.getSize = function(cells, horizontal) 6152{ 6153 var total = 0; 6154 6155 for (var i = 0; i < cells.length; i++) 6156 { 6157 if (!this.isVertexIgnored(cells[i])) 6158 { 6159 var geo = this.graph.getCellGeometry(cells[i]); 6160 6161 if (geo != null) 6162 { 6163 total += (horizontal) ? geo.width : geo.height; 6164 } 6165 } 6166 } 6167 6168 return total; 6169}; 6170 6171/** 6172 * Function: getRowLayout 6173 * 6174 * Returns the column positions for the given row and table width. 6175 */ 6176TableLayout.prototype.getRowLayout = function(row, width) 6177{ 6178 var cells = this.graph.model.getChildCells(row, true); 6179 var off = this.graph.getActualStartSize(row, true); 6180 var sw = this.getSize(cells, true); 6181 var rw = width - off.x - off.width; 6182 var result = []; 6183 var x = off.x; 6184 6185 for (var i = 0; i < cells.length; i++) 6186 { 6187 var cell = this.graph.getCellGeometry(cells[i]); 6188 6189 if (cell != null) 6190 { 6191 x += (cell.alternateBounds != null ? 6192 cell.alternateBounds.width : 6193 cell.width) * rw / sw; 6194 result.push(Math.round(x)); 6195 } 6196 } 6197 6198 return result; 6199}; 6200 6201/** 6202 * Function: layoutRow 6203 * 6204 * Places the cells at the given positions in the given row. 6205 */ 6206TableLayout.prototype.layoutRow = function(row, positions, height, tw, lastCells) 6207{ 6208 var model = this.graph.getModel(); 6209 var cells = model.getChildCells(row, true); 6210 var off = this.graph.getActualStartSize(row, true); 6211 var last = null; 6212 var x = off.x; 6213 var sw = 0; 6214 6215 if (positions != null) 6216 { 6217 positions = positions.slice(); 6218 positions.splice(0, 0, off.x); 6219 } 6220 6221 for (var i = 0; i < cells.length; i++) 6222 { 6223 var cell = this.graph.getCellGeometry(cells[i]); 6224 6225 if (cell != null) 6226 { 6227 cell = cell.clone(); 6228 6229 cell.y = off.y; 6230 cell.height = height - off.y - off.height; 6231 6232 if (positions != null) 6233 { 6234 cell.x = positions[i]; 6235 cell.width = positions[i + 1] - cell.x; 6236 6237 // Fills with last cell if not enough cells 6238 if (i == cells.length - 1 && i < positions.length - 2) 6239 { 6240 cell.width = tw - cell.x - off.x - off.width; 6241 } 6242 } 6243 else 6244 { 6245 cell.x = x; 6246 x += cell.width; 6247 6248 if (i == cells.length - 1) 6249 { 6250 cell.width = tw - off.x - off.width - sw; 6251 } 6252 else 6253 { 6254 sw += cell.width; 6255 } 6256 } 6257 6258 cell.alternateBounds = new mxRectangle(0, 0, cell.width, cell.height); 6259 model.setGeometry(cells[i], cell); 6260 } 6261 6262 var visible = true; 6263 6264 // Handles rowspan 6265 var upper = lastCells[i]; 6266 6267 if (upper != null && upper.geo != null && 6268 upper.rowspan != null && upper.rowspan > 1) 6269 { 6270 upper.geo.height += (cell.alternateBounds != null) ? 6271 cell.alternateBounds.height : cell.height; 6272 visible = false; 6273 upper.rowspan--; 6274 } 6275 6276 // Handles colspan 6277 if (last != null && last.geo != null && 6278 last.colspan != null && last.colspan > 1) 6279 { 6280 last.geo.width += (cell.alternateBounds != null) ? 6281 cell.alternateBounds.width : cell.width; 6282 visible = false; 6283 last.colspan--; 6284 } 6285 6286 model.setVisible(cells[i], visible); 6287 6288 var style = this.graph.getCurrentCellStyle(cells[i], true); 6289 var temp = {style: style, cell: cells[i], geo: cell}; 6290 6291 if (style != null) 6292 { 6293 if (last == null || last.colspan < 1) 6294 { 6295 temp.colspan = parseInt(style['colspan'] || 0); 6296 last = temp; 6297 } 6298 6299 if (upper == null || upper.rowspan < 1) 6300 { 6301 temp.rowspan = parseInt(style['rowspan'] || 0); 6302 lastCells[i] = temp; 6303 } 6304 else if (upper != null) 6305 { 6306 temp.colspan = parseInt(upper.style['colspan'] || 0); 6307 } 6308 } 6309 } 6310 6311 return sw; 6312}; 6313 6314/** 6315 * Function: execute 6316 * 6317 * Implements <mxGraphLayout.execute>. 6318 */ 6319TableLayout.prototype.execute = function(parent) 6320{ 6321 if (parent != null) 6322 { 6323 var offset = this.graph.getActualStartSize(parent, true); 6324 var table = this.graph.getCellGeometry(parent); 6325 var style = this.graph.getCellStyle(parent); 6326 var resizeLastRow = mxUtils.getValue(style, 6327 'resizeLastRow', '0') == '1'; 6328 var resizeLast = mxUtils.getValue(style, 6329 'resizeLast', '0') == '1'; 6330 var fixedRows = mxUtils.getValue(style, 6331 'fixedRows', '0') == '1'; 6332 var model = this.graph.getModel(); 6333 var sw = 0; 6334 6335 model.beginUpdate(); 6336 try 6337 { 6338 var th = table.height - offset.y - offset.height; 6339 var tw = table.width - offset.x - offset.width; 6340 var rows = model.getChildCells(parent, true); 6341 var sh = this.getSize(rows, false); 6342 6343 if (th > 0 && tw > 0 && rows.length > 0 && sh > 0) 6344 { 6345 if (resizeLastRow) 6346 { 6347 var row = this.graph.getCellGeometry(rows[rows.length - 1]); 6348 6349 if (row != null) 6350 { 6351 row = row.clone(); 6352 row.height = th - sh + row.height; 6353 model.setGeometry(rows[rows.length - 1], row); 6354 } 6355 } 6356 6357 var pos = (resizeLast) ? null : this.getRowLayout(rows[0], tw); 6358 var lastCells = []; 6359 var y = offset.y; 6360 6361 // Updates row geometries 6362 for (var i = 0; i < rows.length; i++) 6363 { 6364 var row = this.graph.getCellGeometry(rows[i]); 6365 6366 if (row != null) 6367 { 6368 row = row.clone(); 6369 row.x = offset.x; 6370 row.width = tw; 6371 row.y = Math.round(y); 6372 6373 if (resizeLastRow || fixedRows) 6374 { 6375 y += row.height; 6376 } 6377 else 6378 { 6379 y += (row.height / sh) * th; 6380 } 6381 6382 row.height = Math.round(y) - row.y; 6383 model.setGeometry(rows[i], row); 6384 } 6385 6386 // Updates cell geometries 6387 sw = Math.max(sw, this.layoutRow(rows[i], pos, row.height, tw, lastCells)); 6388 } 6389 6390 if (fixedRows && th < sh) 6391 { 6392 table = table.clone(); 6393 table.height = y + offset.height; 6394 model.setGeometry(parent, table); 6395 } 6396 6397 if (resizeLast && tw < sw + Graph.minTableColumnWidth) 6398 { 6399 table = table.clone(); 6400 table.width = sw + offset.width + offset.x + Graph.minTableColumnWidth; 6401 model.setGeometry(parent, table); 6402 } 6403 } 6404 } 6405 finally 6406 { 6407 model.endUpdate(); 6408 } 6409 } 6410}; 6411 6412(function() 6413{ 6414 /** 6415 * Reset the list of processed edges. 6416 */ 6417 var mxGraphViewResetValidationState = mxGraphView.prototype.resetValidationState; 6418 mxGraphView.prototype.resetValidationState = function() 6419 { 6420 mxGraphViewResetValidationState.apply(this, arguments); 6421 6422 this.validEdges = []; 6423 }; 6424 6425 /** 6426 * Updates jumps for valid edges and repaints if needed. 6427 */ 6428 var mxGraphViewValidateCellState = mxGraphView.prototype.validateCellState; 6429 mxGraphView.prototype.validateCellState = function(cell, recurse) 6430 { 6431 recurse = (recurse != null) ? recurse : true; 6432 var state = this.getState(cell); 6433 6434 // Forces repaint if jumps change on a valid edge 6435 if (state != null && recurse && this.graph.model.isEdge(state.cell) && 6436 state.style != null && state.style[mxConstants.STYLE_CURVED] != 1 && 6437 !state.invalid && this.updateLineJumps(state)) 6438 { 6439 this.graph.cellRenderer.redraw(state, false, this.isRendering()); 6440 } 6441 6442 state = mxGraphViewValidateCellState.apply(this, arguments); 6443 6444 // Adds to the list of edges that may intersect with later edges 6445 if (state != null && recurse && this.graph.model.isEdge(state.cell) && 6446 state.style != null && state.style[mxConstants.STYLE_CURVED] != 1) 6447 { 6448 // LATER: Reuse jumps for valid edges 6449 this.validEdges.push(state); 6450 } 6451 6452 return state; 6453 }; 6454 6455 /** 6456 * Overrides paint to add flowAnimation style. 6457 */ 6458 var mxShapePaint = mxShape.prototype.paint; 6459 6460 mxShape.prototype.paint = function() 6461 { 6462 mxShapePaint.apply(this, arguments); 6463 6464 if (this.state != null && this.node != null && 6465 this.state.view.graph.enableFlowAnimation && 6466 this.state.view.graph.model.isEdge(this.state.cell) && 6467 mxUtils.getValue(this.state.style, 'flowAnimation', '0') == '1') 6468 { 6469 var paths = this.node.getElementsByTagName('path'); 6470 6471 if (paths.length > 1) 6472 { 6473 if (mxUtils.getValue(this.state.style, mxConstants.STYLE_DASHED, '0') != '1') 6474 { 6475 paths[1].setAttribute('stroke-dasharray', (this.state.view.scale * 8)); 6476 } 6477 6478 var anim = this.state.view.graph.getFlowAnimationStyle(); 6479 6480 if (anim != null) 6481 { 6482 paths[1].setAttribute('class', anim.getAttribute('id')); 6483 } 6484 } 6485 } 6486 }; 6487 6488 /** 6489 * Forces repaint if routed points have changed. 6490 */ 6491 var mxCellRendererIsShapeInvalid = mxCellRenderer.prototype.isShapeInvalid; 6492 mxCellRenderer.prototype.isShapeInvalid = function(state, shape) 6493 { 6494 return mxCellRendererIsShapeInvalid.apply(this, arguments) || 6495 (state.routedPoints != null && shape.routedPoints != null && 6496 !mxUtils.equalPoints(shape.routedPoints, state.routedPoints)) 6497 }; 6498 6499 /** 6500 * Updates jumps for invalid edges. 6501 */ 6502 var mxGraphViewUpdateCellState = mxGraphView.prototype.updateCellState; 6503 mxGraphView.prototype.updateCellState = function(state) 6504 { 6505 mxGraphViewUpdateCellState.apply(this, arguments); 6506 6507 // Updates jumps on invalid edge before repaint 6508 if (this.graph.model.isEdge(state.cell) && 6509 state.style[mxConstants.STYLE_CURVED] != 1) 6510 { 6511 this.updateLineJumps(state); 6512 } 6513 }; 6514 6515 /** 6516 * Updates the jumps between given state and processed edges. 6517 */ 6518 mxGraphView.prototype.updateLineJumps = function(state) 6519 { 6520 var pts = state.absolutePoints; 6521 6522 if (Graph.lineJumpsEnabled) 6523 { 6524 var changed = state.routedPoints != null; 6525 var actual = null; 6526 6527 if (pts != null && this.validEdges != null && 6528 mxUtils.getValue(state.style, 'jumpStyle', 'none') !== 'none') 6529 { 6530 var thresh = 0.5 * this.scale; 6531 changed = false; 6532 actual = []; 6533 6534 // Type 0 means normal waypoint, 1 means jump 6535 function addPoint(type, x, y) 6536 { 6537 var rpt = new mxPoint(x, y); 6538 rpt.type = type; 6539 6540 actual.push(rpt); 6541 var curr = (state.routedPoints != null) ? state.routedPoints[actual.length - 1] : null; 6542 6543 return curr == null || curr.type != type || curr.x != x || curr.y != y; 6544 }; 6545 6546 for (var i = 0; i < pts.length - 1; i++) 6547 { 6548 var p1 = pts[i + 1]; 6549 var p0 = pts[i]; 6550 var list = []; 6551 6552 // Ignores waypoints on straight segments 6553 var pn = pts[i + 2]; 6554 6555 while (i < pts.length - 2 && 6556 mxUtils.ptSegDistSq(p0.x, p0.y, pn.x, pn.y, 6557 p1.x, p1.y) < 1 * this.scale * this.scale) 6558 { 6559 p1 = pn; 6560 i++; 6561 pn = pts[i + 2]; 6562 } 6563 6564 changed = addPoint(0, p0.x, p0.y) || changed; 6565 6566 // Processes all previous edges 6567 for (var e = 0; e < this.validEdges.length; e++) 6568 { 6569 var state2 = this.validEdges[e]; 6570 var pts2 = state2.absolutePoints; 6571 6572 if (pts2 != null && mxUtils.intersects(state, state2) && state2.style['noJump'] != '1') 6573 { 6574 // Compares each segment of the edge with the current segment 6575 for (var j = 0; j < pts2.length - 1; j++) 6576 { 6577 var p3 = pts2[j + 1]; 6578 var p2 = pts2[j]; 6579 6580 // Ignores waypoints on straight segments 6581 pn = pts2[j + 2]; 6582 6583 while (j < pts2.length - 2 && 6584 mxUtils.ptSegDistSq(p2.x, p2.y, pn.x, pn.y, 6585 p3.x, p3.y) < 1 * this.scale * this.scale) 6586 { 6587 p3 = pn; 6588 j++; 6589 pn = pts2[j + 2]; 6590 } 6591 6592 var pt = mxUtils.intersection(p0.x, p0.y, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); 6593 6594 // Handles intersection between two segments 6595 if (pt != null && (Math.abs(pt.x - p0.x) > thresh || 6596 Math.abs(pt.y - p0.y) > thresh) && 6597 (Math.abs(pt.x - p1.x) > thresh || 6598 Math.abs(pt.y - p1.y) > thresh) && 6599 (Math.abs(pt.x - p2.x) > thresh || 6600 Math.abs(pt.y - p2.y) > thresh) && 6601 (Math.abs(pt.x - p3.x) > thresh || 6602 Math.abs(pt.y - p3.y) > thresh)) 6603 { 6604 var dx = pt.x - p0.x; 6605 var dy = pt.y - p0.y; 6606 var temp = {distSq: dx * dx + dy * dy, x: pt.x, y: pt.y}; 6607 6608 // Intersections must be ordered by distance from start of segment 6609 for (var t = 0; t < list.length; t++) 6610 { 6611 if (list[t].distSq > temp.distSq) 6612 { 6613 list.splice(t, 0, temp); 6614 temp = null; 6615 6616 break; 6617 } 6618 } 6619 6620 // Ignores multiple intersections at segment joint 6621 if (temp != null && (list.length == 0 || 6622 list[list.length - 1].x !== temp.x || 6623 list[list.length - 1].y !== temp.y)) 6624 { 6625 list.push(temp); 6626 } 6627 } 6628 } 6629 } 6630 } 6631 6632 // Adds ordered intersections to routed points 6633 for (var j = 0; j < list.length; j++) 6634 { 6635 changed = addPoint(1, list[j].x, list[j].y) || changed; 6636 } 6637 } 6638 6639 var pt = pts[pts.length - 1]; 6640 changed = addPoint(0, pt.x, pt.y) || changed; 6641 } 6642 6643 state.routedPoints = actual; 6644 6645 return changed; 6646 } 6647 else 6648 { 6649 return false; 6650 } 6651 }; 6652 6653 /** 6654 * Overrides painting the actual shape for taking into account jump style. 6655 */ 6656 var mxConnectorPaintLine = mxConnector.prototype.paintLine; 6657 6658 mxConnector.prototype.paintLine = function (c, absPts, rounded) 6659 { 6660 // Required for checking dirty state 6661 this.routedPoints = (this.state != null) ? this.state.routedPoints : null; 6662 6663 if (this.outline || this.state == null || this.style == null || 6664 this.state.routedPoints == null || this.state.routedPoints.length == 0) 6665 { 6666 mxConnectorPaintLine.apply(this, arguments); 6667 } 6668 else 6669 { 6670 var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, 6671 mxConstants.LINE_ARCSIZE) / 2; 6672 var size = (parseInt(mxUtils.getValue(this.style, 'jumpSize', 6673 Graph.defaultJumpSize)) - 2) / 2 + this.strokewidth; 6674 var style = mxUtils.getValue(this.style, 'jumpStyle', 'none'); 6675 var moveTo = true; 6676 var last = null; 6677 var len = null; 6678 var pts = []; 6679 var n = null; 6680 c.begin(); 6681 6682 for (var i = 0; i < this.state.routedPoints.length; i++) 6683 { 6684 var rpt = this.state.routedPoints[i]; 6685 var pt = new mxPoint(rpt.x / this.scale, rpt.y / this.scale); 6686 6687 // Takes first and last point from passed-in array 6688 if (i == 0) 6689 { 6690 pt = absPts[0]; 6691 } 6692 else if (i == this.state.routedPoints.length - 1) 6693 { 6694 pt = absPts[absPts.length - 1]; 6695 } 6696 6697 var done = false; 6698 6699 // Type 1 is an intersection 6700 if (last != null && rpt.type == 1) 6701 { 6702 // Checks if next/previous points are too close 6703 var next = this.state.routedPoints[i + 1]; 6704 var dx = next.x / this.scale - pt.x; 6705 var dy = next.y / this.scale - pt.y; 6706 var dist = dx * dx + dy * dy; 6707 6708 if (n == null) 6709 { 6710 n = new mxPoint(pt.x - last.x, pt.y - last.y); 6711 len = Math.sqrt(n.x * n.x + n.y * n.y); 6712 6713 if (len > 0) 6714 { 6715 n.x = n.x * size / len; 6716 n.y = n.y * size / len; 6717 } 6718 else 6719 { 6720 n = null; 6721 } 6722 } 6723 6724 if (dist > size * size && len > 0) 6725 { 6726 var dx = last.x - pt.x; 6727 var dy = last.y - pt.y; 6728 var dist = dx * dx + dy * dy; 6729 6730 if (dist > size * size) 6731 { 6732 var p0 = new mxPoint(pt.x - n.x, pt.y - n.y); 6733 var p1 = new mxPoint(pt.x + n.x, pt.y + n.y); 6734 pts.push(p0); 6735 6736 this.addPoints(c, pts, rounded, arcSize, false, null, moveTo); 6737 6738 var f = (Math.round(n.x) < 0 || (Math.round(n.x) == 0 6739 && Math.round(n.y) <= 0)) ? 1 : -1; 6740 moveTo = false; 6741 6742 if (style == 'sharp') 6743 { 6744 c.lineTo(p0.x - n.y * f, p0.y + n.x * f); 6745 c.lineTo(p1.x - n.y * f, p1.y + n.x * f); 6746 c.lineTo(p1.x, p1.y); 6747 } 6748 else if (style == 'line') 6749 { 6750 c.moveTo(p0.x + n.y * f, p0.y - n.x * f); 6751 c.lineTo(p0.x - n.y * f, p0.y + n.x * f); 6752 c.moveTo(p1.x - n.y * f, p1.y + n.x * f); 6753 c.lineTo(p1.x + n.y * f, p1.y - n.x * f); 6754 c.moveTo(p1.x, p1.y); 6755 } 6756 else if (style == 'arc') 6757 { 6758 f *= 1.3; 6759 c.curveTo(p0.x - n.y * f, p0.y + n.x * f, 6760 p1.x - n.y * f, p1.y + n.x * f, 6761 p1.x, p1.y); 6762 } 6763 else 6764 { 6765 c.moveTo(p1.x, p1.y); 6766 moveTo = true; 6767 } 6768 6769 pts = [p1]; 6770 done = true; 6771 } 6772 } 6773 } 6774 else 6775 { 6776 n = null; 6777 } 6778 6779 if (!done) 6780 { 6781 pts.push(pt); 6782 last = pt; 6783 } 6784 } 6785 6786 this.addPoints(c, pts, rounded, arcSize, false, null, moveTo); 6787 c.stroke(); 6788 } 6789 }; 6790 6791 /** 6792 * Adds support for centerPerimeter which is a special case of a fixed point perimeter. 6793 */ 6794 var mxGraphViewGetFixedTerminalPoint = mxGraphView.prototype.getFixedTerminalPoint; 6795 6796 mxGraphView.prototype.getFixedTerminalPoint = function(edge, terminal, source, constraint) 6797 { 6798 if (terminal != null && terminal.style[mxConstants.STYLE_PERIMETER] == 'centerPerimeter') 6799 { 6800 return new mxPoint(terminal.getCenterX(), terminal.getCenterY()); 6801 } 6802 else 6803 { 6804 return mxGraphViewGetFixedTerminalPoint.apply(this, arguments); 6805 } 6806 }; 6807 6808 /** 6809 * Adds support for snapToPoint style. 6810 */ 6811 var mxGraphViewUpdateFloatingTerminalPoint = mxGraphView.prototype.updateFloatingTerminalPoint; 6812 6813 mxGraphView.prototype.updateFloatingTerminalPoint = function(edge, start, end, source) 6814 { 6815 if (start != null && edge != null && 6816 (start.style['snapToPoint'] == '1' || 6817 edge.style['snapToPoint'] == '1')) 6818 { 6819 start = this.getTerminalPort(edge, start, source); 6820 var next = this.getNextPoint(edge, end, source); 6821 6822 var orth = this.graph.isOrthogonal(edge); 6823 var alpha = mxUtils.toRadians(Number(start.style[mxConstants.STYLE_ROTATION] || '0')); 6824 var center = new mxPoint(start.getCenterX(), start.getCenterY()); 6825 6826 if (alpha != 0) 6827 { 6828 var cos = Math.cos(-alpha); 6829 var sin = Math.sin(-alpha); 6830 next = mxUtils.getRotatedPoint(next, cos, sin, center); 6831 } 6832 6833 var border = parseFloat(edge.style[mxConstants.STYLE_PERIMETER_SPACING] || 0); 6834 border += parseFloat(edge.style[(source) ? 6835 mxConstants.STYLE_SOURCE_PERIMETER_SPACING : 6836 mxConstants.STYLE_TARGET_PERIMETER_SPACING] || 0); 6837 var pt = this.getPerimeterPoint(start, next, alpha == 0 && orth, border); 6838 6839 if (alpha != 0) 6840 { 6841 var cos = Math.cos(alpha); 6842 var sin = Math.sin(alpha); 6843 pt = mxUtils.getRotatedPoint(pt, cos, sin, center); 6844 } 6845 6846 edge.setAbsoluteTerminalPoint(this.snapToAnchorPoint(edge, start, end, source, pt), source); 6847 } 6848 else 6849 { 6850 mxGraphViewUpdateFloatingTerminalPoint.apply(this, arguments); 6851 } 6852 }; 6853 6854 mxGraphView.prototype.snapToAnchorPoint = function(edge, start, end, source, pt) 6855 { 6856 if (start != null && edge != null) 6857 { 6858 var constraints = this.graph.getAllConnectionConstraints(start) 6859 var nearest = null; 6860 var dist = null; 6861 6862 if (constraints != null) 6863 { 6864 for (var i = 0; i < constraints.length; i++) 6865 { 6866 var cp = this.graph.getConnectionPoint(start, constraints[i]); 6867 6868 if (cp != null) 6869 { 6870 var tmp = (cp.x - pt.x) * (cp.x - pt.x) + (cp.y - pt.y) * (cp.y - pt.y); 6871 6872 if (dist == null || tmp < dist) 6873 { 6874 nearest = cp; 6875 dist = tmp; 6876 } 6877 } 6878 } 6879 } 6880 6881 if (nearest != null) 6882 { 6883 pt = nearest; 6884 } 6885 } 6886 6887 return pt; 6888 }; 6889 6890 /** 6891 * Adds support for placeholders in text elements of shapes. 6892 */ 6893 var mxStencilEvaluateTextAttribute = mxStencil.prototype.evaluateTextAttribute; 6894 6895 mxStencil.prototype.evaluateTextAttribute = function(node, attribute, shape) 6896 { 6897 var result = mxStencilEvaluateTextAttribute.apply(this, arguments); 6898 var placeholders = node.getAttribute('placeholders'); 6899 6900 if (placeholders == '1' && shape.state != null) 6901 { 6902 result = shape.state.view.graph.replacePlaceholders(shape.state.cell, result); 6903 } 6904 6905 return result; 6906 }; 6907 6908 /** 6909 * Adds custom stencils defined via shape=stencil(value) style. The value is a base64 encoded, compressed and 6910 * URL encoded XML definition of the shape according to the stencil definition language of mxGraph. 6911 * 6912 * Needs to be in this file to make sure its part of the embed client code. Also the check for ZLib is 6913 * different than for the Editor code. 6914 */ 6915 var mxCellRendererCreateShape = mxCellRenderer.prototype.createShape; 6916 mxCellRenderer.prototype.createShape = function(state) 6917 { 6918 if (state.style != null && typeof(pako) !== 'undefined') 6919 { 6920 var shape = mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null); 6921 6922 // Extracts and decodes stencil XML if shape has the form shape=stencil(value) 6923 if (shape != null && typeof shape === 'string' && shape.substring(0, 8) == 'stencil(') 6924 { 6925 try 6926 { 6927 var stencil = shape.substring(8, shape.length - 1); 6928 var doc = mxUtils.parseXml(Graph.decompress(stencil)); 6929 6930 return new mxShape(new mxStencil(doc.documentElement)); 6931 } 6932 catch (e) 6933 { 6934 if (window.console != null) 6935 { 6936 console.log('Error in shape: ' + e); 6937 } 6938 } 6939 } 6940 } 6941 6942 return mxCellRendererCreateShape.apply(this, arguments); 6943 }; 6944})(); 6945 6946/** 6947 * Overrides stencil registry for dynamic loading of stencils. 6948 */ 6949/** 6950 * Maps from library names to an array of Javascript filenames, 6951 * which are synchronously loaded. Currently only stencil files 6952 * (.xml) and JS files (.js) are supported. 6953 * IMPORTANT: For embedded diagrams to work entries must also 6954 * be added in EmbedServlet.java. 6955 */ 6956mxStencilRegistry.libraries = {}; 6957 6958/** 6959 * Global switch to disable dynamic loading. 6960 */ 6961mxStencilRegistry.dynamicLoading = true; 6962 6963/** 6964 * Global switch to disable eval for JS (preload all JS instead). 6965 */ 6966mxStencilRegistry.allowEval = true; 6967 6968/** 6969 * Stores all package names that have been dynamically loaded. 6970 * Each package is only loaded once. 6971 */ 6972mxStencilRegistry.packages = []; 6973 6974/** 6975 * Stores all package names that have been dynamically loaded. 6976 * Each package is only loaded once. 6977 */ 6978mxStencilRegistry.filesLoaded = {}; 6979 6980// Extends the default stencil registry to add dynamic loading 6981mxStencilRegistry.getStencil = function(name) 6982{ 6983 var result = mxStencilRegistry.stencils[name]; 6984 6985 if (result == null && mxCellRenderer.defaultShapes[name] == null && mxStencilRegistry.dynamicLoading) 6986 { 6987 var basename = mxStencilRegistry.getBasenameForStencil(name); 6988 6989 // Loads stencil files and tries again 6990 if (basename != null) 6991 { 6992 var libs = mxStencilRegistry.libraries[basename]; 6993 6994 if (libs != null) 6995 { 6996 if (mxStencilRegistry.packages[basename] == null) 6997 { 6998 for (var i = 0; i < libs.length; i++) 6999 { 7000 var fname = libs[i]; 7001 7002 if (!mxStencilRegistry.filesLoaded[fname]) 7003 { 7004 mxStencilRegistry.filesLoaded[fname] = true; 7005 7006 if (fname.toLowerCase().substring(fname.length - 4, fname.length) == '.xml') 7007 { 7008 mxStencilRegistry.loadStencilSet(fname, null); 7009 } 7010 else if (fname.toLowerCase().substring(fname.length - 3, fname.length) == '.js') 7011 { 7012 try 7013 { 7014 if (mxStencilRegistry.allowEval) 7015 { 7016 var req = mxUtils.load(fname); 7017 7018 if (req != null && req.getStatus() >= 200 && req.getStatus() <= 299) 7019 { 7020 eval.call(window, req.getText()); 7021 } 7022 } 7023 } 7024 catch (e) 7025 { 7026 if (window.console != null) 7027 { 7028 console.log('error in getStencil:', name, basename, libs, fname, e); 7029 } 7030 } 7031 } 7032 else 7033 { 7034 // FIXME: This does not yet work as the loading is triggered after 7035 // the shape was used in the graph, at which point the keys have 7036 // typically been translated in the calling method. 7037 //mxResources.add(fname); 7038 } 7039 } 7040 } 7041 7042 mxStencilRegistry.packages[basename] = 1; 7043 } 7044 } 7045 else 7046 { 7047 // Replaces '_-_' with '_' 7048 basename = basename.replace('_-_', '_'); 7049 mxStencilRegistry.loadStencilSet(STENCIL_PATH + '/' + basename + '.xml', null); 7050 } 7051 7052 result = mxStencilRegistry.stencils[name]; 7053 } 7054 } 7055 7056 return result; 7057}; 7058 7059// Returns the basename for the given stencil or null if no file must be 7060// loaded to render the given stencil. 7061mxStencilRegistry.getBasenameForStencil = function(name) 7062{ 7063 var tmp = null; 7064 7065 if (name != null && typeof name === 'string') 7066 { 7067 var parts = name.split('.'); 7068 7069 if (parts.length > 0 && parts[0] == 'mxgraph') 7070 { 7071 tmp = parts[1]; 7072 7073 for (var i = 2; i < parts.length - 1; i++) 7074 { 7075 tmp += '/' + parts[i]; 7076 } 7077 } 7078 } 7079 7080 return tmp; 7081}; 7082 7083// Loads the given stencil set 7084mxStencilRegistry.loadStencilSet = function(stencilFile, postStencilLoad, force, async) 7085{ 7086 force = (force != null) ? force : false; 7087 7088 // Uses additional cache for detecting previous load attempts 7089 var xmlDoc = mxStencilRegistry.packages[stencilFile]; 7090 7091 if (force || xmlDoc == null) 7092 { 7093 var install = false; 7094 7095 if (xmlDoc == null) 7096 { 7097 try 7098 { 7099 if (async) 7100 { 7101 mxStencilRegistry.loadStencil(stencilFile, mxUtils.bind(this, function(xmlDoc2) 7102 { 7103 if (xmlDoc2 != null && xmlDoc2.documentElement != null) 7104 { 7105 mxStencilRegistry.packages[stencilFile] = xmlDoc2; 7106 install = true; 7107 mxStencilRegistry.parseStencilSet(xmlDoc2.documentElement, postStencilLoad, install); 7108 } 7109 })); 7110 7111 return; 7112 } 7113 else 7114 { 7115 xmlDoc = mxStencilRegistry.loadStencil(stencilFile); 7116 mxStencilRegistry.packages[stencilFile] = xmlDoc; 7117 install = true; 7118 } 7119 } 7120 catch (e) 7121 { 7122 if (window.console != null) 7123 { 7124 console.log('error in loadStencilSet:', stencilFile, e); 7125 } 7126 } 7127 } 7128 7129 if (xmlDoc != null && xmlDoc.documentElement != null) 7130 { 7131 mxStencilRegistry.parseStencilSet(xmlDoc.documentElement, postStencilLoad, install); 7132 } 7133 } 7134}; 7135 7136// Loads the given stencil XML file. 7137mxStencilRegistry.loadStencil = function(filename, fn) 7138{ 7139 if (fn != null) 7140 { 7141 var req = mxUtils.get(filename, mxUtils.bind(this, function(req) 7142 { 7143 fn((req.getStatus() >= 200 && req.getStatus() <= 299) ? req.getXml() : null); 7144 })); 7145 } 7146 else 7147 { 7148 return mxUtils.load(filename).getXml(); 7149 } 7150}; 7151 7152// Takes array of strings 7153mxStencilRegistry.parseStencilSets = function(stencils) 7154{ 7155 for (var i = 0; i < stencils.length; i++) 7156 { 7157 mxStencilRegistry.parseStencilSet(mxUtils.parseXml(stencils[i]).documentElement); 7158 } 7159}; 7160 7161// Parses the given stencil set 7162mxStencilRegistry.parseStencilSet = function(root, postStencilLoad, install) 7163{ 7164 if (root.nodeName == 'stencils') 7165 { 7166 var shapes = root.firstChild; 7167 7168 while (shapes != null) 7169 { 7170 if (shapes.nodeName == 'shapes') 7171 { 7172 mxStencilRegistry.parseStencilSet(shapes, postStencilLoad, install); 7173 } 7174 7175 shapes = shapes.nextSibling; 7176 } 7177 } 7178 else 7179 { 7180 install = (install != null) ? install : true; 7181 var shape = root.firstChild; 7182 var packageName = ''; 7183 var name = root.getAttribute('name'); 7184 7185 if (name != null) 7186 { 7187 packageName = name + '.'; 7188 } 7189 7190 while (shape != null) 7191 { 7192 if (shape.nodeType == mxConstants.NODETYPE_ELEMENT) 7193 { 7194 name = shape.getAttribute('name'); 7195 7196 if (name != null) 7197 { 7198 packageName = packageName.toLowerCase(); 7199 var stencilName = name.replace(/ /g,"_"); 7200 7201 if (install) 7202 { 7203 mxStencilRegistry.addStencil(packageName + stencilName.toLowerCase(), new mxStencil(shape)); 7204 } 7205 7206 if (postStencilLoad != null) 7207 { 7208 var w = shape.getAttribute('w'); 7209 var h = shape.getAttribute('h'); 7210 7211 w = (w == null) ? 80 : parseInt(w, 10); 7212 h = (h == null) ? 80 : parseInt(h, 10); 7213 7214 postStencilLoad(packageName, stencilName, name, w, h); 7215 } 7216 } 7217 } 7218 7219 shape = shape.nextSibling; 7220 } 7221 } 7222}; 7223 7224/** 7225 * These overrides are only added if mxVertexHandler is defined (ie. not in embedded graph) 7226 */ 7227if (typeof mxVertexHandler != 'undefined') 7228{ 7229 (function() 7230 { 7231 // Sets colors for handles 7232 mxConstants.HANDLE_FILLCOLOR = '#29b6f2'; 7233 mxConstants.HANDLE_STROKECOLOR = '#0088cf'; 7234 mxConstants.VERTEX_SELECTION_COLOR = '#00a8ff'; 7235 mxConstants.OUTLINE_COLOR = '#00a8ff'; 7236 mxConstants.OUTLINE_HANDLE_FILLCOLOR = '#99ccff'; 7237 mxConstants.OUTLINE_HANDLE_STROKECOLOR = '#00a8ff'; 7238 mxConstants.CONNECT_HANDLE_FILLCOLOR = '#cee7ff'; 7239 mxConstants.EDGE_SELECTION_COLOR = '#00a8ff'; 7240 mxConstants.DEFAULT_VALID_COLOR = '#00a8ff'; 7241 mxConstants.LABEL_HANDLE_FILLCOLOR = '#cee7ff'; 7242 mxConstants.GUIDE_COLOR = '#0088cf'; 7243 mxConstants.HIGHLIGHT_OPACITY = 30; 7244 mxConstants.HIGHLIGHT_SIZE = 5; 7245 7246 // Enables snapping to off-grid terminals for edge waypoints 7247 mxEdgeHandler.prototype.snapToTerminals = true; 7248 7249 // Enables guides 7250 mxGraphHandler.prototype.guidesEnabled = true; 7251 7252 // Removes parents where all child cells are moved out 7253 mxGraphHandler.prototype.removeEmptyParents = true; 7254 7255 // Enables fading of rubberband 7256 mxRubberband.prototype.fadeOut = true; 7257 7258 // Alt-move disables guides 7259 mxGuide.prototype.isEnabledForEvent = function(evt) 7260 { 7261 return !mxEvent.isAltDown(evt); 7262 }; 7263 7264 // Ignores all table cells in layouts 7265 var graphLayoutIsVertexIgnored = mxGraphLayout.prototype.isVertexIgnored; 7266 mxGraphLayout.prototype.isVertexIgnored = function(vertex) 7267 { 7268 return graphLayoutIsVertexIgnored.apply(this, arguments) || 7269 this.graph.isTableRow(vertex) || this.graph.isTableCell(vertex); 7270 }; 7271 7272 // Adds support for ignoreEdge style 7273 var graphLayoutIsEdgeIgnored = mxGraphLayout.prototype.isEdgeIgnored; 7274 mxGraphLayout.prototype.isEdgeIgnored = function(edge) 7275 { 7276 return graphLayoutIsEdgeIgnored.apply(this, arguments) || 7277 this.graph.isEdgeIgnored(edge); 7278 }; 7279 7280 // Extends connection handler to enable ctrl+drag for cloning source cell 7281 // since copyOnConnect is now disabled by default 7282 var mxConnectionHandlerCreateTarget = mxConnectionHandler.prototype.isCreateTarget; 7283 mxConnectionHandler.prototype.isCreateTarget = function(evt) 7284 { 7285 return this.graph.isCloneEvent(evt) || mxConnectionHandlerCreateTarget.apply(this, arguments); 7286 }; 7287 7288 // Overrides highlight shape for connection points 7289 mxConstraintHandler.prototype.createHighlightShape = function() 7290 { 7291 var hl = new mxEllipse(null, this.highlightColor, this.highlightColor, 0); 7292 hl.opacity = mxConstants.HIGHLIGHT_OPACITY; 7293 7294 return hl; 7295 }; 7296 7297 // Overrides edge preview to use current edge shape and default style 7298 mxConnectionHandler.prototype.livePreview = true; 7299 mxConnectionHandler.prototype.cursor = 'crosshair'; 7300 7301 // Uses current edge style for connect preview 7302 mxConnectionHandler.prototype.createEdgeState = function(me) 7303 { 7304 var style = this.graph.createCurrentEdgeStyle(); 7305 var edge = this.graph.createEdge(null, null, null, null, null, style); 7306 var state = new mxCellState(this.graph.view, edge, this.graph.getCellStyle(edge)); 7307 7308 for (var key in this.graph.currentEdgeStyle) 7309 { 7310 state.style[key] = this.graph.currentEdgeStyle[key]; 7311 } 7312 7313 state.style = this.graph.postProcessCellStyle(state.style); 7314 7315 return state; 7316 }; 7317 7318 // Overrides dashed state with current edge style 7319 var connectionHandlerCreateShape = mxConnectionHandler.prototype.createShape; 7320 mxConnectionHandler.prototype.createShape = function() 7321 { 7322 var shape = connectionHandlerCreateShape.apply(this, arguments); 7323 7324 shape.isDashed = this.graph.currentEdgeStyle[mxConstants.STYLE_DASHED] == '1'; 7325 7326 return shape; 7327 } 7328 7329 // Overrides live preview to keep current style 7330 mxConnectionHandler.prototype.updatePreview = function(valid) 7331 { 7332 // do not change color of preview 7333 }; 7334 7335 // Overrides connection handler to ignore edges instead of not allowing connections 7336 var mxConnectionHandlerCreateMarker = mxConnectionHandler.prototype.createMarker; 7337 mxConnectionHandler.prototype.createMarker = function() 7338 { 7339 var marker = mxConnectionHandlerCreateMarker.apply(this, arguments); 7340 7341 var markerGetCell = marker.getCell; 7342 marker.getCell = mxUtils.bind(this, function(me) 7343 { 7344 var result = markerGetCell.apply(this, arguments); 7345 7346 this.error = null; 7347 7348 return result; 7349 }); 7350 7351 return marker; 7352 }; 7353 7354 /** 7355 * 7356 */ 7357 Graph.prototype.defaultVertexStyle = {}; 7358 7359 /** 7360 * Contains the default style for edges. 7361 */ 7362 Graph.prototype.defaultEdgeStyle = {'edgeStyle': 'orthogonalEdgeStyle', 'rounded': '0', 7363 'jettySize': 'auto', 'orthogonalLoop': '1'}; 7364 7365 /** 7366 * Returns the current edge style as a string. 7367 */ 7368 Graph.prototype.createCurrentEdgeStyle = function() 7369 { 7370 var style = 'edgeStyle=' + (this.currentEdgeStyle['edgeStyle'] || 'none') + ';'; 7371 var keys = ['shape', 'curved', 'rounded', 'comic', 'sketch', 'fillWeight', 'hachureGap', 7372 'hachureAngle', 'jiggle', 'disableMultiStroke', 'disableMultiStrokeFill', 'fillStyle', 7373 'curveFitting', 'simplification', 'comicStyle', 'jumpStyle', 'jumpSize']; 7374 7375 for (var i = 0; i < keys.length; i++) 7376 { 7377 if (this.currentEdgeStyle[keys[i]] != null) 7378 { 7379 style += keys[i] + '=' + this.currentEdgeStyle[keys[i]] + ';'; 7380 } 7381 } 7382 7383 // Overrides the global default to match the default edge style 7384 if (this.currentEdgeStyle['orthogonalLoop'] != null) 7385 { 7386 style += 'orthogonalLoop=' + this.currentEdgeStyle['orthogonalLoop'] + ';'; 7387 } 7388 else if (Graph.prototype.defaultEdgeStyle['orthogonalLoop'] != null) 7389 { 7390 style += 'orthogonalLoop=' + Graph.prototype.defaultEdgeStyle['orthogonalLoop'] + ';'; 7391 } 7392 7393 // Overrides the global default to match the default edge style 7394 if (this.currentEdgeStyle['jettySize'] != null) 7395 { 7396 style += 'jettySize=' + this.currentEdgeStyle['jettySize'] + ';'; 7397 } 7398 else if (Graph.prototype.defaultEdgeStyle['jettySize'] != null) 7399 { 7400 style += 'jettySize=' + Graph.prototype.defaultEdgeStyle['jettySize'] + ';'; 7401 } 7402 7403 // Special logic for custom property of elbowEdgeStyle 7404 if (this.currentEdgeStyle['edgeStyle'] == 'elbowEdgeStyle' && this.currentEdgeStyle['elbow'] != null) 7405 { 7406 style += 'elbow=' + this.currentEdgeStyle['elbow'] + ';'; 7407 } 7408 7409 if (this.currentEdgeStyle['html'] != null) 7410 { 7411 style += 'html=' + this.currentEdgeStyle['html'] + ';'; 7412 } 7413 else 7414 { 7415 style += 'html=1;'; 7416 } 7417 7418 return style; 7419 }; 7420 7421 /** 7422 * Removes implicit styles from cell styles so that dark mode works using the 7423 * default values from the stylesheet. 7424 */ 7425 Graph.prototype.updateCellStyles = function(key, value, cells) 7426 { 7427 this.model.beginUpdate(); 7428 try 7429 { 7430 for (var i = 0; i < cells.length; i++) 7431 { 7432 if (this.model.isVertex(cells[i]) || this.model.isEdge(cells[i])) 7433 { 7434 this.setCellStyles(key, null, [cells[i]]); 7435 var style = this.getCellStyle(cells[i]); 7436 var temp = style[key]; 7437 7438 if (value != ((temp == null) ? mxConstants.NONE : temp)) 7439 { 7440 this.setCellStyles(key, value, [cells[i]]); 7441 } 7442 } 7443 } 7444 } 7445 finally 7446 { 7447 this.model.endUpdate(); 7448 } 7449 }; 7450 7451 /** 7452 * Hook for subclassers. 7453 */ 7454 Graph.prototype.getPagePadding = function() 7455 { 7456 return new mxPoint(0, 0); 7457 }; 7458 7459 /** 7460 * Loads the stylesheet for this graph. 7461 */ 7462 Graph.prototype.loadStylesheet = function() 7463 { 7464 var node = (this.themes != null) ? this.themes[this.defaultThemeName] : 7465 (!mxStyleRegistry.dynamicLoading) ? null : 7466 mxUtils.load(STYLE_PATH + '/default.xml').getDocumentElement(); 7467 7468 if (node != null) 7469 { 7470 var dec = new mxCodec(node.ownerDocument); 7471 dec.decode(node, this.getStylesheet()); 7472 } 7473 }; 7474 7475 /** 7476 * Creates lookup from object IDs to cell IDs. 7477 */ 7478 Graph.prototype.createCellLookup = function(cells, lookup) 7479 { 7480 lookup = (lookup != null) ? lookup : new Object(); 7481 7482 for (var i = 0; i < cells.length; i++) 7483 { 7484 var cell = cells[i]; 7485 lookup[mxObjectIdentity.get(cell)] = cell.getId(); 7486 var childCount = this.model.getChildCount(cell); 7487 7488 for (var j = 0; j < childCount; j++) 7489 { 7490 this.createCellLookup([this.model.getChildAt(cell, j)], lookup); 7491 } 7492 } 7493 7494 return lookup; 7495 }; 7496 7497 /** 7498 * Creates lookup from original to cloned cell IDs where mapping is 7499 * the mapping used in cloneCells and lookup is a mapping from 7500 * object IDs to cell IDs. 7501 */ 7502 Graph.prototype.createCellMapping = function(mapping, lookup, cellMapping) 7503 { 7504 cellMapping = (cellMapping != null) ? cellMapping : new Object(); 7505 7506 for (var objectId in mapping) 7507 { 7508 var cellId = lookup[objectId]; 7509 7510 if (cellMapping[cellId] == null) 7511 { 7512 // Uses empty string if clone ID was null which means 7513 // the cell was cloned but not inserted into the model. 7514 cellMapping[cellId] = mapping[objectId].getId() || ''; 7515 } 7516 } 7517 7518 return cellMapping; 7519 }; 7520 7521 /** 7522 * 7523 */ 7524 Graph.prototype.importGraphModel = function(node, dx, dy, crop) 7525 { 7526 dx = (dx != null) ? dx : 0; 7527 dy = (dy != null) ? dy : 0; 7528 7529 var codec = new mxCodec(node.ownerDocument); 7530 var tempModel = new mxGraphModel(); 7531 codec.decode(node, tempModel); 7532 var cells = [] 7533 7534 // Clones cells to remove invalid edges 7535 var cloneMap = new Object(); 7536 var cellMapping = new Object(); 7537 var layers = tempModel.getChildren(this.cloneCell(tempModel.root, 7538 this.isCloneInvalidEdges(), cloneMap)); 7539 7540 if (layers != null) 7541 { 7542 // Creates lookup from object IDs to cell IDs 7543 var lookup = this.createCellLookup([tempModel.root]); 7544 7545 // Uses copy as layers are removed from array inside loop 7546 layers = layers.slice(); 7547 7548 this.model.beginUpdate(); 7549 try 7550 { 7551 // Merges into unlocked current layer if one layer is pasted 7552 if (layers.length == 1 && !this.isCellLocked(this.getDefaultParent())) 7553 { 7554 var children = tempModel.getChildren(layers[0]); 7555 7556 if (children != null) 7557 { 7558 cells = this.moveCells(children, 7559 dx, dy, false, this.getDefaultParent()); 7560 7561 // Imported default parent maps to local default parent 7562 cellMapping[tempModel.getChildAt(tempModel.root, 0).getId()] = 7563 this.getDefaultParent().getId(); 7564 } 7565 } 7566 else 7567 { 7568 for (var i = 0; i < layers.length; i++) 7569 { 7570 var children = this.model.getChildren(this.moveCells( 7571 [layers[i]], dx, dy, false, this.model.getRoot())[0]); 7572 7573 if (children != null) 7574 { 7575 cells = cells.concat(children); 7576 } 7577 } 7578 } 7579 7580 if (cells != null) 7581 { 7582 // Adds mapping for all cloned entries from imported to local cell ID 7583 this.createCellMapping(cloneMap, lookup, cellMapping); 7584 this.updateCustomLinks(cellMapping, cells); 7585 7586 if (crop) 7587 { 7588 if (this.isGridEnabled()) 7589 { 7590 dx = this.snap(dx); 7591 dy = this.snap(dy); 7592 } 7593 7594 var bounds = this.getBoundingBoxFromGeometry(cells, true); 7595 7596 if (bounds != null) 7597 { 7598 this.moveCells(cells, dx - bounds.x, dy - bounds.y); 7599 } 7600 } 7601 } 7602 } 7603 finally 7604 { 7605 this.model.endUpdate(); 7606 } 7607 } 7608 7609 return cells; 7610 }; 7611 7612 /** 7613 * Translates this point by the given vector. 7614 * 7615 * @param {number} dx X-coordinate of the translation. 7616 * @param {number} dy Y-coordinate of the translation. 7617 */ 7618 Graph.prototype.encodeCells = function(cells) 7619 { 7620 var cloneMap = new Object(); 7621 var clones = this.cloneCells(cells, null, cloneMap); 7622 7623 // Creates a dictionary for fast lookups 7624 var dict = new mxDictionary(); 7625 7626 for (var i = 0; i < cells.length; i++) 7627 { 7628 dict.put(cells[i], true); 7629 } 7630 7631 var codec = new mxCodec(); 7632 var model = new mxGraphModel(); 7633 var parent = model.getChildAt(model.getRoot(), 0); 7634 7635 for (var i = 0; i < clones.length; i++) 7636 { 7637 model.add(parent, clones[i]); 7638 7639 // Checks for orphaned relative children and makes absolute 7640 var state = this.view.getState(cells[i]); 7641 7642 if (state != null) 7643 { 7644 var geo = this.getCellGeometry(clones[i]); 7645 7646 if (geo != null && geo.relative && !this.model.isEdge(cells[i]) && 7647 dict.get(this.model.getParent(cells[i])) == null) 7648 { 7649 geo.offset = null; 7650 geo.relative = false; 7651 geo.x = state.x / state.view.scale - state.view.translate.x; 7652 geo.y = state.y / state.view.scale - state.view.translate.y; 7653 } 7654 } 7655 } 7656 7657 this.updateCustomLinks(this.createCellMapping(cloneMap, 7658 this.createCellLookup(cells)), clones); 7659 7660 return codec.encode(model); 7661 }; 7662 7663 /** 7664 * Overridden to check for table shape. 7665 */ 7666 Graph.prototype.isSwimlane = function(cell, ignoreState) 7667 { 7668 if (cell != null && this.model.getParent(cell) != this.model.getRoot() && 7669 !this.model.isEdge(cell)) 7670 { 7671 var shape = this.getCurrentCellStyle(cell, ignoreState) 7672 [mxConstants.STYLE_SHAPE]; 7673 7674 return shape == mxConstants.SHAPE_SWIMLANE || shape == 'table'; 7675 } 7676 7677 return false; 7678 }; 7679 7680 /** 7681 * Overridden to add expand style. 7682 */ 7683 var graphIsExtendParent = Graph.prototype.isExtendParent; 7684 Graph.prototype.isExtendParent = function(cell) 7685 { 7686 var parent = this.model.getParent(cell); 7687 7688 if (parent != null) 7689 { 7690 var style = this.getCurrentCellStyle(parent); 7691 7692 if (style['expand'] != null) 7693 { 7694 return style['expand'] != '0'; 7695 } 7696 } 7697 7698 return graphIsExtendParent.apply(this, arguments) && 7699 (parent == null || !this.isTable(parent)); 7700 }; 7701 7702 /** 7703 * Overridden to use table cell instead of table as parent. 7704 */ 7705 var graphSplitEdge = Graph.prototype.splitEdge; 7706 Graph.prototype.splitEdge = function(edge, cells, newEdge, dx, dy, x, y, parent) 7707 { 7708 if (parent == null) 7709 { 7710 parent = this.model.getParent(edge); 7711 7712 if (this.isTable(parent) || this.isTableRow(parent)) 7713 { 7714 parent = this.getCellAt(x, y, null, true, false); 7715 } 7716 } 7717 7718 var newEdge = null; 7719 7720 this.model.beginUpdate(); 7721 try 7722 { 7723 var newEdge = graphSplitEdge.apply(this, [edge, cells, newEdge, dx, dy, x, y, parent]); 7724 7725 // Removes cloned value on first segment 7726 this.model.setValue(newEdge, ''); 7727 7728 // Removes child labels on first or second segment depending on coordinate 7729 // LATER: Split and reposition labels based on x and y 7730 var sourceLabels = this.getChildCells(newEdge, true); 7731 7732 for (var i = 0; i < sourceLabels.length; i++) 7733 { 7734 var geo = this.getCellGeometry(sourceLabels[i]); 7735 7736 if (geo != null && geo.relative && geo.x > 0) 7737 { 7738 this.model.remove(sourceLabels[i]); 7739 } 7740 } 7741 7742 var targetLabels = this.getChildCells(edge, true); 7743 7744 for (var i = 0; i < targetLabels.length; i++) 7745 { 7746 var geo = this.getCellGeometry(targetLabels[i]); 7747 7748 if (geo != null && geo.relative && geo.x <= 0) 7749 { 7750 this.model.remove(targetLabels[i]); 7751 } 7752 } 7753 7754 // Removes end arrow and target perimetr Spacing on first segment, start arrow on second segment 7755 this.setCellStyles(mxConstants.STYLE_TARGET_PERIMETER_SPACING, null, [newEdge]); 7756 this.setCellStyles(mxConstants.STYLE_ENDARROW, mxConstants.NONE, [newEdge]); 7757 7758 // Removes start arrow and source perimeter spacing on second segment 7759 this.setCellStyles(mxConstants.STYLE_SOURCE_PERIMETER_SPACING, null, [edge]); 7760 this.setCellStyles(mxConstants.STYLE_STARTARROW, mxConstants.NONE, [edge]); 7761 7762 // Removes entryX/Y and exitX/Y if snapToPoint is used 7763 var target = this.model.getTerminal(newEdge, false); 7764 7765 if (target != null) 7766 { 7767 var style = this.getCurrentCellStyle(target); 7768 7769 if (style != null && style['snapToPoint'] == '1') 7770 { 7771 this.setCellStyles(mxConstants.STYLE_EXIT_X, null, [edge]); 7772 this.setCellStyles(mxConstants.STYLE_EXIT_Y, null, [edge]); 7773 this.setCellStyles(mxConstants.STYLE_ENTRY_X, null, [newEdge]); 7774 this.setCellStyles(mxConstants.STYLE_ENTRY_Y, null, [newEdge]); 7775 } 7776 } 7777 7778 } 7779 finally 7780 { 7781 this.model.endUpdate(); 7782 } 7783 7784 return newEdge; 7785 }; 7786 7787 /** 7788 * Overridden to flatten cell hierarchy for selecting next and previous. 7789 */ 7790 var graphSelectCell = Graph.prototype.selectCell; 7791 Graph.prototype.selectCell = function(isNext, isParent, isChild) 7792 { 7793 if (isParent || isChild) 7794 { 7795 graphSelectCell.apply(this, arguments); 7796 } 7797 else 7798 { 7799 var cell = this.getSelectionCell(); 7800 var index = null; 7801 var cells = []; 7802 7803 // LATER: Reverse traverse order for !isNext 7804 var flatten = mxUtils.bind(this, function(temp) 7805 { 7806 if (this.view.getState(temp) != null && 7807 (this.model.isVertex(temp) || 7808 this.model.isEdge(temp))) 7809 { 7810 cells.push(temp); 7811 7812 if (temp == cell) 7813 { 7814 index = cells.length - 1; 7815 } 7816 else if ((isNext && cell == null && cells.length > 0) || 7817 (index != null && ((isNext && cells.length > index)) || 7818 (!isNext && index > 0))) 7819 { 7820 return; 7821 } 7822 } 7823 7824 for (var i = 0; i < this.model.getChildCount(temp); i++) 7825 { 7826 flatten(this.model.getChildAt(temp, i)); 7827 } 7828 }); 7829 7830 flatten(this.model.root); 7831 7832 if (cells.length > 0) 7833 { 7834 if (index != null) 7835 { 7836 index = mxUtils.mod(index + ((isNext) ? 1 : -1), cells.length) 7837 } 7838 else 7839 { 7840 index = 0; 7841 } 7842 7843 this.setSelectionCell(cells[index]); 7844 } 7845 } 7846 }; 7847 7848 /** 7849 * Swaps UML Lifelines. 7850 */ 7851 Graph.prototype.swapUmlLifelines = function(cells, target) 7852 { 7853 var result = false; 7854 7855 if (target != null && cells.length == 1) 7856 { 7857 var targetState = this.view.getState(target); 7858 var sourceState = this.view.getState(cells[0]); 7859 7860 if (targetState != null && sourceState != null && 7861 targetState.style['shape'] == 'umlLifeline' && 7862 sourceState.style['shape'] == 'umlLifeline') 7863 { 7864 var g1 = this.getCellGeometry(target); 7865 var g2 = this.getCellGeometry(cells[0]); 7866 7867 if (g1 != null && g2 != null) 7868 { 7869 var ng1 = g1.clone(); 7870 var ng2 = g2.clone(); 7871 ng2.x = ng1.x; 7872 ng2.y = ng1.y; 7873 ng1.x = g2.x; 7874 ng1.y = g2.y; 7875 7876 this.model.beginUpdate(); 7877 try 7878 { 7879 this.model.setGeometry(target, ng1); 7880 this.model.setGeometry(cells[0], ng2); 7881 } 7882 finally 7883 { 7884 this.model.endUpdate(); 7885 } 7886 7887 result = true; 7888 } 7889 } 7890 } 7891 7892 return result; 7893 }; 7894 7895 /** 7896 * Overrides cloning cells in moveCells. 7897 */ 7898 var graphMoveCells = Graph.prototype.moveCells; 7899 Graph.prototype.moveCells = function(cells, dx, dy, clone, target, evt, mapping) 7900 { 7901 if (!clone && this.swapUmlLifelines(cells, target)) 7902 { 7903 return cells; 7904 } 7905 7906 mapping = (mapping != null) ? mapping : new Object(); 7907 7908 // Replaces source tables with rows 7909 if (this.isTable(target)) 7910 { 7911 var newCells = []; 7912 7913 for (var i = 0; i < cells.length; i++) 7914 { 7915 if (this.isTable(cells[i])) 7916 { 7917 newCells = newCells.concat(this.model.getChildCells(cells[i], true).reverse()); 7918 } 7919 else 7920 { 7921 newCells.push(cells[i]); 7922 } 7923 } 7924 7925 cells = newCells; 7926 } 7927 7928 this.model.beginUpdate(); 7929 try 7930 { 7931 // Updates source and target table heights and matches 7932 // column count for moving rows between tables 7933 var sourceTables = []; 7934 7935 for (var i = 0; i < cells.length; i++) 7936 { 7937 if (target != null && this.isTableRow(cells[i])) 7938 { 7939 var parent = this.model.getParent(cells[i]); 7940 var row = this.getCellGeometry(cells[i]); 7941 7942 if (this.isTable(parent)) 7943 { 7944 sourceTables.push(parent); 7945 } 7946 7947 if (parent != null && row != null && 7948 this.isTable(parent) && 7949 this.isTable(target) && 7950 (clone || parent != target)) 7951 { 7952 if (!clone) 7953 { 7954 var table = this.getCellGeometry(parent); 7955 7956 if (table != null) 7957 { 7958 table = table.clone(); 7959 table.height -= row.height; 7960 this.model.setGeometry(parent, table); 7961 } 7962 } 7963 7964 var table = this.getCellGeometry(target); 7965 7966 if (table != null) 7967 { 7968 table = table.clone(); 7969 table.height += row.height; 7970 this.model.setGeometry(target, table); 7971 } 7972 7973 // Matches column count 7974 var rows = this.model.getChildCells(target, true); 7975 7976 if (rows.length > 0) 7977 { 7978 cells[i] = (clone) ? this.cloneCell(cells[i]) : cells[i]; 7979 var sourceCols = this.model.getChildCells(cells[i], true); 7980 var cols = this.model.getChildCells(rows[0], true); 7981 var count = cols.length - sourceCols.length; 7982 7983 if (count > 0) 7984 { 7985 for (var j = 0; j < count; j++) 7986 { 7987 var col = this.cloneCell(sourceCols[sourceCols.length - 1]); 7988 7989 if (col != null) 7990 { 7991 col.value = ''; 7992 7993 this.model.add(cells[i], col); 7994 } 7995 } 7996 } 7997 else if (count < 0) 7998 { 7999 for (var j = 0; j > count; j--) 8000 { 8001 this.model.remove(sourceCols[sourceCols.length + j - 1]); 8002 } 8003 } 8004 8005 // Updates column widths 8006 sourceCols = this.model.getChildCells(cells[i], true); 8007 8008 for (var j = 0; j < cols.length; j++) 8009 { 8010 var geo = this.getCellGeometry(cols[j]); 8011 var geo2 = this.getCellGeometry(sourceCols[j]); 8012 8013 if (geo != null && geo2 != null) 8014 { 8015 geo2 = geo2.clone(); 8016 geo2.width = geo.width; 8017 8018 this.model.setGeometry(sourceCols[j], geo2); 8019 } 8020 } 8021 } 8022 } 8023 } 8024 } 8025 8026 var result = graphMoveCells.apply(this, arguments); 8027 8028 // Removes empty tables 8029 for (var i = 0; i < sourceTables.length; i++) 8030 { 8031 if (!clone && this.model.contains(sourceTables[i]) && 8032 this.model.getChildCount(sourceTables[i]) == 0) 8033 { 8034 this.model.remove(sourceTables[i]); 8035 } 8036 } 8037 8038 if (clone) 8039 { 8040 this.updateCustomLinks(this.createCellMapping(mapping, 8041 this.createCellLookup(cells)), result); 8042 } 8043 } 8044 finally 8045 { 8046 this.model.endUpdate(); 8047 } 8048 8049 return result; 8050 }; 8051 8052 /** 8053 * Overriddes to delete label for table cells. 8054 */ 8055 var graphRemoveCells = Graph.prototype.removeCells; 8056 Graph.prototype.removeCells = function(cells, includeEdges) 8057 { 8058 var result = []; 8059 8060 this.model.beginUpdate(); 8061 try 8062 { 8063 // Clears labels on table cells 8064 for (var i = 0; i < cells.length; i++) 8065 { 8066 if (this.isTableCell(cells[i])) 8067 { 8068 var row = this.model.getParent(cells[i]); 8069 var table = this.model.getParent(row); 8070 8071 // Removes table if one cell in one row left 8072 if (this.model.getChildCount(row) == 1 && 8073 this.model.getChildCount(table) == 1) 8074 { 8075 if (mxUtils.indexOf(cells, table) < 0 && 8076 mxUtils.indexOf(result, table) < 0) 8077 { 8078 result.push(table); 8079 } 8080 } 8081 else 8082 { 8083 this.labelChanged(cells[i], ''); 8084 } 8085 } 8086 else 8087 { 8088 // Deletes table if all rows are removed 8089 if (this.isTableRow(cells[i])) 8090 { 8091 var table = this.model.getParent(cells[i]); 8092 8093 if (mxUtils.indexOf(cells, table) < 0 && 8094 mxUtils.indexOf(result, table) < 0) 8095 { 8096 var rows = this.model.getChildCells(table, true); 8097 var deleteCount = 0; 8098 8099 for (var j = 0; j < rows.length; j++) 8100 { 8101 if (mxUtils.indexOf(cells, rows[j]) >= 0) 8102 { 8103 deleteCount++; 8104 } 8105 } 8106 8107 if (deleteCount == rows.length) 8108 { 8109 result.push(table); 8110 } 8111 } 8112 } 8113 8114 result.push(cells[i]); 8115 } 8116 } 8117 8118 result = graphRemoveCells.apply(this, [result, includeEdges]); 8119 } 8120 finally 8121 { 8122 this.model.endUpdate(); 8123 } 8124 8125 return result; 8126 }; 8127 8128 /** 8129 * Updates cells IDs for custom links in the given cells using an 8130 * optional graph to avoid changing the undo history. 8131 */ 8132 Graph.prototype.updateCustomLinks = function(mapping, cells, graph) 8133 { 8134 graph = (graph != null) ? graph : new Graph(); 8135 8136 for (var i = 0; i < cells.length; i++) 8137 { 8138 if (cells[i] != null) 8139 { 8140 graph.updateCustomLinksForCell(mapping, cells[i], graph); 8141 } 8142 } 8143 }; 8144 8145 /** 8146 * Updates cell IDs in custom links on the given cell and its label. 8147 */ 8148 Graph.prototype.updateCustomLinksForCell = function(mapping, cell) 8149 { 8150 this.doUpdateCustomLinksForCell(mapping, cell); 8151 var childCount = this.model.getChildCount(cell); 8152 8153 for (var i = 0; i < childCount; i++) 8154 { 8155 this.updateCustomLinksForCell(mapping, 8156 this.model.getChildAt(cell, i)); 8157 } 8158 }; 8159 8160 /** 8161 * Updates cell IDs in custom links on the given cell and its label. 8162 */ 8163 Graph.prototype.doUpdateCustomLinksForCell = function(mapping, cell) 8164 { 8165 // Hook for subclassers 8166 }; 8167 8168 /** 8169 * Overrides method to provide connection constraints for shapes. 8170 */ 8171 Graph.prototype.getAllConnectionConstraints = function(terminal, source) 8172 { 8173 if (terminal != null) 8174 { 8175 var constraints = mxUtils.getValue(terminal.style, 'points', null); 8176 8177 if (constraints != null) 8178 { 8179 // Requires an array of arrays with x, y (0..1), an optional 8180 // [perimeter (0 or 1), dx, and dy] eg. points=[[0,0,1,-10,10],[0,1,0],[1,1]] 8181 var result = []; 8182 8183 try 8184 { 8185 var c = JSON.parse(constraints); 8186 8187 for (var i = 0; i < c.length; i++) 8188 { 8189 var tmp = c[i]; 8190 result.push(new mxConnectionConstraint(new mxPoint(tmp[0], tmp[1]), (tmp.length > 2) ? tmp[2] != '0' : true, 8191 null, (tmp.length > 3) ? tmp[3] : 0, (tmp.length > 4) ? tmp[4] : 0)); 8192 } 8193 } 8194 catch (e) 8195 { 8196 // ignore 8197 } 8198 8199 return result; 8200 } 8201 else if (terminal.shape != null && terminal.shape.bounds != null) 8202 { 8203 var dir = terminal.shape.direction; 8204 var bounds = terminal.shape.bounds; 8205 var scale = terminal.shape.scale; 8206 var w = bounds.width / scale; 8207 var h = bounds.height / scale; 8208 8209 if (dir == mxConstants.DIRECTION_NORTH || dir == mxConstants.DIRECTION_SOUTH) 8210 { 8211 var tmp = w; 8212 w = h; 8213 h = tmp; 8214 } 8215 8216 constraints = terminal.shape.getConstraints(terminal.style, w, h); 8217 8218 if (constraints != null) 8219 { 8220 return constraints; 8221 } 8222 else if (terminal.shape.stencil != null && terminal.shape.stencil.constraints != null) 8223 { 8224 return terminal.shape.stencil.constraints; 8225 } 8226 else if (terminal.shape.constraints != null) 8227 { 8228 return terminal.shape.constraints; 8229 } 8230 } 8231 } 8232 8233 return null; 8234 }; 8235 8236 /** 8237 * Inverts the elbow edge style without removing existing styles. 8238 */ 8239 Graph.prototype.flipEdge = function(edge) 8240 { 8241 if (edge != null) 8242 { 8243 var style = this.getCurrentCellStyle(edge); 8244 var elbow = mxUtils.getValue(style, mxConstants.STYLE_ELBOW, 8245 mxConstants.ELBOW_HORIZONTAL); 8246 var value = (elbow == mxConstants.ELBOW_HORIZONTAL) ? 8247 mxConstants.ELBOW_VERTICAL : mxConstants.ELBOW_HORIZONTAL; 8248 this.setCellStyles(mxConstants.STYLE_ELBOW, value, [edge]); 8249 } 8250 }; 8251 8252 /** 8253 * Disables drill-down for non-swimlanes. 8254 */ 8255 Graph.prototype.isValidRoot = function(cell) 8256 { 8257 // Counts non-relative children 8258 var childCount = this.model.getChildCount(cell); 8259 var realChildCount = 0; 8260 8261 for (var i = 0; i < childCount; i++) 8262 { 8263 var child = this.model.getChildAt(cell, i); 8264 8265 if (this.model.isVertex(child)) 8266 { 8267 var geometry = this.getCellGeometry(child); 8268 8269 if (geometry != null && !geometry.relative) 8270 { 8271 realChildCount++; 8272 } 8273 } 8274 } 8275 8276 return realChildCount > 0 || this.isContainer(cell); 8277 }; 8278 8279 /** 8280 * Disables drill-down for non-swimlanes. 8281 */ 8282 Graph.prototype.isValidDropTarget = function(cell, cells, evt) 8283 { 8284 var style = this.getCurrentCellStyle(cell); 8285 var tables = true; 8286 var rows = true; 8287 8288 for (var i = 0; i < cells.length && rows; i++) 8289 { 8290 tables = tables && this.isTable(cells[i]); 8291 rows = rows && this.isTableRow(cells[i]); 8292 } 8293 8294 return ((mxUtils.getValue(style, 'part', '0') != '1' || this.isContainer(cell)) && 8295 mxUtils.getValue(style, 'dropTarget', '1') != '0' && 8296 (mxGraph.prototype.isValidDropTarget.apply(this, arguments) || 8297 this.isContainer(cell)) && !this.isTableRow(cell) && 8298 (!this.isTable(cell) || rows || tables)) && !this.isCellLocked(cell); 8299 }; 8300 8301 /** 8302 * Overrides createGroupCell to set the group style for new groups to 'group'. 8303 */ 8304 Graph.prototype.createGroupCell = function() 8305 { 8306 var group = mxGraph.prototype.createGroupCell.apply(this, arguments); 8307 group.setStyle('group'); 8308 8309 return group; 8310 }; 8311 8312 /** 8313 * Disables extending parents with stack layouts on add 8314 */ 8315 Graph.prototype.isExtendParentsOnAdd = function(cell) 8316 { 8317 var result = mxGraph.prototype.isExtendParentsOnAdd.apply(this, arguments); 8318 8319 if (result && cell != null && this.layoutManager != null) 8320 { 8321 var parent = this.model.getParent(cell); 8322 8323 if (parent != null) 8324 { 8325 var layout = this.layoutManager.getLayout(parent); 8326 8327 if (layout != null && layout.constructor == mxStackLayout) 8328 { 8329 result = false; 8330 } 8331 } 8332 } 8333 8334 return result; 8335 }; 8336 8337 /** 8338 * Overrides autosize to add a border. 8339 */ 8340 Graph.prototype.getPreferredSizeForCell = function(cell) 8341 { 8342 var result = mxGraph.prototype.getPreferredSizeForCell.apply(this, arguments); 8343 8344 // Adds buffer 8345 if (result != null) 8346 { 8347 result.width += 10; 8348 result.height += 4; 8349 8350 if (this.gridEnabled) 8351 { 8352 result.width = this.snap(result.width); 8353 result.height = this.snap(result.height); 8354 } 8355 } 8356 8357 return result; 8358 } 8359 8360 /** 8361 * Turns the given cells and returns the changed cells. 8362 */ 8363 Graph.prototype.turnShapes = function(cells, backwards) 8364 { 8365 var model = this.getModel(); 8366 var select = []; 8367 8368 model.beginUpdate(); 8369 try 8370 { 8371 for (var i = 0; i < cells.length; i++) 8372 { 8373 var cell = cells[i]; 8374 8375 if (model.isEdge(cell)) 8376 { 8377 var src = model.getTerminal(cell, true); 8378 var trg = model.getTerminal(cell, false); 8379 8380 model.setTerminal(cell, trg, true); 8381 model.setTerminal(cell, src, false); 8382 8383 var geo = model.getGeometry(cell); 8384 8385 if (geo != null) 8386 { 8387 geo = geo.clone(); 8388 8389 if (geo.points != null) 8390 { 8391 geo.points.reverse(); 8392 } 8393 8394 var sp = geo.getTerminalPoint(true); 8395 var tp = geo.getTerminalPoint(false) 8396 8397 geo.setTerminalPoint(sp, false); 8398 geo.setTerminalPoint(tp, true); 8399 model.setGeometry(cell, geo); 8400 8401 // Inverts constraints 8402 var edgeState = this.view.getState(cell); 8403 var sourceState = this.view.getState(src); 8404 var targetState = this.view.getState(trg); 8405 8406 if (edgeState != null) 8407 { 8408 var sc = (sourceState != null) ? this.getConnectionConstraint(edgeState, sourceState, true) : null; 8409 var tc = (targetState != null) ? this.getConnectionConstraint(edgeState, targetState, false) : null; 8410 8411 this.setConnectionConstraint(cell, src, true, tc); 8412 this.setConnectionConstraint(cell, trg, false, sc); 8413 8414 // Inverts perimeter spacings 8415 var temp = mxUtils.getValue(edgeState.style, mxConstants.STYLE_SOURCE_PERIMETER_SPACING); 8416 this.setCellStyles(mxConstants.STYLE_SOURCE_PERIMETER_SPACING, mxUtils.getValue( 8417 edgeState.style, mxConstants.STYLE_TARGET_PERIMETER_SPACING), [cell]); 8418 this.setCellStyles(mxConstants.STYLE_TARGET_PERIMETER_SPACING, temp, [cell]); 8419 } 8420 8421 select.push(cell); 8422 } 8423 } 8424 else if (model.isVertex(cell)) 8425 { 8426 var geo = this.getCellGeometry(cell); 8427 8428 if (geo != null) 8429 { 8430 // Rotates the size and position in the geometry 8431 if (!this.isTable(cell) && !this.isTableRow(cell) && 8432 !this.isTableCell(cell) && !this.isSwimlane(cell)) 8433 { 8434 geo = geo.clone(); 8435 geo.x += geo.width / 2 - geo.height / 2; 8436 geo.y += geo.height / 2 - geo.width / 2; 8437 var tmp = geo.width; 8438 geo.width = geo.height; 8439 geo.height = tmp; 8440 model.setGeometry(cell, geo); 8441 } 8442 8443 // Reads the current direction and advances by 90 degrees 8444 var state = this.view.getState(cell); 8445 8446 if (state != null) 8447 { 8448 var dirs = [mxConstants.DIRECTION_EAST, mxConstants.DIRECTION_SOUTH, 8449 mxConstants.DIRECTION_WEST, mxConstants.DIRECTION_NORTH]; 8450 var dir = mxUtils.getValue(state.style, mxConstants.STYLE_DIRECTION, 8451 mxConstants.DIRECTION_EAST); 8452 this.setCellStyles(mxConstants.STYLE_DIRECTION, 8453 dirs[mxUtils.mod(mxUtils.indexOf(dirs, dir) + 8454 ((backwards) ? -1 : 1), dirs.length)], [cell]); 8455 } 8456 8457 select.push(cell); 8458 } 8459 } 8460 } 8461 } 8462 finally 8463 { 8464 model.endUpdate(); 8465 } 8466 8467 return select; 8468 }; 8469 8470 /** 8471 * Returns true if the given stencil contains any placeholder text. 8472 */ 8473 Graph.prototype.stencilHasPlaceholders = function(stencil) 8474 { 8475 if (stencil != null && stencil.fgNode != null) 8476 { 8477 var node = stencil.fgNode.firstChild; 8478 8479 while (node != null) 8480 { 8481 if (node.nodeName == 'text' && node.getAttribute('placeholders') == '1') 8482 { 8483 return true; 8484 } 8485 8486 node = node.nextSibling; 8487 } 8488 } 8489 8490 return false; 8491 }; 8492 8493 /** 8494 * Updates the child cells with placeholders if metadata of a 8495 * cell has changed and propagates geometry changes in tables. 8496 */ 8497 var graphProcessChange = Graph.prototype.processChange; 8498 Graph.prototype.processChange = function(change) 8499 { 8500 if (change instanceof mxGeometryChange && 8501 (this.isTableCell(change.cell) || this.isTableRow(change.cell)) && 8502 ((change.previous == null && change.geometry != null) || 8503 (change.previous != null && !change.previous.equals(change.geometry)))) 8504 { 8505 var cell = change.cell; 8506 8507 if (this.isTableCell(cell)) 8508 { 8509 cell = this.model.getParent(cell); 8510 } 8511 8512 if (this.isTableRow(cell)) 8513 { 8514 cell = this.model.getParent(cell); 8515 } 8516 8517 // Forces repaint of table with unchanged style and geometry 8518 var state = this.view.getState(cell); 8519 8520 if (state != null && state.shape != null) 8521 { 8522 this.view.invalidate(cell); 8523 state.shape.bounds = null; 8524 } 8525 } 8526 8527 graphProcessChange.apply(this, arguments); 8528 8529 if (change instanceof mxValueChange && change.cell != null && 8530 change.cell.value != null && typeof(change.cell.value) == 'object') 8531 { 8532 this.invalidateDescendantsWithPlaceholders(change.cell); 8533 } 8534 }; 8535 8536 /** 8537 * Replaces the given element with a span. 8538 */ 8539 Graph.prototype.invalidateDescendantsWithPlaceholders = function(cell) 8540 { 8541 // Invalidates all descendants with placeholders 8542 var desc = this.model.getDescendants(cell); 8543 8544 // LATER: Check if only label or tooltip have changed 8545 if (desc.length > 0) 8546 { 8547 for (var i = 0; i < desc.length; i++) 8548 { 8549 var state = this.view.getState(desc[i]); 8550 8551 if (state != null && state.shape != null && state.shape.stencil != null && 8552 this.stencilHasPlaceholders(state.shape.stencil)) 8553 { 8554 this.removeStateForCell(desc[i]); 8555 } 8556 else if (this.isReplacePlaceholders(desc[i])) 8557 { 8558 this.view.invalidate(desc[i], false, false); 8559 } 8560 } 8561 } 8562 }; 8563 8564 /** 8565 * Replaces the given element with a span. 8566 */ 8567 Graph.prototype.replaceElement = function(elt, tagName) 8568 { 8569 var span = elt.ownerDocument.createElement((tagName != null) ? tagName : 'span'); 8570 var attributes = Array.prototype.slice.call(elt.attributes); 8571 8572 while (attr = attributes.pop()) 8573 { 8574 span.setAttribute(attr.nodeName, attr.nodeValue); 8575 } 8576 8577 span.innerHTML = elt.innerHTML; 8578 elt.parentNode.replaceChild(span, elt); 8579 }; 8580 8581 /** 8582 * 8583 */ 8584 Graph.prototype.processElements = function(elt, fn) 8585 { 8586 if (elt != null) 8587 { 8588 var elts = elt.getElementsByTagName('*'); 8589 8590 for (var i = 0; i < elts.length; i++) 8591 { 8592 fn(elts[i]); 8593 } 8594 } 8595 }; 8596 8597 /** 8598 * Handles label changes for XML user objects. 8599 */ 8600 Graph.prototype.updateLabelElements = function(cells, fn, tagName) 8601 { 8602 cells = (cells != null) ? cells : this.getSelectionCells(); 8603 var div = document.createElement('div'); 8604 8605 for (var i = 0; i < cells.length; i++) 8606 { 8607 // Changes font tags inside HTML labels 8608 if (this.isHtmlLabel(cells[i])) 8609 { 8610 var label = this.convertValueToString(cells[i]); 8611 8612 if (label != null && label.length > 0) 8613 { 8614 div.innerHTML = label; 8615 var elts = div.getElementsByTagName((tagName != null) ? tagName : '*'); 8616 8617 for (var j = 0; j < elts.length; j++) 8618 { 8619 fn(elts[j]); 8620 } 8621 8622 if (div.innerHTML != label) 8623 { 8624 this.cellLabelChanged(cells[i], div.innerHTML); 8625 } 8626 } 8627 } 8628 } 8629 }; 8630 8631 /** 8632 * Handles label changes for XML user objects. 8633 */ 8634 Graph.prototype.cellLabelChanged = function(cell, value, autoSize) 8635 { 8636 // Removes all illegal control characters in user input 8637 value = Graph.zapGremlins(value); 8638 8639 this.model.beginUpdate(); 8640 try 8641 { 8642 if (cell.value != null && typeof cell.value == 'object') 8643 { 8644 if (this.isReplacePlaceholders(cell) && 8645 cell.getAttribute('placeholder') != null) 8646 { 8647 // LATER: Handle delete, name change 8648 var name = cell.getAttribute('placeholder'); 8649 var current = cell; 8650 8651 while (current != null) 8652 { 8653 if (current == this.model.getRoot() || (current.value != null && 8654 typeof(current.value) == 'object' && current.hasAttribute(name))) 8655 { 8656 this.setAttributeForCell(current, name, value); 8657 8658 break; 8659 } 8660 8661 current = this.model.getParent(current); 8662 } 8663 } 8664 8665 var tmp = cell.value.cloneNode(true); 8666 8667 if (Graph.translateDiagram && Graph.diagramLanguage != null && 8668 tmp.hasAttribute('label_' + Graph.diagramLanguage)) 8669 { 8670 tmp.setAttribute('label_' + Graph.diagramLanguage, value); 8671 } 8672 else 8673 { 8674 tmp.setAttribute('label', value); 8675 } 8676 8677 value = tmp; 8678 } 8679 8680 mxGraph.prototype.cellLabelChanged.apply(this, arguments); 8681 } 8682 finally 8683 { 8684 this.model.endUpdate(); 8685 } 8686 }; 8687 8688 /** 8689 * Removes transparent empty groups if all children are removed. 8690 */ 8691 Graph.prototype.cellsRemoved = function(cells) 8692 { 8693 if (cells != null) 8694 { 8695 var dict = new mxDictionary(); 8696 8697 for (var i = 0; i < cells.length; i++) 8698 { 8699 dict.put(cells[i], true); 8700 } 8701 8702 // LATER: Recurse up the cell hierarchy 8703 var parents = []; 8704 8705 for (var i = 0; i < cells.length; i++) 8706 { 8707 var parent = this.model.getParent(cells[i]); 8708 8709 if (parent != null && !dict.get(parent)) 8710 { 8711 dict.put(parent, true); 8712 parents.push(parent); 8713 } 8714 } 8715 8716 for (var i = 0; i < parents.length; i++) 8717 { 8718 var state = this.view.getState(parents[i]); 8719 8720 if (state != null && (this.model.isEdge(state.cell) || 8721 this.model.isVertex(state.cell)) && 8722 this.isCellDeletable(state.cell) && 8723 this.isTransparentState(state)) 8724 { 8725 var allChildren = true; 8726 8727 for (var j = 0; j < this.model.getChildCount(state.cell) && allChildren; j++) 8728 { 8729 if (!dict.get(this.model.getChildAt(state.cell, j))) 8730 { 8731 allChildren = false; 8732 } 8733 } 8734 8735 if (allChildren) 8736 { 8737 cells.push(state.cell); 8738 } 8739 } 8740 } 8741 } 8742 8743 mxGraph.prototype.cellsRemoved.apply(this, arguments); 8744 }; 8745 8746 /** 8747 * Overrides ungroup to check if group should be removed. 8748 */ 8749 Graph.prototype.removeCellsAfterUngroup = function(cells) 8750 { 8751 var cellsToRemove = []; 8752 8753 for (var i = 0; i < cells.length; i++) 8754 { 8755 if (this.isCellDeletable(cells[i]) && 8756 this.isTransparentState( 8757 this.view.getState(cells[i]))) 8758 { 8759 cellsToRemove.push(cells[i]); 8760 } 8761 } 8762 8763 cells = cellsToRemove; 8764 8765 mxGraph.prototype.removeCellsAfterUngroup.apply(this, arguments); 8766 }; 8767 8768 /** 8769 * Sets the link for the given cell. 8770 */ 8771 Graph.prototype.setLinkForCell = function(cell, link) 8772 { 8773 this.setAttributeForCell(cell, 'link', link); 8774 }; 8775 8776 /** 8777 * Sets the link for the given cell. 8778 */ 8779 Graph.prototype.setTooltipForCell = function(cell, link) 8780 { 8781 var key = 'tooltip'; 8782 8783 if (Graph.translateDiagram && Graph.diagramLanguage != null && 8784 mxUtils.isNode(cell.value) && cell.value.hasAttribute('tooltip_' + Graph.diagramLanguage)) 8785 { 8786 key = 'tooltip_' + Graph.diagramLanguage; 8787 } 8788 8789 this.setAttributeForCell(cell, key, link); 8790 }; 8791 8792 /** 8793 * Returns the cells in the model (or given array) that have all of the 8794 * given tags in their tags property. 8795 */ 8796 Graph.prototype.getAttributeForCell = function(cell, attributeName, defaultValue) 8797 { 8798 var value = (cell.value != null && typeof cell.value === 'object') ? 8799 cell.value.getAttribute(attributeName) : null; 8800 8801 return (value != null) ? value : defaultValue; 8802 }; 8803 8804 /** 8805 * Sets the link for the given cell. 8806 */ 8807 Graph.prototype.setAttributeForCell = function(cell, attributeName, attributeValue) 8808 { 8809 var value = null; 8810 8811 if (cell.value != null && typeof(cell.value) == 'object') 8812 { 8813 value = cell.value.cloneNode(true); 8814 } 8815 else 8816 { 8817 var doc = mxUtils.createXmlDocument(); 8818 8819 value = doc.createElement('UserObject'); 8820 value.setAttribute('label', cell.value || ''); 8821 } 8822 8823 if (attributeValue != null) 8824 { 8825 value.setAttribute(attributeName, attributeValue); 8826 } 8827 else 8828 { 8829 value.removeAttribute(attributeName); 8830 } 8831 8832 this.model.setValue(cell, value); 8833 }; 8834 8835 /** 8836 * Overridden to stop moving edge labels between cells. 8837 */ 8838 var graphGetDropTarget = Graph.prototype.getDropTarget; 8839 Graph.prototype.getDropTarget = function(cells, evt, cell, clone) 8840 { 8841 var model = this.getModel(); 8842 8843 // Disables drop into group if alt is pressed 8844 if (mxEvent.isAltDown(evt)) 8845 { 8846 return null; 8847 } 8848 8849 // Disables dragging edge labels out of edges 8850 for (var i = 0; i < cells.length; i++) 8851 { 8852 var parent = this.model.getParent(cells[i]); 8853 8854 if (this.model.isEdge(parent) && mxUtils.indexOf(cells, parent) < 0) 8855 { 8856 return null; 8857 } 8858 } 8859 8860 var target = graphGetDropTarget.apply(this, arguments); 8861 8862 // Always drops rows to tables 8863 var rows = true; 8864 8865 for (var i = 0; i < cells.length && rows; i++) 8866 { 8867 rows = rows && this.isTableRow(cells[i]); 8868 } 8869 8870 if (rows) 8871 { 8872 if (this.isTableCell(target)) 8873 { 8874 target = this.model.getParent(target); 8875 } 8876 8877 if (this.isTableRow(target)) 8878 { 8879 target = this.model.getParent(target); 8880 } 8881 8882 if (!this.isTable(target)) 8883 { 8884 target = null; 8885 } 8886 } 8887 8888 return target; 8889 }; 8890 8891 /** 8892 * Overrides double click handling to avoid accidental inserts of new labels in dblClick below. 8893 */ 8894 Graph.prototype.click = function(me) 8895 { 8896 mxGraph.prototype.click.call(this, me); 8897 8898 // Stores state and source for checking in dblClick 8899 this.firstClickState = me.getState(); 8900 this.firstClickSource = me.getSource(); 8901 }; 8902 8903 /** 8904 * Overrides double click handling to add the tolerance and inserting text. 8905 */ 8906 Graph.prototype.dblClick = function(evt, cell) 8907 { 8908 if (this.isEnabled()) 8909 { 8910 cell = this.insertTextForEvent(evt, cell); 8911 mxGraph.prototype.dblClick.call(this, evt, cell); 8912 } 8913 }; 8914 8915 /** 8916 * Overrides double click handling to add the tolerance and inserting text. 8917 */ 8918 Graph.prototype.insertTextForEvent = function(evt, cell) 8919 { 8920 var pt = mxUtils.convertPoint(this.container, mxEvent.getClientX(evt), mxEvent.getClientY(evt)); 8921 8922 // Automatically adds new child cells to edges on double click 8923 if (evt != null && !this.model.isVertex(cell)) 8924 { 8925 var state = (this.model.isEdge(cell)) ? this.view.getState(cell) : null; 8926 var src = mxEvent.getSource(evt); 8927 8928 if ((this.firstClickState == state && this.firstClickSource == src) && 8929 (state == null || (state.text == null || state.text.node == null || 8930 state.text.boundingBox == null || (!mxUtils.contains(state.text.boundingBox, 8931 pt.x, pt.y) && !mxUtils.isAncestorNode(state.text.node, mxEvent.getSource(evt))))) && 8932 ((state == null && !this.isCellLocked(this.getDefaultParent())) || 8933 (state != null && !this.isCellLocked(state.cell))) && 8934 (state != null || 8935 (mxClient.IS_SVG && src == this.view.getCanvas().ownerSVGElement))) 8936 { 8937 if (state == null) 8938 { 8939 state = this.view.getState(this.getCellAt(pt.x, pt.y)); 8940 } 8941 8942 cell = this.addText(pt.x, pt.y, state); 8943 } 8944 } 8945 8946 return cell; 8947 }; 8948 8949 /** 8950 * Returns a point that specifies the location for inserting cells. 8951 */ 8952 Graph.prototype.getInsertPoint = function() 8953 { 8954 var gs = this.getGridSize(); 8955 var dx = this.container.scrollLeft / this.view.scale - this.view.translate.x; 8956 var dy = this.container.scrollTop / this.view.scale - this.view.translate.y; 8957 8958 if (this.pageVisible) 8959 { 8960 var layout = this.getPageLayout(); 8961 var page = this.getPageSize(); 8962 dx = Math.max(dx, layout.x * page.width); 8963 dy = Math.max(dy, layout.y * page.height); 8964 } 8965 8966 return new mxPoint(this.snap(dx + gs), this.snap(dy + gs)); 8967 }; 8968 8969 /** 8970 * 8971 */ 8972 Graph.prototype.getFreeInsertPoint = function() 8973 { 8974 var view = this.view; 8975 var bds = this.getGraphBounds(); 8976 var pt = this.getInsertPoint(); 8977 8978 // Places at same x-coord and 2 grid sizes below existing graph 8979 var x = this.snap(Math.round(Math.max(pt.x, bds.x / view.scale - view.translate.x + 8980 ((bds.width == 0) ? 2 * this.gridSize : 0)))); 8981 var y = this.snap(Math.round(Math.max(pt.y, (bds.y + bds.height) / view.scale - view.translate.y + 8982 2 * this.gridSize))); 8983 8984 return new mxPoint(x, y); 8985 }; 8986 8987 /** 8988 * 8989 */ 8990 Graph.prototype.getCenterInsertPoint = function(bbox) 8991 { 8992 bbox = (bbox != null) ? bbox : new mxRectangle(); 8993 8994 if (mxUtils.hasScrollbars(this.container)) 8995 { 8996 return new mxPoint( 8997 this.snap(Math.round((this.container.scrollLeft + this.container.clientWidth / 2) / 8998 this.view.scale - this.view.translate.x - bbox.width / 2)), 8999 this.snap(Math.round((this.container.scrollTop + this.container.clientHeight / 2) / 9000 this.view.scale - this.view.translate.y - bbox.height / 2))); 9001 } 9002 else 9003 { 9004 return new mxPoint( 9005 this.snap(Math.round(this.container.clientWidth / 2 / this.view.scale - 9006 this.view.translate.x - bbox.width / 2)), 9007 this.snap(Math.round(this.container.clientHeight / 2 / this.view.scale - 9008 this.view.translate.y - bbox.height / 2))); 9009 } 9010 }; 9011 9012 /** 9013 * Hook for subclassers to return true if the current insert point was defined 9014 * using a mouse hover event. 9015 */ 9016 Graph.prototype.isMouseInsertPoint = function() 9017 { 9018 return false; 9019 }; 9020 9021 /** 9022 * Adds a new label at the given position and returns the new cell. State is 9023 * an optional edge state to be used as the parent for the label. Vertices 9024 * are not allowed currently as states. 9025 */ 9026 Graph.prototype.addText = function(x, y, state) 9027 { 9028 // Creates a new edge label with a predefined text 9029 var label = new mxCell(); 9030 label.value = 'Text'; 9031 label.geometry = new mxGeometry(0, 0, 0, 0); 9032 label.vertex = true; 9033 var style = 'html=1;align=center;verticalAlign=middle;resizable=0;points=[];'; 9034 9035 if (state != null && this.model.isEdge(state.cell)) 9036 { 9037 label.style = 'edgeLabel;' + style; 9038 label.geometry.relative = true; 9039 label.connectable = false; 9040 9041 // Resets the relative location stored inside the geometry 9042 var pt2 = this.view.getRelativePoint(state, x, y); 9043 label.geometry.x = Math.round(pt2.x * 10000) / 10000; 9044 label.geometry.y = Math.round(pt2.y); 9045 9046 // Resets the offset inside the geometry to find the offset from the resulting point 9047 label.geometry.offset = new mxPoint(0, 0); 9048 pt2 = this.view.getPoint(state, label.geometry); 9049 9050 var scale = this.view.scale; 9051 label.geometry.offset = new mxPoint(Math.round((x - pt2.x) / scale), Math.round((y - pt2.y) / scale)); 9052 } 9053 else 9054 { 9055 var tr = this.view.translate; 9056 label.style = 'text;' + style; 9057 label.geometry.width = 40; 9058 label.geometry.height = 20; 9059 label.geometry.x = Math.round(x / this.view.scale) - 9060 tr.x - ((state != null) ? state.origin.x : 0); 9061 label.geometry.y = Math.round(y / this.view.scale) - 9062 tr.y - ((state != null) ? state.origin.y : 0); 9063 label.style += 'autosize=1;' 9064 } 9065 9066 this.getModel().beginUpdate(); 9067 try 9068 { 9069 this.addCells([label], (state != null) ? state.cell : null); 9070 this.fireEvent(new mxEventObject('textInserted', 'cells', [label])); 9071 9072 // Updates size of text after possible change of style via event 9073 this.autoSizeCell(label); 9074 } 9075 finally 9076 { 9077 this.getModel().endUpdate(); 9078 } 9079 9080 return label; 9081 }; 9082 9083 /** 9084 * Adds a handler for clicking on shapes with links. This replaces all links in labels. 9085 */ 9086 Graph.prototype.addClickHandler = function(highlight, beforeClick, onClick) 9087 { 9088 // Replaces links in labels for consistent right-clicks 9089 var checkLinks = mxUtils.bind(this, function() 9090 { 9091 var links = this.container.getElementsByTagName('a'); 9092 9093 if (links != null) 9094 { 9095 for (var i = 0; i < links.length; i++) 9096 { 9097 var href = this.getAbsoluteUrl(links[i].getAttribute('href')); 9098 9099 if (href != null) 9100 { 9101 links[i].setAttribute('rel', this.linkRelation); 9102 links[i].setAttribute('href', href); 9103 9104 if (beforeClick != null) 9105 { 9106 mxEvent.addGestureListeners(links[i], null, null, beforeClick); 9107 } 9108 } 9109 } 9110 } 9111 }); 9112 9113 this.model.addListener(mxEvent.CHANGE, checkLinks); 9114 checkLinks(); 9115 9116 var cursor = this.container.style.cursor; 9117 var tol = this.getTolerance(); 9118 var graph = this; 9119 9120 var mouseListener = 9121 { 9122 currentState: null, 9123 currentLink: null, 9124 currentTarget: null, 9125 highlight: (highlight != null && highlight != '' && highlight != mxConstants.NONE) ? 9126 new mxCellHighlight(graph, highlight, 4) : null, 9127 startX: 0, 9128 startY: 0, 9129 scrollLeft: 0, 9130 scrollTop: 0, 9131 updateCurrentState: function(me) 9132 { 9133 var tmp = me.sourceState; 9134 9135 // Gets first intersecting ancestor with link 9136 if (tmp == null || graph.getLinkForCell(tmp.cell) == null) 9137 { 9138 var cell = graph.getCellAt(me.getGraphX(), me.getGraphY(), null, null, null, function(state, x, y) 9139 { 9140 return graph.getLinkForCell(state.cell) == null; 9141 }); 9142 9143 tmp = (tmp != null && !graph.model.isAncestor(cell, tmp.cell)) ? null : graph.view.getState(cell); 9144 } 9145 9146 if (tmp != this.currentState) 9147 { 9148 if (this.currentState != null) 9149 { 9150 this.clear(); 9151 } 9152 9153 this.currentState = tmp; 9154 9155 if (this.currentState != null) 9156 { 9157 this.activate(this.currentState); 9158 } 9159 } 9160 }, 9161 mouseDown: function(sender, me) 9162 { 9163 this.startX = me.getGraphX(); 9164 this.startY = me.getGraphY(); 9165 this.scrollLeft = graph.container.scrollLeft; 9166 this.scrollTop = graph.container.scrollTop; 9167 9168 if (this.currentLink == null && graph.container.style.overflow == 'auto') 9169 { 9170 graph.container.style.cursor = 'move'; 9171 } 9172 9173 this.updateCurrentState(me); 9174 }, 9175 mouseMove: function(sender, me) 9176 { 9177 if (graph.isMouseDown) 9178 { 9179 if (this.currentLink != null) 9180 { 9181 var dx = Math.abs(this.startX - me.getGraphX()); 9182 var dy = Math.abs(this.startY - me.getGraphY()); 9183 9184 if (dx > tol || dy > tol) 9185 { 9186 this.clear(); 9187 } 9188 } 9189 } 9190 else 9191 { 9192 // Checks for parent link 9193 var linkNode = me.getSource(); 9194 9195 while (linkNode != null && linkNode.nodeName.toLowerCase() != 'a') 9196 { 9197 linkNode = linkNode.parentNode; 9198 } 9199 9200 if (linkNode != null) 9201 { 9202 this.clear(); 9203 } 9204 else 9205 { 9206 if (graph.tooltipHandler != null && this.currentLink != null && this.currentState != null) 9207 { 9208 graph.tooltipHandler.reset(me, true, this.currentState); 9209 } 9210 9211 if (this.currentState != null && (me.getState() == this.currentState || me.sourceState == null) && 9212 graph.intersects(this.currentState, me.getGraphX(), me.getGraphY())) 9213 { 9214 return; 9215 } 9216 9217 this.updateCurrentState(me); 9218 } 9219 } 9220 }, 9221 mouseUp: function(sender, me) 9222 { 9223 var source = me.getSource(); 9224 var evt = me.getEvent(); 9225 9226 // Checks for parent link 9227 var linkNode = source; 9228 9229 while (linkNode != null && linkNode.nodeName.toLowerCase() != 'a') 9230 { 9231 linkNode = linkNode.parentNode; 9232 } 9233 9234 // Ignores clicks on links and collapse/expand icon 9235 if (linkNode == null && 9236 (((Math.abs(this.scrollLeft - graph.container.scrollLeft) < tol && 9237 Math.abs(this.scrollTop - graph.container.scrollTop) < tol) && 9238 (me.sourceState == null || !me.isSource(me.sourceState.control))) && 9239 (((mxEvent.isLeftMouseButton(evt) || mxEvent.isMiddleMouseButton(evt)) && 9240 !mxEvent.isPopupTrigger(evt)) || mxEvent.isTouchEvent(evt)))) 9241 { 9242 if (this.currentLink != null) 9243 { 9244 var blank = graph.isBlankLink(this.currentLink); 9245 9246 if ((this.currentLink.substring(0, 5) === 'data:' || 9247 !blank) && beforeClick != null) 9248 { 9249 beforeClick(evt, this.currentLink); 9250 } 9251 9252 if (!mxEvent.isConsumed(evt)) 9253 { 9254 var target = (this.currentTarget != null) ? 9255 this.currentTarget : ((mxEvent.isMiddleMouseButton(evt)) ? '_blank' : 9256 ((blank) ? graph.linkTarget : '_top')); 9257 9258 graph.openLink(this.currentLink, target); 9259 me.consume(); 9260 } 9261 } 9262 else if (onClick != null && !me.isConsumed() && 9263 (Math.abs(this.scrollLeft - graph.container.scrollLeft) < tol && 9264 Math.abs(this.scrollTop - graph.container.scrollTop) < tol) && 9265 (Math.abs(this.startX - me.getGraphX()) < tol && 9266 Math.abs(this.startY - me.getGraphY()) < tol)) 9267 { 9268 onClick(me.getEvent()); 9269 } 9270 } 9271 9272 this.clear(); 9273 }, 9274 activate: function(state) 9275 { 9276 this.currentLink = graph.getAbsoluteUrl(graph.getLinkForCell(state.cell)); 9277 9278 if (this.currentLink != null) 9279 { 9280 this.currentTarget = graph.getLinkTargetForCell(state.cell) 9281 graph.container.style.cursor = 'pointer'; 9282 9283 if (this.highlight != null) 9284 { 9285 this.highlight.highlight(state); 9286 } 9287 } 9288 }, 9289 clear: function() 9290 { 9291 if (graph.container != null) 9292 { 9293 graph.container.style.cursor = cursor; 9294 } 9295 9296 this.currentTarget = null; 9297 this.currentState = null; 9298 this.currentLink = null; 9299 9300 if (this.highlight != null) 9301 { 9302 this.highlight.hide(); 9303 } 9304 9305 if (graph.tooltipHandler != null) 9306 { 9307 graph.tooltipHandler.hide(); 9308 } 9309 } 9310 }; 9311 9312 // Ignores built-in click handling 9313 graph.click = function(me) {}; 9314 graph.addMouseListener(mouseListener); 9315 9316 mxEvent.addListener(document, 'mouseleave', function(evt) 9317 { 9318 mouseListener.clear(); 9319 }); 9320 }; 9321 9322 /** 9323 * Duplicates the given cells and returns the duplicates. 9324 */ 9325 Graph.prototype.duplicateCells = function(cells, append) 9326 { 9327 cells = (cells != null) ? cells : this.getSelectionCells(); 9328 append = (append != null) ? append : true; 9329 9330 // Duplicates rows for table cells 9331 for (var i = 0; i < cells.length; i++) 9332 { 9333 if (this.isTableCell(cells[i])) 9334 { 9335 cells[i] = this.model.getParent(cells[i]); 9336 } 9337 } 9338 9339 cells = this.model.getTopmostCells(cells); 9340 9341 var model = this.getModel(); 9342 var s = this.gridSize; 9343 var select = []; 9344 9345 model.beginUpdate(); 9346 try 9347 { 9348 9349 var cloneMap = new Object(); 9350 var lookup = this.createCellLookup(cells); 9351 var clones = this.cloneCells(cells, false, cloneMap, true); 9352 9353 for (var i = 0; i < cells.length; i++) 9354 { 9355 var parent = model.getParent(cells[i]); 9356 9357 if (parent != null) 9358 { 9359 var child = this.moveCells([clones[i]], s, s, false)[0]; 9360 select.push(child); 9361 9362 if (append) 9363 { 9364 model.add(parent, clones[i]); 9365 } 9366 else 9367 { 9368 // Maintains child index by inserting after clone in parent 9369 var index = parent.getIndex(cells[i]); 9370 model.add(parent, clones[i], index + 1); 9371 } 9372 9373 // Extends tables 9374 if (this.isTable(parent)) 9375 { 9376 var row = this.getCellGeometry(clones[i]); 9377 var table = this.getCellGeometry(parent); 9378 9379 if (row != null && table != null) 9380 { 9381 table = table.clone(); 9382 table.height += row.height; 9383 model.setGeometry(parent, table); 9384 } 9385 } 9386 } 9387 else 9388 { 9389 select.push(clones[i]); 9390 } 9391 } 9392 9393 // Updates custom links after inserting into the model for cells to have new IDs 9394 this.updateCustomLinks(this.createCellMapping(cloneMap, lookup), clones, this); 9395 this.fireEvent(new mxEventObject(mxEvent.CELLS_ADDED, 'cells', clones)); 9396 } 9397 finally 9398 { 9399 model.endUpdate(); 9400 } 9401 9402 return select; 9403 }; 9404 9405 /** 9406 * Inserts the given image at the cursor in a content editable text box using 9407 * the insertimage command on the document instance. 9408 */ 9409 Graph.prototype.insertImage = function(newValue, w, h) 9410 { 9411 // To find the new image, we create a list of all existing links first 9412 if (newValue != null && this.cellEditor.textarea != null) 9413 { 9414 var tmp = this.cellEditor.textarea.getElementsByTagName('img'); 9415 var oldImages = []; 9416 9417 for (var i = 0; i < tmp.length; i++) 9418 { 9419 oldImages.push(tmp[i]); 9420 } 9421 9422 // LATER: Fix inserting link/image in IE8/quirks after focus lost 9423 document.execCommand('insertimage', false, newValue); 9424 9425 // Sets size of new image 9426 var newImages = this.cellEditor.textarea.getElementsByTagName('img'); 9427 9428 if (newImages.length == oldImages.length + 1) 9429 { 9430 // Inverse order in favor of appended images 9431 for (var i = newImages.length - 1; i >= 0; i--) 9432 { 9433 if (i == 0 || newImages[i] != oldImages[i - 1]) 9434 { 9435 // Workaround for lost styles during undo and redo is using attributes 9436 newImages[i].setAttribute('width', w); 9437 newImages[i].setAttribute('height', h); 9438 9439 break; 9440 } 9441 } 9442 } 9443 } 9444 }; 9445 9446 /** 9447 * Inserts the given image at the cursor in a content editable text box using 9448 * the insertimage command on the document instance. 9449 */ 9450 Graph.prototype.insertLink = function(value) 9451 { 9452 if (this.cellEditor.textarea != null) 9453 { 9454 if (value.length == 0) 9455 { 9456 document.execCommand('unlink', false); 9457 } 9458 else if (mxClient.IS_FF) 9459 { 9460 // Workaround for Firefox that adds a new link and removes 9461 // the href from the inner link if its parent is a span is 9462 // to remove all inner links inside the new outer link 9463 var tmp = this.cellEditor.textarea.getElementsByTagName('a'); 9464 var oldLinks = []; 9465 9466 for (var i = 0; i < tmp.length; i++) 9467 { 9468 oldLinks.push(tmp[i]); 9469 } 9470 9471 document.execCommand('createlink', false, mxUtils.trim(value)); 9472 9473 // Finds the new link element 9474 var newLinks = this.cellEditor.textarea.getElementsByTagName('a'); 9475 9476 if (newLinks.length == oldLinks.length + 1) 9477 { 9478 // Inverse order in favor of appended links 9479 for (var i = newLinks.length - 1; i >= 0; i--) 9480 { 9481 if (newLinks[i] != oldLinks[i - 1]) 9482 { 9483 // Removes all inner links from the new link and 9484 // moves the children to the inner link parent 9485 var tmp = newLinks[i].getElementsByTagName('a'); 9486 9487 while (tmp.length > 0) 9488 { 9489 var parent = tmp[0].parentNode; 9490 9491 while (tmp[0].firstChild != null) 9492 { 9493 parent.insertBefore(tmp[0].firstChild, tmp[0]); 9494 } 9495 9496 parent.removeChild(tmp[0]); 9497 } 9498 9499 break; 9500 } 9501 } 9502 } 9503 } 9504 else 9505 { 9506 // LATER: Fix inserting link/image in IE8/quirks after focus lost 9507 document.execCommand('createlink', false, mxUtils.trim(value)); 9508 } 9509 } 9510 }; 9511 9512 /** 9513 * 9514 * @param cell 9515 * @returns {Boolean} 9516 */ 9517 Graph.prototype.isCellResizable = function(cell) 9518 { 9519 var result = mxGraph.prototype.isCellResizable.apply(this, arguments); 9520 var style = this.getCurrentCellStyle(cell); 9521 9522 return !this.isTableCell(cell) && !this.isTableRow(cell) && (result || 9523 (mxUtils.getValue(style, mxConstants.STYLE_RESIZABLE, '1') != '0' && 9524 style[mxConstants.STYLE_WHITE_SPACE] == 'wrap')); 9525 }; 9526 9527 /** 9528 * Function: distributeCells 9529 * 9530 * Distribuets the centers of the given cells equally along the available 9531 * horizontal or vertical space. 9532 * 9533 * Parameters: 9534 * 9535 * horizontal - Boolean that specifies the direction of the distribution. 9536 * cells - Optional array of <mxCells> to be distributed. Edges are ignored. 9537 */ 9538 Graph.prototype.distributeCells = function(horizontal, cells) 9539 { 9540 if (cells == null) 9541 { 9542 cells = this.getSelectionCells(); 9543 } 9544 9545 if (cells != null && cells.length > 1) 9546 { 9547 var vertices = []; 9548 var max = null; 9549 var min = null; 9550 9551 for (var i = 0; i < cells.length; i++) 9552 { 9553 if (this.getModel().isVertex(cells[i])) 9554 { 9555 var state = this.view.getState(cells[i]); 9556 9557 if (state != null) 9558 { 9559 var tmp = (horizontal) ? state.getCenterX() : state.getCenterY(); 9560 max = (max != null) ? Math.max(max, tmp) : tmp; 9561 min = (min != null) ? Math.min(min, tmp) : tmp; 9562 9563 vertices.push(state); 9564 } 9565 } 9566 } 9567 9568 if (vertices.length > 2) 9569 { 9570 vertices.sort(function(a, b) 9571 { 9572 return (horizontal) ? a.x - b.x : a.y - b.y; 9573 }); 9574 9575 var t = this.view.translate; 9576 var s = this.view.scale; 9577 9578 min = min / s - ((horizontal) ? t.x : t.y); 9579 max = max / s - ((horizontal) ? t.x : t.y); 9580 9581 this.getModel().beginUpdate(); 9582 try 9583 { 9584 var dt = (max - min) / (vertices.length - 1); 9585 var t0 = min; 9586 9587 for (var i = 1; i < vertices.length - 1; i++) 9588 { 9589 var pstate = this.view.getState(this.model.getParent(vertices[i].cell)); 9590 var geo = this.getCellGeometry(vertices[i].cell); 9591 t0 += dt; 9592 9593 if (geo != null && pstate != null) 9594 { 9595 geo = geo.clone(); 9596 9597 if (horizontal) 9598 { 9599 geo.x = Math.round(t0 - geo.width / 2) - pstate.origin.x; 9600 } 9601 else 9602 { 9603 geo.y = Math.round(t0 - geo.height / 2) - pstate.origin.y; 9604 } 9605 9606 this.getModel().setGeometry(vertices[i].cell, geo); 9607 } 9608 } 9609 } 9610 finally 9611 { 9612 this.getModel().endUpdate(); 9613 } 9614 } 9615 } 9616 9617 return cells; 9618 }; 9619 9620 /** 9621 * Adds meta-drag an Mac. 9622 * @param evt 9623 * @returns 9624 */ 9625 Graph.prototype.isCloneEvent = function(evt) 9626 { 9627 return (mxClient.IS_MAC && mxEvent.isMetaDown(evt)) || mxEvent.isControlDown(evt); 9628 }; 9629 9630 /** 9631 * Translates this point by the given vector. 9632 * 9633 * @param {number} dx X-coordinate of the translation. 9634 * @param {number} dy Y-coordinate of the translation. 9635 */ 9636 Graph.prototype.createSvgImageExport = function() 9637 { 9638 var exp = new mxImageExport(); 9639 9640 // Adds hyperlinks (experimental) 9641 exp.getLinkForCellState = mxUtils.bind(this, function(state, canvas) 9642 { 9643 return this.getLinkForCell(state.cell); 9644 }); 9645 9646 return exp; 9647 }; 9648 9649 /** 9650 * Parses the given background image. 9651 */ 9652 Graph.prototype.parseBackgroundImage = function(json) 9653 { 9654 var result = null; 9655 9656 if (json != null && json.length > 0) 9657 { 9658 var obj = JSON.parse(json); 9659 result = new mxImage(obj.src, obj.width, obj.height) 9660 } 9661 9662 return result; 9663 }; 9664 9665 /** 9666 * Parses the given background image. 9667 */ 9668 Graph.prototype.getBackgroundImageObject = function(obj) 9669 { 9670 return obj; 9671 }; 9672 9673 /** 9674 * Translates this point by the given vector. 9675 * 9676 * @param {number} dx X-coordinate of the translation. 9677 * @param {number} dy Y-coordinate of the translation. 9678 */ 9679 Graph.prototype.getSvg = function(background, scale, border, nocrop, crisp, 9680 ignoreSelection, showText, imgExport, linkTarget, hasShadow, incExtFonts, 9681 keepTheme, exportType, cells) 9682 { 9683 var lookup = null; 9684 9685 if (cells != null) 9686 { 9687 lookup = new mxDictionary(); 9688 9689 for (var i = 0; i < cells.length; i++) 9690 { 9691 lookup.put(cells[i], true); 9692 } 9693 } 9694 9695 //Disable Css Transforms if it is used 9696 var origUseCssTrans = this.useCssTransforms; 9697 9698 if (origUseCssTrans) 9699 { 9700 this.useCssTransforms = false; 9701 this.view.revalidate(); 9702 this.sizeDidChange(); 9703 } 9704 9705 try 9706 { 9707 scale = (scale != null) ? scale : 1; 9708 border = (border != null) ? border : 0; 9709 crisp = (crisp != null) ? crisp : true; 9710 ignoreSelection = (ignoreSelection != null) ? ignoreSelection : true; 9711 showText = (showText != null) ? showText : true; 9712 hasShadow = (hasShadow != null) ? hasShadow : false; 9713 9714 var bounds = (exportType == 'page') ? this.view.getBackgroundPageBounds() : 9715 (((ignoreSelection && lookup == null) || nocrop || 9716 exportType == 'diagram') ? this.getGraphBounds() : 9717 this.getBoundingBox(this.getSelectionCells())); 9718 var vs = this.view.scale; 9719 9720 if (exportType == 'diagram' && this.backgroundImage != null) 9721 { 9722 bounds = mxRectangle.fromRectangle(bounds); 9723 bounds.add(new mxRectangle( 9724 (this.view.translate.x + this.backgroundImage.x) * vs, 9725 (this.view.translate.y + this.backgroundImage.y) * vs, 9726 this.backgroundImage.width * vs, 9727 this.backgroundImage.height * vs)); 9728 } 9729 9730 if (bounds == null) 9731 { 9732 throw Error(mxResources.get('drawingEmpty')); 9733 } 9734 9735 // Prepares SVG document that holds the output 9736 var svgDoc = mxUtils.createXmlDocument(); 9737 var root = (svgDoc.createElementNS != null) ? 9738 svgDoc.createElementNS(mxConstants.NS_SVG, 'svg') : svgDoc.createElement('svg'); 9739 9740 if (background != null) 9741 { 9742 if (root.style != null) 9743 { 9744 root.style.backgroundColor = background; 9745 } 9746 else 9747 { 9748 root.setAttribute('style', 'background-color:' + background); 9749 } 9750 } 9751 9752 if (svgDoc.createElementNS == null) 9753 { 9754 root.setAttribute('xmlns', mxConstants.NS_SVG); 9755 root.setAttribute('xmlns:xlink', mxConstants.NS_XLINK); 9756 } 9757 else 9758 { 9759 // KNOWN: Ignored in IE9-11, adds namespace for each image element instead. No workaround. 9760 root.setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xlink', mxConstants.NS_XLINK); 9761 } 9762 9763 var s = scale / vs; 9764 var w = Math.max(1, Math.ceil(bounds.width * s) + 2 * border) + 9765 ((hasShadow && border == 0) ? 5 : 0); 9766 var h = Math.max(1, Math.ceil(bounds.height * s) + 2 * border) + 9767 ((hasShadow && border == 0) ? 5 : 0); 9768 9769 root.setAttribute('version', '1.1'); 9770 root.setAttribute('width', w + 'px'); 9771 root.setAttribute('height', h + 'px'); 9772 root.setAttribute('viewBox', ((crisp) ? '-0.5 -0.5' : '0 0') + ' ' + w + ' ' + h); 9773 svgDoc.appendChild(root); 9774 9775 // Renders graph. Offset will be multiplied with state's scale when painting state. 9776 // TextOffset only seems to affect FF output but used everywhere for consistency. 9777 var group = (svgDoc.createElementNS != null) ? 9778 svgDoc.createElementNS(mxConstants.NS_SVG, 'g') : svgDoc.createElement('g'); 9779 root.appendChild(group); 9780 9781 var svgCanvas = this.createSvgCanvas(group); 9782 svgCanvas.foOffset = (crisp) ? -0.5 : 0; 9783 svgCanvas.textOffset = (crisp) ? -0.5 : 0; 9784 svgCanvas.imageOffset = (crisp) ? -0.5 : 0; 9785 svgCanvas.translate(Math.floor(border / scale - bounds.x / vs), 9786 Math.floor(border / scale - bounds.y / vs)); 9787 9788 // Convert HTML entities 9789 var htmlConverter = document.createElement('div'); 9790 9791 // Adds simple text fallback for viewers with no support for foreignObjects 9792 var getAlternateText = svgCanvas.getAlternateText; 9793 svgCanvas.getAlternateText = function(fo, x, y, w, h, str, 9794 align, valign, wrap, format, overflow, clip, rotation) 9795 { 9796 // Assumes a max character width of 0.5em 9797 if (str != null && this.state.fontSize > 0) 9798 { 9799 try 9800 { 9801 if (mxUtils.isNode(str)) 9802 { 9803 str = str.innerText; 9804 } 9805 else 9806 { 9807 htmlConverter.innerHTML = str; 9808 str = mxUtils.extractTextWithWhitespace(htmlConverter.childNodes); 9809 } 9810 9811 // Workaround for substring breaking double byte UTF 9812 var exp = Math.ceil(2 * w / this.state.fontSize); 9813 var result = []; 9814 var length = 0; 9815 var index = 0; 9816 9817 while ((exp == 0 || length < exp) && index < str.length) 9818 { 9819 var char = str.charCodeAt(index); 9820 9821 if (char == 10 || char == 13) 9822 { 9823 if (length > 0) 9824 { 9825 break; 9826 } 9827 } 9828 else 9829 { 9830 result.push(str.charAt(index)); 9831 9832 if (char < 255) 9833 { 9834 length++; 9835 } 9836 } 9837 9838 index++; 9839 } 9840 9841 // Uses result and adds ellipsis if more than 1 char remains 9842 if (result.length < str.length && str.length - result.length > 1) 9843 { 9844 str = mxUtils.trim(result.join('')) + '...'; 9845 } 9846 9847 return str; 9848 } 9849 catch (e) 9850 { 9851 return getAlternateText.apply(this, arguments); 9852 } 9853 } 9854 else 9855 { 9856 return getAlternateText.apply(this, arguments); 9857 } 9858 }; 9859 9860 // Paints background image 9861 var bgImg = this.backgroundImage; 9862 9863 if (bgImg != null) 9864 { 9865 var s2 = vs / scale; 9866 var tr = this.view.translate; 9867 var tmp = new mxRectangle((bgImg.x + tr.x) * s2, (bgImg.y + tr.y) * s2, 9868 bgImg.width * s2, bgImg.height * s2); 9869 9870 // Checks if visible 9871 if (mxUtils.intersects(bounds, tmp)) 9872 { 9873 svgCanvas.image(bgImg.x + tr.x, bgImg.y + tr.y, 9874 bgImg.width, bgImg.height, bgImg.src, true); 9875 } 9876 } 9877 9878 svgCanvas.scale(s); 9879 svgCanvas.textEnabled = showText; 9880 9881 imgExport = (imgExport != null) ? imgExport : this.createSvgImageExport(); 9882 var imgExportDrawCellState = imgExport.drawCellState; 9883 9884 // Ignores custom links 9885 var imgExportGetLinkForCellState = imgExport.getLinkForCellState; 9886 9887 imgExport.getLinkForCellState = function(state, canvas) 9888 { 9889 var result = imgExportGetLinkForCellState.apply(this, arguments); 9890 9891 return (result != null && !state.view.graph.isCustomLink(result)) ? result : null; 9892 }; 9893 9894 imgExport.getLinkTargetForCellState = function(state, canvas) 9895 { 9896 return state.view.graph.getLinkTargetForCell(state.cell); 9897 }; 9898 9899 // Implements ignoreSelection flag 9900 imgExport.drawCellState = function(state, canvas) 9901 { 9902 var graph = state.view.graph; 9903 var selected = (lookup != null) ? lookup.get(state.cell) : 9904 graph.isCellSelected(state.cell); 9905 var parent = graph.model.getParent(state.cell); 9906 9907 // Checks if parent cell is selected 9908 while ((!ignoreSelection || lookup != null) && 9909 !selected && parent != null) 9910 { 9911 selected = (lookup != null) ? lookup.get(parent) : 9912 graph.isCellSelected(parent); 9913 parent = graph.model.getParent(parent); 9914 } 9915 9916 if ((ignoreSelection && lookup == null) || selected) 9917 { 9918 imgExportDrawCellState.apply(this, arguments); 9919 } 9920 }; 9921 9922 imgExport.drawState(this.getView().getState(this.model.root), svgCanvas); 9923 this.updateSvgLinks(root, linkTarget, true); 9924 this.addForeignObjectWarning(svgCanvas, root); 9925 9926 return root; 9927 } 9928 finally 9929 { 9930 if (origUseCssTrans) 9931 { 9932 this.useCssTransforms = true; 9933 this.view.revalidate(); 9934 this.sizeDidChange(); 9935 } 9936 } 9937 }; 9938 9939 /** 9940 * Adds warning for truncated labels in older viewers. 9941 */ 9942 Graph.prototype.addForeignObjectWarning = function(canvas, root) 9943 { 9944 if (urlParams['svg-warning'] != '0' && root.getElementsByTagName('foreignObject').length > 0) 9945 { 9946 var sw = canvas.createElement('switch'); 9947 var g1 = canvas.createElement('g'); 9948 g1.setAttribute('requiredFeatures', 'http://www.w3.org/TR/SVG11/feature#Extensibility'); 9949 var a = canvas.createElement('a'); 9950 a.setAttribute('transform', 'translate(0,-5)'); 9951 9952 // Workaround for implicit namespace handling in HTML5 export, IE adds NS1 namespace so use code below 9953 // in all IE versions except quirks mode. KNOWN: Adds xlink namespace to each image tag in output. 9954 if (a.setAttributeNS == null || (root.ownerDocument != document && document.documentMode == null)) 9955 { 9956 a.setAttribute('xlink:href', Graph.foreignObjectWarningLink); 9957 a.setAttribute('target', '_blank'); 9958 } 9959 else 9960 { 9961 a.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', Graph.foreignObjectWarningLink); 9962 a.setAttributeNS(mxConstants.NS_XLINK, 'target', '_blank'); 9963 } 9964 9965 var text = canvas.createElement('text'); 9966 text.setAttribute('text-anchor', 'middle'); 9967 text.setAttribute('font-size', '10px'); 9968 text.setAttribute('x', '50%'); 9969 text.setAttribute('y', '100%'); 9970 mxUtils.write(text, Graph.foreignObjectWarningText); 9971 9972 sw.appendChild(g1); 9973 a.appendChild(text); 9974 sw.appendChild(a); 9975 root.appendChild(sw); 9976 } 9977 }; 9978 9979 /** 9980 * Hook for creating the canvas used in getSvg. 9981 */ 9982 Graph.prototype.updateSvgLinks = function(node, target, removeCustom) 9983 { 9984 var links = node.getElementsByTagName('a'); 9985 9986 for (var i = 0; i < links.length; i++) 9987 { 9988 if (links[i].getAttribute('target') == null) 9989 { 9990 var href = links[i].getAttribute('href'); 9991 9992 if (href == null) 9993 { 9994 href = links[i].getAttribute('xlink:href'); 9995 } 9996 9997 if (href != null) 9998 { 9999 if (target != null && /^https?:\/\//.test(href)) 10000 { 10001 links[i].setAttribute('target', target); 10002 } 10003 else if (removeCustom && this.isCustomLink(href)) 10004 { 10005 links[i].setAttribute('href', 'javascript:void(0);'); 10006 } 10007 } 10008 } 10009 } 10010 }; 10011 10012 /** 10013 * Hook for creating the canvas used in getSvg. 10014 */ 10015 Graph.prototype.createSvgCanvas = function(node) 10016 { 10017 var canvas = new mxSvgCanvas2D(node); 10018 10019 canvas.pointerEvents = true; 10020 10021 return canvas; 10022 }; 10023 10024 /** 10025 * Returns the first ancestor of the current selection with the given name. 10026 */ 10027 Graph.prototype.getSelectedElement = function() 10028 { 10029 var node = null; 10030 10031 if (window.getSelection) 10032 { 10033 var sel = window.getSelection(); 10034 10035 if (sel.getRangeAt && sel.rangeCount) 10036 { 10037 var range = sel.getRangeAt(0); 10038 node = range.commonAncestorContainer; 10039 } 10040 } 10041 else if (document.selection) 10042 { 10043 node = document.selection.createRange().parentElement(); 10044 } 10045 10046 return node; 10047 }; 10048 10049 /** 10050 * Returns the text editing element. 10051 */ 10052 Graph.prototype.getSelectedEditingElement = function() 10053 { 10054 var node = this.getSelectedElement(); 10055 10056 while (node != null && node.nodeType != mxConstants.NODETYPE_ELEMENT) 10057 { 10058 node = node.parentNode; 10059 } 10060 10061 if (node != null) 10062 { 10063 // Workaround for commonAncestor on range in IE11 returning parent of common ancestor 10064 if (node == this.cellEditor.textarea && this.cellEditor.textarea.children.length == 1 && 10065 this.cellEditor.textarea.firstChild.nodeType == mxConstants.NODETYPE_ELEMENT) 10066 { 10067 node = this.cellEditor.textarea.firstChild; 10068 } 10069 } 10070 10071 return node; 10072 }; 10073 10074 /** 10075 * Returns the first ancestor of the current selection with the given name. 10076 */ 10077 Graph.prototype.getParentByName = function(node, name, stopAt) 10078 { 10079 while (node != null) 10080 { 10081 if (node.nodeName == name) 10082 { 10083 return node; 10084 } 10085 10086 if (node == stopAt) 10087 { 10088 return null; 10089 } 10090 10091 node = node.parentNode; 10092 } 10093 10094 return node; 10095 }; 10096 10097 /** 10098 * Returns the first ancestor of the current selection with the given name. 10099 */ 10100 Graph.prototype.getParentByNames = function(node, names, stopAt) 10101 { 10102 while (node != null) 10103 { 10104 if (mxUtils.indexOf(names, node.nodeName) >= 0) 10105 { 10106 return node; 10107 } 10108 10109 if (node == stopAt) 10110 { 10111 return null; 10112 } 10113 10114 node = node.parentNode; 10115 } 10116 10117 return node; 10118 }; 10119 10120 /** 10121 * Selects the given node. 10122 */ 10123 Graph.prototype.selectNode = function(node) 10124 { 10125 var sel = null; 10126 10127 // IE9 and non-IE 10128 if (window.getSelection) 10129 { 10130 sel = window.getSelection(); 10131 10132 if (sel.getRangeAt && sel.rangeCount) 10133 { 10134 var range = document.createRange(); 10135 range.selectNode(node); 10136 sel.removeAllRanges(); 10137 sel.addRange(range); 10138 } 10139 } 10140 // IE < 9 10141 else if ((sel = document.selection) && sel.type != 'Control') 10142 { 10143 var originalRange = sel.createRange(); 10144 originalRange.collapse(true); 10145 var range = sel.createRange(); 10146 range.setEndPoint('StartToStart', originalRange); 10147 range.select(); 10148 } 10149 }; 10150 10151 /** 10152 * Deletes the given cells and returns the cells to be selected. 10153 */ 10154 Graph.prototype.deleteCells = function(cells, includeEdges) 10155 { 10156 var select = null; 10157 10158 if (cells != null && cells.length > 0) 10159 { 10160 this.model.beginUpdate(); 10161 try 10162 { 10163 // Shrinks tables 10164 for (var i = 0; i < cells.length; i++) 10165 { 10166 var parent = this.model.getParent(cells[i]); 10167 10168 if (this.isTable(parent)) 10169 { 10170 var row = this.getCellGeometry(cells[i]); 10171 var table = this.getCellGeometry(parent); 10172 10173 if (row != null && table != null) 10174 { 10175 table = table.clone(); 10176 table.height -= row.height; 10177 this.model.setGeometry(parent, table); 10178 } 10179 } 10180 } 10181 10182 var parents = (this.selectParentAfterDelete) ? this.model.getParents(cells) : null; 10183 this.removeCells(cells, includeEdges); 10184 } 10185 finally 10186 { 10187 this.model.endUpdate(); 10188 } 10189 10190 // Selects parents for easier editing of groups 10191 if (parents != null) 10192 { 10193 select = []; 10194 10195 for (var i = 0; i < parents.length; i++) 10196 { 10197 if (this.model.contains(parents[i]) && 10198 (this.model.isVertex(parents[i]) || 10199 this.model.isEdge(parents[i]))) 10200 { 10201 select.push(parents[i]); 10202 } 10203 } 10204 } 10205 } 10206 10207 return select; 10208 }; 10209 10210 /** 10211 * Inserts a column in the table for the given cell. 10212 */ 10213 Graph.prototype.insertTableColumn = function(cell, before) 10214 { 10215 var model = this.getModel(); 10216 model.beginUpdate(); 10217 10218 try 10219 { 10220 var table = cell; 10221 var index = 0; 10222 10223 if (this.isTableCell(cell)) 10224 { 10225 var row = model.getParent(cell); 10226 table = model.getParent(row); 10227 index = mxUtils.indexOf(model.getChildCells(row, true), cell); 10228 } 10229 else 10230 { 10231 if (this.isTableRow(cell)) 10232 { 10233 table = model.getParent(cell); 10234 } 10235 else 10236 { 10237 cell = model.getChildCells(table, true)[0]; 10238 } 10239 10240 if (!before) 10241 { 10242 index = model.getChildCells(cell, true).length - 1; 10243 } 10244 } 10245 10246 var rows = model.getChildCells(table, true); 10247 var dw = Graph.minTableColumnWidth; 10248 10249 for (var i = 0; i < rows.length; i++) 10250 { 10251 var child = model.getChildCells(rows[i], true)[index]; 10252 var clone = model.cloneCell(child, false); 10253 var geo = this.getCellGeometry(clone); 10254 clone.value = null; 10255 10256 if (geo != null) 10257 { 10258 dw = geo.width; 10259 var rowGeo = this.getCellGeometry(rows[i]); 10260 10261 if (rowGeo != null) 10262 { 10263 geo.height = rowGeo.height; 10264 } 10265 } 10266 10267 model.add(rows[i], clone, index + ((before) ? 0 : 1)); 10268 } 10269 10270 var tableGeo = this.getCellGeometry(table); 10271 10272 if (tableGeo != null) 10273 { 10274 tableGeo = tableGeo.clone(); 10275 tableGeo.width += dw; 10276 10277 model.setGeometry(table, tableGeo); 10278 } 10279 } 10280 finally 10281 { 10282 model.endUpdate(); 10283 } 10284 }; 10285 10286 /** 10287 * Inserts a row in the table for the given cell. 10288 */ 10289 Graph.prototype.deleteLane = function(cell) 10290 { 10291 var model = this.getModel(); 10292 model.beginUpdate(); 10293 10294 try 10295 { 10296 var pool = null; 10297 var lane = cell; 10298 var style = this.getCurrentCellStyle(lane); 10299 10300 if (style['childLayout'] == 'stackLayout') 10301 { 10302 pool = lane; 10303 } 10304 else 10305 { 10306 pool = model.getParent(lane); 10307 } 10308 10309 var lanes = model.getChildCells(pool, true); 10310 10311 if (lanes.length == 0) 10312 { 10313 model.remove(pool); 10314 } 10315 else 10316 { 10317 if (pool == lane) 10318 { 10319 lane = lanes[lanes.length - 1]; 10320 } 10321 10322 model.remove(lane); 10323 } 10324 } 10325 finally 10326 { 10327 model.endUpdate(); 10328 } 10329 }; 10330 10331 /** 10332 * Inserts a row in the table for the given cell. 10333 */ 10334 Graph.prototype.insertLane = function(cell, before) 10335 { 10336 var model = this.getModel(); 10337 model.beginUpdate(); 10338 10339 try 10340 { 10341 var pool = null; 10342 var lane = cell; 10343 var style = this.getCurrentCellStyle(lane); 10344 10345 if (style['childLayout'] == 'stackLayout') 10346 { 10347 pool = lane; 10348 var lanes = model.getChildCells(pool, true); 10349 lane = lanes[(before) ? 0 : lanes.length - 1]; 10350 } 10351 else 10352 { 10353 pool = model.getParent(lane); 10354 } 10355 10356 var index = pool.getIndex(lane); 10357 lane = model.cloneCell(lane, false); 10358 lane.value = null; 10359 model.add(pool, lane, index + ((before) ? 0 : 1)); 10360 } 10361 finally 10362 { 10363 model.endUpdate(); 10364 } 10365 }; 10366 10367 /** 10368 * Inserts a row in the table for the given cell. 10369 */ 10370 Graph.prototype.insertTableRow = function(cell, before) 10371 { 10372 var model = this.getModel(); 10373 model.beginUpdate(); 10374 10375 try 10376 { 10377 var table = cell; 10378 var row = cell; 10379 10380 if (this.isTableCell(cell)) 10381 { 10382 row = model.getParent(cell); 10383 table = model.getParent(row); 10384 } 10385 else if (this.isTableRow(cell)) 10386 { 10387 table = model.getParent(cell); 10388 } 10389 else 10390 { 10391 var rows = model.getChildCells(table, true); 10392 row = rows[(before) ? 0 : rows.length - 1]; 10393 } 10394 10395 var cells = model.getChildCells(row, true); 10396 var index = table.getIndex(row); 10397 row = model.cloneCell(row, false); 10398 row.value = null; 10399 10400 var rowGeo = this.getCellGeometry(row); 10401 10402 if (rowGeo != null) 10403 { 10404 for (var i = 0; i < cells.length; i++) 10405 { 10406 var cell = model.cloneCell(cells[i], false); 10407 row.insert(cell); 10408 cell.value = null; 10409 10410 var geo = this.getCellGeometry(cell); 10411 10412 if (geo != null) 10413 { 10414 geo.height = rowGeo.height; 10415 } 10416 } 10417 10418 model.add(table, row, index + ((before) ? 0 : 1)); 10419 10420 var tableGeo = this.getCellGeometry(table); 10421 10422 if (tableGeo != null) 10423 { 10424 tableGeo = tableGeo.clone(); 10425 tableGeo.height += rowGeo.height; 10426 10427 model.setGeometry(table, tableGeo); 10428 } 10429 } 10430 } 10431 finally 10432 { 10433 model.endUpdate(); 10434 } 10435 }; 10436 10437 /** 10438 * 10439 */ 10440 Graph.prototype.deleteTableColumn = function(cell) 10441 { 10442 var model = this.getModel(); 10443 model.beginUpdate(); 10444 10445 try 10446 { 10447 var table = cell; 10448 var row = cell; 10449 10450 if (this.isTableCell(cell)) 10451 { 10452 row = model.getParent(cell); 10453 } 10454 10455 if (this.isTableRow(row)) 10456 { 10457 table = model.getParent(row); 10458 } 10459 10460 var rows = model.getChildCells(table, true); 10461 10462 if (rows.length == 0) 10463 { 10464 model.remove(table); 10465 } 10466 else 10467 { 10468 if (!this.isTableRow(row)) 10469 { 10470 row = rows[0]; 10471 } 10472 10473 var cells = model.getChildCells(row, true); 10474 10475 if (cells.length <= 1) 10476 { 10477 model.remove(table); 10478 } 10479 else 10480 { 10481 var index = cells.length - 1; 10482 10483 if (this.isTableCell(cell)) 10484 { 10485 index = mxUtils.indexOf(cells, cell); 10486 } 10487 10488 var width = 0; 10489 10490 for (var i = 0; i < rows.length; i++) 10491 { 10492 var child = model.getChildCells(rows[i], true)[index]; 10493 model.remove(child); 10494 10495 var geo = this.getCellGeometry(child); 10496 10497 if (geo != null) 10498 { 10499 width = Math.max(width, geo.width); 10500 } 10501 } 10502 10503 var tableGeo = this.getCellGeometry(table); 10504 10505 if (tableGeo != null) 10506 { 10507 tableGeo = tableGeo.clone(); 10508 tableGeo.width -= width; 10509 10510 model.setGeometry(table, tableGeo); 10511 } 10512 } 10513 } 10514 } 10515 finally 10516 { 10517 model.endUpdate(); 10518 } 10519 }; 10520 10521 /** 10522 * 10523 */ 10524 Graph.prototype.deleteTableRow = function(cell) 10525 { 10526 var model = this.getModel(); 10527 model.beginUpdate(); 10528 10529 try 10530 { 10531 var table = cell; 10532 var row = cell; 10533 10534 if (this.isTableCell(cell)) 10535 { 10536 row = model.getParent(cell); 10537 cell = row; 10538 } 10539 10540 if (this.isTableRow(cell)) 10541 { 10542 table = model.getParent(row); 10543 } 10544 10545 var rows = model.getChildCells(table, true); 10546 10547 if (rows.length <= 1) 10548 { 10549 model.remove(table); 10550 } 10551 else 10552 { 10553 if (!this.isTableRow(row)) 10554 { 10555 row = rows[rows.length - 1]; 10556 } 10557 10558 model.remove(row); 10559 var height = 0; 10560 10561 var geo = this.getCellGeometry(row); 10562 10563 if (geo != null) 10564 { 10565 height = geo.height; 10566 } 10567 10568 var tableGeo = this.getCellGeometry(table); 10569 10570 if (tableGeo != null) 10571 { 10572 tableGeo = tableGeo.clone(); 10573 tableGeo.height -= height; 10574 10575 model.setGeometry(table, tableGeo); 10576 } 10577 } 10578 } 10579 finally 10580 { 10581 model.endUpdate(); 10582 } 10583 }; 10584 10585 /** 10586 * Inserts a new row into the given table. 10587 */ 10588 Graph.prototype.insertRow = function(table, index) 10589 { 10590 var bd = table.tBodies[0]; 10591 var cells = bd.rows[0].cells; 10592 var cols = 0; 10593 10594 // Counts columns including colspans 10595 for (var i = 0; i < cells.length; i++) 10596 { 10597 var colspan = cells[i].getAttribute('colspan'); 10598 cols += (colspan != null) ? parseInt(colspan) : 1; 10599 } 10600 10601 var row = bd.insertRow(index); 10602 10603 for (var i = 0; i < cols; i++) 10604 { 10605 mxUtils.br(row.insertCell(-1)); 10606 } 10607 10608 return row.cells[0]; 10609 }; 10610 10611 /** 10612 * Deletes the given column. 10613 */ 10614 Graph.prototype.deleteRow = function(table, index) 10615 { 10616 table.tBodies[0].deleteRow(index); 10617 }; 10618 10619 /** 10620 * Deletes the given column. 10621 */ 10622 Graph.prototype.insertColumn = function(table, index) 10623 { 10624 var hd = table.tHead; 10625 10626 if (hd != null) 10627 { 10628 // TODO: use colIndex 10629 for (var h = 0; h < hd.rows.length; h++) 10630 { 10631 var th = document.createElement('th'); 10632 hd.rows[h].appendChild(th); 10633 mxUtils.br(th); 10634 } 10635 } 10636 10637 var bd = table.tBodies[0]; 10638 10639 for (var i = 0; i < bd.rows.length; i++) 10640 { 10641 var cell = bd.rows[i].insertCell(index); 10642 mxUtils.br(cell); 10643 } 10644 10645 return bd.rows[0].cells[(index >= 0) ? index : bd.rows[0].cells.length - 1]; 10646 }; 10647 10648 /** 10649 * Deletes the given column. 10650 */ 10651 Graph.prototype.deleteColumn = function(table, index) 10652 { 10653 if (index >= 0) 10654 { 10655 var bd = table.tBodies[0]; 10656 var rows = bd.rows; 10657 10658 for (var i = 0; i < rows.length; i++) 10659 { 10660 if (rows[i].cells.length > index) 10661 { 10662 rows[i].deleteCell(index); 10663 } 10664 } 10665 } 10666 }; 10667 10668 /** 10669 * Inserts the given HTML at the caret position (no undo). 10670 */ 10671 Graph.prototype.pasteHtmlAtCaret = function(html) 10672 { 10673 var sel, range; 10674 10675 // IE9 and non-IE 10676 if (window.getSelection) 10677 { 10678 sel = window.getSelection(); 10679 10680 if (sel.getRangeAt && sel.rangeCount) 10681 { 10682 range = sel.getRangeAt(0); 10683 range.deleteContents(); 10684 10685 // Range.createContextualFragment() would be useful here but is 10686 // only relatively recently standardized and is not supported in 10687 // some browsers (IE9, for one) 10688 var el = document.createElement("div"); 10689 el.innerHTML = html; 10690 var frag = document.createDocumentFragment(), node; 10691 10692 while ((node = el.firstChild)) 10693 { 10694 lastNode = frag.appendChild(node); 10695 } 10696 10697 range.insertNode(frag); 10698 } 10699 } 10700 // IE < 9 10701 else if ((sel = document.selection) && sel.type != "Control") 10702 { 10703 // FIXME: Does not work if selection is empty 10704 sel.createRange().pasteHTML(html); 10705 } 10706 }; 10707 10708 /** 10709 * Creates an anchor elements for handling the given link in the 10710 * hint that is shown when the cell is selected. 10711 */ 10712 Graph.prototype.createLinkForHint = function(link, label) 10713 { 10714 link = (link != null) ? link : 'javascript:void(0);'; 10715 10716 if (label == null || label.length == 0) 10717 { 10718 if (this.isCustomLink(link)) 10719 { 10720 label = this.getLinkTitle(link); 10721 } 10722 else 10723 { 10724 label = link; 10725 } 10726 } 10727 10728 // Helper function to shorten strings 10729 function short(str, max) 10730 { 10731 if (str.length > max) 10732 { 10733 str = str.substring(0, Math.round(max / 2)) + '...' + 10734 str.substring(str.length - Math.round(max / 4)); 10735 } 10736 10737 return str; 10738 }; 10739 10740 var a = document.createElement('a'); 10741 a.setAttribute('rel', this.linkRelation); 10742 a.setAttribute('href', this.getAbsoluteUrl(link)); 10743 a.setAttribute('title', short((this.isCustomLink(link)) ? 10744 this.getLinkTitle(link) : link, 80)); 10745 10746 if (this.linkTarget != null) 10747 { 10748 a.setAttribute('target', this.linkTarget); 10749 } 10750 10751 // Adds shortened label to link 10752 mxUtils.write(a, short(label, 40)); 10753 10754 // Handles custom links 10755 if (this.isCustomLink(link)) 10756 { 10757 mxEvent.addListener(a, 'click', mxUtils.bind(this, function(evt) 10758 { 10759 this.customLinkClicked(link); 10760 mxEvent.consume(evt); 10761 })); 10762 } 10763 10764 return a; 10765 }; 10766 10767 /** 10768 * Customized graph for touch devices. 10769 */ 10770 Graph.prototype.initTouch = function() 10771 { 10772 // Disables new connections via "hotspot" 10773 this.connectionHandler.marker.isEnabled = function() 10774 { 10775 return this.graph.connectionHandler.first != null; 10776 }; 10777 10778 // Hides menu when editing starts 10779 this.addListener(mxEvent.START_EDITING, function(sender, evt) 10780 { 10781 this.popupMenuHandler.hideMenu(); 10782 }); 10783 10784 // Adds custom hit detection if native hit detection found no cell 10785 var graphUpdateMouseEvent = this.updateMouseEvent; 10786 this.updateMouseEvent = function(me) 10787 { 10788 me = graphUpdateMouseEvent.apply(this, arguments); 10789 10790 if (mxEvent.isTouchEvent(me.getEvent()) && me.getState() == null) 10791 { 10792 var cell = this.getCellAt(me.graphX, me.graphY); 10793 10794 if (cell != null && this.isSwimlane(cell) && this.hitsSwimlaneContent(cell, me.graphX, me.graphY)) 10795 { 10796 cell = null; 10797 } 10798 else 10799 { 10800 me.state = this.view.getState(cell); 10801 10802 if (me.state != null && me.state.shape != null) 10803 { 10804 this.container.style.cursor = me.state.shape.node.style.cursor; 10805 } 10806 } 10807 } 10808 10809 if (me.getState() == null && this.isEnabled()) 10810 { 10811 this.container.style.cursor = 'default'; 10812 } 10813 10814 return me; 10815 }; 10816 10817 // Context menu trigger implementation depending on current selection state 10818 // combined with support for normal popup trigger. 10819 var cellSelected = false; 10820 var selectionEmpty = false; 10821 var menuShowing = false; 10822 10823 var oldFireMouseEvent = this.fireMouseEvent; 10824 10825 this.fireMouseEvent = function(evtName, me, sender) 10826 { 10827 if (evtName == mxEvent.MOUSE_DOWN) 10828 { 10829 // For hit detection on edges 10830 me = this.updateMouseEvent(me); 10831 10832 cellSelected = this.isCellSelected(me.getCell()); 10833 selectionEmpty = this.isSelectionEmpty(); 10834 menuShowing = this.popupMenuHandler.isMenuShowing(); 10835 } 10836 10837 oldFireMouseEvent.apply(this, arguments); 10838 }; 10839 10840 // Shows popup menu if cell was selected or selection was empty and background was clicked 10841 // FIXME: Conflicts with mxPopupMenuHandler.prototype.getCellForPopupEvent in Editor.js by 10842 // selecting parent for selected children in groups before this check can be made. 10843 this.popupMenuHandler.mouseUp = mxUtils.bind(this, function(sender, me) 10844 { 10845 var isMouseEvent = mxEvent.isMouseEvent(me.getEvent()); 10846 this.popupMenuHandler.popupTrigger = !this.isEditing() && this.isEnabled() && 10847 (me.getState() == null || !me.isSource(me.getState().control)) && 10848 (this.popupMenuHandler.popupTrigger || (!menuShowing && !isMouseEvent && 10849 ((selectionEmpty && me.getCell() == null && this.isSelectionEmpty()) || 10850 (cellSelected && this.isCellSelected(me.getCell()))))); 10851 10852 // Delays popup menu to allow for double tap to start editing 10853 var popup = (!cellSelected || isMouseEvent) ? null : mxUtils.bind(this, function(cell) 10854 { 10855 window.setTimeout(mxUtils.bind(this, function() 10856 { 10857 if (!this.isEditing()) 10858 { 10859 var origin = mxUtils.getScrollOrigin(); 10860 this.popupMenuHandler.popup(me.getX() + origin.x + 1, 10861 me.getY() + origin.y + 1, cell, me.getEvent()); 10862 } 10863 }), 500); 10864 }); 10865 10866 mxPopupMenuHandler.prototype.mouseUp.apply(this.popupMenuHandler, [sender, me, popup]); 10867 }); 10868 }; 10869 10870 /** 10871 * HTML in-place editor 10872 */ 10873 mxCellEditor.prototype.isContentEditing = function() 10874 { 10875 var state = this.graph.view.getState(this.editingCell); 10876 10877 return state != null && state.style['html'] == 1; 10878 }; 10879 10880 /** 10881 * Returns true if all selected text is inside a table element. 10882 */ 10883 mxCellEditor.prototype.isTableSelected = function() 10884 { 10885 return this.graph.getParentByName( 10886 this.graph.getSelectedElement(), 10887 'TABLE', this.textarea) != null; 10888 }; 10889 10890 /** 10891 * Returns true if text is selected. 10892 */ 10893 mxCellEditor.prototype.isTextSelected = function() 10894 { 10895 var txt = ''; 10896 10897 if (window.getSelection) 10898 { 10899 txt = window.getSelection(); 10900 } 10901 else if (document.getSelection) 10902 { 10903 txt = document.getSelection(); 10904 } 10905 else if (document.selection) 10906 { 10907 txt = document.selection.createRange().text; 10908 } 10909 10910 return txt != ''; 10911 }; 10912 10913 /** 10914 * Inserts a tab at the cursor position. 10915 */ 10916 mxCellEditor.prototype.insertTab = function(spaces) 10917 { 10918 var editor = this.textarea; 10919 var doc = editor.ownerDocument.defaultView; 10920 var sel = doc.getSelection(); 10921 var range = sel.getRangeAt(0); 10922 var str = '\t'; 10923 10924 if (spaces != null) 10925 { 10926 str = ''; 10927 10928 while (spaces > 0) 10929 { 10930 str += '\xa0'; 10931 spaces--; 10932 } 10933 } 10934 10935 // LATER: Fix normalized tab after editing plain text labels 10936 var tabNode = document.createElement('span'); 10937 tabNode.style.whiteSpace = 'pre'; 10938 tabNode.appendChild(document.createTextNode(str)); 10939 range.insertNode(tabNode); 10940 range.setStartAfter(tabNode); 10941 range.setEndAfter(tabNode); 10942 sel.removeAllRanges(); 10943 sel.addRange(range); 10944 }; 10945 10946 /** 10947 * Sets the alignment of the current selected cell. This sets the 10948 * alignment in the cell style, removes all alignment within the 10949 * text and invokes the built-in alignment function. 10950 * 10951 * Only the built-in function is invoked if shift is pressed or 10952 * if table cells are selected and shift is not pressed. 10953 */ 10954 mxCellEditor.prototype.alignText = function(align, evt) 10955 { 10956 var shiftPressed = evt != null && mxEvent.isShiftDown(evt); 10957 10958 if (shiftPressed || (window.getSelection != null && window.getSelection().containsNode != null)) 10959 { 10960 var allSelected = true; 10961 10962 this.graph.processElements(this.textarea, function(node) 10963 { 10964 if (shiftPressed || window.getSelection().containsNode(node, true)) 10965 { 10966 node.removeAttribute('align'); 10967 node.style.textAlign = null; 10968 } 10969 else 10970 { 10971 allSelected = false; 10972 } 10973 }); 10974 10975 if (allSelected) 10976 { 10977 this.graph.cellEditor.setAlign(align); 10978 } 10979 } 10980 10981 document.execCommand('justify' + align.toLowerCase(), false, null); 10982 }; 10983 10984 /** 10985 * Creates the keyboard event handler for the current graph and history. 10986 */ 10987 mxCellEditor.prototype.saveSelection = function() 10988 { 10989 if (window.getSelection) 10990 { 10991 var sel = window.getSelection(); 10992 10993 if (sel.getRangeAt && sel.rangeCount) 10994 { 10995 var ranges = []; 10996 10997 for (var i = 0, len = sel.rangeCount; i < len; ++i) 10998 { 10999 ranges.push(sel.getRangeAt(i)); 11000 } 11001 11002 return ranges; 11003 } 11004 } 11005 else if (document.selection && document.selection.createRange) 11006 { 11007 return document.selection.createRange(); 11008 } 11009 11010 return null; 11011 }; 11012 11013 /** 11014 * Creates the keyboard event handler for the current graph and history. 11015 */ 11016 mxCellEditor.prototype.restoreSelection = function(savedSel) 11017 { 11018 try 11019 { 11020 if (savedSel) 11021 { 11022 if (window.getSelection) 11023 { 11024 sel = window.getSelection(); 11025 sel.removeAllRanges(); 11026 11027 for (var i = 0, len = savedSel.length; i < len; ++i) 11028 { 11029 sel.addRange(savedSel[i]); 11030 } 11031 } 11032 else if (document.selection && savedSel.select) 11033 { 11034 savedSel.select(); 11035 } 11036 } 11037 } 11038 catch (e) 11039 { 11040 // ignore 11041 } 11042 }; 11043 11044 /** 11045 * Handling of special nl2Br style for not converting newlines to breaks in HTML labels. 11046 * NOTE: Since it's easier to set this when the label is created we assume that it does 11047 * not change during the lifetime of the mxText instance. 11048 */ 11049 var mxCellRendererInitializeLabel = mxCellRenderer.prototype.initializeLabel; 11050 mxCellRenderer.prototype.initializeLabel = function(state) 11051 { 11052 if (state.text != null) 11053 { 11054 state.text.replaceLinefeeds = mxUtils.getValue(state.style, 'nl2Br', '1') != '0'; 11055 } 11056 11057 mxCellRendererInitializeLabel.apply(this, arguments); 11058 }; 11059 11060 var mxConstraintHandlerUpdate = mxConstraintHandler.prototype.update; 11061 mxConstraintHandler.prototype.update = function(me, source) 11062 { 11063 if (this.isKeepFocusEvent(me) || !mxEvent.isAltDown(me.getEvent())) 11064 { 11065 mxConstraintHandlerUpdate.apply(this, arguments); 11066 } 11067 else 11068 { 11069 this.reset(); 11070 } 11071 }; 11072 11073 /** 11074 * No dashed shapes. 11075 */ 11076 mxGuide.prototype.createGuideShape = function(horizontal) 11077 { 11078 var guide = new mxPolyline([], mxConstants.GUIDE_COLOR, mxConstants.GUIDE_STROKEWIDTH); 11079 11080 return guide; 11081 }; 11082 11083 /** 11084 * HTML in-place editor 11085 */ 11086 mxCellEditor.prototype.escapeCancelsEditing = false; 11087 11088 /** 11089 * Overridden to set CSS classes. 11090 */ 11091 var mxCellEditorStartEditing = mxCellEditor.prototype.startEditing; 11092 mxCellEditor.prototype.startEditing = function(cell, trigger) 11093 { 11094 cell = this.graph.getStartEditingCell(cell, trigger); 11095 11096 mxCellEditorStartEditing.apply(this, arguments); 11097 11098 // Overrides class in case of HTML content to add 11099 // dashed borders for divs and table cells 11100 var state = this.graph.view.getState(cell); 11101 11102 if (state != null && state.style['html'] == 1) 11103 { 11104 this.textarea.className = 'mxCellEditor geContentEditable'; 11105 } 11106 else 11107 { 11108 this.textarea.className = 'mxCellEditor mxPlainTextEditor'; 11109 } 11110 11111 // Toggles markup vs wysiwyg mode 11112 this.codeViewMode = false; 11113 11114 // Stores current selection range when switching between markup and code 11115 this.switchSelectionState = null; 11116 11117 // Selects editing cell 11118 this.graph.setSelectionCell(cell); 11119 11120 // Enables focus outline for edges and edge labels 11121 var parent = this.graph.getModel().getParent(cell); 11122 var geo = this.graph.getCellGeometry(cell); 11123 11124 if ((this.graph.getModel().isEdge(parent) && geo != null && geo.relative) || 11125 this.graph.getModel().isEdge(cell)) 11126 { 11127 // IE>8 and FF on Windows uses outline default of none 11128 if (mxClient.IS_IE || mxClient.IS_IE11 || (mxClient.IS_FF && mxClient.IS_WIN)) 11129 { 11130 this.textarea.style.outline = 'gray dotted 1px'; 11131 } 11132 else 11133 { 11134 this.textarea.style.outline = ''; 11135 } 11136 } 11137 } 11138 11139 /** 11140 * HTML in-place editor 11141 */ 11142 var cellEditorInstallListeners = mxCellEditor.prototype.installListeners; 11143 mxCellEditor.prototype.installListeners = function(elt) 11144 { 11145 cellEditorInstallListeners.apply(this, arguments); 11146 11147 // Adds a reference from the clone to the original node, recursively 11148 function reference(node, clone) 11149 { 11150 clone.originalNode = node; 11151 11152 node = node.firstChild; 11153 var child = clone.firstChild; 11154 11155 while (node != null && child != null) 11156 { 11157 reference(node, child); 11158 node = node.nextSibling; 11159 child = child.nextSibling; 11160 } 11161 11162 return clone; 11163 }; 11164 11165 // Checks the given node for new nodes, recursively 11166 function checkNode(node, clone) 11167 { 11168 if (node != null) 11169 { 11170 if (clone.originalNode != node) 11171 { 11172 cleanNode(node); 11173 } 11174 else 11175 { 11176 node = node.firstChild; 11177 clone = clone.firstChild; 11178 11179 while (node != null) 11180 { 11181 var nextNode = node.nextSibling; 11182 11183 if (clone == null) 11184 { 11185 cleanNode(node); 11186 } 11187 else 11188 { 11189 checkNode(node, clone); 11190 clone = clone.nextSibling; 11191 } 11192 11193 node = nextNode; 11194 } 11195 } 11196 } 11197 }; 11198 11199 // Removes unused DOM nodes and attributes, recursively 11200 function cleanNode(node) 11201 { 11202 var child = node.firstChild; 11203 11204 while (child != null) 11205 { 11206 var next = child.nextSibling; 11207 cleanNode(child); 11208 child = next; 11209 } 11210 11211 if ((node.nodeType != 1 || (node.nodeName !== 'BR' && node.firstChild == null)) && 11212 (node.nodeType != 3 || mxUtils.trim(mxUtils.getTextContent(node)).length == 0)) 11213 { 11214 node.parentNode.removeChild(node); 11215 } 11216 else 11217 { 11218 // Removes linefeeds 11219 if (node.nodeType == 3) 11220 { 11221 mxUtils.setTextContent(node, mxUtils.getTextContent(node).replace(/\n|\r/g, '')); 11222 } 11223 11224 // Removes CSS classes and styles (for Word and Excel) 11225 if (node.nodeType == 1) 11226 { 11227 node.removeAttribute('style'); 11228 node.removeAttribute('class'); 11229 node.removeAttribute('width'); 11230 node.removeAttribute('cellpadding'); 11231 node.removeAttribute('cellspacing'); 11232 node.removeAttribute('border'); 11233 } 11234 } 11235 }; 11236 11237 // Handles paste from Word, Excel etc by removing styles, classnames and unused nodes 11238 // LATER: Fix undo/redo for paste 11239 if (document.documentMode !== 7 && document.documentMode !== 8) 11240 { 11241 mxEvent.addListener(this.textarea, 'paste', mxUtils.bind(this, function(evt) 11242 { 11243 var clone = reference(this.textarea, this.textarea.cloneNode(true)); 11244 11245 window.setTimeout(mxUtils.bind(this, function() 11246 { 11247 if (this.textarea != null) 11248 { 11249 // Paste from Word or Excel 11250 if (this.textarea.innerHTML.indexOf('<o:OfficeDocumentSettings>') >= 0 || 11251 this.textarea.innerHTML.indexOf('<!--[if !mso]>') >= 0) 11252 { 11253 checkNode(this.textarea, clone); 11254 } 11255 else 11256 { 11257 Graph.removePasteFormatting(this.textarea); 11258 } 11259 } 11260 }), 0); 11261 })); 11262 } 11263 }; 11264 11265 mxCellEditor.prototype.toggleViewMode = function() 11266 { 11267 var state = this.graph.view.getState(this.editingCell); 11268 11269 if (state != null) 11270 { 11271 var nl2Br = state != null && mxUtils.getValue(state.style, 'nl2Br', '1') != '0'; 11272 var tmp = this.saveSelection(); 11273 11274 if (!this.codeViewMode) 11275 { 11276 // Clears the initial empty label on the first keystroke 11277 if (this.clearOnChange && this.textarea.innerHTML == this.getEmptyLabelText()) 11278 { 11279 this.clearOnChange = false; 11280 this.textarea.innerHTML = ''; 11281 } 11282 11283 // Removes newlines from HTML and converts breaks to newlines 11284 // to match the HTML output in plain text 11285 var content = mxUtils.htmlEntities(this.textarea.innerHTML); 11286 11287 // Workaround for trailing line breaks being ignored in the editor 11288 if (document.documentMode != 8) 11289 { 11290 content = mxUtils.replaceTrailingNewlines(content, '<div><br></div>'); 11291 } 11292 11293 content = this.graph.sanitizeHtml((nl2Br) ? content.replace(/\n/g, '').replace(/<br\s*.?>/g, '<br>') : content, true); 11294 this.textarea.className = 'mxCellEditor mxPlainTextEditor'; 11295 11296 var size = mxConstants.DEFAULT_FONTSIZE; 11297 11298 this.textarea.style.lineHeight = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? Math.round(size * mxConstants.LINE_HEIGHT) + 'px' : mxConstants.LINE_HEIGHT; 11299 this.textarea.style.fontSize = Math.round(size) + 'px'; 11300 this.textarea.style.textDecoration = ''; 11301 this.textarea.style.fontWeight = 'normal'; 11302 this.textarea.style.fontStyle = ''; 11303 this.textarea.style.fontFamily = mxConstants.DEFAULT_FONTFAMILY; 11304 this.textarea.style.textAlign = 'left'; 11305 this.textarea.style.width = ''; 11306 11307 // Adds padding to make cursor visible with borders 11308 this.textarea.style.padding = '2px'; 11309 11310 if (this.textarea.innerHTML != content) 11311 { 11312 this.textarea.innerHTML = content; 11313 } 11314 11315 this.codeViewMode = true; 11316 } 11317 else 11318 { 11319 var content = mxUtils.extractTextWithWhitespace(this.textarea.childNodes); 11320 11321 // Strips trailing line break 11322 if (content.length > 0 && content.charAt(content.length - 1) == '\n') 11323 { 11324 content = content.substring(0, content.length - 1); 11325 } 11326 11327 content = this.graph.sanitizeHtml((nl2Br) ? content.replace(/\n/g, '<br/>') : content, true) 11328 this.textarea.className = 'mxCellEditor geContentEditable'; 11329 11330 var size = mxUtils.getValue(state.style, mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE); 11331 var family = mxUtils.getValue(state.style, mxConstants.STYLE_FONTFAMILY, mxConstants.DEFAULT_FONTFAMILY); 11332 var align = mxUtils.getValue(state.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_LEFT); 11333 var bold = (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) & 11334 mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD; 11335 var italic = (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) & 11336 mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC; 11337 var txtDecor = []; 11338 11339 if ((mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) & 11340 mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) 11341 { 11342 txtDecor.push('underline'); 11343 } 11344 11345 if ((mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) & 11346 mxConstants.FONT_STRIKETHROUGH) == mxConstants.FONT_STRIKETHROUGH) 11347 { 11348 txtDecor.push('line-through'); 11349 } 11350 11351 this.textarea.style.lineHeight = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? Math.round(size * mxConstants.LINE_HEIGHT) + 'px' : mxConstants.LINE_HEIGHT; 11352 this.textarea.style.fontSize = Math.round(size) + 'px'; 11353 this.textarea.style.textDecoration = txtDecor.join(' '); 11354 this.textarea.style.fontWeight = (bold) ? 'bold' : 'normal'; 11355 this.textarea.style.fontStyle = (italic) ? 'italic' : ''; 11356 this.textarea.style.fontFamily = family; 11357 this.textarea.style.textAlign = align; 11358 this.textarea.style.padding = '0px'; 11359 11360 if (this.textarea.innerHTML != content) 11361 { 11362 this.textarea.innerHTML = content; 11363 11364 if (this.textarea.innerHTML.length == 0) 11365 { 11366 this.textarea.innerHTML = this.getEmptyLabelText(); 11367 this.clearOnChange = this.textarea.innerHTML.length > 0; 11368 } 11369 } 11370 11371 this.codeViewMode = false; 11372 } 11373 11374 this.textarea.focus(); 11375 11376 if (this.switchSelectionState != null) 11377 { 11378 this.restoreSelection(this.switchSelectionState); 11379 } 11380 11381 this.switchSelectionState = tmp; 11382 this.resize(); 11383 } 11384 }; 11385 11386 var mxCellEditorResize = mxCellEditor.prototype.resize; 11387 mxCellEditor.prototype.resize = function(state, trigger) 11388 { 11389 if (this.textarea != null) 11390 { 11391 var state = this.graph.getView().getState(this.editingCell); 11392 11393 if (this.codeViewMode && state != null) 11394 { 11395 var scale = state.view.scale; 11396 this.bounds = mxRectangle.fromRectangle(state); 11397 11398 // General placement of code editor if cell has no size 11399 // LATER: Fix HTML editor bounds for edge labels 11400 if (this.bounds.width == 0 && this.bounds.height == 0) 11401 { 11402 this.bounds.width = 160 * scale; 11403 this.bounds.height = 60 * scale; 11404 11405 var m = (state.text != null) ? state.text.margin : null; 11406 11407 if (m == null) 11408 { 11409 m = mxUtils.getAlignmentAsPoint(mxUtils.getValue(state.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER), 11410 mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE)); 11411 } 11412 11413 this.bounds.x += m.x * this.bounds.width; 11414 this.bounds.y += m.y * this.bounds.height; 11415 } 11416 11417 this.textarea.style.width = Math.round((this.bounds.width - 4) / scale) + 'px'; 11418 this.textarea.style.height = Math.round((this.bounds.height - 4) / scale) + 'px'; 11419 this.textarea.style.overflow = 'auto'; 11420 11421 // Adds scrollbar offset if visible 11422 if (this.textarea.clientHeight < this.textarea.offsetHeight) 11423 { 11424 this.textarea.style.height = Math.round((this.bounds.height / scale)) + (this.textarea.offsetHeight - this.textarea.clientHeight) + 'px'; 11425 this.bounds.height = parseInt(this.textarea.style.height) * scale; 11426 } 11427 11428 if (this.textarea.clientWidth < this.textarea.offsetWidth) 11429 { 11430 this.textarea.style.width = Math.round((this.bounds.width / scale)) + (this.textarea.offsetWidth - this.textarea.clientWidth) + 'px'; 11431 this.bounds.width = parseInt(this.textarea.style.width) * scale; 11432 } 11433 11434 this.textarea.style.left = Math.round(this.bounds.x) + 'px'; 11435 this.textarea.style.top = Math.round(this.bounds.y) + 'px'; 11436 11437 mxUtils.setPrefixedStyle(this.textarea.style, 'transform', 'scale(' + scale + ',' + scale + ')'); 11438 } 11439 else 11440 { 11441 this.textarea.style.height = ''; 11442 this.textarea.style.overflow = ''; 11443 mxCellEditorResize.apply(this, arguments); 11444 } 11445 } 11446 }; 11447 11448 mxCellEditorGetInitialValue = mxCellEditor.prototype.getInitialValue; 11449 mxCellEditor.prototype.getInitialValue = function(state, trigger) 11450 { 11451 if (mxUtils.getValue(state.style, 'html', '0') == '0') 11452 { 11453 return mxCellEditorGetInitialValue.apply(this, arguments); 11454 } 11455 else 11456 { 11457 var result = this.graph.getEditingValue(state.cell, trigger) 11458 11459 if (mxUtils.getValue(state.style, 'nl2Br', '1') == '1') 11460 { 11461 result = result.replace(/\n/g, '<br/>'); 11462 } 11463 11464 result = this.graph.sanitizeHtml(result, true); 11465 11466 return result; 11467 } 11468 }; 11469 11470 mxCellEditorGetCurrentValue = mxCellEditor.prototype.getCurrentValue; 11471 mxCellEditor.prototype.getCurrentValue = function(state) 11472 { 11473 if (mxUtils.getValue(state.style, 'html', '0') == '0') 11474 { 11475 return mxCellEditorGetCurrentValue.apply(this, arguments); 11476 } 11477 else 11478 { 11479 var result = this.graph.sanitizeHtml(this.textarea.innerHTML, true); 11480 11481 if (mxUtils.getValue(state.style, 'nl2Br', '1') == '1') 11482 { 11483 result = result.replace(/\r\n/g, '<br/>').replace(/\n/g, '<br/>'); 11484 } 11485 else 11486 { 11487 result = result.replace(/\r\n/g, '').replace(/\n/g, ''); 11488 } 11489 11490 return result; 11491 } 11492 }; 11493 11494 var mxCellEditorStopEditing = mxCellEditor.prototype.stopEditing; 11495 mxCellEditor.prototype.stopEditing = function(cancel) 11496 { 11497 // Restores default view mode before applying value 11498 if (this.codeViewMode) 11499 { 11500 this.toggleViewMode(); 11501 } 11502 11503 mxCellEditorStopEditing.apply(this, arguments); 11504 11505 // Tries to move focus back to container after editing if possible 11506 this.focusContainer(); 11507 }; 11508 11509 mxCellEditor.prototype.focusContainer = function() 11510 { 11511 try 11512 { 11513 this.graph.container.focus(); 11514 } 11515 catch (e) 11516 { 11517 // ignore 11518 } 11519 }; 11520 11521 var mxCellEditorApplyValue = mxCellEditor.prototype.applyValue; 11522 mxCellEditor.prototype.applyValue = function(state, value) 11523 { 11524 // Removes empty relative child labels in edges 11525 this.graph.getModel().beginUpdate(); 11526 11527 try 11528 { 11529 mxCellEditorApplyValue.apply(this, arguments); 11530 11531 if (value == '' && this.graph.isCellDeletable(state.cell) && 11532 this.graph.model.getChildCount(state.cell) == 0 && 11533 this.graph.isTransparentState(state)) 11534 { 11535 this.graph.removeCells([state.cell], false); 11536 } 11537 } 11538 finally 11539 { 11540 this.graph.getModel().endUpdate(); 11541 } 11542 }; 11543 11544 /** 11545 * Returns the background color to be used for the editing box. This returns 11546 * the label background for edge labels and null for all other cases. 11547 */ 11548 mxCellEditor.prototype.getBackgroundColor = function(state) 11549 { 11550 var color = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_BACKGROUNDCOLOR, null); 11551 11552 if ((color == null || color == mxConstants.NONE) && 11553 (state.cell.geometry != null && state.cell.geometry.width > 0) && 11554 (mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0) != 0 || 11555 mxUtils.getValue(state.style, mxConstants.STYLE_HORIZONTAL, 1) == 0)) 11556 { 11557 color = mxUtils.getValue(state.style, mxConstants.STYLE_FILLCOLOR, null); 11558 } 11559 11560 if (color == mxConstants.NONE) 11561 { 11562 color = null; 11563 } 11564 11565 return color; 11566 }; 11567 11568 mxCellEditor.prototype.getMinimumSize = function(state) 11569 { 11570 var scale = this.graph.getView().scale; 11571 11572 return new mxRectangle(0, 0, (state.text == null) ? 30 : state.text.size * scale + 20, 30); 11573 }; 11574 11575 /** 11576 * Hold Alt to ignore drop target. 11577 */ 11578 mxGraphHandlerIsValidDropTarget = mxGraphHandler.prototype.isValidDropTarget; 11579 mxGraphHandler.prototype.isValidDropTarget = function(target, me) 11580 { 11581 return mxGraphHandlerIsValidDropTarget.apply(this, arguments) && 11582 !mxEvent.isAltDown(me.getEvent); 11583 }; 11584 11585 /** 11586 * Hints on handlers 11587 */ 11588 function createHint() 11589 { 11590 var hint = document.createElement('div'); 11591 hint.className = 'geHint'; 11592 hint.style.whiteSpace = 'nowrap'; 11593 hint.style.position = 'absolute'; 11594 11595 return hint; 11596 }; 11597 11598 /** 11599 * Format pixels in the given unit 11600 */ 11601 function formatHintText(pixels, unit) 11602 { 11603 switch(unit) 11604 { 11605 case mxConstants.POINTS: 11606 return pixels; 11607 case mxConstants.MILLIMETERS: 11608 return (pixels / mxConstants.PIXELS_PER_MM).toFixed(1); 11609 case mxConstants.METERS: 11610 return (pixels / (mxConstants.PIXELS_PER_MM * 1000)).toFixed(4); 11611 case mxConstants.INCHES: 11612 return (pixels / mxConstants.PIXELS_PER_INCH).toFixed(2); 11613 } 11614 }; 11615 11616 11617 mxGraphView.prototype.formatUnitText = function(pixels) 11618 { 11619 return pixels? formatHintText(pixels, this.unit) : pixels; 11620 }; 11621 11622 /** 11623 * Updates the hint for the current operation. 11624 */ 11625 mxGraphHandler.prototype.updateHint = function(me) 11626 { 11627 if (this.pBounds != null && (this.shape != null || this.livePreviewActive)) 11628 { 11629 if (this.hint == null) 11630 { 11631 this.hint = createHint(); 11632 this.graph.container.appendChild(this.hint); 11633 } 11634 11635 var t = this.graph.view.translate; 11636 var s = this.graph.view.scale; 11637 var x = this.roundLength((this.bounds.x + this.currentDx) / s - t.x); 11638 var y = this.roundLength((this.bounds.y + this.currentDy) / s - t.y); 11639 var unit = this.graph.view.unit; 11640 11641 this.hint.innerHTML = formatHintText(x, unit) + ', ' + formatHintText(y, unit); 11642 11643 this.hint.style.left = (this.pBounds.x + this.currentDx + 11644 Math.round((this.pBounds.width - this.hint.clientWidth) / 2)) + 'px'; 11645 this.hint.style.top = (this.pBounds.y + this.currentDy + 11646 this.pBounds.height + Editor.hintOffset) + 'px'; 11647 } 11648 }; 11649 11650 /** 11651 * Updates the hint for the current operation. 11652 */ 11653 mxGraphHandler.prototype.removeHint = function() 11654 { 11655 if (this.hint != null) 11656 { 11657 this.hint.parentNode.removeChild(this.hint); 11658 this.hint = null; 11659 } 11660 }; 11661 11662 /** 11663 * Overridden to allow for shrinking pools when lanes are resized. 11664 */ 11665 var stackLayoutResizeCell = mxStackLayout.prototype.resizeCell; 11666 mxStackLayout.prototype.resizeCell = function(cell, bounds) 11667 { 11668 stackLayoutResizeCell.apply(this, arguments); 11669 var style = this.graph.getCellStyle(cell); 11670 11671 if (style['childLayout'] == null) 11672 { 11673 var parent = this.graph.model.getParent(cell); 11674 var geo = (parent != null) ? this.graph.getCellGeometry(parent) : null; 11675 11676 if (geo != null) 11677 { 11678 style = this.graph.getCellStyle(parent); 11679 11680 if (style['childLayout'] == 'stackLayout') 11681 { 11682 var border = parseFloat(mxUtils.getValue(style, 'stackBorder', mxStackLayout.prototype.border)); 11683 var horizontal = mxUtils.getValue(style, 'horizontalStack', '1') == '1'; 11684 var start = this.graph.getActualStartSize(parent); 11685 geo = geo.clone(); 11686 11687 if (horizontal) 11688 { 11689 geo.height = bounds.height + start.y + start.height + 2 * border; 11690 } 11691 else 11692 { 11693 geo.width = bounds.width + start.x + start.width + 2 * border; 11694 } 11695 11696 this.graph.model.setGeometry(parent, geo); 11697 } 11698 } 11699 } 11700 }; 11701 11702 /** 11703 * Shows handle for table instead of rows and cells. 11704 */ 11705 var selectionCellsHandlerGetHandledSelectionCells = mxSelectionCellsHandler.prototype.getHandledSelectionCells; 11706 mxSelectionCellsHandler.prototype.getHandledSelectionCells = function() 11707 { 11708 var cells = selectionCellsHandlerGetHandledSelectionCells.apply(this, arguments); 11709 var dict = new mxDictionary(); 11710 var model = this.graph.model; 11711 var result = []; 11712 11713 function addCell(cell) 11714 { 11715 if (!dict.get(cell)) 11716 { 11717 dict.put(cell, true); 11718 result.push(cell); 11719 } 11720 }; 11721 11722 for (var i = 0; i < cells.length; i++) 11723 { 11724 var cell = cells[i]; 11725 11726 if (this.graph.isTableCell(cell)) 11727 { 11728 addCell(model.getParent(model.getParent(cell))); 11729 } 11730 else if (this.graph.isTableRow(cell)) 11731 { 11732 addCell(model.getParent(cell)); 11733 } 11734 11735 addCell(cell); 11736 } 11737 11738 return result; 11739 }; 11740 11741 /** 11742 * Creates the shape used to draw the selection border. 11743 */ 11744 var vertexHandlerCreateParentHighlightShape = mxVertexHandler.prototype.createParentHighlightShape; 11745 mxVertexHandler.prototype.createParentHighlightShape = function(bounds) 11746 { 11747 var shape = vertexHandlerCreateParentHighlightShape.apply(this, arguments); 11748 11749 shape.stroke = '#C0C0C0'; 11750 shape.strokewidth = 1; 11751 11752 return shape; 11753 }; 11754 11755 /** 11756 * Creates the shape used to draw the selection border. 11757 */ 11758 var edgeHandlerCreateParentHighlightShape = mxEdgeHandler.prototype.createParentHighlightShape; 11759 mxEdgeHandler.prototype.createParentHighlightShape = function(bounds) 11760 { 11761 var shape = edgeHandlerCreateParentHighlightShape.apply(this, arguments); 11762 11763 shape.stroke = '#C0C0C0'; 11764 shape.strokewidth = 1; 11765 11766 return shape; 11767 }; 11768 11769 /** 11770 * Moves rotation handle to top, right corner. 11771 */ 11772 mxVertexHandler.prototype.rotationHandleVSpacing = -12; 11773 mxVertexHandler.prototype.getRotationHandlePosition = function() 11774 { 11775 var padding = this.getHandlePadding(); 11776 11777 return new mxPoint(this.bounds.x + this.bounds.width - this.rotationHandleVSpacing + padding.x / 2, 11778 this.bounds.y + this.rotationHandleVSpacing - padding.y / 2) 11779 }; 11780 11781 /** 11782 * Enables recursive resize for groups. 11783 */ 11784 mxVertexHandler.prototype.isRecursiveResize = function(state, me) 11785 { 11786 return this.graph.isRecursiveVertexResize(state) && 11787 !mxEvent.isControlDown(me.getEvent()); 11788 }; 11789 11790 /** 11791 * Enables centered resize events. 11792 */ 11793 mxVertexHandler.prototype.isCenteredEvent = function(state, me) 11794 { 11795 return (!(!this.graph.isSwimlane(state.cell) && this.graph.model.getChildCount(state.cell) > 0 && 11796 !this.graph.isCellCollapsed(state.cell) && 11797 mxUtils.getValue(state.style, 'recursiveResize', '1') == '1' && 11798 mxUtils.getValue(state.style, 'childLayout', null) == null) && 11799 mxEvent.isControlDown(me.getEvent())) || 11800 mxEvent.isMetaDown(me.getEvent()); 11801 }; 11802 11803 /** 11804 * Hides rotation handle for table cells and rows. 11805 */ 11806 var vertexHandlerIsRotationHandleVisible = mxVertexHandler.prototype.isRotationHandleVisible; 11807 mxVertexHandler.prototype.isRotationHandleVisible = function() 11808 { 11809 return vertexHandlerIsRotationHandleVisible.apply(this, arguments) && 11810 !this.graph.isTableCell(this.state.cell) && 11811 !this.graph.isTableRow(this.state.cell) && 11812 !this.graph.isTable(this.state.cell); 11813 }; 11814 11815 /** 11816 * Hides rotation handle for table cells and rows. 11817 */ 11818 mxVertexHandler.prototype.getSizerBounds = function() 11819 { 11820 if (this.graph.isTableCell(this.state.cell)) 11821 { 11822 return this.graph.view.getState(this.graph.model.getParent(this.graph.model.getParent(this.state.cell))); 11823 } 11824 else 11825 { 11826 return this.bounds; 11827 } 11828 }; 11829 11830 /** 11831 * Hides rotation handle for table cells and rows. 11832 */ 11833 var vertexHandlerIsParentHighlightVisible = mxVertexHandler.prototype.isParentHighlightVisible; 11834 mxVertexHandler.prototype.isParentHighlightVisible = function() 11835 { 11836 return vertexHandlerIsParentHighlightVisible.apply(this, arguments) && 11837 !this.graph.isTableCell(this.state.cell) && 11838 !this.graph.isTableRow(this.state.cell); 11839 }; 11840 11841 /** 11842 * Hides rotation handle for table cells and rows. 11843 */ 11844 var vertexHandlerIsCustomHandleVisible = mxVertexHandler.prototype.isCustomHandleVisible; 11845 mxVertexHandler.prototype.isCustomHandleVisible = function(handle) 11846 { 11847 return handle.tableHandle || 11848 (vertexHandlerIsCustomHandleVisible.apply(this, arguments) && 11849 (!this.graph.isTable(this.state.cell) || 11850 this.graph.isCellSelected(this.state.cell))); 11851 }; 11852 11853 /** 11854 * Adds selection border inset for table cells and rows. 11855 */ 11856 mxVertexHandler.prototype.getSelectionBorderInset = function() 11857 { 11858 var result = 0; 11859 11860 if (this.graph.isTableRow(this.state.cell)) 11861 { 11862 result = 1; 11863 } 11864 else if (this.graph.isTableCell(this.state.cell)) 11865 { 11866 result = 2; 11867 } 11868 11869 return result; 11870 }; 11871 11872 /** 11873 * Adds custom handles for table cells. 11874 */ 11875 var vertexHandlerGetSelectionBorderBounds = mxVertexHandler.prototype.getSelectionBorderBounds; 11876 mxVertexHandler.prototype.getSelectionBorderBounds = function() 11877 { 11878 return vertexHandlerGetSelectionBorderBounds.apply(this, arguments).grow( 11879 -this.getSelectionBorderInset()); 11880 }; 11881 11882 /** 11883 * Adds custom handles for table cells. 11884 */ 11885 var vertexHandlerCreateCustomHandles = mxVertexHandler.prototype.createCustomHandles; 11886 mxVertexHandler.prototype.createCustomHandles = function() 11887 { 11888 var handles = vertexHandlerCreateCustomHandles.apply(this, arguments); 11889 11890 if (this.graph.isTable(this.state.cell)) 11891 { 11892 var graph = this.graph; 11893 var model = graph.model; 11894 var tableState = this.state; 11895 var sel = this.selectionBorder; 11896 var self = this; 11897 11898 if (handles == null) 11899 { 11900 handles = []; 11901 } 11902 11903 // Adds handles for rows and columns 11904 var rows = graph.view.getCellStates(model.getChildCells(this.state.cell, true)); 11905 11906 if (rows.length > 0) 11907 { 11908 var cols = model.getChildCells(rows[0].cell, true); 11909 11910 // Adds column width handles 11911 for (var i = 0; i < cols.length; i++) 11912 { 11913 (mxUtils.bind(this, function(index) 11914 { 11915 var colState = graph.view.getState(cols[index]); 11916 var geo = graph.getCellGeometry(cols[index]); 11917 var g = (geo.alternateBounds != null) ? geo.alternateBounds : geo; 11918 11919 if (colState == null) 11920 { 11921 colState = new mxCellState(graph.view, cols[index], 11922 graph.getCellStyle(cols[index])); 11923 colState.x = tableState.x + geo.x * graph.view.scale; 11924 colState.y = tableState.y + geo.y * graph.view.scale; 11925 colState.width = g.width * graph.view.scale; 11926 colState.height = g.height * graph.view.scale; 11927 colState.updateCachedBounds(); 11928 } 11929 11930 var nextCol = (index < cols.length - 1) ? cols[index + 1] : null; 11931 var ngeo = (nextCol != null) ? graph.getCellGeometry(nextCol) : null; 11932 var ng = (ngeo != null && ngeo.alternateBounds != null) ? ngeo.alternateBounds : ngeo; 11933 11934 var shape = new mxLine(new mxRectangle(), mxConstants.NONE, 1, true); 11935 shape.isDashed = sel.isDashed; 11936 11937 // Workaround for event handling on overlapping cells with tolerance 11938 shape.svgStrokeTolerance++; 11939 var handle = new mxHandle(colState, 'col-resize', null, shape); 11940 handle.tableHandle = true; 11941 var dx = 0; 11942 11943 handle.shape.node.parentNode.insertBefore(handle.shape.node, 11944 handle.shape.node.parentNode.firstChild); 11945 11946 handle.redraw = function() 11947 { 11948 if (this.shape != null) 11949 { 11950 var start = graph.getActualStartSize(tableState.cell); 11951 this.shape.stroke = (dx == 0) ? mxConstants.NONE : sel.stroke; 11952 this.shape.bounds.x = this.state.x + (g.width + 11953 dx) * this.graph.view.scale; 11954 this.shape.bounds.width = 1; 11955 this.shape.bounds.y = tableState.y + ((index == cols.length - 1) ? 11956 0 : start.y * this.graph.view.scale); 11957 this.shape.bounds.height = tableState.height - 11958 ((index == cols.length - 1) ? 0 : 11959 (start.height + start.y) * this.graph.view.scale); 11960 this.shape.redraw(); 11961 } 11962 }; 11963 11964 var shiftPressed = false; 11965 11966 handle.setPosition = function(bounds, pt, me) 11967 { 11968 dx = Math.max(Graph.minTableColumnWidth - g.width, 11969 pt.x - bounds.x - g.width); 11970 shiftPressed = mxEvent.isShiftDown(me.getEvent()); 11971 11972 if (ng != null && !shiftPressed) 11973 { 11974 dx = Math.min(dx, ng.width - Graph.minTableColumnWidth); 11975 } 11976 }; 11977 11978 handle.execute = function(me) 11979 { 11980 if (dx != 0) 11981 { 11982 graph.setTableColumnWidth(this.state.cell, 11983 dx, shiftPressed); 11984 } 11985 else if (!self.blockDelayedSelection) 11986 { 11987 var temp = graph.getCellAt(me.getGraphX(), me.getGraphY()) || tableState.cell; 11988 graph.graphHandler.selectCellForEvent(temp, me); 11989 } 11990 11991 dx = 0; 11992 }; 11993 11994 // Stops repaint of text label via vertex handler 11995 handle.positionChanged = function() 11996 { 11997 // do nothing 11998 }; 11999 12000 handle.reset = function() 12001 { 12002 dx = 0; 12003 }; 12004 12005 handles.push(handle); 12006 }))(i); 12007 } 12008 12009 // Adds row height handles 12010 for (var i = 0; i < rows.length; i++) 12011 { 12012 (mxUtils.bind(this, function(index) 12013 { 12014 var rowState = rows[index]; 12015 12016 var shape = new mxLine(new mxRectangle(), mxConstants.NONE, 1); 12017 shape.isDashed = sel.isDashed; 12018 shape.svgStrokeTolerance++; 12019 12020 var handle = new mxHandle(rowState, 'row-resize', null, shape); 12021 handle.tableHandle = true; 12022 var dy = 0; 12023 12024 handle.shape.node.parentNode.insertBefore(handle.shape.node, 12025 handle.shape.node.parentNode.firstChild); 12026 12027 handle.redraw = function() 12028 { 12029 if (this.shape != null && this.state.shape != null) 12030 { 12031 this.shape.stroke = (dy == 0) ? mxConstants.NONE : sel.stroke; 12032 this.shape.bounds.x = this.state.x; 12033 this.shape.bounds.width = this.state.width; 12034 this.shape.bounds.y = this.state.y + this.state.height + 12035 dy * this.graph.view.scale; 12036 this.shape.bounds.height = 1; 12037 this.shape.redraw(); 12038 } 12039 }; 12040 12041 handle.setPosition = function(bounds, pt, me) 12042 { 12043 dy = Math.max(Graph.minTableRowHeight - bounds.height, 12044 pt.y - bounds.y - bounds.height); 12045 }; 12046 12047 handle.execute = function(me) 12048 { 12049 if (dy != 0) 12050 { 12051 graph.setTableRowHeight(this.state.cell, dy, 12052 !mxEvent.isShiftDown(me.getEvent())); 12053 } 12054 else if (!self.blockDelayedSelection) 12055 { 12056 var temp = graph.getCellAt(me.getGraphX(), me.getGraphY()) || tableState.cell; 12057 graph.graphHandler.selectCellForEvent(temp, me); 12058 } 12059 12060 dy = 0; 12061 }; 12062 12063 handle.reset = function() 12064 { 12065 dy = 0; 12066 }; 12067 12068 handles.push(handle); 12069 }))(i); 12070 } 12071 } 12072 } 12073 12074 // Reserve gives point handles precedence over line handles 12075 return (handles != null) ? handles.reverse() : null; 12076 }; 12077 12078 var vertexHandlerSetHandlesVisible = mxVertexHandler.prototype.setHandlesVisible; 12079 12080 mxVertexHandler.prototype.setHandlesVisible = function(visible) 12081 { 12082 vertexHandlerSetHandlesVisible.apply(this, arguments); 12083 12084 if (this.moveHandles != null) 12085 { 12086 for (var i = 0; i < this.moveHandles.length; i++) 12087 { 12088 this.moveHandles[i].style.visibility = (visible) ? '' : 'hidden'; 12089 } 12090 } 12091 12092 if (this.cornerHandles != null) 12093 { 12094 for (var i = 0; i < this.cornerHandles.length; i++) 12095 { 12096 this.cornerHandles[i].node.style.visibility = (visible) ? '' : 'hidden'; 12097 } 12098 } 12099 }; 12100 12101 /** 12102 * Creates or updates special handles for moving rows. 12103 */ 12104 mxVertexHandler.prototype.refreshMoveHandles = function() 12105 { 12106 var graph = this.graph; 12107 var model = graph.model; 12108 12109 // Destroys existing handles 12110 if (this.moveHandles != null) 12111 { 12112 for (var i = 0; i < this.moveHandles.length; i++) 12113 { 12114 this.moveHandles[i].parentNode.removeChild(this.moveHandles[i]); 12115 } 12116 12117 this.moveHandles = null; 12118 } 12119 12120 // Creates new handles 12121 this.moveHandles = []; 12122 12123 for (var i = 0; i < model.getChildCount(this.state.cell); i++) 12124 { 12125 (mxUtils.bind(this, function(rowState) 12126 { 12127 if (rowState != null && model.isVertex(rowState.cell)) 12128 { 12129 // Adds handle to move row 12130 // LATER: Move to overlay pane to hide during zoom but keep padding 12131 var moveHandle = mxUtils.createImage(Editor.rowMoveImage); 12132 moveHandle.style.position = 'absolute'; 12133 moveHandle.style.cursor = 'pointer'; 12134 moveHandle.style.width = '7px'; 12135 moveHandle.style.height = '4px'; 12136 moveHandle.style.padding = '4px 2px 4px 2px'; 12137 moveHandle.rowState = rowState; 12138 12139 mxEvent.addGestureListeners(moveHandle, mxUtils.bind(this, function(evt) 12140 { 12141 this.graph.popupMenuHandler.hideMenu(); 12142 this.graph.stopEditing(false); 12143 12144 if (this.graph.isToggleEvent(evt) || 12145 !this.graph.isCellSelected(rowState.cell)) 12146 { 12147 this.graph.selectCellForEvent(rowState.cell, evt); 12148 } 12149 12150 if (!mxEvent.isPopupTrigger(evt)) 12151 { 12152 this.graph.graphHandler.start(this.state.cell, 12153 mxEvent.getClientX(evt), mxEvent.getClientY(evt), 12154 this.graph.getSelectionCells()); 12155 this.graph.graphHandler.cellWasClicked = true; 12156 this.graph.isMouseTrigger = mxEvent.isMouseEvent(evt); 12157 this.graph.isMouseDown = true; 12158 } 12159 12160 mxEvent.consume(evt); 12161 }), null, mxUtils.bind(this, function(evt) 12162 { 12163 if (mxEvent.isPopupTrigger(evt)) 12164 { 12165 this.graph.popupMenuHandler.popup(mxEvent.getClientX(evt), 12166 mxEvent.getClientY(evt), rowState.cell, evt); 12167 mxEvent.consume(evt); 12168 } 12169 })); 12170 12171 this.moveHandles.push(moveHandle); 12172 this.graph.container.appendChild(moveHandle); 12173 12174 } 12175 }))(this.graph.view.getState(model.getChildAt(this.state.cell, i))); 12176 } 12177 }; 12178 12179 /** 12180 * Adds handle padding for editing cells and exceptions. 12181 */ 12182 mxVertexHandler.prototype.refresh = function() 12183 { 12184 if (this.customHandles != null) 12185 { 12186 for (var i = 0; i < this.customHandles.length; i++) 12187 { 12188 this.customHandles[i].destroy(); 12189 } 12190 12191 this.customHandles = this.createCustomHandles(); 12192 } 12193 12194 if (this.graph.isTable(this.state.cell)) 12195 { 12196 this.refreshMoveHandles(); 12197 } 12198 }; 12199 12200 /** 12201 * Adds handle padding for editing cells and exceptions. 12202 */ 12203 var vertexHandlerGetHandlePadding = mxVertexHandler.prototype.getHandlePadding; 12204 mxVertexHandler.prototype.getHandlePadding = function() 12205 { 12206 var result = new mxPoint(0, 0); 12207 var tol = this.tolerance; 12208 var name = this.state.style['shape']; 12209 12210 if (mxCellRenderer.defaultShapes[name] == null && 12211 mxStencilRegistry.getStencil(name) == null) 12212 { 12213 name = mxConstants.SHAPE_RECTANGLE; 12214 } 12215 12216 // Checks if custom handles are overlapping with the shape border 12217 var handlePadding = this.graph.isTable(this.state.cell) || 12218 this.graph.cellEditor.getEditingCell() == this.state.cell; 12219 12220 if (!handlePadding) 12221 { 12222 if (this.customHandles != null) 12223 { 12224 for (var i = 0; i < this.customHandles.length; i++) 12225 { 12226 if (this.customHandles[i].shape != null && 12227 this.customHandles[i].shape.bounds != null) 12228 { 12229 var b = this.customHandles[i].shape.bounds; 12230 var px = b.getCenterX(); 12231 var py = b.getCenterY(); 12232 12233 if ((Math.abs(this.state.x - px) < b.width / 2) || 12234 (Math.abs(this.state.y - py) < b.height / 2) || 12235 (Math.abs(this.state.x + this.state.width - px) < b.width / 2) || 12236 (Math.abs(this.state.y + this.state.height - py) < b.height / 2)) 12237 { 12238 handlePadding = true; 12239 break; 12240 } 12241 } 12242 } 12243 } 12244 } 12245 12246 if (handlePadding && this.sizers != null && 12247 this.sizers.length > 0 && this.sizers[0] != null) 12248 { 12249 tol /= 2; 12250 12251 // Makes room for row move handle 12252 if (this.graph.isTable(this.state.cell)) 12253 { 12254 tol += 7; 12255 } 12256 12257 result.x = this.sizers[0].bounds.width + tol; 12258 result.y = this.sizers[0].bounds.height + tol; 12259 } 12260 else 12261 { 12262 result = vertexHandlerGetHandlePadding.apply(this, arguments); 12263 } 12264 12265 return result; 12266 }; 12267 12268 /** 12269 * Updates the hint for the current operation. 12270 */ 12271 mxVertexHandler.prototype.updateHint = function(me) 12272 { 12273 if (this.index != mxEvent.LABEL_HANDLE) 12274 { 12275 if (this.hint == null) 12276 { 12277 this.hint = createHint(); 12278 this.state.view.graph.container.appendChild(this.hint); 12279 } 12280 12281 if (this.index == mxEvent.ROTATION_HANDLE) 12282 { 12283 this.hint.innerHTML = this.currentAlpha + '°'; 12284 } 12285 else 12286 { 12287 var s = this.state.view.scale; 12288 var unit = this.state.view.unit; 12289 this.hint.innerHTML = formatHintText(this.roundLength(this.bounds.width / s), unit) + ' x ' + 12290 formatHintText(this.roundLength(this.bounds.height / s), unit); 12291 } 12292 12293 var rot = (this.currentAlpha != null) ? this.currentAlpha : this.state.style[mxConstants.STYLE_ROTATION] || '0'; 12294 var bb = mxUtils.getBoundingBox(this.bounds, rot); 12295 12296 if (bb == null) 12297 { 12298 bb = this.bounds; 12299 } 12300 12301 this.hint.style.left = bb.x + Math.round((bb.width - this.hint.clientWidth) / 2) + 'px'; 12302 this.hint.style.top = (bb.y + bb.height + Editor.hintOffset) + 'px'; 12303 12304 if (this.linkHint != null) 12305 { 12306 this.linkHint.style.display = 'none'; 12307 } 12308 } 12309 }; 12310 12311 /** 12312 * Updates the hint for the current operation. 12313 */ 12314 mxVertexHandler.prototype.removeHint = function() 12315 { 12316 mxGraphHandler.prototype.removeHint.apply(this, arguments); 12317 12318 if (this.linkHint != null) 12319 { 12320 this.linkHint.style.display = ''; 12321 } 12322 }; 12323 12324 /** 12325 * Hides link hint while moving cells. 12326 */ 12327 var edgeHandlerMouseMove = mxEdgeHandler.prototype.mouseMove; 12328 12329 mxEdgeHandler.prototype.mouseMove = function(sender, me) 12330 { 12331 edgeHandlerMouseMove.apply(this, arguments); 12332 12333 if (this.linkHint != null && this.linkHint.style.display != 'none' && 12334 this.graph.graphHandler != null && this.graph.graphHandler.first != null) 12335 { 12336 this.linkHint.style.display = 'none'; 12337 } 12338 } 12339 12340 /** 12341 * Hides link hint while moving cells. 12342 */ 12343 var edgeHandlerMouseUp = mxEdgeHandler.prototype.mouseUp; 12344 12345 mxEdgeHandler.prototype.mouseUp = function(sender, me) 12346 { 12347 edgeHandlerMouseUp.apply(this, arguments); 12348 12349 if (this.linkHint != null && this.linkHint.style.display == 'none') 12350 { 12351 this.linkHint.style.display = ''; 12352 } 12353 } 12354 12355 /** 12356 * Updates the hint for the current operation. 12357 */ 12358 mxEdgeHandler.prototype.updateHint = function(me, point) 12359 { 12360 if (this.hint == null) 12361 { 12362 this.hint = createHint(); 12363 this.state.view.graph.container.appendChild(this.hint); 12364 } 12365 12366 var t = this.graph.view.translate; 12367 var s = this.graph.view.scale; 12368 var x = this.roundLength(point.x / s - t.x); 12369 var y = this.roundLength(point.y / s - t.y); 12370 var unit = this.graph.view.unit; 12371 12372 this.hint.innerHTML = formatHintText(x, unit) + ', ' + formatHintText(y, unit); 12373 this.hint.style.visibility = 'visible'; 12374 12375 if (this.isSource || this.isTarget) 12376 { 12377 if (this.constraintHandler.currentConstraint != null && 12378 this.constraintHandler.currentFocus != null) 12379 { 12380 var pt = this.constraintHandler.currentConstraint.point; 12381 this.hint.innerHTML = '[' + Math.round(pt.x * 100) + '%, '+ Math.round(pt.y * 100) + '%]'; 12382 } 12383 else if (this.marker.hasValidState()) 12384 { 12385 this.hint.style.visibility = 'hidden'; 12386 } 12387 } 12388 12389 this.hint.style.left = Math.round(me.getGraphX() - this.hint.clientWidth / 2) + 'px'; 12390 this.hint.style.top = (Math.max(me.getGraphY(), point.y) + Editor.hintOffset) + 'px'; 12391 12392 if (this.linkHint != null) 12393 { 12394 this.linkHint.style.display = 'none'; 12395 } 12396 }; 12397 12398 /** 12399 * Updates the hint for the current operation. 12400 */ 12401 mxEdgeHandler.prototype.removeHint = mxVertexHandler.prototype.removeHint; 12402 12403 /** 12404 * Defines the handles for the UI. Uses data-URIs to speed-up loading time where supported. 12405 */ 12406 // TODO: Increase handle padding 12407 HoverIcons.prototype.mainHandle = (!mxClient.IS_SVG) ? new mxImage(IMAGE_PATH + '/handle-main.png', 17, 17) : 12408 Graph.createSvgImage(18, 18, '<circle cx="9" cy="9" r="5" stroke="#fff" fill="' + HoverIcons.prototype.arrowFill + '" stroke-width="1"/>'); 12409 HoverIcons.prototype.secondaryHandle = (!mxClient.IS_SVG) ? new mxImage(IMAGE_PATH + '/handle-secondary.png', 17, 17) : 12410 Graph.createSvgImage(16, 16, '<path d="m 8 3 L 13 8 L 8 13 L 3 8 z" stroke="#fff" fill="#fca000"/>'); 12411 HoverIcons.prototype.fixedHandle = (!mxClient.IS_SVG) ? new mxImage(IMAGE_PATH + '/handle-fixed.png', 17, 17) : 12412 Graph.createSvgImage(22, 22, '<circle cx="11" cy="11" r="6" stroke="#fff" fill="#01bd22" stroke-width="1"/><path d="m 8 8 L 14 14M 8 14 L 14 8" stroke="#fff"/>'); 12413 HoverIcons.prototype.terminalHandle = (!mxClient.IS_SVG) ? new mxImage(IMAGE_PATH + '/handle-terminal.png', 17, 17) : 12414 Graph.createSvgImage(22, 22, '<circle cx="11" cy="11" r="6" stroke="#fff" fill="' + HoverIcons.prototype.arrowFill + '" stroke-width="1"/><circle cx="11" cy="11" r="3" stroke="#fff" fill="transparent"/>'); 12415 HoverIcons.prototype.rotationHandle = (!mxClient.IS_SVG) ? new mxImage(IMAGE_PATH + '/handle-rotate.png', 16, 16) : 12416 Graph.createSvgImage(16, 16, '<path stroke="' + HoverIcons.prototype.arrowFill + 12417 '" fill="' + HoverIcons.prototype.arrowFill + 12418 '" d="M15.55 5.55L11 1v3.07C7.06 4.56 4 7.92 4 12s3.05 7.44 7 7.93v-2.02c-2.84-.48-5-2.94-5-5.91s2.16-5.43 5-5.91V10l4.55-4.45zM19.93 11c-.17-1.39-.72-2.73-1.62-3.89l-1.42 1.42c.54.75.88 1.6 1.02 2.47h2.02zM13 17.9v2.02c1.39-.17 2.74-.71 3.9-1.61l-1.44-1.44c-.75.54-1.59.89-2.46 1.03zm3.89-2.42l1.42 1.41c.9-1.16 1.45-2.5 1.62-3.89h-2.02c-.14.87-.48 1.72-1.02 2.48z"/>', 12419 24, 24); 12420 12421 if (mxClient.IS_SVG) 12422 { 12423 mxConstraintHandler.prototype.pointImage = Graph.createSvgImage(5, 5, 12424 '<path d="m 0 0 L 5 5 M 0 5 L 5 0" stroke-width="2" style="stroke-opacity:0.4" stroke="#ffffff"/>' + 12425 '<path d="m 0 0 L 5 5 M 0 5 L 5 0" stroke="' + HoverIcons.prototype.arrowFill + '"/>'); 12426 } 12427 12428 mxVertexHandler.TABLE_HANDLE_COLOR = '#fca000'; 12429 mxVertexHandler.prototype.handleImage = HoverIcons.prototype.mainHandle; 12430 mxVertexHandler.prototype.secondaryHandleImage = HoverIcons.prototype.secondaryHandle; 12431 mxEdgeHandler.prototype.handleImage = HoverIcons.prototype.mainHandle; 12432 mxEdgeHandler.prototype.terminalHandleImage = HoverIcons.prototype.terminalHandle; 12433 mxEdgeHandler.prototype.fixedHandleImage = HoverIcons.prototype.fixedHandle; 12434 mxEdgeHandler.prototype.labelHandleImage = HoverIcons.prototype.secondaryHandle; 12435 mxOutline.prototype.sizerImage = HoverIcons.prototype.mainHandle; 12436 12437 if (window.Sidebar != null) 12438 { 12439 Sidebar.prototype.triangleUp = HoverIcons.prototype.triangleUp; 12440 Sidebar.prototype.triangleRight = HoverIcons.prototype.triangleRight; 12441 Sidebar.prototype.triangleDown = HoverIcons.prototype.triangleDown; 12442 Sidebar.prototype.triangleLeft = HoverIcons.prototype.triangleLeft; 12443 Sidebar.prototype.refreshTarget = HoverIcons.prototype.refreshTarget; 12444 Sidebar.prototype.roundDrop = HoverIcons.prototype.roundDrop; 12445 } 12446 12447 // Pre-fetches images (only needed for non data-uris) 12448 if (!mxClient.IS_SVG) 12449 { 12450 new Image().src = HoverIcons.prototype.mainHandle.src; 12451 new Image().src = HoverIcons.prototype.fixedHandle.src; 12452 new Image().src = HoverIcons.prototype.terminalHandle.src; 12453 new Image().src = HoverIcons.prototype.secondaryHandle.src; 12454 new Image().src = HoverIcons.prototype.rotationHandle.src; 12455 12456 new Image().src = HoverIcons.prototype.triangleUp.src; 12457 new Image().src = HoverIcons.prototype.triangleRight.src; 12458 new Image().src = HoverIcons.prototype.triangleDown.src; 12459 new Image().src = HoverIcons.prototype.triangleLeft.src; 12460 new Image().src = HoverIcons.prototype.refreshTarget.src; 12461 new Image().src = HoverIcons.prototype.roundDrop.src; 12462 } 12463 12464 // Adds rotation handle and live preview 12465 mxVertexHandler.prototype.rotationEnabled = true; 12466 mxVertexHandler.prototype.manageSizers = true; 12467 mxVertexHandler.prototype.livePreview = true; 12468 mxGraphHandler.prototype.maxLivePreview = 16; 12469 12470 // Increases default rubberband opacity (default is 20) 12471 mxRubberband.prototype.defaultOpacity = 30; 12472 12473 // Enables connections along the outline, virtual waypoints, parent highlight etc 12474 mxConnectionHandler.prototype.outlineConnect = true; 12475 mxCellHighlight.prototype.keepOnTop = true; 12476 mxVertexHandler.prototype.parentHighlightEnabled = true; 12477 12478 mxEdgeHandler.prototype.parentHighlightEnabled = true; 12479 mxEdgeHandler.prototype.dblClickRemoveEnabled = true; 12480 mxEdgeHandler.prototype.straightRemoveEnabled = true; 12481 mxEdgeHandler.prototype.virtualBendsEnabled = true; 12482 mxEdgeHandler.prototype.mergeRemoveEnabled = true; 12483 mxEdgeHandler.prototype.manageLabelHandle = true; 12484 mxEdgeHandler.prototype.outlineConnect = true; 12485 12486 // Disables adding waypoints if shift is pressed 12487 mxEdgeHandler.prototype.isAddVirtualBendEvent = function(me) 12488 { 12489 return !mxEvent.isShiftDown(me.getEvent()); 12490 }; 12491 12492 // Disables custom handles if shift is pressed 12493 mxEdgeHandler.prototype.isCustomHandleEvent = function(me) 12494 { 12495 return !mxEvent.isShiftDown(me.getEvent()); 12496 }; 12497 12498 /** 12499 * Implements touch style 12500 */ 12501 if (Graph.touchStyle) 12502 { 12503 // Larger tolerance for real touch devices 12504 if (mxClient.IS_TOUCH || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0) 12505 { 12506 mxShape.prototype.svgStrokeTolerance = 18; 12507 mxVertexHandler.prototype.tolerance = 12; 12508 mxEdgeHandler.prototype.tolerance = 12; 12509 Graph.prototype.tolerance = 12; 12510 12511 mxVertexHandler.prototype.rotationHandleVSpacing = -16; 12512 12513 // Implements a smaller tolerance for mouse events and a larger tolerance for touch 12514 // events on touch devices. The default tolerance (4px) is used for mouse events. 12515 mxConstraintHandler.prototype.getTolerance = function(me) 12516 { 12517 return (mxEvent.isMouseEvent(me.getEvent())) ? 4 : this.graph.getTolerance(); 12518 }; 12519 } 12520 12521 // One finger pans (no rubberband selection) must start regardless of mouse button 12522 mxPanningHandler.prototype.isPanningTrigger = function(me) 12523 { 12524 var evt = me.getEvent(); 12525 12526 return (me.getState() == null && !mxEvent.isMouseEvent(evt)) || 12527 (mxEvent.isPopupTrigger(evt) && (me.getState() == null || 12528 mxEvent.isControlDown(evt) || mxEvent.isShiftDown(evt))); 12529 }; 12530 12531 // Don't clear selection if multiple cells selected 12532 var graphHandlerMouseDown = mxGraphHandler.prototype.mouseDown; 12533 mxGraphHandler.prototype.mouseDown = function(sender, me) 12534 { 12535 graphHandlerMouseDown.apply(this, arguments); 12536 12537 if (mxEvent.isTouchEvent(me.getEvent()) && this.graph.isCellSelected(me.getCell()) && 12538 this.graph.getSelectionCount() > 1) 12539 { 12540 this.delayedSelection = false; 12541 } 12542 }; 12543 } 12544 else 12545 { 12546 // Removes ctrl+shift as panning trigger for space splitting 12547 mxPanningHandler.prototype.isPanningTrigger = function(me) 12548 { 12549 var evt = me.getEvent(); 12550 12551 return (mxEvent.isLeftMouseButton(evt) && ((this.useLeftButtonForPanning && 12552 me.getState() == null) || (mxEvent.isControlDown(evt) && 12553 !mxEvent.isShiftDown(evt)))) || (this.usePopupTrigger && 12554 mxEvent.isPopupTrigger(evt)); 12555 }; 12556 } 12557 12558 // Overrides/extends rubberband for space handling with Ctrl+Shift(+Alt) drag ("scissors tool") 12559 mxRubberband.prototype.isSpaceEvent = function(me) 12560 { 12561 return this.graph.isEnabled() && !this.graph.isCellLocked(this.graph.getDefaultParent()) && 12562 mxEvent.isControlDown(me.getEvent()) && mxEvent.isShiftDown(me.getEvent()) && 12563 mxEvent.isAltDown(me.getEvent()); 12564 }; 12565 12566 // Cancelled state 12567 mxRubberband.prototype.cancelled = false; 12568 12569 // Cancels ongoing rubberband selection but consumed event to avoid reset of selection 12570 mxRubberband.prototype.cancel = function() 12571 { 12572 if (this.isActive()) 12573 { 12574 this.cancelled = true; 12575 this.reset(); 12576 } 12577 }; 12578 12579 // Handles moving of cells in both half panes 12580 mxRubberband.prototype.mouseUp = function(sender, me) 12581 { 12582 if (this.cancelled) 12583 { 12584 this.cancelled = false; 12585 me.consume(); 12586 } 12587 else 12588 { 12589 var execute = this.div != null && this.div.style.display != 'none'; 12590 12591 var x0 = null; 12592 var y0 = null; 12593 var dx = null; 12594 var dy = null; 12595 12596 if (this.first != null && this.currentX != null && this.currentY != null) 12597 { 12598 x0 = this.first.x; 12599 y0 = this.first.y; 12600 dx = (this.currentX - x0) / this.graph.view.scale; 12601 dy = (this.currentY - y0) / this.graph.view.scale; 12602 12603 if (!mxEvent.isAltDown(me.getEvent())) 12604 { 12605 dx = this.graph.snap(dx); 12606 dy = this.graph.snap(dy); 12607 12608 if (!this.graph.isGridEnabled()) 12609 { 12610 if (Math.abs(dx) < this.graph.tolerance) 12611 { 12612 dx = 0; 12613 } 12614 12615 if (Math.abs(dy) < this.graph.tolerance) 12616 { 12617 dy = 0; 12618 } 12619 } 12620 } 12621 } 12622 12623 this.reset(); 12624 12625 if (execute) 12626 { 12627 if (this.isSpaceEvent(me)) 12628 { 12629 this.graph.model.beginUpdate(); 12630 try 12631 { 12632 var cells = this.graph.getCellsBeyond(x0, y0, this.graph.getDefaultParent(), true, true); 12633 12634 for (var i = 0; i < cells.length; i++) 12635 { 12636 if (this.graph.isCellMovable(cells[i])) 12637 { 12638 var tmp = this.graph.view.getState(cells[i]); 12639 var geo = this.graph.getCellGeometry(cells[i]); 12640 12641 if (tmp != null && geo != null) 12642 { 12643 geo = geo.clone(); 12644 geo.translate(dx, dy); 12645 this.graph.model.setGeometry(cells[i], geo); 12646 } 12647 } 12648 } 12649 } 12650 finally 12651 { 12652 this.graph.model.endUpdate(); 12653 } 12654 } 12655 else 12656 { 12657 var rect = new mxRectangle(this.x, this.y, this.width, this.height); 12658 this.graph.selectRegion(rect, me.getEvent()); 12659 } 12660 12661 me.consume(); 12662 } 12663 } 12664 }; 12665 12666 // Handles preview for creating/removing space in diagram 12667 mxRubberband.prototype.mouseMove = function(sender, me) 12668 { 12669 if (!me.isConsumed() && this.first != null) 12670 { 12671 var origin = mxUtils.getScrollOrigin(this.graph.container); 12672 var offset = mxUtils.getOffset(this.graph.container); 12673 origin.x -= offset.x; 12674 origin.y -= offset.y; 12675 var x = me.getX() + origin.x; 12676 var y = me.getY() + origin.y; 12677 var dx = this.first.x - x; 12678 var dy = this.first.y - y; 12679 var tol = this.graph.tolerance; 12680 12681 if (this.div != null || Math.abs(dx) > tol || Math.abs(dy) > tol) 12682 { 12683 if (this.div == null) 12684 { 12685 this.div = this.createShape(); 12686 } 12687 12688 // Clears selection while rubberbanding. This is required because 12689 // the event is not consumed in mouseDown. 12690 mxUtils.clearSelection(); 12691 this.update(x, y); 12692 12693 if (this.isSpaceEvent(me)) 12694 { 12695 var right = this.x + this.width; 12696 var bottom = this.y + this.height; 12697 var scale = this.graph.view.scale; 12698 12699 if (!mxEvent.isAltDown(me.getEvent())) 12700 { 12701 this.width = this.graph.snap(this.width / scale) * scale; 12702 this.height = this.graph.snap(this.height / scale) * scale; 12703 12704 if (!this.graph.isGridEnabled()) 12705 { 12706 if (this.width < this.graph.tolerance) 12707 { 12708 this.width = 0; 12709 } 12710 12711 if (this.height < this.graph.tolerance) 12712 { 12713 this.height = 0; 12714 } 12715 } 12716 12717 if (this.x < this.first.x) 12718 { 12719 this.x = right - this.width; 12720 } 12721 12722 if (this.y < this.first.y) 12723 { 12724 this.y = bottom - this.height; 12725 } 12726 } 12727 12728 this.div.style.borderStyle = 'dashed'; 12729 this.div.style.backgroundColor = 'white'; 12730 this.div.style.left = this.x + 'px'; 12731 this.div.style.top = this.y + 'px'; 12732 this.div.style.width = Math.max(0, this.width) + 'px'; 12733 this.div.style.height = this.graph.container.clientHeight + 'px'; 12734 this.div.style.borderWidth = (this.width <= 0) ? '0px 1px 0px 0px' : '0px 1px 0px 1px'; 12735 12736 if (this.secondDiv == null) 12737 { 12738 this.secondDiv = this.div.cloneNode(true); 12739 this.div.parentNode.appendChild(this.secondDiv); 12740 } 12741 12742 this.secondDiv.style.left = this.x + 'px'; 12743 this.secondDiv.style.top = this.y + 'px'; 12744 this.secondDiv.style.width = this.graph.container.clientWidth + 'px'; 12745 this.secondDiv.style.height = Math.max(0, this.height) + 'px'; 12746 this.secondDiv.style.borderWidth = (this.height <= 0) ? '1px 0px 0px 0px' : '1px 0px 1px 0px'; 12747 } 12748 else 12749 { 12750 // Hides second div and restores style 12751 this.div.style.backgroundColor = ''; 12752 this.div.style.borderWidth = ''; 12753 this.div.style.borderStyle = ''; 12754 12755 if (this.secondDiv != null) 12756 { 12757 this.secondDiv.parentNode.removeChild(this.secondDiv); 12758 this.secondDiv = null; 12759 } 12760 } 12761 12762 me.consume(); 12763 } 12764 } 12765 }; 12766 12767 // Removes preview 12768 var mxRubberbandReset = mxRubberband.prototype.reset; 12769 mxRubberband.prototype.reset = function() 12770 { 12771 if (this.secondDiv != null) 12772 { 12773 this.secondDiv.parentNode.removeChild(this.secondDiv); 12774 this.secondDiv = null; 12775 } 12776 12777 mxRubberbandReset.apply(this, arguments); 12778 }; 12779 12780 // Timer-based activation of outline connect in connection handler 12781 var startTime = new Date().getTime(); 12782 var timeOnTarget = 0; 12783 12784 var mxEdgeHandlerUpdatePreviewState = mxEdgeHandler.prototype.updatePreviewState; 12785 12786 mxEdgeHandler.prototype.updatePreviewState = function(edge, point, terminalState, me) 12787 { 12788 mxEdgeHandlerUpdatePreviewState.apply(this, arguments); 12789 12790 if (terminalState != this.currentTerminalState) 12791 { 12792 startTime = new Date().getTime(); 12793 timeOnTarget = 0; 12794 } 12795 else 12796 { 12797 timeOnTarget = new Date().getTime() - startTime; 12798 } 12799 12800 this.currentTerminalState = terminalState; 12801 }; 12802 12803 // Timer-based outline connect 12804 var mxEdgeHandlerIsOutlineConnectEvent = mxEdgeHandler.prototype.isOutlineConnectEvent; 12805 12806 mxEdgeHandler.prototype.isOutlineConnectEvent = function(me) 12807 { 12808 return (this.currentTerminalState != null && me.getState() == this.currentTerminalState && timeOnTarget > 2000) || 12809 ((this.currentTerminalState == null || mxUtils.getValue(this.currentTerminalState.style, 'outlineConnect', '1') != '0') && 12810 mxEdgeHandlerIsOutlineConnectEvent.apply(this, arguments)); 12811 }; 12812 12813 // Shows secondary handle for fixed connection points 12814 mxEdgeHandler.prototype.createHandleShape = function(index, virtual) 12815 { 12816 var source = index != null && index == 0; 12817 var terminalState = this.state.getVisibleTerminalState(source); 12818 var c = (index != null && (index == 0 || index >= this.state.absolutePoints.length - 1 || 12819 (this.constructor == mxElbowEdgeHandler && index == 2))) ? 12820 this.graph.getConnectionConstraint(this.state, terminalState, source) : null; 12821 var pt = (c != null) ? this.graph.getConnectionPoint(this.state.getVisibleTerminalState(source), c) : null; 12822 var img = (pt != null) ? this.fixedHandleImage : ((c != null && terminalState != null) ? 12823 this.terminalHandleImage : this.handleImage); 12824 12825 if (img != null) 12826 { 12827 var shape = new mxImageShape(new mxRectangle(0, 0, img.width, img.height), img.src); 12828 12829 // Allows HTML rendering of the images 12830 shape.preserveImageAspect = false; 12831 12832 return shape; 12833 } 12834 else 12835 { 12836 var s = mxConstants.HANDLE_SIZE; 12837 12838 if (this.preferHtml) 12839 { 12840 s -= 1; 12841 } 12842 12843 return new mxRectangleShape(new mxRectangle(0, 0, s, s), mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR); 12844 } 12845 }; 12846 12847 var vertexHandlerCreateSizerShape = mxVertexHandler.prototype.createSizerShape; 12848 mxVertexHandler.prototype.createSizerShape = function(bounds, index, fillColor) 12849 { 12850 this.handleImage = (index == mxEvent.ROTATION_HANDLE) ? HoverIcons.prototype.rotationHandle : (index == mxEvent.LABEL_HANDLE) ? this.secondaryHandleImage : this.handleImage; 12851 12852 return vertexHandlerCreateSizerShape.apply(this, arguments); 12853 }; 12854 12855 // Special case for single edge label handle moving in which case the text bounding box is used 12856 var mxGraphHandlerGetBoundingBox = mxGraphHandler.prototype.getBoundingBox; 12857 mxGraphHandler.prototype.getBoundingBox = function(cells) 12858 { 12859 if (cells != null && cells.length == 1) 12860 { 12861 var model = this.graph.getModel(); 12862 var parent = model.getParent(cells[0]); 12863 var geo = this.graph.getCellGeometry(cells[0]); 12864 12865 if (model.isEdge(parent) && geo != null && geo.relative) 12866 { 12867 var state = this.graph.view.getState(cells[0]); 12868 12869 if (state != null && state.width < 2 && state.height < 2 && state.text != null && 12870 state.text.boundingBox != null) 12871 { 12872 return mxRectangle.fromRectangle(state.text.boundingBox); 12873 } 12874 } 12875 } 12876 12877 return mxGraphHandlerGetBoundingBox.apply(this, arguments); 12878 }; 12879 12880 // Ignores child cells with part style as guides 12881 var mxGraphHandlerGetGuideStates = mxGraphHandler.prototype.getGuideStates; 12882 12883 mxGraphHandler.prototype.getGuideStates = function() 12884 { 12885 var states = mxGraphHandlerGetGuideStates.apply(this, arguments); 12886 var result = []; 12887 12888 // NOTE: Could do via isStateIgnored hook 12889 for (var i = 0; i < states.length; i++) 12890 { 12891 if (mxUtils.getValue(states[i].style, 'part', '0') != '1') 12892 { 12893 result.push(states[i]); 12894 } 12895 } 12896 12897 return result; 12898 }; 12899 12900 // Uses text bounding box for edge labels 12901 var mxVertexHandlerGetSelectionBounds = mxVertexHandler.prototype.getSelectionBounds; 12902 mxVertexHandler.prototype.getSelectionBounds = function(state) 12903 { 12904 var model = this.graph.getModel(); 12905 var parent = model.getParent(state.cell); 12906 var geo = this.graph.getCellGeometry(state.cell); 12907 12908 if (model.isEdge(parent) && geo != null && geo.relative && state.width < 2 && state.height < 2 && state.text != null && state.text.boundingBox != null) 12909 { 12910 var bbox = state.text.unrotatedBoundingBox || state.text.boundingBox; 12911 12912 return new mxRectangle(Math.round(bbox.x), Math.round(bbox.y), Math.round(bbox.width), Math.round(bbox.height)); 12913 } 12914 else 12915 { 12916 return mxVertexHandlerGetSelectionBounds.apply(this, arguments); 12917 } 12918 }; 12919 12920 // Redirects moving of edge labels to mxGraphHandler by not starting here. 12921 // This will use the move preview of mxGraphHandler (see above). 12922 var mxVertexHandlerMouseDown = mxVertexHandler.prototype.mouseDown; 12923 mxVertexHandler.prototype.mouseDown = function(sender, me) 12924 { 12925 var model = this.graph.getModel(); 12926 var parent = model.getParent(this.state.cell); 12927 var geo = this.graph.getCellGeometry(this.state.cell); 12928 12929 // Lets rotation events through 12930 var handle = this.getHandleForEvent(me); 12931 12932 if (handle == mxEvent.ROTATION_HANDLE || !model.isEdge(parent) || geo == null || !geo.relative || 12933 this.state == null || this.state.width >= 2 || this.state.height >= 2) 12934 { 12935 mxVertexHandlerMouseDown.apply(this, arguments); 12936 } 12937 }; 12938 12939 // Invokes turn on single click on rotation handle 12940 mxVertexHandler.prototype.rotateClick = function() 12941 { 12942 var stroke = mxUtils.getValue(this.state.style, mxConstants.STYLE_STROKECOLOR, mxConstants.NONE); 12943 var fill = mxUtils.getValue(this.state.style, mxConstants.STYLE_FILLCOLOR, mxConstants.NONE); 12944 12945 if (this.state.view.graph.model.isVertex(this.state.cell) && 12946 stroke == mxConstants.NONE && fill == mxConstants.NONE) 12947 { 12948 var angle = mxUtils.mod(mxUtils.getValue(this.state.style, mxConstants.STYLE_ROTATION, 0) + 90, 360); 12949 this.state.view.graph.setCellStyles(mxConstants.STYLE_ROTATION, angle, [this.state.cell]); 12950 } 12951 else 12952 { 12953 this.state.view.graph.turnShapes([this.state.cell]); 12954 } 12955 }; 12956 12957 var vertexHandlerMouseMove = mxVertexHandler.prototype.mouseMove; 12958 12959 // Workaround for "isConsumed not defined" in MS Edge is to use arguments 12960 mxVertexHandler.prototype.mouseMove = function(sender, me) 12961 { 12962 vertexHandlerMouseMove.apply(this, arguments); 12963 12964 if (this.graph.graphHandler.first != null) 12965 { 12966 if (this.rotationShape != null && this.rotationShape.node != null) 12967 { 12968 this.rotationShape.node.style.display = 'none'; 12969 } 12970 12971 if (this.linkHint != null && this.linkHint.style.display != 'none') 12972 { 12973 this.linkHint.style.display = 'none'; 12974 } 12975 } 12976 }; 12977 12978 var vertexHandlerMouseUp = mxVertexHandler.prototype.mouseUp; 12979 12980 mxVertexHandler.prototype.mouseUp = function(sender, me) 12981 { 12982 vertexHandlerMouseUp.apply(this, arguments); 12983 12984 // Shows rotation handle only if one vertex is selected 12985 if (this.rotationShape != null && this.rotationShape.node != null) 12986 { 12987 this.rotationShape.node.style.display = (this.graph.getSelectionCount() == 1) ? '' : 'none'; 12988 } 12989 12990 if (this.linkHint != null && this.linkHint.style.display == 'none') 12991 { 12992 this.linkHint.style.display = ''; 12993 } 12994 12995 // Resets state after gesture 12996 this.blockDelayedSelection = null; 12997 }; 12998 12999 var vertexHandlerInit = mxVertexHandler.prototype.init; 13000 mxVertexHandler.prototype.init = function() 13001 { 13002 vertexHandlerInit.apply(this, arguments); 13003 var redraw = false; 13004 13005 if (this.rotationShape != null) 13006 { 13007 this.rotationShape.node.setAttribute('title', mxResources.get('rotateTooltip')); 13008 } 13009 13010 if (this.graph.isTable(this.state.cell)) 13011 { 13012 this.refreshMoveHandles(); 13013 } 13014 // Draws corner rectangles for single selected table cells and rows 13015 else if (this.graph.getSelectionCount() == 1 && 13016 (this.graph.isTableCell(this.state.cell) || 13017 this.graph.isTableRow(this.state.cell))) 13018 { 13019 this.cornerHandles = []; 13020 13021 for (var i = 0; i < 4; i++) 13022 { 13023 var shape = new mxRectangleShape(new mxRectangle(0, 0, 6, 6), 13024 '#ffffff', mxConstants.HANDLE_STROKECOLOR); 13025 shape.dialect = mxConstants.DIALECT_SVG; 13026 shape.init(this.graph.view.getOverlayPane()); 13027 this.cornerHandles.push(shape); 13028 } 13029 } 13030 13031 var update = mxUtils.bind(this, function() 13032 { 13033 if (this.specialHandle != null) 13034 { 13035 this.specialHandle.node.style.display = (this.graph.isEnabled() && 13036 this.graph.getSelectionCount() < this.graph.graphHandler.maxCells) ? 13037 '' : 'none'; 13038 } 13039 13040 this.redrawHandles(); 13041 }); 13042 13043 this.changeHandler = mxUtils.bind(this, function(sender, evt) 13044 { 13045 this.updateLinkHint(this.graph.getLinkForCell(this.state.cell), 13046 this.graph.getLinksForState(this.state)); 13047 update(); 13048 }); 13049 13050 this.graph.getSelectionModel().addListener(mxEvent.CHANGE, this.changeHandler); 13051 this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler); 13052 13053 // Repaint needed when editing stops and no change event is fired 13054 this.editingHandler = mxUtils.bind(this, function(sender, evt) 13055 { 13056 this.redrawHandles(); 13057 }); 13058 13059 this.graph.addListener(mxEvent.EDITING_STOPPED, this.editingHandler); 13060 13061 var link = this.graph.getLinkForCell(this.state.cell); 13062 var links = this.graph.getLinksForState(this.state); 13063 this.updateLinkHint(link, links); 13064 13065 if (link != null || (links != null && links.length > 0)) 13066 { 13067 redraw = true; 13068 } 13069 13070 if (redraw) 13071 { 13072 this.redrawHandles(); 13073 } 13074 }; 13075 13076 mxVertexHandler.prototype.updateLinkHint = function(link, links) 13077 { 13078 try 13079 { 13080 if ((link == null && (links == null || links.length == 0)) || 13081 this.graph.getSelectionCount() > 1) 13082 { 13083 if (this.linkHint != null) 13084 { 13085 this.linkHint.parentNode.removeChild(this.linkHint); 13086 this.linkHint = null; 13087 } 13088 } 13089 else if (link != null || (links != null && links.length > 0)) 13090 { 13091 if (this.linkHint == null) 13092 { 13093 this.linkHint = createHint(); 13094 this.linkHint.style.padding = '6px 8px 6px 8px'; 13095 this.linkHint.style.opacity = '1'; 13096 this.linkHint.style.filter = ''; 13097 13098 this.graph.container.appendChild(this.linkHint); 13099 13100 mxEvent.addListener(this.linkHint, 'mouseenter', mxUtils.bind(this, function() 13101 { 13102 this.graph.tooltipHandler.hide(); 13103 })); 13104 } 13105 13106 this.linkHint.innerHTML = ''; 13107 13108 if (link != null) 13109 { 13110 this.linkHint.appendChild(this.graph.createLinkForHint(link)); 13111 13112 if (this.graph.isEnabled() && typeof this.graph.editLink === 'function') 13113 { 13114 var changeLink = document.createElement('img'); 13115 changeLink.setAttribute('src', Editor.editImage); 13116 changeLink.setAttribute('title', mxResources.get('editLink')); 13117 changeLink.setAttribute('width', '11'); 13118 changeLink.setAttribute('height', '11'); 13119 changeLink.style.marginLeft = '10px'; 13120 changeLink.style.marginBottom = '-1px'; 13121 changeLink.style.cursor = 'pointer'; 13122 this.linkHint.appendChild(changeLink); 13123 13124 mxEvent.addListener(changeLink, 'click', mxUtils.bind(this, function(evt) 13125 { 13126 this.graph.setSelectionCell(this.state.cell); 13127 this.graph.editLink(); 13128 mxEvent.consume(evt); 13129 })); 13130 13131 this.linkHint.appendChildGraph.createRemoveIcon(mxResources.get('removeIt', 13132 [mxResources.get('link')]), mxUtils.bind(this, function(evt) 13133 { 13134 this.graph.setLinkForCell(this.state.cell, null); 13135 mxEvent.consume(evt); 13136 })); 13137 } 13138 } 13139 13140 if (links != null) 13141 { 13142 for (var i = 0; i < links.length; i++) 13143 { 13144 var div = document.createElement('div'); 13145 div.style.marginTop = (link != null || i > 0) ? '6px' : '0px'; 13146 div.appendChild(this.graph.createLinkForHint( 13147 links[i].getAttribute('href'), 13148 mxUtils.getTextContent(links[i]))); 13149 13150 this.linkHint.appendChild(div); 13151 } 13152 } 13153 } 13154 } 13155 catch (e) 13156 { 13157 // ignore 13158 } 13159 }; 13160 13161 mxEdgeHandler.prototype.updateLinkHint = mxVertexHandler.prototype.updateLinkHint; 13162 13163 // Creates special handles 13164 var edgeHandlerInit = mxEdgeHandler.prototype.init; 13165 mxEdgeHandler.prototype.init = function() 13166 { 13167 edgeHandlerInit.apply(this, arguments); 13168 13169 // Disables connection points 13170 this.constraintHandler.isEnabled = mxUtils.bind(this, function() 13171 { 13172 return this.state.view.graph.connectionHandler.isEnabled(); 13173 }); 13174 13175 var update = mxUtils.bind(this, function() 13176 { 13177 if (this.linkHint != null) 13178 { 13179 this.linkHint.style.display = (this.graph.getSelectionCount() == 1) ? '' : 'none'; 13180 } 13181 13182 if (this.labelShape != null) 13183 { 13184 this.labelShape.node.style.display = (this.graph.isEnabled() && 13185 this.graph.getSelectionCount() < this.graph.graphHandler.maxCells) ? 13186 '' : 'none'; 13187 } 13188 }); 13189 13190 this.changeHandler = mxUtils.bind(this, function(sender, evt) 13191 { 13192 this.updateLinkHint(this.graph.getLinkForCell(this.state.cell), 13193 this.graph.getLinksForState(this.state)); 13194 update(); 13195 this.redrawHandles(); 13196 }); 13197 13198 this.graph.getSelectionModel().addListener(mxEvent.CHANGE, this.changeHandler); 13199 this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler); 13200 13201 var link = this.graph.getLinkForCell(this.state.cell); 13202 var links = this.graph.getLinksForState(this.state); 13203 13204 if (link != null || (links != null && links.length > 0)) 13205 { 13206 this.updateLinkHint(link, links); 13207 this.redrawHandles(); 13208 } 13209 }; 13210 13211 // Disables connection points 13212 var connectionHandlerInit = mxConnectionHandler.prototype.init; 13213 13214 mxConnectionHandler.prototype.init = function() 13215 { 13216 connectionHandlerInit.apply(this, arguments); 13217 13218 this.constraintHandler.isEnabled = mxUtils.bind(this, function() 13219 { 13220 return this.graph.connectionHandler.isEnabled(); 13221 }); 13222 }; 13223 13224 // Updates special handles 13225 var vertexHandlerRedrawHandles = mxVertexHandler.prototype.redrawHandles; 13226 mxVertexHandler.prototype.redrawHandles = function() 13227 { 13228 if (this.moveHandles != null) 13229 { 13230 for (var i = 0; i < this.moveHandles.length; i++) 13231 { 13232 this.moveHandles[i].style.left = (this.moveHandles[i].rowState.x + 13233 this.moveHandles[i].rowState.width - 5) + 'px'; 13234 this.moveHandles[i].style.top = (this.moveHandles[i].rowState.y + 13235 this.moveHandles[i].rowState.height / 2 - 6) + 'px'; 13236 } 13237 } 13238 13239 if (this.cornerHandles != null) 13240 { 13241 var inset = this.getSelectionBorderInset(); 13242 var ch = this.cornerHandles; 13243 var w = ch[0].bounds.width / 2; 13244 var h = ch[0].bounds.height / 2; 13245 13246 ch[0].bounds.x = this.state.x - w + inset; 13247 ch[0].bounds.y = this.state.y - h + inset; 13248 ch[0].redraw(); 13249 ch[1].bounds.x = ch[0].bounds.x + this.state.width - 2 * inset; 13250 ch[1].bounds.y = ch[0].bounds.y; 13251 ch[1].redraw(); 13252 ch[2].bounds.x = ch[0].bounds.x; 13253 ch[2].bounds.y = this.state.y + this.state.height - 2 * inset; 13254 ch[2].redraw(); 13255 ch[3].bounds.x = ch[1].bounds.x; 13256 ch[3].bounds.y = ch[2].bounds.y; 13257 ch[3].redraw(); 13258 13259 for (var i = 0; i < this.cornerHandles.length; i++) 13260 { 13261 this.cornerHandles[i].node.style.display = (this.graph.getSelectionCount() == 1) ? '' : 'none'; 13262 } 13263 } 13264 13265 // Shows rotation handle only if one vertex is selected 13266 if (this.rotationShape != null && this.rotationShape.node != null) 13267 { 13268 this.rotationShape.node.style.display = (this.moveHandles == null && 13269 (this.graph.getSelectionCount() == 1 && (this.index == null || 13270 this.index == mxEvent.ROTATION_HANDLE))) ? '' : 'none'; 13271 } 13272 13273 vertexHandlerRedrawHandles.apply(this); 13274 13275 if (this.state != null && this.linkHint != null) 13276 { 13277 var c = new mxPoint(this.state.getCenterX(), this.state.getCenterY()); 13278 var tmp = new mxRectangle(this.state.x, this.state.y - 22, this.state.width + 24, this.state.height + 22); 13279 var bb = mxUtils.getBoundingBox(tmp, this.state.style[mxConstants.STYLE_ROTATION] || '0', c); 13280 var rs = (bb != null) ? mxUtils.getBoundingBox(this.state, 13281 this.state.style[mxConstants.STYLE_ROTATION] || '0') : this.state; 13282 var tb = (this.state.text != null) ? this.state.text.boundingBox : null; 13283 13284 if (bb == null) 13285 { 13286 bb = this.state; 13287 } 13288 13289 var b = bb.y + bb.height; 13290 13291 if (tb != null) 13292 { 13293 b = Math.max(b, tb.y + tb.height); 13294 } 13295 13296 this.linkHint.style.left = Math.max(0, Math.round(rs.x + (rs.width - this.linkHint.clientWidth) / 2)) + 'px'; 13297 this.linkHint.style.top = Math.round(b + this.verticalOffset / 2 + Editor.hintOffset) + 'px'; 13298 } 13299 }; 13300 13301 // Destroys special handles 13302 var vertexHandlerDestroy = mxVertexHandler.prototype.destroy; 13303 mxVertexHandler.prototype.destroy = function() 13304 { 13305 vertexHandlerDestroy.apply(this, arguments); 13306 13307 if (this.moveHandles != null) 13308 { 13309 for (var i = 0; i < this.moveHandles.length; i++) 13310 { 13311 if (this.moveHandles[i] != null && this.moveHandles[i].parentNode != null) 13312 { 13313 this.moveHandles[i].parentNode.removeChild(this.moveHandles[i]); 13314 } 13315 } 13316 13317 this.moveHandles = null; 13318 } 13319 13320 if (this.cornerHandles != null) 13321 { 13322 for (var i = 0; i < this.cornerHandles.length; i++) 13323 { 13324 if (this.cornerHandles[i] != null && this.cornerHandles[i].node != null && 13325 this.cornerHandles[i].node.parentNode != null) 13326 { 13327 this.cornerHandles[i].node.parentNode.removeChild(this.cornerHandles[i].node); 13328 } 13329 } 13330 13331 this.cornerHandles = null; 13332 } 13333 13334 if (this.linkHint != null) 13335 { 13336 if (this.linkHint.parentNode != null) 13337 { 13338 this.linkHint.parentNode.removeChild(this.linkHint); 13339 } 13340 13341 this.linkHint = null; 13342 } 13343 13344 if (this.changeHandler != null) 13345 { 13346 this.graph.getSelectionModel().removeListener(this.changeHandler); 13347 this.graph.getModel().removeListener(this.changeHandler); 13348 this.changeHandler = null; 13349 } 13350 13351 if (this.editingHandler != null) 13352 { 13353 this.graph.removeListener(this.editingHandler); 13354 this.editingHandler = null; 13355 } 13356 }; 13357 13358 var edgeHandlerRedrawHandles = mxEdgeHandler.prototype.redrawHandles; 13359 mxEdgeHandler.prototype.redrawHandles = function() 13360 { 13361 // Workaround for special case where handler 13362 // is reset before this which leads to a NPE 13363 if (this.marker != null) 13364 { 13365 edgeHandlerRedrawHandles.apply(this); 13366 13367 if (this.state != null && this.linkHint != null) 13368 { 13369 var b = this.state; 13370 13371 if (this.state.text != null && this.state.text.bounds != null) 13372 { 13373 b = new mxRectangle(b.x, b.y, b.width, b.height); 13374 b.add(this.state.text.bounds); 13375 } 13376 13377 this.linkHint.style.left = Math.max(0, Math.round(b.x + (b.width - this.linkHint.clientWidth) / 2)) + 'px'; 13378 this.linkHint.style.top = Math.round(b.y + b.height + Editor.hintOffset) + 'px'; 13379 } 13380 } 13381 }; 13382 13383 var edgeHandlerReset = mxEdgeHandler.prototype.reset; 13384 mxEdgeHandler.prototype.reset = function() 13385 { 13386 edgeHandlerReset.apply(this, arguments); 13387 13388 if (this.linkHint != null) 13389 { 13390 this.linkHint.style.visibility = ''; 13391 } 13392 }; 13393 13394 var edgeHandlerDestroy = mxEdgeHandler.prototype.destroy; 13395 mxEdgeHandler.prototype.destroy = function() 13396 { 13397 edgeHandlerDestroy.apply(this, arguments); 13398 13399 if (this.linkHint != null) 13400 { 13401 this.linkHint.parentNode.removeChild(this.linkHint); 13402 this.linkHint = null; 13403 } 13404 13405 if (this.changeHandler != null) 13406 { 13407 this.graph.getModel().removeListener(this.changeHandler); 13408 this.graph.getSelectionModel().removeListener(this.changeHandler); 13409 this.changeHandler = null; 13410 } 13411 }; 13412 })(); 13413} 13414