summaryrefslogtreecommitdiff
path: root/Source/WebInspectorUI/UserInterface/Views/HierarchicalPathNavigationItem.js
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Views/HierarchicalPathNavigationItem.js')
-rw-r--r--Source/WebInspectorUI/UserInterface/Views/HierarchicalPathNavigationItem.js239
1 files changed, 239 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Views/HierarchicalPathNavigationItem.js b/Source/WebInspectorUI/UserInterface/Views/HierarchicalPathNavigationItem.js
new file mode 100644
index 000000000..88f963b63
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Views/HierarchicalPathNavigationItem.js
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2013, 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE 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 APPLE 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.
+ */
+
+WebInspector.HierarchicalPathNavigationItem = class HierarchicalPathNavigationItem extends WebInspector.NavigationItem
+{
+ constructor(identifier, components)
+ {
+ super(identifier);
+
+ this.components = components;
+ }
+
+ // Public
+
+ get components()
+ {
+ return this._components;
+ }
+
+ set components(newComponents)
+ {
+ if (!newComponents)
+ newComponents = [];
+
+ for (var i = 0; this._components && i < this._components.length; ++i)
+ this._components[i].removeEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this._siblingPathComponentWasSelected, this);
+
+ // Make a shallow copy of the newComponents array using slice.
+ this._components = newComponents.slice(0);
+
+ for (var i = 0; i < this._components.length; ++i)
+ this._components[i].addEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this._siblingPathComponentWasSelected, this);
+
+ this.element.removeChildren();
+ delete this._collapsedComponent;
+
+ for (var i = 0; i < newComponents.length; ++i)
+ this.element.appendChild(newComponents[i].element);
+
+ // Update layout for the so other items can adjust to the extra space (or lack thereof) too.
+ if (this.parentNavigationBar)
+ this.parentNavigationBar.needsLayout();
+ }
+
+ get lastComponent()
+ {
+ return this._components.lastValue || null;
+ }
+
+ get alwaysShowLastPathComponentSeparator()
+ {
+ return this.element.classList.contains(WebInspector.HierarchicalPathNavigationItem.AlwaysShowLastPathComponentSeparatorStyleClassName);
+ }
+
+ set alwaysShowLastPathComponentSeparator(flag)
+ {
+ if (flag)
+ this.element.classList.add(WebInspector.HierarchicalPathNavigationItem.AlwaysShowLastPathComponentSeparatorStyleClassName);
+ else
+ this.element.classList.remove(WebInspector.HierarchicalPathNavigationItem.AlwaysShowLastPathComponentSeparatorStyleClassName);
+ }
+
+ updateLayout(expandOnly)
+ {
+ var navigationBar = this.parentNavigationBar;
+ if (!navigationBar)
+ return;
+
+ if (this._collapsedComponent) {
+ this.element.removeChild(this._collapsedComponent.element);
+ delete this._collapsedComponent;
+ }
+
+ // Expand our components to full width to test if the items can fit at full width.
+ for (var i = 0; i < this._components.length; ++i) {
+ this._components[i].hidden = false;
+ this._components[i].forcedWidth = null;
+ }
+
+ if (expandOnly)
+ return;
+
+ if (navigationBar.sizesToFit)
+ return;
+
+ // Iterate over all the other navigation items in the bar and calculate their width.
+ var totalOtherItemsWidth = 0;
+ for (var i = 0; i < navigationBar.navigationItems.length; ++i) {
+ // Skip ourself.
+ if (navigationBar.navigationItems[i] === this)
+ continue;
+
+ // Skip flexible space items since they can take up no space at the minimum width.
+ if (navigationBar.navigationItems[i] instanceof WebInspector.FlexibleSpaceNavigationItem)
+ continue;
+
+ totalOtherItemsWidth += navigationBar.navigationItems[i].element.realOffsetWidth;
+ }
+
+ // Calculate the width for all the components.
+ var thisItemWidth = 0;
+ var componentWidths = [];
+ for (var i = 0; i < this._components.length; ++i) {
+ var componentWidth = this._components[i].element.realOffsetWidth;
+ componentWidths.push(componentWidth);
+ thisItemWidth += componentWidth;
+ }
+
+ // If all our components fit with the other navigation items in the width of the bar,
+ // then we don't need to collapse any components.
+ var barWidth = navigationBar.element.realOffsetWidth;
+ if (totalOtherItemsWidth + thisItemWidth <= barWidth)
+ return;
+
+ // Calculate the width we need to remove from our components, then iterate over them
+ // and force their width to be smaller.
+ var widthToRemove = totalOtherItemsWidth + thisItemWidth - barWidth;
+ for (var i = 0; i < this._components.length; ++i) {
+ var componentWidth = componentWidths[i];
+
+ // Try to take the whole width we need to remove from each component.
+ var forcedWidth = componentWidth - widthToRemove;
+ this._components[i].forcedWidth = forcedWidth;
+
+ // Since components have a minimum width, we need to see how much was actually
+ // removed and subtract that from what remans to be removed.
+ componentWidths[i] = Math.max(this._components[i].minimumWidth, forcedWidth);
+ widthToRemove -= (componentWidth - componentWidths[i]);
+
+ // If there is nothing else to remove, then we can stop.
+ if (widthToRemove <= 0)
+ break;
+ }
+
+ // If there is nothing else to remove, then we can stop.
+ if (widthToRemove <= 0)
+ return;
+
+ // If there are 3 or fewer components, then we can stop. Collapsing the middle of 3 components
+ // does not save more than a few pixels over just the icon, so it isn't worth it unless there
+ // are 4 or more components.
+ if (this._components.length <= 3)
+ return;
+
+ // We want to collapse the middle components, so find the nearest middle index.
+ var middle = this._components.length >> 1;
+ var distance = -1;
+ var i = middle;
+
+ // Create a component that will represent the hidden components with a ellipse as the display name.
+ this._collapsedComponent = new WebInspector.HierarchicalPathComponent(ellipsis, []);
+ this._collapsedComponent.collapsed = true;
+
+ // Insert it in the middle, it doesn't matter exactly where since the elements around it will be hidden soon.
+ this.element.insertBefore(this._collapsedComponent.element, this._components[middle].element);
+
+ // Add the width of the collapsed component to the width we need to remove.
+ widthToRemove += this._collapsedComponent.minimumWidth;
+
+ var hiddenDisplayNames = [];
+
+ // Loop through the components starting at the middle and fanning out in each direction.
+ while (i >= 0 && i <= this._components.length - 1) {
+ // Only hide components in the middle and never the ends.
+ if (i > 0 && i < this._components.length - 1) {
+ var component = this._components[i];
+ component.hidden = true;
+
+ // Remember the displayName so it can be put in the tool tip of the collapsed component.
+ if (distance > 0)
+ hiddenDisplayNames.unshift(component.displayName);
+ else
+ hiddenDisplayNames.push(component.displayName);
+
+ // Fully subtract the hidden component's width.
+ widthToRemove -= componentWidths[i];
+
+ // If there is nothing else to remove, then we can stop.
+ if (widthToRemove <= 0)
+ break;
+ }
+
+ // Calculate the next index.
+ i = middle + distance;
+
+ // Increment the distance when it is in the positive direction.
+ if (distance > 0)
+ ++distance;
+
+ // Flip the direction of the distance.
+ distance *= -1;
+ }
+
+ // Set the tool tip of the collapsed component.
+ this._collapsedComponent.element.title = hiddenDisplayNames.join("\n");
+ }
+
+ // Protected
+
+ get additionalClassNames()
+ {
+ return ["hierarchical-path"];
+ }
+
+ // Private
+
+ _siblingPathComponentWasSelected(event)
+ {
+ this.dispatchEventToListeners(WebInspector.HierarchicalPathNavigationItem.Event.PathComponentWasSelected, event.data);
+ }
+};
+
+WebInspector.HierarchicalPathNavigationItem.AlwaysShowLastPathComponentSeparatorStyleClassName = "always-show-last-path-component-separator";
+
+WebInspector.HierarchicalPathNavigationItem.Event = {
+ PathComponentWasSelected: "hierarchical-path-navigation-item-path-component-was-selected"
+};