diff options
author | Jocelyn Turcotte <jocelyn.turcotte@digia.com> | 2014-08-08 14:30:41 +0200 |
---|---|---|
committer | Jocelyn Turcotte <jocelyn.turcotte@digia.com> | 2014-08-12 13:49:54 +0200 |
commit | ab0a50979b9eb4dfa3320eff7e187e41efedf7a9 (patch) | |
tree | 498dfb8a97ff3361a9f7486863a52bb4e26bb898 /chromium/third_party/WebKit/Source/devtools/front_end/ui | |
parent | 4ce69f7403811819800e7c5ae1318b2647e778d1 (diff) |
Update Chromium to beta version 37.0.2062.68
Change-Id: I188e3b5aff1bec75566014291b654eb19f5bc8ca
Reviewed-by: Andras Becsi <andras.becsi@digia.com>
Diffstat (limited to 'chromium/third_party/WebKit/Source/devtools/front_end/ui')
33 files changed, 13413 insertions, 0 deletions
diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/ActionRegistry.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/ActionRegistry.js new file mode 100644 index 00000000000..0cf9faf7538 --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/ActionRegistry.js @@ -0,0 +1,79 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @constructor + */ +WebInspector.ActionRegistry = function() +{ + /** @type {!StringMap.<!WebInspector.ModuleManager.Extension>} */ + this._actionsById = new StringMap(); + this._registerActions(); +} + +WebInspector.ActionRegistry.prototype = { + _registerActions: function() + { + WebInspector.moduleManager.extensions(WebInspector.ActionDelegate).forEach(registerExtension, this); + + /** + * @param {!WebInspector.ModuleManager.Extension} extension + * @this {WebInspector.ActionRegistry} + */ + function registerExtension(extension) + { + var actionId = extension.descriptor()["actionId"]; + console.assert(actionId); + console.assert(!this._actionsById.get(actionId)); + this._actionsById.put(actionId, extension); + } + }, + + /** + * @param {!Array.<string>} actionIds + * @param {!WebInspector.Context} context + * @return {!Array.<string>} + */ + applicableActions: function(actionIds, context) + { + var extensions = []; + actionIds.forEach(function(actionId) { + var extension = this._actionsById.get(actionId); + if (extension) + extensions.push(extension); + }, this); + return context.applicableExtensions(extensions).values().map(function(extension) { + return extension.descriptor()["actionId"]; + }); + }, + + /** + * @param {string} actionId + * @return {boolean} + */ + execute: function(actionId) + { + var extension = this._actionsById.get(actionId); + console.assert(extension, "No action found for actionId '" + actionId + "'"); + return extension.instance().handleAction(WebInspector.context); + } +} + +/** + * @interface + */ +WebInspector.ActionDelegate = function() +{ +} + +WebInspector.ActionDelegate.prototype = { + /** + * @param {!WebInspector.Context} context + * @return {boolean} + */ + handleAction: function(context) {} +} + +/** @type {!WebInspector.ActionRegistry} */ +WebInspector.actionRegistry; diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/Checkbox.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/Checkbox.js new file mode 100644 index 00000000000..7802c095490 --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/Checkbox.js @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2010 Google 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. + */ + +/** + * @constructor + * @param {string} label + * @param {string} className + * @param {string=} tooltip + */ +WebInspector.Checkbox = function(label, className, tooltip) +{ + this.element = document.createElement('label'); + this._inputElement = document.createElement('input'); + this._inputElement.type = "checkbox"; + + this.element.className = className; + this.element.appendChild(this._inputElement); + this.element.appendChild(document.createTextNode(label)); + if (tooltip) + this.element.title = tooltip; +} + +WebInspector.Checkbox.prototype = { + set checked(checked) + { + this._inputElement.checked = checked; + }, + + get checked() + { + return this._inputElement.checked; + }, + + addEventListener: function(listener) + { + function listenerWrapper(event) + { + if (listener) + listener(event); + event.consume(); + return true; + } + + this._inputElement.addEventListener("click", listenerWrapper, false); + this.element.addEventListener("click", listenerWrapper, false); + } +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/Context.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/Context.js new file mode 100644 index 00000000000..0dd80bc98d3 --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/Context.js @@ -0,0 +1,119 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @constructor + */ +WebInspector.Context = function() +{ + this._flavors = new Map(); + this._eventDispatchers = new Map(); +} + +/** + * @enum {string} + */ +WebInspector.Context.Events = { + FlavorChanged: "FlavorChanged" +} + +WebInspector.Context.prototype = { + /** + * @param {function(new:T, ...)} flavorType + * @param {?T} flavorValue + * @template T + */ + setFlavor: function(flavorType, flavorValue) + { + var value = this._flavors.get(flavorType) || null; + if (value === flavorValue) + return; + if (flavorValue) + this._flavors.put(flavorType, flavorValue); + else + this._flavors.remove(flavorType); + + this._dispatchFlavorChange(flavorType, flavorValue); + }, + + /** + * @param {function(new:T, ...)} flavorType + * @param {?T} flavorValue + * @template T + */ + _dispatchFlavorChange: function(flavorType, flavorValue) + { + var dispatcher = this._eventDispatchers.get(flavorType); + if (!dispatcher) + return; + dispatcher.dispatchEventToListeners(WebInspector.Context.Events.FlavorChanged, flavorValue); + }, + + /** + * @param {function(new:Object, ...)} flavorType + * @param {function(!WebInspector.Event)} listener + * @param {!Object=} thisObject + */ + addFlavorChangeListener: function(flavorType, listener, thisObject) + { + var dispatcher = this._eventDispatchers.get(flavorType); + if (!dispatcher) { + dispatcher = new WebInspector.Object(); + this._eventDispatchers.put(flavorType, dispatcher); + } + dispatcher.addEventListener(WebInspector.Context.Events.FlavorChanged, listener, thisObject); + }, + + /** + * @param {function(new:Object, ...)} flavorType + * @param {function(!WebInspector.Event)} listener + * @param {!Object=} thisObject + */ + removeFlavorChangeListener: function(flavorType, listener, thisObject) + { + var dispatcher = this._eventDispatchers.get(flavorType); + if (!dispatcher) + return; + dispatcher.removeEventListener(WebInspector.Context.Events.FlavorChanged, listener, thisObject); + if (!dispatcher.hasEventListeners(WebInspector.Context.Events.FlavorChanged)) + this._eventDispatchers.remove(flavorType); + }, + + /** + * @param {function(new:T, ...)} flavorType + * @return {?T} + * @template T + */ + flavor: function(flavorType) + { + return this._flavors.get(flavorType) || null; + }, + + /** + * @return {!Array.<function(new:Object, ...)>} + */ + flavors: function() + { + return this._flavors.keys(); + }, + + /** + * @param {!Array.<!WebInspector.ModuleManager.Extension>} extensions + * @return {!Set.<!WebInspector.ModuleManager.Extension>} + */ + applicableExtensions: function(extensions) + { + var targetExtensionSet = new Set(); + + var availableFlavors = Set.fromArray(this.flavors()); + extensions.forEach(function(extension) { + if (WebInspector.moduleManager.isExtensionApplicableToContextTypes(extension, availableFlavors)) + targetExtensionSet.add(extension); + }); + + return targetExtensionSet; + } +} + +WebInspector.context = new WebInspector.Context();
\ No newline at end of file diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/ContextMenu.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/ContextMenu.js new file mode 100644 index 00000000000..0eb53028f5e --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/ContextMenu.js @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2009 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER 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. + */ + +/** + * @constructor + * @param {!WebInspector.ContextMenu} topLevelMenu + * @param {string} type + * @param {string=} label + * @param {boolean=} disabled + * @param {boolean=} checked + */ +WebInspector.ContextMenuItem = function(topLevelMenu, type, label, disabled, checked) +{ + this._type = type; + this._label = label; + this._disabled = disabled; + this._checked = checked; + this._contextMenu = topLevelMenu; + if (type === "item" || type === "checkbox") + this._id = topLevelMenu.nextId(); +} + +WebInspector.ContextMenuItem.prototype = { + /** + * @return {number} + */ + id: function() + { + return this._id; + }, + + /** + * @return {string} + */ + type: function() + { + return this._type; + }, + + /** + * @return {boolean} + */ + isEnabled: function() + { + return !this._disabled; + }, + + /** + * @param {boolean} enabled + */ + setEnabled: function(enabled) + { + this._disabled = !enabled; + }, + + _buildDescriptor: function() + { + switch (this._type) { + case "item": + return { type: "item", id: this._id, label: this._label, enabled: !this._disabled }; + case "separator": + return { type: "separator" }; + case "checkbox": + return { type: "checkbox", id: this._id, label: this._label, checked: !!this._checked, enabled: !this._disabled }; + } + } +} + +/** + * @constructor + * @extends {WebInspector.ContextMenuItem} + * @param {!WebInspector.ContextMenu} topLevelMenu + * @param {string=} label + * @param {boolean=} disabled + */ +WebInspector.ContextSubMenuItem = function(topLevelMenu, label, disabled) +{ + WebInspector.ContextMenuItem.call(this, topLevelMenu, "subMenu", label, disabled); + /** @type {!Array.<!WebInspector.ContextMenuItem>} */ + this._items = []; +} + +WebInspector.ContextSubMenuItem.prototype = { + /** + * @param {string} label + * @param {function(?)} handler + * @param {boolean=} disabled + * @return {!WebInspector.ContextMenuItem} + */ + appendItem: function(label, handler, disabled) + { + var item = new WebInspector.ContextMenuItem(this._contextMenu, "item", label, disabled); + this._pushItem(item); + this._contextMenu._setHandler(item.id(), handler); + return item; + }, + + /** + * @param {string} label + * @param {boolean=} disabled + * @return {!WebInspector.ContextSubMenuItem} + */ + appendSubMenuItem: function(label, disabled) + { + var item = new WebInspector.ContextSubMenuItem(this._contextMenu, label, disabled); + this._pushItem(item); + return item; + }, + + /** + * @param {string} label + * @param {function()} handler + * @param {boolean=} checked + * @param {boolean=} disabled + * @return {!WebInspector.ContextMenuItem} + */ + appendCheckboxItem: function(label, handler, checked, disabled) + { + var item = new WebInspector.ContextMenuItem(this._contextMenu, "checkbox", label, disabled, checked); + this._pushItem(item); + this._contextMenu._setHandler(item.id(), handler); + return item; + }, + + appendSeparator: function() + { + if (this._items.length) + this._pendingSeparator = true; + }, + + /** + * @param {!WebInspector.ContextMenuItem} item + */ + _pushItem: function(item) + { + if (this._pendingSeparator) { + this._items.push(new WebInspector.ContextMenuItem(this._contextMenu, "separator")); + delete this._pendingSeparator; + } + this._items.push(item); + }, + + /** + * @return {boolean} + */ + isEmpty: function() + { + return !this._items.length; + }, + + _buildDescriptor: function() + { + var result = { type: "subMenu", label: this._label, enabled: !this._disabled, subItems: [] }; + for (var i = 0; i < this._items.length; ++i) + result.subItems.push(this._items[i]._buildDescriptor()); + return result; + }, + + __proto__: WebInspector.ContextMenuItem.prototype +} + +/** + * @constructor + * @extends {WebInspector.ContextSubMenuItem} + * @param {?Event} event + */ +WebInspector.ContextMenu = function(event) +{ + WebInspector.ContextSubMenuItem.call(this, this, ""); + this._event = /** @type {!Event} */ (event); + this._handlers = {}; + this._id = 0; +} + +/** + * @param {boolean} useSoftMenu + */ +WebInspector.ContextMenu.setUseSoftMenu = function(useSoftMenu) +{ + WebInspector.ContextMenu._useSoftMenu = useSoftMenu; +} + +WebInspector.ContextMenu.prototype = { + /** + * @return {number} + */ + nextId: function() + { + return this._id++; + }, + + show: function() + { + var menuObject = this._buildDescriptor(); + + if (menuObject.length) { + WebInspector._contextMenu = this; + if (WebInspector.ContextMenu._useSoftMenu) { + var softMenu = new WebInspector.SoftContextMenu(menuObject); + softMenu.show(this._event); + } else { + InspectorFrontendHost.showContextMenu(this._event, menuObject); + } + this._event.consume(true); + } + }, + + /** + * @param {number} id + * @param {function(?)} handler + */ + _setHandler: function(id, handler) + { + if (handler) + this._handlers[id] = handler; + }, + + _buildDescriptor: function() + { + var result = []; + for (var i = 0; i < this._items.length; ++i) + result.push(this._items[i]._buildDescriptor()); + return result; + }, + + _itemSelected: function(id) + { + if (this._handlers[id]) + this._handlers[id].call(this); + }, + + /** + * @param {!Object} target + */ + appendApplicableItems: function(target) + { + WebInspector.moduleManager.extensions(WebInspector.ContextMenu.Provider, target).forEach(processProviders.bind(this)); + + /** + * @param {!WebInspector.ModuleManager.Extension} extension + * @this {WebInspector.ContextMenu} + */ + function processProviders(extension) + { + var provider = /** @type {!WebInspector.ContextMenu.Provider} */ (extension.instance()); + this.appendSeparator(); + provider.appendApplicableItems(this._event, this, target); + this.appendSeparator(); + } + }, + + __proto__: WebInspector.ContextSubMenuItem.prototype +} + +/** + * @interface + */ +WebInspector.ContextMenu.Provider = function() { +} + +WebInspector.ContextMenu.Provider.prototype = { + /** + * @param {!Event} event + * @param {!WebInspector.ContextMenu} contextMenu + * @param {!Object} target + */ + appendApplicableItems: function(event, contextMenu, target) { } +} + +WebInspector.contextMenuItemSelected = function(id) +{ + if (WebInspector._contextMenu) + WebInspector._contextMenu._itemSelected(id); +} + +WebInspector.contextMenuCleared = function() +{ + // FIXME: Unfortunately, contextMenuCleared is invoked between show and item selected + // so we can't delete last menu object from WebInspector. Fix the contract. +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/DataGrid.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/DataGrid.js new file mode 100644 index 00000000000..7edaf5c62d6 --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/DataGrid.js @@ -0,0 +1,1837 @@ +/* + * 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. + */ + +/** + * @constructor + * @extends {WebInspector.View} + * @param {!Array.<!WebInspector.DataGrid.ColumnDescriptor>} columnsArray + * @param {function(!WebInspector.DataGridNode, string, string, string)=} editCallback + * @param {function(!WebInspector.DataGridNode)=} deleteCallback + * @param {function()=} refreshCallback + * @param {function(!WebInspector.ContextMenu, !WebInspector.DataGridNode)=} contextMenuCallback + */ +WebInspector.DataGrid = function(columnsArray, editCallback, deleteCallback, refreshCallback, contextMenuCallback) +{ + WebInspector.View.call(this); + this.registerRequiredCSS("dataGrid.css"); + + this.element.className = "data-grid"; // Override + this.element.tabIndex = 0; + this.element.addEventListener("keydown", this._keyDown.bind(this), false); + + this._headerTable = document.createElement("table"); + this._headerTable.className = "header"; + /** + * @type {!Object.<string, !Element>} + */ + this._headerTableHeaders = {}; + + 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); + + this._dataTable.addEventListener("contextmenu", this._contextMenuInDataTable.bind(this), true); + + // FIXME: Add a createCallback which is different from editCallback and has different + // behavior when creating a new node. + if (editCallback) + this._dataTable.addEventListener("dblclick", this._ondblclick.bind(this), false); + this._editCallback = editCallback; + this._deleteCallback = deleteCallback; + this._refreshCallback = refreshCallback; + this._contextMenuCallback = contextMenuCallback; + + this._scrollContainer = document.createElement("div"); + this._scrollContainer.className = "data-container"; + this._scrollContainer.appendChild(this._dataTable); + + this.element.appendChild(this._headerTable); + this.element.appendChild(this._scrollContainer); + + this._headerRow = document.createElement("tr"); + this._headerTableColumnGroup = document.createElement("colgroup"); + this._dataTableColumnGroup = document.createElement("colgroup"); + + this._fillerRow = document.createElement("tr"); + this._fillerRow.className = "filler"; + + this._columnsArray = columnsArray; + this._visibleColumnsArray = columnsArray; + this._columns = {}; + + for (var i = 0; i < columnsArray.length; ++i) { + var column = columnsArray[i]; + var columnIdentifier = column.identifier = column.id || i; + this._columns[columnIdentifier] = column; + if (column.disclosure) + this.disclosureColumnIdentifier = columnIdentifier; + + var cell = document.createElement("th"); + cell.className = columnIdentifier + "-column"; + cell.columnIdentifier = columnIdentifier; + this._headerTableHeaders[columnIdentifier] = cell; + + var div = document.createElement("div"); + if (column.titleDOMFragment) + div.appendChild(column.titleDOMFragment); + else + div.textContent = column.title; + cell.appendChild(div); + + if (column.sort) { + cell.classList.add("sort-" + column.sort); + this._sortColumnCell = cell; + } + + if (column.sortable) { + cell.addEventListener("click", this._clickInHeaderCell.bind(this), false); + cell.classList.add("sortable"); + } + } + + this._headerTable.appendChild(this._headerTableColumnGroup); + this.headerTableBody.appendChild(this._headerRow); + + this._dataTable.appendChild(this._dataTableColumnGroup); + this.dataTableBody.appendChild(this._fillerRow); + + this._refreshHeader(); + + this.selectedNode = null; + this.expandNodesWhenArrowing = false; + this.setRootNode(new WebInspector.DataGridNode()); + this.indentWidth = 15; + this._resizers = []; + this._columnWidthsInitialized = false; + this._cornerWidth = WebInspector.DataGrid.CornerWidth; +} + +// Keep in sync with .data-grid col.corner style rule. +WebInspector.DataGrid.CornerWidth = 14; + +/** @typedef {!{id: ?string, editable: boolean, longText: ?boolean, sort: !WebInspector.DataGrid.Order, sortable: boolean, align: !WebInspector.DataGrid.Align}} */ +WebInspector.DataGrid.ColumnDescriptor; + +WebInspector.DataGrid.Events = { + SelectedNode: "SelectedNode", + DeselectedNode: "DeselectedNode", + SortingChanged: "SortingChanged", + ColumnsResized: "ColumnsResized" +} + +/** @enum {string} */ +WebInspector.DataGrid.Order = { + Ascending: "ascending", + Descending: "descending" +} + +/** @enum {string} */ +WebInspector.DataGrid.Align = { + Center: "center", + Right: "right" +} + +/** + * @param {!Array.<string>} columnNames + * @param {!Array.<string>} values + * @return {?WebInspector.DataGrid} + */ +WebInspector.DataGrid.createSortableDataGrid = function(columnNames, values) +{ + var numColumns = columnNames.length; + if (!numColumns) + return null; + + var columns = []; + for (var i = 0; i < columnNames.length; ++i) + columns.push({title: columnNames[i], width: columnNames[i].length, sortable: true}); + + var nodes = []; + for (var i = 0; i < values.length / numColumns; ++i) { + var data = {}; + for (var j = 0; j < columnNames.length; ++j) + data[j] = values[numColumns * i + j]; + + var node = new WebInspector.DataGridNode(data, false); + node.selectable = false; + nodes.push(node); + } + + var dataGrid = new WebInspector.DataGrid(columns); + var length = nodes.length; + for (var i = 0; i < length; ++i) + dataGrid.rootNode().appendChild(nodes[i]); + + dataGrid.addEventListener(WebInspector.DataGrid.Events.SortingChanged, sortDataGrid); + + function sortDataGrid() + { + var nodes = dataGrid._rootNode.children.slice(); + var sortColumnIdentifier = dataGrid.sortColumnIdentifier(); + var sortDirection = dataGrid.isSortOrderAscending() ? 1 : -1; + var columnIsNumeric = true; + + for (var i = 0; i < nodes.length; i++) { + var value = nodes[i].data[sortColumnIdentifier]; + value = value instanceof Node ? Number(value.textContent) : Number(value); + if (isNaN(value)) { + columnIsNumeric = false; + break; + } + } + + function comparator(dataGridNode1, dataGridNode2) + { + var item1 = dataGridNode1.data[sortColumnIdentifier]; + var item2 = dataGridNode2.data[sortColumnIdentifier]; + item1 = item1 instanceof Node ? item1.textContent : String(item1); + item2 = item2 instanceof Node ? item2.textContent : String(item2); + + var comparison; + if (columnIsNumeric) { + // Sort numbers based on comparing their values rather than a lexicographical comparison. + var number1 = parseFloat(item1); + var number2 = parseFloat(item2); + comparison = number1 < number2 ? -1 : (number1 > number2 ? 1 : 0); + } else + comparison = item1 < item2 ? -1 : (item1 > item2 ? 1 : 0); + + return sortDirection * comparison; + } + + nodes.sort(comparator); + dataGrid.rootNode().removeChildren(); + for (var i = 0; i < nodes.length; i++) + dataGrid._rootNode.appendChild(nodes[i]); + } + return dataGrid; +} + +WebInspector.DataGrid.prototype = { + _refreshHeader: function() + { + this._headerTableColumnGroup.removeChildren(); + this._dataTableColumnGroup.removeChildren(); + this._headerRow.removeChildren(); + this._fillerRow.removeChildren(); + + for (var i = 0; i < this._visibleColumnsArray.length; ++i) { + var column = this._visibleColumnsArray[i]; + var columnIdentifier = column.identifier; + var headerColumn = this._headerTableColumnGroup.createChild("col"); + var dataColumn = this._dataTableColumnGroup.createChild("col"); + if (column.width) { + headerColumn.style.width = column.width; + dataColumn.style.width = column.width; + } + this._headerRow.appendChild(this._headerTableHeaders[columnIdentifier]); + this._fillerRow.createChild("td", columnIdentifier + "-column"); + } + + this._headerRow.createChild("th", "corner"); + this._fillerRow.createChild("td", "corner"); + this._headerTableColumnGroup.createChild("col", "corner"); + this._dataTableColumnGroup.createChild("col", "corner"); + }, + + /** + * @param {!WebInspector.DataGridNode} rootNode + */ + setRootNode: function(rootNode) + { + if (this._rootNode) { + this._rootNode.removeChildren(); + this._rootNode.dataGrid = null; + this._rootNode._isRoot = false; + } + /** @type {!WebInspector.DataGridNode} */ + this._rootNode = rootNode; + rootNode._isRoot = true; + rootNode.hasChildren = false; + rootNode._expanded = true; + rootNode._revealed = true; + rootNode.dataGrid = this; + }, + + /** + * @return {!WebInspector.DataGridNode} + */ + rootNode: function() + { + return this._rootNode; + }, + + _ondblclick: function(event) + { + if (this._editing || this._editingNode) + return; + + var columnIdentifier = this.columnIdentifierFromNode(event.target); + if (!columnIdentifier || !this._columns[columnIdentifier].editable) + return; + this._startEditing(event.target); + }, + + /** + * @param {!WebInspector.DataGridNode} node + * @param {number} cellIndex + */ + _startEditingColumnOfDataGridNode: function(node, cellIndex) + { + this._editing = true; + /** @type {!WebInspector.DataGridNode} */ + this._editingNode = node; + this._editingNode.select(); + + var element = this._editingNode._element.children[cellIndex]; + WebInspector.InplaceEditor.startEditing(element, this._startEditingConfig(element)); + window.getSelection().setBaseAndExtent(element, 0, element, 1); + }, + + _startEditing: function(target) + { + var element = target.enclosingNodeOrSelfWithNodeName("td"); + if (!element) + return; + + this._editingNode = this.dataGridNodeFromNode(target); + if (!this._editingNode) { + if (!this.creationNode) + return; + this._editingNode = this.creationNode; + } + + // Force editing the 1st column when editing the creation node + if (this._editingNode.isCreationNode) + return this._startEditingColumnOfDataGridNode(this._editingNode, this._nextEditableColumn(-1)); + + this._editing = true; + WebInspector.InplaceEditor.startEditing(element, this._startEditingConfig(element)); + + window.getSelection().setBaseAndExtent(element, 0, element, 1); + }, + + renderInline: function() + { + this.element.classList.add("inline"); + this._cornerWidth = 0; + this.updateWidths(); + }, + + _startEditingConfig: function(element) + { + return new WebInspector.InplaceEditor.Config(this._editingCommitted.bind(this), this._editingCancelled.bind(this), element.textContent); + }, + + _editingCommitted: function(element, newText, oldText, context, moveDirection) + { + var columnIdentifier = this.columnIdentifierFromNode(element); + if (!columnIdentifier) { + this._editingCancelled(element); + return; + } + var column = this._columns[columnIdentifier]; + var cellIndex = this._visibleColumnsArray.indexOf(column); + var textBeforeEditing = this._editingNode.data[columnIdentifier]; + var currentEditingNode = this._editingNode; + + /** + * @param {boolean} wasChange + * @this {WebInspector.DataGrid} + */ + function moveToNextIfNeeded(wasChange) { + if (!moveDirection) + return; + + if (moveDirection === "forward") { + var firstEditableColumn = this._nextEditableColumn(-1); + if (currentEditingNode.isCreationNode && cellIndex === firstEditableColumn && !wasChange) + return; + + var nextEditableColumn = this._nextEditableColumn(cellIndex); + if (nextEditableColumn !== -1) + return this._startEditingColumnOfDataGridNode(currentEditingNode, nextEditableColumn); + + var nextDataGridNode = currentEditingNode.traverseNextNode(true, null, true); + if (nextDataGridNode) + return this._startEditingColumnOfDataGridNode(nextDataGridNode, firstEditableColumn); + if (currentEditingNode.isCreationNode && wasChange) { + this.addCreationNode(false); + return this._startEditingColumnOfDataGridNode(this.creationNode, firstEditableColumn); + } + return; + } + + if (moveDirection === "backward") { + var prevEditableColumn = this._nextEditableColumn(cellIndex, true); + if (prevEditableColumn !== -1) + return this._startEditingColumnOfDataGridNode(currentEditingNode, prevEditableColumn); + + var lastEditableColumn = this._nextEditableColumn(this._visibleColumnsArray.length, true); + var nextDataGridNode = currentEditingNode.traversePreviousNode(true, true); + if (nextDataGridNode) + return this._startEditingColumnOfDataGridNode(nextDataGridNode, lastEditableColumn); + return; + } + } + + if (textBeforeEditing == newText) { + this._editingCancelled(element); + moveToNextIfNeeded.call(this, false); + return; + } + + // Update the text in the datagrid that we typed + this._editingNode.data[columnIdentifier] = newText; + + // Make the callback - expects an editing node (table row), the column number that is being edited, + // the text that used to be there, and the new text. + this._editCallback(this._editingNode, columnIdentifier, textBeforeEditing, newText); + + if (this._editingNode.isCreationNode) + this.addCreationNode(false); + + this._editingCancelled(element); + moveToNextIfNeeded.call(this, true); + }, + + _editingCancelled: function(element) + { + delete this._editing; + this._editingNode = null; + }, + + /** + * @param {number} cellIndex + * @param {boolean=} moveBackward + * @return {number} + */ + _nextEditableColumn: function(cellIndex, moveBackward) + { + var increment = moveBackward ? -1 : 1; + var columns = this._visibleColumnsArray; + for (var i = cellIndex + increment; (i >= 0) && (i < columns.length); i += increment) { + if (columns[i].editable) + return i; + } + return -1; + }, + + /** + * @return {?string} + */ + sortColumnIdentifier: function() + { + if (!this._sortColumnCell) + return null; + return this._sortColumnCell.columnIdentifier; + }, + + /** + * @return {?string} + */ + sortOrder: function() + { + if (!this._sortColumnCell || this._sortColumnCell.classList.contains("sort-ascending")) + return WebInspector.DataGrid.Order.Ascending; + if (this._sortColumnCell.classList.contains("sort-descending")) + return WebInspector.DataGrid.Order.Descending; + return null; + }, + + /** + * @return {boolean} + */ + isSortOrderAscending: function() + { + return !this._sortColumnCell || this._sortColumnCell.classList.contains("sort-ascending"); + }, + + 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; + }, + + /** + * @param {!Array.<number>} widths + * @param {number} minPercent + * @param {number=} maxPercent + * @return {!Array.<number>} + */ + _autoSizeWidths: function(widths, minPercent, maxPercent) + { + if (minPercent) + minPercent = Math.min(minPercent, Math.floor(100 / widths.length)); + var totalWidth = 0; + for (var i = 0; i < widths.length; ++i) + totalWidth += widths[i]; + var totalPercentWidth = 0; + for (var i = 0; i < widths.length; ++i) { + var width = Math.round(100 * widths[i] / totalWidth); + if (minPercent && width < minPercent) + width = minPercent; + else if (maxPercent && width > maxPercent) + width = maxPercent; + totalPercentWidth += width; + widths[i] = width; + } + var recoupPercent = totalPercentWidth - 100; + + while (minPercent && recoupPercent > 0) { + for (var i = 0; i < widths.length; ++i) { + if (widths[i] > minPercent) { + --widths[i]; + --recoupPercent; + if (!recoupPercent) + break; + } + } + } + + while (maxPercent && recoupPercent < 0) { + for (var i = 0; i < widths.length; ++i) { + if (widths[i] < maxPercent) { + ++widths[i]; + ++recoupPercent; + if (!recoupPercent) + break; + } + } + } + + return widths; + }, + + /** + * @param {number} minPercent + * @param {number=} maxPercent + * @param {number=} maxDescentLevel + */ + autoSizeColumns: function(minPercent, maxPercent, maxDescentLevel) + { + var widths = []; + for (var i = 0; i < this._columnsArray.length; ++i) + widths.push((this._columnsArray[i].title || "").length); + + maxDescentLevel = maxDescentLevel || 0; + var children = this._enumerateChildren(this._rootNode, [], maxDescentLevel + 1); + for (var i = 0; i < children.length; ++i) { + var node = children[i]; + for (var j = 0; j < this._columnsArray.length; ++j) { + var text = node.data[this._columnsArray[j].identifier] || ""; + if (text.length > widths[j]) + widths[j] = text.length; + } + } + + widths = this._autoSizeWidths(widths, minPercent, maxPercent); + + for (var i = 0; i < this._columnsArray.length; ++i) + this._columnsArray[i].weight = widths[i]; + this._columnWidthsInitialized = false; + this.updateWidths(); + }, + + _enumerateChildren: function(rootNode, result, maxLevel) + { + if (!rootNode._isRoot) + result.push(rootNode); + if (!maxLevel) + return; + for (var i = 0; i < rootNode.children.length; ++i) + this._enumerateChildren(rootNode.children[i], result, maxLevel - 1); + return result; + }, + + onResize: function() + { + this.updateWidths(); + }, + + // Updates the widths of the table, including the positions of the column + // resizers. + // + // IMPORTANT: This function MUST be called once after the element of the + // DataGrid is attached to its parent element and every subsequent time the + // width of the parent element is changed in order to make it possible to + // resize the columns. + // + // If this function is not called after the DataGrid is attached to its + // parent element, then the DataGrid's columns will not be resizable. + updateWidths: function() + { + var headerTableColumns = this._headerTableColumnGroup.children; + + // Use container size to avoid changes of table width caused by change of column widths. + var tableWidth = this.element.offsetWidth - this._cornerWidth; + var numColumns = headerTableColumns.length - 1; // Do not process corner column. + + // Do not attempt to use offsetes if we're not attached to the document tree yet. + if (!this._columnWidthsInitialized && this.element.offsetWidth) { + // Give all the columns initial widths now so that during a resize, + // when the two columns that get resized get a percent value for + // their widths, all the other columns already have percent values + // for their widths. + for (var i = 0; i < numColumns; i++) { + var columnWidth = this.headerTableBody.rows[0].cells[i].offsetWidth; + var column = this._visibleColumnsArray[i]; + if (!column.weight) + column.weight = 100 * columnWidth / tableWidth; + } + this._columnWidthsInitialized = true; + } + this._applyColumnWeights(); + }, + + /** + * @param {string} name + */ + setName: function(name) + { + this._columnWeightsSetting = WebInspector.settings.createSetting("dataGrid-" + name + "-columnWeights", {}); + this._loadColumnWeights(); + }, + + _loadColumnWeights: function() + { + if (!this._columnWeightsSetting) + return; + var weights = this._columnWeightsSetting.get(); + for (var i = 0; i < this._columnsArray.length; ++i) { + var column = this._columnsArray[i]; + var weight = weights[column.identifier]; + if (weight) + column.weight = weight; + } + this._applyColumnWeights(); + }, + + _saveColumnWeights: function() + { + if (!this._columnWeightsSetting) + return; + var weights = {}; + for (var i = 0; i < this._columnsArray.length; ++i) { + var column = this._columnsArray[i]; + weights[column.identifier] = column.weight; + } + this._columnWeightsSetting.set(weights); + }, + + wasShown: function() + { + this._loadColumnWeights(); + }, + + _applyColumnWeights: function() + { + var tableWidth = this.element.offsetWidth - this._cornerWidth; + if (tableWidth <= 0) + return; + + var sumOfWeights = 0.0; + for (var i = 0; i < this._visibleColumnsArray.length; ++i) + sumOfWeights += this._visibleColumnsArray[i].weight; + + var sum = 0; + var lastOffset = 0; + + for (var i = 0; i < this._visibleColumnsArray.length; ++i) { + sum += this._visibleColumnsArray[i].weight; + var offset = (sum * tableWidth / sumOfWeights) | 0; + var width = (offset - lastOffset) + "px"; + this._headerTableColumnGroup.children[i].style.width = width; + this._dataTableColumnGroup.children[i].style.width = width; + lastOffset = offset; + } + + this._positionResizers(); + this.dispatchEventToListeners(WebInspector.DataGrid.Events.ColumnsResized); + }, + + /** + * @param {!Object.<string, boolean>} columnsVisibility + */ + setColumnsVisiblity: function(columnsVisibility) + { + this._visibleColumnsArray = []; + for (var i = 0; i < this._columnsArray.length; ++i) { + var column = this._columnsArray[i]; + if (columnsVisibility[column.identifier]) + this._visibleColumnsArray.push(column); + } + this._refreshHeader(); + this._applyColumnWeights(); + var nodes = this._enumerateChildren(this.rootNode(), [], -1); + for (var i = 0; i < nodes.length; ++i) + nodes[i].refresh(); + }, + + get scrollContainer() + { + return this._scrollContainer; + }, + + /** + * @return {boolean} + */ + isScrolledToLastRow: function() + { + return this._scrollContainer.isScrolledToBottom(); + }, + + scrollToLastRow: function() + { + this._scrollContainer.scrollTop = this._scrollContainer.scrollHeight - this._scrollContainer.offsetHeight; + }, + + _positionResizers: function() + { + var headerTableColumns = this._headerTableColumnGroup.children; + var numColumns = headerTableColumns.length - 1; // Do not process corner column. + var left = []; + var resizers = this._resizers; + + while (resizers.length > numColumns - 1) + resizers.pop().remove(); + + for (var i = 0; i < numColumns - 1; i++) { + // Get the width of the cell in the first (and only) row of the + // header table in order to determine the width of the column, since + // it is not possible to query a column for its width. + left[i] = (left[i-1] || 0) + this.headerTableBody.rows[0].cells[i].offsetWidth; + } + + // Make n - 1 resizers for n columns. + for (var i = 0; i < numColumns - 1; i++) { + var resizer = this._resizers[i]; + if (!resizer) { + // This is the first call to updateWidth, so the resizers need + // to be created. + resizer = document.createElement("div"); + resizer.__index = i; + resizer.classList.add("data-grid-resizer"); + // This resizer is associated with the column to its right. + WebInspector.installDragHandle(resizer, this._startResizerDragging.bind(this), this._resizerDragging.bind(this), this._endResizerDragging.bind(this), "col-resize"); + this.element.appendChild(resizer); + resizers.push(resizer); + } + if (resizer.__position !== left[i]) { + resizer.__position = left[i]; + resizer.style.left = left[i] + "px"; + } + } + }, + + addCreationNode: function(hasChildren) + { + if (this.creationNode) + this.creationNode.makeNormal(); + + var emptyData = {}; + for (var column in this._columns) + emptyData[column] = null; + this.creationNode = new WebInspector.CreationDataGridNode(emptyData, hasChildren); + this.rootNode().appendChild(this.creationNode); + }, + + sortNodes: function(comparator, reverseMode) + { + function comparatorWrapper(a, b) + { + if (a._dataGridNode._data.summaryRow) + return 1; + if (b._dataGridNode._data.summaryRow) + return -1; + + var aDataGirdNode = a._dataGridNode; + var bDataGirdNode = b._dataGridNode; + return reverseMode ? comparator(bDataGirdNode, aDataGirdNode) : comparator(aDataGirdNode, bDataGirdNode); + } + + var tbody = this.dataTableBody; + var tbodyParent = tbody.parentElement; + tbodyParent.removeChild(tbody); + + var childNodes = tbody.childNodes; + var fillerRow = childNodes[childNodes.length - 1]; + + var sortedRows = Array.prototype.slice.call(childNodes, 0, childNodes.length - 1); + sortedRows.sort(comparatorWrapper); + var sortedRowsLength = sortedRows.length; + + tbody.removeChildren(); + var previousSiblingNode = null; + for (var i = 0; i < sortedRowsLength; ++i) { + var row = sortedRows[i]; + var node = row._dataGridNode; + node.previousSibling = previousSiblingNode; + if (previousSiblingNode) + previousSiblingNode.nextSibling = node; + tbody.appendChild(row); + previousSiblingNode = node; + } + if (previousSiblingNode) + previousSiblingNode.nextSibling = null; + + tbody.appendChild(fillerRow); + tbodyParent.appendChild(tbody); + }, + + _keyDown: function(event) + { + if (!this.selectedNode || event.shiftKey || event.metaKey || event.ctrlKey || this._editing) + return; + + var handled = false; + var nextSelectedNode; + if (event.keyIdentifier === "Up" && !event.altKey) { + nextSelectedNode = this.selectedNode.traversePreviousNode(true); + while (nextSelectedNode && !nextSelectedNode.selectable) + nextSelectedNode = nextSelectedNode.traversePreviousNode(true); + handled = nextSelectedNode ? true : false; + } else if (event.keyIdentifier === "Down" && !event.altKey) { + nextSelectedNode = this.selectedNode.traverseNextNode(true); + while (nextSelectedNode && !nextSelectedNode.selectable) + nextSelectedNode = nextSelectedNode.traverseNextNode(true); + 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._isRoot) { + 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(); + } + } + } else if (event.keyCode === 8 || event.keyCode === 46) { + if (this._deleteCallback) { + handled = true; + this._deleteCallback(this.selectedNode); + this.changeNodeAfterDeletion(); + } + } else if (isEnterKey(event)) { + if (this._editCallback) { + handled = true; + this._startEditing(this.selectedNode._element.children[this._nextEditableColumn(-1)]); + } + } + + if (nextSelectedNode) { + nextSelectedNode.reveal(); + nextSelectedNode.select(); + } + + if (handled) + event.consume(true); + }, + + changeNodeAfterDeletion: function() + { + var nextSelectedNode = this.selectedNode.traverseNextNode(true); + while (nextSelectedNode && !nextSelectedNode.selectable) + nextSelectedNode = nextSelectedNode.traverseNextNode(true); + + if (!nextSelectedNode || nextSelectedNode.isCreationNode) { + nextSelectedNode = this.selectedNode.traversePreviousNode(true); + while (nextSelectedNode && !nextSelectedNode.selectable) + nextSelectedNode = nextSelectedNode.traversePreviousNode(true); + } + + if (nextSelectedNode) { + nextSelectedNode.reveal(); + nextSelectedNode.select(); + } + }, + + /** + * @param {!Node} target + * @return {?WebInspector.DataGridNode} + */ + dataGridNodeFromNode: function(target) + { + var rowElement = target.enclosingNodeOrSelfWithNodeName("tr"); + return rowElement && rowElement._dataGridNode; + }, + + /** + * @param {!Node} target + * @return {?string} + */ + columnIdentifierFromNode: function(target) + { + var cellElement = target.enclosingNodeOrSelfWithNodeName("td"); + return cellElement && cellElement.columnIdentifier_; + }, + + _clickInHeaderCell: function(event) + { + var cell = event.target.enclosingNodeOrSelfWithNodeName("th"); + if (!cell || (typeof cell.columnIdentifier === "undefined") || !cell.classList.contains("sortable")) + return; + + var sortOrder = WebInspector.DataGrid.Order.Ascending; + if ((cell === this._sortColumnCell) && this.isSortOrderAscending()) + sortOrder = WebInspector.DataGrid.Order.Descending; + + if (this._sortColumnCell) + this._sortColumnCell.removeMatchingStyleClasses("sort-\\w+"); + this._sortColumnCell = cell; + + cell.classList.add("sort-" + sortOrder); + + this.dispatchEventToListeners(WebInspector.DataGrid.Events.SortingChanged); + }, + + /** + * @param {string} columnIdentifier + * @param {!WebInspector.DataGrid.Order} sortOrder + */ + markColumnAsSortedBy: function(columnIdentifier, sortOrder) + { + if (this._sortColumnCell) + this._sortColumnCell.removeMatchingStyleClasses("sort-\\w+"); + this._sortColumnCell = this._headerTableHeaders[columnIdentifier]; + this._sortColumnCell.classList.add("sort-" + sortOrder); + }, + + /** + * @param {string} columnIdentifier + * @return {!Element} + */ + headerTableHeader: function(columnIdentifier) + { + return this._headerTableHeaders[columnIdentifier]; + }, + + _mouseDownInDataTable: function(event) + { + var gridNode = this.dataGridNodeFromNode(event.target); + if (!gridNode || !gridNode.selectable) + return; + + if (gridNode.isEventWithinDisclosureTriangle(event)) + return; + + if (event.metaKey) { + if (gridNode.selected) + gridNode.deselect(); + else + gridNode.select(); + } else + gridNode.select(); + }, + + _contextMenuInDataTable: function(event) + { + var contextMenu = new WebInspector.ContextMenu(event); + + var gridNode = this.dataGridNodeFromNode(event.target); + if (this._refreshCallback && (!gridNode || gridNode !== this.creationNode)) + contextMenu.appendItem(WebInspector.UIString("Refresh"), this._refreshCallback.bind(this)); + + if (gridNode && gridNode.selectable && !gridNode.isEventWithinDisclosureTriangle(event)) { + if (this._editCallback) { + if (gridNode === this.creationNode) + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add new" : "Add New"), this._startEditing.bind(this, event.target)); + else { + var columnIdentifier = this.columnIdentifierFromNode(event.target); + if (columnIdentifier && this._columns[columnIdentifier].editable) + contextMenu.appendItem(WebInspector.UIString("Edit \"%s\"", this._columns[columnIdentifier].title), this._startEditing.bind(this, event.target)); + } + } + if (this._deleteCallback && gridNode !== this.creationNode) + contextMenu.appendItem(WebInspector.UIString("Delete"), this._deleteCallback.bind(this, gridNode)); + if (this._contextMenuCallback) + this._contextMenuCallback(contextMenu, gridNode); + } + + contextMenu.show(); + }, + + _clickInDataTable: function(event) + { + var gridNode = this.dataGridNodeFromNode(event.target); + 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(); + } + }, + + get resizeMethod() + { + if (typeof this._resizeMethod === "undefined") + return WebInspector.DataGrid.ResizeMethod.Nearest; + return this._resizeMethod; + }, + + set resizeMethod(method) + { + this._resizeMethod = method; + }, + + /** + * @return {boolean} + */ + _startResizerDragging: function(event) + { + this._currentResizer = event.target; + return true; + }, + + _resizerDragging: function(event) + { + var resizer = this._currentResizer; + if (!resizer) + return; + + var tableWidth = this.element.offsetWidth; // Cache it early, before we invalidate layout. + + // Constrain the dragpoint to be within the containing div of the + // datagrid. + var dragPoint = event.clientX - this.element.totalOffsetLeft(); + var firstRowCells = this.headerTableBody.rows[0].cells; + var leftEdgeOfPreviousColumn = 0; + // Constrain the dragpoint to be within the space made up by the + // column directly to the left and the column directly to the right. + var leftCellIndex = resizer.__index; + var rightCellIndex = leftCellIndex + 1; + for (var i = 0; i < leftCellIndex; i++) + leftEdgeOfPreviousColumn += firstRowCells[i].offsetWidth; + + // Differences for other resize methods + if (this.resizeMethod == WebInspector.DataGrid.ResizeMethod.Last) { + rightCellIndex = this._resizers.length; + } else if (this.resizeMethod == WebInspector.DataGrid.ResizeMethod.First) { + leftEdgeOfPreviousColumn += firstRowCells[leftCellIndex].offsetWidth - firstRowCells[0].offsetWidth; + leftCellIndex = 0; + } + + var rightEdgeOfNextColumn = leftEdgeOfPreviousColumn + firstRowCells[leftCellIndex].offsetWidth + firstRowCells[rightCellIndex].offsetWidth; + + // Give each column some padding so that they don't disappear. + var leftMinimum = leftEdgeOfPreviousColumn + this.ColumnResizePadding; + var rightMaximum = rightEdgeOfNextColumn - this.ColumnResizePadding; + if (leftMinimum > rightMaximum) + return; + + dragPoint = Number.constrain(dragPoint, leftMinimum, rightMaximum); + + var position = (dragPoint - this.CenterResizerOverBorderAdjustment); + resizer.__position = position; + resizer.style.left = position + "px"; + + var pxLeftColumn = (dragPoint - leftEdgeOfPreviousColumn) + "px"; + this._headerTableColumnGroup.children[leftCellIndex].style.width = pxLeftColumn; + this._dataTableColumnGroup.children[leftCellIndex].style.width = pxLeftColumn; + + var pxRightColumn = (rightEdgeOfNextColumn - dragPoint) + "px"; + this._headerTableColumnGroup.children[rightCellIndex].style.width = pxRightColumn; + this._dataTableColumnGroup.children[rightCellIndex].style.width = pxRightColumn; + + var leftColumn = this._visibleColumnsArray[leftCellIndex]; + var rightColumn = this._visibleColumnsArray[rightCellIndex]; + if (leftColumn.weight || rightColumn.weight) { + var sumOfWeights = leftColumn.weight + rightColumn.weight; + var delta = rightEdgeOfNextColumn - leftEdgeOfPreviousColumn; + leftColumn.weight = (dragPoint - leftEdgeOfPreviousColumn) * sumOfWeights / delta; + rightColumn.weight = (rightEdgeOfNextColumn - dragPoint) * sumOfWeights / delta; + } + + this._positionResizers(); + event.preventDefault(); + this.dispatchEventToListeners(WebInspector.DataGrid.Events.ColumnsResized); + }, + + /** + * @param {string} columnId + * @return {number} + */ + columnOffset: function(columnId) + { + if (!this.element.offsetWidth) + return 0; + for (var i = 1; i < this._visibleColumnsArray.length; ++i) { + if (columnId === this._visibleColumnsArray[i].identifier) + return this._resizers[i - 1].__position; + } + return 0; + }, + + _endResizerDragging: function(event) + { + this._currentResizer = null; + this._saveColumnWeights(); + this.dispatchEventToListeners(WebInspector.DataGrid.Events.ColumnsResized); + }, + + /** + * @return {?Element} + */ + defaultAttachLocation: function() + { + return this.dataTableBody.firstChild; + }, + + ColumnResizePadding: 24, + + CenterResizerOverBorderAdjustment: 3, + + __proto__: WebInspector.View.prototype +} + +WebInspector.DataGrid.ResizeMethod = { + Nearest: "nearest", + First: "first", + Last: "last" +} + +/** + * @constructor + * @extends {WebInspector.Object} + * @param {?Object.<string, *>=} data + * @param {boolean=} hasChildren + */ +WebInspector.DataGridNode = function(data, hasChildren) +{ + this._expanded = false; + this._selected = false; + this._shouldRefreshChildren = true; + /** @type {!Object.<string, *>} */ + this._data = data || {}; + /** @type {boolean} */ + this.hasChildren = hasChildren || false; + /** @type {!Array.<!WebInspector.DataGridNode>} */ + this.children = []; + this.dataGrid = null; + this.parent = null; + /** @type {?WebInspector.DataGridNode} */ + this.previousSibling = null; + /** @type {?WebInspector.DataGridNode} */ + this.nextSibling = null; + this.disclosureToggleWidth = 10; +} + +WebInspector.DataGridNode.prototype = { + /** @type {boolean} */ + selectable: true, + + /** @type {boolean} */ + _isRoot: false, + + 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.classList.add("parent"); + if (this.expanded) + this._element.classList.add("expanded"); + if (this.selected) + this._element.classList.add("selected"); + if (this.revealed) + this._element.classList.add("revealed"); + + this.createCells(); + this._element.createChild("td", "corner"); + + return this._element; + }, + + createCells: function() + { + var columnsArray = this.dataGrid._visibleColumnsArray; + for (var i = 0; i < columnsArray.length; ++i) { + var cell = this.createCell(columnsArray[i].identifier); + this._element.appendChild(cell); + } + }, + + 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._isRoot) { + if (!currentAncestor.expanded) { + this._revealed = false; + return false; + } + + currentAncestor = currentAncestor.parent; + } + + this._revealed = true; + return true; + }, + + set hasChildren(x) + { + if (this._hasChildren === x) + return; + + this._hasChildren = x; + + if (!this._element) + return; + + this._element.classList.toggle("parent", this._hasChildren); + this._element.classList.toggle("expanded", this._hasChildren && this.expanded); + }, + + get hasChildren() + { + return this._hasChildren; + }, + + set revealed(x) + { + if (this._revealed === x) + return; + + this._revealed = x; + + if (this._element) + this._element.classList.toggle("revealed", this._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._isRoot) + this._depth = this.parent.depth + 1; + else + this._depth = 0; + return this._depth; + }, + + get leftPadding() + { + if (typeof this._leftPadding === "number") + return this._leftPadding; + + this._leftPadding = this.depth * this.dataGrid.indentWidth; + return this._leftPadding; + }, + + 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; + }, + + /** + * @param {boolean} x + */ + set expanded(x) + { + if (x) + this.expand(); + else + this.collapse(); + }, + + refresh: function() + { + if (!this._element || !this.dataGrid) + return; + + this._element.removeChildren(); + this.createCells(); + this._element.createChild("td", "corner"); + }, + + /** + * @param {string} columnIdentifier + * @return {!Element} + */ + createTD: function(columnIdentifier) + { + var cell = document.createElement("td"); + cell.className = columnIdentifier + "-column"; + cell.columnIdentifier_ = columnIdentifier; + + var alignment = this.dataGrid._columns[columnIdentifier].align; + if (alignment) + cell.classList.add(alignment); + + return cell; + }, + + /** + * @param {string} columnIdentifier + * @return {!Element} + */ + createCell: function(columnIdentifier) + { + var cell = this.createTD(columnIdentifier); + + var data = this.data[columnIdentifier]; + if (data instanceof Node) { + cell.appendChild(data); + } else { + cell.textContent = data; + if (this.dataGrid._columns[columnIdentifier].longText) + cell.title = data; + } + + if (columnIdentifier === this.dataGrid.disclosureColumnIdentifier) { + cell.classList.add("disclosure"); + if (this.leftPadding) + cell.style.setProperty("padding-left", this.leftPadding + "px"); + } + + return cell; + }, + + /** + * @return {number} + */ + nodeSelfHeight: function() + { + return 16; + }, + + /** + * @param {!WebInspector.DataGridNode} child + */ + appendChild: function(child) + { + this.insertChild(child, this.children.length); + }, + + /** + * @param {!WebInspector.DataGridNode} child + * @param {number} index + */ + insertChild: function(child, index) + { + if (!child) + throw("insertChild: Node can't be undefined or null."); + if (child.parent === this) + throw("insertChild: Node is already a child of this node."); + + if (child.parent) + child.parent.removeChild(child); + + this.children.splice(index, 0, child); + this.hasChildren = true; + + child.parent = this; + child.dataGrid = this.dataGrid; + child._recalculateSiblings(index); + + delete child._depth; + delete child._revealed; + delete child._attached; + child._shouldRefreshChildren = true; + + var current = child.children[0]; + while (current) { + current.dataGrid = this.dataGrid; + delete current._depth; + delete current._revealed; + delete current._attached; + current._shouldRefreshChildren = true; + current = current.traverseNextNode(false, child, true); + } + + if (this.expanded) + child._attach(); + if (!this.revealed) + child.revealed = false; + }, + + /** + * @param {!WebInspector.DataGridNode} child + */ + removeChild: function(child) + { + if (!child) + throw("removeChild: Node can't be undefined or null."); + if (child.parent !== this) + throw("removeChild: Node is not a child of this node."); + + child.deselect(); + child._detach(); + + 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; + + if (this.children.length <= 0) + this.hasChildren = false; + }, + + 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 = []; + this.hasChildren = false; + }, + + /** + * @param {number} myIndex + */ + _recalculateSiblings: function(myIndex) + { + if (!this.parent) + return; + + var previousChild = this.parent.children[myIndex - 1] || null; + if (previousChild) + previousChild.nextSibling = this; + this.previousSibling = previousChild; + + var nextChild = this.parent.children[myIndex + 1] || null; + if (nextChild) + nextChild.previousSibling = this; + this.nextSibling = nextChild; + }, + + collapse: function() + { + if (this._isRoot) + return; + if (this._element) + this._element.classList.remove("expanded"); + + this._expanded = false; + + for (var i = 0; i < this.children.length; ++i) + this.children[i].revealed = false; + }, + + collapseRecursively: function() + { + var item = this; + while (item) { + if (item.expanded) + item.collapse(); + item = item.traverseNextNode(false, this, true); + } + }, + + populate: function() { }, + + expand: function() + { + if (!this.hasChildren || this.expanded) + return; + if (this._isRoot) + 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.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.classList.add("expanded"); + + this._expanded = true; + }, + + expandRecursively: function() + { + var item = this; + while (item) { + item.expand(); + item = item.traverseNextNode(false, this); + } + }, + + reveal: function() + { + if (this._isRoot) + return; + var currentAncestor = this.parent; + while (currentAncestor && !currentAncestor._isRoot) { + if (!currentAncestor.expanded) + currentAncestor.expand(); + currentAncestor = currentAncestor.parent; + } + + this.element.scrollIntoViewIfNeeded(false); + }, + + /** + * @param {boolean=} supressSelectedEvent + */ + 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.classList.add("selected"); + + if (!supressSelectedEvent) + this.dataGrid.dispatchEventToListeners(WebInspector.DataGrid.Events.SelectedNode); + }, + + revealAndSelect: function() + { + if (this._isRoot) + return; + this.reveal(); + this.select(); + }, + + /** + * @param {boolean=} supressDeselectedEvent + */ + 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.classList.remove("selected"); + + if (!supressDeselectedEvent) + this.dataGrid.dispatchEventToListeners(WebInspector.DataGrid.Events.DeselectedNode); + }, + + /** + * @param {boolean} skipHidden + * @param {?WebInspector.DataGridNode=} stayWithin + * @param {boolean=} dontPopulate + * @param {!Object=} info + * @return {?WebInspector.DataGridNode} + */ + traverseNextNode: function(skipHidden, stayWithin, dontPopulate, info) + { + if (!dontPopulate && this.hasChildren) + this.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._isRoot && !((!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; + }, + + /** + * @param {boolean} skipHidden + * @param {boolean=} dontPopulate + * @return {?WebInspector.DataGridNode} + */ + traversePreviousNode: function(skipHidden, dontPopulate) + { + var node = (!skipHidden || this.revealed) ? this.previousSibling : null; + if (!dontPopulate && node && node.hasChildren) + node.populate(); + + while (node && ((!skipHidden || (node.revealed && node.expanded)) ? node.children[node.children.length - 1] : null)) { + if (!dontPopulate && node.hasChildren) + node.populate(); + node = ((!skipHidden || (node.revealed && node.expanded)) ? node.children[node.children.length - 1] : null); + } + + if (node) + return node; + + if (!this.parent || this.parent._isRoot) + return null; + + return this.parent; + }, + + /** + * @return {boolean} + */ + isEventWithinDisclosureTriangle: function(event) + { + if (!this.hasChildren) + return false; + var cell = event.target.enclosingNodeOrSelfWithNodeName("td"); + if (!cell.classList.contains("disclosure")) + return false; + + var left = cell.totalOffsetLeft() + this.leftPadding; + 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) + nextNode = previousNode.element.nextSibling; + if (!nextNode) + nextNode = this.dataGrid.defaultAttachLocation(); + 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.remove(); + + for (var i = 0; i < this.children.length; ++i) + this.children[i]._detach(); + + this.wasDetached(); + }, + + wasDetached: function() + { + }, + + savePosition: function() + { + if (this._savedPosition) + return; + + if (!this.parent) + throw("savePosition: Node must have a parent."); + this._savedPosition = { + parent: this.parent, + index: this.parent.children.indexOf(this) + }; + }, + + restorePosition: function() + { + if (!this._savedPosition) + return; + + if (this.parent !== this._savedPosition.parent) + this._savedPosition.parent.insertChild(this, this._savedPosition.index); + + delete this._savedPosition; + }, + + __proto__: WebInspector.Object.prototype +} + +/** + * @constructor + * @extends {WebInspector.DataGridNode} + */ +WebInspector.CreationDataGridNode = function(data, hasChildren) +{ + WebInspector.DataGridNode.call(this, data, hasChildren); + this.isCreationNode = true; +} + +WebInspector.CreationDataGridNode.prototype = { + makeNormal: function() + { + delete this.isCreationNode; + delete this.makeNormal; + }, + + __proto__: WebInspector.DataGridNode.prototype +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/Dialog.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/Dialog.js new file mode 100644 index 00000000000..feab83ac024 --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/Dialog.js @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2012 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER 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. + */ + +/** + * @constructor + * @param {!Element} relativeToElement + * @param {!WebInspector.DialogDelegate} delegate + */ +WebInspector.Dialog = function(relativeToElement, delegate) +{ + this._delegate = delegate; + this._relativeToElement = relativeToElement; + + this._glassPane = new WebInspector.GlassPane(); + // Install glass pane capturing events. + this._glassPane.element.tabIndex = 0; + this._glassPane.element.addEventListener("focus", this._onGlassPaneFocus.bind(this), false); + this._glassPane.element.addEventListener("keydown", this._onGlassPaneKeyDown.bind(this), false); + + this._element = this._glassPane.element.createChild("div"); + this._element.tabIndex = 0; + this._element.addEventListener("focus", this._onFocus.bind(this), false); + this._element.addEventListener("keydown", this._onKeyDown.bind(this), false); + this._closeKeys = [ + WebInspector.KeyboardShortcut.Keys.Enter.code, + WebInspector.KeyboardShortcut.Keys.Esc.code, + ]; + + delegate.show(this._element); + + this._position(); + this._delegate.focus(); +} + +/** + * @return {?WebInspector.Dialog} + */ +WebInspector.Dialog.currentInstance = function() +{ + return WebInspector.Dialog._instance; +} + +/** + * @param {!Element} relativeToElement + * @param {!WebInspector.DialogDelegate} delegate + */ +WebInspector.Dialog.show = function(relativeToElement, delegate) +{ + if (WebInspector.Dialog._instance) + return; + WebInspector.Dialog._instance = new WebInspector.Dialog(relativeToElement, delegate); +} + +WebInspector.Dialog.hide = function() +{ + if (!WebInspector.Dialog._instance) + return; + WebInspector.Dialog._instance._hide(); +} + +WebInspector.Dialog.prototype = { + _hide: function() + { + if (this._isHiding) + return; + this._isHiding = true; + + this._delegate.willHide(); + + delete WebInspector.Dialog._instance; + this._glassPane.dispose(); + }, + + _onGlassPaneFocus: function(event) + { + this._hide(); + }, + + /** + * @param {?Event} event + */ + _onGlassPaneKeyDown: function(event) + { + var actions = WebInspector.shortcutRegistry.applicableActions(WebInspector.KeyboardShortcut.makeKeyFromEvent(/** @type {?KeyboardEvent} */ (event))); + if (actions.length) + event.consume(true); + }, + + _onFocus: function(event) + { + this._delegate.focus(); + }, + + _position: function() + { + this._delegate.position(this._element, this._relativeToElement); + }, + + _onKeyDown: function(event) + { + if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Tab.code) { + event.preventDefault(); + return; + } + + if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Enter.code) + this._delegate.onEnter(); + + if (this._closeKeys.indexOf(event.keyCode) >= 0) { + this._hide(); + event.consume(true); + } + } +}; + +/** + * @constructor + * @extends {WebInspector.Object} + */ +WebInspector.DialogDelegate = function() +{ + /** @type {!Element} */ + this.element; +} + +WebInspector.DialogDelegate.prototype = { + /** + * @param {!Element} element + */ + show: function(element) + { + element.appendChild(this.element); + this.element.classList.add("dialog-contents"); + element.classList.add("dialog"); + }, + + /** + * @param {!Element} element + * @param {!Element} relativeToElement + */ + position: function(element, relativeToElement) + { + var container = WebInspector.Dialog._modalHostView.element; + var box = relativeToElement.boxInWindow(window).relativeToElement(container); + + var positionX = box.x + (relativeToElement.offsetWidth - element.offsetWidth) / 2; + positionX = Number.constrain(positionX, 0, container.offsetWidth - element.offsetWidth); + + var positionY = box.y + (relativeToElement.offsetHeight - element.offsetHeight) / 2; + positionY = Number.constrain(positionY, 0, container.offsetHeight - element.offsetHeight); + + element.style.position = "absolute"; + element.positionAt(positionX, positionY, container); + }, + + focus: function() { }, + + onEnter: function() { }, + + willHide: function() { }, + + __proto__: WebInspector.Object.prototype +} + +/** @type {?WebInspector.View} */ +WebInspector.Dialog._modalHostView = null; + +/** + * @param {!WebInspector.View} view + */ +WebInspector.Dialog.setModalHostView = function(view) +{ + WebInspector.Dialog._modalHostView = view; +}; + +/** + * FIXME: make utility method in Dialog, so clients use it instead of this getter. + * Method should be like Dialog.showModalElement(position params, reposition callback). + * @return {?WebInspector.View} + */ +WebInspector.Dialog.modalHostView = function() +{ + return WebInspector.Dialog._modalHostView; +}; + +WebInspector.Dialog.modalHostRepositioned = function() +{ + if (WebInspector.Dialog._instance) + WebInspector.Dialog._instance._position(); +}; + diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/DropDownMenu.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/DropDownMenu.js new file mode 100644 index 00000000000..129cb75ba85 --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/DropDownMenu.js @@ -0,0 +1,68 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @constructor + * @extends {WebInspector.Object} + */ +WebInspector.DropDownMenu = function() +{ + this.element = document.createElementWithClass("select", "drop-down-menu"); + this.element.addEventListener("mousedown", this._onBeforeMouseDown.bind(this), true); + this.element.addEventListener("mousedown", consumeEvent, false); + this.element.addEventListener("change", this._onChange.bind(this), false); +} + +WebInspector.DropDownMenu.Events = { + BeforeShow: "BeforeShow", + ItemSelected: "ItemSelected" +} + +WebInspector.DropDownMenu.prototype = { + _onBeforeMouseDown: function() + { + this.dispatchEventToListeners(WebInspector.DropDownMenu.Events.BeforeShow, null); + }, + + _onChange: function() + { + var options = this.element.options; + var selectedOption = options[this.element.selectedIndex]; + this.dispatchEventToListeners(WebInspector.DropDownMenu.Events.ItemSelected, selectedOption.id); + }, + + /** + * @param {string} id + * @param {string} title + */ + addItem: function(id, title) + { + var option = new Option(title); + option.id = id; + this.element.appendChild(option); + }, + + /** + * @param {?string} id + */ + selectItem: function(id) + { + var children = this.element.children; + for (var i = 0; i < children.length; ++i) { + var child = children[i]; + if (child.id === id) { + this.element.selectedIndex = i; + return; + } + } + this.element.selectedIndex = -1; + }, + + clear: function() + { + this.element.removeChildren(); + }, + + __proto__: WebInspector.Object.prototype +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/EmptyView.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/EmptyView.js new file mode 100644 index 00000000000..0394ecbe78f --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/EmptyView.js @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2011 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER 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. + */ + +/** + * @constructor + * @extends {WebInspector.VBox} + */ +WebInspector.EmptyView = function(text) +{ + WebInspector.VBox.call(this); + this._text = text; +} + +WebInspector.EmptyView.prototype = { + wasShown: function() + { + this.element.classList.add("empty-view"); + this.element.textContent = this._text; + }, + + set text(text) + { + this._text = text; + if (this.isShowing()) + this.element.textContent = this._text; + }, + + __proto__: WebInspector.VBox.prototype +} + diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/InplaceEditor.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/InplaceEditor.js new file mode 100644 index 00000000000..d0e1c363e1c --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/InplaceEditor.js @@ -0,0 +1,260 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @constructor + */ +WebInspector.InplaceEditor = function() +{ +}; + +/** + * @param {!Element} element + * @param {!WebInspector.InplaceEditor.Config=} config + * @return {?{cancel: function(), commit: function(), setWidth: function(number)}} + */ +WebInspector.InplaceEditor.startEditing = function(element, config) +{ + if (config.multiline) + return WebInspector.moduleManager.instance(WebInspector.InplaceEditor).startEditing(element, config); + + if (!WebInspector.InplaceEditor._defaultInstance) + WebInspector.InplaceEditor._defaultInstance = new WebInspector.InplaceEditor(); + return WebInspector.InplaceEditor._defaultInstance.startEditing(element, config); +} + +WebInspector.InplaceEditor.prototype = { + /** + * @return {string} + */ + editorContent: function(editingContext) { + var element = editingContext.element; + if (element.tagName === "INPUT" && element.type === "text") + return element.value; + + return element.textContent; + }, + + setUpEditor: function(editingContext) + { + var element = editingContext.element; + element.classList.add("editing"); + + var oldTabIndex = element.getAttribute("tabIndex"); + if (typeof oldTabIndex !== "number" || oldTabIndex < 0) + element.tabIndex = 0; + WebInspector.setCurrentFocusElement(element); + editingContext.oldTabIndex = oldTabIndex; + }, + + closeEditor: function(editingContext) + { + var element = editingContext.element; + element.classList.remove("editing"); + + if (typeof editingContext.oldTabIndex !== "number") + element.removeAttribute("tabIndex"); + else + element.tabIndex = editingContext.oldTabIndex; + element.scrollTop = 0; + element.scrollLeft = 0; + }, + + cancelEditing: function(editingContext) + { + var element = editingContext.element; + if (element.tagName === "INPUT" && element.type === "text") + element.value = editingContext.oldText; + else + element.textContent = editingContext.oldText; + }, + + augmentEditingHandle: function(editingContext, handle) + { + }, + + /** + * @param {!Element} element + * @param {!WebInspector.InplaceEditor.Config=} config + * @return {?{cancel: function(), commit: function()}} + */ + startEditing: function(element, config) + { + if (!WebInspector.markBeingEdited(element, true)) + return null; + + config = config || new WebInspector.InplaceEditor.Config(function() {}, function() {}); + var editingContext = { element: element, config: config }; + var committedCallback = config.commitHandler; + var cancelledCallback = config.cancelHandler; + var pasteCallback = config.pasteHandler; + var context = config.context; + var isMultiline = config.multiline || false; + var moveDirection = ""; + var self = this; + + /** + * @param {?Event} e + */ + function consumeCopy(e) + { + e.consume(); + } + + this.setUpEditor(editingContext); + + editingContext.oldText = isMultiline ? config.initialValue : this.editorContent(editingContext); + + /** + * @param {?Event=} e + */ + function blurEventListener(e) { + if (!isMultiline || !e || !e.relatedTarget || !e.relatedTarget.isSelfOrDescendant(element)) + editingCommitted.call(element); + } + + function cleanUpAfterEditing() + { + WebInspector.markBeingEdited(element, false); + + element.removeEventListener("blur", blurEventListener, isMultiline); + element.removeEventListener("keydown", keyDownEventListener, true); + if (pasteCallback) + element.removeEventListener("paste", pasteEventListener, true); + + WebInspector.restoreFocusFromElement(element); + self.closeEditor(editingContext); + } + + /** @this {Element} */ + function editingCancelled() + { + self.cancelEditing(editingContext); + cleanUpAfterEditing(); + cancelledCallback(this, context); + } + + /** @this {Element} */ + function editingCommitted() + { + cleanUpAfterEditing(); + + committedCallback(this, self.editorContent(editingContext), editingContext.oldText, context, moveDirection); + } + + function defaultFinishHandler(event) + { + var isMetaOrCtrl = WebInspector.isMac() ? + event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey : + event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey; + if (isEnterKey(event) && (event.isMetaOrCtrlForTest || !isMultiline || isMetaOrCtrl)) + return "commit"; + else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code || event.keyIdentifier === "U+001B") + return "cancel"; + else if (!isMultiline && event.keyIdentifier === "U+0009") // Tab key + return "move-" + (event.shiftKey ? "backward" : "forward"); + } + + function handleEditingResult(result, event) + { + if (result === "commit") { + editingCommitted.call(element); + event.consume(true); + } else if (result === "cancel") { + editingCancelled.call(element); + event.consume(true); + } else if (result && result.startsWith("move-")) { + moveDirection = result.substring(5); + if (event.keyIdentifier !== "U+0009") + blurEventListener(); + } + } + + function pasteEventListener(event) + { + var result = pasteCallback(event); + handleEditingResult(result, event); + } + + function keyDownEventListener(event) + { + var handler = config.customFinishHandler || defaultFinishHandler; + var result = handler(event); + handleEditingResult(result, event); + } + + element.addEventListener("blur", blurEventListener, isMultiline); + element.addEventListener("keydown", keyDownEventListener, true); + if (pasteCallback) + element.addEventListener("paste", pasteEventListener, true); + + var handle = { + cancel: editingCancelled.bind(element), + commit: editingCommitted.bind(element) + }; + this.augmentEditingHandle(editingContext, handle); + return handle; + } +} + +/** + * @constructor + * @param {function(!Element,string,string,T,string)} commitHandler + * @param {function(!Element,T)} cancelHandler + * @param {T=} context + * @template T + */ +WebInspector.InplaceEditor.Config = function(commitHandler, cancelHandler, context) +{ + this.commitHandler = commitHandler; + this.cancelHandler = cancelHandler + this.context = context; + + /** + * Handles the "paste" event, return values are the same as those for customFinishHandler + * @type {function(!Element)|undefined} + */ + this.pasteHandler; + + /** + * Whether the edited element is multiline + * @type {boolean|undefined} + */ + this.multiline; + + /** + * Custom finish handler for the editing session (invoked on keydown) + * @type {function(!Element,*)|undefined} + */ + this.customFinishHandler; +} + +WebInspector.InplaceEditor.Config.prototype = { + setPasteHandler: function(pasteHandler) + { + this.pasteHandler = pasteHandler; + }, + + /** + * @param {string} initialValue + * @param {!Object} mode + * @param {string} theme + * @param {boolean=} lineWrapping + * @param {boolean=} smartIndent + */ + setMultilineOptions: function(initialValue, mode, theme, lineWrapping, smartIndent) + { + this.multiline = true; + this.initialValue = initialValue; + this.mode = mode; + this.theme = theme; + this.lineWrapping = lineWrapping; + this.smartIndent = smartIndent; + }, + + setCustomFinishHandler: function(customFinishHandler) + { + this.customFinishHandler = customFinishHandler; + } +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/KeyboardShortcut.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/KeyboardShortcut.js new file mode 100644 index 00000000000..0d12ed03c7c --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/KeyboardShortcut.js @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2009 Apple Inc. All rights reserved. + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + */ +WebInspector.KeyboardShortcut = function() +{ +} + +/** + * Constants for encoding modifier key set as a bit mask. + * @see #_makeKeyFromCodeAndModifiers + */ +WebInspector.KeyboardShortcut.Modifiers = { + None: 0, // Constant for empty modifiers set. + Shift: 1, + Ctrl: 2, + Alt: 4, + Meta: 8, // Command key on Mac, Win key on other platforms. + get CtrlOrMeta() + { + // "default" command/ctrl key for platform, Command on Mac, Ctrl on other platforms + return WebInspector.isMac() ? this.Meta : this.Ctrl; + }, + get ShiftOrOption() + { + // Shift on Mac, Alt on other platforms + return WebInspector.isMac() ? this.Shift : this.Alt; + } +}; + +/** @typedef {!{code: number, name: (string|!Object.<string, string>)}} */ +WebInspector.KeyboardShortcut.Key; + +/** @type {!Object.<string, !WebInspector.KeyboardShortcut.Key>} */ +WebInspector.KeyboardShortcut.Keys = { + Backspace: { code: 8, name: "\u21a4" }, + Tab: { code: 9, name: { mac: "\u21e5", other: "Tab" } }, + Enter: { code: 13, name: { mac: "\u21a9", other: "Enter" } }, + Ctrl: { code: 17, name: "Ctrl" }, + Esc: { code: 27, name: { mac: "\u238b", other: "Esc" } }, + Space: { code: 32, name: "Space" }, + PageUp: { code: 33, name: { mac: "\u21de", other: "PageUp" } }, // also NUM_NORTH_EAST + PageDown: { code: 34, name: { mac: "\u21df", other: "PageDown" } }, // also NUM_SOUTH_EAST + End: { code: 35, name: { mac: "\u2197", other: "End" } }, // also NUM_SOUTH_WEST + Home: { code: 36, name: { mac: "\u2196", other: "Home" } }, // also NUM_NORTH_WEST + Left: { code: 37, name: "\u2190" }, // also NUM_WEST + Up: { code: 38, name: "\u2191" }, // also NUM_NORTH + Right: { code: 39, name: "\u2192" }, // also NUM_EAST + Down: { code: 40, name: "\u2193" }, // also NUM_SOUTH + Delete: { code: 46, name: "Del" }, + Zero: { code: 48, name: "0" }, + H: { code: 72, name: "H" }, + Meta: { code: 91, name: "Meta" }, + F1: { code: 112, name: "F1" }, + F2: { code: 113, name: "F2" }, + F3: { code: 114, name: "F3" }, + F4: { code: 115, name: "F4" }, + F5: { code: 116, name: "F5" }, + F6: { code: 117, name: "F6" }, + F7: { code: 118, name: "F7" }, + F8: { code: 119, name: "F8" }, + F9: { code: 120, name: "F9" }, + F10: { code: 121, name: "F10" }, + F11: { code: 122, name: "F11" }, + F12: { code: 123, name: "F12" }, + Semicolon: { code: 186, name: ";" }, + NumpadPlus: { code: 107, name: "Numpad +" }, + NumpadMinus: { code: 109, name: "Numpad -" }, + Numpad0: { code: 96, name: "Numpad 0" }, + Plus: { code: 187, name: "+" }, + Comma: { code: 188, name: "," }, + Minus: { code: 189, name: "-" }, + Period: { code: 190, name: "." }, + Slash: { code: 191, name: "/" }, + QuestionMark: { code: 191, name: "?" }, + Apostrophe: { code: 192, name: "`" }, + Tilde: { code: 192, name: "Tilde" }, + Backslash: { code: 220, name: "\\" }, + SingleQuote: { code: 222, name: "\'" }, + get CtrlOrMeta() + { + // "default" command/ctrl key for platform, Command on Mac, Ctrl on other platforms + return WebInspector.isMac() ? this.Meta : this.Ctrl; + }, +}; + +WebInspector.KeyboardShortcut.KeyBindings = {}; + +(function() { + for (var key in WebInspector.KeyboardShortcut.Keys) { + var descriptor = WebInspector.KeyboardShortcut.Keys[key]; + if (typeof descriptor === "object" && descriptor["code"]) { + var name = typeof descriptor["name"] === "string" ? descriptor["name"] : key; + WebInspector.KeyboardShortcut.KeyBindings[name] = { code: descriptor["code"] }; + } + } +})(); + +/** + * Creates a number encoding keyCode in the lower 8 bits and modifiers mask in the higher 8 bits. + * It is useful for matching pressed keys. + * + * @param {number|string} keyCode The code of the key, or a character "a-z" which is converted to a keyCode value. + * @param {number=} modifiers Optional list of modifiers passed as additional parameters. + * @return {number} + */ +WebInspector.KeyboardShortcut.makeKey = function(keyCode, modifiers) +{ + if (typeof keyCode === "string") + keyCode = keyCode.charCodeAt(0) - (/^[a-z]/.test(keyCode) ? 32 : 0); + modifiers = modifiers || WebInspector.KeyboardShortcut.Modifiers.None; + return WebInspector.KeyboardShortcut._makeKeyFromCodeAndModifiers(keyCode, modifiers); +} + +/** + * @param {?KeyboardEvent} keyboardEvent + * @return {number} + */ +WebInspector.KeyboardShortcut.makeKeyFromEvent = function(keyboardEvent) +{ + var modifiers = WebInspector.KeyboardShortcut.Modifiers.None; + if (keyboardEvent.shiftKey) + modifiers |= WebInspector.KeyboardShortcut.Modifiers.Shift; + if (keyboardEvent.ctrlKey) + modifiers |= WebInspector.KeyboardShortcut.Modifiers.Ctrl; + if (keyboardEvent.altKey) + modifiers |= WebInspector.KeyboardShortcut.Modifiers.Alt; + if (keyboardEvent.metaKey) + modifiers |= WebInspector.KeyboardShortcut.Modifiers.Meta; + + function keyCodeForEvent(keyboardEvent) + { + // Use either a real or a synthetic keyCode (for events originating from extensions). + return keyboardEvent.keyCode || keyboardEvent["__keyCode"]; + } + return WebInspector.KeyboardShortcut._makeKeyFromCodeAndModifiers(keyCodeForEvent(keyboardEvent), modifiers); +} + +/** + * @param {?KeyboardEvent} event + * @return {boolean} + */ +WebInspector.KeyboardShortcut.eventHasCtrlOrMeta = function(event) +{ + return WebInspector.isMac() ? event.metaKey && !event.ctrlKey : event.ctrlKey && !event.metaKey; +} + +/** + * @param {?Event} event + * @return {boolean} + */ +WebInspector.KeyboardShortcut.hasNoModifiers = function(event) +{ + return !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey; +} + +/** @typedef {!{key: number, name: string}} */ +WebInspector.KeyboardShortcut.Descriptor; + +/** + * @param {string|!WebInspector.KeyboardShortcut.Key} key + * @param {number=} modifiers + * @return {!WebInspector.KeyboardShortcut.Descriptor} + */ +WebInspector.KeyboardShortcut.makeDescriptor = function(key, modifiers) +{ + return { + key: WebInspector.KeyboardShortcut.makeKey(typeof key === "string" ? key : key.code, modifiers), + name: WebInspector.KeyboardShortcut.shortcutToString(key, modifiers) + }; +} + +/** + * @param {string} shortcut + * @return {number} + */ +WebInspector.KeyboardShortcut.makeKeyFromBindingShortcut = function(shortcut) +{ + var parts = shortcut.split(/\+(?!$)/); + var modifiers = 0; + for (var i = 0; i < parts.length; ++i) { + if (typeof WebInspector.KeyboardShortcut.Modifiers[parts[i]] !== "undefined") { + modifiers |= WebInspector.KeyboardShortcut.Modifiers[parts[i]]; + continue; + } + console.assert(i === parts.length - 1, "Modifiers-only shortcuts are not allowed (encountered <" + shortcut + ">)"); + var key = WebInspector.KeyboardShortcut.Keys[parts[i]] || WebInspector.KeyboardShortcut.KeyBindings[parts[i]]; + if (key && key.shiftKey) + modifiers |= WebInspector.KeyboardShortcut.Modifiers.Shift; + return WebInspector.KeyboardShortcut.makeKey(key ? key.code : parts[i].toLowerCase(), modifiers) + } + console.assert(false); + return 0; +} + +/** + * @param {string|!WebInspector.KeyboardShortcut.Key} key + * @param {number=} modifiers + * @return {string} + */ +WebInspector.KeyboardShortcut.shortcutToString = function(key, modifiers) +{ + return WebInspector.KeyboardShortcut._modifiersToString(modifiers) + WebInspector.KeyboardShortcut._keyName(key); +} + +/** + * @param {string|!WebInspector.KeyboardShortcut.Key} key + * @return {string} + */ +WebInspector.KeyboardShortcut._keyName = function(key) +{ + if (typeof key === "string") + return key.toUpperCase(); + if (typeof key.name === "string") + return key.name; + return key.name[WebInspector.platform()] || key.name.other || ''; +} + +/** + * @param {number} keyCode + * @param {?number} modifiers + * @return {number} + */ +WebInspector.KeyboardShortcut._makeKeyFromCodeAndModifiers = function(keyCode, modifiers) +{ + return (keyCode & 255) | (modifiers << 8); +}; + +/** + * @param {number} key + * @return {!{keyCode: number, modifiers: number}} + */ +WebInspector.KeyboardShortcut.keyCodeAndModifiersFromKey = function(key) +{ + return { keyCode: key & 255, modifiers: key >> 8 }; +} + +/** + * @param {number|undefined} modifiers + * @return {string} + */ +WebInspector.KeyboardShortcut._modifiersToString = function(modifiers) +{ + const cmdKey = "\u2318"; + const optKey = "\u2325"; + const shiftKey = "\u21e7"; + const ctrlKey = "\u2303"; + + var isMac = WebInspector.isMac(); + var res = ""; + if (modifiers & WebInspector.KeyboardShortcut.Modifiers.Ctrl) + res += isMac ? ctrlKey : "Ctrl + "; + if (modifiers & WebInspector.KeyboardShortcut.Modifiers.Alt) + res += isMac ? optKey : "Alt + "; + if (modifiers & WebInspector.KeyboardShortcut.Modifiers.Shift) + res += isMac ? shiftKey : "Shift + "; + if (modifiers & WebInspector.KeyboardShortcut.Modifiers.Meta) + res += isMac ? cmdKey : "Win + "; + + return res; +}; + +WebInspector.KeyboardShortcut.SelectAll = WebInspector.KeyboardShortcut.makeKey("a", WebInspector.KeyboardShortcut.Modifiers.CtrlOrMeta); diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/PieChart.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/PieChart.js new file mode 100644 index 00000000000..8cacabdd920 --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/PieChart.js @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2013 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER 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. + */ + +/** + * @constructor + * @param {number=} totalValue + * @param {function(number):string=} formatter + */ +WebInspector.PieChart = function(totalValue, formatter) +{ + const shadowOffset = 0.04; + this.element = document.createElementWithClass("div", "pie-chart"); + var svg = this._createSVGChild(this.element, "svg"); + svg.setAttribute("width", "100%"); + svg.setAttribute("height", (100 * (1 + shadowOffset)) + "%"); + this._group = this._createSVGChild(svg, "g"); + var shadow = this._createSVGChild(this._group, "circle"); + shadow.setAttribute("r", 1); + shadow.setAttribute("cy", shadowOffset); + shadow.setAttribute("fill", "hsl(0,0%,70%)"); + var background = this._createSVGChild(this._group, "circle"); + background.setAttribute("r", 1); + background.setAttribute("fill", "hsl(0,0%,92%)"); + if (totalValue) { + var totalString = formatter ? formatter(totalValue) : totalValue; + this._totalElement = this.element.createChild("div", "pie-chart-foreground"); + this._totalElement.textContent = totalString; + this._totalValue = totalValue; + } + this._lastAngle = -Math.PI/2; + this.setSize(100); +} + +WebInspector.PieChart.prototype = { + /** + * @param {number} value + */ + setTotal: function(value) + { + this._totalValue = value; + }, + + /** + * @param {number} value + */ + setSize: function(value) + { + this._group.setAttribute("transform", "scale(" + (value / 2) + ") translate(1,1)"); + var size = value + "px"; + this.element.style.width = size; + this.element.style.height = size; + if (this._totalElement) + this._totalElement.style.lineHeight = size; + }, + + /** + * @param {number} value + * @param {string} color + */ + addSlice: function(value, color) + { + var sliceAngle = value / this._totalValue * 2 * Math.PI; + if (!isFinite(sliceAngle)) + return; + sliceAngle = Math.min(sliceAngle, 2 * Math.PI * 0.9999); + var path = this._createSVGChild(this._group, "path"); + var x1 = Math.cos(this._lastAngle); + var y1 = Math.sin(this._lastAngle); + this._lastAngle += sliceAngle; + var x2 = Math.cos(this._lastAngle); + var y2 = Math.sin(this._lastAngle); + var largeArc = sliceAngle > Math.PI ? 1 : 0; + path.setAttribute("d", "M0,0 L" + x1 + "," + y1 + " A1,1,0," + largeArc + ",1," + x2 + "," + y2 + " Z"); + path.setAttribute("fill", color); + }, + + /** + * @param {!Element} parent + * @param {string} childType + * @return {!Element} + */ + _createSVGChild: function(parent, childType) + { + var child = document.createElementNS("http://www.w3.org/2000/svg", childType); + parent.appendChild(child); + return child; + } +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/Popover.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/Popover.js new file mode 100644 index 00000000000..d183d2a1c4e --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/Popover.js @@ -0,0 +1,395 @@ +/* + * Copyright (C) 2009 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER 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. + */ + +/** + * @constructor + * @extends {WebInspector.View} + * @param {!WebInspector.PopoverHelper=} popoverHelper + */ +WebInspector.Popover = function(popoverHelper) +{ + WebInspector.View.call(this); + this.markAsRoot(); + this.element.className = "popover custom-popup-vertical-scroll custom-popup-horizontal-scroll"; // Override + + this._popupArrowElement = document.createElement("div"); + this._popupArrowElement.className = "arrow"; + this.element.appendChild(this._popupArrowElement); + + this._contentDiv = document.createElement("div"); + this._contentDiv.className = "content"; + this.element.appendChild(this._contentDiv); + + this._popoverHelper = popoverHelper; +} + +WebInspector.Popover.prototype = { + /** + * @param {!Element} element + * @param {!Element|!AnchorBox} anchor + * @param {?number=} preferredWidth + * @param {?number=} preferredHeight + * @param {?WebInspector.Popover.Orientation=} arrowDirection + */ + show: function(element, anchor, preferredWidth, preferredHeight, arrowDirection) + { + this._innerShow(null, element, anchor, preferredWidth, preferredHeight, arrowDirection); + }, + + /** + * @param {!WebInspector.View} view + * @param {!Element|!AnchorBox} anchor + * @param {?number=} preferredWidth + * @param {?number=} preferredHeight + */ + showView: function(view, anchor, preferredWidth, preferredHeight) + { + this._innerShow(view, view.element, anchor, preferredWidth, preferredHeight); + }, + + /** + * @param {?WebInspector.View} view + * @param {!Element} contentElement + * @param {!Element|!AnchorBox} anchor + * @param {?number=} preferredWidth + * @param {?number=} preferredHeight + * @param {?WebInspector.Popover.Orientation=} arrowDirection + */ + _innerShow: function(view, contentElement, anchor, preferredWidth, preferredHeight, arrowDirection) + { + if (this._disposed) + return; + this.contentElement = contentElement; + + // This should not happen, but we hide previous popup to be on the safe side. + if (WebInspector.Popover._popover) + WebInspector.Popover._popover.detach(); + WebInspector.Popover._popover = this; + + // Temporarily attach in order to measure preferred dimensions. + var preferredSize = view ? view.measurePreferredSize() : this.contentElement.measurePreferredSize(); + preferredWidth = preferredWidth || preferredSize.width; + preferredHeight = preferredHeight || preferredSize.height; + + WebInspector.View.prototype.show.call(this, document.body); + + if (view) + view.show(this._contentDiv); + else + this._contentDiv.appendChild(this.contentElement); + + this._positionElement(anchor, preferredWidth, preferredHeight, arrowDirection); + + if (this._popoverHelper) { + this._contentDiv.addEventListener("mousemove", this._popoverHelper._killHidePopoverTimer.bind(this._popoverHelper), true); + this.element.addEventListener("mouseout", this._popoverHelper._popoverMouseOut.bind(this._popoverHelper), true); + } + }, + + hide: function() + { + this.detach(); + delete WebInspector.Popover._popover; + }, + + get disposed() + { + return this._disposed; + }, + + dispose: function() + { + if (this.isShowing()) + this.hide(); + this._disposed = true; + }, + + setCanShrink: function(canShrink) + { + this._hasFixedHeight = !canShrink; + this._contentDiv.classList.add("fixed-height"); + }, + + /** + * @param {!Element|!AnchorBox} anchorElement + * @param {number} preferredWidth + * @param {number} preferredHeight + * @param {?WebInspector.Popover.Orientation=} arrowDirection + */ + _positionElement: function(anchorElement, preferredWidth, preferredHeight, arrowDirection) + { + const borderWidth = 25; + const scrollerWidth = this._hasFixedHeight ? 0 : 11; + const arrowHeight = 15; + const arrowOffset = 10; + const borderRadius = 10; + + // Skinny tooltips are not pretty, their arrow location is not nice. + preferredWidth = Math.max(preferredWidth, 50); + // Position relative to main DevTools element. + const container = WebInspector.Dialog.modalHostView().element; + const totalWidth = container.offsetWidth; + const totalHeight = container.offsetHeight; + + var anchorBox = anchorElement instanceof AnchorBox ? anchorElement : anchorElement.boxInWindow(window); + anchorBox = anchorBox.relativeToElement(container); + var newElementPosition = { x: 0, y: 0, width: preferredWidth + scrollerWidth, height: preferredHeight }; + + var verticalAlignment; + var roomAbove = anchorBox.y; + var roomBelow = totalHeight - anchorBox.y - anchorBox.height; + + if ((roomAbove > roomBelow) || (arrowDirection === WebInspector.Popover.Orientation.Bottom)) { + // Positioning above the anchor. + if ((anchorBox.y > newElementPosition.height + arrowHeight + borderRadius) || (arrowDirection === WebInspector.Popover.Orientation.Bottom)) + newElementPosition.y = anchorBox.y - newElementPosition.height - arrowHeight; + else { + newElementPosition.y = borderRadius; + newElementPosition.height = anchorBox.y - borderRadius * 2 - arrowHeight; + if (this._hasFixedHeight && newElementPosition.height < preferredHeight) { + newElementPosition.y = borderRadius; + newElementPosition.height = preferredHeight; + } + } + verticalAlignment = WebInspector.Popover.Orientation.Bottom; + } else { + // Positioning below the anchor. + newElementPosition.y = anchorBox.y + anchorBox.height + arrowHeight; + if ((newElementPosition.y + newElementPosition.height + borderRadius >= totalHeight) && (arrowDirection !== WebInspector.Popover.Orientation.Top)) { + newElementPosition.height = totalHeight - borderRadius - newElementPosition.y; + if (this._hasFixedHeight && newElementPosition.height < preferredHeight) { + newElementPosition.y = totalHeight - preferredHeight - borderRadius; + newElementPosition.height = preferredHeight; + } + } + // Align arrow. + verticalAlignment = WebInspector.Popover.Orientation.Top; + } + + var horizontalAlignment; + if (anchorBox.x + newElementPosition.width < totalWidth) { + newElementPosition.x = Math.max(borderRadius, anchorBox.x - borderRadius - arrowOffset); + horizontalAlignment = "left"; + } else if (newElementPosition.width + borderRadius * 2 < totalWidth) { + newElementPosition.x = totalWidth - newElementPosition.width - borderRadius; + horizontalAlignment = "right"; + // Position arrow accurately. + var arrowRightPosition = Math.max(0, totalWidth - anchorBox.x - anchorBox.width - borderRadius - arrowOffset); + arrowRightPosition += anchorBox.width / 2; + arrowRightPosition = Math.min(arrowRightPosition, newElementPosition.width - borderRadius - arrowOffset); + this._popupArrowElement.style.right = arrowRightPosition + "px"; + } else { + newElementPosition.x = borderRadius; + newElementPosition.width = totalWidth - borderRadius * 2; + newElementPosition.height += scrollerWidth; + horizontalAlignment = "left"; + if (verticalAlignment === WebInspector.Popover.Orientation.Bottom) + newElementPosition.y -= scrollerWidth; + // Position arrow accurately. + this._popupArrowElement.style.left = Math.max(0, anchorBox.x - borderRadius * 2 - arrowOffset) + "px"; + this._popupArrowElement.style.left += anchorBox.width / 2; + } + + this.element.className = "popover custom-popup-vertical-scroll custom-popup-horizontal-scroll " + verticalAlignment + "-" + horizontalAlignment + "-arrow"; + this.element.positionAt(newElementPosition.x - borderWidth, newElementPosition.y - borderWidth, container); + this.element.style.width = newElementPosition.width + borderWidth * 2 + "px"; + this.element.style.height = newElementPosition.height + borderWidth * 2 + "px"; + }, + + __proto__: WebInspector.View.prototype +} + +/** + * @constructor + * @param {!Element} panelElement + * @param {function(!Element, !Event):(!Element|!AnchorBox)|undefined} getAnchor + * @param {function(!Element, !WebInspector.Popover):undefined} showPopover + * @param {function()=} onHide + * @param {boolean=} disableOnClick + */ +WebInspector.PopoverHelper = function(panelElement, getAnchor, showPopover, onHide, disableOnClick) +{ + this._panelElement = panelElement; + this._getAnchor = getAnchor; + this._showPopover = showPopover; + this._onHide = onHide; + this._disableOnClick = !!disableOnClick; + panelElement.addEventListener("mousedown", this._mouseDown.bind(this), false); + panelElement.addEventListener("mousemove", this._mouseMove.bind(this), false); + panelElement.addEventListener("mouseout", this._mouseOut.bind(this), false); + this.setTimeout(1000); +} + +WebInspector.PopoverHelper.prototype = { + setTimeout: function(timeout) + { + this._timeout = timeout; + }, + + /** + * @param {!MouseEvent} event + * @return {boolean} + */ + _eventInHoverElement: function(event) + { + if (!this._hoverElement) + return false; + var box = this._hoverElement instanceof AnchorBox ? this._hoverElement : this._hoverElement.boxInWindow(); + return (box.x <= event.clientX && event.clientX <= box.x + box.width && + box.y <= event.clientY && event.clientY <= box.y + box.height); + }, + + _mouseDown: function(event) + { + if (this._disableOnClick || !this._eventInHoverElement(event)) + this.hidePopover(); + else { + this._killHidePopoverTimer(); + this._handleMouseAction(event, true); + } + }, + + _mouseMove: function(event) + { + // Pretend that nothing has happened. + if (this._eventInHoverElement(event)) + return; + + this._startHidePopoverTimer(); + this._handleMouseAction(event, false); + }, + + _popoverMouseOut: function(event) + { + if (!this.isPopoverVisible()) + return; + if (event.relatedTarget && !event.relatedTarget.isSelfOrDescendant(this._popover._contentDiv)) + this._startHidePopoverTimer(); + }, + + _mouseOut: function(event) + { + if (!this.isPopoverVisible()) + return; + if (!this._eventInHoverElement(event)) + this._startHidePopoverTimer(); + }, + + _startHidePopoverTimer: function() + { + // User has 500ms (this._timeout / 2) to reach the popup. + if (!this._popover || this._hidePopoverTimer) + return; + + /** + * @this {WebInspector.PopoverHelper} + */ + function doHide() + { + this._hidePopover(); + delete this._hidePopoverTimer; + } + this._hidePopoverTimer = setTimeout(doHide.bind(this), this._timeout / 2); + }, + + _handleMouseAction: function(event, isMouseDown) + { + this._resetHoverTimer(); + if (event.which && this._disableOnClick) + return; + this._hoverElement = this._getAnchor(event.target, event); + if (!this._hoverElement) + return; + const toolTipDelay = isMouseDown ? 0 : (this._popup ? this._timeout * 0.6 : this._timeout); + this._hoverTimer = setTimeout(this._mouseHover.bind(this, this._hoverElement), toolTipDelay); + }, + + _resetHoverTimer: function() + { + if (this._hoverTimer) { + clearTimeout(this._hoverTimer); + delete this._hoverTimer; + } + }, + + /** + * @return {boolean} + */ + isPopoverVisible: function() + { + return !!this._popover; + }, + + hidePopover: function() + { + this._resetHoverTimer(); + this._hidePopover(); + }, + + _hidePopover: function() + { + if (!this._popover) + return; + + if (this._onHide) + this._onHide(); + + this._popover.dispose(); + delete this._popover; + this._hoverElement = null; + }, + + _mouseHover: function(element) + { + delete this._hoverTimer; + + this._hidePopover(); + this._popover = new WebInspector.Popover(this); + this._showPopover(element, this._popover); + }, + + _killHidePopoverTimer: function() + { + if (this._hidePopoverTimer) { + clearTimeout(this._hidePopoverTimer); + delete this._hidePopoverTimer; + + // We know that we reached the popup, but we might have moved over other elements. + // Discard pending command. + this._resetHoverTimer(); + } + } +} + +/** @enum {string} */ +WebInspector.Popover.Orientation = { + Top: "top", + Bottom: "bottom" +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/ProgressIndicator.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/ProgressIndicator.js new file mode 100644 index 00000000000..8831be5b9dc --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/ProgressIndicator.js @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2012 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER 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. + */ + +/** + * @constructor + * @implements {WebInspector.Progress} + * @extends {WebInspector.Object} + */ +WebInspector.ProgressIndicator = function() +{ + this.element = document.createElement("div"); + this.element.className = "progress-bar-container"; + this._labelElement = this.element.createChild("span"); + this._progressElement = this.element.createChild("progress"); + this._stopButton = new WebInspector.StatusBarButton(WebInspector.UIString("Cancel"), "progress-bar-stop-button"); + this._stopButton.addEventListener("click", this.cancel, this); + this.element.appendChild(this._stopButton.element); + this._isCanceled = false; + this._worked = 0; +} + +WebInspector.ProgressIndicator.prototype = { + /** + * @param {!Element} parent + */ + show: function(parent) + { + parent.appendChild(this.element); + }, + + hide: function() + { + var parent = this.element.parentElement; + if (parent) + parent.removeChild(this.element); + }, + + done: function() + { + if (this._isDone) + return; + this._isDone = true; + this.hide(); + this.dispatchEventToListeners(WebInspector.Progress.Events.Done); + }, + + cancel: function() + { + this._isCanceled = true; + this.dispatchEventToListeners(WebInspector.Progress.Events.Canceled); + }, + + /** + * @return {boolean} + */ + isCanceled: function() + { + return this._isCanceled; + }, + + /** + * @param {string} title + */ + setTitle: function(title) + { + this._labelElement.textContent = title; + }, + + /** + * @param {number} totalWork + */ + setTotalWork: function(totalWork) + { + this._progressElement.max = totalWork; + }, + + /** + * @param {number} worked + * @param {string=} title + */ + setWorked: function(worked, title) + { + this._worked = worked; + this._progressElement.value = worked; + if (title) + this.setTitle(title); + }, + + /** + * @param {number=} worked + */ + worked: function(worked) + { + this.setWorked(this._worked + (worked || 1)); + }, + + __proto__: WebInspector.Object.prototype +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/ResizerWidget.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/ResizerWidget.js new file mode 100644 index 00000000000..6e61114093c --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/ResizerWidget.js @@ -0,0 +1,156 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @constructor + * @extends {WebInspector.Object} + */ +WebInspector.ResizerWidget = function() +{ + WebInspector.Object.call(this); + + this._isEnabled = true; + this._isVertical = true; + this._elements = []; + this._installDragOnMouseDownBound = this._installDragOnMouseDown.bind(this); +}; + +WebInspector.ResizerWidget.Events = { + ResizeStart: "ResizeStart", + ResizeUpdate: "ResizeUpdate", + ResizeEnd: "ResizeEnd" +}; + +WebInspector.ResizerWidget.prototype = { + /** + * @return {boolean} + */ + isEnabled: function() + { + return this._isEnabled; + }, + + /** + * @param {boolean} enabled + */ + setEnabled: function(enabled) + { + this._isEnabled = enabled; + this._updateElementsClass(); + }, + + /** + * @return {boolean} + */ + isVertical: function() + { + return this._isVertical; + }, + + /** + * Vertical widget resizes height (along y-axis). + * @param {boolean} vertical + */ + setVertical: function(vertical) + { + this._isVertical = vertical; + this._updateElementsClass(); + }, + + /** + * @return {!Array.<!Element>} + */ + elements: function() + { + return this._elements.slice(); + }, + + /** + * @param {!Element} element + */ + addElement: function(element) + { + if (this._elements.indexOf(element) !== -1) + return; + + this._elements.push(element); + element.addEventListener("mousedown", this._installDragOnMouseDownBound, false); + element.classList.toggle("ns-resizer-widget", this._isVertical && this._isEnabled); + element.classList.toggle("ew-resizer-widget", !this._isVertical && this._isEnabled); + }, + + /** + * @param {!Element} element + */ + removeElement: function(element) + { + if (this._elements.indexOf(element) === -1) + return; + + this._elements.remove(element); + element.removeEventListener("mousedown", this._installDragOnMouseDownBound, false); + element.classList.remove("ns-resizer-widget"); + element.classList.remove("ew-resizer-widget"); + }, + + _updateElementsClass: function() + { + for (var i = 0; i < this._elements.length; ++i) { + this._elements[i].classList.toggle("ns-resizer-widget", this._isVertical && this._isEnabled); + this._elements[i].classList.toggle("ew-resizer-widget", !this._isVertical && this._isEnabled); + } + }, + + /** + * @param {?Event} event + */ + _installDragOnMouseDown: function(event) + { + // Only handle drags of the nodes specified. + if (this._elements.indexOf(event.target) === -1) + return false; + WebInspector.elementDragStart(this._dragStart.bind(this), this._drag.bind(this), this._dragEnd.bind(this), this._isVertical ? "ns-resize" : "ew-resize", event); + }, + + /** + * @param {!MouseEvent} event + * @return {boolean} + */ + _dragStart: function(event) + { + if (!this._isEnabled) + return false; + this._startPosition = this._isVertical ? event.pageY : event.pageX; + this.dispatchEventToListeners(WebInspector.ResizerWidget.Events.ResizeStart, { startPosition: this._startPosition, currentPosition: this._startPosition }); + return true; + }, + + /** + * @param {!MouseEvent} event + * @return {boolean} + */ + _drag: function(event) + { + if (!this._isEnabled) { + this._dragEnd(event); + return true; // Cancel drag. + } + + var position = this._isVertical ? event.pageY : event.pageX; + this.dispatchEventToListeners(WebInspector.ResizerWidget.Events.ResizeUpdate, { startPosition: this._startPosition, currentPosition: position, shiftKey: event.shiftKey }); + event.preventDefault(); + return false; // Continue drag. + }, + + /** + * @param {!MouseEvent} event + */ + _dragEnd: function(event) + { + this.dispatchEventToListeners(WebInspector.ResizerWidget.Events.ResizeEnd); + delete this._startPosition; + }, + + __proto__: WebInspector.Object.prototype +}; diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/SettingsUI.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/SettingsUI.js new file mode 100644 index 00000000000..5c5b3451acb --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/SettingsUI.js @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2014 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER 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.SettingsUI = {} + +/** + * @param {string} name + * @param {!WebInspector.Setting} setting + * @param {boolean=} omitParagraphElement + * @param {!Element=} inputElement + * @param {string=} tooltip + * @return {!Element} + */ +WebInspector.SettingsUI.createSettingCheckbox = function(name, setting, omitParagraphElement, inputElement, tooltip) +{ + var input = inputElement || document.createElement("input"); + input.type = "checkbox"; + input.name = name; + WebInspector.SettingsUI.bindCheckbox(input, setting); + + var label = document.createElement("label"); + label.appendChild(input); + label.createTextChild(name); + if (tooltip) + label.title = tooltip; + + if (omitParagraphElement) + return label; + + var p = document.createElement("p"); + p.appendChild(label); + return p; +} + +/** + * @param {!Element} input + * @param {!WebInspector.Setting} setting + */ +WebInspector.SettingsUI.bindCheckbox = function(input, setting) +{ + function settingChanged() + { + if (input.checked !== setting.get()) + input.checked = setting.get(); + } + setting.addChangeListener(settingChanged); + settingChanged(); + + function inputChanged() + { + if (setting.get() !== input.checked) + setting.set(input.checked); + } + input.addEventListener("change", inputChanged, false); +} + +/** + * @param {string} label + * @param {!WebInspector.Setting} setting + * @param {boolean} numeric + * @param {number=} maxLength + * @param {string=} width + * @param {function(string):?string=} validatorCallback + * @param {boolean=} instant + * @param {boolean=} clearForZero + * @param {string=} placeholder + * @return {!Element} + */ +WebInspector.SettingsUI.createSettingInputField = function(label, setting, numeric, maxLength, width, validatorCallback, instant, clearForZero, placeholder) +{ + var p = document.createElement("p"); + var labelElement = p.createChild("label"); + labelElement.textContent = label; + var inputElement = p.createChild("input"); + inputElement.type = "text"; + if (numeric) + inputElement.className = "numeric"; + if (maxLength) + inputElement.maxLength = maxLength; + if (width) + inputElement.style.width = width; + inputElement.placeholder = placeholder || ""; + + if (validatorCallback || instant) { + inputElement.addEventListener("change", onInput, false); + inputElement.addEventListener("input", onInput, false); + } + inputElement.addEventListener("keydown", onKeyDown, false); + + var errorMessageLabel; + if (validatorCallback) { + errorMessageLabel = p.createChild("div"); + errorMessageLabel.classList.add("field-error-message"); + validate(); + } + + function onInput() + { + if (validatorCallback) + validate(); + if (instant) + apply(); + } + + function onKeyDown(event) + { + if (isEnterKey(event)) + apply(); + } + + function validate() + { + var error = validatorCallback(inputElement.value); + if (!error) + error = ""; + inputElement.classList.toggle("error-input", !!error); + errorMessageLabel.textContent = error; + } + + if (!instant) + inputElement.addEventListener("blur", apply, false); + + function apply() + { + if (validatorCallback && validatorCallback(inputElement.value)) + return; + setting.removeChangeListener(onSettingChange); + setting.set(numeric ? Number(inputElement.value) : inputElement.value); + setting.addChangeListener(onSettingChange); + } + + setting.addChangeListener(onSettingChange); + + function onSettingChange() + { + var value = setting.get(); + if (clearForZero && !value) + value = ""; + inputElement.value = value; + } + onSettingChange(); + + return p; +} + +/** + * @param {string} name + * @param {!Element} element + * @return {!Element} + */ +WebInspector.SettingsUI.createCustomSetting = function(name, element) +{ + var p = document.createElement("p"); + var fieldsetElement = document.createElement("fieldset"); + fieldsetElement.createChild("label").textContent = name; + fieldsetElement.appendChild(element); + p.appendChild(fieldsetElement); + return p; +} + +/** + * @param {!WebInspector.Setting} setting + * @return {!Element} + */ +WebInspector.SettingsUI.createSettingFieldset = function(setting) +{ + var fieldset = document.createElement("fieldset"); + fieldset.disabled = !setting.get(); + setting.addChangeListener(settingChanged); + return fieldset; + + function settingChanged() + { + fieldset.disabled = !setting.get(); + } +} + +/** + * @param {string} text + * @return {?string} + */ +WebInspector.SettingsUI.regexValidator = function(text) +{ + var regex; + try { + regex = new RegExp(text); + } catch (e) { + } + return regex ? null : WebInspector.UIString("Invalid pattern"); +} + +/** + * Creates an input element under the parentElement with the given id and defaultText. + * @param {!Element} parentElement + * @param {string} id + * @param {string} defaultText + * @param {function(*)} eventListener + * @param {boolean=} numeric + * @param {string=} size + * @return {!Element} element + */ +WebInspector.SettingsUI.createInput = function(parentElement, id, defaultText, eventListener, numeric, size) +{ + var element = parentElement.createChild("input"); + element.id = id; + element.type = "text"; + element.maxLength = 12; + element.style.width = size || "80px"; + element.value = defaultText; + element.align = "right"; + if (numeric) + element.className = "numeric"; + element.addEventListener("input", eventListener, false); + element.addEventListener("keydown", keyDownListener, false); + function keyDownListener(event) + { + if (isEnterKey(event)) + eventListener(event); + } + return element; +} + +/** + * @constructor + */ +WebInspector.UISettingDelegate = function() +{ +} + +WebInspector.UISettingDelegate.prototype = { + /** + * @return {?Element} + */ + settingElement: function() + { + return null; + } +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/ShortcutRegistry.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/ShortcutRegistry.js new file mode 100644 index 00000000000..abe497398dc --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/ShortcutRegistry.js @@ -0,0 +1,214 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @constructor + * @param {!WebInspector.ActionRegistry} actionRegistry + */ +WebInspector.ShortcutRegistry = function(actionRegistry) +{ + this._actionRegistry = actionRegistry; + /** @type {!StringMultimap.<string>} */ + this._defaultKeyToActions = new StringMultimap(); + this._registerBindings(); +} + +WebInspector.ShortcutRegistry.prototype = { + /** + * @param {number} key + * @return {!Array.<string>} + */ + applicableActions: function(key) + { + return this._actionRegistry.applicableActions(this._actionIdsForKey(key), WebInspector.context); + }, + + /** + * @param {number} key + * @return {!Array.<string>} + */ + _actionIdsForKey: function(key) + { + var result = new StringSet(); + var defaults = this._defaultActionsForKey(key); + defaults.values().forEach(function(actionId) { + result.add(actionId); + }, this); + + return result.values(); + }, + + /** + * @param {number} key + * @return {!Set.<string>} + */ + _defaultActionsForKey: function(key) + { + return this._defaultKeyToActions.get(String(key)); + }, + + /** + * @param {!Array.<string>} actionIds + * @return {!Array.<number>} + */ + keysForActions: function(actionIds) + { + var actionIdSet = actionIds.keySet(); + var result = []; + this._defaultKeyToActions.keys().forEach(function(key) { + var actionIdsForKey = this._defaultKeyToActions.get(key); + actionIdsForKey.values().some(function(actionId) { + if (actionIdSet.hasOwnProperty(actionId)) { + result.push(key); + return true; + } + }); + }, this); + return result; + }, + + /** + * @param {!KeyboardEvent} event + */ + handleShortcut: function(event) + { + this.handleKey(WebInspector.KeyboardShortcut.makeKeyFromEvent(event), event.keyIdentifier, event); + }, + + /** + * @param {number} key + * @param {string} keyIdentifier + * @param {!KeyboardEvent=} event + */ + handleKey: function(key, keyIdentifier, event) + { + var actionIds = this.applicableActions(key); + + for (var i = 0; i < actionIds.length; ++i) { + var keyModifiers = key >> 8; + if (!isPossiblyInputKey()) { + if (handler.call(this, actionIds[i])) + break; + } else { + this._pendingActionTimer = setTimeout(handler.bind(this, actionIds[i]), 0); + break; + } + } + + /** + * @return {boolean} + */ + function isPossiblyInputKey() + { + if (!event || !WebInspector.isBeingEdited(/** @type {!Node} */ (event.target)) || /^F\d+|Control|Shift|Alt|Meta|Win|U\+001B$/.test(keyIdentifier)) + return false; + + if (!keyModifiers) + return true; + + var modifiers = WebInspector.KeyboardShortcut.Modifiers; + if ((keyModifiers & (modifiers.Ctrl | modifiers.Alt)) === (modifiers.Ctrl | modifiers.Alt)) + return WebInspector.isWin(); + + return !hasModifier(modifiers.Ctrl) && !hasModifier(modifiers.Alt) && !hasModifier(modifiers.Meta); + } + + /** + * @param {number} mod + * @return {boolean} + */ + function hasModifier(mod) + { + return !!(keyModifiers & mod); + } + + /** + * @param {string} actionId + * @return {boolean} + * @this {WebInspector.ShortcutRegistry} + */ + function handler(actionId) + { + var result = this._actionRegistry.execute(actionId); + if (result && event) + event.consume(true); + delete this._pendingActionTimer; + return result; + } + }, + + /** + * @param {string} actionId + * @param {string} shortcut + */ + registerShortcut: function(actionId, shortcut) + { + var key = WebInspector.KeyboardShortcut.makeKeyFromBindingShortcut(shortcut); + if (!key) + return; + this._defaultKeyToActions.put(String(key), actionId); + }, + + /** + * @param {?Event} event + */ + _onInput: function(event) + { + if (this._pendingActionTimer) { + clearTimeout(this._pendingActionTimer); + delete this._pendingActionTimer; + } + }, + + _registerBindings: function() + { + document.addEventListener("input", this._onInput.bind(this), true); + var extensions = WebInspector.moduleManager.extensions(WebInspector.ActionDelegate); + extensions.forEach(registerExtension, this); + + /** + * @param {!WebInspector.ModuleManager.Extension} extension + * @this {WebInspector.ShortcutRegistry} + */ + function registerExtension(extension) + { + var descriptor = extension.descriptor(); + var bindings = descriptor["bindings"]; + for (var i = 0; bindings && i < bindings.length; ++i) { + if (!platformMatches(bindings[i].platform)) + continue; + var shortcuts = bindings[i]["shortcut"].split(/\s+/); + shortcuts.forEach(this.registerShortcut.bind(this, descriptor["actionId"])); + } + } + + /** + * @param {string=} platformsString + * @return {boolean} + */ + function platformMatches(platformsString) + { + if (!platformsString) + return true; + var platforms = platformsString.split(","); + var isMatch = false; + var currentPlatform = WebInspector.platform(); + for (var i = 0; !isMatch && i < platforms.length; ++i) + isMatch = platforms[i] === currentPlatform; + return isMatch; + } + } +} + +/** + * @constructor + */ +WebInspector.ShortcutRegistry.ForwardedShortcut = function() +{ +} + +WebInspector.ShortcutRegistry.ForwardedShortcut.instance = new WebInspector.ShortcutRegistry.ForwardedShortcut(); + +/** @type {!WebInspector.ShortcutRegistry} */ +WebInspector.shortcutRegistry; diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/ShowMoreDataGridNode.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/ShowMoreDataGridNode.js new file mode 100644 index 00000000000..e8420fce60f --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/ShowMoreDataGridNode.js @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2011 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER 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. + */ + +/** + * @constructor + * @extends {WebInspector.DataGridNode} + * @param {function(number, number)} callback + * @param {number} startPosition + * @param {number} endPosition + * @param {number} chunkSize + */ +WebInspector.ShowMoreDataGridNode = function(callback, startPosition, endPosition, chunkSize) +{ + WebInspector.DataGridNode.call(this, {summaryRow:true}, false); + this._callback = callback; + this._startPosition = startPosition; + this._endPosition = endPosition; + this._chunkSize = chunkSize; + + this.showNext = document.createElement("button"); + this.showNext.setAttribute("type", "button"); + this.showNext.addEventListener("click", this._showNextChunk.bind(this), false); + this.showNext.textContent = WebInspector.UIString("Show %d before", this._chunkSize); + + this.showAll = document.createElement("button"); + this.showAll.setAttribute("type", "button"); + this.showAll.addEventListener("click", this._showAll.bind(this), false); + + this.showLast = document.createElement("button"); + this.showLast.setAttribute("type", "button"); + this.showLast.addEventListener("click", this._showLastChunk.bind(this), false); + this.showLast.textContent = WebInspector.UIString("Show %d after", this._chunkSize); + + this._updateLabels(); + this.selectable = false; +} + +WebInspector.ShowMoreDataGridNode.prototype = { + _showNextChunk: function() + { + this._callback(this._startPosition, this._startPosition + this._chunkSize); + }, + + _showAll: function() + { + this._callback(this._startPosition, this._endPosition); + }, + + _showLastChunk: function() + { + this._callback(this._endPosition - this._chunkSize, this._endPosition); + }, + + _updateLabels: function() + { + var totalSize = this._endPosition - this._startPosition; + if (totalSize > this._chunkSize) { + this.showNext.classList.remove("hidden"); + this.showLast.classList.remove("hidden"); + } else { + this.showNext.classList.add("hidden"); + this.showLast.classList.add("hidden"); + } + this.showAll.textContent = WebInspector.UIString("Show all %d", totalSize); + }, + + /** override */ + createCells: function() + { + this._hasCells = false; + WebInspector.DataGridNode.prototype.createCells.call(this); + }, + + /** + * @override + * @param {string} columnIdentifier + * @return {!Element} + */ + createCell: function(columnIdentifier) + { + var cell = this.createTD(columnIdentifier); + if (!this._hasCells) { + this._hasCells = true; + if (this.depth) + cell.style.setProperty("padding-left", (this.depth * this.dataGrid.indentWidth) + "px"); + cell.appendChild(this.showNext); + cell.appendChild(this.showAll); + cell.appendChild(this.showLast); + } + return cell; + }, + + /** + * @param {number} from + */ + setStartPosition: function(from) + { + this._startPosition = from; + this._updateLabels(); + }, + + /** + * @param {number} to + */ + setEndPosition: function(to) + { + this._endPosition = to; + this._updateLabels(); + }, + + /** + * @override + * @return {number} + */ + nodeSelfHeight: function() + { + return 32; + }, + + dispose: function() + { + }, + + __proto__: WebInspector.DataGridNode.prototype +} + diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/SidebarPane.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/SidebarPane.js new file mode 100644 index 00000000000..85ac23037ad --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/SidebarPane.js @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.View} + */ +WebInspector.SidebarPane = function(title) +{ + WebInspector.View.call(this); + this.setMinimumSize(25, 0); + this.element.className = "sidebar-pane"; // Override + + this.titleElement = document.createElement("div"); + this.titleElement.className = "sidebar-pane-toolbar"; + + this.bodyElement = this.element.createChild("div", "body"); + + this._title = title; + + this._expandCallback = null; +} + +WebInspector.SidebarPane.EventTypes = { + wasShown: "wasShown" +} + +WebInspector.SidebarPane.prototype = { + /** + * @return {string} + */ + title: function() + { + return this._title; + }, + + /** + * @param {function()} callback + */ + prepareContent: function(callback) + { + if (callback) + callback(); + }, + + expand: function() + { + this.prepareContent(this.onContentReady.bind(this)); + }, + + onContentReady: function() + { + if (this._expandCallback) + this._expandCallback(); + else + this._expandPending = true; + }, + + /** + * @param {function()} callback + */ + setExpandCallback: function(callback) + { + this._expandCallback = callback; + if (this._expandPending) { + delete this._expandPending; + this._expandCallback(); + } + }, + + wasShown: function() + { + WebInspector.View.prototype.wasShown.call(this); + this.dispatchEventToListeners(WebInspector.SidebarPane.EventTypes.wasShown); + }, + + __proto__: WebInspector.View.prototype +} + +/** + * @constructor + * @param {!Element} container + * @param {!WebInspector.SidebarPane} pane + */ +WebInspector.SidebarPaneTitle = function(container, pane) +{ + this._pane = pane; + + this.element = container.createChild("div", "sidebar-pane-title"); + this.element.textContent = pane.title(); + this.element.tabIndex = 0; + this.element.addEventListener("click", this._toggleExpanded.bind(this), false); + this.element.addEventListener("keydown", this._onTitleKeyDown.bind(this), false); + this.element.appendChild(this._pane.titleElement); + + this._pane.setExpandCallback(this._expand.bind(this)); +} + +WebInspector.SidebarPaneTitle.prototype = { + + _expand: function() + { + this.element.classList.add("expanded"); + this._pane.show(this.element.parentElement, /** @type {?Element} */ (this.element.nextSibling)); + }, + + _collapse: function() + { + this.element.classList.remove("expanded"); + if (this._pane.element.parentNode == this.element.parentNode) + this._pane.detach(); + }, + + _toggleExpanded: function() + { + if (this.element.classList.contains("expanded")) + this._collapse(); + else + this._pane.expand(); + }, + + /** + * @param {?Event} event + */ + _onTitleKeyDown: function(event) + { + if (isEnterKey(event) || event.keyCode === WebInspector.KeyboardShortcut.Keys.Space.code) + this._toggleExpanded(); + } +} + +/** + * @constructor + * @extends {WebInspector.View} + */ +WebInspector.SidebarPaneStack = function() +{ + WebInspector.View.call(this); + this.setMinimumSize(25, 0); + this.element.className = "sidebar-pane-stack"; // Override + this.registerRequiredCSS("sidebarPane.css"); +} + +WebInspector.SidebarPaneStack.prototype = { + /** + * @param {!WebInspector.SidebarPane} pane + */ + addPane: function(pane) + { + new WebInspector.SidebarPaneTitle(this.element, pane); + }, + + __proto__: WebInspector.View.prototype +} + +/** + * @constructor + * @extends {WebInspector.TabbedPane} + */ +WebInspector.SidebarTabbedPane = function() +{ + WebInspector.TabbedPane.call(this); + this.setRetainTabOrder(true); + this.element.classList.add("sidebar-tabbed-pane"); + this.registerRequiredCSS("sidebarPane.css"); +} + +WebInspector.SidebarTabbedPane.prototype = { + /** + * @param {!WebInspector.SidebarPane} pane + */ + addPane: function(pane) + { + var title = pane.title(); + this.appendTab(title, title, pane); + pane.element.appendChild(pane.titleElement); + pane.setExpandCallback(this.selectTab.bind(this, title)); + + }, + + __proto__: WebInspector.TabbedPane.prototype +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/SidebarTreeElement.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/SidebarTreeElement.js new file mode 100644 index 00000000000..77d97ae09a1 --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/SidebarTreeElement.js @@ -0,0 +1,195 @@ +/* + * 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. + */ + +/** + * @constructor + * @extends {TreeElement} + */ +WebInspector.SidebarSectionTreeElement = function(title, representedObject, hasChildren) +{ + TreeElement.call(this, title.escapeHTML(), representedObject || {}, hasChildren); + this.expand(); +} + +WebInspector.SidebarSectionTreeElement.prototype = { + selectable: false, + + collapse: function() + { + // Should not collapse as it is not selectable. + }, + + get smallChildren() + { + return this._smallChildren; + }, + + set smallChildren(x) + { + if (this._smallChildren === x) + return; + + this._smallChildren = x; + + this._childrenListNode.classList.toggle("small", this._smallChildren); + }, + + onattach: function() + { + this._listItemNode.classList.add("sidebar-tree-section"); + }, + + onreveal: function() + { + if (this.listItemElement) + this.listItemElement.scrollIntoViewIfNeeded(false); + }, + + __proto__: TreeElement.prototype +} + +/** + * @constructor + * @extends {TreeElement} + * @param {string} className + * @param {string} title + * @param {string=} subtitle + * @param {?Object=} representedObject + * @param {boolean=} hasChildren + */ +WebInspector.SidebarTreeElement = function(className, title, subtitle, representedObject, hasChildren) +{ + TreeElement.call(this, "", representedObject, hasChildren); + + if (hasChildren) { + this.disclosureButton = document.createElement("button"); + this.disclosureButton.className = "disclosure-button"; + } + + this.iconElement = document.createElementWithClass("div", "icon"); + this.statusElement = document.createElementWithClass("div", "status"); + this.titlesElement = document.createElementWithClass("div", "titles"); + + this.titleContainer = this.titlesElement.createChild("span", "title-container"); + this.titleElement = this.titleContainer.createChild("span", "title"); + + this.subtitleElement = this.titlesElement.createChild("span", "subtitle"); + + this.className = className; + this.mainTitle = title; + this.subtitle = subtitle; +} + +WebInspector.SidebarTreeElement.prototype = { + get small() + { + return this._small; + }, + + set small(x) + { + this._small = x; + if (this._listItemNode) + this._listItemNode.classList.toggle("small", this._small); + }, + + get mainTitle() + { + return this._mainTitle; + }, + + set mainTitle(x) + { + this._mainTitle = x; + this.refreshTitles(); + }, + + get subtitle() + { + return this._subtitle; + }, + + set subtitle(x) + { + this._subtitle = x; + this.refreshTitles(); + }, + + set wait(x) + { + this._listItemNode.classList.toggle("wait", x); + }, + + refreshTitles: function() + { + var mainTitle = this.mainTitle; + if (this.titleElement.textContent !== mainTitle) + this.titleElement.textContent = mainTitle; + + var subtitle = this.subtitle; + if (subtitle) { + if (this.subtitleElement.textContent !== subtitle) + this.subtitleElement.textContent = subtitle; + this.titlesElement.classList.remove("no-subtitle"); + } else { + this.subtitleElement.textContent = ""; + this.titlesElement.classList.add("no-subtitle"); + } + }, + + /** + * @return {boolean} + */ + isEventWithinDisclosureTriangle: function(event) + { + return event.target === this.disclosureButton; + }, + + onattach: function() + { + this._listItemNode.classList.add("sidebar-tree-item"); + + if (this.className) + this._listItemNode.classList.add(this.className); + + if (this.small) + this._listItemNode.classList.add("small"); + + if (this.hasChildren && this.disclosureButton) + this._listItemNode.appendChild(this.disclosureButton); + + this._listItemNode.appendChild(this.iconElement); + this._listItemNode.appendChild(this.statusElement); + this._listItemNode.appendChild(this.titlesElement); + }, + + onreveal: function() + { + if (this._listItemNode) + this._listItemNode.scrollIntoViewIfNeeded(false); + }, + + __proto__: TreeElement.prototype +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/SoftContextMenu.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/SoftContextMenu.js new file mode 100644 index 00000000000..a4626e36b1b --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/SoftContextMenu.js @@ -0,0 +1,386 @@ +/* + * Copyright (C) 2011 Google 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. + */ + +/** + * @constructor + * @param {!Array.<!WebInspector.ContextMenuItem>} items + * @param {!WebInspector.SoftContextMenu=} parentMenu + */ +WebInspector.SoftContextMenu = function(items, parentMenu) +{ + this._items = items; + this._parentMenu = parentMenu; +} + +WebInspector.SoftContextMenu.prototype = { + /** + * @param {!Event} event + */ + show: function(event) + { + this._x = event.x; + this._y = event.y; + this._time = new Date().getTime(); + + // Absolutely position menu for iframes. + var absoluteX = event.pageX; + var absoluteY = event.pageY; + var targetElement = event.target; + while (targetElement && window !== targetElement.ownerDocument.defaultView) { + var frameElement = targetElement.ownerDocument.defaultView.frameElement; + absoluteY += frameElement.totalOffsetTop(); + absoluteX += frameElement.totalOffsetLeft(); + targetElement = frameElement; + } + + // Create context menu. + var targetRect; + this._contextMenuElement = document.createElement("div"); + this._contextMenuElement.className = "soft-context-menu"; + this._contextMenuElement.tabIndex = 0; + this._contextMenuElement.style.top = absoluteY + "px"; + this._contextMenuElement.style.left = absoluteX + "px"; + + this._contextMenuElement.addEventListener("mouseup", consumeEvent, false); + this._contextMenuElement.addEventListener("keydown", this._menuKeyDown.bind(this), false); + + for (var i = 0; i < this._items.length; ++i) + this._contextMenuElement.appendChild(this._createMenuItem(this._items[i])); + + // Install glass pane capturing events. + if (!this._parentMenu) { + this._glassPaneElement = document.createElement("div"); + this._glassPaneElement.className = "soft-context-menu-glass-pane"; + this._glassPaneElement.tabIndex = 0; + this._glassPaneElement.addEventListener("mouseup", this._glassPaneMouseUp.bind(this), false); + this._glassPaneElement.appendChild(this._contextMenuElement); + document.body.appendChild(this._glassPaneElement); + this._focus(); + } else + this._parentMenu._parentGlassPaneElement().appendChild(this._contextMenuElement); + + // Re-position menu in case it does not fit. + if (document.body.offsetWidth < this._contextMenuElement.offsetLeft + this._contextMenuElement.offsetWidth) + this._contextMenuElement.style.left = (absoluteX - this._contextMenuElement.offsetWidth) + "px"; + if (document.body.offsetHeight < this._contextMenuElement.offsetTop + this._contextMenuElement.offsetHeight) + this._contextMenuElement.style.top = (document.body.offsetHeight - this._contextMenuElement.offsetHeight) + "px"; + + event.consume(true); + }, + + _parentGlassPaneElement: function() + { + if (this._glassPaneElement) + return this._glassPaneElement; + if (this._parentMenu) + return this._parentMenu._parentGlassPaneElement(); + return null; + }, + + _createMenuItem: function(item) + { + if (item.type === "separator") + return this._createSeparator(); + + if (item.type === "subMenu") + return this._createSubMenu(item); + + var menuItemElement = document.createElement("div"); + menuItemElement.className = "soft-context-menu-item"; + + var checkMarkElement = document.createElement("span"); + checkMarkElement.textContent = "\u2713 "; // Checkmark Unicode symbol + checkMarkElement.className = "soft-context-menu-item-checkmark"; + if (!item.checked) + checkMarkElement.style.opacity = "0"; + + menuItemElement.appendChild(checkMarkElement); + menuItemElement.appendChild(document.createTextNode(item.label)); + + menuItemElement.addEventListener("mousedown", this._menuItemMouseDown.bind(this), false); + menuItemElement.addEventListener("mouseup", this._menuItemMouseUp.bind(this), false); + + // Manually manage hover highlight since :hover does not work in case of click-and-hold menu invocation. + menuItemElement.addEventListener("mouseover", this._menuItemMouseOver.bind(this), false); + menuItemElement.addEventListener("mouseout", this._menuItemMouseOut.bind(this), false); + + menuItemElement._actionId = item.id; + return menuItemElement; + }, + + _createSubMenu: function(item) + { + var menuItemElement = document.createElement("div"); + menuItemElement.className = "soft-context-menu-item"; + menuItemElement._subItems = item.subItems; + + // Occupy the same space on the left in all items. + var checkMarkElement = document.createElement("span"); + checkMarkElement.textContent = "\u2713 "; // Checkmark Unicode symbol + checkMarkElement.className = "soft-context-menu-item-checkmark"; + checkMarkElement.style.opacity = "0"; + menuItemElement.appendChild(checkMarkElement); + + var subMenuArrowElement = document.createElement("span"); + subMenuArrowElement.textContent = "\u25B6"; // BLACK RIGHT-POINTING TRIANGLE + subMenuArrowElement.className = "soft-context-menu-item-submenu-arrow"; + + menuItemElement.appendChild(document.createTextNode(item.label)); + menuItemElement.appendChild(subMenuArrowElement); + + menuItemElement.addEventListener("mousedown", this._menuItemMouseDown.bind(this), false); + menuItemElement.addEventListener("mouseup", this._menuItemMouseUp.bind(this), false); + + // Manually manage hover highlight since :hover does not work in case of click-and-hold menu invocation. + menuItemElement.addEventListener("mouseover", this._menuItemMouseOver.bind(this), false); + menuItemElement.addEventListener("mouseout", this._menuItemMouseOut.bind(this), false); + + return menuItemElement; + }, + + _createSeparator: function() + { + var separatorElement = document.createElement("div"); + separatorElement.className = "soft-context-menu-separator"; + separatorElement._isSeparator = true; + separatorElement.addEventListener("mouseover", this._hideSubMenu.bind(this), false); + separatorElement.createChild("div", "separator-line"); + return separatorElement; + }, + + _menuItemMouseDown: function(event) + { + // Do not let separator's mouse down hit menu's handler - we need to receive mouse up! + event.consume(true); + }, + + _menuItemMouseUp: function(event) + { + this._triggerAction(event.target, event); + event.consume(); + }, + + _focus: function() + { + this._contextMenuElement.focus(); + }, + + _triggerAction: function(menuItemElement, event) + { + if (!menuItemElement._subItems) { + this._discardMenu(true, event); + if (typeof menuItemElement._actionId !== "undefined") { + WebInspector.contextMenuItemSelected(menuItemElement._actionId); + delete menuItemElement._actionId; + } + return; + } + + this._showSubMenu(menuItemElement, event); + event.consume(); + }, + + _showSubMenu: function(menuItemElement, event) + { + if (menuItemElement._subMenuTimer) { + clearTimeout(menuItemElement._subMenuTimer); + delete menuItemElement._subMenuTimer; + } + if (this._subMenu) + return; + + this._subMenu = new WebInspector.SoftContextMenu(menuItemElement._subItems, this); + this._subMenu.show(this._buildMouseEventForSubMenu(menuItemElement)); + }, + + _buildMouseEventForSubMenu: function(subMenuItemElement) + { + var subMenuOffset = { x: subMenuItemElement.offsetWidth - 3, y: subMenuItemElement.offsetTop - 1 }; + var targetX = this._x + subMenuOffset.x; + var targetY = this._y + subMenuOffset.y; + var targetPageX = parseInt(this._contextMenuElement.style.left, 10) + subMenuOffset.x; + var targetPageY = parseInt(this._contextMenuElement.style.top, 10) + subMenuOffset.y; + return { x: targetX, y: targetY, pageX: targetPageX, pageY: targetPageY, consume: function() {} }; + }, + + _hideSubMenu: function() + { + if (!this._subMenu) + return; + this._subMenu._discardSubMenus(); + this._focus(); + }, + + _menuItemMouseOver: function(event) + { + this._highlightMenuItem(event.target); + }, + + _menuItemMouseOut: function(event) + { + if (!this._subMenu || !event.relatedTarget) { + this._highlightMenuItem(null); + return; + } + + var relatedTarget = event.relatedTarget; + if (this._contextMenuElement.isSelfOrAncestor(relatedTarget) || relatedTarget.classList.contains("soft-context-menu-glass-pane")) + this._highlightMenuItem(null); + }, + + _highlightMenuItem: function(menuItemElement) + { + if (this._highlightedMenuItemElement === menuItemElement) + return; + + this._hideSubMenu(); + if (this._highlightedMenuItemElement) { + this._highlightedMenuItemElement.classList.remove("soft-context-menu-item-mouse-over"); + if (this._highlightedMenuItemElement._subItems && this._highlightedMenuItemElement._subMenuTimer) { + clearTimeout(this._highlightedMenuItemElement._subMenuTimer); + delete this._highlightedMenuItemElement._subMenuTimer; + } + } + this._highlightedMenuItemElement = menuItemElement; + if (this._highlightedMenuItemElement) { + this._highlightedMenuItemElement.classList.add("soft-context-menu-item-mouse-over"); + this._contextMenuElement.focus(); + if (this._highlightedMenuItemElement._subItems && !this._highlightedMenuItemElement._subMenuTimer) + this._highlightedMenuItemElement._subMenuTimer = setTimeout(this._showSubMenu.bind(this, this._highlightedMenuItemElement, this._buildMouseEventForSubMenu(this._highlightedMenuItemElement)), 150); + } + }, + + _highlightPrevious: function() + { + var menuItemElement = this._highlightedMenuItemElement ? this._highlightedMenuItemElement.previousSibling : this._contextMenuElement.lastChild; + while (menuItemElement && menuItemElement._isSeparator) + menuItemElement = menuItemElement.previousSibling; + if (menuItemElement) + this._highlightMenuItem(menuItemElement); + }, + + _highlightNext: function() + { + var menuItemElement = this._highlightedMenuItemElement ? this._highlightedMenuItemElement.nextSibling : this._contextMenuElement.firstChild; + while (menuItemElement && menuItemElement._isSeparator) + menuItemElement = menuItemElement.nextSibling; + if (menuItemElement) + this._highlightMenuItem(menuItemElement); + }, + + _menuKeyDown: function(event) + { + switch (event.keyIdentifier) { + case "Up": + this._highlightPrevious(); break; + case "Down": + this._highlightNext(); break; + case "Left": + if (this._parentMenu) { + this._highlightMenuItem(null); + this._parentMenu._focus(); + } + break; + case "Right": + if (!this._highlightedMenuItemElement) + break; + if (this._highlightedMenuItemElement._subItems) { + this._showSubMenu(this._highlightedMenuItemElement, this._buildMouseEventForSubMenu(this._highlightedMenuItemElement)); + this._subMenu._focus(); + this._subMenu._highlightNext(); + } + break; + case "U+001B": // Escape + this._discardMenu(true, event); break; + case "Enter": + if (!isEnterKey(event)) + break; + // Fall through + case "U+0020": // Space + if (this._highlightedMenuItemElement) + this._triggerAction(this._highlightedMenuItemElement, event); + break; + } + event.consume(true); + }, + + _glassPaneMouseUp: function(event) + { + // Return if this is simple 'click', since dispatched on glass pane, can't use 'click' event. + if (event.x === this._x && event.y === this._y && new Date().getTime() - this._time < 300) + return; + this._discardMenu(true, event); + event.consume(); + }, + + /** + * @param {boolean} closeParentMenus + * @param {!Event=} event + */ + _discardMenu: function(closeParentMenus, event) + { + if (this._subMenu && !closeParentMenus) + return; + if (this._glassPaneElement) { + var glassPane = this._glassPaneElement; + delete this._glassPaneElement; + // This can re-enter discardMenu due to blur. + document.body.removeChild(glassPane); + if (this._parentMenu) { + delete this._parentMenu._subMenu; + if (closeParentMenus) + this._parentMenu._discardMenu(closeParentMenus, event); + } + + if (event) + event.consume(true); + } else if (this._parentMenu && this._contextMenuElement.parentElement) { + this._discardSubMenus(); + if (closeParentMenus) + this._parentMenu._discardMenu(closeParentMenus, event); + + if (event) + event.consume(true); + } + }, + + _discardSubMenus: function() + { + if (this._subMenu) + this._subMenu._discardSubMenus(); + this._contextMenuElement.remove(); + if (this._parentMenu) + delete this._parentMenu._subMenu; + } +} + +if (!InspectorFrontendHost.showContextMenu) { + +InspectorFrontendHost.showContextMenu = function(event, items) +{ + new WebInspector.SoftContextMenu(items).show(event); +} + +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/SplitView.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/SplitView.js new file mode 100644 index 00000000000..29745153432 --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/SplitView.js @@ -0,0 +1,888 @@ +/* + * Copyright (C) 2012 Google 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 GOOGLE INC. AND ITS CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC. + * OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.View} + * @param {boolean} isVertical + * @param {boolean} secondIsSidebar + * @param {string=} settingName + * @param {number=} defaultSidebarWidth + * @param {number=} defaultSidebarHeight + * @param {boolean=} constraintsInDip + */ +WebInspector.SplitView = function(isVertical, secondIsSidebar, settingName, defaultSidebarWidth, defaultSidebarHeight, constraintsInDip) +{ + WebInspector.View.call(this); + + this.registerRequiredCSS("splitView.css"); + this.element.classList.add("split-view"); + + this._mainView = new WebInspector.VBox(); + this._mainElement = this._mainView.element; + this._mainElement.className = "split-view-contents scroll-target split-view-main vbox"; // Override + + this._sidebarView = new WebInspector.VBox(); + this._sidebarElement = this._sidebarView.element; + this._sidebarElement.className = "split-view-contents scroll-target split-view-sidebar vbox"; // Override + + this._resizerElement = this.element.createChild("div", "split-view-resizer"); + this._resizerElement.createChild("div", "split-view-resizer-border"); + if (secondIsSidebar) { + this._mainView.show(this.element); + this._sidebarView.show(this.element); + } else { + this._sidebarView.show(this.element); + this._mainView.show(this.element); + } + + this._resizerWidget = new WebInspector.ResizerWidget(); + this._resizerWidget.setEnabled(true); + this._resizerWidget.addEventListener(WebInspector.ResizerWidget.Events.ResizeStart, this._onResizeStart, this); + this._resizerWidget.addEventListener(WebInspector.ResizerWidget.Events.ResizeUpdate, this._onResizeUpdate, this); + this._resizerWidget.addEventListener(WebInspector.ResizerWidget.Events.ResizeEnd, this._onResizeEnd, this); + + this._defaultSidebarWidth = defaultSidebarWidth || 200; + this._defaultSidebarHeight = defaultSidebarHeight || this._defaultSidebarWidth; + this._constraintsInDip = !!constraintsInDip; + this._settingName = settingName; + + this.setSecondIsSidebar(secondIsSidebar); + + this._innerSetVertical(isVertical); + this._showMode = WebInspector.SplitView.ShowMode.Both; + + // Should be called after isVertical has the right value. + this.installResizer(this._resizerElement); +} + +/** @typedef {{showMode: string, size: number}} */ +WebInspector.SplitView.SettingForOrientation; + +WebInspector.SplitView.ShowMode = { + Both: "Both", + OnlyMain: "OnlyMain", + OnlySidebar: "OnlySidebar" +} + +WebInspector.SplitView.Events = { + SidebarSizeChanged: "SidebarSizeChanged", + ShowModeChanged: "ShowModeChanged" +} + +WebInspector.SplitView.MinPadding = 20; + +WebInspector.SplitView.prototype = { + /** + * @return {boolean} + */ + isVertical: function() + { + return this._isVertical; + }, + + /** + * @param {boolean} isVertical + */ + setVertical: function(isVertical) + { + if (this._isVertical === isVertical) + return; + + this._innerSetVertical(isVertical); + + if (this.isShowing()) + this._updateLayout(); + }, + + /** + * @param {boolean} isVertical + */ + _innerSetVertical: function(isVertical) + { + this.element.classList.remove(this._isVertical ? "hbox" : "vbox"); + this._isVertical = isVertical; + this.element.classList.add(this._isVertical ? "hbox" : "vbox"); + delete this._resizerElementSize; + this._sidebarSize = -1; + this._restoreSidebarSizeFromSettings(); + if (this._shouldSaveShowMode) + this._restoreAndApplyShowModeFromSettings(); + this._updateShowHideSidebarButton(); + // FIXME: reverse SplitView.isVertical meaning. + this._resizerWidget.setVertical(!isVertical); + this.invalidateConstraints(); + }, + + /** + * @param {boolean=} animate + */ + _updateLayout: function(animate) + { + delete this._totalSize; // Lazy update. + delete this._totalSizeOtherDimension; + + // Remove properties that might affect total size calculation. + this._mainElement.style.removeProperty("width"); + this._mainElement.style.removeProperty("height"); + this._sidebarElement.style.removeProperty("width"); + this._sidebarElement.style.removeProperty("height"); + + this._innerSetSidebarSize(this._preferredSidebarSize(), !!animate); + }, + + /** + * @return {!Element} + */ + mainElement: function() + { + return this._mainElement; + }, + + /** + * @return {!Element} + */ + sidebarElement: function() + { + return this._sidebarElement; + }, + + /** + * @return {boolean} + */ + isSidebarSecond: function() + { + return this._secondIsSidebar; + }, + + enableShowModeSaving: function() + { + this._shouldSaveShowMode = true; + this._restoreAndApplyShowModeFromSettings(); + }, + + /** + * @return {string} + */ + showMode: function() + { + return this._showMode; + }, + + /** + * @param {boolean} secondIsSidebar + */ + setSecondIsSidebar: function(secondIsSidebar) + { + this._mainElement.classList.toggle("split-view-contents-first", secondIsSidebar); + this._mainElement.classList.toggle("split-view-contents-second", !secondIsSidebar); + this._sidebarElement.classList.toggle("split-view-contents-first", !secondIsSidebar); + this._sidebarElement.classList.toggle("split-view-contents-second", secondIsSidebar); + + // Make sure second is last in the children array. + if (secondIsSidebar) { + if (this._sidebarElement.parentElement && this._sidebarElement.nextSibling) + this.element.appendChild(this._sidebarElement); + } else { + if (this._mainElement.parentElement && this._mainElement.nextSibling) + this.element.appendChild(this._mainElement); + } + + this._secondIsSidebar = secondIsSidebar; + }, + + /** + * @return {?string} + */ + sidebarSide: function() + { + if (this._showMode !== WebInspector.SplitView.ShowMode.Both) + return null; + return this._isVertical ? + (this._secondIsSidebar ? "right" : "left") : + (this._secondIsSidebar ? "bottom" : "top"); + }, + + /** + * @return {number} + */ + preferredSidebarSize: function() + { + return this._preferredSidebarSize(); + }, + + /** + * @return {!Element} + */ + resizerElement: function() + { + return this._resizerElement; + }, + + /** + * @param {boolean=} animate + */ + hideMain: function(animate) + { + this._showOnly(this._sidebarView, this._mainView, animate); + this._updateShowMode(WebInspector.SplitView.ShowMode.OnlySidebar); + }, + + /** + * @param {boolean=} animate + */ + hideSidebar: function(animate) + { + this._showOnly(this._mainView, this._sidebarView, animate); + this._updateShowMode(WebInspector.SplitView.ShowMode.OnlyMain); + }, + + /** + * @override + */ + detachChildViews: function() + { + this._mainView.detachChildViews(); + this._sidebarView.detachChildViews(); + }, + + /** + * @param {!WebInspector.View} sideToShow + * @param {!WebInspector.View} sideToHide + * @param {boolean=} animate + */ + _showOnly: function(sideToShow, sideToHide, animate) + { + this._cancelAnimation(); + + /** + * @this {WebInspector.SplitView} + */ + function callback() + { + sideToShow.show(this.element); + sideToHide.detach(); + sideToShow.element.classList.add("maximized"); + sideToHide.element.classList.remove("maximized"); + this._resizerElement.classList.add("hidden"); + this._removeAllLayoutProperties(); + } + + if (animate) { + this._animate(true, callback.bind(this)); + } else { + callback.call(this); + this.doResize(); + } + + this._sidebarSize = -1; + this.setResizable(false); + }, + + _removeAllLayoutProperties: function() + { + this._sidebarElement.style.removeProperty("flexBasis"); + + this._mainElement.style.removeProperty("width"); + this._mainElement.style.removeProperty("height"); + this._sidebarElement.style.removeProperty("width"); + this._sidebarElement.style.removeProperty("height"); + + this._resizerElement.style.removeProperty("left"); + this._resizerElement.style.removeProperty("right"); + this._resizerElement.style.removeProperty("top"); + this._resizerElement.style.removeProperty("bottom"); + + this._resizerElement.style.removeProperty("margin-left"); + this._resizerElement.style.removeProperty("margin-right"); + this._resizerElement.style.removeProperty("margin-top"); + this._resizerElement.style.removeProperty("margin-bottom"); + }, + + /** + * @param {boolean=} animate + */ + showBoth: function(animate) + { + if (this._showMode === WebInspector.SplitView.ShowMode.Both) + animate = false; + + this._cancelAnimation(); + this._mainElement.classList.remove("maximized"); + this._sidebarElement.classList.remove("maximized"); + this._resizerElement.classList.remove("hidden"); + + this._mainView.show(this.element); + this._sidebarView.show(this.element); + // Order views in DOM properly. + this.setSecondIsSidebar(this._secondIsSidebar); + + this._sidebarSize = -1; + this.setResizable(true); + this._updateShowMode(WebInspector.SplitView.ShowMode.Both); + this._updateLayout(animate); + }, + + /** + * @param {boolean} resizable + */ + setResizable: function(resizable) + { + this._resizerWidget.setEnabled(resizable); + }, + + /** + * @return {boolean} + */ + isResizable: function() + { + return this._resizerWidget.isEnabled(); + }, + + /** + * @param {number} size + */ + setSidebarSize: function(size) + { + size *= WebInspector.zoomManager.zoomFactor(); + this._savedSidebarSize = size; + this._saveSetting(); + this._innerSetSidebarSize(size, false, true); + }, + + /** + * @return {number} + */ + sidebarSize: function() + { + var size = Math.max(0, this._sidebarSize); + return size / WebInspector.zoomManager.zoomFactor(); + }, + + /** + * Returns total size in DIP. + * @return {number} + */ + _totalSizeDIP: function() + { + if (!this._totalSize) { + this._totalSize = this._isVertical ? this.element.offsetWidth : this.element.offsetHeight; + this._totalSizeOtherDimension = this._isVertical ? this.element.offsetHeight : this.element.offsetWidth; + } + return this._totalSize * WebInspector.zoomManager.zoomFactor(); + }, + + /** + * @param {string} showMode + */ + _updateShowMode: function(showMode) + { + this._showMode = showMode; + this._saveShowModeToSettings(); + this._updateShowHideSidebarButton(); + this.dispatchEventToListeners(WebInspector.SplitView.Events.ShowModeChanged, showMode); + this.invalidateConstraints(); + }, + + /** + * @param {number} size + * @param {boolean} animate + * @param {boolean=} userAction + */ + _innerSetSidebarSize: function(size, animate, userAction) + { + if (this._showMode !== WebInspector.SplitView.ShowMode.Both || !this.isShowing()) + return; + + size = this._applyConstraints(size, userAction); + if (this._sidebarSize === size) + return; + + if (!this._resizerElementSize) + this._resizerElementSize = this._isVertical ? this._resizerElement.offsetWidth : this._resizerElement.offsetHeight; + + // Invalidate layout below. + + this._removeAllLayoutProperties(); + + // this._totalSize is available below since we successfully applied constraints. + var sidebarSizeValue = (size / WebInspector.zoomManager.zoomFactor()) + "px"; + var mainSizeValue = (this._totalSize - size / WebInspector.zoomManager.zoomFactor()) + "px"; + this.sidebarElement().style.flexBasis = sidebarSizeValue; + + // Make both sides relayout boundaries. + if (this._isVertical) { + this._sidebarElement.style.width = sidebarSizeValue; + this._mainElement.style.width = mainSizeValue; + this._sidebarElement.style.height = this._totalSizeOtherDimension + "px"; + this._mainElement.style.height = this._totalSizeOtherDimension + "px"; + } else { + this._sidebarElement.style.height = sidebarSizeValue; + this._mainElement.style.height = mainSizeValue; + this._sidebarElement.style.width = this._totalSizeOtherDimension + "px"; + this._mainElement.style.width = this._totalSizeOtherDimension + "px"; + } + + // Position resizer. + if (this._isVertical) { + if (this._secondIsSidebar) { + this._resizerElement.style.right = sidebarSizeValue; + this._resizerElement.style.marginRight = -this._resizerElementSize / 2 + "px"; + } else { + this._resizerElement.style.left = sidebarSizeValue; + this._resizerElement.style.marginLeft = -this._resizerElementSize / 2 + "px"; + } + } else { + if (this._secondIsSidebar) { + this._resizerElement.style.bottom = sidebarSizeValue; + this._resizerElement.style.marginBottom = -this._resizerElementSize / 2 + "px"; + } else { + this._resizerElement.style.top = sidebarSizeValue; + this._resizerElement.style.marginTop = -this._resizerElementSize / 2 + "px"; + } + } + + this._sidebarSize = size; + + // Force layout. + + if (animate) { + this._animate(false); + } else { + // No need to recalculate this._sidebarSize and this._totalSize again. + this.doResize(); + this.dispatchEventToListeners(WebInspector.SplitView.Events.SidebarSizeChanged, this.sidebarSize()); + } + }, + + /** + * @param {boolean} reverse + * @param {function()=} callback + */ + _animate: function(reverse, callback) + { + var animationTime = 50; + this._animationCallback = callback; + + var animatedMarginPropertyName; + if (this._isVertical) + animatedMarginPropertyName = this._secondIsSidebar ? "margin-right" : "margin-left"; + else + animatedMarginPropertyName = this._secondIsSidebar ? "margin-bottom" : "margin-top"; + + var zoomFactor = WebInspector.zoomManager.zoomFactor(); + var marginFrom = reverse ? "0" : "-" + (this._sidebarSize / zoomFactor) + "px"; + var marginTo = reverse ? "-" + (this._sidebarSize / zoomFactor) + "px" : "0"; + + // This order of things is important. + // 1. Resize main element early and force layout. + this.element.style.setProperty(animatedMarginPropertyName, marginFrom); + if (!reverse) { + suppressUnused(this._mainElement.offsetWidth); + suppressUnused(this._sidebarElement.offsetWidth); + } + + // 2. Issue onresize to the sidebar element, its size won't change. + if (!reverse) + this._sidebarView.doResize(); + + // 3. Configure and run animation + this.element.style.setProperty("transition", animatedMarginPropertyName + " " + animationTime + "ms linear"); + + var boundAnimationFrame; + var startTime; + /** + * @this {WebInspector.SplitView} + */ + function animationFrame() + { + delete this._animationFrameHandle; + + if (!startTime) { + // Kick animation on first frame. + this.element.style.setProperty(animatedMarginPropertyName, marginTo); + startTime = window.performance.now(); + } else if (window.performance.now() < startTime + animationTime) { + // Process regular animation frame. + this._mainView.doResize(); + } else { + // Complete animation. + this._cancelAnimation(); + this._mainView.doResize(); + this.dispatchEventToListeners(WebInspector.SplitView.Events.SidebarSizeChanged, this.sidebarSize()); + return; + } + this._animationFrameHandle = window.requestAnimationFrame(boundAnimationFrame); + } + boundAnimationFrame = animationFrame.bind(this); + this._animationFrameHandle = window.requestAnimationFrame(boundAnimationFrame); + }, + + _cancelAnimation: function() + { + this.element.style.removeProperty("margin-top"); + this.element.style.removeProperty("margin-right"); + this.element.style.removeProperty("margin-bottom"); + this.element.style.removeProperty("margin-left"); + this.element.style.removeProperty("transition"); + + if (this._animationFrameHandle) { + window.cancelAnimationFrame(this._animationFrameHandle); + delete this._animationFrameHandle; + } + if (this._animationCallback) { + this._animationCallback(); + delete this._animationCallback; + } + }, + + /** + * @param {number} sidebarSize + * @param {boolean=} userAction + * @return {number} + */ + _applyConstraints: function(sidebarSize, userAction) + { + var totalSize = this._totalSizeDIP(); + var zoomFactor = this._constraintsInDip ? 1 : WebInspector.zoomManager.zoomFactor(); + + var constraints = this._sidebarView.constraints(); + var minSidebarSize = this.isVertical() ? constraints.minimum.width : constraints.minimum.height; + if (!minSidebarSize) + minSidebarSize = WebInspector.SplitView.MinPadding; + minSidebarSize *= zoomFactor; + + var preferredSidebarSize = this.isVertical() ? constraints.preferred.width : constraints.preferred.height; + if (!preferredSidebarSize) + preferredSidebarSize = WebInspector.SplitView.MinPadding; + preferredSidebarSize *= zoomFactor; + // Allow sidebar to be less than preferred by explicit user action. + if (sidebarSize < preferredSidebarSize) + preferredSidebarSize = Math.max(sidebarSize, minSidebarSize); + + constraints = this._mainView.constraints(); + var minMainSize = this.isVertical() ? constraints.minimum.width : constraints.minimum.height; + if (!minMainSize) + minMainSize = WebInspector.SplitView.MinPadding; + minMainSize *= zoomFactor; + + var preferredMainSize = this.isVertical() ? constraints.preferred.width : constraints.preferred.height; + if (!preferredMainSize) + preferredMainSize = WebInspector.SplitView.MinPadding; + preferredMainSize *= zoomFactor; + var savedMainSize = this.isVertical() ? this._savedVerticalMainSize : this._savedHorizontalMainSize; + if (typeof savedMainSize !== "undefined") + preferredMainSize = Math.min(preferredMainSize, savedMainSize * zoomFactor); + if (userAction) + preferredMainSize = minMainSize; + + // Enough space for preferred. + var totalPreferred = preferredMainSize + preferredSidebarSize; + if (totalPreferred <= totalSize) + return Number.constrain(sidebarSize, preferredSidebarSize, totalSize - preferredMainSize); + + // Enough space for minimum. + if (minMainSize + minSidebarSize <= totalSize) { + var delta = totalPreferred - totalSize; + var sidebarDelta = delta * preferredSidebarSize / totalPreferred; + sidebarSize = preferredSidebarSize - sidebarDelta; + return Number.constrain(sidebarSize, minSidebarSize, totalSize - minMainSize); + } + + // Not enough space even for minimum sizes. + return Math.max(0, totalSize - minMainSize); + }, + + wasShown: function() + { + this._forceUpdateLayout(); + WebInspector.zoomManager.addEventListener(WebInspector.ZoomManager.Events.ZoomChanged, this._onZoomChanged, this); + }, + + willHide: function() + { + WebInspector.zoomManager.removeEventListener(WebInspector.ZoomManager.Events.ZoomChanged, this._onZoomChanged, this); + }, + + onResize: function() + { + this._updateLayout(); + }, + + onLayout: function() + { + this._updateLayout(); + }, + + /** + * @return {!Constraints} + */ + calculateConstraints: function() + { + if (this._showMode === WebInspector.SplitView.ShowMode.OnlyMain) + return this._mainView.constraints(); + if (this._showMode === WebInspector.SplitView.ShowMode.OnlySidebar) + return this._sidebarView.constraints(); + + var mainConstraints = this._mainView.constraints(); + var sidebarConstraints = this._sidebarView.constraints(); + var min = WebInspector.SplitView.MinPadding; + if (this._isVertical) { + mainConstraints = mainConstraints.widthToMax(min); + sidebarConstraints = sidebarConstraints.widthToMax(min); + return mainConstraints.addWidth(sidebarConstraints).heightToMax(sidebarConstraints); + } else { + mainConstraints = mainConstraints.heightToMax(min); + sidebarConstraints = sidebarConstraints.heightToMax(min); + return mainConstraints.widthToMax(sidebarConstraints).addHeight(sidebarConstraints); + } + }, + + /** + * @param {!WebInspector.Event} event + */ + _onResizeStart: function(event) + { + this._resizeStartSize = this._sidebarSize; + }, + + /** + * @param {!WebInspector.Event} event + */ + _onResizeUpdate: function(event) + { + var cssOffset = event.data.currentPosition - event.data.startPosition; + var dipOffset = cssOffset * WebInspector.zoomManager.zoomFactor(); + var newSize = this._secondIsSidebar ? this._resizeStartSize - dipOffset : this._resizeStartSize + dipOffset; + var constrainedSize = this._applyConstraints(newSize, true); + this._savedSidebarSize = constrainedSize; + this._saveSetting(); + this._innerSetSidebarSize(constrainedSize, false, true); + if (this.isVertical()) + this._savedVerticalMainSize = this._totalSizeDIP() - this._sidebarSize; + else + this._savedHorizontalMainSize = this._totalSizeDIP() - this._sidebarSize; + }, + + /** + * @param {!WebInspector.Event} event + */ + _onResizeEnd: function(event) + { + delete this._resizeStartSize; + }, + + hideDefaultResizer: function() + { + this.uninstallResizer(this._resizerElement); + }, + + /** + * @param {!Element} resizerElement + */ + installResizer: function(resizerElement) + { + this._resizerWidget.addElement(resizerElement); + }, + + /** + * @param {!Element} resizerElement + */ + uninstallResizer: function(resizerElement) + { + this._resizerWidget.removeElement(resizerElement); + }, + + /** + * @return {boolean} + */ + hasCustomResizer: function() + { + var elements = this._resizerWidget.elements(); + return elements.length > 1 || (elements.length == 1 && elements[0] !== this._resizerElement); + }, + + /** + * @param {!Element} resizer + * @param {boolean} on + */ + toggleResizer: function(resizer, on) + { + if (on) + this.installResizer(resizer); + else + this.uninstallResizer(resizer); + }, + + /** + * @return {?WebInspector.Setting} + */ + _setting: function() + { + if (!this._settingName) + return null; + + if (!WebInspector.settings[this._settingName]) + WebInspector.settings[this._settingName] = WebInspector.settings.createSetting(this._settingName, {}); + + return WebInspector.settings[this._settingName]; + }, + + /** + * @return {?WebInspector.SplitView.SettingForOrientation} + */ + _settingForOrientation: function() + { + var state = this._setting() ? this._setting().get() : {}; + return this._isVertical ? state.vertical : state.horizontal; + }, + + /** + * @return {number} + */ + _preferredSidebarSize: function() + { + var size = this._savedSidebarSize; + if (!size) { + size = this._isVertical ? this._defaultSidebarWidth : this._defaultSidebarHeight; + // If we have default value in percents, calculate it on first use. + if (0 < size && size < 1) + size *= this._totalSizeDIP(); + } + return size; + }, + + _restoreSidebarSizeFromSettings: function() + { + var settingForOrientation = this._settingForOrientation(); + this._savedSidebarSize = settingForOrientation ? settingForOrientation.size : 0; + }, + + _restoreAndApplyShowModeFromSettings: function() + { + var orientationState = this._settingForOrientation(); + this._savedShowMode = orientationState ? orientationState.showMode : WebInspector.SplitView.ShowMode.Both; + this._showMode = this._savedShowMode; + + switch (this._savedShowMode) { + case WebInspector.SplitView.ShowMode.Both: + this.showBoth(); + break; + case WebInspector.SplitView.ShowMode.OnlyMain: + this.hideSidebar(); + break; + case WebInspector.SplitView.ShowMode.OnlySidebar: + this.hideMain(); + break; + } + }, + + _saveShowModeToSettings: function() + { + this._savedShowMode = this._showMode; + this._saveSetting(); + }, + + _saveSetting: function() + { + var setting = this._setting(); + if (!setting) + return; + var state = setting.get(); + var orientationState = (this._isVertical ? state.vertical : state.horizontal) || {}; + + orientationState.size = this._savedSidebarSize; + if (this._shouldSaveShowMode) + orientationState.showMode = this._savedShowMode; + + if (this._isVertical) + state.vertical = orientationState; + else + state.horizontal = orientationState; + setting.set(state); + }, + + _forceUpdateLayout: function() + { + // Force layout even if sidebar size does not change. + this._sidebarSize = -1; + this._updateLayout(); + }, + + /** + * @param {!WebInspector.Event} event + */ + _onZoomChanged: function(event) + { + this._forceUpdateLayout(); + }, + + /** + * @param {string} title + * @param {string} className + * @return {!WebInspector.StatusBarButton} + */ + createShowHideSidebarButton: function(title, className) + { + console.assert(this.isVertical(), "Buttons for split view with horizontal split are not supported yet."); + + this._showHideSidebarButtonTitle = WebInspector.UIString(title); + this._showHideSidebarButton = new WebInspector.StatusBarButton("", "sidebar-show-hide-button " + className, 3); + this._showHideSidebarButton.addEventListener("click", buttonClicked.bind(this)); + this._updateShowHideSidebarButton(); + + /** + * @this {WebInspector.SplitView} + * @param {!WebInspector.Event} event + */ + function buttonClicked(event) + { + if (this._showMode !== WebInspector.SplitView.ShowMode.Both) + this.showBoth(true); + else + this.hideSidebar(true); + } + + return this._showHideSidebarButton; + }, + + _updateShowHideSidebarButton: function() + { + if (!this._showHideSidebarButton) + return; + var sidebarHidden = this._showMode === WebInspector.SplitView.ShowMode.OnlyMain; + this._showHideSidebarButton.state = sidebarHidden ? "show" : "hide"; + this._showHideSidebarButton.element.classList.toggle("top-sidebar-show-hide-button", !this.isVertical() && !this.isSidebarSecond()); + this._showHideSidebarButton.element.classList.toggle("right-sidebar-show-hide-button", this.isVertical() && this.isSidebarSecond()); + this._showHideSidebarButton.element.classList.toggle("bottom-sidebar-show-hide-button", !this.isVertical() && this.isSidebarSecond()); + this._showHideSidebarButton.element.classList.toggle("left-sidebar-show-hide-button", this.isVertical() && !this.isSidebarSecond()); + this._showHideSidebarButton.title = sidebarHidden ? WebInspector.UIString("Show %s", this._showHideSidebarButtonTitle) : WebInspector.UIString("Hide %s", this._showHideSidebarButtonTitle); + }, + + __proto__: WebInspector.View.prototype +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/StackView.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/StackView.js new file mode 100644 index 00000000000..1b9e4f20406 --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/StackView.js @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2014 Google 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 GOOGLE INC. AND ITS CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC. + * OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.VBox} + * @param {boolean} isVertical + */ +WebInspector.StackView = function(isVertical) +{ + WebInspector.VBox.call(this); + this._isVertical = isVertical; + this._currentSplitView = null; +} + +WebInspector.StackView.prototype = { + /** + * @param {!WebInspector.View} view + * @param {string=} sidebarSizeSettingName + * @param {number=} defaultSidebarWidth + * @param {number=} defaultSidebarHeight + * @return {!WebInspector.SplitView} + */ + appendView: function(view, sidebarSizeSettingName, defaultSidebarWidth, defaultSidebarHeight) + { + var splitView = new WebInspector.SplitView(this._isVertical, true, sidebarSizeSettingName, defaultSidebarWidth, defaultSidebarHeight); + view.show(splitView.mainElement()); + splitView.hideSidebar(); + + if (!this._currentSplitView) { + splitView.show(this.element); + } else { + splitView.show(this._currentSplitView.sidebarElement()); + this._currentSplitView.showBoth(); + } + + this._currentSplitView = splitView; + return splitView; + }, + + detachChildViews: function() + { + WebInspector.View.prototype.detachChildViews.call(this); + this._currentSplitView = null; + }, + + __proto__: WebInspector.VBox.prototype +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/StatusBarButton.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/StatusBarButton.js new file mode 100644 index 00000000000..cc0128d736d --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/StatusBarButton.js @@ -0,0 +1,688 @@ +/* + * Copyright (C) 2009 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER 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. + */ + +/** + * @constructor + * @extends {WebInspector.Object} + * @param {string} elementType + */ +WebInspector.StatusBarItem = function(elementType) +{ + this.element = document.createElement(elementType); + this._enabled = true; + this._visible = true; +} + +WebInspector.StatusBarItem.prototype = { + /** + * @param {boolean} value + */ + setEnabled: function(value) + { + if (this._enabled === value) + return; + this._enabled = value; + this._applyEnabledState(); + }, + + /** + * @protected + */ + _applyEnabledState: function() + { + this.element.disabled = !this._enabled; + }, + + get visible() + { + return this._visible; + }, + + set visible(x) + { + if (this._visible === x) + return; + this.element.classList.toggle("hidden", !x); + this._visible = x; + }, + + __proto__: WebInspector.Object.prototype +} + +/** + * @constructor + * @extends {WebInspector.StatusBarItem} + * @param {string} text + * @param {string=} className + */ +WebInspector.StatusBarText = function(text, className) +{ + WebInspector.StatusBarItem.call(this, "span"); + this.element.className = "status-bar-item status-bar-text"; + if (className) + this.element.classList.add(className); + this.element.textContent = text; +} + +WebInspector.StatusBarText.prototype = { + /** + * @param {string} text + */ + setText: function(text) + { + this.element.textContent = text; + }, + + __proto__: WebInspector.StatusBarItem.prototype +} + +/** + * @constructor + * @extends {WebInspector.StatusBarItem} + * @param {string=} placeholder + * @param {number=} width + */ +WebInspector.StatusBarInput = function(placeholder, width) +{ + WebInspector.StatusBarItem.call(this, "input"); + this.element.className = "status-bar-item"; + this.element.addEventListener("input", this._onChangeCallback.bind(this), false); + if (width) + this.element.style.width = width + "px"; + if (placeholder) + this.element.setAttribute("placeholder", placeholder); + this._value = ""; +} + +WebInspector.StatusBarInput.Event = { + TextChanged: "TextChanged" +}; + +WebInspector.StatusBarInput.prototype = { + /** + * @param {string} value + */ + setValue: function(value) + { + this._value = value; + this.element.value = value; + }, + + /** + * @return {string} + */ + value: function() + { + return this.element.value; + }, + + _onChangeCallback: function() + { + this.dispatchEventToListeners(WebInspector.StatusBarInput.Event.TextChanged, this.element.value); + }, + + __proto__: WebInspector.StatusBarItem.prototype +} + +/** + * @constructor + * @extends {WebInspector.StatusBarItem} + * @param {string} title + * @param {string} className + * @param {number=} states + */ +WebInspector.StatusBarButton = function(title, className, states) +{ + WebInspector.StatusBarItem.call(this, "button"); + this.element.className = className + " status-bar-item"; + this.element.addEventListener("click", this._clicked.bind(this), false); + + this.glyph = document.createElement("div"); + this.glyph.className = "glyph"; + this.element.appendChild(this.glyph); + + this.glyphShadow = document.createElement("div"); + this.glyphShadow.className = "glyph shadow"; + this.element.appendChild(this.glyphShadow); + + this.states = states; + if (!states) + this.states = 2; + + if (states == 2) + this._state = false; + else + this._state = 0; + + this.title = title; + this.className = className; +} + +WebInspector.StatusBarButton.prototype = { + _clicked: function() + { + this.dispatchEventToListeners("click"); + if (this._longClickInterval) { + clearInterval(this._longClickInterval); + delete this._longClickInterval; + } + }, + + /** + * @override + */ + _applyEnabledState: function() + { + this.element.disabled = !this._enabled; + if (this._longClickInterval) { + clearInterval(this._longClickInterval); + delete this._longClickInterval; + } + }, + + /** + * @return {boolean} + */ + enabled: function() + { + return this._enabled; + }, + + get title() + { + return this._title; + }, + + set title(x) + { + if (this._title === x) + return; + this._title = x; + this.element.title = x; + }, + + get state() + { + return this._state; + }, + + set state(x) + { + if (this._state === x) + return; + + if (this.states === 2) + this.element.classList.toggle("toggled-on", x); + else { + this.element.classList.remove("toggled-" + this._state); + if (x !== 0) + this.element.classList.add("toggled-" + x); + } + this._state = x; + }, + + get toggled() + { + if (this.states !== 2) + throw("Only used toggled when there are 2 states, otherwise, use state"); + return this.state; + }, + + set toggled(x) + { + if (this.states !== 2) + throw("Only used toggled when there are 2 states, otherwise, use state"); + this.state = x; + }, + + makeLongClickEnabled: function() + { + var boundMouseDown = mouseDown.bind(this); + var boundMouseUp = mouseUp.bind(this); + + this.element.addEventListener("mousedown", boundMouseDown, false); + this.element.addEventListener("mouseout", boundMouseUp, false); + this.element.addEventListener("mouseup", boundMouseUp, false); + + var longClicks = 0; + + this._longClickData = { mouseUp: boundMouseUp, mouseDown: boundMouseDown }; + + /** + * @param {?Event} e + * @this {WebInspector.StatusBarButton} + */ + function mouseDown(e) + { + if (e.which !== 1) + return; + longClicks = 0; + this._longClickInterval = setInterval(longClicked.bind(this), 200); + } + + /** + * @param {?Event} e + * @this {WebInspector.StatusBarButton} + */ + function mouseUp(e) + { + if (e.which !== 1) + return; + if (this._longClickInterval) { + clearInterval(this._longClickInterval); + delete this._longClickInterval; + } + } + + /** + * @this {WebInspector.StatusBarButton} + */ + function longClicked() + { + ++longClicks; + this.dispatchEventToListeners(longClicks === 1 ? "longClickDown" : "longClickPress"); + } + }, + + unmakeLongClickEnabled: function() + { + if (!this._longClickData) + return; + this.element.removeEventListener("mousedown", this._longClickData.mouseDown, false); + this.element.removeEventListener("mouseout", this._longClickData.mouseUp, false); + this.element.removeEventListener("mouseup", this._longClickData.mouseUp, false); + delete this._longClickData; + }, + + /** + * @param {?function():!Array.<!WebInspector.StatusBarButton>} buttonsProvider + */ + setLongClickOptionsEnabled: function(buttonsProvider) + { + if (buttonsProvider) { + if (!this._longClickOptionsData) { + this.makeLongClickEnabled(); + + this.longClickGlyph = document.createElement("div"); + this.longClickGlyph.className = "fill long-click-glyph"; + this.element.appendChild(this.longClickGlyph); + + this.longClickGlyphShadow = document.createElement("div"); + this.longClickGlyphShadow.className = "fill long-click-glyph shadow"; + this.element.appendChild(this.longClickGlyphShadow); + + var longClickDownListener = this._showOptions.bind(this); + this.addEventListener("longClickDown", longClickDownListener, this); + + this._longClickOptionsData = { + glyphElement: this.longClickGlyph, + glyphShadowElement: this.longClickGlyphShadow, + longClickDownListener: longClickDownListener + }; + } + this._longClickOptionsData.buttonsProvider = buttonsProvider; + } else { + if (!this._longClickOptionsData) + return; + this.element.removeChild(this._longClickOptionsData.glyphElement); + this.element.removeChild(this._longClickOptionsData.glyphShadowElement); + + this.removeEventListener("longClickDown", this._longClickOptionsData.longClickDownListener, this); + delete this._longClickOptionsData; + + this.unmakeLongClickEnabled(); + } + }, + + _showOptions: function() + { + var buttons = this._longClickOptionsData.buttonsProvider(); + var mainButtonClone = new WebInspector.StatusBarButton(this.title, this.className, this.states); + mainButtonClone.addEventListener("click", this._clicked, this); + mainButtonClone.state = this.state; + buttons.push(mainButtonClone); + + document.documentElement.addEventListener("mouseup", mouseUp, false); + + var optionsGlassPane = new WebInspector.GlassPane(); + var optionsBarElement = optionsGlassPane.element.createChild("div", "alternate-status-bar-buttons-bar"); + const buttonHeight = 23; + + var hostButtonPosition = this.element.totalOffset(); + + var topNotBottom = hostButtonPosition.top + buttonHeight * buttons.length < document.documentElement.offsetHeight; + + if (topNotBottom) + buttons = buttons.reverse(); + + optionsBarElement.style.height = (buttonHeight * buttons.length) + "px"; + if (topNotBottom) + optionsBarElement.style.top = (hostButtonPosition.top + 1) + "px"; + else + optionsBarElement.style.top = (hostButtonPosition.top - (buttonHeight * (buttons.length - 1))) + "px"; + optionsBarElement.style.left = (hostButtonPosition.left + 1) + "px"; + + for (var i = 0; i < buttons.length; ++i) { + buttons[i].element.addEventListener("mousemove", mouseOver, false); + buttons[i].element.addEventListener("mouseout", mouseOut, false); + optionsBarElement.appendChild(buttons[i].element); + } + var hostButtonIndex = topNotBottom ? 0 : buttons.length - 1; + buttons[hostButtonIndex].element.classList.add("emulate-active"); + + function mouseOver(e) + { + if (e.which !== 1) + return; + var buttonElement = e.target.enclosingNodeOrSelfWithClass("status-bar-item"); + buttonElement.classList.add("emulate-active"); + } + + function mouseOut(e) + { + if (e.which !== 1) + return; + var buttonElement = e.target.enclosingNodeOrSelfWithClass("status-bar-item"); + buttonElement.classList.remove("emulate-active"); + } + + function mouseUp(e) + { + if (e.which !== 1) + return; + optionsGlassPane.dispose(); + document.documentElement.removeEventListener("mouseup", mouseUp, false); + + for (var i = 0; i < buttons.length; ++i) { + if (buttons[i].element.classList.contains("emulate-active")) { + buttons[i].element.classList.remove("emulate-active"); + buttons[i]._clicked(); + break; + } + } + } + }, + + __proto__: WebInspector.StatusBarItem.prototype +} + +/** + * @interface + */ +WebInspector.StatusBarButton.Provider = function() +{ +} + +WebInspector.StatusBarButton.Provider.prototype = { + /** + * @return {?WebInspector.StatusBarButton} + */ + button: function() {} +} + +/** + * @constructor + * @extends {WebInspector.StatusBarItem} + * @param {?function(?Event)} changeHandler + * @param {string=} className + */ +WebInspector.StatusBarComboBox = function(changeHandler, className) +{ + WebInspector.StatusBarItem.call(this, "span"); + this.element.className = "status-bar-select-container"; + + this._selectElement = this.element.createChild("select", "status-bar-item"); + this.element.createChild("div", "status-bar-select-arrow"); + if (changeHandler) + this._selectElement.addEventListener("change", changeHandler, false); + if (className) + this._selectElement.classList.add(className); +} + +WebInspector.StatusBarComboBox.prototype = { + /** + * @return {!Element} + */ + selectElement: function() + { + return this._selectElement; + }, + + /** + * @return {number} + */ + size: function() + { + return this._selectElement.childElementCount; + }, + + /** + * @param {!Element} option + */ + addOption: function(option) + { + this._selectElement.appendChild(option); + }, + + /** + * @param {string} label + * @param {string=} title + * @param {string=} value + * @return {!Element} + */ + createOption: function(label, title, value) + { + var option = this._selectElement.createChild("option"); + option.text = label; + if (title) + option.title = title; + if (typeof value !== "undefined") + option.value = value; + return option; + }, + + /** + * @override + */ + _applyEnabledState: function() + { + this._selectElement.disabled = !this._enabled; + }, + + /** + * @param {!Element} option + */ + removeOption: function(option) + { + this._selectElement.removeChild(option); + }, + + removeOptions: function() + { + this._selectElement.removeChildren(); + }, + + /** + * @return {?Element} + */ + selectedOption: function() + { + if (this._selectElement.selectedIndex >= 0) + return this._selectElement[this._selectElement.selectedIndex]; + return null; + }, + + /** + * @param {!Element} option + */ + select: function(option) + { + this._selectElement.selectedIndex = Array.prototype.indexOf.call(/** @type {?} */ (this._selectElement), option); + }, + + /** + * @param {number} index + */ + setSelectedIndex: function(index) + { + this._selectElement.selectedIndex = index; + }, + + /** + * @return {number} + */ + selectedIndex: function() + { + return this._selectElement.selectedIndex; + }, + + __proto__: WebInspector.StatusBarItem.prototype +} + +/** + * @constructor + * @extends {WebInspector.StatusBarItem} + * @param {string} title + */ +WebInspector.StatusBarCheckbox = function(title) +{ + WebInspector.StatusBarItem.call(this, "label"); + this.element.classList.add("status-bar-item", "checkbox"); + this.inputElement = this.element.createChild("input"); + this.inputElement.type = "checkbox"; + this.element.createTextChild(title); +} + +WebInspector.StatusBarCheckbox.prototype = { + /** + * @return {boolean} + */ + checked: function() + { + return this.inputElement.checked; + }, + + __proto__: WebInspector.StatusBarItem.prototype +} + +/** + * @constructor + * @extends {WebInspector.StatusBarButton} + * @param {string} className + * @param {!Array.<string>} states + * @param {!Array.<string>} titles + * @param {string} initialState + * @param {!WebInspector.Setting} currentStateSetting + * @param {!WebInspector.Setting} lastStateSetting + * @param {?function(string)} stateChangedCallback + */ +WebInspector.StatusBarStatesSettingButton = function(className, states, titles, initialState, currentStateSetting, lastStateSetting, stateChangedCallback) +{ + WebInspector.StatusBarButton.call(this, "", className, states.length); + + var onClickBound = this._onClick.bind(this); + this.addEventListener("click", onClickBound, this); + + this._states = states; + this._buttons = []; + for (var index = 0; index < states.length; index++) { + var button = new WebInspector.StatusBarButton(titles[index], className, states.length); + button.state = this._states[index]; + button.addEventListener("click", onClickBound, this); + this._buttons.push(button); + } + + this._currentStateSetting = currentStateSetting; + this._lastStateSetting = lastStateSetting; + this._stateChangedCallback = stateChangedCallback; + this.setLongClickOptionsEnabled(this._createOptions.bind(this)); + + this._currentState = null; + this.toggleState(initialState); +} + +WebInspector.StatusBarStatesSettingButton.prototype = { + /** + * @param {!WebInspector.Event} e + */ + _onClick: function(e) + { + this.toggleState(e.target.state); + }, + + /** + * @param {string} state + */ + toggleState: function(state) + { + if (this._currentState === state) + return; + + if (this._currentState) + this._lastStateSetting.set(this._currentState); + this._currentState = state; + this._currentStateSetting.set(this._currentState); + + if (this._stateChangedCallback) + this._stateChangedCallback(state); + + var defaultState = this._defaultState(); + this.state = defaultState; + this.title = this._buttons[this._states.indexOf(defaultState)].title; + }, + + /** + * @return {string} + */ + _defaultState: function() + { + var lastState = this._lastStateSetting.get(); + if (lastState && this._states.indexOf(lastState) >= 0 && lastState != this._currentState) + return lastState; + if (this._states.length > 1 && this._currentState === this._states[0]) + return this._states[1]; + return this._states[0]; + }, + + /** + * @return {!Array.<!WebInspector.StatusBarButton>} + */ + _createOptions: function() + { + var options = []; + for (var index = 0; index < this._states.length; index++) { + if (this._states[index] !== this.state && this._states[index] !== this._currentState) + options.push(this._buttons[index]); + } + return options; + }, + + __proto__: WebInspector.StatusBarButton.prototype +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/SuggestBox.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/SuggestBox.js new file mode 100644 index 00000000000..2f805dfc080 --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/SuggestBox.js @@ -0,0 +1,443 @@ +/* + * Copyright (C) 2013 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER 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. + */ + +/** + * @interface + */ +WebInspector.SuggestBoxDelegate = function() +{ +} + +WebInspector.SuggestBoxDelegate.prototype = { + /** + * @param {string} suggestion + * @param {boolean=} isIntermediateSuggestion + */ + applySuggestion: function(suggestion, isIntermediateSuggestion) { }, + + /** + * acceptSuggestion will be always called after call to applySuggestion with isIntermediateSuggestion being equal to false. + */ + acceptSuggestion: function() { }, +} + +/** + * @constructor + * @param {!WebInspector.SuggestBoxDelegate} suggestBoxDelegate + * @param {number=} maxItemsHeight + */ +WebInspector.SuggestBox = function(suggestBoxDelegate, maxItemsHeight) +{ + this._suggestBoxDelegate = suggestBoxDelegate; + this._length = 0; + this._selectedIndex = -1; + this._selectedElement = null; + this._maxItemsHeight = maxItemsHeight; + this._bodyElement = document.body; + this._maybeHideBound = this._maybeHide.bind(this); + this._element = document.createElement("div"); + this._element.className = "suggest-box"; + this._element.addEventListener("mousedown", this._onBoxMouseDown.bind(this), true); +} + +WebInspector.SuggestBox.prototype = { + /** + * @return {boolean} + */ + visible: function() + { + return !!this._element.parentElement; + }, + + /** + * @param {!AnchorBox} anchorBox + */ + setPosition: function(anchorBox) + { + this._updateBoxPosition(anchorBox); + }, + + /** + * @param {!AnchorBox} anchorBox + */ + _updateBoxPosition: function(anchorBox) + { + console.assert(this._overlay); + if (this._lastAnchorBox && this._lastAnchorBox.equals(anchorBox)) + return; + this._lastAnchorBox = anchorBox; + + // Position relative to main DevTools element. + var container = WebInspector.Dialog.modalHostView().element; + anchorBox = anchorBox.relativeToElement(container); + var totalWidth = container.offsetWidth; + var totalHeight = container.offsetHeight; + var aboveHeight = anchorBox.y; + var underHeight = totalHeight - anchorBox.y - anchorBox.height; + + var rowHeight = 17; + const spacer = 6; + + var maxHeight = this._maxItemsHeight ? this._maxItemsHeight * rowHeight : Math.max(underHeight, aboveHeight) - spacer; + var under = underHeight >= aboveHeight; + this._leftSpacerElement.style.flexBasis = anchorBox.x + "px"; + + this._overlay.element.classList.toggle("under-anchor", under); + + if (under) { + this._bottomSpacerElement.style.flexBasis = "auto"; + this._topSpacerElement.style.flexBasis = (anchorBox.y + anchorBox.height) + "px"; + } else { + this._bottomSpacerElement.style.flexBasis = (totalHeight - anchorBox.y) + "px"; + this._topSpacerElement.style.flexBasis = "auto"; + } + this._element.style.maxHeight = maxHeight + "px"; + }, + + /** + * @param {?Event} event + */ + _onBoxMouseDown: function(event) + { + if (this._hideTimeoutId) { + window.clearTimeout(this._hideTimeoutId); + delete this._hideTimeoutId; + } + event.preventDefault(); + }, + + _maybeHide: function() + { + if (!this._hideTimeoutId) + this._hideTimeoutId = window.setTimeout(this.hide.bind(this), 0); + }, + + _show: function() + { + if (this.visible()) + return; + this._overlay = new WebInspector.SuggestBox.Overlay(); + this._bodyElement.addEventListener("mousedown", this._maybeHideBound, true); + + this._leftSpacerElement = this._overlay.element.createChild("div", "suggest-box-left-spacer"); + this._horizontalElement = this._overlay.element.createChild("div", "suggest-box-horizontal"); + this._topSpacerElement = this._horizontalElement.createChild("div", "suggest-box-top-spacer"); + this._horizontalElement.appendChild(this._element); + this._bottomSpacerElement = this._horizontalElement.createChild("div", "suggest-box-bottom-spacer"); + }, + + hide: function() + { + if (!this.visible()) + return; + + this._bodyElement.removeEventListener("mousedown", this._maybeHideBound, true); + this._element.remove(); + this._overlay.dispose(); + delete this._overlay; + delete this._selectedElement; + this._selectedIndex = -1; + delete this._lastAnchorBox; + }, + + removeFromElement: function() + { + this.hide(); + }, + + /** + * @param {boolean=} isIntermediateSuggestion + */ + _applySuggestion: function(isIntermediateSuggestion) + { + if (!this.visible() || !this._selectedElement) + return false; + + var suggestion = this._selectedElement.textContent; + if (!suggestion) + return false; + + this._suggestBoxDelegate.applySuggestion(suggestion, isIntermediateSuggestion); + return true; + }, + + /** + * @return {boolean} + */ + acceptSuggestion: function() + { + var result = this._applySuggestion(); + this.hide(); + if (!result) + return false; + + this._suggestBoxDelegate.acceptSuggestion(); + + return true; + }, + + /** + * @param {number} shift + * @param {boolean=} isCircular + * @return {boolean} is changed + */ + _selectClosest: function(shift, isCircular) + { + if (!this._length) + return false; + + if (this._selectedIndex === -1 && shift < 0) + shift += 1; + + var index = this._selectedIndex + shift; + + if (isCircular) + index = (this._length + index) % this._length; + else + index = Number.constrain(index, 0, this._length - 1); + + this._selectItem(index, true); + this._applySuggestion(true); + return true; + }, + + /** + * @param {?Event} event + */ + _onItemMouseDown: function(event) + { + this._selectedElement = event.currentTarget; + this.acceptSuggestion(); + event.consume(true); + }, + + /** + * @param {string} prefix + * @param {string} text + */ + _createItemElement: function(prefix, text) + { + var element = document.createElement("div"); + element.className = "suggest-box-content-item source-code"; + element.tabIndex = -1; + if (prefix && prefix.length && !text.indexOf(prefix)) { + var prefixElement = element.createChild("span", "prefix"); + prefixElement.textContent = prefix; + var suffixElement = element.createChild("span", "suffix"); + suffixElement.textContent = text.substring(prefix.length); + } else { + var suffixElement = element.createChild("span", "suffix"); + suffixElement.textContent = text; + } + element.createChild("span", "spacer"); + element.addEventListener("mousedown", this._onItemMouseDown.bind(this), false); + return element; + }, + + /** + * @param {!Array.<string>} items + * @param {string} userEnteredText + */ + _updateItems: function(items, userEnteredText) + { + this._length = items.length; + this._element.removeChildren(); + delete this._selectedElement; + + for (var i = 0; i < items.length; ++i) { + var item = items[i]; + var currentItemElement = this._createItemElement(userEnteredText, item); + this._element.appendChild(currentItemElement); + } + }, + + /** + * @param {number} index + * @param {boolean} scrollIntoView + */ + _selectItem: function(index, scrollIntoView) + { + if (this._selectedElement) + this._selectedElement.classList.remove("selected"); + + this._selectedIndex = index; + if (index < 0) + return; + + this._selectedElement = this._element.children[index]; + this._selectedElement.classList.add("selected"); + + if (scrollIntoView) + this._selectedElement.scrollIntoViewIfNeeded(false); + }, + + /** + * @param {!Array.<string>} completions + * @param {boolean} canShowForSingleItem + * @param {string} userEnteredText + */ + _canShowBox: function(completions, canShowForSingleItem, userEnteredText) + { + if (!completions || !completions.length) + return false; + + if (completions.length > 1) + return true; + + // Do not show a single suggestion if it is the same as user-entered prefix, even if allowed to show single-item suggest boxes. + return canShowForSingleItem && completions[0] !== userEnteredText; + }, + + _ensureRowCountPerViewport: function() + { + if (this._rowCountPerViewport) + return; + if (!this._element.firstChild) + return; + + this._rowCountPerViewport = Math.floor(this._element.offsetHeight / this._element.firstChild.offsetHeight); + }, + + /** + * @param {!AnchorBox} anchorBox + * @param {!Array.<string>} completions + * @param {number} selectedIndex + * @param {boolean} canShowForSingleItem + * @param {string} userEnteredText + */ + updateSuggestions: function(anchorBox, completions, selectedIndex, canShowForSingleItem, userEnteredText) + { + if (this._canShowBox(completions, canShowForSingleItem, userEnteredText)) { + this._updateItems(completions, userEnteredText); + this._show(); + this._updateBoxPosition(anchorBox); + this._selectItem(selectedIndex, selectedIndex > 0); + delete this._rowCountPerViewport; + } else + this.hide(); + }, + + /** + * @param {!KeyboardEvent} event + * @return {boolean} + */ + keyPressed: function(event) + { + switch (event.keyIdentifier) { + case "Up": + return this.upKeyPressed(); + case "Down": + return this.downKeyPressed(); + case "PageUp": + return this.pageUpKeyPressed(); + case "PageDown": + return this.pageDownKeyPressed(); + case "Enter": + return this.enterKeyPressed(); + } + return false; + }, + + /** + * @return {boolean} + */ + upKeyPressed: function() + { + return this._selectClosest(-1, true); + }, + + /** + * @return {boolean} + */ + downKeyPressed: function() + { + return this._selectClosest(1, true); + }, + + /** + * @return {boolean} + */ + pageUpKeyPressed: function() + { + this._ensureRowCountPerViewport(); + return this._selectClosest(-this._rowCountPerViewport, false); + }, + + /** + * @return {boolean} + */ + pageDownKeyPressed: function() + { + this._ensureRowCountPerViewport(); + return this._selectClosest(this._rowCountPerViewport, false); + }, + + /** + * @return {boolean} + */ + enterKeyPressed: function() + { + var hasSelectedItem = !!this._selectedElement; + this.acceptSuggestion(); + + // Report the event as non-handled if there is no selected item, + // to commit the input or handle it otherwise. + return hasSelectedItem; + } +} + +/** + * @constructor + */ +WebInspector.SuggestBox.Overlay = function() +{ + this.element = document.createElement("div"); + this.element.classList.add("suggest-box-overlay"); + this._resize(); + document.body.appendChild(this.element); +} + +WebInspector.SuggestBox.Overlay.prototype = { + _resize: function() + { + var container = WebInspector.Dialog.modalHostView().element; + var containerBox = container.boxInWindow(container.ownerDocument.defaultView); + + this.element.style.left = containerBox.x + "px"; + this.element.style.top = containerBox.y + "px"; + this.element.style.height = containerBox.height + "px"; + this.element.style.width = containerBox.width + "px"; + }, + + dispose: function() + { + this.element.remove(); + } +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/TabbedPane.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/TabbedPane.js new file mode 100644 index 00000000000..9d41fcb8094 --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/TabbedPane.js @@ -0,0 +1,1180 @@ +/* + * Copyright (C) 2010 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER 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. + */ + +/** + * @extends {WebInspector.VBox} + * @constructor + */ +WebInspector.TabbedPane = function() +{ + WebInspector.VBox.call(this); + this.element.classList.add("tabbed-pane"); + this.element.tabIndex = -1; + this._headerElement = this.element.createChild("div", "tabbed-pane-header"); + this._headerContentsElement = this._headerElement.createChild("div", "tabbed-pane-header-contents"); + this._tabsElement = this._headerContentsElement.createChild("div", "tabbed-pane-header-tabs"); + this._contentElement = this.element.createChild("div", "tabbed-pane-content scroll-target"); + /** @type {!Array.<!WebInspector.TabbedPaneTab>} */ + this._tabs = []; + /** @type {!Array.<!WebInspector.TabbedPaneTab>} */ + this._tabsHistory = []; + /** @type {!Object.<string, !WebInspector.TabbedPaneTab>} */ + this._tabsById = {}; + + this._dropDownButton = this._createDropDownButton(); + WebInspector.zoomManager.addEventListener(WebInspector.ZoomManager.Events.ZoomChanged, this._zoomChanged, this); +} + +WebInspector.TabbedPane.EventTypes = { + TabSelected: "TabSelected", + TabClosed: "TabClosed" +} + +WebInspector.TabbedPane.prototype = { + /** + * @return {?WebInspector.View} + */ + get visibleView() + { + return this._currentTab ? this._currentTab.view : null; + }, + + /** + * @return {!Array.<!WebInspector.View>} + */ + tabViews: function() + { + /** + * @param {!WebInspector.TabbedPaneTab} tab + * @return {!WebInspector.View} + */ + function tabToView(tab) + { + return tab.view; + } + return this._tabs.map(tabToView); + }, + + /** + * @return {?string} + */ + get selectedTabId() + { + return this._currentTab ? this._currentTab.id : null; + }, + + /** + * @type {boolean} shrinkableTabs + */ + set shrinkableTabs(shrinkableTabs) + { + this._shrinkableTabs = shrinkableTabs; + }, + + /** + * @type {boolean} verticalTabLayout + */ + set verticalTabLayout(verticalTabLayout) + { + this._verticalTabLayout = verticalTabLayout; + this.invalidateConstraints(); + }, + + /** + * @type {boolean} closeableTabs + */ + set closeableTabs(closeableTabs) + { + this._closeableTabs = closeableTabs; + }, + + /** + * @param {boolean} retainTabOrder + * @param {function(string, string):number=} tabOrderComparator + */ + setRetainTabOrder: function(retainTabOrder, tabOrderComparator) + { + this._retainTabOrder = retainTabOrder; + this._tabOrderComparator = tabOrderComparator; + }, + + /** + * @return {?Element} + */ + defaultFocusedElement: function() + { + return this.visibleView ? this.visibleView.defaultFocusedElement() : null; + }, + + focus: function() + { + if (this.visibleView) + this.visibleView.focus(); + else + this.element.focus(); + }, + + /** + * @return {!Element} + */ + headerElement: function() + { + return this._headerElement; + }, + + /** + * @param {string} id + * @return {boolean} + */ + isTabCloseable: function(id) + { + var tab = this._tabsById[id]; + return tab ? tab.isCloseable() : false; + }, + + /** + * @param {!WebInspector.TabbedPaneTabDelegate} delegate + */ + setTabDelegate: function(delegate) + { + var tabs = this._tabs.slice(); + for (var i = 0; i < tabs.length; ++i) + tabs[i].setDelegate(delegate); + this._delegate = delegate; + }, + + /** + * @param {string} id + * @param {string} tabTitle + * @param {!WebInspector.View} view + * @param {string=} tabTooltip + * @param {boolean=} userGesture + * @param {boolean=} isCloseable + */ + appendTab: function(id, tabTitle, view, tabTooltip, userGesture, isCloseable) + { + isCloseable = typeof isCloseable === "boolean" ? isCloseable : this._closeableTabs; + var tab = new WebInspector.TabbedPaneTab(this, id, tabTitle, isCloseable, view, tabTooltip); + tab.setDelegate(this._delegate); + this._tabsById[id] = tab; + + /** + * @param {!WebInspector.TabbedPaneTab} tab1 + * @param {!WebInspector.TabbedPaneTab} tab2 + * @this {WebInspector.TabbedPane} + * @return {number} + */ + function comparator(tab1, tab2) + { + return this._tabOrderComparator(tab1.id, tab2.id); + } + + if (this._retainTabOrder && this._tabOrderComparator) + this._tabs.splice(insertionIndexForObjectInListSortedByFunction(tab, this._tabs, comparator.bind(this)), 0, tab); + else + this._tabs.push(tab); + + this._tabsHistory.push(tab); + + if (this._tabsHistory[0] === tab && this.isShowing()) + this.selectTab(tab.id, userGesture); + + this._updateTabElements(); + }, + + /** + * @param {string} id + * @param {boolean=} userGesture + */ + closeTab: function(id, userGesture) + { + this.closeTabs([id], userGesture); + }, + + /** + * @param {!Array.<string>} ids + * @param {boolean=} userGesture + */ + closeTabs: function(ids, userGesture) + { + var focused = this.hasFocus(); + for (var i = 0; i < ids.length; ++i) + this._innerCloseTab(ids[i], userGesture); + this._updateTabElements(); + if (this._tabsHistory.length) + this.selectTab(this._tabsHistory[0].id, false); + if (focused) + this.focus(); + }, + + /** + * @param {string} id + * @param {boolean=} userGesture + */ + _innerCloseTab: function(id, userGesture) + { + if (!this._tabsById[id]) + return; + if (userGesture && !this._tabsById[id]._closeable) + return; + if (this._currentTab && this._currentTab.id === id) + this._hideCurrentTab(); + + var tab = this._tabsById[id]; + delete this._tabsById[id]; + + this._tabsHistory.splice(this._tabsHistory.indexOf(tab), 1); + this._tabs.splice(this._tabs.indexOf(tab), 1); + if (tab._shown) + this._hideTabElement(tab); + + var eventData = { tabId: id, view: tab.view, isUserGesture: userGesture }; + this.dispatchEventToListeners(WebInspector.TabbedPane.EventTypes.TabClosed, eventData); + return true; + }, + + /** + * @param {string} tabId + * @return {boolean} + */ + hasTab: function(tabId) + { + return !!this._tabsById[tabId]; + }, + + /** + * @return {!Array.<string>} + */ + allTabs: function() + { + var result = []; + var tabs = this._tabs.slice(); + for (var i = 0; i < tabs.length; ++i) + result.push(tabs[i].id); + return result; + }, + + /** + * @param {string} id + * @return {!Array.<string>} + */ + otherTabs: function(id) + { + var result = []; + var tabs = this._tabs.slice(); + for (var i = 0; i < tabs.length; ++i) { + if (tabs[i].id !== id) + result.push(tabs[i].id); + } + return result; + }, + + /** + * @param {string} id + * @param {boolean=} userGesture + * @return {boolean} + */ + selectTab: function(id, userGesture) + { + var focused = this.hasFocus(); + var tab = this._tabsById[id]; + if (!tab) + return false; + if (this._currentTab && this._currentTab.id === id) + return true; + + this._hideCurrentTab(); + this._showTab(tab); + this._currentTab = tab; + + this._tabsHistory.splice(this._tabsHistory.indexOf(tab), 1); + this._tabsHistory.splice(0, 0, tab); + + this._updateTabElements(); + if (focused) + this.focus(); + + var eventData = { tabId: id, view: tab.view, isUserGesture: userGesture }; + this.dispatchEventToListeners(WebInspector.TabbedPane.EventTypes.TabSelected, eventData); + return true; + }, + + /** + * @param {number} tabsCount + * @return {!Array.<string>} + */ + lastOpenedTabIds: function(tabsCount) + { + function tabToTabId(tab) { + return tab.id; + } + + return this._tabsHistory.slice(0, tabsCount).map(tabToTabId); + }, + + /** + * @param {string} id + * @param {string} iconClass + * @param {string=} iconTooltip + */ + setTabIcon: function(id, iconClass, iconTooltip) + { + var tab = this._tabsById[id]; + if (tab._setIconClass(iconClass, iconTooltip)) + this._updateTabElements(); + }, + + /** + * @param {!WebInspector.Event} event + */ + _zoomChanged: function(event) + { + for (var i = 0; i < this._tabs.length; ++i) + delete this._tabs[i]._measuredWidth; + if (this.isShowing()) + this._updateTabElements(); + }, + + /** + * @param {string} id + * @param {string} tabTitle + */ + changeTabTitle: function(id, tabTitle) + { + var tab = this._tabsById[id]; + if (tab.title === tabTitle) + return; + tab.title = tabTitle; + this._updateTabElements(); + }, + + /** + * @param {string} id + * @param {!WebInspector.View} view + */ + changeTabView: function(id, view) + { + var tab = this._tabsById[id]; + if (this._currentTab && this._currentTab.id === tab.id) { + if (tab.view !== view) + this._hideTab(tab); + tab.view = view; + this._showTab(tab); + } else + tab.view = view; + }, + + /** + * @param {string} id + * @param {string=} tabTooltip + */ + changeTabTooltip: function(id, tabTooltip) + { + var tab = this._tabsById[id]; + tab.tooltip = tabTooltip; + }, + + onResize: function() + { + this._updateTabElements(); + }, + + headerResized: function() + { + this._updateTabElements(); + }, + + wasShown: function() + { + var effectiveTab = this._currentTab || this._tabsHistory[0]; + if (effectiveTab) + this.selectTab(effectiveTab.id); + }, + + /** + * @return {!Constraints} + */ + calculateConstraints: function() + { + var constraints = WebInspector.VBox.prototype.calculateConstraints.call(this); + var minContentConstraints = new Constraints(new Size(0, 0), new Size(50, 50)); + constraints = constraints.widthToMax(minContentConstraints).heightToMax(minContentConstraints); + if (this._verticalTabLayout) + constraints = constraints.addWidth(new Constraints(new Size(this._headerElement.offsetWidth, 0))); + else + constraints = constraints.addHeight(new Constraints(new Size(0, this._headerElement.offsetHeight))); + return constraints; + }, + + _updateTabElements: function() + { + WebInspector.invokeOnceAfterBatchUpdate(this, this._innerUpdateTabElements); + }, + + /** + * @param {string} text + */ + setPlaceholderText: function(text) + { + this._noTabsMessage = text; + }, + + _innerUpdateTabElements: function() + { + if (!this.isShowing()) + return; + + if (!this._tabs.length) { + this._contentElement.classList.add("has-no-tabs"); + if (this._noTabsMessage && !this._noTabsMessageElement) { + this._noTabsMessageElement = this._contentElement.createChild("div", "tabbed-pane-placeholder fill"); + this._noTabsMessageElement.textContent = this._noTabsMessage; + } + } else { + this._contentElement.classList.remove("has-no-tabs"); + if (this._noTabsMessageElement) { + this._noTabsMessageElement.remove(); + delete this._noTabsMessageElement; + } + } + + if (!this._measuredDropDownButtonWidth) + this._measureDropDownButton(); + + this._updateWidths(); + this._updateTabsDropDown(); + }, + + /** + * @param {number} index + * @param {!WebInspector.TabbedPaneTab} tab + */ + _showTabElement: function(index, tab) + { + if (index >= this._tabsElement.children.length) + this._tabsElement.appendChild(tab.tabElement); + else + this._tabsElement.insertBefore(tab.tabElement, this._tabsElement.children[index]); + tab._shown = true; + }, + + /** + * @param {!WebInspector.TabbedPaneTab} tab + */ + _hideTabElement: function(tab) + { + this._tabsElement.removeChild(tab.tabElement); + tab._shown = false; + }, + + _createDropDownButton: function() + { + var dropDownContainer = document.createElement("div"); + dropDownContainer.classList.add("tabbed-pane-header-tabs-drop-down-container"); + var dropDownButton = dropDownContainer.createChild("div", "tabbed-pane-header-tabs-drop-down"); + dropDownButton.appendChild(document.createTextNode("\u00bb")); + + this._dropDownMenu = new WebInspector.DropDownMenu(); + this._dropDownMenu.addEventListener(WebInspector.DropDownMenu.Events.ItemSelected, this._dropDownMenuItemSelected, this); + dropDownButton.appendChild(this._dropDownMenu.element); + + return dropDownContainer; + }, + + /** + * @param {!WebInspector.Event} event + */ + _dropDownMenuItemSelected: function(event) + { + var tabId = /** @type {string} */ (event.data); + this.selectTab(tabId, true); + }, + + _totalWidth: function() + { + return this._headerContentsElement.getBoundingClientRect().width; + }, + + _updateTabsDropDown: function() + { + var tabsToShowIndexes = this._tabsToShowIndexes(this._tabs, this._tabsHistory, this._totalWidth(), this._measuredDropDownButtonWidth); + + for (var i = 0; i < this._tabs.length; ++i) { + if (this._tabs[i]._shown && tabsToShowIndexes.indexOf(i) === -1) + this._hideTabElement(this._tabs[i]); + } + for (var i = 0; i < tabsToShowIndexes.length; ++i) { + var tab = this._tabs[tabsToShowIndexes[i]]; + if (!tab._shown) + this._showTabElement(i, tab); + } + + this._populateDropDownFromIndex(); + }, + + _populateDropDownFromIndex: function() + { + if (this._dropDownButton.parentElement) + this._headerContentsElement.removeChild(this._dropDownButton); + + this._dropDownMenu.clear(); + + var tabsToShow = []; + for (var i = 0; i < this._tabs.length; ++i) { + if (!this._tabs[i]._shown) + tabsToShow.push(this._tabs[i]); + continue; + } + + function compareFunction(tab1, tab2) + { + return tab1.title.localeCompare(tab2.title); + } + if (!this._retainTabOrder) + tabsToShow.sort(compareFunction); + + var selectedId = null; + for (var i = 0; i < tabsToShow.length; ++i) { + var tab = tabsToShow[i]; + this._dropDownMenu.addItem(tab.id, tab.title); + if (this._tabsHistory[0] === tab) + selectedId = tab.id; + } + if (tabsToShow.length) { + this._headerContentsElement.appendChild(this._dropDownButton); + this._dropDownMenu.selectItem(selectedId); + } + }, + + _measureDropDownButton: function() + { + this._dropDownButton.classList.add("measuring"); + this._headerContentsElement.appendChild(this._dropDownButton); + this._measuredDropDownButtonWidth = this._dropDownButton.getBoundingClientRect().width; + this._headerContentsElement.removeChild(this._dropDownButton); + this._dropDownButton.classList.remove("measuring"); + }, + + _updateWidths: function() + { + var measuredWidths = this._measureWidths(); + var maxWidth = this._shrinkableTabs ? this._calculateMaxWidth(measuredWidths.slice(), this._totalWidth()) : Number.MAX_VALUE; + + var i = 0; + for (var tabId in this._tabs) { + var tab = this._tabs[tabId]; + tab.setWidth(this._verticalTabLayout ? -1 : Math.min(maxWidth, measuredWidths[i++])); + } + }, + + _measureWidths: function() + { + // Add all elements to measure into this._tabsElement + this._tabsElement.style.setProperty("width", "2000px"); + var measuringTabElements = []; + for (var tabId in this._tabs) { + var tab = this._tabs[tabId]; + if (typeof tab._measuredWidth === "number") + continue; + var measuringTabElement = tab._createTabElement(true); + measuringTabElement.__tab = tab; + measuringTabElements.push(measuringTabElement); + this._tabsElement.appendChild(measuringTabElement); + } + + // Perform measurement + for (var i = 0; i < measuringTabElements.length; ++i) + measuringTabElements[i].__tab._measuredWidth = measuringTabElements[i].getBoundingClientRect().width; + + // Nuke elements from the UI + for (var i = 0; i < measuringTabElements.length; ++i) + measuringTabElements[i].remove(); + + // Combine the results. + var measuredWidths = []; + for (var tabId in this._tabs) + measuredWidths.push(this._tabs[tabId]._measuredWidth); + this._tabsElement.style.removeProperty("width"); + + return measuredWidths; + }, + + /** + * @param {!Array.<number>} measuredWidths + * @param {number} totalWidth + */ + _calculateMaxWidth: function(measuredWidths, totalWidth) + { + if (!measuredWidths.length) + return 0; + + measuredWidths.sort(function(x, y) { return x - y }); + + var totalMeasuredWidth = 0; + for (var i = 0; i < measuredWidths.length; ++i) + totalMeasuredWidth += measuredWidths[i]; + + if (totalWidth >= totalMeasuredWidth) + return measuredWidths[measuredWidths.length - 1]; + + var totalExtraWidth = 0; + for (var i = measuredWidths.length - 1; i > 0; --i) { + var extraWidth = measuredWidths[i] - measuredWidths[i - 1]; + totalExtraWidth += (measuredWidths.length - i) * extraWidth; + + if (totalWidth + totalExtraWidth >= totalMeasuredWidth) + return measuredWidths[i - 1] + (totalWidth + totalExtraWidth - totalMeasuredWidth) / (measuredWidths.length - i); + } + + return totalWidth / measuredWidths.length; + }, + + /** + * @param {!Array.<!WebInspector.TabbedPaneTab>} tabsOrdered + * @param {!Array.<!WebInspector.TabbedPaneTab>} tabsHistory + * @param {number} totalWidth + * @param {number} measuredDropDownButtonWidth + * @return {!Array.<number>} + */ + _tabsToShowIndexes: function(tabsOrdered, tabsHistory, totalWidth, measuredDropDownButtonWidth) + { + var tabsToShowIndexes = []; + + var totalTabsWidth = 0; + var tabCount = tabsOrdered.length; + for (var i = 0; i < tabCount; ++i) { + var tab = this._retainTabOrder ? tabsOrdered[i] : tabsHistory[i]; + totalTabsWidth += tab.width(); + var minimalRequiredWidth = totalTabsWidth; + if (i !== tabCount - 1) + minimalRequiredWidth += measuredDropDownButtonWidth; + if (!this._verticalTabLayout && minimalRequiredWidth > totalWidth) + break; + tabsToShowIndexes.push(tabsOrdered.indexOf(tab)); + } + + tabsToShowIndexes.sort(function(x, y) { return x - y }); + + return tabsToShowIndexes; + }, + + _hideCurrentTab: function() + { + if (!this._currentTab) + return; + + this._hideTab(this._currentTab); + delete this._currentTab; + }, + + /** + * @param {!WebInspector.TabbedPaneTab} tab + */ + _showTab: function(tab) + { + tab.tabElement.classList.add("selected"); + tab.view.show(this._contentElement); + }, + + /** + * @param {!WebInspector.TabbedPaneTab} tab + */ + _hideTab: function(tab) + { + tab.tabElement.classList.remove("selected"); + tab.view.detach(); + }, + + /** + * @return {!Array.<!Element>} + */ + elementsToRestoreScrollPositionsFor: function() + { + return [ this._contentElement ]; + }, + + /** + * @param {!WebInspector.TabbedPaneTab} tab + * @param {number} index + */ + _insertBefore: function(tab, index) + { + this._tabsElement.insertBefore(tab._tabElement, this._tabsElement.childNodes[index]); + var oldIndex = this._tabs.indexOf(tab); + this._tabs.splice(oldIndex, 1); + if (oldIndex < index) + --index; + this._tabs.splice(index, 0, tab); + }, + + __proto__: WebInspector.VBox.prototype +} + +/** + * @constructor + * @param {!WebInspector.TabbedPane} tabbedPane + * @param {string} id + * @param {string} title + * @param {boolean} closeable + * @param {!WebInspector.View} view + * @param {string=} tooltip + */ +WebInspector.TabbedPaneTab = function(tabbedPane, id, title, closeable, view, tooltip) +{ + this._closeable = closeable; + this._tabbedPane = tabbedPane; + this._id = id; + this._title = title; + this._tooltip = tooltip; + this._view = view; + this._shown = false; + /** @type {number} */ this._measuredWidth; + /** @type {!Element|undefined} */ this._tabElement; +} + +WebInspector.TabbedPaneTab.prototype = { + /** + * @return {string} + */ + get id() + { + return this._id; + }, + + /** + * @return {string} + */ + get title() + { + return this._title; + }, + + set title(title) + { + if (title === this._title) + return; + this._title = title; + if (this._titleElement) + this._titleElement.textContent = title; + delete this._measuredWidth; + }, + + /** + * @return {string} + */ + iconClass: function() + { + return this._iconClass; + }, + + /** + * @return {boolean} + */ + isCloseable: function() + { + return this._closeable; + }, + + /** + * @param {string} iconClass + * @param {string} iconTooltip + * @return {boolean} + */ + _setIconClass: function(iconClass, iconTooltip) + { + if (iconClass === this._iconClass && iconTooltip === this._iconTooltip) + return false; + this._iconClass = iconClass; + this._iconTooltip = iconTooltip; + if (this._iconElement) + this._iconElement.remove(); + if (this._iconClass && this._tabElement) + this._iconElement = this._createIconElement(this._tabElement, this._titleElement); + delete this._measuredWidth; + return true; + }, + + /** + * @return {!WebInspector.View} + */ + get view() + { + return this._view; + }, + + set view(view) + { + this._view = view; + }, + + /** + * @return {string|undefined} + */ + get tooltip() + { + return this._tooltip; + }, + + set tooltip(tooltip) + { + this._tooltip = tooltip; + if (this._titleElement) + this._titleElement.title = tooltip || ""; + }, + + /** + * @return {!Element} + */ + get tabElement() + { + if (!this._tabElement) + this._tabElement = this._createTabElement(false); + + return this._tabElement; + }, + + /** + * @return {number} + */ + width: function() + { + return this._width; + }, + + /** + * @param {number} width + */ + setWidth: function(width) + { + this.tabElement.style.width = width === -1 ? "" : (width + "px"); + this._width = width; + }, + + /** + * @param {!WebInspector.TabbedPaneTabDelegate} delegate + */ + setDelegate: function(delegate) + { + this._delegate = delegate; + }, + + _createIconElement: function(tabElement, titleElement) + { + var iconElement = document.createElement("span"); + iconElement.className = "tabbed-pane-header-tab-icon " + this._iconClass; + if (this._iconTooltip) + iconElement.title = this._iconTooltip; + tabElement.insertBefore(iconElement, titleElement); + return iconElement; + }, + + /** + * @param {boolean} measuring + * @return {!Element} + */ + _createTabElement: function(measuring) + { + var tabElement = document.createElement("div"); + tabElement.classList.add("tabbed-pane-header-tab"); + tabElement.id = "tab-" + this._id; + tabElement.tabIndex = -1; + tabElement.selectTabForTest = this._tabbedPane.selectTab.bind(this._tabbedPane, this.id, true); + + var titleElement = tabElement.createChild("span", "tabbed-pane-header-tab-title"); + titleElement.textContent = this.title; + titleElement.title = this.tooltip || ""; + if (this._iconClass) + this._createIconElement(tabElement, titleElement); + if (!measuring) + this._titleElement = titleElement; + + if (this._closeable) + tabElement.createChild("div", "close-button-gray"); + + if (measuring) { + tabElement.classList.add("measuring"); + } else { + tabElement.addEventListener("click", this._tabClicked.bind(this), false); + tabElement.addEventListener("mousedown", this._tabMouseDown.bind(this), false); + tabElement.addEventListener("mouseup", this._tabMouseUp.bind(this), false); + + if (this._closeable) { + tabElement.addEventListener("contextmenu", this._tabContextMenu.bind(this), false); + WebInspector.installDragHandle(tabElement, this._startTabDragging.bind(this), this._tabDragging.bind(this), this._endTabDragging.bind(this), "pointer"); + } + } + + return tabElement; + }, + + /** + * @param {?Event} event + */ + _tabClicked: function(event) + { + var middleButton = event.button === 1; + var shouldClose = this._closeable && (middleButton || event.target.classList.contains("close-button-gray")); + if (!shouldClose) { + this._tabbedPane.focus(); + return; + } + this._closeTabs([this.id]); + event.consume(true); + }, + + /** + * @param {?Event} event + */ + _tabMouseDown: function(event) + { + if (event.target.classList.contains("close-button-gray") || event.button === 1) + return; + this._tabbedPane.selectTab(this.id, true); + }, + + /** + * @param {?Event} event + */ + _tabMouseUp: function(event) + { + // This is needed to prevent middle-click pasting on linux when tabs are clicked. + if (event.button === 1) + event.consume(true); + }, + + /** + * @param {!Array.<string>} ids + */ + _closeTabs: function(ids) + { + if (this._delegate) { + this._delegate.closeTabs(this._tabbedPane, ids); + return; + } + this._tabbedPane.closeTabs(ids, true); + }, + + _tabContextMenu: function(event) + { + /** + * @this {WebInspector.TabbedPaneTab} + */ + function close() + { + this._closeTabs([this.id]); + } + + /** + * @this {WebInspector.TabbedPaneTab} + */ + function closeOthers() + { + this._closeTabs(this._tabbedPane.otherTabs(this.id)); + } + + /** + * @this {WebInspector.TabbedPaneTab} + */ + function closeAll() + { + this._closeTabs(this._tabbedPane.allTabs()); + } + + var contextMenu = new WebInspector.ContextMenu(event); + contextMenu.appendItem(WebInspector.UIString("Close"), close.bind(this)); + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Close others" : "Close Others"), closeOthers.bind(this)); + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Close all" : "Close All"), closeAll.bind(this)); + contextMenu.show(); + }, + + /** + * @param {!Event} event + * @return {boolean} + */ + _startTabDragging: function(event) + { + if (event.target.classList.contains("close-button-gray")) + return false; + this._dragStartX = event.pageX; + return true; + }, + + /** + * @param {!Event} event + */ + _tabDragging: function(event) + { + var tabElements = this._tabbedPane._tabsElement.childNodes; + for (var i = 0; i < tabElements.length; ++i) { + var tabElement = tabElements[i]; + if (tabElement === this._tabElement) + continue; + + var intersects = tabElement.offsetLeft + tabElement.clientWidth > this._tabElement.offsetLeft && + this._tabElement.offsetLeft + this._tabElement.clientWidth > tabElement.offsetLeft; + if (!intersects) + continue; + + if (Math.abs(event.pageX - this._dragStartX) < tabElement.clientWidth / 2 + 5) + break; + + if (event.pageX - this._dragStartX > 0) { + tabElement = tabElement.nextSibling; + ++i; + } + + var oldOffsetLeft = this._tabElement.offsetLeft; + this._tabbedPane._insertBefore(this, i); + this._dragStartX += this._tabElement.offsetLeft - oldOffsetLeft; + break; + } + + if (!this._tabElement.previousSibling && event.pageX - this._dragStartX < 0) { + this._tabElement.style.setProperty("left", "0px"); + return; + } + if (!this._tabElement.nextSibling && event.pageX - this._dragStartX > 0) { + this._tabElement.style.setProperty("left", "0px"); + return; + } + + this._tabElement.style.setProperty("position", "relative"); + this._tabElement.style.setProperty("left", (event.pageX - this._dragStartX) + "px"); + }, + + /** + * @param {!Event} event + */ + _endTabDragging: function(event) + { + this._tabElement.style.removeProperty("position"); + this._tabElement.style.removeProperty("left"); + delete this._dragStartX; + } +} + +/** + * @interface + */ +WebInspector.TabbedPaneTabDelegate = function() +{ +} + +WebInspector.TabbedPaneTabDelegate.prototype = { + /** + * @param {!WebInspector.TabbedPane} tabbedPane + * @param {!Array.<string>} ids + */ + closeTabs: function(tabbedPane, ids) { } +} + +/** + * @constructor + * @param {!WebInspector.TabbedPane} tabbedPane + * @param {string} extensionPoint + * @param {function(string, !WebInspector.View)=} viewCallback + */ +WebInspector.ExtensibleTabbedPaneController = function(tabbedPane, extensionPoint, viewCallback) +{ + this._tabbedPane = tabbedPane; + this._extensionPoint = extensionPoint; + this._viewCallback = viewCallback; + + this._tabbedPane.setRetainTabOrder(true, WebInspector.moduleManager.orderComparator(extensionPoint, "name", "order")); + this._tabbedPane.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, this._tabSelected, this); + /** @type {!StringMap.<?WebInspector.View>} */ + this._views = new StringMap(); + this._initialize(); +} + +WebInspector.ExtensibleTabbedPaneController.prototype = { + _initialize: function() + { + this._extensions = {}; + var extensions = WebInspector.moduleManager.extensions(this._extensionPoint); + + for (var i = 0; i < extensions.length; ++i) { + var descriptor = extensions[i].descriptor(); + var id = descriptor["name"]; + var title = WebInspector.UIString(descriptor["title"]); + var settingName = descriptor["setting"]; + var setting = settingName ? /** @type {!WebInspector.Setting|undefined} */ (WebInspector.settings[settingName]) : null; + + this._extensions[id] = extensions[i]; + + if (setting) { + setting.addChangeListener(this._toggleSettingBasedView.bind(this, id, title, setting)); + if (setting.get()) + this._tabbedPane.appendTab(id, title, new WebInspector.View()); + } else { + this._tabbedPane.appendTab(id, title, new WebInspector.View()); + } + } + }, + + /** + * @param {string} id + * @param {string} title + * @param {!WebInspector.Setting} setting + */ + _toggleSettingBasedView: function(id, title, setting) + { + this._tabbedPane.closeTab(id); + if (setting.get()) + this._tabbedPane.appendTab(id, title, new WebInspector.View()); + }, + + /** + * @param {!WebInspector.Event} event + */ + _tabSelected: function(event) + { + var tabId = this._tabbedPane.selectedTabId; + if (!tabId) + return; + var view = this._viewForId(tabId); + if (view) + this._tabbedPane.changeTabView(tabId, view); + }, + + /** + * @return {?WebInspector.View} + */ + _viewForId: function(id) + { + if (this._views.contains(id)) + return /** @type {!WebInspector.View} */ (this._views.get(id)); + var view = this._extensions[id] ? /** @type {!WebInspector.View} */ (this._extensions[id].instance()) : null; + this._views.put(id, view); + if (this._viewCallback && view) + this._viewCallback(id, view); + return view; + } +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/TextEditor.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/TextEditor.js new file mode 100644 index 00000000000..ee36f9ec4f8 --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/TextEditor.js @@ -0,0 +1,373 @@ +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * Copyright (C) 2010 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER 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. + */ + +/** + * @interface + */ +WebInspector.TextEditor = function() { }; + +WebInspector.TextEditor.Events = { + GutterClick: "gutterClick" +}; + +/** @typedef {{lineNumber: number, event: !Event}} */ +WebInspector.TextEditor.GutterClickEventData; + +WebInspector.TextEditor.prototype = { + + undo: function() { }, + + redo: function() { }, + + /** + * @return {boolean} + */ + isClean: function() { }, + + markClean: function() { }, + + /** + * @return {string} + */ + indent: function() { }, + + /** + * @param {number} lineNumber + * @param {number} column + * @return {?{x: number, y: number, height: number}} + */ + cursorPositionToCoordinates: function(lineNumber, column) { return null; }, + + /** + * @param {number} x + * @param {number} y + * @return {?WebInspector.TextRange} + */ + coordinatesToCursorPosition: function(x, y) { return null; }, + + /** + * @param {number} lineNumber + * @param {number} column + * @return {?{startColumn: number, endColumn: number, type: string}} + */ + tokenAtTextPosition: function(lineNumber, column) { return null; }, + + /** + * @param {string} mimeType + */ + setMimeType: function(mimeType) { }, + + /** + * @param {boolean} readOnly + */ + setReadOnly: function(readOnly) { }, + + /** + * @return {boolean} + */ + readOnly: function() { }, + + /** + * @return {!Element} + */ + defaultFocusedElement: function() { }, + + /** + * @param {!WebInspector.TextRange} range + * @param {string} cssClass + * @return {!Object} + */ + highlightRange: function(range, cssClass) { }, + + /** + * @param {!Object} highlightDescriptor + */ + removeHighlight: function(highlightDescriptor) { }, + + /** + * @param {number} lineNumber + * @param {boolean} disabled + * @param {boolean} conditional + */ + addBreakpoint: function(lineNumber, disabled, conditional) { }, + + /** + * @param {number} lineNumber + */ + removeBreakpoint: function(lineNumber) { }, + + /** + * @param {number} lineNumber + */ + setExecutionLine: function(lineNumber) { }, + + clearExecutionLine: function() { }, + + /** + * @param {number} lineNumber + * @param {!Element} element + */ + addDecoration: function(lineNumber, element) { }, + + /** + * @param {number} lineNumber + * @param {!Element} element + */ + removeDecoration: function(lineNumber, element) { }, + + /** + * @param {!RegExp} regex + * @param {?WebInspector.TextRange} range + */ + highlightSearchResults: function(regex, range) { }, + + /** + * @param {number} lineNumber + * @param {number=} columnNumber + * @param {boolean=} shouldHighlight + */ + revealPosition: function(lineNumber, columnNumber, shouldHighlight) { }, + + clearPositionHighlight: function() { }, + + /** + * @return {!Array.<!Element>} + */ + elementsToRestoreScrollPositionsFor: function() { }, + + /** + * @param {!WebInspector.TextEditor} textEditor + */ + inheritScrollPositions: function(textEditor) { }, + + beginUpdates: function() { }, + + endUpdates: function() { }, + + onResize: function() { }, + + /** + * @param {!WebInspector.TextRange} range + * @param {string} text + * @return {!WebInspector.TextRange} + */ + editRange: function(range, text) { }, + + /** + * @param {number} lineNumber + */ + scrollToLine: function(lineNumber) { }, + + /** + * @return {number} + */ + firstVisibleLine: function() { }, + + /** + * @return {number} + */ + lastVisibleLine: function() { }, + + /** + * @return {!WebInspector.TextRange} + */ + selection: function() { }, + + /** + * @return {!Array.<!WebInspector.TextRange>} + */ + selections: function() { }, + + /** + * @return {?WebInspector.TextRange} + */ + lastSelection: function() { }, + + /** + * @param {!WebInspector.TextRange} textRange + */ + setSelection: function(textRange) { }, + + /** + * @param {!WebInspector.TextRange} range + * @return {string} + */ + copyRange: function(range) { }, + + /** + * @param {string} text + */ + setText: function(text) { }, + + /** + * @return {string} + */ + text: function() { }, + + /** + * @return {!WebInspector.TextRange} + */ + range: function() { }, + + /** + * @param {number} lineNumber + * @return {string} + */ + line: function(lineNumber) { }, + + /** + * @return {number} + */ + get linesCount() { }, + + /** + * @param {number} line + * @param {string} name + * @param {?Object} value + */ + setAttribute: function(line, name, value) { }, + + /** + * @param {number} line + * @param {string} name + * @return {?Object} value + */ + getAttribute: function(line, name) { }, + + /** + * @param {number} line + * @param {string} name + */ + removeAttribute: function(line, name) { }, + + wasShown: function() { }, + + willHide: function() { }, + + /** + * @param {?WebInspector.CompletionDictionary} dictionary + */ + setCompletionDictionary: function(dictionary) { }, + + /** + * @param {number} lineNumber + * @param {number} columnNumber + * @return {?WebInspector.TextEditorPositionHandle} + */ + textEditorPositionHandle: function(lineNumber, columnNumber) { }, + + dispose: function() { } +} + +/** + * @interface + */ +WebInspector.TextEditorPositionHandle = function() +{ +} + +WebInspector.TextEditorPositionHandle.prototype = { + /** + * @return {?{lineNumber: number, columnNumber: number}} + */ + resolve: function() { }, + + /** + * @param {!WebInspector.TextEditorPositionHandle} positionHandle + * @return {boolean} + */ + equal: function(positionHandle) { } +} + +/** + * @interface + */ +WebInspector.TextEditorDelegate = function() +{ +} + +WebInspector.TextEditorDelegate.prototype = { + /** + * @param {!WebInspector.TextRange} oldRange + * @param {!WebInspector.TextRange} newRange + */ + onTextChanged: function(oldRange, newRange) { }, + + /** + * @param {!WebInspector.TextRange} textRange + */ + selectionChanged: function(textRange) { }, + + /** + * @param {number} lineNumber + */ + scrollChanged: function(lineNumber) { }, + + editorFocused: function() { }, + + /** + * @param {!WebInspector.ContextMenu} contextMenu + * @param {number} lineNumber + */ + populateLineGutterContextMenu: function(contextMenu, lineNumber) { }, + + /** + * @param {!WebInspector.ContextMenu} contextMenu + * @param {number} lineNumber + */ + populateTextAreaContextMenu: function(contextMenu, lineNumber) { }, + + /** + * @param {string} hrefValue + * @param {boolean} isExternal + * @return {!Element} + */ + createLink: function(hrefValue, isExternal) { }, + + /** + * @param {?WebInspector.TextRange} from + * @param {?WebInspector.TextRange} to + */ + onJumpToPosition: function(from, to) { } +} + +/** + * @interface + */ +WebInspector.TokenizerFactory = function() { } + +WebInspector.TokenizerFactory.prototype = { + /** + * @param {string} mimeType + * @return {function(string, function(string, ?string, number, number))} + */ + createTokenizer: function(mimeType) { } +}
\ No newline at end of file diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/TextPrompt.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/TextPrompt.js new file mode 100644 index 00000000000..8efa6190b3d --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/TextPrompt.js @@ -0,0 +1,908 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.Object} + * @implements {WebInspector.SuggestBoxDelegate} + * @param {function(!Element, !Range, boolean, function(!Array.<string>, number=))} completions + * @param {string=} stopCharacters + */ +WebInspector.TextPrompt = function(completions, stopCharacters) +{ + /** + * @type {!Element|undefined} + */ + this._proxyElement; + this._proxyElementDisplay = "inline-block"; + this._loadCompletions = completions; + this._completionStopCharacters = stopCharacters || " =:[({;,!+-*/&|^<>."; +} + +WebInspector.TextPrompt.Events = { + ItemApplied: "text-prompt-item-applied", + ItemAccepted: "text-prompt-item-accepted" +}; + +WebInspector.TextPrompt.prototype = { + get proxyElement() + { + return this._proxyElement; + }, + + /** + * @param {boolean} suggestBoxEnabled + */ + setSuggestBoxEnabled: function(suggestBoxEnabled) + { + this._suggestBoxEnabled = suggestBoxEnabled; + }, + + renderAsBlock: function() + { + this._proxyElementDisplay = "block"; + }, + + /** + * Clients should never attach any event listeners to the |element|. Instead, + * they should use the result of this method to attach listeners for bubbling events. + * + * @param {!Element} element + * @return {!Element} + */ + attach: function(element) + { + return this._attachInternal(element); + }, + + /** + * Clients should never attach any event listeners to the |element|. Instead, + * they should use the result of this method to attach listeners for bubbling events + * or the |blurListener| parameter to register a "blur" event listener on the |element| + * (since the "blur" event does not bubble.) + * + * @param {!Element} element + * @param {function(!Event)} blurListener + * @return {!Element} + */ + attachAndStartEditing: function(element, blurListener) + { + this._attachInternal(element); + this._startEditing(blurListener); + return this.proxyElement; + }, + + /** + * @param {!Element} element + * @return {!Element} + */ + _attachInternal: function(element) + { + if (this.proxyElement) + throw "Cannot attach an attached TextPrompt"; + this._element = element; + + this._boundOnKeyDown = this.onKeyDown.bind(this); + this._boundOnInput = this.onInput.bind(this); + this._boundOnMouseWheel = this.onMouseWheel.bind(this); + this._boundSelectStart = this._selectStart.bind(this); + this._boundRemoveSuggestionAids = this._removeSuggestionAids.bind(this); + this._proxyElement = element.ownerDocument.createElement("span"); + this._proxyElement.style.display = this._proxyElementDisplay; + element.parentElement.insertBefore(this.proxyElement, element); + this.proxyElement.appendChild(element); + this._element.classList.add("text-prompt"); + this._element.addEventListener("keydown", this._boundOnKeyDown, false); + this._element.addEventListener("input", this._boundOnInput, false); + this._element.addEventListener("mousewheel", this._boundOnMouseWheel, false); + this._element.addEventListener("selectstart", this._boundSelectStart, false); + this._element.addEventListener("blur", this._boundRemoveSuggestionAids, false); + + if (this._suggestBoxEnabled) + this._suggestBox = new WebInspector.SuggestBox(this); + + return this.proxyElement; + }, + + detach: function() + { + this._removeFromElement(); + this.proxyElement.parentElement.insertBefore(this._element, this.proxyElement); + this.proxyElement.remove(); + delete this._proxyElement; + this._element.classList.remove("text-prompt"); + WebInspector.restoreFocusFromElement(this._element); + }, + + /** + * @type {string} + */ + get text() + { + return this._element.textContent; + }, + + /** + * @param {string} x + */ + set text(x) + { + this._removeSuggestionAids(); + if (!x) { + // Append a break element instead of setting textContent to make sure the selection is inside the prompt. + this._element.removeChildren(); + this._element.appendChild(document.createElement("br")); + } else + this._element.textContent = x; + + this.moveCaretToEndOfPrompt(); + this._element.scrollIntoView(); + }, + + _removeFromElement: function() + { + this.clearAutoComplete(true); + this._element.removeEventListener("keydown", this._boundOnKeyDown, false); + this._element.removeEventListener("input", this._boundOnInput, false); + this._element.removeEventListener("selectstart", this._boundSelectStart, false); + this._element.removeEventListener("blur", this._boundRemoveSuggestionAids, false); + if (this._isEditing) + this._stopEditing(); + if (this._suggestBox) + this._suggestBox.removeFromElement(); + }, + + /** + * @param {function(!Event)=} blurListener + */ + _startEditing: function(blurListener) + { + this._isEditing = true; + this._element.classList.add("editing"); + if (blurListener) { + this._blurListener = blurListener; + this._element.addEventListener("blur", this._blurListener, false); + } + this._oldTabIndex = this._element.tabIndex; + if (this._element.tabIndex < 0) + this._element.tabIndex = 0; + WebInspector.setCurrentFocusElement(this._element); + if (!this.text) + this._updateAutoComplete(); + }, + + _stopEditing: function() + { + this._element.tabIndex = this._oldTabIndex; + if (this._blurListener) + this._element.removeEventListener("blur", this._blurListener, false); + this._element.classList.remove("editing"); + delete this._isEditing; + }, + + _removeSuggestionAids: function() + { + this.clearAutoComplete(); + this.hideSuggestBox(); + }, + + _selectStart: function() + { + if (this._selectionTimeout) + clearTimeout(this._selectionTimeout); + + this._removeSuggestionAids(); + + /** + * @this {WebInspector.TextPrompt} + */ + function moveBackIfOutside() + { + delete this._selectionTimeout; + if (!this.isCaretInsidePrompt() && window.getSelection().isCollapsed) { + this.moveCaretToEndOfPrompt(); + this.autoCompleteSoon(); + } + } + + this._selectionTimeout = setTimeout(moveBackIfOutside.bind(this), 100); + }, + + /** + * @param {boolean=} force + */ + _updateAutoComplete: function(force) + { + this.clearAutoComplete(); + this.autoCompleteSoon(force); + }, + + /** + * @param {?Event} event + */ + onMouseWheel: function(event) + { + // Subclasses can implement. + }, + + /** + * @param {?Event} event + */ + onKeyDown: function(event) + { + var handled = false; + delete this._needUpdateAutocomplete; + + switch (event.keyIdentifier) { + case "U+0009": // Tab + handled = this.tabKeyPressed(event); + break; + case "Left": + case "Home": + this._removeSuggestionAids(); + break; + case "Right": + case "End": + if (this.isCaretAtEndOfPrompt()) + handled = this.acceptAutoComplete(); + else + this._removeSuggestionAids(); + break; + case "U+001B": // Esc + if (this.isSuggestBoxVisible()) { + this._removeSuggestionAids(); + handled = true; + } + break; + case "U+0020": // Space + if (event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) { + this._updateAutoComplete(true); + handled = true; + } + break; + case "Alt": + case "Meta": + case "Shift": + case "Control": + break; + } + + if (!handled && this.isSuggestBoxVisible()) + handled = this._suggestBox.keyPressed(event); + + if (!handled) + this._needUpdateAutocomplete = true; + + if (handled) + event.consume(true); + }, + + /** + * @param {?Event} event + */ + onInput: function(event) + { + if (this._needUpdateAutocomplete) + this._updateAutoComplete(); + }, + + /** + * @return {boolean} + */ + acceptAutoComplete: function() + { + var result = false; + if (this.isSuggestBoxVisible()) + result = this._suggestBox.acceptSuggestion(); + if (!result) + result = this._acceptSuggestionInternal(); + + return result; + }, + + /** + * @param {boolean=} includeTimeout + */ + clearAutoComplete: function(includeTimeout) + { + if (includeTimeout && this._completeTimeout) { + clearTimeout(this._completeTimeout); + delete this._completeTimeout; + } + delete this._waitingForCompletions; + + if (!this.autoCompleteElement) + return; + + this.autoCompleteElement.remove(); + delete this.autoCompleteElement; + delete this._userEnteredRange; + delete this._userEnteredText; + }, + + /** + * @param {boolean=} force + */ + autoCompleteSoon: function(force) + { + var immediately = this.isSuggestBoxVisible() || force; + if (!this._completeTimeout) + this._completeTimeout = setTimeout(this.complete.bind(this, force), immediately ? 0 : 250); + }, + + /** + * @param {boolean=} force + * @param {boolean=} reverse + */ + complete: function(force, reverse) + { + this.clearAutoComplete(true); + var selection = window.getSelection(); + if (!selection.rangeCount) + return; + + var selectionRange = selection.getRangeAt(0); + var shouldExit; + + if (!force && !this.isCaretAtEndOfPrompt() && !this.isSuggestBoxVisible()) + shouldExit = true; + else if (!selection.isCollapsed) + shouldExit = true; + else if (!force) { + // BUG72018: Do not show suggest box if caret is followed by a non-stop character. + var wordSuffixRange = selectionRange.startContainer.rangeOfWord(selectionRange.endOffset, this._completionStopCharacters, this._element, "forward"); + if (wordSuffixRange.toString().length) + shouldExit = true; + } + if (shouldExit) { + this.hideSuggestBox(); + return; + } + + var wordPrefixRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, this._completionStopCharacters, this._element, "backward"); + this._waitingForCompletions = true; + this._loadCompletions(this.proxyElement, wordPrefixRange, force || false, this._completionsReady.bind(this, selection, wordPrefixRange, !!reverse)); + }, + + disableDefaultSuggestionForEmptyInput: function() + { + this._disableDefaultSuggestionForEmptyInput = true; + }, + + /** + * @param {!Selection} selection + * @param {!Range} textRange + */ + _boxForAnchorAtStart: function(selection, textRange) + { + var rangeCopy = selection.getRangeAt(0).cloneRange(); + var anchorElement = document.createElement("span"); + anchorElement.textContent = "\u200B"; + textRange.insertNode(anchorElement); + var box = anchorElement.boxInWindow(window); + anchorElement.remove(); + selection.removeAllRanges(); + selection.addRange(rangeCopy); + return box; + }, + + /** + * @param {!Array.<string>} completions + * @param {number} wordPrefixLength + */ + _buildCommonPrefix: function(completions, wordPrefixLength) + { + var commonPrefix = completions[0]; + for (var i = 0; i < completions.length; ++i) { + var completion = completions[i]; + var lastIndex = Math.min(commonPrefix.length, completion.length); + for (var j = wordPrefixLength; j < lastIndex; ++j) { + if (commonPrefix[j] !== completion[j]) { + commonPrefix = commonPrefix.substr(0, j); + break; + } + } + } + return commonPrefix; + }, + + /** + * @param {!Selection} selection + * @param {!Range} originalWordPrefixRange + * @param {boolean} reverse + * @param {!Array.<string>} completions + * @param {number=} selectedIndex + */ + _completionsReady: function(selection, originalWordPrefixRange, reverse, completions, selectedIndex) + { + if (!this._waitingForCompletions || !completions.length) { + this.hideSuggestBox(); + return; + } + delete this._waitingForCompletions; + + var selectionRange = selection.getRangeAt(0); + + var fullWordRange = document.createRange(); + fullWordRange.setStart(originalWordPrefixRange.startContainer, originalWordPrefixRange.startOffset); + fullWordRange.setEnd(selectionRange.endContainer, selectionRange.endOffset); + + if (originalWordPrefixRange.toString() + selectionRange.toString() !== fullWordRange.toString()) + return; + + selectedIndex = (this._disableDefaultSuggestionForEmptyInput && !this.text) ? -1 : (selectedIndex || 0); + + this._userEnteredRange = fullWordRange; + this._userEnteredText = fullWordRange.toString(); + + if (this._suggestBox) + this._suggestBox.updateSuggestions(this._boxForAnchorAtStart(selection, fullWordRange), completions, selectedIndex, !this.isCaretAtEndOfPrompt(), this._userEnteredText); + + if (selectedIndex === -1) + return; + + var wordPrefixLength = originalWordPrefixRange.toString().length; + this._commonPrefix = this._buildCommonPrefix(completions, wordPrefixLength); + + if (this.isCaretAtEndOfPrompt()) { + this._userEnteredRange.deleteContents(); + this._element.normalize(); + var finalSelectionRange = document.createRange(); + var completionText = completions[selectedIndex]; + var prefixText = completionText.substring(0, wordPrefixLength); + var suffixText = completionText.substring(wordPrefixLength); + + var prefixTextNode = document.createTextNode(prefixText); + fullWordRange.insertNode(prefixTextNode); + + this.autoCompleteElement = document.createElement("span"); + this.autoCompleteElement.className = "auto-complete-text"; + this.autoCompleteElement.textContent = suffixText; + + prefixTextNode.parentNode.insertBefore(this.autoCompleteElement, prefixTextNode.nextSibling); + + finalSelectionRange.setStart(prefixTextNode, wordPrefixLength); + finalSelectionRange.setEnd(prefixTextNode, wordPrefixLength); + selection.removeAllRanges(); + selection.addRange(finalSelectionRange); + this.dispatchEventToListeners(WebInspector.TextPrompt.Events.ItemApplied); + } + }, + + _completeCommonPrefix: function() + { + if (!this.autoCompleteElement || !this._commonPrefix || !this._userEnteredText || !this._commonPrefix.startsWith(this._userEnteredText)) + return; + + if (!this.isSuggestBoxVisible()) { + this.acceptAutoComplete(); + return; + } + + this.autoCompleteElement.textContent = this._commonPrefix.substring(this._userEnteredText.length); + this._acceptSuggestionInternal(true); + }, + + /** + * @param {string} completionText + * @param {boolean=} isIntermediateSuggestion + */ + applySuggestion: function(completionText, isIntermediateSuggestion) + { + this._applySuggestion(completionText, isIntermediateSuggestion); + }, + + /** + * @param {string} completionText + * @param {boolean=} isIntermediateSuggestion + * @param {!Range=} originalPrefixRange + */ + _applySuggestion: function(completionText, isIntermediateSuggestion, originalPrefixRange) + { + var wordPrefixLength; + if (originalPrefixRange) + wordPrefixLength = originalPrefixRange.toString().length; + else + wordPrefixLength = this._userEnteredText ? this._userEnteredText.length : 0; + + this._userEnteredRange.deleteContents(); + this._element.normalize(); + var finalSelectionRange = document.createRange(); + var completionTextNode = document.createTextNode(completionText); + this._userEnteredRange.insertNode(completionTextNode); + if (this.autoCompleteElement) { + this.autoCompleteElement.remove(); + delete this.autoCompleteElement; + } + + if (isIntermediateSuggestion) + finalSelectionRange.setStart(completionTextNode, wordPrefixLength); + else + finalSelectionRange.setStart(completionTextNode, completionText.length); + + finalSelectionRange.setEnd(completionTextNode, completionText.length); + + var selection = window.getSelection(); + selection.removeAllRanges(); + selection.addRange(finalSelectionRange); + if (isIntermediateSuggestion) + this.dispatchEventToListeners(WebInspector.TextPrompt.Events.ItemApplied, { itemText: completionText }); + }, + + /** + * @override + */ + acceptSuggestion: function() + { + this._acceptSuggestionInternal(); + }, + + /** + * @param {boolean=} prefixAccepted + * @return {boolean} + */ + _acceptSuggestionInternal: function(prefixAccepted) + { + if (this._isAcceptingSuggestion) + return false; + + if (!this.autoCompleteElement || !this.autoCompleteElement.parentNode) + return false; + + var text = this.autoCompleteElement.textContent; + var textNode = document.createTextNode(text); + this.autoCompleteElement.parentNode.replaceChild(textNode, this.autoCompleteElement); + delete this.autoCompleteElement; + + var finalSelectionRange = document.createRange(); + finalSelectionRange.setStart(textNode, text.length); + finalSelectionRange.setEnd(textNode, text.length); + + var selection = window.getSelection(); + selection.removeAllRanges(); + selection.addRange(finalSelectionRange); + + if (!prefixAccepted) { + this.hideSuggestBox(); + this.dispatchEventToListeners(WebInspector.TextPrompt.Events.ItemAccepted); + } else + this.autoCompleteSoon(true); + + return true; + }, + + hideSuggestBox: function() + { + if (this.isSuggestBoxVisible()) + this._suggestBox.hide(); + }, + + /** + * @return {boolean} + */ + isSuggestBoxVisible: function() + { + return this._suggestBox && this._suggestBox.visible(); + }, + + /** + * @return {boolean} + */ + isCaretInsidePrompt: function() + { + return this._element.isInsertionCaretInside(); + }, + + /** + * @return {boolean} + */ + isCaretAtEndOfPrompt: function() + { + var selection = window.getSelection(); + if (!selection.rangeCount || !selection.isCollapsed) + return false; + + var selectionRange = selection.getRangeAt(0); + var node = selectionRange.startContainer; + if (!node.isSelfOrDescendant(this._element)) + return false; + + if (node.nodeType === Node.TEXT_NODE && selectionRange.startOffset < node.nodeValue.length) + return false; + + var foundNextText = false; + while (node) { + if (node.nodeType === Node.TEXT_NODE && node.nodeValue.length) { + if (foundNextText && (!this.autoCompleteElement || !this.autoCompleteElement.isAncestor(node))) + return false; + foundNextText = true; + } + + node = node.traverseNextNode(this._element); + } + + return true; + }, + + /** + * @return {boolean} + */ + isCaretOnFirstLine: function() + { + var selection = window.getSelection(); + var focusNode = selection.focusNode; + if (!focusNode || focusNode.nodeType !== Node.TEXT_NODE || focusNode.parentNode !== this._element) + return true; + + if (focusNode.textContent.substring(0, selection.focusOffset).indexOf("\n") !== -1) + return false; + focusNode = focusNode.previousSibling; + + while (focusNode) { + if (focusNode.nodeType !== Node.TEXT_NODE) + return true; + if (focusNode.textContent.indexOf("\n") !== -1) + return false; + focusNode = focusNode.previousSibling; + } + + return true; + }, + + /** + * @return {boolean} + */ + isCaretOnLastLine: function() + { + var selection = window.getSelection(); + var focusNode = selection.focusNode; + if (!focusNode || focusNode.nodeType !== Node.TEXT_NODE || focusNode.parentNode !== this._element) + return true; + + if (focusNode.textContent.substring(selection.focusOffset).indexOf("\n") !== -1) + return false; + focusNode = focusNode.nextSibling; + + while (focusNode) { + if (focusNode.nodeType !== Node.TEXT_NODE) + return true; + if (focusNode.textContent.indexOf("\n") !== -1) + return false; + focusNode = focusNode.nextSibling; + } + + return true; + }, + + moveCaretToEndOfPrompt: function() + { + var selection = window.getSelection(); + var selectionRange = document.createRange(); + + var offset = this._element.childNodes.length; + selectionRange.setStart(this._element, offset); + selectionRange.setEnd(this._element, offset); + + selection.removeAllRanges(); + selection.addRange(selectionRange); + }, + + /** + * @param {!Event} event + * @return {boolean} + */ + tabKeyPressed: function(event) + { + this._completeCommonPrefix(); + + // Consume the key. + return true; + }, + + __proto__: WebInspector.Object.prototype +} + + +/** + * @constructor + * @extends {WebInspector.TextPrompt} + * @param {function(!Element, !Range, boolean, function(!Array.<string>, number=))} completions + * @param {string=} stopCharacters + */ +WebInspector.TextPromptWithHistory = function(completions, stopCharacters) +{ + WebInspector.TextPrompt.call(this, completions, stopCharacters); + + /** + * @type {!Array.<string>} + */ + this._data = []; + + /** + * 1-based entry in the history stack. + * @type {number} + */ + this._historyOffset = 1; + + /** + * Whether to coalesce duplicate items in the history, default is true. + * @type {boolean} + */ + this._coalesceHistoryDupes = true; +} + +WebInspector.TextPromptWithHistory.prototype = { + /** + * @return {!Array.<string>} + */ + get historyData() + { + // FIXME: do we need to copy this? + return this._data; + }, + + /** + * @param {boolean} x + */ + setCoalesceHistoryDupes: function(x) + { + this._coalesceHistoryDupes = x; + }, + + /** + * @param {!Array.<string>} data + */ + setHistoryData: function(data) + { + this._data = [].concat(data); + this._historyOffset = 1; + }, + + /** + * Pushes a committed text into the history. + * @param {string} text + */ + pushHistoryItem: function(text) + { + if (this._uncommittedIsTop) { + this._data.pop(); + delete this._uncommittedIsTop; + } + + this._historyOffset = 1; + if (this._coalesceHistoryDupes && text === this._currentHistoryItem()) + return; + this._data.push(text); + }, + + /** + * Pushes the current (uncommitted) text into the history. + */ + _pushCurrentText: function() + { + if (this._uncommittedIsTop) + this._data.pop(); // Throw away obsolete uncommitted text. + this._uncommittedIsTop = true; + this.clearAutoComplete(true); + this._data.push(this.text); + }, + + /** + * @return {string|undefined} + */ + _previous: function() + { + if (this._historyOffset > this._data.length) + return undefined; + if (this._historyOffset === 1) + this._pushCurrentText(); + ++this._historyOffset; + return this._currentHistoryItem(); + }, + + /** + * @return {string|undefined} + */ + _next: function() + { + if (this._historyOffset === 1) + return undefined; + --this._historyOffset; + return this._currentHistoryItem(); + }, + + /** + * @return {string|undefined} + */ + _currentHistoryItem: function() + { + return this._data[this._data.length - this._historyOffset]; + }, + + /** + * @override + */ + onKeyDown: function(event) + { + var newText; + var isPrevious; + + switch (event.keyIdentifier) { + case "Up": + if (!this.isCaretOnFirstLine() || this.isSuggestBoxVisible()) + break; + newText = this._previous(); + isPrevious = true; + break; + case "Down": + if (!this.isCaretOnLastLine() || this.isSuggestBoxVisible()) + break; + newText = this._next(); + break; + case "U+0050": // Ctrl+P = Previous + if (WebInspector.isMac() && event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) { + newText = this._previous(); + isPrevious = true; + } + break; + case "U+004E": // Ctrl+N = Next + if (WebInspector.isMac() && event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) + newText = this._next(); + break; + } + + if (newText !== undefined) { + event.consume(true); + this.text = newText; + + if (isPrevious) { + var firstNewlineIndex = this.text.indexOf("\n"); + if (firstNewlineIndex === -1) + this.moveCaretToEndOfPrompt(); + else { + var selection = window.getSelection(); + var selectionRange = document.createRange(); + + selectionRange.setStart(this._element.firstChild, firstNewlineIndex); + selectionRange.setEnd(this._element.firstChild, firstNewlineIndex); + + selection.removeAllRanges(); + selection.addRange(selectionRange); + } + } + + return; + } + + WebInspector.TextPrompt.prototype.onKeyDown.apply(this, arguments); + }, + + __proto__: WebInspector.TextPrompt.prototype +} + diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/TextUtils.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/TextUtils.js new file mode 100644 index 00000000000..87273320313 --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/TextUtils.js @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2013 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER 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.TextUtils = { + /** + * @param {string} char + * @return {boolean} + */ + isStopChar: function(char) + { + return (char > " " && char < "0") || + (char > "9" && char < "A") || + (char > "Z" && char < "_") || + (char > "_" && char < "a") || + (char > "z" && char <= "~"); + }, + + /** + * @param {string} char + * @return {boolean} + */ + isWordChar: function(char) + { + return !WebInspector.TextUtils.isStopChar(char) && !WebInspector.TextUtils.isSpaceChar(char); + }, + + /** + * @param {string} char + * @return {boolean} + */ + isSpaceChar: function(char) + { + return WebInspector.TextUtils._SpaceCharRegex.test(char); + }, + + /** + * @param {string} word + * @return {boolean} + */ + isWord: function(word) + { + for (var i = 0; i < word.length; ++i) { + if (!WebInspector.TextUtils.isWordChar(word.charAt(i))) + return false; + } + return true; + }, + + /** + * @param {string} char + * @return {boolean} + */ + isOpeningBraceChar: function(char) + { + return char === "(" || char === "{"; + }, + + /** + * @param {string} char + * @return {boolean} + */ + isClosingBraceChar: function(char) + { + return char === ")" || char === "}"; + }, + + /** + * @param {string} char + * @return {boolean} + */ + isBraceChar: function(char) + { + return WebInspector.TextUtils.isOpeningBraceChar(char) || WebInspector.TextUtils.isClosingBraceChar(char); + }, + + /** + * @param {string} text + * @param {function(string):boolean} isWordChar + * @return {!Array.<string>} + */ + textToWords: function(text, isWordChar) + { + var words = []; + var startWord = -1; + for(var i = 0; i < text.length; ++i) { + if (!isWordChar(text.charAt(i))) { + if (startWord !== -1) + words.push(text.substring(startWord, i)); + startWord = -1; + } else if (startWord === -1) + startWord = i; + } + if (startWord !== -1) + words.push(text.substring(startWord)); + return words; + }, + + /** + * @param {string} source + * @param {number=} startIndex + * @param {number=} lastIndex + * @return {number} + */ + findBalancedCurlyBrackets: function(source, startIndex, lastIndex) { + lastIndex = lastIndex || source.length; + startIndex = startIndex || 0; + var counter = 0; + var inString = false; + + for (var index = startIndex; index < lastIndex; ++index) { + var character = source[index]; + if (inString) { + if (character === "\\") + ++index; + else if (character === "\"") + inString = false; + } else { + if (character === "\"") + inString = true; + else if (character === "{") + ++counter; + else if (character === "}") { + if (--counter === 0) + return index + 1; + } + } + } + return -1; + } +} + +WebInspector.TextUtils._SpaceCharRegex = /\s/; + +/** + * @enum {string} + */ +WebInspector.TextUtils.Indent = { + TwoSpaces: " ", + FourSpaces: " ", + EightSpaces: " ", + TabCharacter: "\t" +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/UIUtils.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/UIUtils.js new file mode 100644 index 00000000000..a2d7f8feadd --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/UIUtils.js @@ -0,0 +1,911 @@ +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2007 Matt Lilek (pewtermoose@gmail.com). + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @param {!Element} element + * @param {?function(!MouseEvent): boolean} elementDragStart + * @param {function(!MouseEvent)} elementDrag + * @param {?function(!MouseEvent)} elementDragEnd + * @param {string} cursor + * @param {?string=} hoverCursor + */ +WebInspector.installDragHandle = function(element, elementDragStart, elementDrag, elementDragEnd, cursor, hoverCursor) +{ + element.addEventListener("mousedown", WebInspector.elementDragStart.bind(WebInspector, elementDragStart, elementDrag, elementDragEnd, cursor), false); + if (hoverCursor !== null) + element.style.cursor = hoverCursor || cursor; +} + +/** + * @param {?function(!MouseEvent):boolean} elementDragStart + * @param {function(!MouseEvent)} elementDrag + * @param {?function(!MouseEvent)} elementDragEnd + * @param {string} cursor + * @param {?Event} event + */ +WebInspector.elementDragStart = function(elementDragStart, elementDrag, elementDragEnd, cursor, event) +{ + // Only drag upon left button. Right will likely cause a context menu. So will ctrl-click on mac. + if (event.button || (WebInspector.isMac() && event.ctrlKey)) + return; + + if (WebInspector._elementDraggingEventListener) + return; + + if (elementDragStart && !elementDragStart(/** @type {!MouseEvent} */ (event))) + return; + + if (WebInspector._elementDraggingGlassPane) { + WebInspector._elementDraggingGlassPane.dispose(); + delete WebInspector._elementDraggingGlassPane; + } + + var targetDocument = event.target.ownerDocument; + + WebInspector._elementDraggingEventListener = elementDrag; + WebInspector._elementEndDraggingEventListener = elementDragEnd; + WebInspector._mouseOutWhileDraggingTargetDocument = targetDocument; + + targetDocument.addEventListener("mousemove", WebInspector._elementDragMove, true); + targetDocument.addEventListener("mouseup", WebInspector._elementDragEnd, true); + targetDocument.addEventListener("mouseout", WebInspector._mouseOutWhileDragging, true); + + targetDocument.body.style.cursor = cursor; + + event.preventDefault(); +} + +WebInspector._mouseOutWhileDragging = function() +{ + WebInspector._unregisterMouseOutWhileDragging(); + WebInspector._elementDraggingGlassPane = new WebInspector.GlassPane(); +} + +WebInspector._unregisterMouseOutWhileDragging = function() +{ + if (!WebInspector._mouseOutWhileDraggingTargetDocument) + return; + WebInspector._mouseOutWhileDraggingTargetDocument.removeEventListener("mouseout", WebInspector._mouseOutWhileDragging, true); + delete WebInspector._mouseOutWhileDraggingTargetDocument; +} + +/** + * @param {!Event} event + */ +WebInspector._elementDragMove = function(event) +{ + if (WebInspector._elementDraggingEventListener(/** @type {!MouseEvent} */ (event))) + WebInspector._cancelDragEvents(event); +} + +/** + * @param {!Event} event + */ +WebInspector._cancelDragEvents = function(event) +{ + var targetDocument = event.target.ownerDocument; + targetDocument.removeEventListener("mousemove", WebInspector._elementDragMove, true); + targetDocument.removeEventListener("mouseup", WebInspector._elementDragEnd, true); + WebInspector._unregisterMouseOutWhileDragging(); + + targetDocument.body.style.removeProperty("cursor"); + + if (WebInspector._elementDraggingGlassPane) + WebInspector._elementDraggingGlassPane.dispose(); + + delete WebInspector._elementDraggingGlassPane; + delete WebInspector._elementDraggingEventListener; + delete WebInspector._elementEndDraggingEventListener; +} + +/** + * @param {!Event} event + */ +WebInspector._elementDragEnd = function(event) +{ + var elementDragEnd = WebInspector._elementEndDraggingEventListener; + + WebInspector._cancelDragEvents(/** @type {!MouseEvent} */ (event)); + + event.preventDefault(); + if (elementDragEnd) + elementDragEnd(/** @type {!MouseEvent} */ (event)); +} + +/** + * @constructor + */ +WebInspector.GlassPane = function() +{ + this.element = document.createElement("div"); + this.element.style.cssText = "position:absolute;top:0;bottom:0;left:0;right:0;background-color:transparent;z-index:1000;"; + this.element.id = "glass-pane"; + document.body.appendChild(this.element); + WebInspector._glassPane = this; +} + +WebInspector.GlassPane.prototype = { + dispose: function() + { + delete WebInspector._glassPane; + if (WebInspector.GlassPane.DefaultFocusedViewStack.length) + WebInspector.GlassPane.DefaultFocusedViewStack[0].focus(); + this.element.remove(); + } +} + +/** + * @type {!Array.<!WebInspector.View>} + */ +WebInspector.GlassPane.DefaultFocusedViewStack = []; + +/** + * @param {?Node=} node + * @return {boolean} + */ +WebInspector.isBeingEdited = function(node) +{ + if (!node || node.nodeType !== Node.ELEMENT_NODE) + return false; + var element = /** {!Element} */ (node); + if (element.classList.contains("text-prompt") || element.nodeName === "INPUT" || element.nodeName === "TEXTAREA") + return true; + + if (!WebInspector.__editingCount) + return false; + + while (element) { + if (element.__editing) + return true; + element = element.parentElement; + } + return false; +} + +/** + * @param {!Element} element + * @param {boolean} value + * @return {boolean} + */ +WebInspector.markBeingEdited = function(element, value) +{ + if (value) { + if (element.__editing) + return false; + element.classList.add("being-edited"); + element.__editing = true; + WebInspector.__editingCount = (WebInspector.__editingCount || 0) + 1; + } else { + if (!element.__editing) + return false; + element.classList.remove("being-edited"); + delete element.__editing; + --WebInspector.__editingCount; + } + return true; +} + +WebInspector.CSSNumberRegex = /^(-?(?:\d+(?:\.\d+)?|\.\d+))$/; + +WebInspector.StyleValueDelimiters = " \xA0\t\n\"':;,/()"; + + +/** + * @param {!Event} event + * @return {?string} + */ +WebInspector._valueModificationDirection = function(event) +{ + var direction = null; + if (event.type === "mousewheel") { + if (event.wheelDeltaY > 0) + direction = "Up"; + else if (event.wheelDeltaY < 0) + direction = "Down"; + } else { + if (event.keyIdentifier === "Up" || event.keyIdentifier === "PageUp") + direction = "Up"; + else if (event.keyIdentifier === "Down" || event.keyIdentifier === "PageDown") + direction = "Down"; + } + return direction; +} + +/** + * @param {string} hexString + * @param {!Event} event + */ +WebInspector._modifiedHexValue = function(hexString, event) +{ + var direction = WebInspector._valueModificationDirection(event); + if (!direction) + return hexString; + + var number = parseInt(hexString, 16); + if (isNaN(number) || !isFinite(number)) + return hexString; + + var maxValue = Math.pow(16, hexString.length) - 1; + var arrowKeyOrMouseWheelEvent = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down" || event.type === "mousewheel"); + var delta; + + if (arrowKeyOrMouseWheelEvent) + delta = (direction === "Up") ? 1 : -1; + else + delta = (event.keyIdentifier === "PageUp") ? 16 : -16; + + if (event.shiftKey) + delta *= 16; + + var result = number + delta; + if (result < 0) + result = 0; // Color hex values are never negative, so clamp to 0. + else if (result > maxValue) + return hexString; + + // Ensure the result length is the same as the original hex value. + var resultString = result.toString(16).toUpperCase(); + for (var i = 0, lengthDelta = hexString.length - resultString.length; i < lengthDelta; ++i) + resultString = "0" + resultString; + return resultString; +} + +/** + * @param {number} number + * @param {!Event} event + */ +WebInspector._modifiedFloatNumber = function(number, event) +{ + var direction = WebInspector._valueModificationDirection(event); + if (!direction) + return number; + + var arrowKeyOrMouseWheelEvent = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down" || event.type === "mousewheel"); + + // Jump by 10 when shift is down or jump by 0.1 when Alt/Option is down. + // Also jump by 10 for page up and down, or by 100 if shift is held with a page key. + var changeAmount = 1; + if (event.shiftKey && !arrowKeyOrMouseWheelEvent) + changeAmount = 100; + else if (event.shiftKey || !arrowKeyOrMouseWheelEvent) + changeAmount = 10; + else if (event.altKey) + changeAmount = 0.1; + + if (direction === "Down") + changeAmount *= -1; + + // Make the new number and constrain it to a precision of 6, this matches numbers the engine returns. + // Use the Number constructor to forget the fixed precision, so 1.100000 will print as 1.1. + var result = Number((number + changeAmount).toFixed(6)); + if (!String(result).match(WebInspector.CSSNumberRegex)) + return null; + + return result; +} + +/** + * @param {?Event} event + * @param {!Element} element + * @param {function(string,string)=} finishHandler + * @param {function(string)=} suggestionHandler + * @param {function(number):number=} customNumberHandler + * @return {boolean} + */ +WebInspector.handleElementValueModifications = function(event, element, finishHandler, suggestionHandler, customNumberHandler) +{ + var arrowKeyOrMouseWheelEvent = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down" || event.type === "mousewheel"); + var pageKeyPressed = (event.keyIdentifier === "PageUp" || event.keyIdentifier === "PageDown"); + if (!arrowKeyOrMouseWheelEvent && !pageKeyPressed) + return false; + + var selection = window.getSelection(); + if (!selection.rangeCount) + return false; + + var selectionRange = selection.getRangeAt(0); + if (!selectionRange.commonAncestorContainer.isSelfOrDescendant(element)) + return false; + + var originalValue = element.textContent; + var wordRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, WebInspector.StyleValueDelimiters, element); + var wordString = wordRange.toString(); + + if (suggestionHandler && suggestionHandler(wordString)) + return false; + + var replacementString; + var prefix, suffix, number; + + var matches; + matches = /(.*#)([\da-fA-F]+)(.*)/.exec(wordString); + if (matches && matches.length) { + prefix = matches[1]; + suffix = matches[3]; + number = WebInspector._modifiedHexValue(matches[2], event); + + if (customNumberHandler) + number = customNumberHandler(number); + + replacementString = prefix + number + suffix; + } else { + matches = /(.*?)(-?(?:\d+(?:\.\d+)?|\.\d+))(.*)/.exec(wordString); + if (matches && matches.length) { + prefix = matches[1]; + suffix = matches[3]; + number = WebInspector._modifiedFloatNumber(parseFloat(matches[2]), event); + + // Need to check for null explicitly. + if (number === null) + return false; + + if (customNumberHandler) + number = customNumberHandler(number); + + replacementString = prefix + number + suffix; + } + } + + if (replacementString) { + var replacementTextNode = document.createTextNode(replacementString); + + wordRange.deleteContents(); + wordRange.insertNode(replacementTextNode); + + var finalSelectionRange = document.createRange(); + finalSelectionRange.setStart(replacementTextNode, 0); + finalSelectionRange.setEnd(replacementTextNode, replacementString.length); + + selection.removeAllRanges(); + selection.addRange(finalSelectionRange); + + event.handled = true; + event.preventDefault(); + + if (finishHandler) + finishHandler(originalValue, replacementString); + + return true; + } + return false; +} + +/** + * @param {number} ms + * @param {number=} precision + * @return {string} + */ +Number.preciseMillisToString = function(ms, precision) +{ + precision = precision || 0; + var format = "%." + precision + "f\u2009ms"; + return WebInspector.UIString(format, ms); +} + +/** + * @param {number} ms + * @param {boolean=} higherResolution + * @return {string} + */ +Number.millisToString = function(ms, higherResolution) +{ + if (!isFinite(ms)) + return "-"; + + if (ms === 0) + return "0"; + + if (higherResolution && ms < 1000) + return WebInspector.UIString("%.3f\u2009ms", ms); + else if (ms < 1000) + return WebInspector.UIString("%.0f\u2009ms", ms); + + var seconds = ms / 1000; + if (seconds < 60) + return WebInspector.UIString("%.2f\u2009s", seconds); + + var minutes = seconds / 60; + if (minutes < 60) + return WebInspector.UIString("%.1f\u2009min", minutes); + + var hours = minutes / 60; + if (hours < 24) + return WebInspector.UIString("%.1f\u2009hrs", hours); + + var days = hours / 24; + return WebInspector.UIString("%.1f\u2009days", days); +} + +/** + * @param {number} seconds + * @param {boolean=} higherResolution + * @return {string} + */ +Number.secondsToString = function(seconds, higherResolution) +{ + if (!isFinite(seconds)) + return "-"; + return Number.millisToString(seconds * 1000, higherResolution); +} + +/** + * @param {number} bytes + * @return {string} + */ +Number.bytesToString = function(bytes) +{ + if (bytes < 1024) + return WebInspector.UIString("%.0f\u2009B", bytes); + + var kilobytes = bytes / 1024; + if (kilobytes < 100) + return WebInspector.UIString("%.1f\u2009KB", kilobytes); + if (kilobytes < 1024) + return WebInspector.UIString("%.0f\u2009KB", kilobytes); + + var megabytes = kilobytes / 1024; + if (megabytes < 100) + return WebInspector.UIString("%.1f\u2009MB", megabytes); + else + return WebInspector.UIString("%.0f\u2009MB", megabytes); +} + +/** + * @param {number} num + * @return {string} + */ +Number.withThousandsSeparator = function(num) +{ + var str = num + ""; + var re = /(\d+)(\d{3})/; + while (str.match(re)) + str = str.replace(re, "$1\u2009$2"); // \u2009 is a thin space. + return str; +} + +/** + * @return {boolean} + */ +WebInspector.useLowerCaseMenuTitles = function() +{ + return WebInspector.platform() === "windows"; +} + +/** + * @param {string} format + * @param {?Array.<string>} substitutions + * @param {!Object.<string, function(string, ...):*>} formatters + * @param {string} initialValue + * @param {function(string, string): ?} append + * @return {!{formattedResult: string, unusedSubstitutions: ?Array.<string>}}; + */ +WebInspector.formatLocalized = function(format, substitutions, formatters, initialValue, append) +{ + return String.format(WebInspector.UIString(format), substitutions, formatters, initialValue, append); +} + +/** + * @return {string} + */ +WebInspector.openLinkExternallyLabel = function() +{ + return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Open link in new tab" : "Open Link in New Tab"); +} + +/** + * @return {string} + */ +WebInspector.copyLinkAddressLabel = function() +{ + return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy link address" : "Copy Link Address"); +} + +WebInspector.installPortStyles = function() +{ + var platform = WebInspector.platform(); + document.body.classList.add("platform-" + platform); + var flavor = WebInspector.platformFlavor(); + if (flavor) + document.body.classList.add("platform-" + flavor); + var port = WebInspector.port(); + document.body.classList.add("port-" + port); +} + +WebInspector._windowFocused = function(event) +{ + if (event.target.document.nodeType === Node.DOCUMENT_NODE) + document.body.classList.remove("inactive"); +} + +WebInspector._windowBlurred = function(event) +{ + if (event.target.document.nodeType === Node.DOCUMENT_NODE) + document.body.classList.add("inactive"); +} + +/** + * @return {!Element} + */ +WebInspector.previousFocusElement = function() +{ + return WebInspector._previousFocusElement; +} + +/** + * @return {!Element} + */ +WebInspector.currentFocusElement = function() +{ + return WebInspector._currentFocusElement; +} + +WebInspector._focusChanged = function(event) +{ + WebInspector.setCurrentFocusElement(event.target); +} + +WebInspector._documentBlurred = function(event) +{ + // We want to know when currentFocusElement loses focus to nowhere. + // This is the case when event.relatedTarget is null (no element is being focused) + // and document.activeElement is reset to default (this is not a window blur). + if (!event.relatedTarget && document.activeElement === document.body) + WebInspector.setCurrentFocusElement(null); +} + +WebInspector._textInputTypes = ["text", "search", "tel", "url", "email", "password"].keySet(); +WebInspector._isTextEditingElement = function(element) +{ + if (element instanceof HTMLInputElement) + return element.type in WebInspector._textInputTypes; + + if (element instanceof HTMLTextAreaElement) + return true; + + return false; +} + +WebInspector.setCurrentFocusElement = function(x) +{ + if (WebInspector._glassPane && x && !WebInspector._glassPane.element.isAncestor(x)) + return; + if (WebInspector._currentFocusElement !== x) + WebInspector._previousFocusElement = WebInspector._currentFocusElement; + WebInspector._currentFocusElement = x; + + if (WebInspector._currentFocusElement) { + WebInspector._currentFocusElement.focus(); + + // Make a caret selection inside the new element if there isn't a range selection and there isn't already a caret selection inside. + // This is needed (at least) to remove caret from console when focus is moved to some element in the panel. + // The code below should not be applied to text fields and text areas, hence _isTextEditingElement check. + var selection = window.getSelection(); + if (!WebInspector._isTextEditingElement(WebInspector._currentFocusElement) && selection.isCollapsed && !WebInspector._currentFocusElement.isInsertionCaretInside()) { + var selectionRange = WebInspector._currentFocusElement.ownerDocument.createRange(); + selectionRange.setStart(WebInspector._currentFocusElement, 0); + selectionRange.setEnd(WebInspector._currentFocusElement, 0); + + selection.removeAllRanges(); + selection.addRange(selectionRange); + } + } else if (WebInspector._previousFocusElement) + WebInspector._previousFocusElement.blur(); +} + +WebInspector.restoreFocusFromElement = function(element) +{ + if (element && element.isSelfOrAncestor(WebInspector.currentFocusElement())) + WebInspector.setCurrentFocusElement(WebInspector.previousFocusElement()); +} + +WebInspector.setToolbarColors = function(backgroundColor, color) +{ + if (!WebInspector._themeStyleElement) { + WebInspector._themeStyleElement = document.createElement("style"); + document.head.appendChild(WebInspector._themeStyleElement); + } + var parsedColor = WebInspector.Color.parse(color); + var shadowColor = parsedColor ? parsedColor.invert().setAlpha(0.33).toString(WebInspector.Color.Format.RGBA) : "white"; + var prefix = WebInspector.isMac() ? "body:not(.undocked)" : ""; + WebInspector._themeStyleElement.textContent = + String.sprintf( + "%s .toolbar-background {\ + background-image: none !important;\ + background-color: %s !important;\ + color: %s !important;\ + }", prefix, backgroundColor, color) + + String.sprintf( + "%s .toolbar-background button.status-bar-item .glyph, %s .toolbar-background button.status-bar-item .long-click-glyph {\ + background-color: %s;\ + }", prefix, prefix, color) + + String.sprintf( + "%s .toolbar-background button.status-bar-item .glyph.shadow, %s .toolbar-background button.status-bar-item .long-click-glyph.shadow {\ + background-color: %s;\ + }", prefix, prefix, shadowColor); +} + +WebInspector.resetToolbarColors = function() +{ + if (WebInspector._themeStyleElement) + WebInspector._themeStyleElement.textContent = ""; +} + +/** + * @param {!Element} element + * @param {number} offset + * @param {number} length + * @param {!Array.<!Object>=} domChanges + * @return {?Element} + */ +WebInspector.highlightSearchResult = function(element, offset, length, domChanges) +{ + var result = WebInspector.highlightSearchResults(element, [new WebInspector.SourceRange(offset, length)], domChanges); + return result.length ? result[0] : null; +} + +/** + * @param {!Element} element + * @param {!Array.<!WebInspector.SourceRange>} resultRanges + * @param {!Array.<!Object>=} changes + * @return {!Array.<!Element>} + */ +WebInspector.highlightSearchResults = function(element, resultRanges, changes) +{ + return WebInspector.highlightRangesWithStyleClass(element, resultRanges, "highlighted-search-result", changes); +} + +/** + * @param {!Element} element + * @param {string} className + */ +WebInspector.runCSSAnimationOnce = function(element, className) +{ + function animationEndCallback() + { + element.classList.remove(className); + element.removeEventListener("animationend", animationEndCallback, false); + } + + if (element.classList.contains(className)) + element.classList.remove(className); + + element.addEventListener("animationend", animationEndCallback, false); + element.classList.add(className); +} + +/** + * @param {!Element} element + * @param {!Array.<!WebInspector.SourceRange>} resultRanges + * @param {string} styleClass + * @param {!Array.<!Object>=} changes + * @return {!Array.<!Element>} + */ +WebInspector.highlightRangesWithStyleClass = function(element, resultRanges, styleClass, changes) +{ + changes = changes || []; + var highlightNodes = []; + var lineText = element.textContent; + var ownerDocument = element.ownerDocument; + var textNodeSnapshot = ownerDocument.evaluate(".//text()", element, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + + var snapshotLength = textNodeSnapshot.snapshotLength; + if (snapshotLength === 0) + return highlightNodes; + + var nodeRanges = []; + var rangeEndOffset = 0; + for (var i = 0; i < snapshotLength; ++i) { + var range = {}; + range.offset = rangeEndOffset; + range.length = textNodeSnapshot.snapshotItem(i).textContent.length; + rangeEndOffset = range.offset + range.length; + nodeRanges.push(range); + } + + var startIndex = 0; + for (var i = 0; i < resultRanges.length; ++i) { + var startOffset = resultRanges[i].offset; + var endOffset = startOffset + resultRanges[i].length; + + while (startIndex < snapshotLength && nodeRanges[startIndex].offset + nodeRanges[startIndex].length <= startOffset) + startIndex++; + var endIndex = startIndex; + while (endIndex < snapshotLength && nodeRanges[endIndex].offset + nodeRanges[endIndex].length < endOffset) + endIndex++; + if (endIndex === snapshotLength) + break; + + var highlightNode = ownerDocument.createElement("span"); + highlightNode.className = styleClass; + highlightNode.textContent = lineText.substring(startOffset, endOffset); + + var lastTextNode = textNodeSnapshot.snapshotItem(endIndex); + var lastText = lastTextNode.textContent; + lastTextNode.textContent = lastText.substring(endOffset - nodeRanges[endIndex].offset); + changes.push({ node: lastTextNode, type: "changed", oldText: lastText, newText: lastTextNode.textContent }); + + if (startIndex === endIndex) { + lastTextNode.parentElement.insertBefore(highlightNode, lastTextNode); + changes.push({ node: highlightNode, type: "added", nextSibling: lastTextNode, parent: lastTextNode.parentElement }); + highlightNodes.push(highlightNode); + + var prefixNode = ownerDocument.createTextNode(lastText.substring(0, startOffset - nodeRanges[startIndex].offset)); + lastTextNode.parentElement.insertBefore(prefixNode, highlightNode); + changes.push({ node: prefixNode, type: "added", nextSibling: highlightNode, parent: lastTextNode.parentElement }); + } else { + var firstTextNode = textNodeSnapshot.snapshotItem(startIndex); + var firstText = firstTextNode.textContent; + var anchorElement = firstTextNode.nextSibling; + + firstTextNode.parentElement.insertBefore(highlightNode, anchorElement); + changes.push({ node: highlightNode, type: "added", nextSibling: anchorElement, parent: firstTextNode.parentElement }); + highlightNodes.push(highlightNode); + + firstTextNode.textContent = firstText.substring(0, startOffset - nodeRanges[startIndex].offset); + changes.push({ node: firstTextNode, type: "changed", oldText: firstText, newText: firstTextNode.textContent }); + + for (var j = startIndex + 1; j < endIndex; j++) { + var textNode = textNodeSnapshot.snapshotItem(j); + var text = textNode.textContent; + textNode.textContent = ""; + changes.push({ node: textNode, type: "changed", oldText: text, newText: textNode.textContent }); + } + } + startIndex = endIndex; + nodeRanges[startIndex].offset = endOffset; + nodeRanges[startIndex].length = lastTextNode.textContent.length; + + } + return highlightNodes; +} + +WebInspector.applyDomChanges = function(domChanges) +{ + for (var i = 0, size = domChanges.length; i < size; ++i) { + var entry = domChanges[i]; + switch (entry.type) { + case "added": + entry.parent.insertBefore(entry.node, entry.nextSibling); + break; + case "changed": + entry.node.textContent = entry.newText; + break; + } + } +} + +WebInspector.revertDomChanges = function(domChanges) +{ + for (var i = domChanges.length - 1; i >= 0; --i) { + var entry = domChanges[i]; + switch (entry.type) { + case "added": + entry.node.remove(); + break; + case "changed": + entry.node.textContent = entry.oldText; + break; + } + } +} + +/** + * @constructor + * @param {boolean} autoInvoke + */ +WebInspector.InvokeOnceHandlers = function(autoInvoke) +{ + this._handlers = null; + this._autoInvoke = autoInvoke; +} + +WebInspector.InvokeOnceHandlers.prototype = { + /** + * @param {!Object} object + * @param {function()} method + */ + add: function(object, method) + { + if (!this._handlers) { + this._handlers = new Map(); + if (this._autoInvoke) + this.scheduleInvoke(); + } + var methods = this._handlers.get(object); + if (!methods) { + methods = new Set(); + this._handlers.put(object, methods); + } + methods.add(method); + }, + + scheduleInvoke: function() + { + if (this._handlers) + requestAnimationFrame(this._invoke.bind(this)); + }, + + _invoke: function() + { + var handlers = this._handlers; + this._handlers = null; + var keys = handlers.keys(); + for (var i = 0; i < keys.length; ++i) { + var object = keys[i]; + var methods = handlers.get(object).values(); + for (var j = 0; j < methods.length; ++j) + methods[j].call(object); + } + } +} + +WebInspector._coalescingLevel = 0; +WebInspector._postUpdateHandlers = null; + +WebInspector.startBatchUpdate = function() +{ + if (!WebInspector._coalescingLevel++) + WebInspector._postUpdateHandlers = new WebInspector.InvokeOnceHandlers(false); +} + +WebInspector.endBatchUpdate = function() +{ + if (--WebInspector._coalescingLevel) + return; + WebInspector._postUpdateHandlers.scheduleInvoke(); + WebInspector._postUpdateHandlers = null; +} + +/** + * @param {!Object} object + * @param {function()} method + */ +WebInspector.invokeOnceAfterBatchUpdate = function(object, method) +{ + if (!WebInspector._postUpdateHandlers) + WebInspector._postUpdateHandlers = new WebInspector.InvokeOnceHandlers(true); + WebInspector._postUpdateHandlers.add(object, method); +} + +;(function() { + +function windowLoaded() +{ + window.addEventListener("focus", WebInspector._windowFocused, false); + window.addEventListener("blur", WebInspector._windowBlurred, false); + document.addEventListener("focus", WebInspector._focusChanged, true); + document.addEventListener("blur", WebInspector._documentBlurred, true); + window.removeEventListener("DOMContentLoaded", windowLoaded, false); +} + +window.addEventListener("DOMContentLoaded", windowLoaded, false); + +})(); diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/View.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/View.js new file mode 100644 index 00000000000..3a841c088b5 --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/View.js @@ -0,0 +1,703 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * Copyright (C) 2011 Google 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. + */ + +/** + * @constructor + * @extends {WebInspector.Object} + */ +WebInspector.View = function() +{ + this.element = document.createElement("div"); + this.element.className = "view"; + this.element.__view = this; + this._visible = true; + this._isRoot = false; + this._isShowing = false; + this._children = []; + this._hideOnDetach = false; + this._cssFiles = []; + this._notificationDepth = 0; +} + +WebInspector.View._cssFileToVisibleViewCount = {}; +WebInspector.View._cssFileToStyleElement = {}; +WebInspector.View._cssUnloadTimeout = 2000; + +WebInspector.View._buildSourceURL = function(cssFile) +{ + return "\n/*# sourceURL=" + WebInspector.ParsedURL.completeURL(window.location.href, cssFile) + " */"; +} + +/** + * @param {string} cssFile + * @return {!Element} + */ +WebInspector.View.createStyleElement = function(cssFile) +{ + var styleElement = document.createElement("style"); + styleElement.type = "text/css"; + styleElement.textContent = loadResource(cssFile) + WebInspector.View._buildSourceURL(cssFile); + document.head.insertBefore(styleElement, document.head.firstChild); + return styleElement; +} + +WebInspector.View.prototype = { + markAsRoot: function() + { + WebInspector.View._assert(!this.element.parentElement, "Attempt to mark as root attached node"); + this._isRoot = true; + }, + + /** + * @return {?WebInspector.View} + */ + parentView: function() + { + return this._parentView; + }, + + /** + * @return {!Array.<!WebInspector.View>} + */ + children: function() + { + return this._children; + }, + + /** + * @return {boolean} + */ + isShowing: function() + { + return this._isShowing; + }, + + setHideOnDetach: function() + { + this._hideOnDetach = true; + }, + + /** + * @return {boolean} + */ + _inNotification: function() + { + return !!this._notificationDepth || (this._parentView && this._parentView._inNotification()); + }, + + _parentIsShowing: function() + { + if (this._isRoot) + return true; + return this._parentView && this._parentView.isShowing(); + }, + + /** + * @param {function(this:WebInspector.View)} method + */ + _callOnVisibleChildren: function(method) + { + var copy = this._children.slice(); + for (var i = 0; i < copy.length; ++i) { + if (copy[i]._parentView === this && copy[i]._visible) + method.call(copy[i]); + } + }, + + _processWillShow: function() + { + this._loadCSSIfNeeded(); + this._callOnVisibleChildren(this._processWillShow); + this._isShowing = true; + }, + + _processWasShown: function() + { + if (this._inNotification()) + return; + this.restoreScrollPositions(); + this._notify(this.wasShown); + this._callOnVisibleChildren(this._processWasShown); + }, + + _processWillHide: function() + { + if (this._inNotification()) + return; + this.storeScrollPositions(); + + this._callOnVisibleChildren(this._processWillHide); + this._notify(this.willHide); + this._isShowing = false; + }, + + _processWasHidden: function() + { + this._disableCSSIfNeeded(); + this._callOnVisibleChildren(this._processWasHidden); + }, + + _processOnResize: function() + { + if (this._inNotification()) + return; + if (!this.isShowing()) + return; + this._notify(this.onResize); + this._callOnVisibleChildren(this._processOnResize); + }, + + /** + * @param {function(this:WebInspector.View)} notification + */ + _notify: function(notification) + { + ++this._notificationDepth; + try { + notification.call(this); + } finally { + --this._notificationDepth; + } + }, + + wasShown: function() + { + }, + + willHide: function() + { + }, + + onResize: function() + { + }, + + onLayout: function() + { + }, + + /** + * @param {?Element} parentElement + * @param {?Element=} insertBefore + */ + show: function(parentElement, insertBefore) + { + WebInspector.View._assert(parentElement, "Attempt to attach view with no parent element"); + + // Update view hierarchy + if (this.element.parentElement !== parentElement) { + if (this.element.parentElement) + this.detach(); + + var currentParent = parentElement; + while (currentParent && !currentParent.__view) + currentParent = currentParent.parentElement; + + if (currentParent) { + this._parentView = currentParent.__view; + this._parentView._children.push(this); + this._isRoot = false; + } else + WebInspector.View._assert(this._isRoot, "Attempt to attach view to orphan node"); + } else if (this._visible) { + return; + } + + this._visible = true; + + if (this._parentIsShowing()) + this._processWillShow(); + + this.element.classList.add("visible"); + + // Reparent + if (this.element.parentElement !== parentElement) { + WebInspector.View._incrementViewCounter(parentElement, this.element); + if (insertBefore) + WebInspector.View._originalInsertBefore.call(parentElement, this.element, insertBefore); + else + WebInspector.View._originalAppendChild.call(parentElement, this.element); + } + + if (this._parentIsShowing()) + this._processWasShown(); + + if (this._parentView && this._hasNonZeroConstraints()) + this._parentView.invalidateConstraints(); + else + this._processOnResize(); + }, + + /** + * @param {boolean=} overrideHideOnDetach + */ + detach: function(overrideHideOnDetach) + { + var parentElement = this.element.parentElement; + if (!parentElement) + return; + + if (this._parentIsShowing()) + this._processWillHide(); + + if (this._hideOnDetach && !overrideHideOnDetach) { + this.element.classList.remove("visible"); + this._visible = false; + if (this._parentIsShowing()) + this._processWasHidden(); + if (this._parentView && this._hasNonZeroConstraints()) + this._parentView.invalidateConstraints(); + return; + } + + // Force legal removal + WebInspector.View._decrementViewCounter(parentElement, this.element); + WebInspector.View._originalRemoveChild.call(parentElement, this.element); + + this._visible = false; + if (this._parentIsShowing()) + this._processWasHidden(); + + // Update view hierarchy + if (this._parentView) { + var childIndex = this._parentView._children.indexOf(this); + WebInspector.View._assert(childIndex >= 0, "Attempt to remove non-child view"); + this._parentView._children.splice(childIndex, 1); + var parent = this._parentView; + this._parentView = null; + if (this._hasNonZeroConstraints()) + parent.invalidateConstraints(); + } else + WebInspector.View._assert(this._isRoot, "Removing non-root view from DOM"); + }, + + detachChildViews: function() + { + var children = this._children.slice(); + for (var i = 0; i < children.length; ++i) + children[i].detach(); + }, + + /** + * @return {!Array.<!Element>} + */ + elementsToRestoreScrollPositionsFor: function() + { + return [this.element]; + }, + + storeScrollPositions: function() + { + var elements = this.elementsToRestoreScrollPositionsFor(); + for (var i = 0; i < elements.length; ++i) { + var container = elements[i]; + container._scrollTop = container.scrollTop; + container._scrollLeft = container.scrollLeft; + } + }, + + restoreScrollPositions: function() + { + var elements = this.elementsToRestoreScrollPositionsFor(); + for (var i = 0; i < elements.length; ++i) { + var container = elements[i]; + if (container._scrollTop) + container.scrollTop = container._scrollTop; + if (container._scrollLeft) + container.scrollLeft = container._scrollLeft; + } + }, + + doResize: function() + { + if (!this.isShowing()) + return; + // No matter what notification we are in, dispatching onResize is not needed. + if (!this._inNotification()) + this._callOnVisibleChildren(this._processOnResize); + }, + + doLayout: function() + { + if (!this.isShowing()) + return; + this._notify(this.onLayout); + this.doResize(); + }, + + registerRequiredCSS: function(cssFile) + { + this._cssFiles.push(cssFile); + }, + + _loadCSSIfNeeded: function() + { + for (var i = 0; i < this._cssFiles.length; ++i) { + var cssFile = this._cssFiles[i]; + + var viewsWithCSSFile = WebInspector.View._cssFileToVisibleViewCount[cssFile]; + WebInspector.View._cssFileToVisibleViewCount[cssFile] = (viewsWithCSSFile || 0) + 1; + if (!viewsWithCSSFile) + this._doLoadCSS(cssFile); + } + }, + + _doLoadCSS: function(cssFile) + { + var styleElement = WebInspector.View._cssFileToStyleElement[cssFile]; + if (styleElement) { + styleElement.disabled = false; + return; + } + styleElement = WebInspector.View.createStyleElement(cssFile); + WebInspector.View._cssFileToStyleElement[cssFile] = styleElement; + }, + + _disableCSSIfNeeded: function() + { + var scheduleUnload = !!WebInspector.View._cssUnloadTimer; + + for (var i = 0; i < this._cssFiles.length; ++i) { + var cssFile = this._cssFiles[i]; + + if (!--WebInspector.View._cssFileToVisibleViewCount[cssFile]) + scheduleUnload = true; + } + + function doUnloadCSS() + { + delete WebInspector.View._cssUnloadTimer; + + for (cssFile in WebInspector.View._cssFileToVisibleViewCount) { + if (WebInspector.View._cssFileToVisibleViewCount.hasOwnProperty(cssFile) && !WebInspector.View._cssFileToVisibleViewCount[cssFile]) + WebInspector.View._cssFileToStyleElement[cssFile].disabled = true; + } + } + + if (scheduleUnload && !WebInspector.View._cssUnloadTimer) + WebInspector.View._cssUnloadTimer = setTimeout(doUnloadCSS, WebInspector.View._cssUnloadTimeout); + }, + + printViewHierarchy: function() + { + var lines = []; + this._collectViewHierarchy("", lines); + console.log(lines.join("\n")); + }, + + _collectViewHierarchy: function(prefix, lines) + { + lines.push(prefix + "[" + this.element.className + "]" + (this._children.length ? " {" : "")); + + for (var i = 0; i < this._children.length; ++i) + this._children[i]._collectViewHierarchy(prefix + " ", lines); + + if (this._children.length) + lines.push(prefix + "}"); + }, + + /** + * @return {!Element} + */ + defaultFocusedElement: function() + { + return this._defaultFocusedElement || this.element; + }, + + /** + * @param {!Element} element + */ + setDefaultFocusedElement: function(element) + { + this._defaultFocusedElement = element; + }, + + focus: function() + { + var element = this.defaultFocusedElement(); + if (!element || element.isAncestor(document.activeElement)) + return; + + WebInspector.setCurrentFocusElement(element); + }, + + /** + * @return {boolean} + */ + hasFocus: function() + { + var activeElement = document.activeElement; + return activeElement && activeElement.isSelfOrDescendant(this.element); + }, + + /** + * @return {!Size} + */ + measurePreferredSize: function() + { + this._loadCSSIfNeeded(); + WebInspector.View._originalAppendChild.call(document.body, this.element); + this.element.positionAt(0, 0); + var result = new Size(this.element.offsetWidth, this.element.offsetHeight); + this.element.positionAt(undefined, undefined); + WebInspector.View._originalRemoveChild.call(document.body, this.element); + this._disableCSSIfNeeded(); + return result; + }, + + /** + * @return {!Constraints} + */ + calculateConstraints: function() + { + return new Constraints(new Size(0, 0)); + }, + + /** + * @return {!Constraints} + */ + constraints: function() + { + if (typeof this._constraints !== "undefined") + return this._constraints; + if (typeof this._cachedConstraints === "undefined") + this._cachedConstraints = this.calculateConstraints(); + return this._cachedConstraints; + }, + + /** + * @param {number} width + * @param {number} height + * @param {number} preferredWidth + * @param {number} preferredHeight + */ + setMinimumAndPreferredSizes: function(width, height, preferredWidth, preferredHeight) + { + this._constraints = new Constraints(new Size(width, height), new Size(preferredWidth, preferredHeight)); + this.invalidateConstraints(); + }, + + /** + * @param {number} width + * @param {number} height + */ + setMinimumSize: function(width, height) + { + this._constraints = new Constraints(new Size(width, height)); + this.invalidateConstraints(); + }, + + /** + * @return {boolean} + */ + _hasNonZeroConstraints: function() + { + var constraints = this.constraints(); + return !!(constraints.minimum.width || constraints.minimum.height || constraints.preferred.width || constraints.preferred.height); + }, + + invalidateConstraints: function() + { + var cached = this._cachedConstraints; + delete this._cachedConstraints; + var actual = this.constraints(); + if (!actual.isEqual(cached) && this._parentView) + this._parentView.invalidateConstraints(); + else + this.doLayout(); + }, + + __proto__: WebInspector.Object.prototype +} + +WebInspector.View._originalAppendChild = Element.prototype.appendChild; +WebInspector.View._originalInsertBefore = Element.prototype.insertBefore; +WebInspector.View._originalRemoveChild = Element.prototype.removeChild; +WebInspector.View._originalRemoveChildren = Element.prototype.removeChildren; + +WebInspector.View._incrementViewCounter = function(parentElement, childElement) +{ + var count = (childElement.__viewCounter || 0) + (childElement.__view ? 1 : 0); + if (!count) + return; + + while (parentElement) { + parentElement.__viewCounter = (parentElement.__viewCounter || 0) + count; + parentElement = parentElement.parentElement; + } +} + +WebInspector.View._decrementViewCounter = function(parentElement, childElement) +{ + var count = (childElement.__viewCounter || 0) + (childElement.__view ? 1 : 0); + if (!count) + return; + + while (parentElement) { + parentElement.__viewCounter -= count; + parentElement = parentElement.parentElement; + } +} + +WebInspector.View._assert = function(condition, message) +{ + if (!condition) { + console.trace(); + throw new Error(message); + } +} + +/** + * @constructor + * @extends {WebInspector.View} + */ +WebInspector.VBox = function() +{ + WebInspector.View.call(this); + this.element.classList.add("vbox"); +}; + +WebInspector.VBox.prototype = { + /** + * @return {!Constraints} + */ + calculateConstraints: function() + { + var constraints = new Constraints(new Size(0, 0)); + + /** + * @this {!WebInspector.View} + * @suppressReceiverCheck + */ + function updateForChild() + { + var child = this.constraints(); + constraints = constraints.widthToMax(child); + constraints = constraints.addHeight(child); + } + + this._callOnVisibleChildren(updateForChild); + return constraints; + }, + + __proto__: WebInspector.View.prototype +}; + +/** + * @constructor + * @extends {WebInspector.View} + */ +WebInspector.HBox = function() +{ + WebInspector.View.call(this); + this.element.classList.add("hbox"); +}; + +WebInspector.HBox.prototype = { + /** + * @return {!Constraints} + */ + calculateConstraints: function() + { + var constraints = new Constraints(new Size(0, 0)); + + /** + * @this {!WebInspector.View} + * @suppressReceiverCheck + */ + function updateForChild() + { + var child = this.constraints(); + constraints = constraints.addWidth(child); + constraints = constraints.heightToMax(child); + } + + this._callOnVisibleChildren(updateForChild); + return constraints; + }, + + __proto__: WebInspector.View.prototype +}; + +/** + * @constructor + * @extends {WebInspector.VBox} + * @param {function()} resizeCallback + */ +WebInspector.VBoxWithResizeCallback = function(resizeCallback) +{ + WebInspector.VBox.call(this); + this._resizeCallback = resizeCallback; +} + +WebInspector.VBoxWithResizeCallback.prototype = { + onResize: function() + { + this._resizeCallback(); + }, + + __proto__: WebInspector.VBox.prototype +} + +/** + * @param {?Node} child + * @return {?Node} + * @suppress {duplicate} + */ +Element.prototype.appendChild = function(child) +{ + WebInspector.View._assert(!child.__view || child.parentElement === this, "Attempt to add view via regular DOM operation."); + return WebInspector.View._originalAppendChild.call(this, child); +} + +/** + * @param {?Node} child + * @param {?Node} anchor + * @return {?Node} + * @suppress {duplicate} + */ +Element.prototype.insertBefore = function(child, anchor) +{ + WebInspector.View._assert(!child.__view || child.parentElement === this, "Attempt to add view via regular DOM operation."); + return WebInspector.View._originalInsertBefore.call(this, child, anchor); +} + +/** + * @param {?Node} child + * @return {?Node} + * @suppress {duplicate} + */ +Element.prototype.removeChild = function(child) +{ + WebInspector.View._assert(!child.__viewCounter && !child.__view, "Attempt to remove element containing view via regular DOM operation"); + return WebInspector.View._originalRemoveChild.call(this, child); +} + +Element.prototype.removeChildren = function() +{ + WebInspector.View._assert(!this.__viewCounter, "Attempt to remove element containing view via regular DOM operation"); + WebInspector.View._originalRemoveChildren.call(this); +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/ViewportControl.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/ViewportControl.js new file mode 100644 index 00000000000..87d91652ad3 --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/ViewportControl.js @@ -0,0 +1,529 @@ +/* + * Copyright (C) 2013 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER 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. + */ + +/** + * @constructor + * @param {!WebInspector.ViewportControl.Provider} provider + */ +WebInspector.ViewportControl = function(provider) +{ + this.element = document.createElement("div"); + this.element.style.overflow = "auto"; + this._topGapElement = this.element.createChild("div", "viewport-control-gap-element"); + this._topGapElement.textContent = "."; + this._topGapElement.style.height = "0px"; + this._contentElement = this.element.createChild("div"); + this._bottomGapElement = this.element.createChild("div", "viewport-control-gap-element"); + this._bottomGapElement.textContent = "."; + this._bottomGapElement.style.height = "0px"; + + this._provider = provider; + this.element.addEventListener("scroll", this._onScroll.bind(this), false); + this.element.addEventListener("copy", this._onCopy.bind(this), false); + this.element.addEventListener("dragstart", this._onDragStart.bind(this), false); + + this._firstVisibleIndex = 0; + this._lastVisibleIndex = -1; + this._renderedItems = []; + this._anchorSelection = null; + this._headSelection = null; + this._stickToBottom = false; +} + +/** + * @interface + */ +WebInspector.ViewportControl.Provider = function() +{ +} + +WebInspector.ViewportControl.Provider.prototype = { + /** + * @param {number} index + * @return {number} + */ + fastHeight: function(index) { return 0; }, + + /** + * @return {number} + */ + itemCount: function() { return 0; }, + + /** + * @return {number} + */ + minimumRowHeight: function() { return 0; }, + + /** + * @param {number} index + * @return {?WebInspector.ViewportElement} + */ + itemElement: function(index) { return null; } +} + +/** + * @interface + */ +WebInspector.ViewportElement = function() { } +WebInspector.ViewportElement.prototype = { + cacheFastHeight: function() { }, + + willHide: function() { }, + + wasShown: function() { }, + + /** + * @return {!Element} + */ + element: function() { }, +} + +/** + * @constructor + * @implements {WebInspector.ViewportElement} + * @param {!Element} element + */ +WebInspector.StaticViewportElement = function(element) +{ + this._element = element; +} + +WebInspector.StaticViewportElement.prototype = { + cacheFastHeight: function() { }, + + willHide: function() { }, + + wasShown: function() { }, + + /** + * @return {!Element} + */ + element: function() + { + return this._element; + }, +} + +WebInspector.ViewportControl.prototype = { + /** + * @param {boolean} value + */ + setStickToBottom: function(value) + { + this._stickToBottom = value; + }, + + /** + * @param {?Event} event + */ + _onCopy: function(event) + { + var text = this._selectedText(); + if (!text) + return; + event.preventDefault(); + event.clipboardData.setData("text/plain", text); + }, + + /** + * @param {?Event} event + */ + _onDragStart: function(event) + { + var text = this._selectedText(); + if (!text) + return false; + event.dataTransfer.clearData(); + event.dataTransfer.setData("text/plain", text); + event.dataTransfer.effectAllowed = "copy"; + return true; + }, + + /** + * @return {!Element} + */ + contentElement: function() + { + return this._contentElement; + }, + + invalidate: function() + { + delete this._cumulativeHeights; + this.refresh(); + }, + + _rebuildCumulativeHeightsIfNeeded: function() + { + if (this._cumulativeHeights) + return; + var itemCount = this._provider.itemCount(); + if (!itemCount) + return; + this._cumulativeHeights = new Int32Array(itemCount); + this._cumulativeHeights[0] = this._provider.fastHeight(0); + for (var i = 1; i < itemCount; ++i) + this._cumulativeHeights[i] = this._cumulativeHeights[i - 1] + this._provider.fastHeight(i); + }, + + /** + * @param {number} index + * @return {number} + */ + _cachedItemHeight: function(index) + { + return index === 0 ? this._cumulativeHeights[0] : this._cumulativeHeights[index] - this._cumulativeHeights[index - 1]; + }, + + /** + * @param {?Selection} selection + */ + _isSelectionBackwards: function(selection) + { + if (!selection || !selection.rangeCount) + return false; + var range = document.createRange(); + range.setStart(selection.anchorNode, selection.anchorOffset); + range.setEnd(selection.focusNode, selection.focusOffset); + return range.collapsed; + }, + + /** + * @param {number} itemIndex + * @param {!Node} node + * @param {number} offset + * @return {!{item: number, node: !Node, offset: number}} + */ + _createSelectionModel: function(itemIndex, node, offset) + { + return { + item: itemIndex, + node: node, + offset: offset + }; + }, + + /** + * @param {?Selection} selection + */ + _updateSelectionModel: function(selection) + { + if (!selection || !selection.rangeCount) { + this._headSelection = null; + this._anchorSelection = null; + return false; + } + + var firstSelected = Number.MAX_VALUE; + var lastSelected = -1; + + var range = selection.getRangeAt(0); + var hasVisibleSelection = false; + for (var i = 0; i < this._renderedItems.length; ++i) { + if (range.intersectsNode(this._renderedItems[i].element())) { + var index = i + this._firstVisibleIndex; + firstSelected = Math.min(firstSelected, index); + lastSelected = Math.max(lastSelected, index); + hasVisibleSelection = true; + } + } + if (hasVisibleSelection) { + firstSelected = this._createSelectionModel(firstSelected, /** @type {!Node} */(range.startContainer), range.startOffset); + lastSelected = this._createSelectionModel(lastSelected, /** @type {!Node} */(range.endContainer), range.endOffset); + } + var topOverlap = range.intersectsNode(this._topGapElement) && this._topGapElement._active; + var bottomOverlap = range.intersectsNode(this._bottomGapElement) && this._bottomGapElement._active; + if (!topOverlap && !bottomOverlap && !hasVisibleSelection) { + this._headSelection = null; + this._anchorSelection = null; + return false; + } + + if (!this._anchorSelection || !this._headSelection) { + this._anchorSelection = this._createSelectionModel(0, this.element, 0); + this._headSelection = this._createSelectionModel(this._provider.itemCount() - 1, this.element, this.element.children.length); + this._selectionIsBackward = false; + } + + var isBackward = this._isSelectionBackwards(selection); + var startSelection = this._selectionIsBackward ? this._headSelection : this._anchorSelection; + var endSelection = this._selectionIsBackward ? this._anchorSelection : this._headSelection; + if (topOverlap && bottomOverlap && hasVisibleSelection) { + firstSelected = firstSelected.item < startSelection.item ? firstSelected : startSelection; + lastSelected = lastSelected.item > endSelection.item ? lastSelected : endSelection; + } else if (!hasVisibleSelection) { + firstSelected = startSelection; + lastSelected = endSelection; + } else if (topOverlap) + firstSelected = isBackward ? this._headSelection : this._anchorSelection; + else if (bottomOverlap) + lastSelected = isBackward ? this._anchorSelection : this._headSelection; + + if (isBackward) { + this._anchorSelection = lastSelected; + this._headSelection = firstSelected; + } else { + this._anchorSelection = firstSelected; + this._headSelection = lastSelected; + } + this._selectionIsBackward = isBackward; + return true; + }, + + /** + * @param {?Selection} selection + */ + _restoreSelection: function(selection) + { + var anchorElement = null; + var anchorOffset; + if (this._firstVisibleIndex <= this._anchorSelection.item && this._anchorSelection.item <= this._lastVisibleIndex) { + anchorElement = this._anchorSelection.node; + anchorOffset = this._anchorSelection.offset; + } else { + if (this._anchorSelection.item < this._firstVisibleIndex) + anchorElement = this._topGapElement; + else if (this._anchorSelection.item > this._lastVisibleIndex) + anchorElement = this._bottomGapElement; + anchorOffset = this._selectionIsBackward ? 1 : 0; + } + + var headElement = null; + var headOffset; + if (this._firstVisibleIndex <= this._headSelection.item && this._headSelection.item <= this._lastVisibleIndex) { + headElement = this._headSelection.node; + headOffset = this._headSelection.offset; + } else { + if (this._headSelection.item < this._firstVisibleIndex) + headElement = this._topGapElement; + else if (this._headSelection.item > this._lastVisibleIndex) + headElement = this._bottomGapElement; + headOffset = this._selectionIsBackward ? 0 : 1; + } + + selection.setBaseAndExtent(anchorElement, anchorOffset, headElement, headOffset); + }, + + refresh: function() + { + if (!this.element.clientHeight) + return; // Do nothing for invisible controls. + + var itemCount = this._provider.itemCount(); + if (!itemCount) { + for (var i = 0; i < this._renderedItems.length; ++i) + this._renderedItems[i].cacheFastHeight(); + for (var i = 0; i < this._renderedItems.length; ++i) + this._renderedItems[i].willHide(); + this._renderedItems = []; + this._contentElement.removeChildren(); + this._topGapElement.style.height = "0px"; + this._bottomGapElement.style.height = "0px"; + this._firstVisibleIndex = -1; + this._lastVisibleIndex = -1; + return; + } + + var selection = window.getSelection(); + var shouldRestoreSelection = this._updateSelectionModel(selection); + + var visibleFrom = this.element.scrollTop; + var clientHeight = this.element.clientHeight; + var shouldStickToBottom = this._stickToBottom && this.element.isScrolledToBottom(); + + if (this._cumulativeHeights && itemCount !== this._cumulativeHeights.length) + delete this._cumulativeHeights; + for (var i = 0; i < this._renderedItems.length; ++i) { + this._renderedItems[i].cacheFastHeight(); + // Tolerate 1-pixel error due to double-to-integer rounding errors. + if (this._cumulativeHeights && Math.abs(this._cachedItemHeight(this._firstVisibleIndex + i) - this._provider.fastHeight(i + this._firstVisibleIndex)) > 1) + delete this._cumulativeHeights; + } + this._rebuildCumulativeHeightsIfNeeded(); + if (shouldStickToBottom) { + this._lastVisibleIndex = itemCount - 1; + this._firstVisibleIndex = Math.max(itemCount - Math.ceil(clientHeight / this._provider.minimumRowHeight()), 0); + } else { + this._firstVisibleIndex = Math.max(Array.prototype.lowerBound.call(this._cumulativeHeights, visibleFrom + 1), 0); + // Proactively render more rows in case some of them will be collapsed without triggering refresh. @see crbug.com/390169 + this._lastVisibleIndex = this._firstVisibleIndex + Math.ceil(clientHeight / this._provider.minimumRowHeight()) - 1; + this._lastVisibleIndex = Math.min(this._lastVisibleIndex, itemCount - 1); + } + var topGapHeight = this._cumulativeHeights[this._firstVisibleIndex - 1] || 0; + var bottomGapHeight = this._cumulativeHeights[this._cumulativeHeights.length - 1] - this._cumulativeHeights[this._lastVisibleIndex]; + + this._topGapElement.style.height = topGapHeight + "px"; + this._bottomGapElement.style.height = bottomGapHeight + "px"; + this._topGapElement._active = !!topGapHeight; + this._bottomGapElement._active = !!bottomGapHeight; + + this._contentElement.style.setProperty("height", "10000000px"); + for (var i = 0; i < this._renderedItems.length; ++i) + this._renderedItems[i].willHide(); + this._renderedItems = []; + this._contentElement.removeChildren(); + for (var i = this._firstVisibleIndex; i <= this._lastVisibleIndex; ++i) { + var viewportElement = this._provider.itemElement(i); + this._contentElement.appendChild(viewportElement.element()); + this._renderedItems.push(viewportElement); + viewportElement.wasShown(); + } + + this._contentElement.style.removeProperty("height"); + // Should be the last call in the method as it might force layout. + if (shouldRestoreSelection) + this._restoreSelection(selection); + if (shouldStickToBottom) + this.element.scrollTop = this.element.scrollHeight; + }, + + /** + * @return {?string} + */ + _selectedText: function() + { + this._updateSelectionModel(window.getSelection()); + if (!this._headSelection || !this._anchorSelection) + return null; + + var startSelection = null; + var endSelection = null; + if (this._selectionIsBackward) { + startSelection = this._headSelection; + endSelection = this._anchorSelection; + } else { + startSelection = this._anchorSelection; + endSelection = this._headSelection; + } + + var textLines = []; + for (var i = startSelection.item; i <= endSelection.item; ++i) + textLines.push(this._provider.itemElement(i).element().textContent); + + var endSelectionElement = this._provider.itemElement(endSelection.item).element(); + if (endSelection.node && endSelection.node.isSelfOrDescendant(endSelectionElement)) { + var itemTextOffset = this._textOffsetInNode(endSelectionElement, endSelection.node, endSelection.offset); + textLines[textLines.length - 1] = textLines.peekLast().substring(0, itemTextOffset); + } + + var startSelectionElement = this._provider.itemElement(startSelection.item).element(); + if (startSelection.node && startSelection.node.isSelfOrDescendant(startSelectionElement)) { + var itemTextOffset = this._textOffsetInNode(startSelectionElement, startSelection.node, startSelection.offset); + textLines[0] = textLines[0].substring(itemTextOffset); + } + + return textLines.join("\n"); + }, + + /** + * @param {!Element} itemElement + * @param {!Node} container + * @param {number} offset + * @return {number} + */ + _textOffsetInNode: function(itemElement, container, offset) + { + var chars = 0; + var node = itemElement; + while ((node = node.traverseNextTextNode()) && node !== container) + chars += node.textContent.length; + return chars + offset; + }, + + /** + * @param {?Event} event + */ + _onScroll: function(event) + { + this.refresh(); + }, + + /** + * @return {number} + */ + firstVisibleIndex: function() + { + return this._firstVisibleIndex; + }, + + /** + * @return {number} + */ + lastVisibleIndex: function() + { + return this._lastVisibleIndex; + }, + + /** + * @return {?Element} + */ + renderedElementAt: function(index) + { + if (index < this._firstVisibleIndex) + return null; + if (index > this._lastVisibleIndex) + return null; + return this._renderedItems[index - this._firstVisibleIndex].element(); + }, + + /** + * @param {number} index + * @param {boolean=} makeLast + */ + scrollItemIntoView: function(index, makeLast) + { + if (index > this._firstVisibleIndex && index < this._lastVisibleIndex) + return; + if (makeLast) + this.forceScrollItemToBeLast(index); + else if (index <= this._firstVisibleIndex) + this.forceScrollItemToBeFirst(index); + else if (index >= this._lastVisibleIndex) + this.forceScrollItemToBeLast(index); + }, + + /** + * @param {number} index + */ + forceScrollItemToBeFirst: function(index) + { + this._rebuildCumulativeHeightsIfNeeded(); + this.element.scrollTop = index > 0 ? this._cumulativeHeights[index - 1] : 0; + }, + + /** + * @param {number} index + */ + forceScrollItemToBeLast: function(index) + { + this._rebuildCumulativeHeightsIfNeeded(); + this.element.scrollTop = this._cumulativeHeights[index] - this.element.clientHeight; + } +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/ZoomManager.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/ZoomManager.js new file mode 100644 index 00000000000..83aa496aca5 --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/ZoomManager.js @@ -0,0 +1,42 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @constructor + * @extends {WebInspector.Object} + */ +WebInspector.ZoomManager = function() +{ + this._zoomFactor = InspectorFrontendHost.zoomFactor(); + window.addEventListener("resize", this._onWindowResize.bind(this), true); +}; + +WebInspector.ZoomManager.Events = { + ZoomChanged: "ZoomChanged" +}; + +WebInspector.ZoomManager.prototype = { + /** + * @return {number} + */ + zoomFactor: function() + { + return this._zoomFactor; + }, + + _onWindowResize: function() + { + var oldZoomFactor = this._zoomFactor; + this._zoomFactor = InspectorFrontendHost.zoomFactor(); + if (oldZoomFactor !== this._zoomFactor) + this.dispatchEventToListeners(WebInspector.ZoomManager.Events.ZoomChanged, {from: oldZoomFactor, to: this._zoomFactor}); + }, + + __proto__: WebInspector.Object.prototype +}; + +/** + * @type {!WebInspector.ZoomManager} + */ +WebInspector.zoomManager; diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/treeoutline.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/treeoutline.js new file mode 100644 index 00000000000..437b58e190d --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/treeoutline.js @@ -0,0 +1,990 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @param {!Element} listNode + * @param {boolean=} nonFocusable + */ +function TreeOutline(listNode, nonFocusable) +{ + /** @type {!Array.<!TreeElement>} */ + this.children = []; + this.selectedTreeElement = null; + this._childrenListNode = listNode; + this.childrenListElement = this._childrenListNode; + this._childrenListNode.removeChildren(); + this.expandTreeElementsWhenArrowing = false; + this.root = true; + this.hasChildren = false; + this.expanded = true; + this.selected = false; + this.treeOutline = this; + /** @type {?function(!TreeElement, !TreeElement):number} */ + this.comparator = null; + + this.setFocusable(!nonFocusable); + this._childrenListNode.addEventListener("keydown", this._treeKeyDown.bind(this), true); + + /** @type {!Map.<!Object, !Array.<!TreeElement>>} */ + this._treeElementsMap = new Map(); + /** @type {!Map.<!Object, boolean>} */ + this._expandedStateMap = new Map(); + this.element = listNode; +} + +TreeOutline.prototype.setFocusable = function(focusable) +{ + if (focusable) + this._childrenListNode.setAttribute("tabIndex", 0); + else + this._childrenListNode.removeAttribute("tabIndex"); +} + +/** + * @param {!TreeElement} child + */ +TreeOutline.prototype.appendChild = function(child) +{ + var insertionIndex; + if (this.treeOutline.comparator) + insertionIndex = insertionIndexForObjectInListSortedByFunction(child, this.children, this.treeOutline.comparator); + else + insertionIndex = this.children.length; + this.insertChild(child, insertionIndex); +} + +/** + * @param {!TreeElement} child + * @param {!TreeElement} beforeChild + */ +TreeOutline.prototype.insertBeforeChild = function(child, beforeChild) +{ + if (!child) + throw("child can't be undefined or null"); + + if (!beforeChild) + throw("beforeChild can't be undefined or null"); + + var childIndex = this.children.indexOf(beforeChild); + if (childIndex === -1) + throw("beforeChild not found in this node's children"); + + this.insertChild(child, childIndex); +} + +/** + * @param {!TreeElement} child + * @param {number} index + */ +TreeOutline.prototype.insertChild = function(child, index) +{ + if (!child) + throw("child can't be undefined or null"); + + 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.treeOutline = this.treeOutline; + child.treeOutline._rememberTreeElement(child); + + var current = child.children[0]; + while (current) { + current.treeOutline = this.treeOutline; + current.treeOutline._rememberTreeElement(current); + current = current.traverseNextTreeElement(false, child, true); + } + + if (child.hasChildren && typeof(child.treeOutline._expandedStateMap.get(child.representedObject)) !== "undefined") + child.expanded = child.treeOutline._expandedStateMap.get(child.representedObject); + + if (!this._childrenListNode) { + this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol"); + this._childrenListNode.parentTreeElement = this; + this._childrenListNode.classList.add("children"); + if (this.hidden) + this._childrenListNode.classList.add("hidden"); + } + + child._attach(); +} + +/** + * @param {number} childIndex + */ +TreeOutline.prototype.removeChildAtIndex = function(childIndex) +{ + if (childIndex < 0 || childIndex >= this.children.length) + throw("childIndex out of range"); + + var child = this.children[childIndex]; + this.children.splice(childIndex, 1); + + var parent = child.parent; + if (child.deselect()) { + if (child.previousSibling) + child.previousSibling.select(); + else if (child.nextSibling) + child.nextSibling.select(); + else + parent.select(); + } + + if (child.previousSibling) + child.previousSibling.nextSibling = child.nextSibling; + if (child.nextSibling) + child.nextSibling.previousSibling = child.previousSibling; + + if (child.treeOutline) { + child.treeOutline._forgetTreeElement(child); + child.treeOutline._forgetChildrenRecursive(child); + } + + child._detach(); + child.treeOutline = null; + child.parent = null; + child.nextSibling = null; + child.previousSibling = null; +} + +/** + * @param {!TreeElement} child + */ +TreeOutline.prototype.removeChild = function(child) +{ + if (!child) + throw("child can't be undefined or null"); + + var childIndex = this.children.indexOf(child); + if (childIndex === -1) + throw("child not found in this node's children"); + + this.removeChildAtIndex.call(this, childIndex); +} + +TreeOutline.prototype.removeChildren = function() +{ + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i]; + child.deselect(); + + if (child.treeOutline) { + child.treeOutline._forgetTreeElement(child); + child.treeOutline._forgetChildrenRecursive(child); + } + + child._detach(); + child.treeOutline = null; + child.parent = null; + child.nextSibling = null; + child.previousSibling = null; + } + + this.children = []; +} + +/** + * @param {!TreeElement} element + */ +TreeOutline.prototype._rememberTreeElement = function(element) +{ + if (!this._treeElementsMap.get(element.representedObject)) + this._treeElementsMap.put(element.representedObject, []); + + // check if the element is already known + var elements = this._treeElementsMap.get(element.representedObject); + if (elements.indexOf(element) !== -1) + return; + + // add the element + elements.push(element); +} + +/** + * @param {!TreeElement} element + */ +TreeOutline.prototype._forgetTreeElement = function(element) +{ + if (this._treeElementsMap.get(element.representedObject)) { + var elements = this._treeElementsMap.get(element.representedObject); + elements.remove(element, true); + if (!elements.length) + this._treeElementsMap.remove(element.representedObject); + } +} + +/** + * @param {!TreeElement} parentElement + */ +TreeOutline.prototype._forgetChildrenRecursive = function(parentElement) +{ + var child = parentElement.children[0]; + while (child) { + this._forgetTreeElement(child); + child = child.traverseNextTreeElement(false, parentElement, true); + } +} + +/** + * @param {?Object} representedObject + * @return {?TreeElement} + */ +TreeOutline.prototype.getCachedTreeElement = function(representedObject) +{ + if (!representedObject) + return null; + + var elements = this._treeElementsMap.get(representedObject); + if (elements && elements.length) + return elements[0]; + return null; +} + +/** + * @param {?Object} representedObject + * @param {function(!Object):?Object} getParent + * @return {?TreeElement} + */ +TreeOutline.prototype.findTreeElement = function(representedObject, getParent) +{ + if (!representedObject) + return null; + + var cachedElement = this.getCachedTreeElement(representedObject); + if (cachedElement) + return cachedElement; + + // Walk up the parent pointers from the desired representedObject + var ancestors = []; + for (var currentObject = getParent(representedObject); currentObject; currentObject = getParent(currentObject)) { + ancestors.push(currentObject); + if (this.getCachedTreeElement(currentObject)) // stop climbing as soon as we hit + break; + } + + if (!currentObject) + return null; + + // Walk down to populate each ancestor's children, to fill in the tree and the cache. + for (var i = ancestors.length - 1; i >= 0; --i) { + var treeElement = this.getCachedTreeElement(ancestors[i]); + if (treeElement) + treeElement.onpopulate(); // fill the cache with the children of treeElement + } + + return this.getCachedTreeElement(representedObject); +} + +/** + * @param {number} x + * @param {number} y + * @return {?TreeElement} + */ +TreeOutline.prototype.treeElementFromPoint = function(x, y) +{ + var node = this._childrenListNode.ownerDocument.elementFromPoint(x, y); + if (!node) + return null; + + var listNode = node.enclosingNodeOrSelfWithNodeNameInArray(["ol", "li"]); + if (listNode) + return listNode.parentTreeElement || listNode.treeElement; + return null; +} + +TreeOutline.prototype._treeKeyDown = function(event) +{ + if (event.target !== this._childrenListNode) + return; + + if (!this.selectedTreeElement || event.shiftKey || event.metaKey || event.ctrlKey) + return; + + var handled = false; + var nextSelectedElement; + if (event.keyIdentifier === "Up" && !event.altKey) { + nextSelectedElement = this.selectedTreeElement.traversePreviousTreeElement(true); + while (nextSelectedElement && !nextSelectedElement.selectable) + nextSelectedElement = nextSelectedElement.traversePreviousTreeElement(!this.expandTreeElementsWhenArrowing); + handled = nextSelectedElement ? true : false; + } else if (event.keyIdentifier === "Down" && !event.altKey) { + nextSelectedElement = this.selectedTreeElement.traverseNextTreeElement(true); + while (nextSelectedElement && !nextSelectedElement.selectable) + nextSelectedElement = nextSelectedElement.traverseNextTreeElement(!this.expandTreeElementsWhenArrowing); + handled = nextSelectedElement ? true : false; + } else if (event.keyIdentifier === "Left") { + if (this.selectedTreeElement.expanded) { + if (event.altKey) + this.selectedTreeElement.collapseRecursively(); + else + this.selectedTreeElement.collapse(); + handled = true; + } else if (this.selectedTreeElement.parent && !this.selectedTreeElement.parent.root) { + handled = true; + if (this.selectedTreeElement.parent.selectable) { + nextSelectedElement = this.selectedTreeElement.parent; + while (nextSelectedElement && !nextSelectedElement.selectable) + nextSelectedElement = nextSelectedElement.parent; + handled = nextSelectedElement ? true : false; + } else if (this.selectedTreeElement.parent) + this.selectedTreeElement.parent.collapse(); + } + } else if (event.keyIdentifier === "Right") { + if (!this.selectedTreeElement.revealed()) { + this.selectedTreeElement.reveal(); + handled = true; + } else if (this.selectedTreeElement.hasChildren) { + handled = true; + if (this.selectedTreeElement.expanded) { + nextSelectedElement = this.selectedTreeElement.children[0]; + while (nextSelectedElement && !nextSelectedElement.selectable) + nextSelectedElement = nextSelectedElement.nextSibling; + handled = nextSelectedElement ? true : false; + } else { + if (event.altKey) + this.selectedTreeElement.expandRecursively(); + else + this.selectedTreeElement.expand(); + } + } + } else if (event.keyCode === 8 /* Backspace */ || event.keyCode === 46 /* Delete */) + handled = this.selectedTreeElement.ondelete(); + else if (isEnterKey(event)) + handled = this.selectedTreeElement.onenter(); + else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Space.code) + handled = this.selectedTreeElement.onspace(); + + if (nextSelectedElement) { + nextSelectedElement.reveal(); + nextSelectedElement.select(false, true); + } + + if (handled) + event.consume(true); +} + +TreeOutline.prototype.expand = function() +{ + // this is the root, do nothing +} + +TreeOutline.prototype.collapse = function() +{ + // this is the root, do nothing +} + +/** + * @return {boolean} + */ +TreeOutline.prototype.revealed = function() +{ + return true; +} + +TreeOutline.prototype.reveal = function() +{ + // this is the root, do nothing +} + +TreeOutline.prototype.select = function() +{ + // this is the root, do nothing +} + +/** + * @param {boolean=} omitFocus + */ +TreeOutline.prototype.revealAndSelect = function(omitFocus) +{ + // this is the root, do nothing +} + +/** + * @constructor + * @param {string|!Node} title + * @param {?Object=} representedObject + * @param {boolean=} hasChildren + */ +function TreeElement(title, representedObject, hasChildren) +{ + this._title = title; + this.representedObject = (representedObject || {}); + + this.root = false; + this._hidden = false; + this._selectable = true; + this.expanded = false; + this.selected = false; + this.hasChildren = hasChildren; + this.children = []; + this.treeOutline = null; + this.parent = null; + this.previousSibling = null; + this.nextSibling = null; + this._listItemNode = null; +} + +TreeElement.prototype = { + arrowToggleWidth: 10, + + get selectable() { + if (this._hidden) + return false; + return this._selectable; + }, + + set selectable(x) { + this._selectable = x; + }, + + get listItemElement() { + return this._listItemNode; + }, + + get childrenListElement() { + return this._childrenListNode; + }, + + get title() { + return this._title; + }, + + set title(x) { + this._title = x; + this._setListItemNodeContent(); + }, + + get tooltip() { + return this._tooltip; + }, + + set tooltip(x) { + this._tooltip = x; + if (this._listItemNode) + this._listItemNode.title = x ? x : ""; + }, + + get hasChildren() { + return this._hasChildren; + }, + + set hasChildren(x) { + if (this._hasChildren === x) + return; + + this._hasChildren = x; + + if (!this._listItemNode) + return; + + if (x) + this._listItemNode.classList.add("parent"); + else { + this._listItemNode.classList.remove("parent"); + this.collapse(); + } + }, + + get hidden() { + return this._hidden; + }, + + set hidden(x) { + if (this._hidden === x) + return; + + this._hidden = x; + + if (x) { + if (this._listItemNode) + this._listItemNode.classList.add("hidden"); + if (this._childrenListNode) + this._childrenListNode.classList.add("hidden"); + } else { + if (this._listItemNode) + this._listItemNode.classList.remove("hidden"); + if (this._childrenListNode) + this._childrenListNode.classList.remove("hidden"); + } + }, + + get shouldRefreshChildren() { + return this._shouldRefreshChildren; + }, + + set shouldRefreshChildren(x) { + this._shouldRefreshChildren = x; + if (x && this.expanded) + this.expand(); + }, + + _setListItemNodeContent: function() + { + if (!this._listItemNode) + return; + + if (typeof this._title === "string") + this._listItemNode.textContent = this._title; + else { + this._listItemNode.removeChildren(); + if (this._title) + this._listItemNode.appendChild(this._title); + } + } +} + +TreeElement.prototype.appendChild = TreeOutline.prototype.appendChild; +TreeElement.prototype.insertChild = TreeOutline.prototype.insertChild; +TreeElement.prototype.insertBeforeChild = TreeOutline.prototype.insertBeforeChild; +TreeElement.prototype.removeChild = TreeOutline.prototype.removeChild; +TreeElement.prototype.removeChildAtIndex = TreeOutline.prototype.removeChildAtIndex; +TreeElement.prototype.removeChildren = TreeOutline.prototype.removeChildren; + +TreeElement.prototype._attach = function() +{ + if (!this._listItemNode || this.parent._shouldRefreshChildren) { + if (this._listItemNode && this._listItemNode.parentNode) + this._listItemNode.parentNode.removeChild(this._listItemNode); + + this._listItemNode = this.treeOutline._childrenListNode.ownerDocument.createElement("li"); + this._listItemNode.treeElement = this; + this._setListItemNodeContent(); + this._listItemNode.title = this._tooltip ? this._tooltip : ""; + + if (this.hidden) + this._listItemNode.classList.add("hidden"); + if (this.hasChildren) + this._listItemNode.classList.add("parent"); + if (this.expanded) + this._listItemNode.classList.add("expanded"); + if (this.selected) + this._listItemNode.classList.add("selected"); + + this._listItemNode.addEventListener("mousedown", TreeElement.treeElementMouseDown, false); + this._listItemNode.addEventListener("click", TreeElement.treeElementToggled, false); + this._listItemNode.addEventListener("dblclick", TreeElement.treeElementDoubleClicked, false); + + this.onattach(); + } + + var nextSibling = null; + if (this.nextSibling && this.nextSibling._listItemNode && this.nextSibling._listItemNode.parentNode === this.parent._childrenListNode) + nextSibling = this.nextSibling._listItemNode; + this.parent._childrenListNode.insertBefore(this._listItemNode, nextSibling); + if (this._childrenListNode) + this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling); + if (this.selected) + this.select(); + if (this.expanded) + this.expand(); +} + +TreeElement.prototype._detach = function() +{ + if (this._listItemNode && this._listItemNode.parentNode) + this._listItemNode.parentNode.removeChild(this._listItemNode); + if (this._childrenListNode && this._childrenListNode.parentNode) + this._childrenListNode.parentNode.removeChild(this._childrenListNode); +} + +TreeElement.treeElementMouseDown = function(event) +{ + var element = event.currentTarget; + if (!element || !element.treeElement || !element.treeElement.selectable) + return; + + if (element.treeElement.isEventWithinDisclosureTriangle(event)) + return; + + element.treeElement.selectOnMouseDown(event); +} + +TreeElement.treeElementToggled = function(event) +{ + var element = event.currentTarget; + if (!element || !element.treeElement) + return; + + var toggleOnClick = element.treeElement.toggleOnClick && !element.treeElement.selectable; + var isInTriangle = element.treeElement.isEventWithinDisclosureTriangle(event); + if (!toggleOnClick && !isInTriangle) + return; + + if (element.treeElement.expanded) { + if (event.altKey) + element.treeElement.collapseRecursively(); + else + element.treeElement.collapse(); + } else { + if (event.altKey) + element.treeElement.expandRecursively(); + else + element.treeElement.expand(); + } + event.consume(); +} + +TreeElement.treeElementDoubleClicked = function(event) +{ + var element = event.currentTarget; + if (!element || !element.treeElement) + return; + + var handled = element.treeElement.ondblclick.call(element.treeElement, event); + if (handled) + return; + if (element.treeElement.hasChildren && !element.treeElement.expanded) + element.treeElement.expand(); +} + +TreeElement.prototype.collapse = function() +{ + if (this._listItemNode) + this._listItemNode.classList.remove("expanded"); + if (this._childrenListNode) + this._childrenListNode.classList.remove("expanded"); + + this.expanded = false; + + if (this.treeOutline) + this.treeOutline._expandedStateMap.put(this.representedObject, false); + + this.oncollapse(); +} + +TreeElement.prototype.collapseRecursively = function() +{ + var item = this; + while (item) { + if (item.expanded) + item.collapse(); + item = item.traverseNextTreeElement(false, this, true); + } +} + +TreeElement.prototype.expand = function() +{ + if (!this.hasChildren || (this.expanded && !this._shouldRefreshChildren && this._childrenListNode)) + return; + + // Set this before onpopulate. Since onpopulate can add elements, this makes + // sure the expanded flag is true before calling those functions. This prevents the possibility + // of an infinite loop if onpopulate were to call expand. + + this.expanded = true; + if (this.treeOutline) + this.treeOutline._expandedStateMap.put(this.representedObject, true); + + if (this.treeOutline && (!this._childrenListNode || this._shouldRefreshChildren)) { + if (this._childrenListNode && this._childrenListNode.parentNode) + this._childrenListNode.parentNode.removeChild(this._childrenListNode); + + this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol"); + this._childrenListNode.parentTreeElement = this; + this._childrenListNode.classList.add("children"); + + if (this.hidden) + this._childrenListNode.classList.add("hidden"); + + this.onpopulate(); + + for (var i = 0; i < this.children.length; ++i) + this.children[i]._attach(); + + delete this._shouldRefreshChildren; + } + + if (this._listItemNode) { + this._listItemNode.classList.add("expanded"); + if (this._childrenListNode && this._childrenListNode.parentNode != this._listItemNode.parentNode) + this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling); + } + + if (this._childrenListNode) + this._childrenListNode.classList.add("expanded"); + + this.onexpand(); +} + +TreeElement.prototype.expandRecursively = function(maxDepth) +{ + var item = this; + var info = {}; + var depth = 0; + + // The Inspector uses TreeOutlines to represents object properties, so recursive expansion + // in some case can be infinite, since JavaScript objects can hold circular references. + // So default to a recursion cap of 3 levels, since that gives fairly good results. + if (isNaN(maxDepth)) + maxDepth = 3; + + while (item) { + if (depth < maxDepth) + item.expand(); + item = item.traverseNextTreeElement(false, this, (depth >= maxDepth), info); + depth += info.depthChange; + } +} + +/** + * @param {?TreeElement} ancestor + * @return {boolean} + */ +TreeElement.prototype.hasAncestor = function(ancestor) { + if (!ancestor) + return false; + + var currentNode = this.parent; + while (currentNode) { + if (ancestor === currentNode) + return true; + currentNode = currentNode.parent; + } + + return false; +} + +TreeElement.prototype.reveal = function() +{ + var currentAncestor = this.parent; + while (currentAncestor && !currentAncestor.root) { + if (!currentAncestor.expanded) + currentAncestor.expand(); + currentAncestor = currentAncestor.parent; + } + + this.onreveal(); +} + +/** + * @return {boolean} + */ +TreeElement.prototype.revealed = function() +{ + var currentAncestor = this.parent; + while (currentAncestor && !currentAncestor.root) { + if (!currentAncestor.expanded) + return false; + currentAncestor = currentAncestor.parent; + } + + return true; +} + +TreeElement.prototype.selectOnMouseDown = function(event) +{ + if (this.select(false, true)) + event.consume(true); +} + +/** + * @param {boolean=} omitFocus + * @param {boolean=} selectedByUser + * @return {boolean} + */ +TreeElement.prototype.select = function(omitFocus, selectedByUser) +{ + if (!this.treeOutline || !this.selectable || this.selected) + return false; + + if (this.treeOutline.selectedTreeElement) + this.treeOutline.selectedTreeElement.deselect(); + + this.selected = true; + + if (!omitFocus) + this.treeOutline._childrenListNode.focus(); + + // Focusing on another node may detach "this" from tree. + if (!this.treeOutline) + return false; + this.treeOutline.selectedTreeElement = this; + if (this._listItemNode) + this._listItemNode.classList.add("selected"); + + return this.onselect(selectedByUser); +} + +/** + * @param {boolean=} omitFocus + */ +TreeElement.prototype.revealAndSelect = function(omitFocus) +{ + this.reveal(); + this.select(omitFocus); +} + +/** + * @param {boolean=} supressOnDeselect + * @return {boolean} + */ +TreeElement.prototype.deselect = function(supressOnDeselect) +{ + if (!this.treeOutline || this.treeOutline.selectedTreeElement !== this || !this.selected) + return false; + + this.selected = false; + this.treeOutline.selectedTreeElement = null; + if (this._listItemNode) + this._listItemNode.classList.remove("selected"); + return true; +} + +// Overridden by subclasses. +TreeElement.prototype.onpopulate = function() { } + +/** + * @return {boolean} + */ +TreeElement.prototype.onenter = function() { return false; } + +/** + * @return {boolean} + */ +TreeElement.prototype.ondelete = function() { return false; } + +/** + * @return {boolean} + */ +TreeElement.prototype.onspace = function() { return false; } + +TreeElement.prototype.onattach = function() { } + +TreeElement.prototype.onexpand = function() { } + +TreeElement.prototype.oncollapse = function() { } + +/** + * @param {!MouseEvent} e + * @return {boolean} + */ +TreeElement.prototype.ondblclick = function(e) { return false; } + +TreeElement.prototype.onreveal = function() { } + +/** + * @param {boolean=} selectedByUser + * @return {boolean} + */ +TreeElement.prototype.onselect = function(selectedByUser) { return false; } + +/** + * @param {boolean} skipUnrevealed + * @param {(!TreeOutline|!TreeElement|null)=} stayWithin + * @param {boolean=} dontPopulate + * @param {!Object=} info + * @return {?TreeElement} + */ +TreeElement.prototype.traverseNextTreeElement = function(skipUnrevealed, stayWithin, dontPopulate, info) +{ + if (!dontPopulate && this.hasChildren) + this.onpopulate(); + + if (info) + info.depthChange = 0; + + var element = skipUnrevealed ? (this.revealed() ? this.children[0] : null) : this.children[0]; + if (element && (!skipUnrevealed || (skipUnrevealed && this.expanded))) { + if (info) + info.depthChange = 1; + return element; + } + + if (this === stayWithin) + return null; + + element = skipUnrevealed ? (this.revealed() ? this.nextSibling : null) : this.nextSibling; + if (element) + return element; + + element = this; + while (element && !element.root && !(skipUnrevealed ? (element.revealed() ? element.nextSibling : null) : element.nextSibling) && element.parent !== stayWithin) { + if (info) + info.depthChange -= 1; + element = element.parent; + } + + if (!element) + return null; + + return (skipUnrevealed ? (element.revealed() ? element.nextSibling : null) : element.nextSibling); +} + +/** + * @param {boolean} skipUnrevealed + * @param {boolean=} dontPopulate + * @return {?TreeElement} + */ +TreeElement.prototype.traversePreviousTreeElement = function(skipUnrevealed, dontPopulate) +{ + var element = skipUnrevealed ? (this.revealed() ? this.previousSibling : null) : this.previousSibling; + if (!dontPopulate && element && element.hasChildren) + element.onpopulate(); + + while (element && (skipUnrevealed ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1])) { + if (!dontPopulate && element.hasChildren) + element.onpopulate(); + element = (skipUnrevealed ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1]); + } + + if (element) + return element; + + if (!this.parent || this.parent.root) + return null; + + return this.parent; +} + +/** + * @return {boolean} + */ +TreeElement.prototype.isEventWithinDisclosureTriangle = function(event) +{ + // FIXME: We should not use getComputedStyle(). For that we need to get rid of using ::before for disclosure triangle. (http://webk.it/74446) + var paddingLeftValue = window.getComputedStyle(this._listItemNode).getPropertyCSSValue("padding-left"); + var computedLeftPadding = paddingLeftValue ? paddingLeftValue.getFloatValue(CSSPrimitiveValue.CSS_PX) : 0; + var left = this._listItemNode.totalOffsetLeft() + computedLeftPadding; + return event.pageX >= left && event.pageX <= left + this.arrowToggleWidth && this.hasChildren; +} |