1/* 2 * Copyright (C) 2013 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26WebInspector.HierarchicalPathNavigationItem = function(identifier, components) { 27 WebInspector.NavigationItem.call(this, identifier); 28 29 this.components = components; 30}; 31 32WebInspector.HierarchicalPathNavigationItem.StyleClassName = "hierarchical-path"; 33WebInspector.HierarchicalPathNavigationItem.AlwaysShowLastPathComponentSeparatorStyleClassName = "always-show-last-path-component-separator"; 34 35WebInspector.HierarchicalPathNavigationItem.Event = { 36 PathComponentWasSelected: "hierarchical-path-navigation-item-path-component-was-selected" 37}; 38 39WebInspector.HierarchicalPathNavigationItem.prototype = { 40 constructor: WebInspector.HierarchicalPathNavigationItem, 41 42 // Public 43 44 get components() 45 { 46 return this._components; 47 }, 48 49 set components(newComponents) 50 { 51 if (!newComponents) 52 newComponents = []; 53 54 for (var i = 0; this._components && i < this._components.length; ++i) 55 this._components[i].removeEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this._siblingPathComponentWasSelected, this); 56 57 // Make a shallow copy of the newComponents array using slice. 58 this._components = newComponents.slice(0); 59 60 for (var i = 0; i < this._components.length; ++i) 61 this._components[i].addEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this._siblingPathComponentWasSelected, this); 62 63 this.element.removeChildren(); 64 delete this._collapsedComponent; 65 66 for (var i = 0; i < newComponents.length; ++i) 67 this.element.appendChild(newComponents[i].element); 68 69 // Update layout for the so other items can adjust to the extra space (or lack thereof) too. 70 if (this.parentNavigationBar) 71 this.parentNavigationBar.updateLayoutSoon(); 72 }, 73 74 get lastComponent() 75 { 76 return this._components.lastValue || null; 77 }, 78 79 get alwaysShowLastPathComponentSeparator() 80 { 81 return this.element.classList.contains(WebInspector.HierarchicalPathNavigationItem.AlwaysShowLastPathComponentSeparatorStyleClassName); 82 }, 83 84 set alwaysShowLastPathComponentSeparator(flag) 85 { 86 if (flag) 87 this.element.classList.add(WebInspector.HierarchicalPathNavigationItem.AlwaysShowLastPathComponentSeparatorStyleClassName); 88 else 89 this.element.classList.remove(WebInspector.HierarchicalPathNavigationItem.AlwaysShowLastPathComponentSeparatorStyleClassName); 90 }, 91 92 updateLayout: function(expandOnly) 93 { 94 var navigationBar = this.parentNavigationBar; 95 if (!navigationBar) 96 return; 97 98 if (this._collapsedComponent) { 99 this.element.removeChild(this._collapsedComponent.element); 100 delete this._collapsedComponent; 101 } 102 103 // Expand our components to full width to test if the items can fit at full width. 104 for (var i = 0; i < this._components.length; ++i) { 105 this._components[i].hidden = false; 106 this._components[i].forcedWidth = null; 107 } 108 109 if (expandOnly) 110 return; 111 112 if (navigationBar.sizesToFit) 113 return; 114 115 // Iterate over all the other navigation items in the bar and calculate their width. 116 var totalOtherItemsWidth = 0; 117 for (var i = 0; i < navigationBar.navigationItems.length; ++i) { 118 // Skip ourself. 119 if (navigationBar.navigationItems[i] === this) 120 continue; 121 122 // Skip flexible space items since they can take up no space at the minimum width. 123 if (navigationBar.navigationItems[i] instanceof WebInspector.FlexibleSpaceNavigationItem) 124 continue; 125 126 totalOtherItemsWidth += navigationBar.navigationItems[i].element.offsetWidth; 127 } 128 129 // Calculate the width for all the components. 130 var thisItemWidth = 0; 131 var componentWidths = []; 132 for (var i = 0; i < this._components.length; ++i) { 133 var componentWidth = this._components[i].element.offsetWidth; 134 componentWidths.push(componentWidth); 135 thisItemWidth += componentWidth; 136 } 137 138 // If all our components fit with the other navigation items in the width of the bar, 139 // then we don't need to collapse any components. 140 var barWidth = navigationBar.element.offsetWidth; 141 if (totalOtherItemsWidth + thisItemWidth <= barWidth) 142 return; 143 144 // Calculate the width we need to remove from our components, then iterate over them 145 // and force their width to be smaller. 146 var widthToRemove = totalOtherItemsWidth + thisItemWidth - barWidth; 147 for (var i = 0; i < this._components.length; ++i) { 148 var componentWidth = componentWidths[i]; 149 150 // Try to take the whole width we need to remove from each component. 151 var forcedWidth = componentWidth - widthToRemove; 152 this._components[i].forcedWidth = forcedWidth; 153 154 // Since components have a minimum width, we need to see how much was actually 155 // removed and subtract that from what remans to be removed. 156 componentWidths[i] = Math.max(this._components[i].minimumWidth, forcedWidth); 157 widthToRemove -= (componentWidth - componentWidths[i]); 158 159 // If there is nothing else to remove, then we can stop. 160 if (widthToRemove <= 0) 161 break; 162 } 163 164 // If there is nothing else to remove, then we can stop. 165 if (widthToRemove <= 0) 166 return; 167 168 // If there are 3 or fewer components, then we can stop. Collapsing the middle of 3 components 169 // does not save more than a few pixels over just the icon, so it isn't worth it unless there 170 // are 4 or more components. 171 if (this._components.length <= 3) 172 return; 173 174 // We want to collapse the middle components, so find the nearest middle index. 175 var middle = this._components.length >> 1; 176 var distance = -1; 177 var i = middle; 178 179 // Create a component that will represent the hidden components with a ellipse as the display name. 180 this._collapsedComponent = new WebInspector.HierarchicalPathComponent("\u2026", []); 181 this._collapsedComponent.collapsed = true; 182 183 // Insert it in the middle, it doesn't matter exactly where since the elements around it will be hidden soon. 184 this.element.insertBefore(this._collapsedComponent.element, this._components[middle].element); 185 186 // Add the width of the collapsed component to the width we need to remove. 187 widthToRemove += this._collapsedComponent.minimumWidth; 188 189 var hiddenDisplayNames = []; 190 191 // Loop through the components starting at the middle and fanning out in each direction. 192 while (i >= 0 && i <= this._components.length - 1) { 193 // Only hide components in the middle and never the ends. 194 if (i > 0 && i < this._components.length - 1) { 195 var component = this._components[i]; 196 component.hidden = true; 197 198 // Remember the displayName so it can be put in the tool tip of the collapsed component. 199 if (distance > 0) 200 hiddenDisplayNames.unshift(component.displayName); 201 else 202 hiddenDisplayNames.push(component.displayName); 203 204 // Fully subtract the hidden component's width. 205 widthToRemove -= componentWidths[i]; 206 207 // If there is nothing else to remove, then we can stop. 208 if (widthToRemove <= 0) 209 break; 210 } 211 212 // Calculate the next index. 213 i = middle + distance; 214 215 // Increment the distance when it is in the positive direction. 216 if (distance > 0) 217 ++distance; 218 219 // Flip the direction of the distance. 220 distance *= -1; 221 } 222 223 // Set the tool tip of the collapsed component. 224 this._collapsedComponent.element.title = hiddenDisplayNames.join("\n"); 225 }, 226 227 // Private 228 229 _additionalClassNames: [WebInspector.HierarchicalPathNavigationItem.StyleClassName], 230 231 _siblingPathComponentWasSelected: function(event) 232 { 233 this.dispatchEventToListeners(WebInspector.HierarchicalPathNavigationItem.Event.PathComponentWasSelected, event.data); 234 } 235}; 236 237WebInspector.HierarchicalPathNavigationItem.prototype.__proto__ = WebInspector.NavigationItem.prototype; 238