• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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    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
64WebInspector.NavigatorView.Events = {
65    ItemSelected: "ItemSelected",
66    ItemSearchStarted: "ItemSearchStarted",
67    ItemRenamingRequested: "ItemRenamingRequested",
68    ItemCreationRequested: "ItemCreationRequested"
69}
70
71WebInspector.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
80WebInspector.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 */
413WebInspector.NavigatorTreeOutline = function(element)
414{
415    TreeOutline.call(this, element);
416    this.element = element;
417
418    this.comparator = WebInspector.NavigatorTreeOutline._treeElementsCompare;
419}
420
421WebInspector.NavigatorTreeOutline.Types = {
422    Root: "Root",
423    Domain: "Domain",
424    Folder: "Folder",
425    UISourceCode: "UISourceCode",
426    FileSystem: "FileSystem"
427}
428
429WebInspector.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
463WebInspector.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 */
491WebInspector.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
500WebInspector.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 */
577WebInspector.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
584WebInspector.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 */
633WebInspector.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
641WebInspector.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 */
779WebInspector.NavigatorTreeNode = function(id)
780{
781    this.id = id;
782    /** @type {!StringMap.<!WebInspector.NavigatorTreeNode>} */
783    this._children = new StringMap();
784}
785
786WebInspector.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 */
910WebInspector.NavigatorRootTreeNode = function(navigatorView)
911{
912    WebInspector.NavigatorTreeNode.call(this, "");
913    this._navigatorView = navigatorView;
914}
915
916WebInspector.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 */
942WebInspector.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
950WebInspector.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 */
1131WebInspector.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
1141WebInspector.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