0320f14b20cd356e083bb054a627c560dffd1175
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / GeneralTreeElement.js
1 /*
2  * Copyright (C) 2013 Apple 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
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WebInspector.GeneralTreeElement = function(classNames, title, subtitle, representedObject, hasChildren)
27 {
28     TreeElement.call(this, "", representedObject, hasChildren);
29
30     this.classNames = classNames;
31
32     this._tooltipHandledSeparately = false;
33     this._mainTitle = title || "";
34     this._subtitle = subtitle || "";
35     this._status = "";
36 };
37
38 WebInspector.GeneralTreeElement.StyleClassName = "item";
39 WebInspector.GeneralTreeElement.DisclosureButtonStyleClassName = "disclosure-button";
40 WebInspector.GeneralTreeElement.IconElementStyleClassName = "icon";
41 WebInspector.GeneralTreeElement.StatusElementStyleClassName = "status";
42 WebInspector.GeneralTreeElement.TitlesElementStyleClassName = "titles";
43 WebInspector.GeneralTreeElement.MainTitleElementStyleClassName = "title";
44 WebInspector.GeneralTreeElement.SubtitleElementStyleClassName = "subtitle";
45 WebInspector.GeneralTreeElement.NoSubtitleStyleClassName = "no-subtitle";
46 WebInspector.GeneralTreeElement.SmallStyleClassName = "small";
47 WebInspector.GeneralTreeElement.TwoLineStyleClassName = "two-line";
48
49 WebInspector.GeneralTreeElement.Event = {
50     MainTitleDidChange: "general-tree-element-main-title-did-change"
51 };
52
53 WebInspector.GeneralTreeElement.prototype = {
54     constructor: WebInspector.GeneralTreeElement,
55     __proto__: TreeElement.prototype,
56
57     // Public
58
59     get element()
60     {
61         return this._listItemNode;
62     },
63
64     get iconElement()
65     {
66         this._createElementsIfNeeded();
67         return this._iconElement;
68     },
69
70     get titlesElement()
71     {
72         this._createElementsIfNeeded();
73         return this._titlesElement;
74     },
75
76     get mainTitleElement()
77     {
78         this._createElementsIfNeeded();
79         return this._mainTitleElement;
80     },
81
82     get subtitleElement()
83     {
84         this._createElementsIfNeeded();
85         this._createSubtitleElementIfNeeded();
86         return this._subtitleElement;
87     },
88
89     get classNames()
90     {
91         return this._classNames;
92     },
93
94     set classNames(x)
95     {
96         if (this._listItemNode && this._classNames) {
97             for (var i = 0; i < this._classNames.length; ++i)
98                 this._listItemNode.classList.remove(this._classNames[i]);
99         }
100
101         if (typeof x === "string")
102             x = [x];
103
104         this._classNames = x || [];
105
106         if (this._listItemNode) {
107             for (var i = 0; i < this._classNames.length; ++i)
108                 this._listItemNode.classList.add(this._classNames[i]);
109         }
110     },
111
112     addClassName: function(className)
113     {
114         if (this._classNames.contains(className))
115             return;
116
117         this._classNames.push(className);
118
119         if (this._listItemNode)
120             this._listItemNode.classList.add(className);
121     },
122
123     removeClassName: function(className)
124     {
125         if (!this._classNames.contains(className))
126             return;
127
128         this._classNames.remove(className);
129
130         if (this._listItemNode)
131             this._listItemNode.classList.remove(className);
132     },
133
134     get small()
135     {
136         return this._small;
137     },
138
139     set small(x)
140     {
141         this._small = x;
142
143         if (this._listItemNode) {
144             if (this._small)
145                 this._listItemNode.classList.add(WebInspector.GeneralTreeElement.SmallStyleClassName);
146             else
147                 this._listItemNode.classList.remove(WebInspector.GeneralTreeElement.SmallStyleClassName);
148         }
149     },
150
151     get twoLine()
152     {
153         return this._twoLine;
154     },
155
156     set twoLine(x)
157     {
158         this._twoLine = x;
159
160         if (this._listItemNode) {
161             if (this._twoLine)
162                 this._listItemNode.classList.add(WebInspector.GeneralTreeElement.TwoLineStyleClassName);
163             else
164                 this._listItemNode.classList.remove(WebInspector.GeneralTreeElement.TwoLineStyleClassName);
165         }
166     },
167
168     get mainTitle()
169     {
170         return this._mainTitle;
171     },
172
173     set mainTitle(x)
174     {
175         this._mainTitle = x || "";
176         this._updateTitleElements();
177         this.didChange();
178         this.dispatchEventToListeners(WebInspector.GeneralTreeElement.Event.MainTitleDidChange);
179     },
180
181     get subtitle()
182     {
183         return this._subtitle;
184     },
185
186     set subtitle(x)
187     {
188         this._subtitle = x || "";
189         this._updateTitleElements();
190         this.didChange();
191     },
192
193     get status()
194     {
195         return this._status;
196     },
197
198     set status(x)
199     {
200         if (this._status === x)
201             return;
202
203         if (!this._statusElement) {
204             this._statusElement = document.createElement("div");
205             this._statusElement.className = WebInspector.GeneralTreeElement.StatusElementStyleClassName;
206         }
207
208         this._status = x || "";
209         this._updateStatusElement();
210     },
211
212     get filterableData()
213     {
214         return {text: [this.mainTitle, this.subtitle]};
215     },
216
217     get tooltipHandledSeparately()
218     {
219         return this._tooltipHandledSeparately;
220     },
221
222     set tooltipHandledSeparately(x)
223     {
224         this._tooltipHandledSeparately = x || false;
225     },
226
227     // Overrides from TreeElement (Private)
228
229     isEventWithinDisclosureTriangle: function(event)
230     {
231         return event.target === this._disclosureButton;
232     },
233
234     onattach: function()
235     {
236         this._createElementsIfNeeded();
237         this._updateTitleElements();
238
239         this._listItemNode.classList.add(WebInspector.GeneralTreeElement.StyleClassName);
240
241         if (this._classNames) {
242             for (var i = 0; i < this._classNames.length; ++i)
243                 this._listItemNode.classList.add(this._classNames[i]);
244         }
245
246         if (this._small)
247             this._listItemNode.classList.add(WebInspector.GeneralTreeElement.SmallStyleClassName);
248
249         if (this._twoLine)
250             this._listItemNode.classList.add(WebInspector.GeneralTreeElement.TwoLineStyleClassName);
251
252         this._listItemNode.appendChild(this._disclosureButton);
253         this._listItemNode.appendChild(this._iconElement);
254         this._listItemNode.appendChild(this._titlesElement);
255
256         if (this.oncontextmenu && typeof this.oncontextmenu === "function") {
257             this._boundContextMenuEventHandler = this.oncontextmenu.bind(this);
258             this._listItemNode.addEventListener("contextmenu", this._boundContextMenuEventHandler, true);
259         }
260
261         if (!this._boundContextMenuEventHandler && this.treeOutline.oncontextmenu && typeof this.treeOutline.oncontextmenu === "function") {
262             this._boundContextMenuEventHandler = function(event) { this.treeOutline.oncontextmenu(event, this); }.bind(this);
263             this._listItemNode.addEventListener("contextmenu", this._boundContextMenuEventHandler, true);
264         }
265
266         this._updateStatusElement();
267     },
268
269     ondetach: function()
270     {
271         if (this._boundContextMenuEventHandler) {
272             this._listItemNode.removeEventListener("contextmenu", this._boundContextMenuEventHandler, true);
273             delete this._boundContextMenuEventHandler;
274         }
275     },
276
277     onreveal: function()
278     {
279         if (this._listItemNode)
280             this._listItemNode.scrollIntoViewIfNeeded(false);
281     },
282
283     // Protected
284
285     callFirstAncestorFunction: function(functionName, args)
286     {
287         // Call the first ancestor that implements a function named functionName (if any).
288         var currentNode = this.parent;
289         while (currentNode) {
290             if (typeof currentNode[functionName] === "function") {
291                 currentNode[functionName].apply(currentNode, args);
292                 break;
293             }
294
295             currentNode = currentNode.parent;
296         }
297     },
298
299     // Private
300
301     _createElementsIfNeeded: function()
302     {
303         if (this._createdElements)
304             return;
305
306         this._disclosureButton = document.createElement("button");
307         this._disclosureButton.className = WebInspector.GeneralTreeElement.DisclosureButtonStyleClassName;
308
309         // Don't allow the disclosure button to be keyboard focusable. The TreeOutline is focusable and has
310         // its own keybindings for toggling expand and collapse.
311         this._disclosureButton.tabIndex = -1;
312
313         this._iconElement = document.createElement("img");
314         this._iconElement.className = WebInspector.GeneralTreeElement.IconElementStyleClassName;
315
316         this._titlesElement = document.createElement("div");
317         this._titlesElement.className = WebInspector.GeneralTreeElement.TitlesElementStyleClassName;
318
319         this._mainTitleElement = document.createElement("span");
320         this._mainTitleElement.className = WebInspector.GeneralTreeElement.MainTitleElementStyleClassName;
321         this._titlesElement.appendChild(this._mainTitleElement);
322
323         this._createdElements = true;
324     },
325
326     _createSubtitleElementIfNeeded: function()
327     {
328         if (this._subtitleElement)
329             return;
330
331         this._subtitleElement = document.createElement("span");
332         this._subtitleElement.className = WebInspector.GeneralTreeElement.SubtitleElementStyleClassName;
333         this._titlesElement.appendChild(this._subtitleElement);
334     },
335
336     _updateTitleElements: function()
337     {
338         if (!this._createdElements)
339             return;
340
341         if (typeof this._mainTitle === "string") {
342             if (this._mainTitleElement.textContent !== this._mainTitle)
343                 this._mainTitleElement.textContent = this._mainTitle;
344         } else if (this._mainTitle instanceof Node) {
345             this._mainTitleElement.removeChildren();
346             this._mainTitleElement.appendChild(this._mainTitle);
347         }
348
349         if (typeof this._subtitle === "string" && this._subtitle) {
350             this._createSubtitleElementIfNeeded();
351             if (this._subtitleElement.textContent !== this._subtitle)
352                 this._subtitleElement.textContent = this._subtitle;
353             this._titlesElement.classList.remove(WebInspector.GeneralTreeElement.NoSubtitleStyleClassName);
354         } else if (this._subtitle instanceof Node) {
355             this._createSubtitleElementIfNeeded();
356             this._subtitleElement.removeChildren();
357             this._subtitleElement.appendChild(this._subtitle);
358         } else {
359             if (this._subtitleElement)
360                 this._subtitleElement.textContent = "";
361             this._titlesElement.classList.add(WebInspector.GeneralTreeElement.NoSubtitleStyleClassName);
362         }
363
364         // Set a default tooltip if there isn't a custom one already assigned.
365         if (!this.tooltip && !this._tooltipHandledSeparately) {
366             console.assert(this._listItemNode);
367
368             // Get the textContent for the elements since they can contain other nodes,
369             // and the tool tip only cares about the text.
370             var mainTitleText = this._mainTitleElement.textContent;
371             var subtitleText = this._subtitleElement ? this._subtitleElement.textContent : "";
372
373             if (mainTitleText && subtitleText)
374                 this._listItemNode.title = mainTitleText + (this._small && !this._twoLine ? " \u2014 " : "\n") + subtitleText;
375             else if (mainTitleText)
376                 this._listItemNode.title = mainTitleText;
377             else
378                 this._listItemNode.title = subtitleText;
379         }
380     },
381
382     _updateStatusElement: function()
383     {
384         if (!this._statusElement)
385             return;
386
387         if (!this._statusElement.parentNode && this._listItemNode)
388             this._listItemNode.insertBefore(this._statusElement, this._titlesElement);
389
390         if (this._status instanceof Node) {
391             this._statusElement.removeChildren();
392             this._statusElement.appendChild(this._status);
393         } else
394             this._statusElement.textContent = this._status;
395     }
396 };