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 */ 33WebInspector.NavigatorView = function() 34{ 35 WebInspector.View.call(this); 36 this.registerRequiredCSS("navigatorView.css"); 37 38 this._treeSearchBoxElement = document.createElement("div"); 39 this._treeSearchBoxElement.className = "navigator-tree-search-box"; 40 this.element.appendChild(this._treeSearchBoxElement); 41 42 var scriptsTreeElement = document.createElement("ol"); 43 this._scriptsTree = new WebInspector.NavigatorTreeOutline(this._treeSearchBoxElement, scriptsTreeElement); 44 45 var scriptsOutlineElement = document.createElement("div"); 46 scriptsOutlineElement.addStyleClass("outline-disclosure"); 47 scriptsOutlineElement.addStyleClass("navigator"); 48 scriptsOutlineElement.appendChild(scriptsTreeElement); 49 50 this.element.addStyleClass("fill"); 51 this.element.addStyleClass("navigator-container"); 52 this.element.appendChild(scriptsOutlineElement); 53 this.setDefaultFocusedElement(this._scriptsTree.element); 54 55 /** @type {Object.<string, WebInspector.NavigatorUISourceCodeTreeNode>} */ 56 this._uiSourceCodeNodes = {}; 57 58 this._rootNode = new WebInspector.NavigatorRootTreeNode(this); 59 this._rootNode.populate(); 60 61 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.InspectedURLChanged, this._inspectedURLChanged, this); 62} 63 64WebInspector.NavigatorView.Events = { 65 ItemSelected: "ItemSelected", 66 FileRenamed: "FileRenamed" 67} 68 69WebInspector.NavigatorView.iconClassForType = function(type) 70{ 71 if (type === WebInspector.NavigatorTreeOutline.Types.Domain) 72 return "navigator-domain-tree-item"; 73 if (type === WebInspector.NavigatorTreeOutline.Types.FileSystem) 74 return "navigator-folder-tree-item"; 75 return "navigator-folder-tree-item"; 76} 77 78WebInspector.NavigatorView.prototype = { 79 /** 80 * @param {WebInspector.UISourceCode} uiSourceCode 81 */ 82 addUISourceCode: function(uiSourceCode) 83 { 84 var node = this._getOrCreateUISourceCodeParentNode(uiSourceCode); 85 var uiSourceCodeNode = new WebInspector.NavigatorUISourceCodeTreeNode(this, uiSourceCode); 86 this._uiSourceCodeNodes[uiSourceCode.uri()] = uiSourceCodeNode; 87 node.appendChild(uiSourceCodeNode); 88 if (uiSourceCode.url === WebInspector.inspectedPageURL) 89 this.revealUISourceCode(uiSourceCode); 90 }, 91 92 /** 93 * @param {WebInspector.Event} event 94 */ 95 _inspectedURLChanged: function(event) 96 { 97 var nodes = Object.values(this._uiSourceCodeNodes); 98 for (var i = 0; i < nodes.length; ++i) { 99 var uiSourceCode = nodes[i].uiSourceCode(); 100 if (uiSourceCode.url === WebInspector.inspectedPageURL) 101 this.revealUISourceCode(uiSourceCode); 102 } 103 104 }, 105 106 /** 107 * @param {WebInspector.Project} project 108 * @return {WebInspector.NavigatorTreeNode} 109 */ 110 _getProjectNode: function(project) 111 { 112 if (!project.displayName()) 113 return this._rootNode; 114 return this._rootNode.child(project.id()); 115 }, 116 117 /** 118 * @param {WebInspector.Project} project 119 * @return {WebInspector.NavigatorFolderTreeNode} 120 */ 121 _createProjectNode: function(project) 122 { 123 var type = project.type() === WebInspector.projectTypes.FileSystem ? WebInspector.NavigatorTreeOutline.Types.FileSystem : WebInspector.NavigatorTreeOutline.Types.Domain; 124 var projectNode = new WebInspector.NavigatorFolderTreeNode(this, project.id(), type, project.displayName()); 125 this._rootNode.appendChild(projectNode); 126 return projectNode; 127 }, 128 129 /** 130 * @param {WebInspector.Project} project 131 * @return {WebInspector.NavigatorTreeNode} 132 */ 133 _getOrCreateProjectNode: function(project) 134 { 135 return this._getProjectNode(project) || this._createProjectNode(project); 136 }, 137 138 /** 139 * @param {WebInspector.NavigatorTreeNode} parentNode 140 * @param {string} name 141 * @return {WebInspector.NavigatorFolderTreeNode} 142 */ 143 _getFolderNode: function(parentNode, name) 144 { 145 return parentNode.child(name); 146 }, 147 148 /** 149 * @param {WebInspector.NavigatorTreeNode} parentNode 150 * @param {string} name 151 * @return {WebInspector.NavigatorFolderTreeNode} 152 */ 153 _createFolderNode: function(parentNode, name) 154 { 155 var folderNode = new WebInspector.NavigatorFolderTreeNode(this, name, WebInspector.NavigatorTreeOutline.Types.Folder, name); 156 parentNode.appendChild(folderNode); 157 return folderNode; 158 }, 159 160 /** 161 * @param {WebInspector.NavigatorTreeNode} parentNode 162 * @param {string} name 163 * @return {WebInspector.NavigatorFolderTreeNode} 164 */ 165 _getOrCreateFolderNode: function(parentNode, name) 166 { 167 return this._getFolderNode(parentNode, name) || this._createFolderNode(parentNode, name); 168 }, 169 170 /** 171 * @param {WebInspector.UISourceCode} uiSourceCode 172 * @return {WebInspector.NavigatorTreeNode} 173 */ 174 _getUISourceCodeParentNode: function(uiSourceCode) 175 { 176 var projectNode = this._getProjectNode(uiSourceCode.project()); 177 if (!projectNode) 178 return null; 179 var path = uiSourceCode.path(); 180 var parentNode = projectNode; 181 for (var i = 0; i < path.length - 1; ++i) { 182 parentNode = this._getFolderNode(parentNode, path[i]); 183 if (!parentNode) 184 return null; 185 } 186 return parentNode; 187 }, 188 189 /** 190 * @param {WebInspector.UISourceCode} uiSourceCode 191 * @return {WebInspector.NavigatorTreeNode} 192 */ 193 _getOrCreateUISourceCodeParentNode: function(uiSourceCode) 194 { 195 var projectNode = this._getOrCreateProjectNode(uiSourceCode.project()); 196 if (!projectNode) 197 return null; 198 var path = uiSourceCode.path(); 199 var parentNode = projectNode; 200 for (var i = 0; i < path.length - 1; ++i) { 201 parentNode = this._getOrCreateFolderNode(parentNode, path[i]); 202 if (!parentNode) 203 return null; 204 } 205 return parentNode; 206 }, 207 208 /** 209 * @param {WebInspector.UISourceCode} uiSourceCode 210 * @param {boolean=} select 211 */ 212 revealUISourceCode: function(uiSourceCode, select) 213 { 214 var node = this._uiSourceCodeNodes[uiSourceCode.uri()]; 215 if (!node) 216 return null; 217 if (this._scriptsTree.selectedTreeElement) 218 this._scriptsTree.selectedTreeElement.deselect(); 219 this._lastSelectedUISourceCode = uiSourceCode; 220 node.reveal(select); 221 }, 222 223 /** 224 * @param {WebInspector.UISourceCode} uiSourceCode 225 * @param {boolean} focusSource 226 */ 227 _scriptSelected: function(uiSourceCode, focusSource) 228 { 229 this._lastSelectedUISourceCode = uiSourceCode; 230 var data = { uiSourceCode: uiSourceCode, focusSource: focusSource}; 231 this.dispatchEventToListeners(WebInspector.NavigatorView.Events.ItemSelected, data); 232 }, 233 234 /** 235 * @param {WebInspector.UISourceCode} uiSourceCode 236 */ 237 removeUISourceCode: function(uiSourceCode) 238 { 239 var parentNode = this._getUISourceCodeParentNode(uiSourceCode); 240 if (!parentNode) 241 return; 242 var node = this._uiSourceCodeNodes[uiSourceCode.uri()]; 243 if (!node) 244 return; 245 delete this._uiSourceCodeNodes[uiSourceCode.uri()] 246 parentNode.removeChild(node); 247 node = parentNode; 248 while (node) { 249 parentNode = node.parent; 250 if (!parentNode || !node.isEmpty()) 251 break; 252 parentNode.removeChild(node); 253 node = parentNode; 254 } 255 }, 256 257 _fileRenamed: function(uiSourceCode, newTitle) 258 { 259 var data = { uiSourceCode: uiSourceCode, name: newTitle }; 260 this.dispatchEventToListeners(WebInspector.NavigatorView.Events.FileRenamed, data); 261 }, 262 263 /** 264 * @param {WebInspector.UISourceCode} uiSourceCode 265 * @param {function(boolean)=} callback 266 */ 267 rename: function(uiSourceCode, callback) 268 { 269 var node = this._uiSourceCodeNodes[uiSourceCode.uri()]; 270 if (!node) 271 return null; 272 node.rename(callback); 273 }, 274 275 reset: function() 276 { 277 for (var uri in this._uiSourceCodeNodes) 278 this._uiSourceCodeNodes[uri].dispose(); 279 280 this._scriptsTree.stopSearch(); 281 this._scriptsTree.removeChildren(); 282 this._uiSourceCodeNodes = {}; 283 this._rootNode.reset(); 284 }, 285 286 handleContextMenu: function(event, uiSourceCode) 287 { 288 var contextMenu = new WebInspector.ContextMenu(event); 289 contextMenu.appendApplicableItems(uiSourceCode); 290 contextMenu.show(); 291 }, 292 293 __proto__: WebInspector.View.prototype 294} 295 296/** 297 * @constructor 298 * @extends {TreeOutline} 299 * @param {Element} treeSearchBoxElement 300 * @param {Element} element 301 */ 302WebInspector.NavigatorTreeOutline = function(treeSearchBoxElement, element) 303{ 304 TreeOutline.call(this, element); 305 this.element = element; 306 307 this._treeSearchBoxElement = treeSearchBoxElement; 308 309 this.comparator = WebInspector.NavigatorTreeOutline._treeElementsCompare; 310 311 this.searchable = true; 312 this.searchInputElement = document.createElement("input"); 313} 314 315WebInspector.NavigatorTreeOutline.Types = { 316 Root: "Root", 317 Domain: "Domain", 318 Folder: "Folder", 319 UISourceCode: "UISourceCode", 320 FileSystem: "FileSystem" 321} 322 323WebInspector.NavigatorTreeOutline._treeElementsCompare = function compare(treeElement1, treeElement2) 324{ 325 // Insert in the alphabetical order, first domains, then folders, then scripts. 326 function typeWeight(treeElement) 327 { 328 var type = treeElement.type(); 329 if (type === WebInspector.NavigatorTreeOutline.Types.Domain) { 330 if (treeElement.titleText === WebInspector.inspectedPageDomain) 331 return 1; 332 return 2; 333 } 334 if (type === WebInspector.NavigatorTreeOutline.Types.FileSystem) 335 return 3; 336 if (type === WebInspector.NavigatorTreeOutline.Types.Folder) 337 return 4; 338 return 5; 339 } 340 341 var typeWeight1 = typeWeight(treeElement1); 342 var typeWeight2 = typeWeight(treeElement2); 343 344 var result; 345 if (typeWeight1 > typeWeight2) 346 result = 1; 347 else if (typeWeight1 < typeWeight2) 348 result = -1; 349 else { 350 var title1 = treeElement1.titleText; 351 var title2 = treeElement2.titleText; 352 result = title1.compareTo(title2); 353 } 354 return result; 355} 356 357WebInspector.NavigatorTreeOutline.prototype = { 358 /** 359 * @return {Array.<WebInspector.UISourceCode>} 360 */ 361 scriptTreeElements: function() 362 { 363 var result = []; 364 if (this.children.length) { 365 for (var treeElement = this.children[0]; treeElement; treeElement = treeElement.traverseNextTreeElement(false, this, true)) { 366 if (treeElement instanceof WebInspector.NavigatorSourceTreeElement) 367 result.push(treeElement.uiSourceCode); 368 } 369 } 370 return result; 371 }, 372 373 searchStarted: function() 374 { 375 this._treeSearchBoxElement.appendChild(this.searchInputElement); 376 this._treeSearchBoxElement.addStyleClass("visible"); 377 }, 378 379 searchFinished: function() 380 { 381 this._treeSearchBoxElement.removeChild(this.searchInputElement); 382 this._treeSearchBoxElement.removeStyleClass("visible"); 383 }, 384 385 __proto__: TreeOutline.prototype 386} 387 388/** 389 * @constructor 390 * @extends {TreeElement} 391 * @param {string} type 392 * @param {string} title 393 * @param {Array.<string>} iconClasses 394 * @param {boolean} hasChildren 395 * @param {boolean=} noIcon 396 */ 397WebInspector.BaseNavigatorTreeElement = function(type, title, iconClasses, hasChildren, noIcon) 398{ 399 this._type = type; 400 TreeElement.call(this, "", null, hasChildren); 401 this._titleText = title; 402 this._iconClasses = iconClasses; 403 this._noIcon = noIcon; 404} 405 406WebInspector.BaseNavigatorTreeElement.prototype = { 407 onattach: function() 408 { 409 this.listItemElement.removeChildren(); 410 if (this._iconClasses) { 411 for (var i = 0; i < this._iconClasses.length; ++i) 412 this.listItemElement.addStyleClass(this._iconClasses[i]); 413 } 414 415 var selectionElement = document.createElement("div"); 416 selectionElement.className = "selection"; 417 this.listItemElement.appendChild(selectionElement); 418 419 if (!this._noIcon) { 420 this.imageElement = document.createElement("img"); 421 this.imageElement.className = "icon"; 422 this.listItemElement.appendChild(this.imageElement); 423 } 424 425 this.titleElement = document.createElement("div"); 426 this.titleElement.className = "base-navigator-tree-element-title"; 427 this._titleTextNode = document.createTextNode(""); 428 this._titleTextNode.textContent = this._titleText; 429 this.titleElement.appendChild(this._titleTextNode); 430 this.listItemElement.appendChild(this.titleElement); 431 }, 432 433 onreveal: function() 434 { 435 if (this.listItemElement) 436 this.listItemElement.scrollIntoViewIfNeeded(true); 437 }, 438 439 /** 440 * @return {string} 441 */ 442 get titleText() 443 { 444 return this._titleText; 445 }, 446 447 set titleText(titleText) 448 { 449 if (this._titleText === titleText) 450 return; 451 this._titleText = titleText || ""; 452 if (this.titleElement) 453 this.titleElement.textContent = this._titleText; 454 }, 455 456 /** 457 * @param {string} searchText 458 */ 459 matchesSearchText: function(searchText) 460 { 461 return this.titleText.match(new RegExp("^" + searchText.escapeForRegExp(), "i")); 462 }, 463 464 /** 465 * @return {string} 466 */ 467 type: function() 468 { 469 return this._type; 470 }, 471 472 __proto__: TreeElement.prototype 473} 474 475/** 476 * @constructor 477 * @extends {WebInspector.BaseNavigatorTreeElement} 478 * @param {string} type 479 * @param {string} title 480 */ 481WebInspector.NavigatorFolderTreeElement = function(type, title) 482{ 483 var iconClass = WebInspector.NavigatorView.iconClassForType(type); 484 WebInspector.BaseNavigatorTreeElement.call(this, type, title, [iconClass], true); 485} 486 487WebInspector.NavigatorFolderTreeElement.prototype = { 488 onpopulate: function() 489 { 490 this._node.populate(); 491 }, 492 493 onattach: function() 494 { 495 WebInspector.BaseNavigatorTreeElement.prototype.onattach.call(this); 496 this.collapse(); 497 }, 498 499 __proto__: WebInspector.BaseNavigatorTreeElement.prototype 500} 501 502/** 503 * @constructor 504 * @extends {WebInspector.BaseNavigatorTreeElement} 505 * @param {WebInspector.NavigatorView} navigatorView 506 * @param {WebInspector.UISourceCode} uiSourceCode 507 * @param {string} title 508 */ 509WebInspector.NavigatorSourceTreeElement = function(navigatorView, uiSourceCode, title) 510{ 511 WebInspector.BaseNavigatorTreeElement.call(this, WebInspector.NavigatorTreeOutline.Types.UISourceCode, title, ["navigator-" + uiSourceCode.contentType().name() + "-tree-item"], false); 512 this._navigatorView = navigatorView; 513 this._uiSourceCode = uiSourceCode; 514 this.tooltip = uiSourceCode.originURL(); 515} 516 517WebInspector.NavigatorSourceTreeElement.prototype = { 518 /** 519 * @return {WebInspector.UISourceCode} 520 */ 521 get uiSourceCode() 522 { 523 return this._uiSourceCode; 524 }, 525 526 onattach: function() 527 { 528 WebInspector.BaseNavigatorTreeElement.prototype.onattach.call(this); 529 this.listItemElement.draggable = true; 530 this.listItemElement.addEventListener("click", this._onclick.bind(this), false); 531 this.listItemElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), false); 532 this.listItemElement.addEventListener("mousedown", this._onmousedown.bind(this), false); 533 this.listItemElement.addEventListener("dragstart", this._ondragstart.bind(this), false); 534 }, 535 536 _onmousedown: function(event) 537 { 538 if (event.which === 1) // Warm-up data for drag'n'drop 539 this._uiSourceCode.requestContent(callback.bind(this)); 540 /** 541 * @param {?string} content 542 * @param {boolean} contentEncoded 543 * @param {string} mimeType 544 */ 545 function callback(content, contentEncoded, mimeType) 546 { 547 this._warmedUpContent = content; 548 } 549 }, 550 551 _ondragstart: function(event) 552 { 553 event.dataTransfer.setData("text/plain", this._warmedUpContent); 554 event.dataTransfer.effectAllowed = "copy"; 555 return true; 556 }, 557 558 onspace: function() 559 { 560 this._navigatorView._scriptSelected(this.uiSourceCode, true); 561 return true; 562 }, 563 564 /** 565 * @param {Event} event 566 */ 567 _onclick: function(event) 568 { 569 this._navigatorView._scriptSelected(this.uiSourceCode, false); 570 }, 571 572 /** 573 * @param {Event} event 574 */ 575 ondblclick: function(event) 576 { 577 var middleClick = event.button === 1; 578 this._navigatorView._scriptSelected(this.uiSourceCode, !middleClick); 579 }, 580 581 onenter: function() 582 { 583 this._navigatorView._scriptSelected(this.uiSourceCode, true); 584 return true; 585 }, 586 587 /** 588 * @param {Event} event 589 */ 590 _handleContextMenuEvent: function(event) 591 { 592 this._navigatorView.handleContextMenu(event, this._uiSourceCode); 593 }, 594 595 __proto__: WebInspector.BaseNavigatorTreeElement.prototype 596} 597 598/** 599 * @constructor 600 * @param {string} id 601 */ 602WebInspector.NavigatorTreeNode = function(id) 603{ 604 this.id = id; 605 this._children = {}; 606} 607 608WebInspector.NavigatorTreeNode.prototype = { 609 /** 610 * @return {TreeElement} 611 */ 612 treeElement: function() { }, 613 614 dispose: function() { }, 615 616 /** 617 * @return {boolean} 618 */ 619 isRoot: function() 620 { 621 return false; 622 }, 623 624 /** 625 * @return {boolean} 626 */ 627 hasChildren: function() 628 { 629 return true; 630 }, 631 632 populate: function() 633 { 634 if (this.isPopulated()) 635 return; 636 if (this.parent) 637 this.parent.populate(); 638 this._populated = true; 639 this.wasPopulated(); 640 }, 641 642 wasPopulated: function() 643 { 644 for (var id in this._children) 645 this.treeElement().appendChild(this._children[id].treeElement()); 646 }, 647 648 didAddChild: function(node) 649 { 650 if (this.isPopulated()) 651 this.treeElement().appendChild(node.treeElement()); 652 }, 653 654 willRemoveChild: function(node) 655 { 656 if (this.isPopulated()) 657 this.treeElement().removeChild(node.treeElement()); 658 }, 659 660 isPopulated: function() 661 { 662 return this._populated; 663 }, 664 665 isEmpty: function() 666 { 667 return this.children().length === 0; 668 }, 669 670 child: function(id) 671 { 672 return this._children[id]; 673 }, 674 675 children: function() 676 { 677 return Object.values(this._children); 678 }, 679 680 appendChild: function(node) 681 { 682 this._children[node.id] = node; 683 node.parent = this; 684 this.didAddChild(node); 685 }, 686 687 removeChild: function(node) 688 { 689 this.willRemoveChild(node); 690 delete this._children[node.id]; 691 delete node.parent; 692 node.dispose(); 693 }, 694 695 reset: function() 696 { 697 this._children = {}; 698 } 699} 700 701/** 702 * @constructor 703 * @extends {WebInspector.NavigatorTreeNode} 704 * @param {WebInspector.NavigatorView} navigatorView 705 */ 706WebInspector.NavigatorRootTreeNode = function(navigatorView) 707{ 708 WebInspector.NavigatorTreeNode.call(this, ""); 709 this._navigatorView = navigatorView; 710} 711 712WebInspector.NavigatorRootTreeNode.prototype = { 713 /** 714 * @return {boolean} 715 */ 716 isRoot: function() 717 { 718 return true; 719 }, 720 721 /** 722 * @return {TreeOutline} 723 */ 724 treeElement: function() 725 { 726 return this._navigatorView._scriptsTree; 727 }, 728 729 wasPopulated: function() 730 { 731 for (var id in this._children) 732 this.treeElement().appendChild(this._children[id].treeElement()); 733 }, 734 735 didAddChild: function(node) 736 { 737 if (this.isPopulated()) 738 this.treeElement().appendChild(node.treeElement()); 739 }, 740 741 willRemoveChild: function(node) 742 { 743 if (this.isPopulated()) 744 this.treeElement().removeChild(node.treeElement()); 745 }, 746 747 __proto__: WebInspector.NavigatorTreeNode.prototype 748} 749 750/** 751 * @constructor 752 * @extends {WebInspector.NavigatorTreeNode} 753 * @param {WebInspector.NavigatorView} navigatorView 754 * @param {WebInspector.UISourceCode} uiSourceCode 755 */ 756WebInspector.NavigatorUISourceCodeTreeNode = function(navigatorView, uiSourceCode) 757{ 758 WebInspector.NavigatorTreeNode.call(this, uiSourceCode.name()); 759 this._navigatorView = navigatorView; 760 this._uiSourceCode = uiSourceCode; 761} 762 763WebInspector.NavigatorUISourceCodeTreeNode.prototype = { 764 /** 765 * @return {WebInspector.UISourceCode} 766 */ 767 uiSourceCode: function() 768 { 769 return this._uiSourceCode; 770 }, 771 772 /** 773 * @return {TreeElement} 774 */ 775 treeElement: function() 776 { 777 if (this._treeElement) 778 return this._treeElement; 779 780 this._treeElement = new WebInspector.NavigatorSourceTreeElement(this._navigatorView, this._uiSourceCode, ""); 781 this.updateTitle(); 782 783 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.TitleChanged, this._titleChanged, this); 784 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this); 785 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this); 786 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.FormattedChanged, this._formattedChanged, this); 787 788 return this._treeElement; 789 }, 790 791 /** 792 * @param {boolean=} ignoreIsDirty 793 */ 794 updateTitle: function(ignoreIsDirty) 795 { 796 if (!this._treeElement) 797 return; 798 799 var titleText = this._uiSourceCode.name().trimEnd(100); 800 if (!titleText) 801 titleText = WebInspector.UIString("(program)"); 802 if (!ignoreIsDirty && this._uiSourceCode.isDirty()) 803 titleText = "*" + titleText; 804 this._treeElement.titleText = titleText; 805 }, 806 807 /** 808 * @return {boolean} 809 */ 810 hasChildren: function() 811 { 812 return false; 813 }, 814 815 dispose: function() 816 { 817 if (!this._treeElement) 818 return; 819 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.TitleChanged, this._titleChanged, this); 820 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this); 821 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this); 822 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.FormattedChanged, this._formattedChanged, this); 823 }, 824 825 _titleChanged: function(event) 826 { 827 this.updateTitle(); 828 }, 829 830 _workingCopyChanged: function(event) 831 { 832 this.updateTitle(); 833 }, 834 835 _workingCopyCommitted: function(event) 836 { 837 this.updateTitle(); 838 }, 839 840 _formattedChanged: function(event) 841 { 842 this.updateTitle(); 843 }, 844 845 /** 846 * @param {boolean=} select 847 */ 848 reveal: function(select) 849 { 850 this.parent.populate(); 851 this.parent.treeElement().expand(); 852 this._treeElement.reveal(); 853 if (select) 854 this._treeElement.select(); 855 }, 856 857 /** 858 * @param {function(boolean)=} callback 859 */ 860 rename: function(callback) 861 { 862 if (!this._treeElement) 863 return; 864 865 // Tree outline should be marked as edited as well as the tree element to prevent search from starting. 866 var treeOutlineElement = this._treeElement.treeOutline.element; 867 WebInspector.markBeingEdited(treeOutlineElement, true); 868 869 function commitHandler(element, newTitle, oldTitle) 870 { 871 if (newTitle && newTitle !== oldTitle) 872 this._navigatorView._fileRenamed(this._uiSourceCode, newTitle); 873 afterEditing.call(this, true); 874 } 875 876 function cancelHandler() 877 { 878 afterEditing.call(this, false); 879 } 880 881 /** 882 * @param {boolean} committed 883 */ 884 function afterEditing(committed) 885 { 886 WebInspector.markBeingEdited(treeOutlineElement, false); 887 this.updateTitle(); 888 if (callback) 889 callback(committed); 890 } 891 892 var editingConfig = new WebInspector.EditingConfig(commitHandler.bind(this), cancelHandler.bind(this)); 893 this.updateTitle(true); 894 WebInspector.startEditing(this._treeElement.titleElement, editingConfig); 895 window.getSelection().setBaseAndExtent(this._treeElement.titleElement, 0, this._treeElement.titleElement, 1); 896 }, 897 898 __proto__: WebInspector.NavigatorTreeNode.prototype 899} 900 901/** 902 * @constructor 903 * @extends {WebInspector.NavigatorTreeNode} 904 * @param {WebInspector.NavigatorView} navigatorView 905 * @param {string} id 906 * @param {string} type 907 * @param {string} title 908 */ 909WebInspector.NavigatorFolderTreeNode = function(navigatorView, id, type, title) 910{ 911 WebInspector.NavigatorTreeNode.call(this, id); 912 this._navigatorView = navigatorView; 913 this._type = type; 914 this._title = title; 915} 916 917WebInspector.NavigatorFolderTreeNode.prototype = { 918 /** 919 * @return {TreeElement} 920 */ 921 treeElement: function() 922 { 923 if (this._treeElement) 924 return this._treeElement; 925 this._treeElement = this._createTreeElement(this._title, this); 926 return this._treeElement; 927 }, 928 929 /** 930 * @return {TreeElement} 931 */ 932 _createTreeElement: function(title, node) 933 { 934 var treeElement = new WebInspector.NavigatorFolderTreeElement(this._type, title); 935 treeElement._node = node; 936 return treeElement; 937 }, 938 939 wasPopulated: function() 940 { 941 if (!this._treeElement || this._treeElement._node !== this) 942 return; 943 this._addChildrenRecursive(); 944 }, 945 946 _addChildrenRecursive: function() 947 { 948 for (var id in this._children) { 949 var child = this._children[id]; 950 this.didAddChild(child); 951 if (child instanceof WebInspector.NavigatorFolderTreeNode) 952 child._addChildrenRecursive(); 953 } 954 }, 955 956 _shouldMerge: function(node) 957 { 958 return this._type !== WebInspector.NavigatorTreeOutline.Types.Domain && node instanceof WebInspector.NavigatorFolderTreeNode; 959 }, 960 961 didAddChild: function(node) 962 { 963 function titleForNode(node) 964 { 965 return node._title; 966 } 967 968 if (!this._treeElement) 969 return; 970 971 var children = this.children(); 972 973 if (children.length === 1 && this._shouldMerge(node)) { 974 node._isMerged = true; 975 this._treeElement.titleText = this._treeElement.titleText + "/" + node._title; 976 node._treeElement = this._treeElement; 977 this._treeElement._node = node; 978 return; 979 } 980 981 var oldNode; 982 if (children.length === 2) 983 oldNode = children[0] !== node ? children[0] : children[1]; 984 if (oldNode && oldNode._isMerged) { 985 delete oldNode._isMerged; 986 var mergedToNodes = []; 987 mergedToNodes.push(this); 988 var treeNode = this; 989 while (treeNode._isMerged) { 990 treeNode = treeNode.parent; 991 mergedToNodes.push(treeNode); 992 } 993 mergedToNodes.reverse(); 994 var titleText = mergedToNodes.map(titleForNode).join("/"); 995 996 var nodes = []; 997 treeNode = oldNode; 998 do { 999 nodes.push(treeNode); 1000 children = treeNode.children(); 1001 treeNode = children.length === 1 ? children[0] : null; 1002 } while (treeNode && treeNode._isMerged); 1003 1004 if (!this.isPopulated()) { 1005 this._treeElement.titleText = titleText; 1006 this._treeElement._node = this; 1007 for (var i = 0; i < nodes.length; ++i) { 1008 delete nodes[i]._treeElement; 1009 delete nodes[i]._isMerged; 1010 } 1011 return; 1012 } 1013 var oldTreeElement = this._treeElement; 1014 var treeElement = this._createTreeElement(titleText, this); 1015 for (var i = 0; i < mergedToNodes.length; ++i) 1016 mergedToNodes[i]._treeElement = treeElement; 1017 oldTreeElement.parent.appendChild(treeElement); 1018 1019 oldTreeElement._node = nodes[nodes.length - 1]; 1020 oldTreeElement.titleText = nodes.map(titleForNode).join("/"); 1021 oldTreeElement.parent.removeChild(oldTreeElement); 1022 this._treeElement.appendChild(oldTreeElement); 1023 if (oldTreeElement.expanded) 1024 treeElement.expand(); 1025 } 1026 if (this.isPopulated()) 1027 this._treeElement.appendChild(node.treeElement()); 1028 }, 1029 1030 willRemoveChild: function(node) 1031 { 1032 if (node._isMerged || !this.isPopulated()) 1033 return; 1034 this._treeElement.removeChild(node._treeElement); 1035 }, 1036 1037 __proto__: WebInspector.NavigatorTreeNode.prototype 1038} 1039