Adds a DataGrid object that is used for multi-column data
authortimothy@apple.com <timothy@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 20 May 2008 13:57:27 +0000 (13:57 +0000)
committertimothy@apple.com <timothy@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 20 May 2008 13:57:27 +0000 (13:57 +0000)
and can contain hierarchical content with disclosure arrows.
A lot of DataGrid was copied from treeoutline.js. This change
makes the database views use the DataGrid. It will later be
used by the ProfileView.

Reviewed by Adam Roben.

* page/inspector/DataGrid.js: Added. Most copied from treeoutline.js
and modified to work with table elements.
* page/inspector/DatabaseQueryView.js:
(WebInspector.DatabaseQueryView.prototype._queryFinished):
Call DatabasesPanel.dataGridForResult and adds the inline style to
the DataGrid element.
* page/inspector/DatabaseTableView.js:
(WebInspector.DatabaseTableView.prototype._queryFinished):
Call DatabasesPanel.dataGridForResult.
* page/inspector/DatabasesPanel.js:
(WebInspector.DatabasesPanel.prototype._tableForResult): Removed.
(WebInspector.DatabasesPanel.prototype.dataGridForResult): Added.
Similar to the previous _tableForResult function, but makes a DataGrid.
* page/inspector/inspector.css: Changes to the data-grid
style rules.
* WebCore.vcproj/WebCore.vcproj: Add DataGrid.js.
* page/inspector/WebKit.qrc: Ditto.
* page/inspector/inspector.html: Ditto.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@33927 268f45cc-cd09-0410-ab3c-d52691b4dbfc

14 files changed:
WebCore/ChangeLog
WebCore/WebCore.vcproj/WebCore.vcproj
WebCore/page/inspector/DataGrid.js [new file with mode: 0644]
WebCore/page/inspector/DatabaseQueryView.js
WebCore/page/inspector/DatabaseTableView.js
WebCore/page/inspector/DatabasesPanel.js
WebCore/page/inspector/Images/glossyHeader.png
WebCore/page/inspector/Images/glossyHeaderPressed.png
WebCore/page/inspector/Images/glossyHeaderSelected.png [new file with mode: 0644]
WebCore/page/inspector/Images/glossyHeaderSelectedPressed.png [new file with mode: 0644]
WebCore/page/inspector/Images/glossySelected.png [deleted file]
WebCore/page/inspector/WebKit.qrc
WebCore/page/inspector/inspector.css
WebCore/page/inspector/inspector.html

index 7cb52bc..82290e5 100644 (file)
@@ -1,3 +1,32 @@
+2008-05-19  Timothy Hatcher  <timothy@apple.com>
+
+        Adds a DataGrid object that is used for multi-column data
+        and can contain hierarchical content with disclosure arrows.
+        A lot of DataGrid was copied from treeoutline.js. This change
+        makes the database views use the DataGrid. It will later be
+        used by the ProfileView.
+
+        Reviewed by Adam Roben.
+
+        * page/inspector/DataGrid.js: Added. Most copied from treeoutline.js
+        and modified to work with table elements.
+        * page/inspector/DatabaseQueryView.js:
+        (WebInspector.DatabaseQueryView.prototype._queryFinished):
+        Call DatabasesPanel.dataGridForResult and adds the inline style to
+        the DataGrid element.
+        * page/inspector/DatabaseTableView.js:
+        (WebInspector.DatabaseTableView.prototype._queryFinished):
+        Call DatabasesPanel.dataGridForResult.
+        * page/inspector/DatabasesPanel.js:
+        (WebInspector.DatabasesPanel.prototype._tableForResult): Removed.
+        (WebInspector.DatabasesPanel.prototype.dataGridForResult): Added.
+        Similar to the previous _tableForResult function, but makes a DataGrid.
+        * page/inspector/inspector.css: Changes to the data-grid
+        style rules.
+        * WebCore.vcproj/WebCore.vcproj: Add DataGrid.js.
+        * page/inspector/WebKit.qrc: Ditto.
+        * page/inspector/inspector.html: Ditto.
+
 2008-05-19  Kevin McCullough  <kmccullough@apple.com>
 
         Reviewed by Adam.
index b7db924..c0faf5f 100644 (file)
                                        >\r
                                </File>\r
                                <File\r
+                                       RelativePath="..\page\inspector\DataGrid.js"\r
+                                       >\r
+                               </File>\r
+                               <File\r
                                        RelativePath="..\page\inspector\DatabaseQueryView.js"\r
                                        >\r
                                </File>\r
diff --git a/WebCore/page/inspector/DataGrid.js b/WebCore/page/inspector/DataGrid.js
new file mode 100644 (file)
index 0000000..01c259c
--- /dev/null
@@ -0,0 +1,844 @@
+/*
+ * Copyright (C) 2008 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 INC. OR
+ * 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.DataGrid = function(columns)
+{
+    this.element = document.createElement("div");
+    this.element.className = "data-grid focusable";
+    this.element.tabIndex = 0;
+    this.element.addEventListener("keydown", this._keyDown.bind(this), false);
+
+    this._headerTable = document.createElement("table");
+    this._headerTable.className = "header";
+
+    this._dataTable = document.createElement("table");
+    this._dataTable.className = "data";
+
+    this._dataTable.addEventListener("mousedown", this._mouseDownInDataTable.bind(this), true);
+    this._dataTable.addEventListener("click", this._clickInDataTable.bind(this), true);
+
+    var scrollContainer = document.createElement("div");
+    scrollContainer.className = "data-container";
+    scrollContainer.appendChild(this._dataTable);
+
+    this.element.appendChild(this._headerTable);
+    this.element.appendChild(scrollContainer);
+
+    var headerRow = document.createElement("tr");
+    var columnGroup = document.createElement("colgroup");
+    var columnCount = 0;
+
+    for (var columnIdentifier in columns) {
+        var column = columns[columnIdentifier];
+        if (column.disclosure)
+            this.disclosureColumnIdentifier = columnIdentifier;
+
+        var col = document.createElement("col");
+        if (column.width)
+            col.style.width = column.width;
+        columnGroup.appendChild(col);
+
+        var cell = document.createElement("th");
+        cell.className = columnIdentifier + "-column";
+        cell.columnIdentifier = columnIdentifier;
+
+        var div = document.createElement("div");
+        div.textContent = column.title;
+        cell.appendChild(div);
+
+        if (column.sort) {
+            cell.addStyleClass("sort-" + column.sort);
+            this._sortColumnCell = cell;
+        }
+
+        if (column.sortable) {
+            cell.addEventListener("click", this._clickInHeaderCell.bind(this), false);
+            cell.addStyleClass("sortable");
+        }
+
+        headerRow.appendChild(cell);
+
+        ++columnCount;
+    }
+
+    columnGroup.span = columnCount;
+
+    var cell = document.createElement("th");
+    cell.className = "corner";
+    headerRow.appendChild(cell);
+
+    this._headerTable.appendChild(columnGroup);
+    this.headerTableBody.appendChild(headerRow);
+
+    var fillerRow = document.createElement("tr");
+    fillerRow.className = "filler";
+
+    for (var i = 0; i < columnCount; ++i) {
+        var cell = document.createElement("td");
+        fillerRow.appendChild(cell);
+    }
+
+    this._dataTable.appendChild(columnGroup.cloneNode(true));
+    this.dataTableBody.appendChild(fillerRow);
+
+    this.columns = columns || {};
+    this.children = [];
+    this.selectedNode = null;
+    this.expandNodesWhenArrowing = false;
+    this.root = true;
+    this.hasChildren = false;
+    this.expanded = true;
+    this.revealed = true;
+    this.selected = false;
+    this.dataGrid = this;
+    this.indentWidth = 15;
+}
+
+WebInspector.DataGrid.prototype = {
+    get sortColumnIdentifier()
+    {
+        if (!this._sortColumnCell)
+            return null;
+        return this._sortColumnCell.columnIdentifier;
+    },
+
+    get sortOrder()
+    {
+        if (!this._sortColumnCell || this._sortColumnCell.hasStyleClass("sort-ascending"))
+            return "ascending";
+        if (this._sortColumnCell.hasStyleClass("sort-descending"))
+            return "descending";
+        return null;
+    },
+
+    get headerTableBody()
+    {
+        if ("_headerTableBody" in this)
+            return this._headerTableBody;
+
+        this._headerTableBody = this._headerTable.getElementsByTagName("tbody")[0];
+        if (!this._headerTableBody) {
+            this._headerTableBody = this.element.ownerDocument.createElement("tbody");
+            this._headerTable.insertBefore(this._headerTableBody, this._headerTable.tFoot);
+        }
+
+        return this._headerTableBody;
+    },
+
+    get dataTableBody()
+    {
+        if ("_dataTableBody" in this)
+            return this._dataTableBody;
+
+        this._dataTableBody = this._dataTable.getElementsByTagName("tbody")[0];
+        if (!this._dataTableBody) {
+            this._dataTableBody = this.element.ownerDocument.createElement("tbody");
+            this._dataTable.insertBefore(this._dataTableBody, this._dataTable.tFoot);
+        }
+
+        return this._dataTableBody;
+    },
+
+    appendChild: function(child)
+    {
+        this.insertChild(child, this.children.length);
+    },
+
+    insertChild: function(child, index)
+    {
+        if (!child)
+            throw("Node can't be undefined or null.");
+        if (child.parent === this)
+            throw("Node is already a child of this node.");
+
+        if (child.parent)
+            child.parent.removeChild(child);
+
+        var previousChild = (index > 0 ? this.children[index - 1] : null);
+        if (previousChild) {
+            previousChild.nextSibling = child;
+            child.previousSibling = previousChild;
+        } else
+            child.previousSibling = null;
+
+        var nextChild = this.children[index];
+        if (nextChild) {
+            nextChild.previousSibling = child;
+            child.nextSibling = nextChild;
+        } else
+            child.nextSibling = null;
+
+        this.children.splice(index, 0, child);
+        this.hasChildren = true;
+
+        child.parent = this;
+        child.dataGrid = this.dataGrid;
+
+        delete child._depth;
+        delete child._revealed;
+        delete child._attached;
+
+        var current = child.children[0];
+        while (current) {
+            current.dataGrid = this.dataGrid;
+            delete current._depth;
+            delete current._revealed;
+            delete current._attached;
+            current = current.traverseNextNode(false, child, true);
+        }
+
+        if (this.expanded)
+            child._attach();
+    },
+
+    removeChild: function(child)
+    {
+        if (!child)
+            throw("Node can't be undefined or null.");
+        if (child.parent !== this)
+            throw("Node is not a child of this node.");
+
+        child.deselect();
+
+        this.children.remove(child, true);
+
+        if (child.previousSibling)
+            child.previousSibling.nextSibling = child.nextSibling;
+        if (child.nextSibling)
+            child.nextSibling.previousSibling = child.previousSibling;
+
+        child.dataGrid = null;
+        child.parent = null;
+        child.nextSibling = null;
+        child.previousSibling = null;
+    },
+
+    removeChildren: function()
+    {
+        for (var i = 0; i < this.children.length; ++i) {
+            var child = this.children[i];
+            child.deselect();
+            child._detach();
+
+            child.dataGrid = null;
+            child.parent = null;
+            child.nextSibling = null;
+            child.previousSibling = null;
+        }
+
+        this.children = [];
+    },
+
+    removeChildrenRecursive: function()
+    {
+        var childrenToRemove = this.children;
+
+        var child = this.children[0];
+        while (child) {
+            if (child.children.length)
+                childrenToRemove = childrenToRemove.concat(child.children);
+            child = child.traverseNextNode(false, this, true);
+        }
+
+        for (var i = 0; i < childrenToRemove.length; ++i) {
+            var child = childrenToRemove[i];
+            child.deselect();
+            child._detach();
+
+            child.children = [];
+            child.dataGrid = null;
+            child.parent = null;
+            child.nextSibling = null;
+            child.previousSibling = null;
+        }
+
+        this.children = [];
+    },
+
+    handleKeyEvent: function(event)
+    {
+        if (!this.selectedNode || event.shiftKey || event.metaKey || event.ctrlKey)
+            return false;
+
+        var handled = false;
+        var nextSelectedNode;
+        if (event.keyIdentifier === "Up" && !event.altKey) {
+            nextSelectedNode = this.selectedNode.traversePreviousNode(true);
+            while (nextSelectedNode && !nextSelectedNode.selectable)
+                nextSelectedNode = nextSelectedNode.traversePreviousNode(!this.expandTreeNodesWhenArrowing);
+            handled = nextSelectedNode ? true : false;
+        } else if (event.keyIdentifier === "Down" && !event.altKey) {
+            nextSelectedNode = this.selectedNode.traverseNextNode(true);
+            while (nextSelectedNode && !nextSelectedNode.selectable)
+                nextSelectedNode = nextSelectedNode.traverseNextNode(!this.expandTreeNodesWhenArrowing);
+            handled = nextSelectedNode ? true : false;
+        } else if (event.keyIdentifier === "Left") {
+            if (this.selectedNode.expanded) {
+                if (event.altKey)
+                    this.selectedNode.collapseRecursively();
+                else
+                    this.selectedNode.collapse();
+                handled = true;
+            } else if (this.selectedNode.parent && !this.selectedNode.parent.root) {
+                handled = true;
+                if (this.selectedNode.parent.selectable) {
+                    nextSelectedNode = this.selectedNode.parent;
+                    handled = nextSelectedNode ? true : false;
+                } else if (this.selectedNode.parent)
+                    this.selectedNode.parent.collapse();
+            }
+        } else if (event.keyIdentifier === "Right") {
+            if (!this.selectedNode.revealed) {
+                this.selectedNode.reveal();
+                handled = true;
+            } else if (this.selectedNode.hasChildren) {
+                handled = true;
+                if (this.selectedNode.expanded) {
+                    nextSelectedNode = this.selectedNode.children[0];
+                    handled = nextSelectedNode ? true : false;
+                } else {
+                    if (event.altKey)
+                        this.selectedNode.expandRecursively();
+                    else
+                        this.selectedNode.expand();
+                }
+            }
+        }
+
+        if (nextSelectedNode) {
+            nextSelectedNode.reveal();
+            nextSelectedNode.select();
+        }
+
+        if (handled) {
+            event.preventDefault();
+            event.stopPropagation();
+        }
+
+        return handled;
+    },
+
+    expand: function()
+    {
+        // This is the root, do nothing.
+    },
+
+    collapse: function()
+    {
+        // This is the root, do nothing.
+    },
+
+    reveal: function()
+    {
+        // This is the root, do nothing.
+    },
+
+    dataGridNodeFromEvent: function(event)
+    {
+        var rowElement = event.target.enclosingNodeOrSelfWithNodeName("tr");
+        return rowElement._dataGridNode;
+    },
+
+    dataGridNodeFromPoint: function(x, y)
+    {
+        var node = this._dataTable.ownerDocument.elementFromPoint(x, y);
+        var rowElement = node.enclosingNodeOrSelfWithNodeName("tr");
+        return rowElement._dataGridNode;
+    },
+
+    _keyDown: function(event)
+    {
+        this.handleKeyEvent(event);
+    },
+
+    _clickInHeaderCell: function(event)
+    {
+        var cell = event.target.enclosingNodeOrSelfWithNodeName("th");
+        if (!cell || !cell.columnIdentifier || !cell.hasStyleClass("sortable"))
+            return;
+
+        var sortOrder = this.sortOrder;
+
+        if (this._sortColumnCell) {
+            this._sortColumnCell.removeStyleClass("sort-ascending");
+            this._sortColumnCell.removeStyleClass("sort-descending");
+        }
+
+        if (cell == this._sortColumnCell) {
+            if (sortOrder == "ascending")
+                sortOrder = "descending";
+            else
+                sortOrder = "ascending";
+        }
+
+        this._sortColumnCell = cell;
+
+        cell.addStyleClass("sort-" + sortOrder);
+
+        this.dispatchEventToListeners("sorting changed");
+    },
+
+    _mouseDownInDataTable: function(event)
+    {
+        var gridNode = this.dataGridNodeFromEvent(event);
+        if (!gridNode || !gridNode.selectable)
+            return;
+    
+        if (gridNode.isEventWithinDisclosureTriangle(event))
+            return;
+
+        if (event.metaKey) {
+            if (gridNode.selected)
+                gridNode.deselect();
+            else
+                gridNode.select();
+        } else
+            gridNode.select();
+    },
+
+    _clickInDataTable: function(event)
+    {
+        var gridNode = this.dataGridNodeFromEvent(event);
+        if (!gridNode || !gridNode.hasChildren)
+            return;
+
+        if (!gridNode.isEventWithinDisclosureTriangle(event))
+            return;
+
+        if (gridNode.expanded) {
+            if (event.altKey)
+                gridNode.collapseRecursively();
+            else
+                gridNode.collapse();
+        } else {
+            if (event.altKey)
+                gridNode.expandRecursively();
+            else
+                gridNode.expand();
+        }
+    }
+}
+
+WebInspector.DataGrid.prototype.__proto__ = WebInspector.Object.prototype;
+
+WebInspector.DataGridNode = function(data, hasChildren)
+{
+    this._expanded = false;
+    this._selected = false;
+    this._shouldRefreshChildren = true;
+    this._data = data || {};
+    this.hasChildren = hasChildren || false;
+    this.children = [];
+    this.dataGrid = null;
+    this.parent = null;
+    this.previousSibling = null;
+    this.nextSibling = null;
+    this.disclosureToggleWidth = 10;
+}
+
+WebInspector.DataGridNode.prototype = {
+    selectable: true,
+
+    get element()
+    {
+        if (this._element)
+            return this._element;
+
+        if (!this.dataGrid)
+            return null;
+
+        this._element = document.createElement("tr");
+        this._element._dataGridNode = this;
+
+        if (this.hasChildren)
+            this._element.addStyleClass("parent");
+        if (this.expanded)
+            this._element.addStyleClass("expanded");
+        if (this.selected)
+            this._element.addStyleClass("selected");
+        if (this.revealed)
+            this._element.addStyleClass("revealed");
+
+        for (var columnIdentifier in this.dataGrid.columns) {
+            var cell = this.createCell(columnIdentifier);
+            this._element.appendChild(cell);
+        }
+
+        return this._element;
+    },
+
+    get data()
+    {
+        return this._data;
+    },
+
+    set data(x)
+    {
+        this._data = x || {};
+        this.refresh();
+    },
+
+    get revealed()
+    {
+        if ("_revealed" in this)
+            return this._revealed;
+
+        var currentAncestor = this.parent;
+        while (currentAncestor && !currentAncestor.root) {
+            if (!currentAncestor.expanded) {
+                this._revealed = false;
+                return false;
+            }
+
+            currentAncestor = currentAncestor.parent;
+        }
+
+        this._revealed = true;
+        return true;
+    },
+
+    set revealed(x)
+    {
+        if (this._revealed === x)
+            return;
+
+        this._revealed = x;
+
+        if (this._element) {
+            if (this._revealed)
+                this._element.addStyleClass("revealed");
+            else
+                this._element.removeStyleClass("revealed");
+        }
+
+        for (var i = 0; i < this.children.length; ++i)
+            this.children[i].revealed = x && this.expanded;
+    },
+
+    get depth()
+    {
+        if ("_depth" in this)
+            return this._depth;
+        if (this.parent && !this.parent.root)
+            this._depth = this.parent.depth + 1;
+        else
+            this._depth = 0;
+        return this._depth;
+    },
+
+    get shouldRefreshChildren()
+    {
+        return this._shouldRefreshChildren;
+    },
+
+    set shouldRefreshChildren(x)
+    {
+        this._shouldRefreshChildren = x;
+        if (x && this.expanded)
+            this.expand();
+    },
+
+    get selected()
+    {
+        return this._selected;
+    },
+
+    set selected(x)
+    {
+        if (x)
+            this.select();
+        else
+            this.deselect();
+    },
+
+    get expanded()
+    {
+        return this._expanded;
+    },
+
+    set expanded(x)
+    {
+        if (x)
+            this.expand();
+        else
+            this.collapse();
+    },
+
+    refresh: function()
+    {
+        if (!this._element || !this.dataGrid)
+            return;
+
+        this._element.removeChildren();
+
+        for (var columnIdentifier in this.dataGrid.columns) {
+            var cell = this.createCell(columnIdentifier);
+            this._element.appendChild(cell);
+        }
+    },
+
+    createCell: function(columnIdentifier)
+    {
+        var cell = document.createElement("td");
+        cell.className = columnIdentifier + "-column";
+
+        var div = document.createElement("div");
+        div.textContent = this.data[columnIdentifier];
+        cell.appendChild(div);
+
+        if (columnIdentifier === this.dataGrid.disclosureColumnIdentifier) {
+            cell.addStyleClass("disclosure");
+            if (this.depth)
+                cell.style.setProperty("padding-left", (this.depth * this.dataGrid.indentWidth) + "px");
+        }
+
+        return cell;
+    },
+
+    // Share these functions with DataGrid. They are written to work with a DataGridNode this object.
+    appendChild: WebInspector.DataGrid.prototype.appendChild,
+    insertChild: WebInspector.DataGrid.prototype.insertChild,
+    removeChild: WebInspector.DataGrid.prototype.removeChild,
+    removeChildren: WebInspector.DataGrid.prototype.removeChildren,
+    removeChildrenRecursive: WebInspector.DataGrid.prototype.removeChildrenRecursive,
+
+    collapse: function()
+    {
+        if (this._element)
+            this._element.removeStyleClass("expanded");
+
+        this._expanded = false;
+
+        for (var i = 0; i < this.children.length; ++i)
+            this.children[i].revealed = false;
+
+        this.dispatchEventToListeners("collapsed");
+    },
+
+    collapseRecursively: function()
+    {
+        var item = this;
+        while (item) {
+            if (item.expanded)
+                item.collapse();
+            item = item.traverseNextNode(false, this, true);
+        }
+    },
+
+    expand: function()
+    {
+        if (!this.hasChildren || this.expanded)
+            return;
+
+        if (this.revealed && !this._shouldRefreshChildren)
+            for (var i = 0; i < this.children.length; ++i)
+                this.children[i].revealed = true;
+
+        if (this._shouldRefreshChildren) {
+            for (var i = 0; i < this.children.length; ++i)
+                this.children[i]._detach();
+
+            this.dispatchEventToListeners("populate");
+
+            if (this._attached) {
+                for (var i = 0; i < this.children.length; ++i) {
+                    var child = this.children[i];
+                    if (this.revealed)
+                        child.revealed = true;
+                    child._attach();
+                }
+            }
+
+            delete this._shouldRefreshChildren;
+        }
+
+        if (this._element)
+            this._element.addStyleClass("expanded");
+
+        this._expanded = true;
+
+        this.dispatchEventToListeners("expanded");
+    },
+
+    expandRecursively: function()
+    {
+        var item = this;
+        while (item) {
+            item.expand();
+            item = item.traverseNextNode(false, this);
+        }
+    },
+
+    reveal: function()
+    {
+        var currentAncestor = this.parent;
+        while (currentAncestor && !currentAncestor.root) {
+            if (!currentAncestor.expanded)
+                currentAncestor.expand();
+            currentAncestor = currentAncestor.parent;
+        }
+
+        this.element.scrollIntoViewIfNeeded(false);
+
+        this.dispatchEventToListeners("revealed");
+    },
+
+    select: function(supressSelectedEvent)
+    {
+        if (!this.dataGrid || !this.selectable || this.selected)
+            return;
+
+        if (this.dataGrid.selectedNode)
+            this.dataGrid.selectedNode.deselect();
+
+        this._selected = true;
+        this.dataGrid.selectedNode = this;
+
+        if (this._element)
+            this._element.addStyleClass("selected");
+
+        if (!supressSelectedEvent)
+            this.dispatchEventToListeners("selected");
+    },
+
+    deselect: function(supressDeselectedEvent)
+    {
+        if (!this.dataGrid || this.dataGrid.selectedNode !== this || !this.selected)
+            return;
+
+        this._selected = false;
+        this.dataGrid.selectedNode = null;
+
+        if (this._element)
+            this._element.removeStyleClass("selected");
+
+        if (!supressDeselectedEvent)
+            this.dispatchEventToListeners("deselected");
+    },
+
+    traverseNextNode: function(skipHidden, stayWithin, dontPopulate, info)
+    {
+        if (!dontPopulate && this.hasChildren)
+            this.dispatchEventToListeners("populate");
+
+        if (info)
+            info.depthChange = 0;
+
+        var node = (!skipHidden || this.revealed) ? this.children[0] : null;
+        if (node && (!skipHidden || this.expanded)) {
+            if (info)
+                info.depthChange = 1;
+            return node;
+        }
+
+        if (this === stayWithin)
+            return null;
+
+        node = (!skipHidden || this.revealed) ? this.nextSibling : null;
+        if (node)
+            return node;
+
+        node = this;
+        while (node && !node.root && !((!skipHidden || node.revealed) ? node.nextSibling : null) && node.parent !== stayWithin) {
+            if (info)
+                info.depthChange -= 1;
+            node = node.parent;
+        }
+
+        if (!node)
+            return null;
+
+        return (!skipHidden || node.revealed) ? node.nextSibling : null;
+    },
+
+    traversePreviousNode: function(skipHidden, dontPopulate)
+    {
+        var node = (!skipHidden || this.revealed) ? this.previousSibling : null;
+        if (!dontPopulate && node && node.hasChildren)
+            node.dispatchEventToListeners("populate");
+
+        while (node && ((!skipHidden || (node.revealed && node.expanded)) ? node.children[node.children.length - 1] : null)) {
+            if (!dontPopulate && node.hasChildren)
+                node.dispatchEventToListeners("populate");
+            node = ((!skipHidden || (node.revealed && node.expanded)) ? node.children[node.children.length - 1] : null);
+        }
+
+        if (node)
+            return node;
+
+        if (!this.parent || this.parent.root)
+            return null;
+
+        return this.parent;
+    },
+
+    isEventWithinDisclosureTriangle: function(event)
+    {
+        if (!this.hasChildren)
+            return false;
+        var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
+        if (!cell.hasStyleClass("disclosure"))
+            return false;
+        var computedLeftPadding = window.getComputedStyle(cell).getPropertyCSSValue("padding-left").getFloatValue(CSSPrimitiveValue.CSS_PX);
+        var left = cell.totalOffsetLeft + computedLeftPadding;
+        return event.pageX >= left && event.pageX <= left + this.disclosureToggleWidth;
+    },
+
+    _attach: function()
+    {
+        if (!this.dataGrid || this._attached)
+            return;
+
+        this._attached = true;
+
+        var nextNode = null;
+        var previousNode = this.traversePreviousNode(true, true);
+        if (previousNode && previousNode.element.parentNode && previousNode.element.nextSibling)
+            var nextNode = previousNode.element.nextSibling;
+        if (!nextNode)
+            nextNode = this.dataGrid.dataTableBody.lastChild;
+        this.dataGrid.dataTableBody.insertBefore(this.element, nextNode);
+
+        if (this.expanded)
+            for (var i = 0; i < this.children.length; ++i)
+                this.children[i]._attach();
+    },
+
+    _detach: function()
+    {
+        if (!this._attached)
+            return;
+
+        this._attached = false;
+
+        if (this._element && this._element.parentNode)
+            this._element.parentNode.removeChild(this._element);
+
+        for (var i = 0; i < this.children.length; ++i)
+            this.children[i]._detach();
+    }
+}
+
+WebInspector.DataGridNode.prototype.__proto__ = WebInspector.Object.prototype;
index c5792af..ddc6026 100644 (file)
@@ -150,7 +150,9 @@ WebInspector.DatabaseQueryView.prototype = {
 
     _queryFinished: function(query, tx, result)
     {
-        this._appendQueryResult(query, WebInspector.panels.databases._tableForResult(result));
+        var dataGrid = WebInspector.panels.databases.dataGridForResult(result);
+        dataGrid.element.addStyleClass("inline");
+        this._appendQueryResult(query, dataGrid.element);
 
         if (query.match(/^create /i) || query.match(/^drop table /i))
             WebInspector.panels.databases.updateDatabaseTables(this.database);
index ccf7590..2e72240 100644 (file)
@@ -55,8 +55,8 @@ WebInspector.DatabaseTableView.prototype = {
     {
         this.element.removeChildren();
 
-        var table = WebInspector.panels.databases._tableForResult(result);
-        if (!table) {
+        var dataGrid = WebInspector.panels.databases.dataGridForResult(result);
+        if (!dataGrid) {
             var emptyMsgElement = document.createElement("div");
             emptyMsgElement.className = "database-table-empty";
             emptyMsgElement.textContent = WebInspector.UIString("The ā€œ%sā€\ntable is empty.", this.tableName);
@@ -64,22 +64,7 @@ WebInspector.DatabaseTableView.prototype = {
             return;
         }
 
-        var rowCount = table.getElementsByTagName("tr").length;
-        var columnCount = table.getElementsByTagName("tr").item(0).getElementsByTagName("th").length;
-
-        var tr = document.createElement("tr");
-        tr.className = "database-result-filler-row";
-        table.appendChild(tr);
-
-        if (!(rowCount % 2))
-            tr.addStyleClass("alternate");
-
-        for (var i = 0; i < columnCount; ++i) {
-            var td = document.createElement("td");
-            tr.appendChild(td);
-        }
-
-        this.element.appendChild(table);
+        this.element.appendChild(dataGrid.element);
     },
 
     _queryError: function(tx, error)
index ed28344..74c9e80 100644 (file)
@@ -162,79 +162,63 @@ WebInspector.DatabasesPanel.prototype = {
         }
     },
 
-    _tableForResult: function(result)
+    dataGridForResult: function(result)
     {
         if (!result.rows.length)
             return null;
 
-        var rows = result.rows;
-        var length = rows.length;
-        var columnWidths = [];
-
-        var table = document.createElement("table");
-        table.className = "data-grid";
-
-        var headerRow = document.createElement("tr");
-        table.appendChild(headerRow);
+        var columns = {};
 
-        var j = 0;
-        for (var column in rows.item(0)) {
-            var th = document.createElement("th");
-            headerRow.appendChild(th);
-
-            var div = document.createElement("div");
-            div.textContent = column;
-            div.title = column;
-            th.appendChild(div);
+        var rows = result.rows;
+        for (var columnIdentifier in rows.item(0)) {
+            var column = {};
+            column.width = columnIdentifier.length;
+            column.title = columnIdentifier;
 
-            columnWidths[j++] = column.length;
+            columns[columnIdentifier] = column;
         }
 
+        var nodes = [];
+        var length = rows.length;
         for (var i = 0; i < length; ++i) {
+            var data = {};
+
             var row = rows.item(i);
-            var tr = document.createElement("tr");
-            if (i % 2)
-                tr.className = "alternate";
-            table.appendChild(tr);
-
-            var j = 0;
-            for (var column in row) {
-                var td = document.createElement("td");
-                tr.appendChild(td);
-
-                var text = row[column];
-                var div = document.createElement("div");
-                div.textContent = text;
-                div.title = text;
-                td.appendChild(div);
-
-                if (text.length > columnWidths[j])
-                    columnWidths[j] = text.length;
-                ++j;
+            for (var columnIdentifier in row) {
+                var text = row[columnIdentifier];
+                data[columnIdentifier] = text;
+                if (text.length > columns[columnIdentifier].width)
+                    columns[columnIdentifier].width = text.length;
             }
+
+            var node = new WebInspector.DataGridNode(data, false);
+            node.selectable = false;
+            nodes.push(node);
         }
 
         var totalColumnWidths = 0;
-        length = columnWidths.length;
-        for (var i = 0; i < length; ++i)
-            totalColumnWidths += columnWidths[i];
+        for (var columnIdentifier in columns)
+            totalColumnWidths += columns[columnIdentifier].width;
 
         // Calculate the percentage width for the columns.
-        var minimumPrecent = 5;
+        const minimumPrecent = 5;
         var recoupPercent = 0;
-        for (var i = 0; i < length; ++i) {
-            columnWidths[i] = Math.round((columnWidths[i] / totalColumnWidths) * 100);
-            if (columnWidths[i] < minimumPrecent) {
-                recoupPercent += (minimumPrecent - columnWidths[i]);
-                columnWidths[i] = minimumPrecent;
+        for (var columnIdentifier in columns) {
+            var width = columns[columnIdentifier].width;
+            width = Math.round((width / totalColumnWidths) * 100);
+            if (width < minimumPrecent) {
+                recoupPercent += (minimumPrecent - width);
+                width = minimumPrecent;
             }
+
+            columns[columnIdentifier].width = width;
         }
 
         // Enforce the minimum percentage width.
         while (recoupPercent > 0) {
-            for (var i = 0; i < length; ++i) {
-                if (columnWidths[i] > minimumPrecent) {
-                    --columnWidths[i];
+            for (var columnIdentifier in columns) {
+                if (columns[columnIdentifier].width > minimumPrecent) {
+                    --columns[columnIdentifier].width;
                     --recoupPercent;
                     if (!recoupPercent)
                         break;
@@ -242,13 +226,16 @@ WebInspector.DatabasesPanel.prototype = {
             }
         }
 
-        length = headerRow.childNodes.length;
-        for (var i = 0; i < length; ++i) {
-            var th = headerRow.childNodes[i];
-            th.style.width = columnWidths[i] + "%";
-        }
+        // Change the width property to a string suitable for a style width.
+        for (var columnIdentifier in columns)
+            columns[columnIdentifier].width += "%";
+
+        var dataGrid = new WebInspector.DataGrid(columns);
+        var length = nodes.length;
+        for (var i = 0; i < length; ++i)
+            dataGrid.appendChild(nodes[i]);
 
-        return table;
+        return dataGrid;
     },
 
     _startSidebarDragging: function(event)
index 8c80b6b..6cbefb7 100644 (file)
Binary files a/WebCore/page/inspector/Images/glossyHeader.png and b/WebCore/page/inspector/Images/glossyHeader.png differ
index 6b0dd60..1153506 100644 (file)
Binary files a/WebCore/page/inspector/Images/glossyHeaderPressed.png and b/WebCore/page/inspector/Images/glossyHeaderPressed.png differ
diff --git a/WebCore/page/inspector/Images/glossyHeaderSelected.png b/WebCore/page/inspector/Images/glossyHeaderSelected.png
new file mode 100644 (file)
index 0000000..71d5af6
Binary files /dev/null and b/WebCore/page/inspector/Images/glossyHeaderSelected.png differ
diff --git a/WebCore/page/inspector/Images/glossyHeaderSelectedPressed.png b/WebCore/page/inspector/Images/glossyHeaderSelectedPressed.png
new file mode 100644 (file)
index 0000000..7047dbe
Binary files /dev/null and b/WebCore/page/inspector/Images/glossyHeaderSelectedPressed.png differ
diff --git a/WebCore/page/inspector/Images/glossySelected.png b/WebCore/page/inspector/Images/glossySelected.png
deleted file mode 100644 (file)
index 20b1f1a..0000000
Binary files a/WebCore/page/inspector/Images/glossySelected.png and /dev/null differ
index cbf149b..abcd866 100644 (file)
@@ -5,6 +5,7 @@
     <file>CallStackSidebarPane.js</file>
     <file>Console.js</file>
     <file>Database.js</file>
+    <file>DataGrid.js</file>
     <file>DatabaseQueryView.js</file>
     <file>DatabasesPanel.js</file>
     <file>DatabaseTableView.js</file>
index 8095a71..707036d 100644 (file)
@@ -1307,9 +1307,7 @@ body.inactive .sidebar {
 }
 
 .database-view.table {
-    font-size: 10px;
-    overflow-y: auto;
-    overflow-x: hidden;
+    overflow: hidden;
 }
 
 .database-view.table .data-grid {
@@ -1339,51 +1337,96 @@ body.inactive .sidebar {
     color: rgb(66%, 33%, 33%);
 }
 
-.data-grid .database-result-filler-row {
-    height: auto;
-}
-
-.data-grid .database-result-filler-row.alternate td {
-    background-position-y: 16px;
+.data-grid {
+    position: relative;
+    border: 1px solid #aaa;
 }
 
-.database-result-filler-row td {
-    background-image: -webkit-gradient(linear, left top, left bottom, from(white), color-stop(0.5, white), color-stop(0.5, rgb(234, 243, 255)), to(rgb(234, 243, 255)));
-    -webkit-background-size: auto 32px;
-    -webkit-background-origin: padding;
-    -webkit-background-clip: padding;
+.data-grid:focus {
+    outline: none;
 }
 
-.data-grid {
-    border: 1px solid #aaa;
+.data-grid table {
     table-layout: fixed;
     border-spacing: 0;
     border-collapse: collapse;
     width: 100%;
+    font-size: 10px;
+    font-family: Lucida Grande, sans-serif;
+}
+
+.data-grid .data-container {
+    position: absolute;
+    top: 16px;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    padding-right: 14px;
+    overflow-x: hidden;
+    overflow-y: overlay;
+    background-image: -webkit-gradient(linear, left top, left bottom, from(white), color-stop(0.5, white), color-stop(0.5, rgb(234, 243, 255)), to(rgb(234, 243, 255)));
+    -webkit-background-size: 1px 32px;
+}
+
+.data-grid.inline .data-container {
+    position: static;
 }
 
 .data-grid th {
     text-align: left;
-    background: url(Images/glossyHeader.png) repeat-x;
-    border-right: 1px solid #aaa;
+    background-image: url(Images/glossyHeader.png);
+    background-repeat: repeat-x;
+    border-right: 1px solid rgb(179, 179, 179);
+    border-bottom: 1px solid rgb(179, 179, 179);
     height: 15px;
-    border-bottom: 1px solid #aaa;
     font-weight: normal;
     vertical-align: middle;
     padding: 0 4px;
     white-space: nowrap;
 }
 
-.data-grid tr {
-    height: 16px;
+.data-grid th.corner {
+    width: 15px;
+    border-right: 0 none transparent;
+}
+
+.data-grid tr.filler {
+    display: table-row !important;
+    height: auto !important;
+}
+
+.data-grid tr.filler td {
+    height: auto !important;
+    padding: 0 !important;
+}
+
+.data-grid table.data {
+    position: absolute;
+    left: 0;
+    top: 0;
+    right: 16px;
+    bottom: 0;
+    height: 100%;
+    border-top: 0 none transparent;
+    background-image: -webkit-gradient(linear, left top, left bottom, from(white), color-stop(0.5, white), color-stop(0.5, rgb(234, 243, 255)), to(rgb(234, 243, 255)));
+    -webkit-background-size: 1px 32px;
+}
+
+.data-grid.inline table.data {
+    position: static;
+}
+
+.data-grid table.data tr {
+    display: none;
 }
 
-.data-grid tr.alternate {
-    background-color: rgb(236, 243, 254);
+.data-grid table.data tr.revealed {
+    display: table-row;
 }
 
 .data-grid td {
     vertical-align: top;
+    height: 12px;
     padding: 2px 4px;
     white-space: nowrap;
     border-right: 1px solid #aaa;
@@ -1396,40 +1439,86 @@ body.inactive .sidebar {
     overflow: hidden;
 }
 
-.data-grid th.narrow {
-    width: 75px;
+.data-grid th.sortable div {
+    position: relative;
 }
 
-.data-grid th.selected {
-    border-top: 1px solid rgb(156, 168, 207);
-    border-bottom: 1px solid rgb(107, 139, 195);
-    border-left: url(Images/glossySelected.png);
-    border-right: url(Images/glossySelected.png);
-    background: url(Images/glossySelected.png) repeat-x;
+.data-grid th.sortable:active {
+    background-image: url(Images/glossyHeaderPressed.png);
 }
 
-.data-grid th.sort-ascending::before {
-    float: right;
-    display: inline-block;
-    margin-bottom: 2px;
-    margin-right: 3px;
+.data-grid th.sort-ascending, .data-grid th.sort-descending {
+    border-right: 1px solid rgb(107, 140, 196);
+    border-bottom: 1px solid rgb(107, 140, 196);
+    background-image: url(Images/glossyHeaderSelected.png);
+    background-repeat: repeat-x;
+}
+
+.data-grid th.sortable.sort-ascending:active, .data-grid th.sortable.sort-descending:active {
+    background-image: url(Images/glossyHeaderSelectedPressed.png);
+}
+
+.data-grid th.sort-ascending div::after {
+    position: absolute;
+    top: 0;
+    right: 0;
     width: 8px;
     height: 8px;
     content: url(Images/treeUpTriangleBlack.png);
 }
 
-.data-grid th.sort-descending::before {
-    float: right;
-    display: inline-block;
-    margin-top: 2px;
-    margin-right: 3px;
+.data-grid th.sort-descending div::after {
+    position: absolute;
+    top: 0;
+    right: 0;
+    margin-top: 1px;
     width: 8px;
     height: 8px;
     content: url(Images/treeDownTriangleBlack.png);
 }
 
 body.inactive .data-grid th.sort-ascending, body.inactive .data-grid th.sort-descending {
-    background: url(Images/glossyHeader.png) repeat-x;
+    background-image: url(Images/glossyHeader.png);
+    border-right: 1px solid rgb(179, 179, 179);
+    border-bottom: 1px solid rgb(179, 179, 179);
+}
+
+.data-grid tr.parent td.disclosure::before {
+    float: left;
+    content: url(Images/treeRightTriangleBlack.png);
+    width: 8px;
+    height: 8px;
+    margin-right: 2px;
+    -webkit-user-select: none;
+}
+
+.data-grid tr.expanded td.disclosure::before {
+    content: url(Images/treeDownTriangleBlack.png);
+    width: 8px;
+    height: 8px;
+    margin-top: 1px;
+}
+
+.data-grid tr.selected {
+    background-color: rgb(212, 212, 212);
+    color: inherit;
+}
+
+.data-grid:focus tr.selected {
+    background-color: rgb(56, 121, 217);
+    color: white;
+}
+
+.data-grid:focus tr.parent.selected td.disclosure::before {
+    content: url(Images/treeRightTriangleWhite.png);
+}
+
+.data-grid:focus tr.expanded.selected td.disclosure::before {
+    content: url(Images/treeDownTriangleWhite.png);
+}
+
+.data-grid tr:not(.parent) td.disclosure {
+    text-indent: 10px;
 }
 
 .database-view.query {
index 81393f2..cb0bff8 100644 (file)
@@ -41,6 +41,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     <script type="text/javascript" src="Resource.js"></script>
     <script type="text/javascript" src="ResourceCategory.js"></script>
     <script type="text/javascript" src="Database.js"></script>
+    <script type="text/javascript" src="DataGrid.js"></script>
     <script type="text/javascript" src="Script.js"></script>
     <script type="text/javascript" src="Breakpoint.js"></script>
     <script type="text/javascript" src="SidebarPane.js"></script>