tizen beta release
[profile/ivi/webkit-efl.git] / debian / tmp / usr / share / ewebkit-0 / webinspector / DataGrid.js
1 /*
2  * Copyright (C) 2008 Apple Inc. All Rights Reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *        notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *        notice, this list of conditions and the following disclaimer in the
11  *        documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.         IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 /**
27  * @constructor
28  * @extends {WebInspector.View}
29  * @param {function(WebInspector.DataGridNode, number, string, string)=} editCallback
30  * @param {function(WebInspector.DataGridNode)=} deleteCallback
31  */
32 WebInspector.DataGrid = function(columns, editCallback, deleteCallback)
33 {
34     WebInspector.View.call(this);
35     this.registerRequiredCSS("dataGrid.css");
36
37     this.element.className = "data-grid";
38     this.element.tabIndex = 0;
39     this.element.addEventListener("keydown", this._keyDown.bind(this), false);
40
41     this._headerTable = document.createElement("table");
42     this._headerTable.className = "header";
43     this._headerTableHeaders = {};
44
45     this._dataTable = document.createElement("table");
46     this._dataTable.className = "data";
47
48     this._dataTable.addEventListener("mousedown", this._mouseDownInDataTable.bind(this), true);
49     this._dataTable.addEventListener("click", this._clickInDataTable.bind(this), true);
50
51     this._dataTable.addEventListener("contextmenu", this._contextMenuInDataTable.bind(this), true);
52
53     // FIXME: Add a createCallback which is different from editCallback and has different
54     // behavior when creating a new node.
55     if (editCallback) {
56         this._dataTable.addEventListener("dblclick", this._ondblclick.bind(this), false);
57         this._editCallback = editCallback;
58     }
59     if (deleteCallback)
60         this._deleteCallback = deleteCallback;
61
62     this.aligned = {};
63
64     this._scrollContainer = document.createElement("div");
65     this._scrollContainer.className = "data-container";
66     this._scrollContainer.appendChild(this._dataTable);
67
68     this.element.appendChild(this._headerTable);
69     this.element.appendChild(this._scrollContainer);
70
71     var headerRow = document.createElement("tr");
72     var columnGroup = document.createElement("colgroup");
73     this._columnCount = 0;
74
75     for (var columnIdentifier in columns) {
76         var column = columns[columnIdentifier];
77         if (column.disclosure)
78             this.disclosureColumnIdentifier = columnIdentifier;
79
80         var col = document.createElement("col");
81         if (column.width)
82             col.style.width = column.width;
83         column.element = col;
84         columnGroup.appendChild(col);
85
86         var cell = document.createElement("th");
87         cell.className = columnIdentifier + "-column";
88         cell.columnIdentifier = columnIdentifier;
89         this._headerTableHeaders[columnIdentifier] = cell;
90
91         var div = document.createElement("div");
92         if (column.titleDOMFragment)
93             div.appendChild(column.titleDOMFragment);
94         else
95             div.textContent = column.title;
96         cell.appendChild(div);
97
98         if (column.sort) {
99             cell.addStyleClass("sort-" + column.sort);
100             this._sortColumnCell = cell;
101         }
102
103         if (column.sortable) {
104             cell.addEventListener("click", this._clickInHeaderCell.bind(this), false);
105             cell.addStyleClass("sortable");
106         }
107
108         if (column.aligned)
109             this.aligned[columnIdentifier] = column.aligned;
110
111         headerRow.appendChild(cell);
112
113         ++this._columnCount;
114     }
115
116     columnGroup.span = this._columnCount;
117
118     var cell = document.createElement("th");
119     cell.className = "corner";
120     headerRow.appendChild(cell);
121
122     this._headerTableColumnGroup = columnGroup;
123     this._headerTable.appendChild(this._headerTableColumnGroup);
124     this.headerTableBody.appendChild(headerRow);
125
126     var fillerRow = document.createElement("tr");
127     fillerRow.className = "filler";
128
129     for (var columnIdentifier in columns) {
130         var column = columns[columnIdentifier];
131         var td = document.createElement("td");
132         td.className = columnIdentifier + "-column";
133         fillerRow.appendChild(td);
134     }
135
136     this._dataTableColumnGroup = columnGroup.cloneNode(true);
137     this._dataTable.appendChild(this._dataTableColumnGroup);
138     this.dataTableBody.appendChild(fillerRow);
139
140     this.columns = columns || {};
141     this._columnsArray = [];
142     for (var columnIdentifier in columns) {
143         columns[columnIdentifier].ordinal = this._columnsArray.length;
144         columns[columnIdentifier].identifier = columnIdentifier;
145         this._columnsArray.push(columns[columnIdentifier]);
146     }
147
148     for (var i = 0; i < this._columnsArray.length; ++i)
149         this._columnsArray[i].bodyElement = this._dataTableColumnGroup.children[i];
150
151     this.children = [];
152     this.selectedNode = null;
153     this.expandNodesWhenArrowing = false;
154     this.root = true;
155     this.hasChildren = false;
156     this.expanded = true;
157     this.revealed = true;
158     this.selected = false;
159     this.dataGrid = this;
160     this.indentWidth = 15;
161     this.resizers = [];
162     this._columnWidthsInitialized = false;
163 }
164
165 /**
166  * @param {Array.<string>} columnNames
167  * @param {Array.<string>} values
168  */
169 WebInspector.DataGrid.createSortableDataGrid = function(columnNames, values)
170 {
171     var numColumns = columnNames.length;
172     if (!numColumns)
173         return null;
174
175     var columns = {};
176
177     for (var i = 0; i < columnNames.length; ++i) {
178         var column = {};
179         column.width = columnNames[i].length;
180         column.title = columnNames[i];
181         column.sortable = true;
182
183         columns[columnNames[i]] = column;
184     }
185
186     var nodes = [];
187     for (var i = 0; i < values.length / numColumns; ++i) {
188         var data = {};
189         for (var j = 0; j < columnNames.length; ++j)
190             data[columnNames[j]] = values[numColumns * i + j];
191
192         var node = new WebInspector.DataGridNode(data, false);
193         node.selectable = false;
194         nodes.push(node);
195     }
196
197     var dataGrid = new WebInspector.DataGrid(columns);
198     var length = nodes.length;
199     for (var i = 0; i < length; ++i)
200         dataGrid.appendChild(nodes[i]);
201
202     dataGrid.addEventListener("sorting changed", sortDataGrid, this);
203
204     function sortDataGrid()
205     {
206         var nodes = dataGrid.children.slice();
207         var sortColumnIdentifier = dataGrid.sortColumnIdentifier;
208         var sortDirection = dataGrid.sortOrder === "ascending" ? 1 : -1;
209         var columnIsNumeric = true;
210
211         for (var i = 0; i < nodes.length; i++) {
212             if (isNaN(Number(nodes[i].data[sortColumnIdentifier])))
213                 columnIsNumeric = false;
214         }
215
216         function comparator(dataGridNode1, dataGridNode2)
217         {
218             var item1 = dataGridNode1.data[sortColumnIdentifier];
219             var item2 = dataGridNode2.data[sortColumnIdentifier];
220
221             var comparison;
222             if (columnIsNumeric) {
223                 // Sort numbers based on comparing their values rather than a lexicographical comparison.
224                 var number1 = parseFloat(item1);
225                 var number2 = parseFloat(item2);
226                 comparison = number1 < number2 ? -1 : (number1 > number2 ? 1 : 0);
227             } else
228                 comparison = item1 < item2 ? -1 : (item1 > item2 ? 1 : 0);
229
230             return sortDirection * comparison;
231         }
232
233         nodes.sort(comparator);
234         dataGrid.removeChildren();
235         for (var i = 0; i < nodes.length; i++)
236             dataGrid.appendChild(nodes[i]);
237     }
238     return dataGrid;
239 }
240
241 WebInspector.DataGrid.prototype = {
242     get refreshCallback()
243     {
244         return this._refreshCallback;
245     },
246
247     set refreshCallback(refreshCallback)
248     {
249         this._refreshCallback = refreshCallback;
250     },
251
252     _ondblclick: function(event)
253     {
254         if (this._editing || this._editingNode)
255             return;
256
257         this._startEditing(event.target);
258     },
259
260     _startEditingColumnOfDataGridNode: function(node, column)
261     {
262         this._editing = true;
263         this._editingNode = node;
264         this._editingNode.select();
265
266         var element = this._editingNode._element.children[column];
267         WebInspector.startEditing(element, this._startEditingConfig(element));
268         window.getSelection().setBaseAndExtent(element, 0, element, 1);
269     },
270
271     _startEditing: function(target)
272     {
273         var element = target.enclosingNodeOrSelfWithNodeName("td");
274         if (!element)
275             return;
276
277         this._editingNode = this.dataGridNodeFromNode(target);
278         if (!this._editingNode) {
279             if (!this.creationNode)
280                 return;
281             this._editingNode = this.creationNode;
282         }
283
284         // Force editing the 1st column when editing the creation node
285         if (this._editingNode.isCreationNode)
286             return this._startEditingColumnOfDataGridNode(this._editingNode, 0);
287
288         this._editing = true;
289         WebInspector.startEditing(element, this._startEditingConfig(element));
290
291         window.getSelection().setBaseAndExtent(element, 0, element, 1);
292     },
293
294
295     _startEditingConfig: function(element)
296     {
297         return new WebInspector.EditingConfig(this._editingCommitted.bind(this), this._editingCancelled.bind(this), element.textContent);
298     },
299
300     _editingCommitted: function(element, newText, oldText, context, moveDirection)
301     {
302         // FIXME: We need more column identifiers here throughout this function.
303         // Not needed yet since only editable DataGrid is DOM Storage, which is Key - Value.
304
305         // FIXME: Better way to do this than regular expressions?
306         var columnIdentifier = parseInt(element.className.match(/\b(\d+)-column\b/)[1], 10);
307
308         var textBeforeEditing = this._editingNode.data[columnIdentifier];
309         var currentEditingNode = this._editingNode;
310
311         function moveToNextIfNeeded(wasChange) {
312             if (!moveDirection)
313                 return;
314
315             if (moveDirection === "forward") {
316                 if (currentEditingNode.isCreationNode && columnIdentifier === 0 && !wasChange)
317                     return;
318
319                 if (columnIdentifier === 0)
320                     return this._startEditingColumnOfDataGridNode(currentEditingNode, 1);
321
322                 var nextDataGridNode = currentEditingNode.traverseNextNode(true, null, true);
323                 if (nextDataGridNode)
324                     return this._startEditingColumnOfDataGridNode(nextDataGridNode, 0);
325                 if (currentEditingNode.isCreationNode && wasChange) {
326                     this.addCreationNode(false);
327                     return this._startEditingColumnOfDataGridNode(this.creationNode, 0);
328                 }
329                 return;
330             }
331
332             if (moveDirection === "backward") {
333                 if (columnIdentifier === 1)
334                     return this._startEditingColumnOfDataGridNode(currentEditingNode, 0);
335                     var nextDataGridNode = currentEditingNode.traversePreviousNode(true, null, true);
336
337                 if (nextDataGridNode)
338                     return this._startEditingColumnOfDataGridNode(nextDataGridNode, 1);
339                 return;
340             }
341         }
342
343         if (textBeforeEditing == newText) {
344             this._editingCancelled(element);
345             moveToNextIfNeeded.call(this, false);
346             return;
347         }
348
349         // Update the text in the datagrid that we typed
350         this._editingNode.data[columnIdentifier] = newText;
351
352         // Make the callback - expects an editing node (table row), the column number that is being edited,
353         // the text that used to be there, and the new text.
354         this._editCallback(this._editingNode, columnIdentifier, textBeforeEditing, newText);
355
356         if (this._editingNode.isCreationNode)
357             this.addCreationNode(false);
358
359         this._editingCancelled(element);
360         moveToNextIfNeeded.call(this, true);
361     },
362
363     _editingCancelled: function(element)
364     {
365         delete this._editing;
366         this._editingNode = null;
367     },
368
369     get sortColumnIdentifier()
370     {
371         if (!this._sortColumnCell)
372             return null;
373         return this._sortColumnCell.columnIdentifier;
374     },
375
376     get sortOrder()
377     {
378         if (!this._sortColumnCell || this._sortColumnCell.hasStyleClass("sort-ascending"))
379             return "ascending";
380         if (this._sortColumnCell.hasStyleClass("sort-descending"))
381             return "descending";
382         return null;
383     },
384
385     get headerTableBody()
386     {
387         if ("_headerTableBody" in this)
388             return this._headerTableBody;
389
390         this._headerTableBody = this._headerTable.getElementsByTagName("tbody")[0];
391         if (!this._headerTableBody) {
392             this._headerTableBody = this.element.ownerDocument.createElement("tbody");
393             this._headerTable.insertBefore(this._headerTableBody, this._headerTable.tFoot);
394         }
395
396         return this._headerTableBody;
397     },
398
399     get dataTableBody()
400     {
401         if ("_dataTableBody" in this)
402             return this._dataTableBody;
403
404         this._dataTableBody = this._dataTable.getElementsByTagName("tbody")[0];
405         if (!this._dataTableBody) {
406             this._dataTableBody = this.element.ownerDocument.createElement("tbody");
407             this._dataTable.insertBefore(this._dataTableBody, this._dataTable.tFoot);
408         }
409
410         return this._dataTableBody;
411     },
412
413     /**
414      * @param {number=} maxDescentLevel
415      */
416     autoSizeColumns: function(minPercent, maxPercent, maxDescentLevel)
417     {
418         if (minPercent)
419             minPercent = Math.min(minPercent, Math.floor(100 / this._columnCount));
420         var widths = {};
421         var columns = this.columns;
422         for (var columnIdentifier in columns)
423             widths[columnIdentifier] = (columns[columnIdentifier].title || "").length;
424
425         var children = maxDescentLevel ? this._enumerateChildren(this, [], maxDescentLevel + 1) : this.children;
426         for (var i = 0; i < children.length; ++i) {
427             var node = children[i];
428             for (var columnIdentifier in columns) {
429                 var text = node.data[columnIdentifier] || "";
430                 if (text.length > widths[columnIdentifier])
431                     widths[columnIdentifier] = text.length;
432             }
433         }
434
435         var totalColumnWidths = 0;
436         for (var columnIdentifier in columns)
437             totalColumnWidths += widths[columnIdentifier];
438
439         var recoupPercent = 0;
440         for (var columnIdentifier in columns) {
441             var width = Math.round(100 * widths[columnIdentifier] / totalColumnWidths);
442             if (minPercent && width < minPercent) {
443                 recoupPercent += (minPercent - width);
444                 width = minPercent;
445             } else if (maxPercent && width > maxPercent) {
446                 recoupPercent -= (width - maxPercent);
447                 width = maxPercent;
448             }
449             widths[columnIdentifier] = width;
450         }
451
452         while (minPercent && recoupPercent > 0) {
453             for (var columnIdentifier in columns) {
454                 if (widths[columnIdentifier] > minPercent) {
455                     --widths[columnIdentifier];
456                     --recoupPercent;
457                     if (!recoupPercent)
458                         break;
459                 }
460             }
461         }
462
463         while (maxPercent && recoupPercent < 0) {
464             for (var columnIdentifier in columns) {
465                 if (widths[columnIdentifier] < maxPercent) {
466                     ++widths[columnIdentifier];
467                     ++recoupPercent;
468                     if (!recoupPercent)
469                         break;
470                 }
471             }
472         }
473
474         for (var columnIdentifier in columns)
475             columns[columnIdentifier].element.style.width = widths[columnIdentifier] + "%";
476         this._columnWidthsInitialized = false;
477         this.updateWidths();
478     },
479
480     _enumerateChildren: function(rootNode, result, maxLevel)
481     {
482         if (!rootNode.root)
483             result.push(rootNode);
484         if (!maxLevel)
485             return;
486         for (var i = 0; i < rootNode.children.length; ++i)
487             this._enumerateChildren(rootNode.children[i], result, maxLevel - 1);
488         return result;
489     },
490
491     onResize: function()
492     {
493         this.updateWidths();
494     },
495
496     // Updates the widths of the table, including the positions of the column
497     // resizers.
498     //
499     // IMPORTANT: This function MUST be called once after the element of the
500     // DataGrid is attached to its parent element and every subsequent time the
501     // width of the parent element is changed in order to make it possible to
502     // resize the columns.
503     //
504     // If this function is not called after the DataGrid is attached to its
505     // parent element, then the DataGrid's columns will not be resizable.
506     updateWidths: function()
507     {
508         var headerTableColumns = this._headerTableColumnGroup.children;
509
510         var tableWidth = this._dataTable.offsetWidth;
511         var numColumns = headerTableColumns.length;
512
513         // Do not attempt to use offsetes if we're not attached to the document tree yet.
514         if (!this._columnWidthsInitialized && this.element.offsetWidth) {
515             // Give all the columns initial widths now so that during a resize,
516             // when the two columns that get resized get a percent value for
517             // their widths, all the other columns already have percent values
518             // for their widths.
519             for (var i = 0; i < numColumns; i++) {
520                 var columnWidth = this.headerTableBody.rows[0].cells[i].offsetWidth;
521                 var percentWidth = ((columnWidth / tableWidth) * 100) + "%";
522                 this._headerTableColumnGroup.children[i].style.width = percentWidth;
523                 this._dataTableColumnGroup.children[i].style.width = percentWidth;
524             }
525             this._columnWidthsInitialized = true;
526         }
527         this._positionResizers();
528         this.dispatchEventToListeners("width changed");
529     },
530
531     columnWidthsMap: function()
532     {
533         var result = {};
534         for (var i = 0; i < this._columnsArray.length; ++i) {
535             var width = this._headerTableColumnGroup.children[i].style.width;
536             result[this._columnsArray[i].columnIdentifier] = parseFloat(width);
537         }
538         return result;
539     },
540
541     applyColumnWidthsMap: function(columnWidthsMap)
542     {
543         for (var columnIdentifier in this.columns) {
544             var column = this.columns[columnIdentifier];
545             var width = (columnWidthsMap[columnIdentifier] || 0) + "%";
546             this._headerTableColumnGroup.children[column.ordinal].style.width = width;
547             this._dataTableColumnGroup.children[column.ordinal].style.width = width;
548         }
549
550         // Normalize widths
551         delete this._columnWidthsInitialized;
552         this.updateWidths();
553     },
554
555     isColumnVisible: function(columnIdentifier)
556     {
557         var column = this.columns[columnIdentifier];
558         var columnElement = column.element;
559         return !columnElement.hidden;
560     },
561
562     showColumn: function(columnIdentifier)
563     {
564         var column = this.columns[columnIdentifier];
565         var columnElement = column.element;
566         if (!columnElement.hidden)
567             return;
568
569         columnElement.hidden = false;
570         columnElement.removeStyleClass("hidden");
571
572         var columnBodyElement = column.bodyElement;
573         columnBodyElement.hidden = false;
574         columnBodyElement.removeStyleClass("hidden");
575     },
576
577     hideColumn: function(columnIdentifier)
578     {
579         var column = this.columns[columnIdentifier];
580         var columnElement = column.element;
581         if (columnElement.hidden)
582             return;
583
584         var oldWidth = parseFloat(columnElement.style.width);
585
586         columnElement.hidden = true;
587         columnElement.addStyleClass("hidden");
588         columnElement.style.width = 0;
589
590         var columnBodyElement = column.bodyElement;
591         columnBodyElement.hidden = true;
592         columnBodyElement.addStyleClass("hidden");
593         columnBodyElement.style.width = 0;
594
595         this._columnWidthsInitialized = false;
596     },
597
598     get scrollContainer()
599     {
600         return this._scrollContainer;
601     },
602
603     isScrolledToLastRow: function()
604     {
605         return this._scrollContainer.isScrolledToBottom();
606     },
607
608     scrollToLastRow: function()
609     {
610         this._scrollContainer.scrollTop = this._scrollContainer.scrollHeight - this._scrollContainer.offsetHeight;
611     },
612
613     _positionResizers: function()
614     {
615         var headerTableColumns = this._headerTableColumnGroup.children;
616         var numColumns = headerTableColumns.length;
617         var left = 0;
618         var previousResizer = null;
619
620         // Make n - 1 resizers for n columns.
621         for (var i = 0; i < numColumns - 1; i++) {
622             var resizer = this.resizers[i];
623
624             if (!resizer) {
625                 // This is the first call to updateWidth, so the resizers need
626                 // to be created.
627                 resizer = document.createElement("div");
628                 resizer.addStyleClass("data-grid-resizer");
629                 // This resizer is associated with the column to its right.
630                 resizer.addEventListener("mousedown", this._startResizerDragging.bind(this), false);
631                 this.element.appendChild(resizer);
632                 this.resizers[i] = resizer;
633             }
634
635             // Get the width of the cell in the first (and only) row of the
636             // header table in order to determine the width of the column, since
637             // it is not possible to query a column for its width.
638             left += this.headerTableBody.rows[0].cells[i].offsetWidth;
639
640             var columnIsVisible = !this._headerTableColumnGroup.children[i].hidden;
641             if (columnIsVisible) {
642                 resizer.style.removeProperty("display");
643                 resizer.style.left = left + "px";
644                 resizer.leftNeighboringColumnID = i;
645                 if (previousResizer)
646                     previousResizer.rightNeighboringColumnID = i;
647                 previousResizer = resizer;
648             } else {
649                 resizer.style.setProperty("display", "none");
650                 resizer.leftNeighboringColumnID = 0;
651                 resizer.rightNeighboringColumnID = 0;
652             }
653         }
654         if (previousResizer)
655             previousResizer.rightNeighboringColumnID = numColumns - 1;
656     },
657
658     addCreationNode: function(hasChildren)
659     {
660         if (this.creationNode)
661             this.creationNode.makeNormal();
662
663         var emptyData = {};
664         for (var column in this.columns)
665             emptyData[column] = '';
666         this.creationNode = new WebInspector.CreationDataGridNode(emptyData, hasChildren);
667         this.appendChild(this.creationNode);
668     },
669
670     appendChild: function(child)
671     {
672         this.insertChild(child, this.children.length);
673     },
674
675     insertChild: function(child, index)
676     {
677         if (!child)
678             throw("insertChild: Node can't be undefined or null.");
679         if (child.parent === this)
680             throw("insertChild: Node is already a child of this node.");
681
682         if (child.parent)
683             child.parent.removeChild(child);
684
685         this.children.splice(index, 0, child);
686         this.hasChildren = true;
687
688         child.parent = this;
689         child.dataGrid = this.dataGrid;
690         child._recalculateSiblings(index);
691
692         delete child._depth;
693         delete child._revealed;
694         delete child._attached;
695         child._shouldRefreshChildren = true;
696
697         var current = child.children[0];
698         while (current) {
699             current.dataGrid = this.dataGrid;
700             delete current._depth;
701             delete current._revealed;
702             delete current._attached;
703             current._shouldRefreshChildren = true;
704             current = current.traverseNextNode(false, child, true);
705         }
706
707         if (this.expanded)
708             child._attach();
709     },
710
711     removeChild: function(child)
712     {
713         if (!child)
714             throw("removeChild: Node can't be undefined or null.");
715         if (child.parent !== this)
716             throw("removeChild: Node is not a child of this node.");
717
718         child.deselect();
719         child._detach();
720
721         this.children.remove(child, true);
722
723         if (child.previousSibling)
724             child.previousSibling.nextSibling = child.nextSibling;
725         if (child.nextSibling)
726             child.nextSibling.previousSibling = child.previousSibling;
727
728         child.dataGrid = null;
729         child.parent = null;
730         child.nextSibling = null;
731         child.previousSibling = null;
732
733         if (this.children.length <= 0)
734             this.hasChildren = false;
735     },
736
737     removeChildren: function()
738     {
739         for (var i = 0; i < this.children.length; ++i) {
740             var child = this.children[i];
741             child.deselect();
742             child._detach();
743
744             child.dataGrid = null;
745             child.parent = null;
746             child.nextSibling = null;
747             child.previousSibling = null;
748         }
749
750         this.children = [];
751         this.hasChildren = false;
752     },
753
754     removeChildrenRecursive: function()
755     {
756         var childrenToRemove = this.children;
757
758         var child = this.children[0];
759         while (child) {
760             if (child.children.length)
761                 childrenToRemove = childrenToRemove.concat(child.children);
762             child = child.traverseNextNode(false, this, true);
763         }
764
765         for (var i = 0; i < childrenToRemove.length; ++i) {
766             child = childrenToRemove[i];
767             child.deselect();
768             child._detach();
769
770             child.children = [];
771             child.dataGrid = null;
772             child.parent = null;
773             child.nextSibling = null;
774             child.previousSibling = null;
775         }
776
777         this.children = [];
778     },
779
780     sortNodes: function(comparator, reverseMode)
781     {
782         function comparatorWrapper(a, b)
783         {
784             if (a._dataGridNode._data.summaryRow)
785                 return 1;
786             if (b._dataGridNode._data.summaryRow)
787                 return -1;
788
789             var aDataGirdNode = a._dataGridNode;
790             var bDataGirdNode = b._dataGridNode;
791             return reverseMode ? comparator(bDataGirdNode, aDataGirdNode) : comparator(aDataGirdNode, bDataGirdNode);
792         }
793
794         var tbody = this.dataTableBody;
795         var tbodyParent = tbody.parentElement;
796         tbodyParent.removeChild(tbody);
797
798         var childNodes = tbody.childNodes;
799         var fillerRow = childNodes[childNodes.length - 1];
800
801         var sortedRows = Array.prototype.slice.call(childNodes, 0, childNodes.length - 1);
802         sortedRows.sort(comparatorWrapper);
803         var sortedRowsLength = sortedRows.length;
804
805         tbody.removeChildren();
806         var previousSiblingNode = null;
807         for (var i = 0; i < sortedRowsLength; ++i) {
808             var row = sortedRows[i];
809             var node = row._dataGridNode;
810             node.previousSibling = previousSiblingNode;
811             if (previousSiblingNode)
812                 previousSiblingNode.nextSibling = node;
813             tbody.appendChild(row);
814             previousSiblingNode = node;
815         }
816         if (previousSiblingNode)
817             previousSiblingNode.nextSibling = null;
818
819         tbody.appendChild(fillerRow);
820         tbodyParent.appendChild(tbody);
821     },
822
823     _keyDown: function(event)
824     {
825         if (!this.selectedNode || event.shiftKey || event.metaKey || event.ctrlKey || this._editing)
826             return;
827
828         var handled = false;
829         var nextSelectedNode;
830         if (event.keyIdentifier === "Up" && !event.altKey) {
831             nextSelectedNode = this.selectedNode.traversePreviousNode(true);
832             while (nextSelectedNode && !nextSelectedNode.selectable)
833                 nextSelectedNode = nextSelectedNode.traversePreviousNode(true);
834             handled = nextSelectedNode ? true : false;
835         } else if (event.keyIdentifier === "Down" && !event.altKey) {
836             nextSelectedNode = this.selectedNode.traverseNextNode(true);
837             while (nextSelectedNode && !nextSelectedNode.selectable)
838                 nextSelectedNode = nextSelectedNode.traverseNextNode(true);
839             handled = nextSelectedNode ? true : false;
840         } else if (event.keyIdentifier === "Left") {
841             if (this.selectedNode.expanded) {
842                 if (event.altKey)
843                     this.selectedNode.collapseRecursively();
844                 else
845                     this.selectedNode.collapse();
846                 handled = true;
847             } else if (this.selectedNode.parent && !this.selectedNode.parent.root) {
848                 handled = true;
849                 if (this.selectedNode.parent.selectable) {
850                     nextSelectedNode = this.selectedNode.parent;
851                     handled = nextSelectedNode ? true : false;
852                 } else if (this.selectedNode.parent)
853                     this.selectedNode.parent.collapse();
854             }
855         } else if (event.keyIdentifier === "Right") {
856             if (!this.selectedNode.revealed) {
857                 this.selectedNode.reveal();
858                 handled = true;
859             } else if (this.selectedNode.hasChildren) {
860                 handled = true;
861                 if (this.selectedNode.expanded) {
862                     nextSelectedNode = this.selectedNode.children[0];
863                     handled = nextSelectedNode ? true : false;
864                 } else {
865                     if (event.altKey)
866                         this.selectedNode.expandRecursively();
867                     else
868                         this.selectedNode.expand();
869                 }
870             }
871         } else if (event.keyCode === 8 || event.keyCode === 46) {
872             if (this._deleteCallback) {
873                 handled = true;
874                 this._deleteCallback(this.selectedNode);
875             }
876         } else if (isEnterKey(event)) {
877             if (this._editCallback) {
878                 handled = true;
879                 // The first child of the selected element is the <td class="0-column">,
880                 // and that's what we want to edit.
881                 this._startEditing(this.selectedNode._element.children[0]);
882             }
883         }
884
885         if (nextSelectedNode) {
886             nextSelectedNode.reveal();
887             nextSelectedNode.select();
888         }
889
890         if (handled) {
891             event.preventDefault();
892             event.stopPropagation();
893         }
894     },
895
896     expand: function()
897     {
898         // This is the root, do nothing.
899     },
900
901     collapse: function()
902     {
903         // This is the root, do nothing.
904     },
905
906     reveal: function()
907     {
908         // This is the root, do nothing.
909     },
910
911     revealAndSelect: function()
912     {
913         // This is the root, do nothing.
914     },
915
916     dataGridNodeFromNode: function(target)
917     {
918         var rowElement = target.enclosingNodeOrSelfWithNodeName("tr");
919         return rowElement && rowElement._dataGridNode;
920     },
921
922     dataGridNodeFromPoint: function(x, y)
923     {
924         var node = this._dataTable.ownerDocument.elementFromPoint(x, y);
925         var rowElement = node.enclosingNodeOrSelfWithNodeName("tr");
926         return rowElement && rowElement._dataGridNode;
927     },
928
929     _clickInHeaderCell: function(event)
930     {
931         var cell = event.target.enclosingNodeOrSelfWithNodeName("th");
932         if (!cell || !cell.columnIdentifier || !cell.hasStyleClass("sortable"))
933             return;
934
935         var sortOrder = this.sortOrder;
936
937         if (this._sortColumnCell)
938             this._sortColumnCell.removeMatchingStyleClasses("sort-\\w+");
939
940         if (cell == this._sortColumnCell) {
941             if (sortOrder === "ascending")
942                 sortOrder = "descending";
943             else
944                 sortOrder = "ascending";
945         }
946
947         this._sortColumnCell = cell;
948
949         cell.addStyleClass("sort-" + sortOrder);
950
951         this.dispatchEventToListeners("sorting changed");
952     },
953
954     markColumnAsSortedBy: function(columnIdentifier, sortOrder)
955     {
956         if (this._sortColumnCell)
957             this._sortColumnCell.removeMatchingStyleClasses("sort-\\w+");
958         this._sortColumnCell = this._headerTableHeaders[columnIdentifier];
959         this._sortColumnCell.addStyleClass("sort-" + sortOrder);
960     },
961
962     headerTableHeader: function(columnIdentifier)
963     {
964         return this._headerTableHeaders[columnIdentifier];
965     },
966
967     _mouseDownInDataTable: function(event)
968     {
969         var gridNode = this.dataGridNodeFromNode(event.target);
970         if (!gridNode || !gridNode.selectable)
971             return;
972
973         if (gridNode.isEventWithinDisclosureTriangle(event))
974             return;
975
976         if (event.metaKey) {
977             if (gridNode.selected)
978                 gridNode.deselect();
979             else
980                 gridNode.select();
981         } else
982             gridNode.select();
983     },
984
985     _contextMenuInDataTable: function(event)
986     {
987         var contextMenu = new WebInspector.ContextMenu();
988
989         var gridNode = this.dataGridNodeFromNode(event.target);
990         if (this.dataGrid._refreshCallback && (!gridNode || gridNode !== this.creationNode))
991             contextMenu.appendItem(WebInspector.UIString("Refresh"), this._refreshCallback.bind(this));
992
993         if (gridNode && gridNode.selectable && !gridNode.isEventWithinDisclosureTriangle(event)) {
994             // FIXME: Use the column names for Editing, instead of just "Edit".
995             if (this.dataGrid._editCallback) {
996                 if (gridNode === this.creationNode)
997                     contextMenu.appendItem(WebInspector.UIString("Add New"), this._startEditing.bind(this, event.target));
998                 else
999                     contextMenu.appendItem(WebInspector.UIString("Edit"), this._startEditing.bind(this, event.target));
1000             }
1001             if (this.dataGrid._deleteCallback && gridNode !== this.creationNode)
1002                 contextMenu.appendItem(WebInspector.UIString("Delete"), this._deleteCallback.bind(this, gridNode));
1003         }
1004
1005         contextMenu.show(event);
1006     },
1007
1008     _clickInDataTable: function(event)
1009     {
1010         var gridNode = this.dataGridNodeFromNode(event.target);
1011         if (!gridNode || !gridNode.hasChildren)
1012             return;
1013
1014         if (!gridNode.isEventWithinDisclosureTriangle(event))
1015             return;
1016
1017         if (gridNode.expanded) {
1018             if (event.altKey)
1019                 gridNode.collapseRecursively();
1020             else
1021                 gridNode.collapse();
1022         } else {
1023             if (event.altKey)
1024                 gridNode.expandRecursively();
1025             else
1026                 gridNode.expand();
1027         }
1028     },
1029
1030     get resizeMethod()
1031     {
1032         if (typeof this._resizeMethod === "undefined")
1033             return WebInspector.DataGrid.ResizeMethod.Nearest;
1034         return this._resizeMethod;
1035     },
1036
1037     set resizeMethod(method)
1038     {
1039         this._resizeMethod = method;
1040     },
1041
1042     _startResizerDragging: function(event)
1043     {
1044         this._currentResizer = event.target;
1045         if (!this._currentResizer.rightNeighboringColumnID)
1046             return;
1047         WebInspector.elementDragStart(this._currentResizer, this._resizerDragging.bind(this),
1048             this._endResizerDragging.bind(this), event, "col-resize");
1049     },
1050
1051     _resizerDragging: function(event)
1052     {
1053         var resizer = this._currentResizer;
1054         if (!resizer)
1055             return;
1056
1057         // Constrain the dragpoint to be within the containing div of the
1058         // datagrid.
1059         var dragPoint = event.clientX - this.element.totalOffsetLeft();
1060         // Constrain the dragpoint to be within the space made up by the
1061         // column directly to the left and the column directly to the right.
1062         var leftCellIndex = resizer.leftNeighboringColumnID;
1063         var rightCellIndex = resizer.rightNeighboringColumnID;
1064         var firstRowCells = this.headerTableBody.rows[0].cells;
1065         var leftEdgeOfPreviousColumn = 0;
1066         for (var i = 0; i < leftCellIndex; i++)
1067             leftEdgeOfPreviousColumn += firstRowCells[i].offsetWidth;
1068
1069         // Differences for other resize methods
1070         if (this.resizeMethod == WebInspector.DataGrid.ResizeMethod.Last) {
1071             rightCellIndex = this.resizers.length;
1072         } else if (this.resizeMethod == WebInspector.DataGrid.ResizeMethod.First) {
1073             leftEdgeOfPreviousColumn += firstRowCells[leftCellIndex].offsetWidth - firstRowCells[0].offsetWidth;
1074             leftCellIndex = 0;
1075         }
1076
1077         var rightEdgeOfNextColumn = leftEdgeOfPreviousColumn + firstRowCells[leftCellIndex].offsetWidth + firstRowCells[rightCellIndex].offsetWidth;
1078
1079         // Give each column some padding so that they don't disappear.
1080         var leftMinimum = leftEdgeOfPreviousColumn + this.ColumnResizePadding;
1081         var rightMaximum = rightEdgeOfNextColumn - this.ColumnResizePadding;
1082
1083         dragPoint = Number.constrain(dragPoint, leftMinimum, rightMaximum);
1084
1085         resizer.style.left = (dragPoint - this.CenterResizerOverBorderAdjustment) + "px";
1086
1087         var percentLeftColumn = (((dragPoint - leftEdgeOfPreviousColumn) / this._dataTable.offsetWidth) * 100) + "%";
1088         this._headerTableColumnGroup.children[leftCellIndex].style.width = percentLeftColumn;
1089         this._dataTableColumnGroup.children[leftCellIndex].style.width = percentLeftColumn;
1090
1091         var percentRightColumn = (((rightEdgeOfNextColumn - dragPoint) / this._dataTable.offsetWidth) * 100) + "%";
1092         this._headerTableColumnGroup.children[rightCellIndex].style.width =  percentRightColumn;
1093         this._dataTableColumnGroup.children[rightCellIndex].style.width = percentRightColumn;
1094
1095         this._positionResizers();
1096         event.preventDefault();
1097         this.dispatchEventToListeners("width changed");
1098     },
1099
1100     _endResizerDragging: function(event)
1101     {
1102         WebInspector.elementDragEnd(event);
1103         this._currentResizer = null;
1104         this.dispatchEventToListeners("width changed");
1105     },
1106
1107     ColumnResizePadding: 10,
1108
1109     CenterResizerOverBorderAdjustment: 3,
1110 }
1111
1112 WebInspector.DataGrid.ResizeMethod = {
1113     Nearest: "nearest",
1114     First: "first",
1115     Last: "last"
1116 }
1117
1118 WebInspector.DataGrid.prototype.__proto__ = WebInspector.View.prototype;
1119
1120 /**
1121  * @constructor
1122  * @extends {WebInspector.Object}
1123  * @param {boolean=} hasChildren
1124  */
1125 WebInspector.DataGridNode = function(data, hasChildren)
1126 {
1127     this._expanded = false;
1128     this._selected = false;
1129     this._shouldRefreshChildren = true;
1130     this._data = data || {};
1131     this.hasChildren = hasChildren || false;
1132     this.children = [];
1133     this.dataGrid = null;
1134     this.parent = null;
1135     this.previousSibling = null;
1136     this.nextSibling = null;
1137     this.disclosureToggleWidth = 10;
1138 }
1139
1140 WebInspector.DataGridNode.prototype = {
1141     selectable: true,
1142
1143     get element()
1144     {
1145         if (this._element)
1146             return this._element;
1147
1148         if (!this.dataGrid)
1149             return null;
1150
1151         this._element = document.createElement("tr");
1152         this._element._dataGridNode = this;
1153
1154         if (this.hasChildren)
1155             this._element.addStyleClass("parent");
1156         if (this.expanded)
1157             this._element.addStyleClass("expanded");
1158         if (this.selected)
1159             this._element.addStyleClass("selected");
1160         if (this.revealed)
1161             this._element.addStyleClass("revealed");
1162
1163         this.createCells();
1164         return this._element;
1165     },
1166
1167     createCells: function()
1168     {
1169         for (var columnIdentifier in this.dataGrid.columns) {
1170             var cell = this.createCell(columnIdentifier);
1171             this._element.appendChild(cell);
1172         }
1173     },
1174
1175     get data()
1176     {
1177         return this._data;
1178     },
1179
1180     set data(x)
1181     {
1182         this._data = x || {};
1183         this.refresh();
1184     },
1185
1186     get revealed()
1187     {
1188         if ("_revealed" in this)
1189             return this._revealed;
1190
1191         var currentAncestor = this.parent;
1192         while (currentAncestor && !currentAncestor.root) {
1193             if (!currentAncestor.expanded) {
1194                 this._revealed = false;
1195                 return false;
1196             }
1197
1198             currentAncestor = currentAncestor.parent;
1199         }
1200
1201         this._revealed = true;
1202         return true;
1203     },
1204
1205     set hasChildren(x)
1206     {
1207         if (this._hasChildren === x)
1208             return;
1209
1210         this._hasChildren = x;
1211
1212         if (!this._element)
1213             return;
1214
1215         if (this._hasChildren)
1216         {
1217             this._element.addStyleClass("parent");
1218             if (this.expanded)
1219                 this._element.addStyleClass("expanded");
1220         }
1221         else
1222         {
1223             this._element.removeStyleClass("parent");
1224             this._element.removeStyleClass("expanded");
1225         }
1226     },
1227
1228     get hasChildren()
1229     {
1230         return this._hasChildren;
1231     },
1232
1233     set revealed(x)
1234     {
1235         if (this._revealed === x)
1236             return;
1237
1238         this._revealed = x;
1239
1240         if (this._element) {
1241             if (this._revealed)
1242                 this._element.addStyleClass("revealed");
1243             else
1244                 this._element.removeStyleClass("revealed");
1245         }
1246
1247         for (var i = 0; i < this.children.length; ++i)
1248             this.children[i].revealed = x && this.expanded;
1249     },
1250
1251     get depth()
1252     {
1253         if ("_depth" in this)
1254             return this._depth;
1255         if (this.parent && !this.parent.root)
1256             this._depth = this.parent.depth + 1;
1257         else
1258             this._depth = 0;
1259         return this._depth;
1260     },
1261
1262     get leftPadding()
1263     {
1264         if (typeof(this._leftPadding) === "number")
1265             return this._leftPadding;
1266         
1267         this._leftPadding = this.depth * this.dataGrid.indentWidth;
1268         return this._leftPadding;
1269     },
1270
1271     get shouldRefreshChildren()
1272     {
1273         return this._shouldRefreshChildren;
1274     },
1275
1276     set shouldRefreshChildren(x)
1277     {
1278         this._shouldRefreshChildren = x;
1279         if (x && this.expanded)
1280             this.expand();
1281     },
1282
1283     get selected()
1284     {
1285         return this._selected;
1286     },
1287
1288     set selected(x)
1289     {
1290         if (x)
1291             this.select();
1292         else
1293             this.deselect();
1294     },
1295
1296     get expanded()
1297     {
1298         return this._expanded;
1299     },
1300
1301     set expanded(x)
1302     {
1303         if (x)
1304             this.expand();
1305         else
1306             this.collapse();
1307     },
1308
1309     refresh: function()
1310     {
1311         if (!this._element || !this.dataGrid)
1312             return;
1313
1314         this._element.removeChildren();
1315         this.createCells();
1316     },
1317
1318     createCell: function(columnIdentifier)
1319     {
1320         var cell = document.createElement("td");
1321         cell.className = columnIdentifier + "-column";
1322
1323         var alignment = this.dataGrid.aligned[columnIdentifier];
1324         if (alignment)
1325             cell.addStyleClass(alignment);
1326
1327         var div = document.createElement("div");
1328         div.textContent = this.data[columnIdentifier];
1329         cell.appendChild(div);
1330
1331         if (columnIdentifier === this.dataGrid.disclosureColumnIdentifier) {
1332             cell.addStyleClass("disclosure");
1333             if (this.leftPadding)
1334                 cell.style.setProperty("padding-left", this.leftPadding + "px");
1335         }
1336
1337         return cell;
1338     },
1339
1340     // Share these functions with DataGrid. They are written to work with a DataGridNode this object.
1341     appendChild: WebInspector.DataGrid.prototype.appendChild,
1342     insertChild: WebInspector.DataGrid.prototype.insertChild,
1343     removeChild: WebInspector.DataGrid.prototype.removeChild,
1344     removeChildren: WebInspector.DataGrid.prototype.removeChildren,
1345     removeChildrenRecursive: WebInspector.DataGrid.prototype.removeChildrenRecursive,
1346
1347     _recalculateSiblings: function(myIndex)
1348     {
1349         if (!this.parent)
1350             return;
1351
1352         var previousChild = (myIndex > 0 ? this.parent.children[myIndex - 1] : null);
1353
1354         if (previousChild) {
1355             previousChild.nextSibling = this;
1356             this.previousSibling = previousChild;
1357         } else
1358             this.previousSibling = null;
1359
1360         var nextChild = this.parent.children[myIndex + 1];
1361
1362         if (nextChild) {
1363             nextChild.previousSibling = this;
1364             this.nextSibling = nextChild;
1365         } else
1366             this.nextSibling = null;
1367     },
1368
1369     collapse: function()
1370     {
1371         if (this._element)
1372             this._element.removeStyleClass("expanded");
1373
1374         this._expanded = false;
1375
1376         for (var i = 0; i < this.children.length; ++i)
1377             this.children[i].revealed = false;
1378
1379         this.dispatchEventToListeners("collapsed");
1380     },
1381
1382     collapseRecursively: function()
1383     {
1384         var item = this;
1385         while (item) {
1386             if (item.expanded)
1387                 item.collapse();
1388             item = item.traverseNextNode(false, this, true);
1389         }
1390     },
1391
1392     expand: function()
1393     {
1394         if (!this.hasChildren || this.expanded)
1395             return;
1396
1397         if (this.revealed && !this._shouldRefreshChildren)
1398             for (var i = 0; i < this.children.length; ++i)
1399                 this.children[i].revealed = true;
1400
1401         if (this._shouldRefreshChildren) {
1402             for (var i = 0; i < this.children.length; ++i)
1403                 this.children[i]._detach();
1404
1405             this.dispatchEventToListeners("populate");
1406
1407             if (this._attached) {
1408                 for (var i = 0; i < this.children.length; ++i) {
1409                     var child = this.children[i];
1410                     if (this.revealed)
1411                         child.revealed = true;
1412                     child._attach();
1413                 }
1414             }
1415
1416             delete this._shouldRefreshChildren;
1417         }
1418
1419         if (this._element)
1420             this._element.addStyleClass("expanded");
1421
1422         this._expanded = true;
1423
1424         this.dispatchEventToListeners("expanded");
1425     },
1426
1427     expandRecursively: function()
1428     {
1429         var item = this;
1430         while (item) {
1431             item.expand();
1432             item = item.traverseNextNode(false, this);
1433         }
1434     },
1435
1436     reveal: function()
1437     {
1438         var currentAncestor = this.parent;
1439         while (currentAncestor && !currentAncestor.root) {
1440             if (!currentAncestor.expanded)
1441                 currentAncestor.expand();
1442             currentAncestor = currentAncestor.parent;
1443         }
1444
1445         this.element.scrollIntoViewIfNeeded(false);
1446
1447         this.dispatchEventToListeners("revealed");
1448     },
1449
1450     /**
1451      * @param {boolean=} supressSelectedEvent
1452      */
1453     select: function(supressSelectedEvent)
1454     {
1455         if (!this.dataGrid || !this.selectable || this.selected)
1456             return;
1457
1458         if (this.dataGrid.selectedNode)
1459             this.dataGrid.selectedNode.deselect();
1460
1461         this._selected = true;
1462         this.dataGrid.selectedNode = this;
1463
1464         if (this._element)
1465             this._element.addStyleClass("selected");
1466
1467         if (!supressSelectedEvent)
1468             this.dispatchEventToListeners("selected");
1469     },
1470
1471     revealAndSelect: function()
1472     {
1473         this.reveal();
1474         this.select();
1475     },
1476
1477     /**
1478      * @param {boolean=} supressDeselectedEvent
1479      */
1480     deselect: function(supressDeselectedEvent)
1481     {
1482         if (!this.dataGrid || this.dataGrid.selectedNode !== this || !this.selected)
1483             return;
1484
1485         this._selected = false;
1486         this.dataGrid.selectedNode = null;
1487
1488         if (this._element)
1489             this._element.removeStyleClass("selected");
1490
1491         if (!supressDeselectedEvent)
1492             this.dispatchEventToListeners("deselected");
1493     },
1494
1495     traverseNextNode: function(skipHidden, stayWithin, dontPopulate, info)
1496     {
1497         if (!dontPopulate && this.hasChildren)
1498             this.dispatchEventToListeners("populate");
1499
1500         if (info)
1501             info.depthChange = 0;
1502
1503         var node = (!skipHidden || this.revealed) ? this.children[0] : null;
1504         if (node && (!skipHidden || this.expanded)) {
1505             if (info)
1506                 info.depthChange = 1;
1507             return node;
1508         }
1509
1510         if (this === stayWithin)
1511             return null;
1512
1513         node = (!skipHidden || this.revealed) ? this.nextSibling : null;
1514         if (node)
1515             return node;
1516
1517         node = this;
1518         while (node && !node.root && !((!skipHidden || node.revealed) ? node.nextSibling : null) && node.parent !== stayWithin) {
1519             if (info)
1520                 info.depthChange -= 1;
1521             node = node.parent;
1522         }
1523
1524         if (!node)
1525             return null;
1526
1527         return (!skipHidden || node.revealed) ? node.nextSibling : null;
1528     },
1529
1530     traversePreviousNode: function(skipHidden, dontPopulate)
1531     {
1532         var node = (!skipHidden || this.revealed) ? this.previousSibling : null;
1533         if (!dontPopulate && node && node.hasChildren)
1534             node.dispatchEventToListeners("populate");
1535
1536         while (node && ((!skipHidden || (node.revealed && node.expanded)) ? node.children[node.children.length - 1] : null)) {
1537             if (!dontPopulate && node.hasChildren)
1538                 node.dispatchEventToListeners("populate");
1539             node = ((!skipHidden || (node.revealed && node.expanded)) ? node.children[node.children.length - 1] : null);
1540         }
1541
1542         if (node)
1543             return node;
1544
1545         if (!this.parent || this.parent.root)
1546             return null;
1547
1548         return this.parent;
1549     },
1550
1551     isEventWithinDisclosureTriangle: function(event)
1552     {
1553         if (!this.hasChildren)
1554             return false;
1555         var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
1556         if (!cell.hasStyleClass("disclosure"))
1557             return false;
1558         
1559         var left = cell.totalOffsetLeft() + this.leftPadding;
1560         return event.pageX >= left && event.pageX <= left + this.disclosureToggleWidth;
1561     },
1562
1563     _attach: function()
1564     {
1565         if (!this.dataGrid || this._attached)
1566             return;
1567
1568         this._attached = true;
1569
1570         var nextNode = null;
1571         var previousNode = this.traversePreviousNode(true, true);
1572         if (previousNode && previousNode.element.parentNode && previousNode.element.nextSibling)
1573             nextNode = previousNode.element.nextSibling;
1574         if (!nextNode)
1575             nextNode = this.dataGrid.dataTableBody.lastChild;
1576         this.dataGrid.dataTableBody.insertBefore(this.element, nextNode);
1577
1578         if (this.expanded)
1579             for (var i = 0; i < this.children.length; ++i)
1580                 this.children[i]._attach();
1581     },
1582
1583     _detach: function()
1584     {
1585         if (!this._attached)
1586             return;
1587
1588         this._attached = false;
1589
1590         if (this._element && this._element.parentNode)
1591             this._element.parentNode.removeChild(this._element);
1592
1593         for (var i = 0; i < this.children.length; ++i)
1594             this.children[i]._detach();
1595     },
1596
1597     savePosition: function()
1598     {
1599         if (this._savedPosition)
1600             return;
1601
1602         if (!this.parent)
1603             throw("savePosition: Node must have a parent.");
1604         this._savedPosition = {
1605             parent: this.parent,
1606             index: this.parent.children.indexOf(this)
1607         };
1608     },
1609
1610     restorePosition: function()
1611     {
1612         if (!this._savedPosition)
1613             return;
1614
1615         if (this.parent !== this._savedPosition.parent)
1616             this._savedPosition.parent.insertChild(this, this._savedPosition.index);
1617
1618         delete this._savedPosition;
1619     }
1620 }
1621
1622 WebInspector.DataGridNode.prototype.__proto__ = WebInspector.Object.prototype;
1623
1624 /**
1625  * @constructor
1626  * @extends {WebInspector.DataGridNode}
1627  */
1628 WebInspector.CreationDataGridNode = function(data, hasChildren)
1629 {
1630     WebInspector.DataGridNode.call(this, data, hasChildren);
1631     this.isCreationNode = true;
1632 }
1633
1634 WebInspector.CreationDataGridNode.prototype = {
1635     makeNormal: function()
1636     {
1637         delete this.isCreationNode;
1638         delete this.makeNormal;
1639     }
1640 }
1641
1642 WebInspector.CreationDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype;