diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebInspectorUI/UserInterface/Views/TreeElement.js | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Views/TreeElement.js')
-rw-r--r-- | Source/WebInspectorUI/UserInterface/Views/TreeElement.js | 635 |
1 files changed, 635 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Views/TreeElement.js b/Source/WebInspectorUI/UserInterface/Views/TreeElement.js new file mode 100644 index 000000000..aeecf1849 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Views/TreeElement.js @@ -0,0 +1,635 @@ +/* + * Copyright (C) 2007, 2013, 2015 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.TreeElement = class TreeElement extends WebInspector.Object +{ + constructor(title, representedObject, hasChildren) + { + super(); + + this._title = title; + this.representedObject = (representedObject || {}); + + if (this.representedObject.__treeElementIdentifier) + this.identifier = this.representedObject.__treeElementIdentifier; + else { + this.identifier = WebInspector.TreeOutline._knownTreeElementNextIdentifier++; + this.representedObject.__treeElementIdentifier = this.identifier; + } + + this._hidden = false; + this._selectable = true; + this.expanded = false; + this.selected = false; + this.hasChildren = hasChildren; + this.children = []; + this.treeOutline = null; + this.parent = null; + this.previousSibling = null; + this.nextSibling = null; + this._listItemNode = null; + } + + // Methods + + appendChild() { return WebInspector.TreeOutline.prototype.appendChild.apply(this, arguments); } + insertChild() { return WebInspector.TreeOutline.prototype.insertChild.apply(this, arguments); } + removeChild() { return WebInspector.TreeOutline.prototype.removeChild.apply(this, arguments); } + removeChildAtIndex() { return WebInspector.TreeOutline.prototype.removeChildAtIndex.apply(this, arguments); } + removeChildren() { return WebInspector.TreeOutline.prototype.removeChildren.apply(this, arguments); } + removeChildrenRecursive() { return WebInspector.TreeOutline.prototype.removeChildrenRecursive.apply(this, arguments); } + + get arrowToggleWidth() + { + return 10; + } + + get selectable() + { + if (this._hidden) + return false; + return this._selectable; + } + + set selectable(x) + { + this._selectable = x; + } + + get listItemElement() + { + return this._listItemNode; + } + + get title() + { + return this._title; + } + + set title(x) + { + this._title = x; + this._setListItemNodeContent(); + this.didChange(); + } + + get titleHTML() + { + return this._titleHTML; + } + + set titleHTML(x) + { + this._titleHTML = x; + this._setListItemNodeContent(); + this.didChange(); + } + + get tooltip() + { + return this._tooltip; + } + + set tooltip(x) + { + this._tooltip = x; + if (this._listItemNode) + this._listItemNode.title = x ? x : ""; + } + + get hasChildren() + { + return this._hasChildren; + } + + set hasChildren(x) + { + if (this._hasChildren === x) + return; + + this._hasChildren = x; + + if (!this._listItemNode) + return; + + if (x) + this._listItemNode.classList.add("parent"); + else { + this._listItemNode.classList.remove("parent"); + this.collapse(); + } + + this.didChange(); + } + + get hidden() + { + return this._hidden; + } + + set hidden(x) + { + if (this._hidden === x) + return; + + this._hidden = x; + + if (this._listItemNode) + this._listItemNode.hidden = this._hidden; + if (this._childrenListNode) + this._childrenListNode.hidden = this._hidden; + + if (this.treeOutline) + this.treeOutline.dispatchEventToListeners(WebInspector.TreeOutline.Event.ElementVisibilityDidChange, {element: this}); + } + + get shouldRefreshChildren() + { + return this._shouldRefreshChildren; + } + + set shouldRefreshChildren(x) + { + this._shouldRefreshChildren = x; + if (x && this.expanded) + this.expand(); + } + + _fireDidChange() + { + if (this.treeOutline) + this.treeOutline._treeElementDidChange(this); + } + + didChange() + { + if (!this.treeOutline) + return; + + this.onNextFrame._fireDidChange(); + } + + _setListItemNodeContent() + { + if (!this._listItemNode) + return; + + if (!this._titleHTML && !this._title) + this._listItemNode.removeChildren(); + else if (typeof this._titleHTML === "string") + this._listItemNode.innerHTML = this._titleHTML; + else if (typeof this._title === "string") + this._listItemNode.textContent = this._title; + else { + this._listItemNode.removeChildren(); + if (this._title.parentNode) + this._title.parentNode.removeChild(this._title); + this._listItemNode.appendChild(this._title); + } + } + + _attach() + { + if (!this._listItemNode || this.parent._shouldRefreshChildren) { + if (this._listItemNode && this._listItemNode.parentNode) + this._listItemNode.parentNode.removeChild(this._listItemNode); + + this._listItemNode = this.treeOutline._childrenListNode.ownerDocument.createElement("li"); + this._listItemNode.treeElement = this; + this._setListItemNodeContent(); + this._listItemNode.title = this._tooltip ? this._tooltip : ""; + this._listItemNode.hidden = this.hidden; + + if (this.hasChildren) + this._listItemNode.classList.add("parent"); + if (this.expanded) + this._listItemNode.classList.add("expanded"); + if (this.selected) + this._listItemNode.classList.add("selected"); + + this._listItemNode.addEventListener("mousedown", WebInspector.TreeElement.treeElementMouseDown); + this._listItemNode.addEventListener("click", WebInspector.TreeElement.treeElementToggled); + this._listItemNode.addEventListener("dblclick", WebInspector.TreeElement.treeElementDoubleClicked); + + if (this.onattach) + this.onattach(this); + } + + var nextSibling = null; + if (this.nextSibling && this.nextSibling._listItemNode && this.nextSibling._listItemNode.parentNode === this.parent._childrenListNode) + nextSibling = this.nextSibling._listItemNode; + this.parent._childrenListNode.insertBefore(this._listItemNode, nextSibling); + if (this._childrenListNode) + this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling); + if (this.selected) + this.select(); + if (this.expanded) + this.expand(); + } + + _detach() + { + if (this.ondetach) + this.ondetach(this); + if (this._listItemNode && this._listItemNode.parentNode) + this._listItemNode.parentNode.removeChild(this._listItemNode); + if (this._childrenListNode && this._childrenListNode.parentNode) + this._childrenListNode.parentNode.removeChild(this._childrenListNode); + } + + static treeElementMouseDown(event) + { + var element = event.currentTarget; + if (!element || !element.treeElement || !element.treeElement.selectable) + return; + + if (element.treeElement.isEventWithinDisclosureTriangle(event)) { + event.preventDefault(); + return; + } + + element.treeElement.selectOnMouseDown(event); + } + + static treeElementToggled(event) + { + var element = event.currentTarget; + if (!element || !element.treeElement) + return; + + var toggleOnClick = element.treeElement.toggleOnClick && !element.treeElement.selectable; + var isInTriangle = element.treeElement.isEventWithinDisclosureTriangle(event); + if (!toggleOnClick && !isInTriangle) + return; + + if (element.treeElement.expanded) { + if (event.altKey) + element.treeElement.collapseRecursively(); + else + element.treeElement.collapse(); + } else { + if (event.altKey) + element.treeElement.expandRecursively(); + else + element.treeElement.expand(); + } + event.stopPropagation(); + } + + static treeElementDoubleClicked(event) + { + var element = event.currentTarget; + if (!element || !element.treeElement) + return; + + if (element.treeElement.isEventWithinDisclosureTriangle(event)) + return; + + if (element.treeElement.dispatchEventToListeners(WebInspector.TreeElement.Event.DoubleClick)) + return; + + if (element.treeElement.ondblclick) + element.treeElement.ondblclick.call(element.treeElement, event); + else if (element.treeElement.hasChildren && !element.treeElement.expanded) + element.treeElement.expand(); + } + + collapse() + { + if (this._listItemNode) + this._listItemNode.classList.remove("expanded"); + if (this._childrenListNode) + this._childrenListNode.classList.remove("expanded"); + + this.expanded = false; + if (this.treeOutline) + this.treeOutline._treeElementsExpandedState[this.identifier] = false; + + if (this.oncollapse) + this.oncollapse(this); + + if (this.treeOutline) + this.treeOutline.dispatchEventToListeners(WebInspector.TreeOutline.Event.ElementDisclosureDidChanged, {element: this}); + } + + collapseRecursively() + { + var item = this; + while (item) { + if (item.expanded) + item.collapse(); + item = item.traverseNextTreeElement(false, this, true); + } + } + + expand() + { + if (this.expanded && !this._shouldRefreshChildren && this._childrenListNode) + return; + + // Set this before onpopulate. Since onpopulate can add elements and dispatch an ElementAdded event, + // this makes sure the expanded flag is true before calling those functions. This prevents the + // possibility of an infinite loop if onpopulate or an event handler were to call expand. + + this.expanded = true; + if (this.treeOutline) + this.treeOutline._treeElementsExpandedState[this.identifier] = true; + + // If there are no children, return. We will be expanded once we have children. + if (!this.hasChildren) + return; + + if (this.treeOutline && (!this._childrenListNode || this._shouldRefreshChildren)) { + if (this._childrenListNode && this._childrenListNode.parentNode) + this._childrenListNode.parentNode.removeChild(this._childrenListNode); + + this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol"); + this._childrenListNode.parentTreeElement = this; + this._childrenListNode.classList.add("children"); + this._childrenListNode.hidden = this.hidden; + + this.onpopulate(); + + for (var i = 0; i < this.children.length; ++i) + this.children[i]._attach(); + + this._shouldRefreshChildren = false; + } + + if (this._listItemNode) { + this._listItemNode.classList.add("expanded"); + if (this._childrenListNode && this._childrenListNode.parentNode !== this._listItemNode.parentNode) + this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling); + } + + if (this._childrenListNode) + this._childrenListNode.classList.add("expanded"); + + if (this.onexpand) + this.onexpand(this); + + if (this.treeOutline) + this.treeOutline.dispatchEventToListeners(WebInspector.TreeOutline.Event.ElementDisclosureDidChanged, {element: this}); + } + + expandRecursively(maxDepth) + { + var item = this; + var info = {}; + var depth = 0; + + // The Inspector uses TreeOutlines to represents object properties, so recursive expansion + // in some case can be infinite, since JavaScript objects can hold circular references. + // So default to a recursion cap of 3 levels, since that gives fairly good results. + if (maxDepth === undefined) + maxDepth = 3; + + while (item) { + if (depth < maxDepth) + item.expand(); + item = item.traverseNextTreeElement(false, this, (depth >= maxDepth), info); + depth += info.depthChange; + } + } + + hasAncestor(ancestor) + { + if (!ancestor) + return false; + + var currentNode = this.parent; + while (currentNode) { + if (ancestor === currentNode) + return true; + currentNode = currentNode.parent; + } + + return false; + } + + reveal() + { + var currentAncestor = this.parent; + while (currentAncestor && !currentAncestor.root) { + if (!currentAncestor.expanded) + currentAncestor.expand(); + currentAncestor = currentAncestor.parent; + } + + if (this.onreveal) + this.onreveal(this); + } + + revealed(ignoreHidden) + { + if (!ignoreHidden && this.hidden) + return false; + + var currentAncestor = this.parent; + while (currentAncestor && !currentAncestor.root) { + if (!currentAncestor.expanded) + return false; + if (!ignoreHidden && currentAncestor.hidden) + return false; + currentAncestor = currentAncestor.parent; + } + + return true; + } + + selectOnMouseDown(event) + { + this.select(false, true); + } + + select(omitFocus, selectedByUser, suppressOnSelect, suppressOnDeselect) + { + if (!this.treeOutline || !this.selectable) + return; + + if (this.selected && !this.treeOutline.allowsRepeatSelection) + return; + + if (!omitFocus) + this.treeOutline._childrenListNode.focus(); + + // Focusing on another node may detach "this" from tree. + let treeOutline = this.treeOutline; + if (!treeOutline) + return; + + treeOutline.processingSelectionChange = true; + + // Prevent dispatching a SelectionDidChange event for the deselected element if + // it will be dispatched for the selected element. The event data includes both + // the selected and deselected elements, so one event is. + if (!suppressOnSelect) + suppressOnDeselect = true; + + let deselectedElement = treeOutline.selectedTreeElement; + if (!this.selected) { + if (treeOutline.selectedTreeElement) + treeOutline.selectedTreeElement.deselect(suppressOnDeselect); + + this.selected = true; + treeOutline.selectedTreeElement = this; + + if (this._listItemNode) + this._listItemNode.classList.add("selected"); + } + + if (!suppressOnSelect) { + if (this.onselect) + this.onselect(this, selectedByUser); + + treeOutline.dispatchEventToListeners(WebInspector.TreeOutline.Event.SelectionDidChange, {selectedElement: this, deselectedElement, selectedByUser}); + } + + treeOutline.processingSelectionChange = false; + + let treeOutlineGroup = WebInspector.TreeOutlineGroup.groupForTreeOutline(treeOutline); + if (!treeOutlineGroup) + return; + + treeOutlineGroup.didSelectTreeElement(this); + } + + revealAndSelect(omitFocus, selectedByUser, suppressOnSelect, suppressOnDeselect) + { + this.reveal(); + this.select(omitFocus, selectedByUser, suppressOnSelect, suppressOnDeselect); + } + + deselect(suppressOnDeselect) + { + if (!this.treeOutline || this.treeOutline.selectedTreeElement !== this || !this.selected) + return false; + + this.selected = false; + this.treeOutline.selectedTreeElement = null; + + if (this._listItemNode) + this._listItemNode.classList.remove("selected"); + + if (!suppressOnDeselect) { + if (this.ondeselect) + this.ondeselect(this); + + this.treeOutline.dispatchEventToListeners(WebInspector.TreeOutline.Event.SelectionDidChange, {deselectedElement: this}); + } + + return true; + } + + onpopulate() + { + // Overriden by subclasses. + } + + traverseNextTreeElement(skipUnrevealed, stayWithin, dontPopulate, info) + { + function shouldSkip(element) { + return skipUnrevealed && !element.revealed(true); + } + + var depthChange = 0; + var element = this; + + if (!dontPopulate) + element.onpopulate(); + + do { + if (element.hasChildren && element.children[0] && (!skipUnrevealed || element.expanded)) { + element = element.children[0]; + depthChange += 1; + } else { + while (element && !element.nextSibling && element.parent && !element.parent.root && element.parent !== stayWithin) { + element = element.parent; + depthChange -= 1; + } + + if (element) + element = element.nextSibling; + } + } while (element && shouldSkip(element)); + + if (info) + info.depthChange = depthChange; + + return element; + } + + traversePreviousTreeElement(skipUnrevealed, dontPopulate) + { + function shouldSkip(element) { + return skipUnrevealed && !element.revealed(true); + } + + var element = this; + + do { + if (element.previousSibling) { + element = element.previousSibling; + + while (element && element.hasChildren && element.expanded && !shouldSkip(element)) { + if (!dontPopulate) + element.onpopulate(); + element = element.children.lastValue; + } + } else + element = element.parent && element.parent.root ? null : element.parent; + } while (element && shouldSkip(element)); + + return element; + } + + isEventWithinDisclosureTriangle(event) + { + if (!document.contains(this._listItemNode)) + return false; + + // FIXME: We should not use getComputedStyle(). For that we need to get rid of using ::before for disclosure triangle. (http://webk.it/74446) + var computedLeftPadding = window.getComputedStyle(this._listItemNode).getPropertyCSSValue("padding-left").getFloatValue(CSSPrimitiveValue.CSS_PX); + var left = this._listItemNode.totalOffsetLeft + computedLeftPadding; + return event.pageX >= left && event.pageX <= left + this.arrowToggleWidth && this.hasChildren; + } + + populateContextMenu(contextMenu, event) + { + if (this.children.some((child) => child.hasChildren) || (this.hasChildren && !this.children.length)) { + contextMenu.appendSeparator(); + + contextMenu.appendItem(WebInspector.UIString("Expand All"), this.expandRecursively.bind(this)); + contextMenu.appendItem(WebInspector.UIString("Collapse All"), this.collapseRecursively.bind(this)); + } + } +}; + +WebInspector.TreeElement.Event = { + DoubleClick: "tree-element-double-click", +}; |