1 /* 2 * Copyright (C) 2012 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 11 * 2. Redistributions in binary form must reproduce the above 12 * copyright notice, this list of conditions and the following disclaimer 13 * in the documentation and/or other materials provided with the 14 * distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS CONTRIBUTORS 17 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC. 20 * OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 /** 30 * @extends {WebInspector.View} 31 * @constructor 32 */ 33 WebInspector.NavigatorView = function() 34 { 35 WebInspector.View.call(this); 36 this.registerRequiredCSS("navigatorView.css"); 37 38 var scriptsTreeElement = document.createElement("ol"); 39 this._scriptsTree = new WebInspector.NavigatorTreeOutline(scriptsTreeElement); 40 this._scriptsTree.childrenListElement.addEventListener("keypress", this._treeKeyPress.bind(this), true); 41 42 var scriptsOutlineElement = document.createElement("div"); 43 scriptsOutlineElement.addStyleClass("outline-disclosure"); 44 scriptsOutlineElement.addStyleClass("navigator"); 45 scriptsOutlineElement.appendChild(scriptsTreeElement); 46 47 this.element.addStyleClass("fill"); 48 this.element.addStyleClass("navigator-container"); 49 this.element.appendChild(scriptsOutlineElement); 50 this.setDefaultFocusedElement(this._scriptsTree.element); 51 52 /** @type {!Map.<WebInspector.UISourceCode, !WebInspector.NavigatorUISourceCodeTreeNode>} */ 53 this._uiSourceCodeNodes = new Map(); 54 /** @type {!Map.<WebInspector.NavigatorTreeNode, !StringMap.<!WebInspector.NavigatorFolderTreeNode>>} */ 55 this._subfolderNodes = new Map(); 56 57 this._rootNode = new WebInspector.NavigatorRootTreeNode(this); 58 this._rootNode.populate(); 59 60 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.InspectedURLChanged, this._inspectedURLChanged, this); 61 this.element.addEventListener("contextmenu", this.handleContextMenu.bind(this), false); 62 } 63 64 WebInspector.NavigatorView.Events = { 65 ItemSelected: "ItemSelected", 66 ItemSearchStarted: "ItemSearchStarted", 67 ItemRenamingRequested: "ItemRenamingRequested", 68 ItemCreationRequested: "ItemCreationRequested" 69 } 70 71 WebInspector.NavigatorView.iconClassForType = function(type) 72 { 73 if (type === WebInspector.NavigatorTreeOutline.Types.Domain) 74 return "navigator-domain-tree-item"; 75 if (type === WebInspector.NavigatorTreeOutline.Types.FileSystem) 76 return "navigator-folder-tree-item"; 77 return "navigator-folder-tree-item"; 78 } 79 80 WebInspector.NavigatorView.prototype = { 81 /** 82 * @param {WebInspector.UISourceCode} uiSourceCode 83 */ 84 addUISourceCode: function(uiSourceCode) 85 { 86 var projectNode = this._projectNode(uiSourceCode.project()); 87 var folderNode = this._folderNode(projectNode, uiSourceCode.parentPath()); 88 var uiSourceCodeNode = new WebInspector.NavigatorUISourceCodeTreeNode(this, uiSourceCode); 89 this._uiSourceCodeNodes.put(uiSourceCode, uiSourceCodeNode); 90 folderNode.appendChild(uiSourceCodeNode); 91 if (uiSourceCode.url === WebInspector.inspectedPageURL) 92 this.revealUISourceCode(uiSourceCode); 93 }, 94 95 /** 96 * @param {WebInspector.Event} event 97 */ 98 _inspectedURLChanged: function(event) 99 { 100 var nodes = this._uiSourceCodeNodes.values(); 101 for (var i = 0; i < nodes.length; ++i) { 102 var uiSourceCode = nodes[i].uiSourceCode(); 103 if (uiSourceCode.url === WebInspector.inspectedPageURL) 104 this.revealUISourceCode(uiSourceCode); 105 } 106 }, 107 108 /** 109 * @param {WebInspector.Project} project 110 * @return {WebInspector.NavigatorTreeNode} 111 */ 112 _projectNode: function(project) 113 { 114 if (!project.displayName()) 115 return this._rootNode; 116 117 var projectNode = this._rootNode.child(project.id()); 118 if (!projectNode) { 119 var type = project.type() === WebInspector.projectTypes.FileSystem ? WebInspector.NavigatorTreeOutline.Types.FileSystem : WebInspector.NavigatorTreeOutline.Types.Domain; 120 projectNode = new WebInspector.NavigatorFolderTreeNode(this, project, project.id(), type, "", project.displayName()); 121 this._rootNode.appendChild(projectNode); 122 } 123 return projectNode; 124 }, 125 126 /** 127 * @param {WebInspector.NavigatorTreeNode} projectNode 128 * @param {string} folderPath 129 * @return {WebInspector.NavigatorTreeNode} 130 */ 131 _folderNode: function(projectNode, folderPath) 132 { 133 if (!folderPath) 134 return projectNode; 135 136 var subfolderNodes = this._subfolderNodes.get(projectNode); 137 if (!subfolderNodes) { 138 subfolderNodes = /** @type {!StringMap.<!WebInspector.NavigatorFolderTreeNode>} */ (new StringMap()); 139 this._subfolderNodes.put(projectNode, subfolderNodes); 140 } 141 142 var folderNode = subfolderNodes.get(folderPath); 143 if (folderNode) 144 return folderNode; 145 146 var parentNode = projectNode; 147 var index = folderPath.lastIndexOf("/"); 148 if (index !== -1) 149 parentNode = this._folderNode(projectNode, folderPath.substring(0, index)); 150 151 var name = folderPath.substring(index + 1); 152 folderNode = new WebInspector.NavigatorFolderTreeNode(this, null, name, WebInspector.NavigatorTreeOutline.Types.Folder, folderPath, name); 153 subfolderNodes.put(folderPath, folderNode); 154 parentNode.appendChild(folderNode); 155 return folderNode; 156 }, 157 158 /** 159 * @param {WebInspector.UISourceCode} uiSourceCode 160 * @param {boolean=} select 161 */ 162 revealUISourceCode: function(uiSourceCode, select) 163 { 164 var node = this._uiSourceCodeNodes.get(uiSourceCode); 165 if (!node) 166 return null; 167 if (this._scriptsTree.selectedTreeElement) 168 this._scriptsTree.selectedTreeElement.deselect(); 169 this._lastSelectedUISourceCode = uiSourceCode; 170 node.reveal(select); 171 }, 172 173 /** 174 * @param {WebInspector.UISourceCode} uiSourceCode 175 * @param {boolean} focusSource 176 */ 177 _scriptSelected: function(uiSourceCode, focusSource) 178 { 179 this._lastSelectedUISourceCode = uiSourceCode; 180 var data = { uiSourceCode: uiSourceCode, focusSource: focusSource}; 181 this.dispatchEventToListeners(WebInspector.NavigatorView.Events.ItemSelected, data); 182 }, 183 184 /** 185 * @param {WebInspector.UISourceCode} uiSourceCode 186 */ 187 removeUISourceCode: function(uiSourceCode) 188 { 189 var node = this._uiSourceCodeNodes.get(uiSourceCode); 190 if (!node) 191 return; 192 193 var projectNode = this._projectNode(uiSourceCode.project()); 194 var subfolderNodes = this._subfolderNodes.get(projectNode); 195 var parentNode = node.parent; 196 this._uiSourceCodeNodes.remove(uiSourceCode); 197 parentNode.removeChild(node); 198 node = parentNode; 199 200 while (node) { 201 parentNode = node.parent; 202 if (!parentNode || !node.isEmpty()) 203 break; 204 if (subfolderNodes) 205 subfolderNodes.remove(node._folderPath); 206 parentNode.removeChild(node); 207 node = parentNode; 208 } 209 }, 210 211 /** 212 * @param {WebInspector.UISourceCode} uiSourceCode 213 */ 214 requestRename: function(uiSourceCode) 215 { 216 this.dispatchEventToListeners(WebInspector.ScriptsNavigator.Events.ItemRenamingRequested, uiSourceCode); 217 }, 218 219 /** 220 * @param {WebInspector.UISourceCode} uiSourceCode 221 * @param {function(boolean)=} callback 222 */ 223 rename: function(uiSourceCode, callback) 224 { 225 var node = this._uiSourceCodeNodes.get(uiSourceCode); 226 if (!node) 227 return null; 228 node.rename(callback); 229 }, 230 231 reset: function() 232 { 233 var nodes = this._uiSourceCodeNodes.values(); 234 for (var i = 0; i < nodes.length; ++i) 235 nodes[i].dispose(); 236 237 this._scriptsTree.removeChildren(); 238 this._uiSourceCodeNodes.clear(); 239 this._subfolderNodes.clear(); 240 this._rootNode.reset(); 241 }, 242 243 /** 244 * @param {Event} event 245 */ 246 handleContextMenu: function(event) 247 { 248 var contextMenu = new WebInspector.ContextMenu(event); 249 this._appendAddFolderItem(contextMenu); 250 contextMenu.show(); 251 }, 252 253 /** 254 * @param {WebInspector.ContextMenu} contextMenu 255 */ 256 _appendAddFolderItem: function(contextMenu) 257 { 258 function addFolder() 259 { 260 WebInspector.isolatedFileSystemManager.addFileSystem(); 261 } 262 263 var addFolderLabel = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add folder to workspace" : "Add Folder to Workspace"); 264 contextMenu.appendItem(addFolderLabel, addFolder); 265 }, 266 267 /** 268 * @param {Event} event 269 * @param {WebInspector.UISourceCode} uiSourceCode 270 */ 271 handleFileContextMenu: function(event, uiSourceCode) 272 { 273 var contextMenu = new WebInspector.ContextMenu(event); 274 contextMenu.appendApplicableItems(uiSourceCode); 275 contextMenu.appendSeparator(); 276 this._appendAddFolderItem(contextMenu); 277 contextMenu.show(); 278 }, 279 280 /** 281 * @param {Event} event 282 * @param {WebInspector.NavigatorFolderTreeNode} node 283 */ 284 handleFolderContextMenu: function(event, node) 285 { 286 var contextMenu = new WebInspector.ContextMenu(event); 287 var path = "/"; 288 var projectNode = node; 289 while (projectNode.parent !== this._rootNode) { 290 path = "/" + projectNode.id + path; 291 projectNode = projectNode.parent; 292 } 293 294 var project = projectNode._project; 295 296 if (project.type() === WebInspector.projectTypes.FileSystem) { 297 function refresh() 298 { 299 project.refresh(path); 300 } 301 302 function create() 303 { 304 var data = {}; 305 data.project = project; 306 data.path = path; 307 this.dispatchEventToListeners(WebInspector.NavigatorView.Events.ItemCreationRequested, data); 308 } 309 310 contextMenu.appendItem(WebInspector.UIString("Refresh"), refresh.bind(this)); 311 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "New file" : "New File"), create.bind(this)); 312 } 313 contextMenu.appendSeparator(); 314 this._appendAddFolderItem(contextMenu); 315 if (project.type() === WebInspector.projectTypes.FileSystem && node === projectNode) { 316 function removeFolder() 317 { 318 var shouldRemove = window.confirm(WebInspector.UIString("Are you sure you want to remove this folder?")); 319 if (shouldRemove) 320 project.remove(); 321 } 322 323 var removeFolderLabel = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Remove folder from workspace" : "Remove Folder from Workspace"); 324 contextMenu.appendItem(removeFolderLabel, removeFolder); 325 } 326 327 contextMenu.show(); 328 }, 329 330 /** 331 * @param {Event} event 332 */ 333 _treeKeyPress: function(event) 334 { 335 if (WebInspector.isBeingEdited(this._scriptsTree.childrenListElement)) 336 return; 337 338 var searchText = String.fromCharCode(event.charCode); 339 if (searchText.trim() !== searchText) 340 return; 341 this.dispatchEventToListeners(WebInspector.NavigatorView.Events.ItemSearchStarted, searchText); 342 event.consume(true); 343 }, 344 345 __proto__: WebInspector.View.prototype 346 } 347 348 /** 349 * @constructor 350 * @extends {TreeOutline} 351 * @param {Element} element 352 */ 353 WebInspector.NavigatorTreeOutline = function(element) 354 { 355 TreeOutline.call(this, element); 356 this.element = element; 357 358 this.comparator = WebInspector.NavigatorTreeOutline._treeElementsCompare; 359 } 360 361 WebInspector.NavigatorTreeOutline.Types = { 362 Root: "Root", 363 Domain: "Domain", 364 Folder: "Folder", 365 UISourceCode: "UISourceCode", 366 FileSystem: "FileSystem" 367 } 368 369 WebInspector.NavigatorTreeOutline._treeElementsCompare = function compare(treeElement1, treeElement2) 370 { 371 // Insert in the alphabetical order, first domains, then folders, then scripts. 372 function typeWeight(treeElement) 373 { 374 var type = treeElement.type(); 375 if (type === WebInspector.NavigatorTreeOutline.Types.Domain) { 376 if (treeElement.titleText === WebInspector.inspectedPageDomain) 377 return 1; 378 return 2; 379 } 380 if (type === WebInspector.NavigatorTreeOutline.Types.FileSystem) 381 return 3; 382 if (type === WebInspector.NavigatorTreeOutline.Types.Folder) 383 return 4; 384 return 5; 385 } 386 387 var typeWeight1 = typeWeight(treeElement1); 388 var typeWeight2 = typeWeight(treeElement2); 389 390 var result; 391 if (typeWeight1 > typeWeight2) 392 result = 1; 393 else if (typeWeight1 < typeWeight2) 394 result = -1; 395 else { 396 var title1 = treeElement1.titleText; 397 var title2 = treeElement2.titleText; 398 result = title1.compareTo(title2); 399 } 400 return result; 401 } 402 403 WebInspector.NavigatorTreeOutline.prototype = { 404 /** 405 * @return {Array.<WebInspector.UISourceCode>} 406 */ 407 scriptTreeElements: function() 408 { 409 var result = []; 410 if (this.children.length) { 411 for (var treeElement = this.children[0]; treeElement; treeElement = treeElement.traverseNextTreeElement(false, this, true)) { 412 if (treeElement instanceof WebInspector.NavigatorSourceTreeElement) 413 result.push(treeElement.uiSourceCode); 414 } 415 } 416 return result; 417 }, 418 419 __proto__: TreeOutline.prototype 420 } 421 422 /** 423 * @constructor 424 * @extends {TreeElement} 425 * @param {string} type 426 * @param {string} title 427 * @param {Array.<string>} iconClasses 428 * @param {boolean} hasChildren 429 * @param {boolean=} noIcon 430 */ 431 WebInspector.BaseNavigatorTreeElement = function(type, title, iconClasses, hasChildren, noIcon) 432 { 433 this._type = type; 434 TreeElement.call(this, "", null, hasChildren); 435 this._titleText = title; 436 this._iconClasses = iconClasses; 437 this._noIcon = noIcon; 438 } 439 440 WebInspector.BaseNavigatorTreeElement.prototype = { 441 onattach: function() 442 { 443 this.listItemElement.removeChildren(); 444 if (this._iconClasses) { 445 for (var i = 0; i < this._iconClasses.length; ++i) 446 this.listItemElement.addStyleClass(this._iconClasses[i]); 447 } 448 449 var selectionElement = document.createElement("div"); 450 selectionElement.className = "selection"; 451 this.listItemElement.appendChild(selectionElement); 452 453 if (!this._noIcon) { 454 this.imageElement = document.createElement("img"); 455 this.imageElement.className = "icon"; 456 this.listItemElement.appendChild(this.imageElement); 457 } 458 459 this.titleElement = document.createElement("div"); 460 this.titleElement.className = "base-navigator-tree-element-title"; 461 this._titleTextNode = document.createTextNode(""); 462 this._titleTextNode.textContent = this._titleText; 463 this.titleElement.appendChild(this._titleTextNode); 464 this.listItemElement.appendChild(this.titleElement); 465 }, 466 467 onreveal: function() 468 { 469 if (this.listItemElement) 470 this.listItemElement.scrollIntoViewIfNeeded(true); 471 }, 472 473 /** 474 * @return {string} 475 */ 476 get titleText() 477 { 478 return this._titleText; 479 }, 480 481 set titleText(titleText) 482 { 483 if (this._titleText === titleText) 484 return; 485 this._titleText = titleText || ""; 486 if (this.titleElement) 487 this.titleElement.textContent = this._titleText; 488 }, 489 490 /** 491 * @return {string} 492 */ 493 type: function() 494 { 495 return this._type; 496 }, 497 498 __proto__: TreeElement.prototype 499 } 500 501 /** 502 * @constructor 503 * @extends {WebInspector.BaseNavigatorTreeElement} 504 * @param {WebInspector.NavigatorView} navigatorView 505 * @param {string} type 506 * @param {string} title 507 */ 508 WebInspector.NavigatorFolderTreeElement = function(navigatorView, type, title) 509 { 510 var iconClass = WebInspector.NavigatorView.iconClassForType(type); 511 WebInspector.BaseNavigatorTreeElement.call(this, type, title, [iconClass], true); 512 this._navigatorView = navigatorView; 513 } 514 515 WebInspector.NavigatorFolderTreeElement.prototype = { 516 onpopulate: function() 517 { 518 this._node.populate(); 519 }, 520 521 onattach: function() 522 { 523 WebInspector.BaseNavigatorTreeElement.prototype.onattach.call(this); 524 this.collapse(); 525 this.listItemElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), false); 526 }, 527 528 /** 529 * @param {WebInspector.NavigatorFolderTreeNode} node 530 */ 531 setNode: function(node) 532 { 533 this._node = node; 534 var paths = []; 535 while (node && !node.isRoot()) { 536 paths.push(node._title); 537 node = node.parent; 538 } 539 paths.reverse(); 540 this.tooltip = paths.join("/"); 541 }, 542 543 /** 544 * @param {Event} event 545 */ 546 _handleContextMenuEvent: function(event) 547 { 548 if (!this._node) 549 return; 550 this.select(); 551 this._navigatorView.handleFolderContextMenu(event, this._node); 552 }, 553 554 __proto__: WebInspector.BaseNavigatorTreeElement.prototype 555 } 556 557 /** 558 * @constructor 559 * @extends {WebInspector.BaseNavigatorTreeElement} 560 * @param {WebInspector.NavigatorView} navigatorView 561 * @param {WebInspector.UISourceCode} uiSourceCode 562 * @param {string} title 563 */ 564 WebInspector.NavigatorSourceTreeElement = function(navigatorView, uiSourceCode, title) 565 { 566 WebInspector.BaseNavigatorTreeElement.call(this, WebInspector.NavigatorTreeOutline.Types.UISourceCode, title, ["navigator-" + uiSourceCode.contentType().name() + "-tree-item"], false); 567 this._navigatorView = navigatorView; 568 this._uiSourceCode = uiSourceCode; 569 this.tooltip = uiSourceCode.originURL(); 570 } 571 572 WebInspector.NavigatorSourceTreeElement.prototype = { 573 /** 574 * @return {WebInspector.UISourceCode} 575 */ 576 get uiSourceCode() 577 { 578 return this._uiSourceCode; 579 }, 580 581 onattach: function() 582 { 583 WebInspector.BaseNavigatorTreeElement.prototype.onattach.call(this); 584 this.listItemElement.draggable = true; 585 this.listItemElement.addEventListener("click", this._onclick.bind(this), false); 586 this.listItemElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), false); 587 this.listItemElement.addEventListener("mousedown", this._onmousedown.bind(this), false); 588 this.listItemElement.addEventListener("dragstart", this._ondragstart.bind(this), false); 589 }, 590 591 _onmousedown: function(event) 592 { 593 if (event.which === 1) // Warm-up data for drag'n'drop 594 this._uiSourceCode.requestContent(callback.bind(this)); 595 /** 596 * @param {?string} content 597 * @param {boolean} contentEncoded 598 * @param {string} mimeType 599 */ 600 function callback(content, contentEncoded, mimeType) 601 { 602 this._warmedUpContent = content; 603 } 604 }, 605 606 _shouldRenameOnMouseDown: function() 607 { 608 if (!this._uiSourceCode.canRename()) 609 return false; 610 var isSelected = this === this.treeOutline.selectedTreeElement; 611 var isFocused = this.treeOutline.childrenListElement.isSelfOrAncestor(document.activeElement); 612 return isSelected && isFocused && !WebInspector.isBeingEdited(this.treeOutline.element); 613 }, 614 615 selectOnMouseDown: function(event) 616 { 617 if (event.which !== 1 || !this._shouldRenameOnMouseDown()) { 618 TreeElement.prototype.selectOnMouseDown.call(this, event); 619 return; 620 } 621 setTimeout(rename.bind(this), 300); 622 623 function rename() 624 { 625 if (this._shouldRenameOnMouseDown()) 626 this._navigatorView.requestRename(this._uiSourceCode); 627 } 628 }, 629 630 _ondragstart: function(event) 631 { 632 event.dataTransfer.setData("text/plain", this._warmedUpContent); 633 event.dataTransfer.effectAllowed = "copy"; 634 return true; 635 }, 636 637 onspace: function() 638 { 639 this._navigatorView._scriptSelected(this.uiSourceCode, true); 640 return true; 641 }, 642 643 /** 644 * @param {Event} event 645 */ 646 _onclick: function(event) 647 { 648 this._navigatorView._scriptSelected(this.uiSourceCode, false); 649 }, 650 651 /** 652 * @param {Event} event 653 */ 654 ondblclick: function(event) 655 { 656 var middleClick = event.button === 1; 657 this._navigatorView._scriptSelected(this.uiSourceCode, !middleClick); 658 }, 659 660 onenter: function() 661 { 662 this._navigatorView._scriptSelected(this.uiSourceCode, true); 663 return true; 664 }, 665 666 /** 667 * @param {Event} event 668 */ 669 _handleContextMenuEvent: function(event) 670 { 671 this.select(); 672 this._navigatorView.handleFileContextMenu(event, this._uiSourceCode); 673 }, 674 675 __proto__: WebInspector.BaseNavigatorTreeElement.prototype 676 } 677 678 /** 679 * @constructor 680 * @param {string} id 681 */ 682 WebInspector.NavigatorTreeNode = function(id) 683 { 684 this.id = id; 685 /** @type {!StringMap.<!WebInspector.NavigatorTreeNode>} */ 686 this._children = new StringMap(); 687 } 688 689 WebInspector.NavigatorTreeNode.prototype = { 690 /** 691 * @return {TreeElement} 692 */ 693 treeElement: function() { }, 694 695 dispose: function() { }, 696 697 /** 698 * @return {boolean} 699 */ 700 isRoot: function() 701 { 702 return false; 703 }, 704 705 /** 706 * @return {boolean} 707 */ 708 hasChildren: function() 709 { 710 return true; 711 }, 712 713 populate: function() 714 { 715 if (this.isPopulated()) 716 return; 717 if (this.parent) 718 this.parent.populate(); 719 this._populated = true; 720 this.wasPopulated(); 721 }, 722 723 wasPopulated: function() 724 { 725 var children = this.children(); 726 for (var i = 0; i < children.length; ++i) 727 this.treeElement().appendChild(children[i].treeElement()); 728 }, 729 730 /** 731 * @param {!WebInspector.NavigatorTreeNode} node 732 */ 733 didAddChild: function(node) 734 { 735 if (this.isPopulated()) 736 this.treeElement().appendChild(node.treeElement()); 737 }, 738 739 /** 740 * @param {!WebInspector.NavigatorTreeNode} node 741 */ 742 willRemoveChild: function(node) 743 { 744 if (this.isPopulated()) 745 this.treeElement().removeChild(node.treeElement()); 746 }, 747 748 /** 749 * @return {boolean} 750 */ 751 isPopulated: function() 752 { 753 return this._populated; 754 }, 755 756 /** 757 * @return {boolean} 758 */ 759 isEmpty: function() 760 { 761 return !this._children.size(); 762 }, 763 764 /** 765 * @param {string} id 766 * @return {WebInspector.NavigatorTreeNode} 767 */ 768 child: function(id) 769 { 770 return this._children.get(id); 771 }, 772 773 /** 774 * @return {!Array.<!WebInspector.NavigatorTreeNode>} 775 */ 776 children: function() 777 { 778 return this._children.values(); 779 }, 780 781 /** 782 * @param {!WebInspector.NavigatorTreeNode} node 783 */ 784 appendChild: function(node) 785 { 786 this._children.put(node.id, node); 787 node.parent = this; 788 this.didAddChild(node); 789 }, 790 791 /** 792 * @param {!WebInspector.NavigatorTreeNode} node 793 */ 794 removeChild: function(node) 795 { 796 this.willRemoveChild(node); 797 this._children.remove(node.id); 798 delete node.parent; 799 node.dispose(); 800 }, 801 802 reset: function() 803 { 804 this._children.clear(); 805 } 806 } 807 808 /** 809 * @constructor 810 * @extends {WebInspector.NavigatorTreeNode} 811 * @param {WebInspector.NavigatorView} navigatorView 812 */ 813 WebInspector.NavigatorRootTreeNode = function(navigatorView) 814 { 815 WebInspector.NavigatorTreeNode.call(this, ""); 816 this._navigatorView = navigatorView; 817 } 818 819 WebInspector.NavigatorRootTreeNode.prototype = { 820 /** 821 * @return {boolean} 822 */ 823 isRoot: function() 824 { 825 return true; 826 }, 827 828 /** 829 * @return {TreeOutline} 830 */ 831 treeElement: function() 832 { 833 return this._navigatorView._scriptsTree; 834 }, 835 836 __proto__: WebInspector.NavigatorTreeNode.prototype 837 } 838 839 /** 840 * @constructor 841 * @extends {WebInspector.NavigatorTreeNode} 842 * @param {WebInspector.NavigatorView} navigatorView 843 * @param {WebInspector.UISourceCode} uiSourceCode 844 */ 845 WebInspector.NavigatorUISourceCodeTreeNode = function(navigatorView, uiSourceCode) 846 { 847 WebInspector.NavigatorTreeNode.call(this, uiSourceCode.name()); 848 this._navigatorView = navigatorView; 849 this._uiSourceCode = uiSourceCode; 850 this._treeElement = null; 851 } 852 853 WebInspector.NavigatorUISourceCodeTreeNode.prototype = { 854 /** 855 * @return {WebInspector.UISourceCode} 856 */ 857 uiSourceCode: function() 858 { 859 return this._uiSourceCode; 860 }, 861 862 /** 863 * @return {TreeElement} 864 */ 865 treeElement: function() 866 { 867 if (this._treeElement) 868 return this._treeElement; 869 870 this._treeElement = new WebInspector.NavigatorSourceTreeElement(this._navigatorView, this._uiSourceCode, ""); 871 this.updateTitle(); 872 873 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.TitleChanged, this._titleChanged, this); 874 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this); 875 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this); 876 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.FormattedChanged, this._formattedChanged, this); 877 878 return this._treeElement; 879 }, 880 881 /** 882 * @param {boolean=} ignoreIsDirty 883 */ 884 updateTitle: function(ignoreIsDirty) 885 { 886 if (!this._treeElement) 887 return; 888 889 var titleText = this._uiSourceCode.displayName(); 890 if (!ignoreIsDirty && this._uiSourceCode.isDirty()) 891 titleText = "*" + titleText; 892 this._treeElement.titleText = titleText; 893 }, 894 895 /** 896 * @return {boolean} 897 */ 898 hasChildren: function() 899 { 900 return false; 901 }, 902 903 dispose: function() 904 { 905 if (!this._treeElement) 906 return; 907 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.TitleChanged, this._titleChanged, this); 908 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this); 909 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this); 910 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.FormattedChanged, this._formattedChanged, this); 911 }, 912 913 _titleChanged: function(event) 914 { 915 this.updateTitle(); 916 }, 917 918 _workingCopyChanged: function(event) 919 { 920 this.updateTitle(); 921 }, 922 923 _workingCopyCommitted: function(event) 924 { 925 this.updateTitle(); 926 }, 927 928 _formattedChanged: function(event) 929 { 930 this.updateTitle(); 931 }, 932 933 /** 934 * @param {boolean=} select 935 */ 936 reveal: function(select) 937 { 938 this.parent.populate(); 939 this.parent.treeElement().expand(); 940 this._treeElement.reveal(); 941 if (select) 942 this._treeElement.select(); 943 }, 944 945 /** 946 * @param {function(boolean)=} callback 947 */ 948 rename: function(callback) 949 { 950 if (!this._treeElement) 951 return; 952 953 // Tree outline should be marked as edited as well as the tree element to prevent search from starting. 954 var treeOutlineElement = this._treeElement.treeOutline.element; 955 WebInspector.markBeingEdited(treeOutlineElement, true); 956 957 function commitHandler(element, newTitle, oldTitle) 958 { 959 if (newTitle !== oldTitle) { 960 this._treeElement.titleText = newTitle; 961 this._uiSourceCode.rename(newTitle, renameCallback.bind(this)); 962 return; 963 } 964 afterEditing.call(this, true); 965 } 966 967 function renameCallback(success) 968 { 969 if (!success) { 970 WebInspector.markBeingEdited(treeOutlineElement, false); 971 this.updateTitle(); 972 this.rename(callback); 973 return; 974 } 975 afterEditing.call(this, true); 976 } 977 978 function cancelHandler() 979 { 980 afterEditing.call(this, false); 981 } 982 983 /** 984 * @param {boolean} committed 985 */ 986 function afterEditing(committed) 987 { 988 WebInspector.markBeingEdited(treeOutlineElement, false); 989 this.updateTitle(); 990 this._treeElement.treeOutline.childrenListElement.focus(); 991 if (callback) 992 callback(committed); 993 } 994 995 var editingConfig = new WebInspector.EditingConfig(commitHandler.bind(this), cancelHandler.bind(this)); 996 this.updateTitle(true); 997 WebInspector.startEditing(this._treeElement.titleElement, editingConfig); 998 window.getSelection().setBaseAndExtent(this._treeElement.titleElement, 0, this._treeElement.titleElement, 1); 999 }, 1000 1001 __proto__: WebInspector.NavigatorTreeNode.prototype 1002 } 1003 1004 /** 1005 * @constructor 1006 * @extends {WebInspector.NavigatorTreeNode} 1007 * @param {WebInspector.NavigatorView} navigatorView 1008 * @param {WebInspector.Project} project 1009 * @param {string} id 1010 * @param {string} type 1011 * @param {string} folderPath 1012 * @param {string} title 1013 */ 1014 WebInspector.NavigatorFolderTreeNode = function(navigatorView, project, id, type, folderPath, title) 1015 { 1016 WebInspector.NavigatorTreeNode.call(this, id); 1017 this._navigatorView = navigatorView; 1018 this._project = project; 1019 this._type = type; 1020 this._folderPath = folderPath; 1021 this._title = title; 1022 } 1023 1024 WebInspector.NavigatorFolderTreeNode.prototype = { 1025 /** 1026 * @return {TreeElement} 1027 */ 1028 treeElement: function() 1029 { 1030 if (this._treeElement) 1031 return this._treeElement; 1032 this._treeElement = this._createTreeElement(this._title, this); 1033 return this._treeElement; 1034 }, 1035 1036 /** 1037 * @return {TreeElement} 1038 */ 1039 _createTreeElement: function(title, node) 1040 { 1041 var treeElement = new WebInspector.NavigatorFolderTreeElement(this._navigatorView, this._type, title); 1042 treeElement.setNode(node); 1043 return treeElement; 1044 }, 1045 1046 wasPopulated: function() 1047 { 1048 if (!this._treeElement || this._treeElement._node !== this) 1049 return; 1050 this._addChildrenRecursive(); 1051 }, 1052 1053 _addChildrenRecursive: function() 1054 { 1055 var children = this.children(); 1056 for (var i = 0; i < children.length; ++i) { 1057 var child = children[i]; 1058 this.didAddChild(child); 1059 if (child instanceof WebInspector.NavigatorFolderTreeNode) 1060 child._addChildrenRecursive(); 1061 } 1062 }, 1063 1064 _shouldMerge: function(node) 1065 { 1066 return this._type !== WebInspector.NavigatorTreeOutline.Types.Domain && node instanceof WebInspector.NavigatorFolderTreeNode; 1067 }, 1068 1069 didAddChild: function(node) 1070 { 1071 function titleForNode(node) 1072 { 1073 return node._title; 1074 } 1075 1076 if (!this._treeElement) 1077 return; 1078 1079 var children = this.children(); 1080 1081 if (children.length === 1 && this._shouldMerge(node)) { 1082 node._isMerged = true; 1083 this._treeElement.titleText = this._treeElement.titleText + "/" + node._title; 1084 node._treeElement = this._treeElement; 1085 this._treeElement.setNode(node); 1086 return; 1087 } 1088 1089 var oldNode; 1090 if (children.length === 2) 1091 oldNode = children[0] !== node ? children[0] : children[1]; 1092 if (oldNode && oldNode._isMerged) { 1093 delete oldNode._isMerged; 1094 var mergedToNodes = []; 1095 mergedToNodes.push(this); 1096 var treeNode = this; 1097 while (treeNode._isMerged) { 1098 treeNode = treeNode.parent; 1099 mergedToNodes.push(treeNode); 1100 } 1101 mergedToNodes.reverse(); 1102 var titleText = mergedToNodes.map(titleForNode).join("/"); 1103 1104 var nodes = []; 1105 treeNode = oldNode; 1106 do { 1107 nodes.push(treeNode); 1108 children = treeNode.children(); 1109 treeNode = children.length === 1 ? children[0] : null; 1110 } while (treeNode && treeNode._isMerged); 1111 1112 if (!this.isPopulated()) { 1113 this._treeElement.titleText = titleText; 1114 this._treeElement.setNode(this); 1115 for (var i = 0; i < nodes.length; ++i) { 1116 delete nodes[i]._treeElement; 1117 delete nodes[i]._isMerged; 1118 } 1119 return; 1120 } 1121 var oldTreeElement = this._treeElement; 1122 var treeElement = this._createTreeElement(titleText, this); 1123 for (var i = 0; i < mergedToNodes.length; ++i) 1124 mergedToNodes[i]._treeElement = treeElement; 1125 oldTreeElement.parent.appendChild(treeElement); 1126 1127 oldTreeElement.setNode(nodes[nodes.length - 1]); 1128 oldTreeElement.titleText = nodes.map(titleForNode).join("/"); 1129 oldTreeElement.parent.removeChild(oldTreeElement); 1130 this._treeElement.appendChild(oldTreeElement); 1131 if (oldTreeElement.expanded) 1132 treeElement.expand(); 1133 } 1134 if (this.isPopulated()) 1135 this._treeElement.appendChild(node.treeElement()); 1136 }, 1137 1138 willRemoveChild: function(node) 1139 { 1140 if (node._isMerged || !this.isPopulated()) 1141 return; 1142 this._treeElement.removeChild(node._treeElement); 1143 }, 1144 1145 __proto__: WebInspector.NavigatorTreeNode.prototype 1146 } 1147