1/** 2 * Mindmaps plugin. 3 * 4 * Todo: 5 * - Make cursor key selection more generic 6 * - Handle single-cell movement on touch 7 * - Move multiple cells without subtrees 8 * - Make offset subtrees more generic 9 */ 10Draw.loadPlugin(function(ui) 11{ 12 if (ui.editor.isChromelessView()) 13 { 14 return; 15 } 16 17 var spacing = 10; 18 var level = 40; 19 20 var graph = ui.editor.graph; 21 var model = graph.getModel(); 22 23 // Adds resources for actions 24 mxResources.parse('selectChildren=Select Children'); 25 mxResources.parse('selectSiblings=Select Siblings'); 26 mxResources.parse('selectSubtree=Select Subtree'); 27 mxResources.parse('selectParent=Select Parent'); 28 29 function isTreeCell(cell) 30 { 31 var result = false; 32 33 if (cell != null) 34 { 35 graph.traverse(cell, true, function(vertex) 36 { 37 result = vertex.getAttribute('treeRoot') == '1'; 38 39 return !result; 40 }, null, null, true); 41 } 42 43 return result; 44 }; 45 46 var uiCreatePopupMenu = ui.menus.createPopupMenu; 47 ui.menus.createPopupMenu = function(menu, cell, evt) 48 { 49 uiCreatePopupMenu.apply(this, arguments); 50 51 if (isTreeCell(graph.getSelectionCell()) && graph.getSelectionCount() == 1) 52 { 53 var cell = graph.getSelectionCell(); 54 var sib = graph.getOutgoingEdges(cell); 55 menu.addSeparator(); 56 57 if (sib != null && sib.length > 0) 58 { 59 this.addMenuItems(menu, ['selectChildren', 'selectSubtree'], null, evt); 60 } 61 62 menu.addSeparator(); 63 64 if (cell.getAttribute('treeRoot') != '1') 65 { 66 this.addMenuItems(menu, ['selectSiblings', 'selectParent'], null, evt); 67 } 68 } 69 }; 70 71 // Adds actions 72 ui.actions.addAction('selectChildren', function() 73 { 74 if (graph.isEnabled() && graph.getSelectionCount() == 1) 75 { 76 var cell = graph.getSelectionCell(); 77 var sib = graph.getOutgoingEdges(cell); 78 79 if (sib != null) 80 { 81 var tmp = []; 82 83 for (var i = 0; i < sib.length; i++) 84 { 85 tmp.push(graph.model.getTerminal(sib[i], false)); 86 } 87 88 graph.setSelectionCells(tmp); 89 } 90 } 91 }, null, null, 'Alt+Shift+X'); 92 93 // Adds actions 94 ui.actions.addAction('selectSiblings', function() 95 { 96 if (graph.isEnabled() && graph.getSelectionCount() == 1) 97 { 98 var cell = graph.getSelectionCell(); 99 var edges = graph.getIncomingEdges(cell); 100 101 if (edges != null && edges.length > 0) 102 { 103 var sib = graph.getOutgoingEdges(graph.model.getTerminal(edges[0], true)); 104 105 if (sib != null) 106 { 107 var tmp = []; 108 109 for (var i = 0; i < sib.length; i++) 110 { 111 tmp.push(graph.model.getTerminal(sib[i], false)); 112 } 113 114 graph.setSelectionCells(tmp); 115 } 116 } 117 } 118 }, null, null, 'Alt+Shift+S'); 119 120 // Adds actions 121 ui.actions.addAction('selectParent', function() 122 { 123 if (graph.isEnabled() && graph.getSelectionCount() == 1) 124 { 125 var cell = graph.getSelectionCell(); 126 var edges = graph.getIncomingEdges(cell); 127 128 if (edges != null && edges.length > 0) 129 { 130 graph.setSelectionCell(graph.model.getTerminal(edges[0], true)); 131 } 132 } 133 }, null, null, 'Alt+Shift+P'); 134 135 ui.actions.addAction('selectSubtree', function() 136 { 137 if (graph.isEnabled() && graph.getSelectionCount() == 1) 138 { 139 var cell = graph.getSelectionCell(); 140 // Makes space for new parent 141 var subtree = []; 142 143 graph.traverse(cell, true, function(vertex, edge) 144 { 145 if (edge != null) 146 { 147 subtree.push(edge); 148 } 149 150 subtree.push(vertex); 151 152 return true; 153 }); 154 155 graph.setSelectionCells(subtree); 156 } 157 }, null, null, 'Alt+Shift+T'); 158 159 /** 160 * Overriddes 161 */ 162 var graphFoldCells = graph.foldCells; 163 164 graph.foldCells = function(collapse, recurse, cells, checkFoldable, evt) 165 { 166 //console.log('cells', cells, collapse); 167 this.stopEditing(); 168 169 this.model.beginUpdate(); 170 try 171 { 172 var newCells = cells.splice(); 173 var tmp = []; 174 175 for (var i = 0; i < cells.length; i++) 176 { 177 if (isTreeCell(cells[i])) 178 { 179 graph.traverse(cells[i], true, function(vertex, edge) 180 { 181 if (edge != null) 182 { 183 tmp.push(edge); 184 } 185 186 if (vertex != cells[i]) 187 { 188 tmp.push(vertex); 189 } 190 191 // Stop traversal on collapsed vertices 192 return vertex == cells[i] || !graph.model.isCollapsed(vertex); 193 }); 194 195 graph.model.setCollapsed(cells[i], collapse); 196 } 197 } 198 199 for (var i = 0; i < tmp.length; i++) 200 { 201 graph.model.setVisible(tmp[i], !collapse); 202 } 203 204 cells = newCells; 205 graphFoldCells.apply(this, arguments); 206 } 207 finally 208 { 209 this.model.endUpdate(); 210 } 211 }; 212 213 var graphRemoveCells = graph.removeCells; 214 215 graph.removeCells = function(cells, includeEdges) 216 { 217 var tmp = []; 218 219 for (var i = 0; i < cells.length; i++) 220 { 221 if (isTreeCell(cells[i])) 222 { 223 graph.traverse(cells[i], true, function(vertex, edge) 224 { 225 if (edge != null) 226 { 227 tmp.push(edge); 228 } 229 230 tmp.push(vertex); 231 232 return true; 233 }); 234 235 var edges = graph.getIncomingEdges(cells[i]); 236 cells = cells.concat(edges); 237 } 238 else 239 { 240 tmp.push(cells[i]); 241 } 242 } 243 244 cells = tmp; 245 246 graphRemoveCells.apply(this, arguments); 247 }; 248 249 ui.hoverIcons.getStateAt = function(state, x, y) 250 { 251 return (isTreeCell(state.cell)) ? null : this.graph.view.getState(this.graph.getCellAt(x, y)); 252 }; 253 254 var graphDuplicateCells = graph.duplicateCells; 255 256 graph.duplicateCells = function(cells, append) 257 { 258 cells = (cells != null) ? cells : this.getSelectionCells(); 259 var temp = cells.slice(0); 260 261 for (var i = 0; i < temp.length; i++) 262 { 263 var cell = temp[i]; 264 var state = graph.view.getState(cell); 265 266 if (state != null && isTreeCell(state.cell)) 267 { 268 // Avoids disconnecting subtree by removing all incoming edges 269 var edges = graph.getIncomingEdges(state.cell); 270 271 for (var j = 0; j < edges.length; j++) 272 { 273 mxUtils.remove(edges[j], cells); 274 } 275 } 276 } 277 278 this.model.beginUpdate(); 279 try 280 { 281 var result = graphDuplicateCells.call(this, cells, append); 282 283 if (result.length == cells.length) 284 { 285 for (var i = 0; i < cells.length; i++) 286 { 287 if (isTreeCell(cells[i])) 288 { 289 var newEdges = graph.getIncomingEdges(result[i]); 290 var edges = graph.getIncomingEdges(cells[i]); 291 292 if (newEdges.length == 0 && edges.length > 0) 293 { 294 var clone = this.cloneCells([edges[0]])[0]; 295 this.addEdge(clone, graph.getDefaultParent(), 296 this.model.getTerminal(edges[0], true), result[i]); 297 } 298 } 299 } 300 } 301 } 302 finally 303 { 304 this.model.endUpdate(); 305 } 306 307 return result; 308 }; 309 310 var graphMoveCells = graph.moveCells; 311 312 graph.moveCells = function(cells, dx, dy, clone, target, evt, mapping) 313 { 314 var result = null; 315 316 this.model.beginUpdate(); 317 try 318 { 319 var newSource = target; 320 321 if (isTreeCell(target)) 322 { 323 // Handles only drag from tree or from sidebar with dangling edges 324 for (var i = 0; i < cells.length; i++) 325 { 326 if (isTreeCell(cells[i]) || (graph.model.isEdge(cells[i]) && 327 graph.model.getTerminal(cells[i], true) == null)) 328 { 329 target = null; 330 break; 331 } 332 } 333 334 // Applies distance between previous and current parent for non-sidebar drags 335 if (newSource != null && target == null && this.view.getState(cells[0]) != null) 336 { 337 var edges = graph.getIncomingEdges(cells[0]); 338 339 if (edges.length > 0) 340 { 341 var state1 = graph.view.getState(graph.model.getTerminal(edges[0], true)); 342 343 if (state1 != null) 344 { 345 var state2 = graph.view.getState(newSource); 346 347 if (state2 != null) 348 { 349 dx = state2.getCenterX() - state1.getCenterX(); 350 dy = state2.getCenterY() - state1.getCenterY(); 351 } 352 } 353 } 354 } 355 } 356 357 result = graphMoveCells.apply(this, arguments); 358 359 if (result.length == cells.length) 360 { 361 for (var i = 0; i < result.length; i++) 362 { 363 // Connects all dangling edges from the sidebar when dropped into drop target (not hover icon) 364 if (this.model.isEdge(result[i])) 365 { 366 if (isTreeCell(newSource) && mxUtils.indexOf(result, this.model.getTerminal(result[i], true)) < 0) 367 { 368 this.model.setTerminal(result[i], newSource, true); 369 } 370 } 371 else if (isTreeCell(cells[i])) 372 { 373 var edges = graph.getIncomingEdges(cells[i]); 374 375 if (edges.length > 0) 376 { 377 if (!clone) 378 { 379 if (isTreeCell(newSource) && mxUtils.indexOf(cells, this.model.getTerminal(edges[0], true)) < 0) 380 { 381 this.model.setTerminal(edges[0], newSource, true); 382 } 383 } 384 else 385 { 386 var newEdges = graph.getIncomingEdges(result[i]); 387 388 if (newEdges.length == 0) 389 { 390 var temp = newSource; 391 392 if (temp == null) 393 { 394 temp = graph.model.getTerminal(edges[0], true); 395 } 396 397 var clone = this.cloneCells([edges[0]])[0]; 398 this.addEdge(clone, graph.getDefaultParent(), temp, result[i]); 399 } 400 } 401 } 402 } 403 } 404 } 405 } 406 finally 407 { 408 this.model.endUpdate(); 409 } 410 411 return result; 412 }; 413 414 // Connects all dangling edges from the sidebar (by 415 // default only first dangling edge gets connected) 416 var sidebarDropAndConnect = ui.sidebar.dropAndConnect; 417 418 ui.sidebar.dropAndConnect = function(source, targets, direction, dropCellIndex) 419 { 420 var model = graph.model; 421 var result = null; 422 423 model.beginUpdate(); 424 try 425 { 426 result = sidebarDropAndConnect.apply(this, arguments); 427 428 if (isTreeCell(source)) 429 { 430 for (var i = 0; i < result.length; i++) 431 { 432 if (model.isEdge(result[i]) && model.getTerminal(result[i], true) == null) 433 { 434 model.setTerminal(result[i], source, true); 435 var geo = graph.getCellGeometry(result[i]); 436 geo.points = null; 437 438 if (geo.getTerminalPoint(true) != null) 439 { 440 geo.setTerminalPoint(null, true); 441 } 442 } 443 } 444 } 445 } 446 finally 447 { 448 model.endUpdate(); 449 } 450 451 return result; 452 }; 453 454 /** 455 * Checks source point of incoming edge relative to target terminal. 456 */ 457 function getTreeDirection(cell) 458 { 459 var state = graph.view.getState(cell); 460 461 if (state != null) 462 { 463 var edges = graph.getIncomingEdges(state.cell); 464 465 if (edges.length > 0) 466 { 467 var edgeState = graph.view.getState(edges[0]); 468 469 if (edgeState != null) 470 { 471 var abs = edgeState.absolutePoints; 472 473 if (abs != null && abs.length > 0) 474 { 475 var pt = abs[abs.length - 1]; 476 477 if (pt != null) 478 { 479 if (pt.y == state.y && Math.abs(pt.x - state.getCenterX()) < state.width / 2) 480 { 481 return mxConstants.DIRECTION_SOUTH; 482 } 483 else if (pt.y == state.y + state.height && Math.abs(pt.x - state.getCenterX()) < state.width / 2) 484 { 485 return mxConstants.DIRECTION_NORTH; 486 } 487 else if (pt.x > state.getCenterX()) 488 { 489 return mxConstants.DIRECTION_WEST; 490 } 491 } 492 } 493 } 494 } 495 } 496 497 return mxConstants.DIRECTION_EAST; 498 }; 499 500 function addSibling(cell, after) 501 { 502 after = (after != null) ? after : true; 503 504 graph.model.beginUpdate(); 505 try 506 { 507 var edges = graph.getIncomingEdges(cell); 508 var clones = graph.cloneCells([edges[0], cell]); 509 graph.model.setTerminal(clones[0], graph.model.getTerminal(edges[0], true), true); 510 511 var dir = getTreeDirection(cell); 512 513 if (dir == mxConstants.DIRECTION_SOUTH || dir == mxConstants.DIRECTION_NORTH) 514 { 515 clones[1].geometry.x += (after) ? cell.geometry.width + spacing : 516 -clones[1].geometry.width - spacing; 517 } 518 else 519 { 520 clones[1].geometry.y += (after) ? cell.geometry.height + spacing : 521 -clones[1].geometry.height - spacing; 522 } 523 524 if (dir == mxConstants.DIRECTION_WEST) 525 { 526 clones[1].geometry.x = cell.geometry.x + cell.geometry.width - clones[1].geometry.width; 527 } 528 529 // Moves existing siblings 530 var state = graph.view.getState(cell); 531 var s = graph.view.scale; 532 533 if (state != null) 534 { 535 var bbox = mxRectangle.fromRectangle(state); 536 537 if (dir == mxConstants.DIRECTION_SOUTH || 538 dir == mxConstants.DIRECTION_NORTH) 539 { 540 bbox.x += ((after) ? cell.geometry.width + spacing : 541 -clones[1].geometry.width - spacing) * s; 542 } 543 else 544 { 545 bbox.y += ((after) ? cell.geometry.height + spacing : 546 -clones[1].geometry.height - spacing) * s; 547 } 548 549 var sib = graph.getOutgoingEdges(graph.model.getTerminal(edges[0], true)); 550 551 if (sib != null) 552 { 553 var hor = (dir == mxConstants.DIRECTION_SOUTH || dir == mxConstants.DIRECTION_NORTH); 554 var dx = 0; 555 var dy = 0; 556 557 for (var i = 0; i < sib.length; i++) 558 { 559 var temp = graph.model.getTerminal(sib[i], false); 560 561 if (dir == getTreeDirection(temp)) 562 { 563 var sibling = graph.view.getState(temp); 564 565 if (temp != cell && sibling != null) 566 { 567 if ((hor && after != sibling.getCenterX() < state.getCenterX()) || 568 (!hor && after != sibling.getCenterY() < state.getCenterY())) 569 { 570 if (mxUtils.intersects(bbox, sibling)) 571 { 572 dx = spacing + Math.max(dx, (Math.min(bbox.x + bbox.width, 573 sibling.x + sibling.width) - Math.max(bbox.x, sibling.x)) / s); 574 dy = spacing + Math.max(dy, (Math.min(bbox.y + bbox.height, 575 sibling.y + sibling.height) - Math.max(bbox.y, sibling.y)) / s); 576 } 577 } 578 } 579 } 580 } 581 582 if (hor) 583 { 584 dy = 0; 585 } 586 else 587 { 588 dx = 0; 589 } 590 591 for (var i = 0; i < sib.length; i++) 592 { 593 var temp = graph.model.getTerminal(sib[i], false); 594 595 if (dir == getTreeDirection(temp)) 596 { 597 var sibling = graph.view.getState(temp); 598 599 if (temp != cell && sibling != null) 600 { 601 if ((hor && after != sibling.getCenterX() < state.getCenterX()) || 602 (!hor && after != sibling.getCenterY() < state.getCenterY())) 603 { 604 var subtree = []; 605 606 graph.traverse(sibling.cell, true, function(vertex, edge) 607 { 608 if (edge != null) 609 { 610 subtree.push(edge); 611 } 612 613 subtree.push(vertex); 614 615 return true; 616 }); 617 618 graph.moveCells(subtree, ((after) ? 1 : -1) * dx, ((after) ? 1 : -1) * dy); 619 } 620 } 621 } 622 } 623 } 624 } 625 626 return graph.addCells(clones); 627 } 628 finally 629 { 630 graph.model.endUpdate(); 631 } 632 }; 633 634 function addParent(cell) 635 { 636 graph.model.beginUpdate(); 637 try 638 { 639 var dir = getTreeDirection(cell); 640 var edges = graph.getIncomingEdges(cell); 641 var clones = graph.cloneCells([edges[0], cell]); 642 graph.model.setTerminal(edges[0], clones[1], false); 643 graph.model.setTerminal(clones[0], clones[1], true); 644 graph.model.setTerminal(clones[0], cell, false); 645 646 // Makes space for new parent 647 var subtree = []; 648 649 graph.traverse(cell, true, function(vertex, edge) 650 { 651 if (edge != null) 652 { 653 subtree.push(edge); 654 } 655 656 subtree.push(vertex); 657 658 return true; 659 }); 660 661 var dx = cell.geometry.width + level; 662 var dy = cell.geometry.height + level; 663 664 if (dir == mxConstants.DIRECTION_SOUTH) 665 { 666 dx = 0; 667 } 668 else if (dir == mxConstants.DIRECTION_NORTH) 669 { 670 dx = 0; 671 dy = -level; 672 } 673 else if (dir == mxConstants.DIRECTION_WEST) 674 { 675 dx = -level; 676 dy = 0; 677 } 678 else if (dir == mxConstants.DIRECTION_EAST) 679 { 680 dy = 0; 681 } 682 683 graph.moveCells(subtree, dx, dy); 684 685 return graph.addCells(clones); 686 } 687 finally 688 { 689 graph.model.endUpdate(); 690 } 691 }; 692 693 function addChild(cell) 694 { 695 graph.model.beginUpdate(); 696 try 697 { 698 var edges = graph.getIncomingEdges(cell); 699 var clones = graph.cloneCells([edges[0], cell]); 700 graph.model.setTerminal(clones[0], cell, true); 701 702 // Finds free space 703 var edges = graph.getOutgoingEdges(cell); 704 var targets = []; 705 706 for (var i = 0; i < edges.length; i++) 707 { 708 var target = graph.model.getTerminal(edges[i], false); 709 710 if (target != null) 711 { 712 targets.push(target); 713 } 714 } 715 716 var bbox = graph.view.getBounds(targets); 717 var dir = getTreeDirection(cell); 718 var tr = graph.view.translate; 719 var s = graph.view.scale; 720 721 if (dir == mxConstants.DIRECTION_SOUTH) 722 { 723 clones[1].geometry.x = (bbox == null) ? cell.geometry.x + (cell.geometry.width - 724 clones[1].geometry.width) / 2 : (bbox.x + bbox.width) / s - tr.x + spacing; 725 clones[1].geometry.y += cell.geometry.height + level; 726 } 727 else if (dir == mxConstants.DIRECTION_NORTH) 728 { 729 clones[1].geometry.x = (bbox == null) ? cell.geometry.x + (cell.geometry.width - 730 clones[1].geometry.width) / 2 : (bbox.x + bbox.width) / s - tr.x + spacing; 731 clones[1].geometry.y -= clones[1].geometry.height + level; 732 } 733 else if (dir == mxConstants.DIRECTION_WEST) 734 { 735 clones[1].geometry.x -= clones[1].geometry.width + level; 736 clones[1].geometry.y = (bbox == null) ? cell.geometry.y + (cell.geometry.height - 737 clones[1].geometry.height) / 2 : (bbox.y + bbox.height) / s - tr.y + spacing; 738 } 739 else 740 { 741 clones[1].geometry.x += cell.geometry.width + level; 742 clones[1].geometry.y = (bbox == null) ? cell.geometry.y + (cell.geometry.height - 743 clones[1].geometry.height) / 2 : (bbox.y + bbox.height) / s - tr.y + spacing; 744 } 745 746 return graph.addCells(clones); 747 } 748 finally 749 { 750 graph.model.endUpdate(); 751 } 752 }; 753 754 function getOrderedTargets(cell, horizontal, ref) 755 { 756 var sib = graph.getOutgoingEdges(cell); 757 var state = graph.view.getState(ref); 758 var targets = []; 759 760 if (state != null && sib != null) 761 { 762 for (var i = 0; i < sib.length; i++) 763 { 764 var temp = graph.view.getState(graph.model.getTerminal(sib[i], false)); 765 766 if (temp != null && ((!horizontal && (Math.min(temp.x + temp.width, 767 state.x + state.width) >= Math.max(temp.x, state.x))) || 768 (horizontal && (Math.min(temp.y + temp.height, state.y + state.height) >= 769 Math.max(temp.y, state.y))))) 770 { 771 targets.push(temp); 772 } 773 } 774 775 targets.sort(function(a, b) 776 { 777 return (horizontal) ? a.x + a.width - b.x - b.width : a.y + a.height - b.y - b.height; 778 }); 779 } 780 781 return targets; 782 }; 783 784 function selectCell(cell, direction) 785 { 786 var dir = getTreeDirection(cell); 787 var h1 = dir == mxConstants.DIRECTION_EAST || dir == mxConstants.DIRECTION_WEST; 788 var h2 = direction == mxConstants.DIRECTION_EAST || direction == mxConstants.DIRECTION_WEST; 789 790 if (h1 == h2 && dir != direction) 791 { 792 ui.actions.get('selectParent').funct(); 793 } 794 else if (dir == direction) 795 { 796 var sib = graph.getOutgoingEdges(cell); 797 798 if (sib != null && sib.length > 0) 799 { 800 graph.setSelectionCell(graph.model.getTerminal(sib[0], false)); 801 } 802 } 803 else 804 { 805 var edges = graph.getIncomingEdges(cell); 806 807 if (edges != null && edges.length > 0) 808 { 809 var targets = getOrderedTargets(graph.model.getTerminal(edges[0], true), h2, cell); 810 var state = graph.view.getState(cell); 811 812 if (state != null) 813 { 814 var idx = mxUtils.indexOf(targets, state); 815 816 if (idx >= 0) 817 { 818 idx += (direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_WEST) ? -1 : 1; 819 820 if (idx >= 0 && idx <= targets.length - 1) 821 { 822 graph.setSelectionCell(targets[idx].cell); 823 } 824 } 825 } 826 } 827 } 828 }; 829 830 // Overrides keyboard shortcuts 831 var altShiftActions = {88: ui.actions.get('selectChildren'), // Alt+Shift+X 832 84: ui.actions.get('selectSubtree'), // Alt+Shift+T 833 80: ui.actions.get('selectParent'), // Alt+Shift+P 834 83: ui.actions.get('selectSiblings')} // Alt+Shift+S 835 836 var editorUiOnKeyDown = ui.onKeyDown; 837 838 ui.onKeyDown = function(evt) 839 { 840 try 841 { 842 if (graph.isEnabled() && !graph.isEditing() && graph.getSelectionCount() == 1 && 843 isTreeCell(graph.getSelectionCell())) 844 { 845 var cells = null; 846 847 if (graph.getSelectionCell().getAttribute('treeRoot') != '1') 848 { 849 if (evt.which == 9) // Tab adds child 850 { 851 cells = (mxEvent.isShiftDown(evt)) ? 852 addParent(graph.getSelectionCell()) : 853 addChild(graph.getSelectionCell()); 854 } 855 else if (evt.which == 13) // Enter adds sibling 856 { 857 cells = addSibling(graph.getSelectionCell(), !mxEvent.isShiftDown(evt)); 858 } 859 } 860 861 if (cells != null && cells.length > 0) 862 { 863 if (cells.length == 1 && graph.model.isEdge(cells[0])) 864 { 865 graph.setSelectionCell(graph.model.getTerminal(cells[0], false)); 866 } 867 else 868 { 869 graph.setSelectionCell(cells[cells.length - 1]); 870 } 871 872 if (ui.hoverIcons != null) 873 { 874 ui.hoverIcons.update(graph.view.getState(graph.getSelectionCell())); 875 } 876 877 graph.startEditingAtCell(graph.getSelectionCell()); 878 mxEvent.consume(evt); 879 } 880 else 881 { 882 if (mxEvent.isAltDown(evt) && mxEvent.isShiftDown(evt)) 883 { 884 var action = altShiftActions[evt.keyCode]; 885 886 if (action != null) 887 { 888 action.funct(evt); 889 mxEvent.consume(evt); 890 } 891 } 892 else 893 { 894 if (evt.keyCode == 37) // left 895 { 896 selectCell(graph.getSelectionCell(), mxConstants.DIRECTION_WEST); 897 mxEvent.consume(evt); 898 } 899 else if (evt.keyCode == 38) // up 900 { 901 selectCell(graph.getSelectionCell(), mxConstants.DIRECTION_NORTH); 902 mxEvent.consume(evt); 903 } 904 else if (evt.keyCode == 39) // right 905 { 906 selectCell(graph.getSelectionCell(), mxConstants.DIRECTION_EAST); 907 mxEvent.consume(evt); 908 } 909 else if (evt.keyCode == 40) // down 910 { 911 selectCell(graph.getSelectionCell(), mxConstants.DIRECTION_SOUTH); 912 mxEvent.consume(evt); 913 } 914 } 915 } 916 } 917 } 918 catch (e) 919 { 920 console.log('error', e); 921 } 922 923 if (!mxEvent.isConsumed(evt)) 924 { 925 editorUiOnKeyDown.apply(this, arguments); 926 } 927 }; 928 929 var graphConnectVertex = graph.connectVertex; 930 931 graph.connectVertex = function(source, direction, length, evt, forceClone, ignoreCellAt) 932 { 933 if (isTreeCell(source) && source.getAttribute('treeRoot') != '1') 934 { 935 var dir = getTreeDirection(source); 936 var h1 = dir == mxConstants.DIRECTION_EAST || dir == mxConstants.DIRECTION_WEST; 937 var h2 = direction == mxConstants.DIRECTION_EAST || direction == mxConstants.DIRECTION_WEST; 938 939 if (dir == direction) 940 { 941 return addChild(source); 942 } 943 else if (h1 == h2) 944 { 945 return addParent(source); 946 } 947 else 948 { 949 return addSibling(source, direction != mxConstants.DIRECTION_NORTH && 950 direction != mxConstants.DIRECTION_WEST); 951 } 952 953 return []; 954 } 955 else 956 { 957 this.model.beginUpdate(); 958 try 959 { 960 var cells = graphConnectVertex.call(this, source, direction, length, evt, forceClone, 961 ignoreCellAt || source.getAttribute('treeRoot') == '1'); 962 963 // Removes treeRoot flag in clones 964 if (source.getAttribute('treeRoot') == '1') 965 { 966 for (var i = 0; i < cells.length; i++) 967 { 968 if (cells[i].getAttribute('treeRoot') == '1') 969 { 970 graph.setAttributeForCell(cells[i], 'treeRoot', null); 971 } 972 } 973 } 974 } 975 finally 976 { 977 this.model.endUpdate(); 978 } 979 980 return cells; 981 } 982 }; 983 984 var graphHandlerGetCells = graph.graphHandler.getCells; 985 986 graph.graphHandler.getCells = function(initialCell) 987 { 988 var cells = graphHandlerGetCells.apply(this, arguments); 989 var temp = cells.slice(0); 990 991 // Removes all edges first 992 for (var i = 0; i < temp.length; i++) 993 { 994 if (isTreeCell(temp[i])) 995 { 996 // Avoids disconnecting subtree by removing all incoming edges 997 var edges = graph.getIncomingEdges(temp[i]); 998 999 for (var j = 0; j < edges.length; j++) 1000 { 1001 mxUtils.remove(edges[j], cells); 1002 } 1003 } 1004 } 1005 1006 for (var i = 0; i < temp.length; i++) 1007 { 1008 if (isTreeCell(temp[i])) 1009 { 1010 // Gets the subtree from cell downwards 1011 graph.traverse(temp[i], true, function(vertex, edge) 1012 { 1013 // TODO: Use dictionary to avoid duplicates 1014 if (edge != null && mxUtils.indexOf(cells, edge) < 0) 1015 { 1016 cells.push(edge); 1017 } 1018 1019 if (mxUtils.indexOf(cells, vertex) < 0) 1020 { 1021 cells.push(vertex); 1022 } 1023 1024 return true; 1025 }); 1026 } 1027 } 1028 1029 return cells; 1030 }; 1031 1032// var ignoreMove = false; 1033// 1034// graph.addListener(mxEvent.MOVE_CELLS, function(sender, evt) 1035// { 1036// if (!ignoreMove) 1037// { 1038// var cells = evt.getProperty('cells'); 1039// var dx = evt.getProperty('dx'); 1040// var dy = evt.getProperty('dy'); 1041// ignoreMove = true; 1042// 1043// for (var i = 0; i < cells.length; i++) 1044// { 1045// var state = graph.view.getState(cells[i]); 1046// 1047// if (state != null && state.style['mindmapRoot'] == '1') 1048// { 1049// // TODO: Move subtree by same dx/dy 1050// //layout.execute(model.getParent(state.cell), state.cell); 1051// 1052// // Gets the subtree from cell downwards 1053// var tmp = []; 1054// graph.traverse(cells[i], true, function(vertex) 1055// { 1056// tmp.push(vertex); 1057// 1058// return true; 1059// }); 1060// 1061// mxUtils.remove(cells[i], tmp); 1062// graph.moveCells(tmp, dx, dy); 1063// } 1064// } 1065// 1066// ignoreMove = false; 1067// } 1068// }); 1069 1070 // Defines a new class for all icons 1071 function mxIconSet(state) 1072 { 1073 this.images = []; 1074 var graph = state.view.graph; 1075 1076 // Icon1 1077// var img = mxUtils.createImage('images/handle-connect.png'); 1078// img.setAttribute('title', 'Duplicate'); 1079// img.style.position = 'absolute'; 1080// img.style.cursor = 'pointer'; 1081// img.style.width = '26px'; 1082// img.style.height = '26px'; 1083// img.style.left = (state.x - 13) + 'px'; 1084// img.style.top = (state.getCenterY() - 13) + 'px'; 1085// 1086// mxEvent.addGestureListeners(img, 1087// mxUtils.bind(this, function(evt) 1088// { 1089// var s = graph.gridSize; 1090// graph.setSelectionCells(graph.moveCells([state.cell], s, s, true)); 1091// mxEvent.consume(evt); 1092// this.destroy(); 1093// }) 1094// ); 1095// 1096// state.view.graph.container.appendChild(img); 1097// this.images.push(img); 1098 1099 // Delete 1100 var img = mxUtils.createImage('plugins/trees/handle-move.gif'); 1101 img.setAttribute('title', 'Move Cell without Subtree'); 1102 img.style.position = 'absolute'; 1103 img.style.cursor = 'pointer'; 1104 img.style.width = '26px'; 1105 img.style.height = '26px'; 1106 img.style.left = (state.getCenterX() - 13) + 'px'; 1107 img.style.top = (state.getCenterY() - 13) + 'px'; 1108 1109 mxEvent.addGestureListeners(img, mxUtils.bind(this, function(evt) 1110 { 1111 graph.stopEditing(false); 1112 ui.hoverIcons.reset(); 1113 1114 if (!graph.isCellSelected(state.cell)) 1115 { 1116 graph.setSelectionCell(state.cell); 1117 } 1118 1119 graph.graphHandler.start(state.cell, mxEvent.getClientX(evt), mxEvent.getClientY(evt)); 1120 1121 graph.graphHandler.cells = [state.cell]; 1122 graph.graphHandler.bounds = graph.graphHandler.graph.getView().getBounds(graph.graphHandler.cells); 1123 graph.graphHandler.pBounds = graph.graphHandler.getPreviewBounds(graph.graphHandler.cells); 1124 1125 graph.graphHandler.cellWasClicked = true; 1126 graph.isMouseDown = true; 1127 graph.isMouseTrigger = mxEvent.isMouseEvent(evt); 1128 mxEvent.consume(evt); 1129 1130 // Disables dragging the image 1131 mxEvent.consume(evt); 1132 this.destroy(); 1133 })); 1134 1135// mxEvent.addListener(img, 'click', 1136// mxUtils.bind(this, function(evt) 1137// { 1138// console.log('here', graph.graphHandler.dx); 1139// 1140// if (Math.abs(graph.graphHandler.currentDx) < graph.tolerance) 1141// { 1142// graph.setSelectionCell(state.cell); 1143// } 1144// }) 1145// ); 1146 1147 state.view.graph.container.appendChild(img); 1148 this.images.push(img); 1149 }; 1150 1151 mxIconSet.prototype.destroy = function() 1152 { 1153 if (this.images != null) 1154 { 1155 for (var i = 0; i < this.images.length; i++) 1156 { 1157 var img = this.images[i]; 1158 img.parentNode.removeChild(img); 1159 } 1160 } 1161 1162 this.images = null; 1163 }; 1164 1165 // Defines the tolerance before removing the icons 1166 var iconTolerance = 20; 1167 1168 // Shows icons if the mouse is over a cell 1169 graph.addMouseListener( 1170 { 1171 currentState: null, 1172 currentIconSet: null, 1173 mouseDown: function(sender, me) 1174 { 1175 // Hides icons on mouse down 1176 if (this.currentState != null) 1177 { 1178 this.dragLeave(me.getEvent(), this.currentState); 1179 this.currentState = null; 1180 } 1181 1182 // TODO: Fix single cell movement on touch devices 1183// if (mxEvent.isTouchEvent(me.getEvent())) 1184// { 1185// this.mouseMove(sender, me); 1186// } 1187 }, 1188 mouseMove: function(sender, me) 1189 { 1190 if (this.currentState != null && (me.getState() == this.currentState || 1191 me.getState() == null)) 1192 { 1193 var tol = iconTolerance; 1194 var tmp = new mxRectangle(me.getGraphX() - tol, 1195 me.getGraphY() - tol, 2 * tol, 2 * tol); 1196 1197 if (mxUtils.intersects(tmp, this.currentState)) 1198 { 1199 return; 1200 } 1201 } 1202 1203 var tmp = me.getState(); 1204 1205 // Ignores everything but vertices 1206 if ((graph.isMouseDown && !mxEvent.isTouchEvent(me.getEvent())) || 1207 graph.isEditing() || (tmp != null && 1208 (!graph.getModel().isVertex(tmp.cell) || !isTreeCell(me.getCell())))) 1209 { 1210 tmp = null; 1211 } 1212 1213 if (tmp != this.currentState) 1214 { 1215 if (this.currentState != null) 1216 { 1217 this.dragLeave(me.getEvent(), this.currentState); 1218 } 1219 1220 this.currentState = tmp; 1221 1222 if (this.currentState != null) 1223 { 1224 this.dragEnter(me.getEvent(), this.currentState); 1225 } 1226 } 1227 }, 1228 mouseUp: function(sender, me) { }, 1229 dragEnter: function(evt, state) 1230 { 1231 if (this.currentIconSet == null) 1232 { 1233 this.currentIconSet = new mxIconSet(state); 1234 } 1235 }, 1236 dragLeave: function(evt, state) 1237 { 1238 if (this.currentIconSet != null) 1239 { 1240 this.currentIconSet.destroy(); 1241 this.currentIconSet = null; 1242 } 1243 } 1244 }); 1245 1246 // Adds sidebar entries 1247 var sb = ui.sidebar; 1248 1249 sb.addPalette('trees', 'Trees', true, function(content) 1250 { 1251 (function() 1252 { 1253 var cell = new mxCell('Central Idea', new mxGeometry(0, 20, 100, 40), 1254 'ellipse;whiteSpace=wrap;html=1;align=center;' + 1255 'collapsible=0;container=1;recursiveResize=0;'); 1256 graph.setAttributeForCell(cell, 'treeRoot', '1'); 1257 cell.vertex = true; 1258 1259 var cell2 = new mxCell('Branch', new mxGeometry(160, 0, 80, 20), 1260 'whiteSpace=wrap;html=1;shape=partialRectangle;top=0;left=0;bottom=1;right=0;points=[[0,1],[1,1]];' + 1261 'strokeColor=#000000;fillColor=none;align=center;verticalAlign=bottom;routingCenterY=0.5;' + 1262 'snapToPoint=1;collapsible=0;container=1;recursiveResize=0;autosize=1;'); 1263 cell2.vertex = true; 1264 1265 var edge = new mxCell('', new mxGeometry(0, 0, 0, 0), 'edgeStyle=entityRelationEdgeStyle;' + 1266 'startArrow=none;endArrow=none;segment=10;curved=1;'); 1267 edge.geometry.relative = true; 1268 edge.edge = true; 1269 1270 cell.insertEdge(edge, true); 1271 cell2.insertEdge(edge, false); 1272 1273 var cell3 = new mxCell('Sub Topic', new mxGeometry(160, 40, 72, 26), 1274 'whiteSpace=wrap;html=1;rounded=1;arcSize=50;align=center;verticalAlign=middle;' + 1275 'collapsible=0;container=1;recursiveResize=0;strokeWidth=1;autosize=1;spacing=4;'); 1276 cell3.vertex = true; 1277 1278 var edge2 = new mxCell('', new mxGeometry(0, 0, 0, 0), 'edgeStyle=entityRelationEdgeStyle;' + 1279 'startArrow=none;endArrow=none;segment=10;curved=1;'); 1280 edge2.geometry.setTerminalPoint(new mxPoint(-40, 40), true); 1281 edge2.geometry.relative = true; 1282 edge2.edge = true; 1283 1284 cell.insertEdge(edge2, true); 1285 cell3.insertEdge(edge2, false); 1286 1287 content.appendChild(sb.createVertexTemplateFromCells([edge, edge2, cell, cell2, cell3], 240, 66, 'Mindmap')); 1288 })(); 1289 1290 (function() 1291 { 1292 var cell = new mxCell('Central Idea', new mxGeometry(0, 0, 100, 40), 1293 'ellipse;whiteSpace=wrap;html=1;align=center;' + 1294 'collapsible=0;container=1;recursiveResize=0;'); 1295 graph.setAttributeForCell(cell, 'treeRoot', '1'); 1296 cell.vertex = true; 1297 1298 content.appendChild(sb.createVertexTemplateFromCells([cell], 100, 40, 'Central Idea')); 1299 })(); 1300 1301 (function() 1302 { 1303 var cell = new mxCell('Branch', new mxGeometry(0, 0, 80, 20), 1304 'whiteSpace=wrap;html=1;shape=partialRectangle;top=0;left=0;bottom=1;right=0;points=[[0,1],[1,1]];' + 1305 'strokeColor=#000000;fillColor=none;align=center;verticalAlign=bottom;routingCenterY=0.5;' + 1306 'snapToPoint=1;collapsible=0;container=1;recursiveResize=0;autosize=1;'); 1307 cell.vertex = true; 1308 1309 var edge = new mxCell('', new mxGeometry(0, 0, 0, 0), 'edgeStyle=entityRelationEdgeStyle;' + 1310 'startArrow=none;endArrow=none;segment=10;curved=1;'); 1311 edge.geometry.setTerminalPoint(new mxPoint(-40, 40), true); 1312 edge.geometry.relative = true; 1313 edge.edge = true; 1314 1315 cell.insertEdge(edge, false); 1316 1317 content.appendChild(sb.createVertexTemplateFromCells([edge, cell], 80, 20, 'Branch')); 1318 })(); 1319 1320 (function() 1321 { 1322 var cell = new mxCell('Sub Topic', new mxGeometry(0, 0, 72, 26), 1323 'whiteSpace=wrap;html=1;rounded=1;arcSize=50;align=center;verticalAlign=middle;' + 1324 'collapsible=0;container=1;recursiveResize=0;strokeWidth=1;autosize=1;spacing=4;'); 1325 cell.vertex = true; 1326 1327 var edge = new mxCell('', new mxGeometry(0, 0, 0, 0), 'edgeStyle=entityRelationEdgeStyle;' + 1328 'startArrow=none;endArrow=none;segment=10;curved=1;'); 1329 edge.geometry.setTerminalPoint(new mxPoint(-40, 40), true); 1330 edge.geometry.relative = true; 1331 edge.edge = true; 1332 1333 cell.insertEdge(edge, false); 1334 1335 content.appendChild(sb.createVertexTemplateFromCells([edge, cell], 72, 26, 'Sub Topic')); 1336 })(); 1337 1338 (function() 1339 { 1340 var cell = new mxCell('Organization', new mxGeometry(60, 0, 120, 60), 1341 'whiteSpace=wrap;html=1;align=center;' + 1342 'collapsible=0;container=1;recursiveResize=0;'); 1343 graph.setAttributeForCell(cell, 'treeRoot', '1'); 1344 cell.vertex = true; 1345 1346 var cell2 = new mxCell('Division', new mxGeometry(0, 100, 100, 60), 1347 'whiteSpace=wrap;html=1;align=center;verticalAlign=middle;' + 1348 'collapsible=0;container=1;recursiveResize=0;'); 1349 cell2.vertex = true; 1350 1351 var edge = new mxCell('', new mxGeometry(0, 0, 0, 0), 'edgeStyle=elbowEdgeStyle;elbow=vertical;' + 1352 'startArrow=none;endArrow=none;rounded=0;'); 1353 edge.geometry.relative = true; 1354 edge.edge = true; 1355 1356 cell.insertEdge(edge, true); 1357 cell2.insertEdge(edge, false); 1358 1359 var cell3 = new mxCell('Division', new mxGeometry(140, 100, 100, 60), 1360 'whiteSpace=wrap;html=1;align=center;verticalAlign=middle;' + 1361 'collapsible=0;container=1;recursiveResize=0;'); 1362 cell3.vertex = true; 1363 1364 var edge2 = new mxCell('', new mxGeometry(0, 0, 0, 0), 'edgeStyle=elbowEdgeStyle;elbow=vertical;' + 1365 'startArrow=none;endArrow=none;rounded=0;'); 1366 edge2.geometry.relative = true; 1367 edge2.edge = true; 1368 1369 cell.insertEdge(edge2, true); 1370 cell3.insertEdge(edge2, false); 1371 1372 content.appendChild(sb.createVertexTemplateFromCells([edge, edge2, cell, cell2, cell3], 240, 160, 'Orgchart')); 1373 })(); 1374 1375 (function() 1376 { 1377 var cell = new mxCell('Tree Root', new mxGeometry(0, 0, 120, 60), 1378 'whiteSpace=wrap;html=1;align=center;' + 1379 'collapsible=0;container=1;recursiveResize=0;'); 1380 graph.setAttributeForCell(cell, 'treeRoot', '1'); 1381 cell.vertex = true; 1382 1383 content.appendChild(sb.createVertexTemplateFromCells([cell], 120, 60, 'Tree Root')); 1384 })(); 1385 1386 (function() 1387 { 1388 var cell = new mxCell('Sub Tree', new mxGeometry(0, 0, 100, 60), 1389 'whiteSpace=wrap;html=1;align=center;verticalAlign=middle;' + 1390 'collapsible=0;container=1;recursiveResize=0;'); 1391 cell.vertex = true; 1392 1393 var edge = new mxCell('', new mxGeometry(0, 0, 0, 0), 'edgeStyle=elbowEdgeStyle;elbow=vertical;' + 1394 'startArrow=none;endArrow=none;rounded=0;'); 1395 edge.geometry.setTerminalPoint(new mxPoint(0, -40), true); 1396 edge.geometry.relative = true; 1397 edge.edge = true; 1398 1399 cell.insertEdge(edge, false); 1400 1401 content.appendChild(sb.createVertexTemplateFromCells([edge, cell], 100, 60, 'Sub Tree')); 1402 })(); 1403 1404 (function() 1405 { 1406 var cell = new mxCell('Sub Section', new mxGeometry(0, 0, 100, 60), 1407 'whiteSpace=wrap;html=1;align=center;verticalAlign=middle;' + 1408 'collapsible=0;container=1;recursiveResize=0;'); 1409 cell.vertex = true; 1410 1411 var edge = new mxCell('', new mxGeometry(0, 0, 0, 0), 'edgeStyle=orthogonalEdgeStyle;' + 1412 'startArrow=none;endArrow=none;rounded=0;targetPortConstraint=eastwest;sourcePortConstraint=northsouth;'); 1413 edge.geometry.setTerminalPoint(new mxPoint(110, -40), true); 1414 edge.geometry.relative = true; 1415 edge.edge = true; 1416 1417 cell.insertEdge(edge, false); 1418 1419 var cell2 = new mxCell('Sub Section', new mxGeometry(120, 0, 100, 60), 1420 'whiteSpace=wrap;html=1;align=center;verticalAlign=middle;' + 1421 'collapsible=0;container=1;recursiveResize=0;'); 1422 cell2.vertex = true; 1423 1424 var edge2 = new mxCell('', new mxGeometry(0, 0, 0, 0), 'edgeStyle=orthogonalEdgeStyle;' + 1425 'startArrow=none;endArrow=none;rounded=0;targetPortConstraint=eastwest;sourcePortConstraint=northsouth;'); 1426 edge2.geometry.setTerminalPoint(new mxPoint(110, -40), true); 1427 edge2.geometry.relative = true; 1428 edge2.edge = true; 1429 1430 cell2.insertEdge(edge2, false); 1431 1432 content.appendChild(sb.createVertexTemplateFromCells([edge, edge2, cell, cell2], 220, 60, 'Sub Sections')); 1433 })(); 1434 }); 1435 1436 // Collapses default sidebar entry and inserts this before 1437 var c = ui.sidebar.container; 1438 var general = c.getElementsByTagName('a')[0]; 1439 general.click(); 1440 c.insertBefore(c.lastChild.previousSibling, general); 1441 c.insertBefore(c.lastChild, general); 1442});