• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2007 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 *
8 * 1.  Redistributions of source code must retain the above copyright
9 *     notice, this list of conditions and the following disclaimer.
10 * 2.  Redistributions in binary form must reproduce the above copyright
11 *     notice, this list of conditions and the following disclaimer in the
12 *     documentation and/or other materials provided with the distribution.
13 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 *     its contributors may be used to endorse or promote products derived
15 *     from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29function TreeOutline(listNode)
30{
31    this.children = [];
32    this.selectedTreeElement = null;
33    this._childrenListNode = listNode;
34    this._childrenListNode.removeChildren();
35    this._knownTreeElements = [];
36    this._treeElementsExpandedState = [];
37    this.expandTreeElementsWhenArrowing = false;
38    this.root = true;
39    this.hasChildren = false;
40    this.expanded = true;
41    this.selected = false;
42    this.treeOutline = this;
43}
44
45TreeOutline._knownTreeElementNextIdentifier = 1;
46
47TreeOutline._appendChild = function(child)
48{
49    if (!child)
50        throw("child can't be undefined or null");
51
52    var lastChild = this.children[this.children.length - 1];
53    if (lastChild) {
54        lastChild.nextSibling = child;
55        child.previousSibling = lastChild;
56    } else {
57        child.previousSibling = null;
58        child.nextSibling = null;
59    }
60
61    this.children.push(child);
62    this.hasChildren = true;
63    child.parent = this;
64    child.treeOutline = this.treeOutline;
65    child.treeOutline._rememberTreeElement(child);
66
67    var current = child.children[0];
68    while (current) {
69        current.treeOutline = this.treeOutline;
70        current.treeOutline._rememberTreeElement(current);
71        current = current.traverseNextTreeElement(false, child, true);
72    }
73
74    if (child.hasChildren && child.treeOutline._treeElementsExpandedState[child.identifier] !== undefined)
75        child.expanded = child.treeOutline._treeElementsExpandedState[child.identifier];
76
77    if (!this._childrenListNode) {
78        this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol");
79        this._childrenListNode.parentTreeElement = this;
80        this._childrenListNode.addStyleClass("children");
81        if (this.hidden)
82            this._childrenListNode.addStyleClass("hidden");
83    }
84
85    child._attach();
86}
87
88TreeOutline._insertChild = function(child, index)
89{
90    if (!child)
91        throw("child can't be undefined or null");
92
93    var previousChild = (index > 0 ? this.children[index - 1] : null);
94    if (previousChild) {
95        previousChild.nextSibling = child;
96        child.previousSibling = previousChild;
97    } else {
98        child.previousSibling = null;
99    }
100
101    var nextChild = this.children[index];
102    if (nextChild) {
103        nextChild.previousSibling = child;
104        child.nextSibling = nextChild;
105    } else {
106        child.nextSibling = null;
107    }
108
109    this.children.splice(index, 0, child);
110    this.hasChildren = true;
111    child.parent = this;
112    child.treeOutline = this.treeOutline;
113    child.treeOutline._rememberTreeElement(child);
114
115    var current = child.children[0];
116    while (current) {
117        current.treeOutline = this.treeOutline;
118        current.treeOutline._rememberTreeElement(current);
119        current = current.traverseNextTreeElement(false, child, true);
120    }
121
122    if (child.hasChildren && child.treeOutline._treeElementsExpandedState[child.identifier] !== undefined)
123        child.expanded = child.treeOutline._treeElementsExpandedState[child.identifier];
124
125    if (!this._childrenListNode) {
126        this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol");
127        this._childrenListNode.parentTreeElement = this;
128        this._childrenListNode.addStyleClass("children");
129        if (this.hidden)
130            this._childrenListNode.addStyleClass("hidden");
131    }
132
133    child._attach();
134}
135
136TreeOutline._removeChildAtIndex = function(childIndex)
137{
138    if (childIndex < 0 || childIndex >= this.children.length)
139        throw("childIndex out of range");
140
141    var child = this.children[childIndex];
142    this.children.splice(childIndex, 1);
143
144    child.deselect();
145
146    if (child.previousSibling)
147        child.previousSibling.nextSibling = child.nextSibling;
148    if (child.nextSibling)
149        child.nextSibling.previousSibling = child.previousSibling;
150
151    if (child.treeOutline) {
152        child.treeOutline._forgetTreeElement(child);
153        child.treeOutline._forgetChildrenRecursive(child);
154    }
155
156    child._detach();
157    child.treeOutline = null;
158    child.parent = null;
159    child.nextSibling = null;
160    child.previousSibling = null;
161}
162
163TreeOutline._removeChild = function(child)
164{
165    if (!child)
166        throw("child can't be undefined or null");
167
168    var childIndex = this.children.indexOf(child);
169    if (childIndex === -1)
170        throw("child not found in this node's children");
171
172    TreeOutline._removeChildAtIndex.call(this, childIndex);
173}
174
175TreeOutline._removeChildren = function()
176{
177    for (var i = 0; i < this.children.length; ++i) {
178        var child = this.children[i];
179        child.deselect();
180
181        if (child.treeOutline) {
182            child.treeOutline._forgetTreeElement(child);
183            child.treeOutline._forgetChildrenRecursive(child);
184        }
185
186        child._detach();
187        child.treeOutline = null;
188        child.parent = null;
189        child.nextSibling = null;
190        child.previousSibling = null;
191    }
192
193    this.children = [];
194}
195
196TreeOutline._removeChildrenRecursive = function()
197{
198    var childrenToRemove = this.children;
199
200    var child = this.children[0];
201    while (child) {
202        if (child.children.length)
203            childrenToRemove = childrenToRemove.concat(child.children);
204        child = child.traverseNextTreeElement(false, this, true);
205    }
206
207    for (var i = 0; i < childrenToRemove.length; ++i) {
208        var child = childrenToRemove[i];
209        child.deselect();
210        if (child.treeOutline)
211            child.treeOutline._forgetTreeElement(child);
212        child._detach();
213        child.children = [];
214        child.treeOutline = null;
215        child.parent = null;
216        child.nextSibling = null;
217        child.previousSibling = null;
218    }
219
220    this.children = [];
221}
222
223TreeOutline.prototype._rememberTreeElement = function(element)
224{
225    if (!this._knownTreeElements[element.identifier])
226        this._knownTreeElements[element.identifier] = [];
227
228    // check if the element is already known
229    var elements = this._knownTreeElements[element.identifier];
230    if (elements.indexOf(element) !== -1)
231        return;
232
233    // add the element
234    elements.push(element);
235}
236
237TreeOutline.prototype._forgetTreeElement = function(element)
238{
239    if (this._knownTreeElements[element.identifier])
240        this._knownTreeElements[element.identifier].remove(element, true);
241}
242
243TreeOutline.prototype._forgetChildrenRecursive = function(parentElement)
244{
245    var child = parentElement.children[0];
246    while (child) {
247        this._forgetTreeElement(child);
248        child = child.traverseNextTreeElement(false, this, true);
249    }
250}
251
252TreeOutline.prototype.findTreeElement = function(representedObject, isAncestor, getParent, equal)
253{
254    if (!representedObject)
255        return null;
256
257    if (!equal)
258        equal = function(a, b) { return a === b };
259
260    if ("__treeElementIdentifier" in representedObject) {
261        // If this representedObject has a tree element identifier, and it is a known TreeElement
262        // in our tree we can just return that tree element.
263        var elements = this._knownTreeElements[representedObject.__treeElementIdentifier];
264        if (elements) {
265            for (var i = 0; i < elements.length; ++i)
266                if (equal(elements[i].representedObject, representedObject))
267                    return elements[i];
268        }
269    }
270
271    if (!isAncestor || !(isAncestor instanceof Function) || !getParent || !(getParent instanceof Function))
272        return null;
273
274    // The representedObject isn't know, so we start at the top of the tree and work down to find the first
275    // tree element that represents representedObject or one of its ancestors.
276    var item;
277    var found = false;
278    for (var i = 0; i < this.children.length; ++i) {
279        item = this.children[i];
280        if (equal(item.representedObject, representedObject) || isAncestor(item.representedObject, representedObject)) {
281            found = true;
282            break;
283        }
284    }
285
286    if (!found)
287        return null;
288
289    // Make sure the item that we found is connected to the root of the tree.
290    // Build up a list of representedObject's ancestors that aren't already in our tree.
291    var ancestors = [];
292    var currentObject = representedObject;
293    while (currentObject) {
294        ancestors.unshift(currentObject);
295        if (equal(currentObject, item.representedObject))
296            break;
297        currentObject = getParent(currentObject);
298    }
299
300    // For each of those ancestors we populate them to fill in the tree.
301    for (var i = 0; i < ancestors.length; ++i) {
302        // Make sure we don't call findTreeElement with the same representedObject
303        // again, to prevent infinite recursion.
304        if (equal(ancestors[i], representedObject))
305            continue;
306        // FIXME: we could do something faster than findTreeElement since we will know the next
307        // ancestor exists in the tree.
308        item = this.findTreeElement(ancestors[i], isAncestor, getParent, equal);
309        if (item && item.onpopulate)
310            item.onpopulate(item);
311    }
312
313    // Now that all the ancestors are populated, try to find the representedObject again. This time
314    // without the isAncestor and getParent functions to prevent an infinite recursion if it isn't found.
315    return this.findTreeElement(representedObject, null, null, equal);
316}
317
318TreeOutline.prototype.treeElementFromPoint = function(x, y)
319{
320    var node = this._childrenListNode.ownerDocument.elementFromPoint(x, y);
321    var listNode = node.enclosingNodeOrSelfWithNodeNameInArray(["ol", "li"]);
322    if (listNode)
323        return listNode.parentTreeElement || listNode.treeElement;
324    return null;
325}
326
327TreeOutline.prototype.handleKeyEvent = function(event)
328{
329    if (!this.selectedTreeElement || event.shiftKey || event.metaKey || event.ctrlKey)
330        return false;
331
332    var handled = false;
333    var nextSelectedElement;
334    if (event.keyIdentifier === "Up" && !event.altKey) {
335        nextSelectedElement = this.selectedTreeElement.traversePreviousTreeElement(true);
336        while (nextSelectedElement && !nextSelectedElement.selectable)
337            nextSelectedElement = nextSelectedElement.traversePreviousTreeElement(!this.expandTreeElementsWhenArrowing);
338        handled = nextSelectedElement ? true : false;
339    } else if (event.keyIdentifier === "Down" && !event.altKey) {
340        nextSelectedElement = this.selectedTreeElement.traverseNextTreeElement(true);
341        while (nextSelectedElement && !nextSelectedElement.selectable)
342            nextSelectedElement = nextSelectedElement.traverseNextTreeElement(!this.expandTreeElementsWhenArrowing);
343        handled = nextSelectedElement ? true : false;
344    } else if (event.keyIdentifier === "Left") {
345        if (this.selectedTreeElement.expanded) {
346            if (event.altKey)
347                this.selectedTreeElement.collapseRecursively();
348            else
349                this.selectedTreeElement.collapse();
350            handled = true;
351        } else if (this.selectedTreeElement.parent && !this.selectedTreeElement.parent.root) {
352            handled = true;
353            if (this.selectedTreeElement.parent.selectable) {
354                nextSelectedElement = this.selectedTreeElement.parent;
355                handled = nextSelectedElement ? true : false;
356            } else if (this.selectedTreeElement.parent)
357                this.selectedTreeElement.parent.collapse();
358        }
359    } else if (event.keyIdentifier === "Right") {
360        if (!this.selectedTreeElement.revealed()) {
361            this.selectedTreeElement.reveal();
362            handled = true;
363        } else if (this.selectedTreeElement.hasChildren) {
364            handled = true;
365            if (this.selectedTreeElement.expanded) {
366                nextSelectedElement = this.selectedTreeElement.children[0];
367                handled = nextSelectedElement ? true : false;
368            } else {
369                if (event.altKey)
370                    this.selectedTreeElement.expandRecursively();
371                else
372                    this.selectedTreeElement.expand();
373            }
374        }
375    }
376
377    if (nextSelectedElement) {
378        nextSelectedElement.reveal();
379        nextSelectedElement.select();
380    }
381
382    if (handled) {
383        event.preventDefault();
384        event.stopPropagation();
385    }
386
387    return handled;
388}
389
390TreeOutline.prototype.expand = function()
391{
392    // this is the root, do nothing
393}
394
395TreeOutline.prototype.collapse = function()
396{
397    // this is the root, do nothing
398}
399
400TreeOutline.prototype.revealed = function()
401{
402    return true;
403}
404
405TreeOutline.prototype.reveal = function()
406{
407    // this is the root, do nothing
408}
409
410TreeOutline.prototype.appendChild = TreeOutline._appendChild;
411TreeOutline.prototype.insertChild = TreeOutline._insertChild;
412TreeOutline.prototype.removeChild = TreeOutline._removeChild;
413TreeOutline.prototype.removeChildAtIndex = TreeOutline._removeChildAtIndex;
414TreeOutline.prototype.removeChildren = TreeOutline._removeChildren;
415TreeOutline.prototype.removeChildrenRecursive = TreeOutline._removeChildrenRecursive;
416
417function TreeElement(title, representedObject, hasChildren)
418{
419    this._title = title;
420    this.representedObject = (representedObject || {});
421
422    if (this.representedObject.__treeElementIdentifier)
423        this.identifier = this.representedObject.__treeElementIdentifier;
424    else {
425        this.identifier = TreeOutline._knownTreeElementNextIdentifier++;
426        this.representedObject.__treeElementIdentifier = this.identifier;
427    }
428
429    this._hidden = false;
430    this.expanded = false;
431    this.selected = false;
432    this.hasChildren = hasChildren;
433    this.children = [];
434    this.treeOutline = null;
435    this.parent = null;
436    this.previousSibling = null;
437    this.nextSibling = null;
438    this._listItemNode = null;
439}
440
441TreeElement.prototype = {
442    selectable: true,
443    arrowToggleWidth: 10,
444
445    get listItemElement() {
446        return this._listItemNode;
447    },
448
449    get childrenListElement() {
450        return this._childrenListNode;
451    },
452
453    get title() {
454        return this._title;
455    },
456
457    set title(x) {
458        this._title = x;
459        if (this._listItemNode)
460            this._listItemNode.innerHTML = x;
461    },
462
463    get tooltip() {
464        return this._tooltip;
465    },
466
467    set tooltip(x) {
468        this._tooltip = x;
469        if (this._listItemNode)
470            this._listItemNode.title = x ? x : "";
471    },
472
473    get hasChildren() {
474        return this._hasChildren;
475    },
476
477    set hasChildren(x) {
478        if (this._hasChildren === x)
479            return;
480
481        this._hasChildren = x;
482
483        if (!this._listItemNode)
484            return;
485
486        if (x)
487            this._listItemNode.addStyleClass("parent");
488        else {
489            this._listItemNode.removeStyleClass("parent");
490            this.collapse();
491        }
492    },
493
494    get hidden() {
495        return this._hidden;
496    },
497
498    set hidden(x) {
499        if (this._hidden === x)
500            return;
501
502        this._hidden = x;
503
504        if (x) {
505            if (this._listItemNode)
506                this._listItemNode.addStyleClass("hidden");
507            if (this._childrenListNode)
508                this._childrenListNode.addStyleClass("hidden");
509        } else {
510            if (this._listItemNode)
511                this._listItemNode.removeStyleClass("hidden");
512            if (this._childrenListNode)
513                this._childrenListNode.removeStyleClass("hidden");
514        }
515    },
516
517    get shouldRefreshChildren() {
518        return this._shouldRefreshChildren;
519    },
520
521    set shouldRefreshChildren(x) {
522        this._shouldRefreshChildren = x;
523        if (x && this.expanded)
524            this.expand();
525    }
526}
527
528TreeElement.prototype.appendChild = TreeOutline._appendChild;
529TreeElement.prototype.insertChild = TreeOutline._insertChild;
530TreeElement.prototype.removeChild = TreeOutline._removeChild;
531TreeElement.prototype.removeChildAtIndex = TreeOutline._removeChildAtIndex;
532TreeElement.prototype.removeChildren = TreeOutline._removeChildren;
533TreeElement.prototype.removeChildrenRecursive = TreeOutline._removeChildrenRecursive;
534
535TreeElement.prototype._attach = function()
536{
537    if (!this._listItemNode || this.parent._shouldRefreshChildren) {
538        if (this._listItemNode && this._listItemNode.parentNode)
539            this._listItemNode.parentNode.removeChild(this._listItemNode);
540
541        this._listItemNode = this.treeOutline._childrenListNode.ownerDocument.createElement("li");
542        this._listItemNode.treeElement = this;
543        this._listItemNode.innerHTML = this._title;
544        this._listItemNode.title = this._tooltip ? this._tooltip : "";
545
546        if (this.hidden)
547            this._listItemNode.addStyleClass("hidden");
548        if (this.hasChildren)
549            this._listItemNode.addStyleClass("parent");
550        if (this.expanded)
551            this._listItemNode.addStyleClass("expanded");
552        if (this.selected)
553            this._listItemNode.addStyleClass("selected");
554
555        this._listItemNode.addEventListener("mousedown", TreeElement.treeElementSelected, false);
556        this._listItemNode.addEventListener("click", TreeElement.treeElementToggled, false);
557        this._listItemNode.addEventListener("dblclick", TreeElement.treeElementDoubleClicked, false);
558
559        if (this.onattach)
560            this.onattach(this);
561    }
562
563    var nextSibling = null;
564    if (this.nextSibling && this.nextSibling._listItemNode && this.nextSibling._listItemNode.parentNode === this.parent._childrenListNode)
565        nextSibling = this.nextSibling._listItemNode;
566    this.parent._childrenListNode.insertBefore(this._listItemNode, nextSibling);
567    if (this._childrenListNode)
568        this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling);
569    if (this.selected)
570        this.select();
571    if (this.expanded)
572        this.expand();
573}
574
575TreeElement.prototype._detach = function()
576{
577    if (this._listItemNode && this._listItemNode.parentNode)
578        this._listItemNode.parentNode.removeChild(this._listItemNode);
579    if (this._childrenListNode && this._childrenListNode.parentNode)
580        this._childrenListNode.parentNode.removeChild(this._childrenListNode);
581}
582
583TreeElement.treeElementSelected = function(event)
584{
585    var element = event.currentTarget;
586    if (!element || !element.treeElement || !element.treeElement.selectable)
587        return;
588
589    if (element.treeElement.isEventWithinDisclosureTriangle(event))
590        return;
591
592    element.treeElement.select();
593}
594
595TreeElement.treeElementToggled = function(event)
596{
597    var element = event.currentTarget;
598    if (!element || !element.treeElement)
599        return;
600
601    if (!element.treeElement.isEventWithinDisclosureTriangle(event))
602        return;
603
604    if (element.treeElement.expanded) {
605        if (event.altKey)
606            element.treeElement.collapseRecursively();
607        else
608            element.treeElement.collapse();
609    } else {
610        if (event.altKey)
611            element.treeElement.expandRecursively();
612        else
613            element.treeElement.expand();
614    }
615}
616
617TreeElement.treeElementDoubleClicked = function(event)
618{
619    var element = event.currentTarget;
620    if (!element || !element.treeElement)
621        return;
622
623    if (element.treeElement.ondblclick)
624        element.treeElement.ondblclick(element.treeElement, event);
625    else if (element.treeElement.hasChildren && !element.treeElement.expanded)
626        element.treeElement.expand();
627}
628
629TreeElement.prototype.collapse = function()
630{
631    if (this._listItemNode)
632        this._listItemNode.removeStyleClass("expanded");
633    if (this._childrenListNode)
634        this._childrenListNode.removeStyleClass("expanded");
635
636    this.expanded = false;
637    if (this.treeOutline)
638        this.treeOutline._treeElementsExpandedState[this.identifier] = true;
639
640    if (this.oncollapse)
641        this.oncollapse(this);
642}
643
644TreeElement.prototype.collapseRecursively = function()
645{
646    var item = this;
647    while (item) {
648        if (item.expanded)
649            item.collapse();
650        item = item.traverseNextTreeElement(false, this, true);
651    }
652}
653
654TreeElement.prototype.expand = function()
655{
656    if (!this.hasChildren || (this.expanded && !this._shouldRefreshChildren && this._childrenListNode))
657        return;
658
659    if (this.treeOutline && (!this._childrenListNode || this._shouldRefreshChildren)) {
660        if (this._childrenListNode && this._childrenListNode.parentNode)
661            this._childrenListNode.parentNode.removeChild(this._childrenListNode);
662
663        this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol");
664        this._childrenListNode.parentTreeElement = this;
665        this._childrenListNode.addStyleClass("children");
666
667        if (this.hidden)
668            this._childrenListNode.addStyleClass("hidden");
669
670        if (this.onpopulate)
671            this.onpopulate(this);
672
673        for (var i = 0; i < this.children.length; ++i)
674            this.children[i]._attach();
675
676        delete this._shouldRefreshChildren;
677    }
678
679    if (this._listItemNode) {
680        this._listItemNode.addStyleClass("expanded");
681        if (this._childrenListNode && this._childrenListNode.parentNode != this._listItemNode.parentNode)
682            this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling);
683    }
684
685    if (this._childrenListNode)
686        this._childrenListNode.addStyleClass("expanded");
687
688    this.expanded = true;
689    if (this.treeOutline)
690        this.treeOutline._treeElementsExpandedState[this.identifier] = true;
691
692    if (this.onexpand)
693        this.onexpand(this);
694}
695
696TreeElement.prototype.expandRecursively = function(maxDepth)
697{
698    var item = this;
699    var info = {};
700    var depth = 0;
701
702    // The Inspector uses TreeOutlines to represents object properties, so recursive expansion
703    // in some case can be infinite, since JavaScript objects can hold circular references.
704    // So default to a recursion cap of 3 levels, since that gives fairly good results.
705    if (typeof maxDepth === "undefined" || typeof maxDepth === "null")
706        maxDepth = 3;
707
708    while (item) {
709        if (depth < maxDepth)
710            item.expand();
711        item = item.traverseNextTreeElement(false, this, (depth >= maxDepth), info);
712        depth += info.depthChange;
713    }
714}
715
716TreeElement.prototype.hasAncestor = function(ancestor) {
717    if (!ancestor)
718        return false;
719
720    var currentNode = this.parent;
721    while (currentNode) {
722        if (ancestor === currentNode)
723            return true;
724        currentNode = currentNode.parent;
725    }
726
727    return false;
728}
729
730TreeElement.prototype.reveal = function()
731{
732    var currentAncestor = this.parent;
733    while (currentAncestor && !currentAncestor.root) {
734        if (!currentAncestor.expanded)
735            currentAncestor.expand();
736        currentAncestor = currentAncestor.parent;
737    }
738
739    if (this.onreveal)
740        this.onreveal(this);
741}
742
743TreeElement.prototype.revealed = function()
744{
745    var currentAncestor = this.parent;
746    while (currentAncestor && !currentAncestor.root) {
747        if (!currentAncestor.expanded)
748            return false;
749        currentAncestor = currentAncestor.parent;
750    }
751
752    return true;
753}
754
755TreeElement.prototype.select = function(supressOnSelect)
756{
757    if (!this.treeOutline || !this.selectable || this.selected)
758        return;
759
760    if (this.treeOutline.selectedTreeElement)
761        this.treeOutline.selectedTreeElement.deselect();
762
763    this.selected = true;
764    this.treeOutline.selectedTreeElement = this;
765    if (this._listItemNode)
766        this._listItemNode.addStyleClass("selected");
767
768    if (this.onselect && !supressOnSelect)
769        this.onselect(this);
770}
771
772TreeElement.prototype.deselect = function(supressOnDeselect)
773{
774    if (!this.treeOutline || this.treeOutline.selectedTreeElement !== this || !this.selected)
775        return;
776
777    this.selected = false;
778    this.treeOutline.selectedTreeElement = null;
779    if (this._listItemNode)
780        this._listItemNode.removeStyleClass("selected");
781
782    if (this.ondeselect && !supressOnDeselect)
783        this.ondeselect(this);
784}
785
786TreeElement.prototype.traverseNextTreeElement = function(skipHidden, stayWithin, dontPopulate, info)
787{
788    if (!dontPopulate && this.hasChildren && this.onpopulate)
789        this.onpopulate(this);
790
791    if (info)
792        info.depthChange = 0;
793
794    var element = skipHidden ? (this.revealed() ? this.children[0] : null) : this.children[0];
795    if (element && (!skipHidden || (skipHidden && this.expanded))) {
796        if (info)
797            info.depthChange = 1;
798        return element;
799    }
800
801    if (this === stayWithin)
802        return null;
803
804    element = skipHidden ? (this.revealed() ? this.nextSibling : null) : this.nextSibling;
805    if (element)
806        return element;
807
808    element = this;
809    while (element && !element.root && !(skipHidden ? (element.revealed() ? element.nextSibling : null) : element.nextSibling) && element.parent !== stayWithin) {
810        if (info)
811            info.depthChange -= 1;
812        element = element.parent;
813    }
814
815    if (!element)
816        return null;
817
818    return (skipHidden ? (element.revealed() ? element.nextSibling : null) : element.nextSibling);
819}
820
821TreeElement.prototype.traversePreviousTreeElement = function(skipHidden, dontPopulate)
822{
823    var element = skipHidden ? (this.revealed() ? this.previousSibling : null) : this.previousSibling;
824    if (!dontPopulate && element && element.hasChildren && element.onpopulate)
825        element.onpopulate(element);
826
827    while (element && (skipHidden ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1])) {
828        if (!dontPopulate && element.hasChildren && element.onpopulate)
829            element.onpopulate(element);
830        element = (skipHidden ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1]);
831    }
832
833    if (element)
834        return element;
835
836    if (!this.parent || this.parent.root)
837        return null;
838
839    return this.parent;
840}
841
842TreeElement.prototype.isEventWithinDisclosureTriangle = function(event)
843{
844    var left = this._listItemNode.totalOffsetLeft;
845    return event.pageX >= left && event.pageX <= left + this.arrowToggleWidth && this.hasChildren;
846}
847