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.classList.add("outline-disclosure"); 44 scriptsOutlineElement.classList.add("navigator"); 45 scriptsOutlineElement.appendChild(scriptsTreeElement); 46 47 this.element.classList.add("fill"); 48 this.element.classList.add("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 _sourceSelected: 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 sourceDeleted: function(uiSourceCode) 188 { 189 }, 190 191 /** 192 * @param {!WebInspector.UISourceCode} uiSourceCode 193 */ 194 removeUISourceCode: function(uiSourceCode) 195 { 196 var node = this._uiSourceCodeNodes.get(uiSourceCode); 197 if (!node) 198 return; 199 200 var projectNode = this._projectNode(uiSourceCode.project()); 201 var subfolderNodes = this._subfolderNodes.get(projectNode); 202 var parentNode = node.parent; 203 this._uiSourceCodeNodes.remove(uiSourceCode); 204 parentNode.removeChild(node); 205 node = parentNode; 206 207 while (node) { 208 parentNode = node.parent; 209 if (!parentNode || !node.isEmpty()) 210 break; 211 if (subfolderNodes) 212 subfolderNodes.remove(node._folderPath); 213 parentNode.removeChild(node); 214 node = parentNode; 215 } 216 }, 217 218 /** 219 * @param {!WebInspector.UISourceCode} uiSourceCode 220 */ 221 updateIcon: function(uiSourceCode) 222 { 223 var node = this._uiSourceCodeNodes.get(uiSourceCode); 224 node.updateIcon(); 225 }, 226 227 /** 228 * @param {!WebInspector.UISourceCode} uiSourceCode 229 */ 230 requestRename: function(uiSourceCode) 231 { 232 this.dispatchEventToListeners(WebInspector.SourcesNavigator.Events.ItemRenamingRequested, uiSourceCode); 233 }, 234 235 /** 236 * @param {!WebInspector.UISourceCode} uiSourceCode 237 * @param {function(boolean)=} callback 238 */ 239 rename: function(uiSourceCode, callback) 240 { 241 var node = this._uiSourceCodeNodes.get(uiSourceCode); 242 if (!node) 243 return null; 244 node.rename(callback); 245 }, 246 247 reset: function() 248 { 249 var nodes = this._uiSourceCodeNodes.values(); 250 for (var i = 0; i < nodes.length; ++i) 251 nodes[i].dispose(); 252 253 this._scriptsTree.removeChildren(); 254 this._uiSourceCodeNodes.clear(); 255 this._subfolderNodes.clear(); 256 this._rootNode.reset(); 257 }, 258 259 /** 260 * @param {?Event} event 261 */ 262 handleContextMenu: function(event) 263 { 264 var contextMenu = new WebInspector.ContextMenu(event); 265 this._appendAddFolderItem(contextMenu); 266 contextMenu.show(); 267 }, 268 269 /** 270 * @param {!WebInspector.ContextMenu} contextMenu 271 */ 272 _appendAddFolderItem: function(contextMenu) 273 { 274 function addFolder() 275 { 276 WebInspector.isolatedFileSystemManager.addFileSystem(); 277 } 278 279 var addFolderLabel = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add folder to workspace" : "Add Folder to Workspace"); 280 contextMenu.appendItem(addFolderLabel, addFolder); 281 }, 282 283 /** 284 * @param {!WebInspector.Project} project 285 * @param {string} path 286 */ 287 _handleContextMenuRefresh: function(project, path) 288 { 289 project.refresh(path); 290 }, 291 292 /** 293 * @param {!WebInspector.Project} project 294 * @param {string} path 295 * @param {!WebInspector.UISourceCode=} uiSourceCode 296 */ 297 _handleContextMenuCreate: function(project, path, uiSourceCode) 298 { 299 var data = {}; 300 data.project = project; 301 data.path = path; 302 data.uiSourceCode = uiSourceCode; 303 this.dispatchEventToListeners(WebInspector.NavigatorView.Events.ItemCreationRequested, data); 304 }, 305 306 /** 307 * @param {!WebInspector.Project} project 308 * @param {string} path 309 */ 310 _handleContextMenuExclude: function(project, path) 311 { 312 var shouldExclude = window.confirm(WebInspector.UIString("Are you sure you want to exclude this folder?")); 313 if (shouldExclude) { 314 WebInspector.startBatchUpdate(); 315 project.excludeFolder(path); 316 WebInspector.endBatchUpdate(); 317 } 318 }, 319 320 /** 321 * @param {!WebInspector.UISourceCode} uiSourceCode 322 */ 323 _handleContextMenuDelete: function(uiSourceCode) 324 { 325 var shouldDelete = window.confirm(WebInspector.UIString("Are you sure you want to delete this file?")); 326 if (shouldDelete) 327 uiSourceCode.project().deleteFile(uiSourceCode.path()); 328 }, 329 330 /** 331 * @param {!Event} event 332 * @param {!WebInspector.UISourceCode} uiSourceCode 333 */ 334 handleFileContextMenu: function(event, uiSourceCode) 335 { 336 var contextMenu = new WebInspector.ContextMenu(event); 337 contextMenu.appendApplicableItems(uiSourceCode); 338 contextMenu.appendSeparator(); 339 340 var project = uiSourceCode.project(); 341 var path = uiSourceCode.parentPath(); 342 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Refresh parent" : "Refresh Parent"), this._handleContextMenuRefresh.bind(this, project, path)); 343 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Duplicate file" : "Duplicate File"), this._handleContextMenuCreate.bind(this, project, path, uiSourceCode)); 344 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Exclude parent folder" : "Exclude Parent Folder"), this._handleContextMenuExclude.bind(this, project, path)); 345 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Delete file" : "Delete File"), this._handleContextMenuDelete.bind(this, uiSourceCode)); 346 contextMenu.appendSeparator(); 347 this._appendAddFolderItem(contextMenu); 348 contextMenu.show(); 349 }, 350 351 /** 352 * @param {!Event} event 353 * @param {!WebInspector.NavigatorFolderTreeNode} node 354 */ 355 handleFolderContextMenu: function(event, node) 356 { 357 var contextMenu = new WebInspector.ContextMenu(event); 358 var path = "/"; 359 var projectNode = node; 360 while (projectNode.parent !== this._rootNode) { 361 path = "/" + projectNode.id + path; 362 projectNode = projectNode.parent; 363 } 364 365 var project = projectNode._project; 366 367 if (project.type() === WebInspector.projectTypes.FileSystem) { 368 contextMenu.appendItem(WebInspector.UIString("Refresh"), this._handleContextMenuRefresh.bind(this, project, path)); 369 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "New file" : "New File"), this._handleContextMenuCreate.bind(this, project, path)); 370 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Exclude folder" : "Exclude Folder"), this._handleContextMenuExclude.bind(this, project, path)); 371 } 372 contextMenu.appendSeparator(); 373 this._appendAddFolderItem(contextMenu); 374 375 function removeFolder() 376 { 377 var shouldRemove = window.confirm(WebInspector.UIString("Are you sure you want to remove this folder?")); 378 if (shouldRemove) 379 project.remove(); 380 } 381 382 if (project.type() === WebInspector.projectTypes.FileSystem && node === projectNode) { 383 var removeFolderLabel = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Remove folder from workspace" : "Remove Folder from Workspace"); 384 contextMenu.appendItem(removeFolderLabel, removeFolder); 385 } 386 387 contextMenu.show(); 388 }, 389 390 /** 391 * @param {?Event} event 392 */ 393 _treeKeyPress: function(event) 394 { 395 if (WebInspector.isBeingEdited(this._scriptsTree.childrenListElement)) 396 return; 397 398 var searchText = String.fromCharCode(event.charCode); 399 if (searchText.trim() !== searchText) 400 return; 401 this.dispatchEventToListeners(WebInspector.NavigatorView.Events.ItemSearchStarted, searchText); 402 event.consume(true); 403 }, 404 405 __proto__: WebInspector.View.prototype 406 } 407 408 /** 409 * @constructor 410 * @extends {TreeOutline} 411 * @param {!Element} element 412 */ 413 WebInspector.NavigatorTreeOutline = function(element) 414 { 415 TreeOutline.call(this, element); 416 this.element = element; 417 418 this.comparator = WebInspector.NavigatorTreeOutline._treeElementsCompare; 419 } 420 421 WebInspector.NavigatorTreeOutline.Types = { 422 Root: "Root", 423 Domain: "Domain", 424 Folder: "Folder", 425 UISourceCode: "UISourceCode", 426 FileSystem: "FileSystem" 427 } 428 429 WebInspector.NavigatorTreeOutline._treeElementsCompare = function compare(treeElement1, treeElement2) 430 { 431 // Insert in the alphabetical order, first domains, then folders, then scripts. 432 function typeWeight(treeElement) 433 { 434 var type = treeElement.type(); 435 if (type === WebInspector.NavigatorTreeOutline.Types.Domain) { 436 if (treeElement.titleText === WebInspector.inspectedPageDomain) 437 return 1; 438 return 2; 439 } 440 if (type === WebInspector.NavigatorTreeOutline.Types.FileSystem) 441 return 3; 442 if (type === WebInspector.NavigatorTreeOutline.Types.Folder) 443 return 4; 444 return 5; 445 } 446 447 var typeWeight1 = typeWeight(treeElement1); 448 var typeWeight2 = typeWeight(treeElement2); 449 450 var result; 451 if (typeWeight1 > typeWeight2) 452 result = 1; 453 else if (typeWeight1 < typeWeight2) 454 result = -1; 455 else { 456 var title1 = treeElement1.titleText; 457 var title2 = treeElement2.titleText; 458 result = title1.compareTo(title2); 459 } 460 return result; 461 } 462 463 WebInspector.NavigatorTreeOutline.prototype = { 464 /** 465 * @return {!Array.<!WebInspector.UISourceCode>} 466 */ 467 scriptTreeElements: function() 468 { 469 var result = []; 470 if (this.children.length) { 471 for (var treeElement = this.children[0]; treeElement; treeElement = treeElement.traverseNextTreeElement(false, this, true)) { 472 if (treeElement instanceof WebInspector.NavigatorSourceTreeElement) 473 result.push(treeElement.uiSourceCode); 474 } 475 } 476 return result; 477 }, 478 479 __proto__: TreeOutline.prototype 480 } 481 482 /** 483 * @constructor 484 * @extends {TreeElement} 485 * @param {string} type 486 * @param {string} title 487 * @param {!Array.<string>} iconClasses 488 * @param {boolean} hasChildren 489 * @param {boolean=} noIcon 490 */ 491 WebInspector.BaseNavigatorTreeElement = function(type, title, iconClasses, hasChildren, noIcon) 492 { 493 this._type = type; 494 TreeElement.call(this, "", null, hasChildren); 495 this._titleText = title; 496 this._iconClasses = iconClasses; 497 this._noIcon = noIcon; 498 } 499 500 WebInspector.BaseNavigatorTreeElement.prototype = { 501 onattach: function() 502 { 503 this.listItemElement.removeChildren(); 504 if (this._iconClasses) { 505 for (var i = 0; i < this._iconClasses.length; ++i) 506 this.listItemElement.classList.add(this._iconClasses[i]); 507 } 508 509 var selectionElement = document.createElement("div"); 510 selectionElement.className = "selection"; 511 this.listItemElement.appendChild(selectionElement); 512 513 if (!this._noIcon) { 514 this.imageElement = document.createElement("img"); 515 this.imageElement.className = "icon"; 516 this.listItemElement.appendChild(this.imageElement); 517 } 518 519 this.titleElement = document.createElement("div"); 520 this.titleElement.className = "base-navigator-tree-element-title"; 521 this._titleTextNode = document.createTextNode(""); 522 this._titleTextNode.textContent = this._titleText; 523 this.titleElement.appendChild(this._titleTextNode); 524 this.listItemElement.appendChild(this.titleElement); 525 }, 526 527 updateIconClasses: function(iconClasses) 528 { 529 for (var i = 0; i < this._iconClasses.length; ++i) 530 this.listItemElement.classList.remove(this._iconClasses[i]); 531 this._iconClasses = iconClasses; 532 for (var i = 0; i < this._iconClasses.length; ++i) 533 this.listItemElement.classList.add(this._iconClasses[i]); 534 }, 535 536 onreveal: function() 537 { 538 if (this.listItemElement) 539 this.listItemElement.scrollIntoViewIfNeeded(true); 540 }, 541 542 /** 543 * @return {string} 544 */ 545 get titleText() 546 { 547 return this._titleText; 548 }, 549 550 set titleText(titleText) 551 { 552 if (this._titleText === titleText) 553 return; 554 this._titleText = titleText || ""; 555 if (this.titleElement) 556 this.titleElement.textContent = this._titleText; 557 }, 558 559 /** 560 * @return {string} 561 */ 562 type: function() 563 { 564 return this._type; 565 }, 566 567 __proto__: TreeElement.prototype 568 } 569 570 /** 571 * @constructor 572 * @extends {WebInspector.BaseNavigatorTreeElement} 573 * @param {!WebInspector.NavigatorView} navigatorView 574 * @param {string} type 575 * @param {string} title 576 */ 577 WebInspector.NavigatorFolderTreeElement = function(navigatorView, type, title) 578 { 579 var iconClass = WebInspector.NavigatorView.iconClassForType(type); 580 WebInspector.BaseNavigatorTreeElement.call(this, type, title, [iconClass], true); 581 this._navigatorView = navigatorView; 582 } 583 584 WebInspector.NavigatorFolderTreeElement.prototype = { 585 onpopulate: function() 586 { 587 this._node.populate(); 588 }, 589 590 onattach: function() 591 { 592 WebInspector.BaseNavigatorTreeElement.prototype.onattach.call(this); 593 this.collapse(); 594 this.listItemElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), false); 595 }, 596 597 /** 598 * @param {!WebInspector.NavigatorFolderTreeNode} node 599 */ 600 setNode: function(node) 601 { 602 this._node = node; 603 var paths = []; 604 while (node && !node.isRoot()) { 605 paths.push(node._title); 606 node = node.parent; 607 } 608 paths.reverse(); 609 this.tooltip = paths.join("/"); 610 }, 611 612 /** 613 * @param {?Event} event 614 */ 615 _handleContextMenuEvent: function(event) 616 { 617 if (!this._node) 618 return; 619 this.select(); 620 this._navigatorView.handleFolderContextMenu(/** @type {!Event} */ (event), this._node); 621 }, 622 623 __proto__: WebInspector.BaseNavigatorTreeElement.prototype 624 } 625 626 /** 627 * @constructor 628 * @extends {WebInspector.BaseNavigatorTreeElement} 629 * @param {!WebInspector.NavigatorView} navigatorView 630 * @param {!WebInspector.UISourceCode} uiSourceCode 631 * @param {string} title 632 */ 633 WebInspector.NavigatorSourceTreeElement = function(navigatorView, uiSourceCode, title) 634 { 635 this._navigatorView = navigatorView; 636 this._uiSourceCode = uiSourceCode; 637 WebInspector.BaseNavigatorTreeElement.call(this, WebInspector.NavigatorTreeOutline.Types.UISourceCode, title, this._calculateIconClasses(), false); 638 this.tooltip = uiSourceCode.originURL(); 639 } 640 641 WebInspector.NavigatorSourceTreeElement.prototype = { 642 /** 643 * @return {!WebInspector.UISourceCode} 644 */ 645 get uiSourceCode() 646 { 647 return this._uiSourceCode; 648 }, 649 650 /** 651 * @return {!Array.<string>} 652 */ 653 _calculateIconClasses: function() 654 { 655 return ["navigator-" + this._uiSourceCode.contentType().name() + "-tree-item"]; 656 }, 657 658 updateIcon: function() 659 { 660 this.updateIconClasses(this._calculateIconClasses()); 661 }, 662 663 onattach: function() 664 { 665 WebInspector.BaseNavigatorTreeElement.prototype.onattach.call(this); 666 this.listItemElement.draggable = true; 667 this.listItemElement.addEventListener("click", this._onclick.bind(this), false); 668 this.listItemElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), false); 669 this.listItemElement.addEventListener("mousedown", this._onmousedown.bind(this), false); 670 this.listItemElement.addEventListener("dragstart", this._ondragstart.bind(this), false); 671 }, 672 673 _onmousedown: function(event) 674 { 675 if (event.which === 1) // Warm-up data for drag'n'drop 676 this._uiSourceCode.requestContent(callback.bind(this)); 677 /** 678 * @param {?string} content 679 * @this {WebInspector.NavigatorSourceTreeElement} 680 */ 681 function callback(content) 682 { 683 this._warmedUpContent = content; 684 } 685 }, 686 687 _shouldRenameOnMouseDown: function() 688 { 689 if (!this._uiSourceCode.canRename()) 690 return false; 691 var isSelected = this === this.treeOutline.selectedTreeElement; 692 var isFocused = this.treeOutline.childrenListElement.isSelfOrAncestor(document.activeElement); 693 return isSelected && isFocused && !WebInspector.isBeingEdited(this.treeOutline.element); 694 }, 695 696 selectOnMouseDown: function(event) 697 { 698 if (event.which !== 1 || !this._shouldRenameOnMouseDown()) { 699 TreeElement.prototype.selectOnMouseDown.call(this, event); 700 return; 701 } 702 setTimeout(rename.bind(this), 300); 703 704 /** 705 * @this {WebInspector.NavigatorSourceTreeElement} 706 */ 707 function rename() 708 { 709 if (this._shouldRenameOnMouseDown()) 710 this._navigatorView.requestRename(this._uiSourceCode); 711 } 712 }, 713 714 _ondragstart: function(event) 715 { 716 event.dataTransfer.setData("text/plain", this._warmedUpContent); 717 event.dataTransfer.effectAllowed = "copy"; 718 return true; 719 }, 720 721 onspace: function() 722 { 723 this._navigatorView._sourceSelected(this.uiSourceCode, true); 724 return true; 725 }, 726 727 /** 728 * @param {!Event} event 729 */ 730 _onclick: function(event) 731 { 732 this._navigatorView._sourceSelected(this.uiSourceCode, false); 733 }, 734 735 /** 736 * @override 737 */ 738 ondblclick: function(event) 739 { 740 var middleClick = event.button === 1; 741 this._navigatorView._sourceSelected(this.uiSourceCode, !middleClick); 742 return false; 743 }, 744 745 /** 746 * @override 747 */ 748 onenter: function() 749 { 750 this._navigatorView._sourceSelected(this.uiSourceCode, true); 751 return true; 752 }, 753 754 /** 755 * @override 756 */ 757 ondelete: function() 758 { 759 this._navigatorView.sourceDeleted(this.uiSourceCode); 760 return true; 761 }, 762 763 /** 764 * @param {!Event} event 765 */ 766 _handleContextMenuEvent: function(event) 767 { 768 this.select(); 769 this._navigatorView.handleFileContextMenu(event, this._uiSourceCode); 770 }, 771 772 __proto__: WebInspector.BaseNavigatorTreeElement.prototype 773 } 774 775 /** 776 * @constructor 777 * @param {string} id 778 */ 779 WebInspector.NavigatorTreeNode = function(id) 780 { 781 this.id = id; 782 /** @type {!StringMap.<!WebInspector.NavigatorTreeNode>} */ 783 this._children = new StringMap(); 784 } 785 786 WebInspector.NavigatorTreeNode.prototype = { 787 /** 788 * @return {!TreeElement} 789 */ 790 treeElement: function() { }, 791 792 dispose: function() { }, 793 794 /** 795 * @return {boolean} 796 */ 797 isRoot: function() 798 { 799 return false; 800 }, 801 802 /** 803 * @return {boolean} 804 */ 805 hasChildren: function() 806 { 807 return true; 808 }, 809 810 populate: function() 811 { 812 if (this.isPopulated()) 813 return; 814 if (this.parent) 815 this.parent.populate(); 816 this._populated = true; 817 this.wasPopulated(); 818 }, 819 820 wasPopulated: function() 821 { 822 var children = this.children(); 823 for (var i = 0; i < children.length; ++i) 824 this.treeElement().appendChild(children[i].treeElement()); 825 }, 826 827 /** 828 * @param {!WebInspector.NavigatorTreeNode} node 829 */ 830 didAddChild: function(node) 831 { 832 if (this.isPopulated()) 833 this.treeElement().appendChild(node.treeElement()); 834 }, 835 836 /** 837 * @param {!WebInspector.NavigatorTreeNode} node 838 */ 839 willRemoveChild: function(node) 840 { 841 if (this.isPopulated()) 842 this.treeElement().removeChild(node.treeElement()); 843 }, 844 845 /** 846 * @return {boolean} 847 */ 848 isPopulated: function() 849 { 850 return this._populated; 851 }, 852 853 /** 854 * @return {boolean} 855 */ 856 isEmpty: function() 857 { 858 return !this._children.size(); 859 }, 860 861 /** 862 * @param {string} id 863 * @return {!WebInspector.NavigatorTreeNode} 864 */ 865 child: function(id) 866 { 867 return this._children.get(id); 868 }, 869 870 /** 871 * @return {!Array.<!WebInspector.NavigatorTreeNode>} 872 */ 873 children: function() 874 { 875 return this._children.values(); 876 }, 877 878 /** 879 * @param {!WebInspector.NavigatorTreeNode} node 880 */ 881 appendChild: function(node) 882 { 883 this._children.put(node.id, node); 884 node.parent = this; 885 this.didAddChild(node); 886 }, 887 888 /** 889 * @param {!WebInspector.NavigatorTreeNode} node 890 */ 891 removeChild: function(node) 892 { 893 this.willRemoveChild(node); 894 this._children.remove(node.id); 895 delete node.parent; 896 node.dispose(); 897 }, 898 899 reset: function() 900 { 901 this._children.clear(); 902 } 903 } 904 905 /** 906 * @constructor 907 * @extends {WebInspector.NavigatorTreeNode} 908 * @param {!WebInspector.NavigatorView} navigatorView 909 */ 910 WebInspector.NavigatorRootTreeNode = function(navigatorView) 911 { 912 WebInspector.NavigatorTreeNode.call(this, ""); 913 this._navigatorView = navigatorView; 914 } 915 916 WebInspector.NavigatorRootTreeNode.prototype = { 917 /** 918 * @return {boolean} 919 */ 920 isRoot: function() 921 { 922 return true; 923 }, 924 925 /** 926 * @return {!TreeOutline} 927 */ 928 treeElement: function() 929 { 930 return this._navigatorView._scriptsTree; 931 }, 932 933 __proto__: WebInspector.NavigatorTreeNode.prototype 934 } 935 936 /** 937 * @constructor 938 * @extends {WebInspector.NavigatorTreeNode} 939 * @param {!WebInspector.NavigatorView} navigatorView 940 * @param {!WebInspector.UISourceCode} uiSourceCode 941 */ 942 WebInspector.NavigatorUISourceCodeTreeNode = function(navigatorView, uiSourceCode) 943 { 944 WebInspector.NavigatorTreeNode.call(this, uiSourceCode.name()); 945 this._navigatorView = navigatorView; 946 this._uiSourceCode = uiSourceCode; 947 this._treeElement = null; 948 } 949 950 WebInspector.NavigatorUISourceCodeTreeNode.prototype = { 951 /** 952 * @return {!WebInspector.UISourceCode} 953 */ 954 uiSourceCode: function() 955 { 956 return this._uiSourceCode; 957 }, 958 959 updateIcon: function() 960 { 961 if (this._treeElement) 962 this._treeElement.updateIcon(); 963 }, 964 965 /** 966 * @return {!TreeElement} 967 */ 968 treeElement: function() 969 { 970 if (this._treeElement) 971 return this._treeElement; 972 973 this._treeElement = new WebInspector.NavigatorSourceTreeElement(this._navigatorView, this._uiSourceCode, ""); 974 this.updateTitle(); 975 976 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.TitleChanged, this._titleChanged, this); 977 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this); 978 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this); 979 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.FormattedChanged, this._formattedChanged, this); 980 981 return this._treeElement; 982 }, 983 984 /** 985 * @param {boolean=} ignoreIsDirty 986 */ 987 updateTitle: function(ignoreIsDirty) 988 { 989 if (!this._treeElement) 990 return; 991 992 var titleText = this._uiSourceCode.displayName(); 993 if (!ignoreIsDirty && (this._uiSourceCode.isDirty() || this._uiSourceCode.hasUnsavedCommittedChanges())) 994 titleText = "*" + titleText; 995 this._treeElement.titleText = titleText; 996 }, 997 998 /** 999 * @return {boolean} 1000 */ 1001 hasChildren: function() 1002 { 1003 return false; 1004 }, 1005 1006 dispose: function() 1007 { 1008 if (!this._treeElement) 1009 return; 1010 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.TitleChanged, this._titleChanged, this); 1011 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this); 1012 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this); 1013 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.FormattedChanged, this._formattedChanged, this); 1014 }, 1015 1016 _titleChanged: function(event) 1017 { 1018 this.updateTitle(); 1019 }, 1020 1021 _workingCopyChanged: function(event) 1022 { 1023 this.updateTitle(); 1024 }, 1025 1026 _workingCopyCommitted: function(event) 1027 { 1028 this.updateTitle(); 1029 }, 1030 1031 _formattedChanged: function(event) 1032 { 1033 this.updateTitle(); 1034 }, 1035 1036 /** 1037 * @param {boolean=} select 1038 */ 1039 reveal: function(select) 1040 { 1041 this.parent.populate(); 1042 this.parent.treeElement().expand(); 1043 this._treeElement.reveal(); 1044 if (select) 1045 this._treeElement.select(); 1046 }, 1047 1048 /** 1049 * @param {function(boolean)=} callback 1050 */ 1051 rename: function(callback) 1052 { 1053 if (!this._treeElement) 1054 return; 1055 1056 // Tree outline should be marked as edited as well as the tree element to prevent search from starting. 1057 var treeOutlineElement = this._treeElement.treeOutline.element; 1058 WebInspector.markBeingEdited(treeOutlineElement, true); 1059 1060 /** 1061 * @param {!Element} element 1062 * @param {string} newTitle 1063 * @param {string} oldTitle 1064 * @this {WebInspector.NavigatorUISourceCodeTreeNode} 1065 */ 1066 function commitHandler(element, newTitle, oldTitle) 1067 { 1068 if (newTitle !== oldTitle) { 1069 this._treeElement.titleText = newTitle; 1070 this._uiSourceCode.rename(newTitle, renameCallback.bind(this)); 1071 return; 1072 } 1073 afterEditing.call(this, true); 1074 } 1075 1076 /** 1077 * @param {boolean} success 1078 * @this {WebInspector.NavigatorUISourceCodeTreeNode} 1079 */ 1080 function renameCallback(success) 1081 { 1082 if (!success) { 1083 WebInspector.markBeingEdited(treeOutlineElement, false); 1084 this.updateTitle(); 1085 this.rename(callback); 1086 return; 1087 } 1088 afterEditing.call(this, true); 1089 } 1090 1091 /** 1092 * @this {WebInspector.NavigatorUISourceCodeTreeNode} 1093 */ 1094 function cancelHandler() 1095 { 1096 afterEditing.call(this, false); 1097 } 1098 1099 /** 1100 * @param {boolean} committed 1101 * @this {WebInspector.NavigatorUISourceCodeTreeNode} 1102 */ 1103 function afterEditing(committed) 1104 { 1105 WebInspector.markBeingEdited(treeOutlineElement, false); 1106 this.updateTitle(); 1107 this._treeElement.treeOutline.childrenListElement.focus(); 1108 if (callback) 1109 callback(committed); 1110 } 1111 1112 var editingConfig = new WebInspector.EditingConfig(commitHandler.bind(this), cancelHandler.bind(this)); 1113 this.updateTitle(true); 1114 WebInspector.startEditing(this._treeElement.titleElement, editingConfig); 1115 window.getSelection().setBaseAndExtent(this._treeElement.titleElement, 0, this._treeElement.titleElement, 1); 1116 }, 1117 1118 __proto__: WebInspector.NavigatorTreeNode.prototype 1119 } 1120 1121 /** 1122 * @constructor 1123 * @extends {WebInspector.NavigatorTreeNode} 1124 * @param {!WebInspector.NavigatorView} navigatorView 1125 * @param {?WebInspector.Project} project 1126 * @param {string} id 1127 * @param {string} type 1128 * @param {string} folderPath 1129 * @param {string} title 1130 */ 1131 WebInspector.NavigatorFolderTreeNode = function(navigatorView, project, id, type, folderPath, title) 1132 { 1133 WebInspector.NavigatorTreeNode.call(this, id); 1134 this._navigatorView = navigatorView; 1135 this._project = project; 1136 this._type = type; 1137 this._folderPath = folderPath; 1138 this._title = title; 1139 } 1140 1141 WebInspector.NavigatorFolderTreeNode.prototype = { 1142 /** 1143 * @return {!TreeElement} 1144 */ 1145 treeElement: function() 1146 { 1147 if (this._treeElement) 1148 return this._treeElement; 1149 this._treeElement = this._createTreeElement(this._title, this); 1150 return this._treeElement; 1151 }, 1152 1153 /** 1154 * @return {!TreeElement} 1155 */ 1156 _createTreeElement: function(title, node) 1157 { 1158 var treeElement = new WebInspector.NavigatorFolderTreeElement(this._navigatorView, this._type, title); 1159 treeElement.setNode(node); 1160 return treeElement; 1161 }, 1162 1163 wasPopulated: function() 1164 { 1165 if (!this._treeElement || this._treeElement._node !== this) 1166 return; 1167 this._addChildrenRecursive(); 1168 }, 1169 1170 _addChildrenRecursive: function() 1171 { 1172 var children = this.children(); 1173 for (var i = 0; i < children.length; ++i) { 1174 var child = children[i]; 1175 this.didAddChild(child); 1176 if (child instanceof WebInspector.NavigatorFolderTreeNode) 1177 child._addChildrenRecursive(); 1178 } 1179 }, 1180 1181 _shouldMerge: function(node) 1182 { 1183 return this._type !== WebInspector.NavigatorTreeOutline.Types.Domain && node instanceof WebInspector.NavigatorFolderTreeNode; 1184 }, 1185 1186 didAddChild: function(node) 1187 { 1188 function titleForNode(node) 1189 { 1190 return node._title; 1191 } 1192 1193 if (!this._treeElement) 1194 return; 1195 1196 var children = this.children(); 1197 1198 if (children.length === 1 && this._shouldMerge(node)) { 1199 node._isMerged = true; 1200 this._treeElement.titleText = this._treeElement.titleText + "/" + node._title; 1201 node._treeElement = this._treeElement; 1202 this._treeElement.setNode(node); 1203 return; 1204 } 1205 1206 var oldNode; 1207 if (children.length === 2) 1208 oldNode = children[0] !== node ? children[0] : children[1]; 1209 if (oldNode && oldNode._isMerged) { 1210 delete oldNode._isMerged; 1211 var mergedToNodes = []; 1212 mergedToNodes.push(this); 1213 var treeNode = this; 1214 while (treeNode._isMerged) { 1215 treeNode = treeNode.parent; 1216 mergedToNodes.push(treeNode); 1217 } 1218 mergedToNodes.reverse(); 1219 var titleText = mergedToNodes.map(titleForNode).join("/"); 1220 1221 var nodes = []; 1222 treeNode = oldNode; 1223 do { 1224 nodes.push(treeNode); 1225 children = treeNode.children(); 1226 treeNode = children.length === 1 ? children[0] : null; 1227 } while (treeNode && treeNode._isMerged); 1228 1229 if (!this.isPopulated()) { 1230 this._treeElement.titleText = titleText; 1231 this._treeElement.setNode(this); 1232 for (var i = 0; i < nodes.length; ++i) { 1233 delete nodes[i]._treeElement; 1234 delete nodes[i]._isMerged; 1235 } 1236 return; 1237 } 1238 var oldTreeElement = this._treeElement; 1239 var treeElement = this._createTreeElement(titleText, this); 1240 for (var i = 0; i < mergedToNodes.length; ++i) 1241 mergedToNodes[i]._treeElement = treeElement; 1242 oldTreeElement.parent.appendChild(treeElement); 1243 1244 oldTreeElement.setNode(nodes[nodes.length - 1]); 1245 oldTreeElement.titleText = nodes.map(titleForNode).join("/"); 1246 oldTreeElement.parent.removeChild(oldTreeElement); 1247 this._treeElement.appendChild(oldTreeElement); 1248 if (oldTreeElement.expanded) 1249 treeElement.expand(); 1250 } 1251 if (this.isPopulated()) 1252 this._treeElement.appendChild(node.treeElement()); 1253 }, 1254 1255 willRemoveChild: function(node) 1256 { 1257 if (node._isMerged || !this.isPopulated()) 1258 return; 1259 this._treeElement.removeChild(node._treeElement); 1260 }, 1261 1262 __proto__: WebInspector.NavigatorTreeNode.prototype 1263 } 1264