1 /* 2 * Copyright (C) 2007 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 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 14 * its contributors may be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 WebInspector.MetricsSidebarPane = function() 30 { 31 WebInspector.SidebarPane.call(this, WebInspector.UIString("Metrics")); 32 this._inlineStyleId = null; 33 } 34 35 WebInspector.MetricsSidebarPane.prototype = { 36 update: function(node) 37 { 38 if (node) 39 this.node = node; 40 else 41 node = this.node; 42 43 if (!node || node.nodeType() !== Node.ELEMENT_NODE) { 44 this.bodyElement.removeChildren(); 45 return; 46 } 47 48 var self = this; 49 var callback = function(style) { 50 if (!style) 51 return; 52 self._update(style); 53 }; 54 WebInspector.cssModel.getComputedStyleAsync(node.id, callback); 55 56 var inlineStyleCallback = function(style) { 57 if (!style) 58 return; 59 self.inlineStyle = style; 60 }; 61 WebInspector.cssModel.getInlineStyleAsync(node.id, inlineStyleCallback); 62 }, 63 64 _getPropertyValueAsPx: function(style, propertyName) 65 { 66 return Number(style.getPropertyValue(propertyName).replace(/px$/, "") || 0); 67 }, 68 69 _getBox: function(computedStyle, componentName) 70 { 71 var suffix = componentName === "border" ? "-width" : ""; 72 var left = this._getPropertyValueAsPx(computedStyle, componentName + "-left" + suffix); 73 var top = this._getPropertyValueAsPx(computedStyle, componentName + "-top" + suffix); 74 var right = this._getPropertyValueAsPx(computedStyle, componentName + "-right" + suffix); 75 var bottom = this._getPropertyValueAsPx(computedStyle, componentName + "-bottom" + suffix); 76 return { left: left, top: top, right: right, bottom: bottom }; 77 }, 78 79 _update: function(style) 80 { 81 // Updating with computed style. 82 var metricsElement = document.createElement("div"); 83 metricsElement.className = "metrics"; 84 var self = this; 85 86 function createBoxPartElement(style, name, side, suffix) 87 { 88 var propertyName = (name !== "position" ? name + "-" : "") + side + suffix; 89 var value = style.getPropertyValue(propertyName); 90 if (value === "" || (name !== "position" && value === "0px")) 91 value = "\u2012"; 92 else if (name === "position" && value === "auto") 93 value = "\u2012"; 94 value = value.replace(/px$/, ""); 95 96 var element = document.createElement("div"); 97 element.className = side; 98 element.textContent = value; 99 element.addEventListener("dblclick", this.startEditing.bind(this, element, name, propertyName), false); 100 return element; 101 } 102 103 function getContentAreaWidthPx(style) 104 { 105 var width = style.getPropertyValue("width").replace(/px$/, ""); 106 if (style.getPropertyValue("box-sizing") === "border-box") { 107 var borderBox = self._getBox(style, "border"); 108 var paddingBox = self._getBox(style, "padding"); 109 110 width = width - borderBox.left - borderBox.right - paddingBox.left - paddingBox.right; 111 } 112 113 return width; 114 } 115 116 function getContentAreaHeightPx(style) 117 { 118 var height = style.getPropertyValue("height").replace(/px$/, ""); 119 if (style.getPropertyValue("box-sizing") === "border-box") { 120 var borderBox = self._getBox(style, "border"); 121 var paddingBox = self._getBox(style, "padding"); 122 123 height = height - borderBox.top - borderBox.bottom - paddingBox.top - paddingBox.bottom; 124 } 125 126 return height; 127 } 128 129 // Display types for which margin is ignored. 130 var noMarginDisplayType = { 131 "table-cell": true, 132 "table-column": true, 133 "table-column-group": true, 134 "table-footer-group": true, 135 "table-header-group": true, 136 "table-row": true, 137 "table-row-group": true 138 }; 139 140 // Display types for which padding is ignored. 141 var noPaddingDisplayType = { 142 "table-column": true, 143 "table-column-group": true, 144 "table-footer-group": true, 145 "table-header-group": true, 146 "table-row": true, 147 "table-row-group": true 148 }; 149 150 // Position types for which top, left, bottom and right are ignored. 151 var noPositionType = { 152 "static": true 153 }; 154 155 var boxes = ["content", "padding", "border", "margin", "position"]; 156 var boxLabels = [WebInspector.UIString("content"), WebInspector.UIString("padding"), WebInspector.UIString("border"), WebInspector.UIString("margin"), WebInspector.UIString("position")]; 157 var previousBox; 158 for (var i = 0; i < boxes.length; ++i) { 159 var name = boxes[i]; 160 161 if (name === "margin" && noMarginDisplayType[style.getPropertyValue("display")]) 162 continue; 163 if (name === "padding" && noPaddingDisplayType[style.getPropertyValue("display")]) 164 continue; 165 if (name === "position" && noPositionType[style.getPropertyValue("position")]) 166 continue; 167 168 var boxElement = document.createElement("div"); 169 boxElement.className = name; 170 171 if (name === "content") { 172 var widthElement = document.createElement("span"); 173 widthElement.textContent = getContentAreaWidthPx(style); 174 widthElement.addEventListener("dblclick", this.startEditing.bind(this, widthElement, "width", "width", style), false); 175 176 var heightElement = document.createElement("span"); 177 heightElement.textContent = getContentAreaHeightPx(style); 178 heightElement.addEventListener("dblclick", this.startEditing.bind(this, heightElement, "height", "height", style), false); 179 180 boxElement.appendChild(widthElement); 181 boxElement.appendChild(document.createTextNode(" \u00D7 ")); 182 boxElement.appendChild(heightElement); 183 } else { 184 var suffix = (name === "border" ? "-width" : ""); 185 186 var labelElement = document.createElement("div"); 187 labelElement.className = "label"; 188 labelElement.textContent = boxLabels[i]; 189 boxElement.appendChild(labelElement); 190 191 boxElement.appendChild(createBoxPartElement.call(this, style, name, "top", suffix)); 192 boxElement.appendChild(document.createElement("br")); 193 boxElement.appendChild(createBoxPartElement.call(this, style, name, "left", suffix)); 194 195 if (previousBox) 196 boxElement.appendChild(previousBox); 197 198 boxElement.appendChild(createBoxPartElement.call(this, style, name, "right", suffix)); 199 boxElement.appendChild(document.createElement("br")); 200 boxElement.appendChild(createBoxPartElement.call(this, style, name, "bottom", suffix)); 201 } 202 203 previousBox = boxElement; 204 } 205 206 metricsElement.appendChild(previousBox); 207 this.bodyElement.removeChildren(); 208 this.bodyElement.appendChild(metricsElement); 209 }, 210 211 startEditing: function(targetElement, box, styleProperty, computedStyle) 212 { 213 if (WebInspector.isBeingEdited(targetElement)) 214 return; 215 216 var context = { box: box, styleProperty: styleProperty, computedStyle: computedStyle }; 217 218 WebInspector.startEditing(targetElement, { 219 context: context, 220 commitHandler: this.editingCommitted.bind(this), 221 cancelHandler: this.editingCancelled.bind(this) 222 }); 223 }, 224 225 editingCancelled: function(element, context) 226 { 227 this.update(); 228 }, 229 230 editingCommitted: function(element, userInput, previousContent, context) 231 { 232 if (!this.inlineStyle) { 233 // Element has no renderer. 234 return this.editingCancelled(element, context); // nothing changed, so cancel 235 } 236 237 if (userInput === previousContent) 238 return this.editingCancelled(element, context); // nothing changed, so cancel 239 240 if (context.box !== "position" && (!userInput || userInput === "\u2012")) 241 userInput = "0px"; 242 else if (context.box === "position" && (!userInput || userInput === "\u2012")) 243 userInput = "auto"; 244 245 userInput = userInput.toLowerCase(); 246 // Append a "px" unit if the user input was just a number. 247 if (/^\d+$/.test(userInput)) 248 userInput += "px"; 249 250 var styleProperty = context.styleProperty; 251 var computedStyle = context.computedStyle; 252 253 if (computedStyle.getPropertyValue("box-sizing") === "border-box" && (styleProperty === "width" || styleProperty === "height")) { 254 if (!userInput.match(/px$/)) { 255 WebInspector.log("For elements with box-sizing: border-box, only absolute content area dimensions can be applied", WebInspector.ConsoleMessage.MessageLevel.Error); 256 WebInspector.showConsole(); 257 return; 258 } 259 260 var borderBox = this._getBox(computedStyle, "border"); 261 var paddingBox = this._getBox(computedStyle, "padding"); 262 var userValuePx = Number(userInput.replace(/px$/, "")); 263 if (isNaN(userValuePx)) 264 return; 265 if (styleProperty === "width") 266 userValuePx += borderBox.left + borderBox.right + paddingBox.left + paddingBox.right; 267 else 268 userValuePx += borderBox.top + borderBox.bottom + paddingBox.top + paddingBox.bottom; 269 270 userInput = userValuePx + "px"; 271 } 272 273 var self = this; 274 var callback = function(style) { 275 if (!style) 276 return; 277 self.inlineStyle = style; 278 self.dispatchEventToListeners("metrics edited"); 279 self.update(); 280 }; 281 282 function setEnabledValueCallback(context, style) 283 { 284 var property = style.getLiveProperty(styleProperty); 285 if (!property) 286 style.appendProperty(context.styleProperty, userInput, callback); 287 else 288 property.setValue(userInput, callback); 289 } 290 291 var allProperties = this.inlineStyle.allProperties; 292 for (var i = 0; i < allProperties.length; ++i) { 293 var property = allProperties[i]; 294 if (property.name !== context.styleProperty || property.inactive) 295 continue; 296 if (property.disabled) 297 property.setDisabled(false, setEnabledValueCallback.bind(null, context)); 298 else 299 property.setValue(userInput, callback); 300 return; 301 } 302 303 this.inlineStyle.appendProperty(context.styleProperty, userInput, callback); 304 } 305 } 306 307 WebInspector.MetricsSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; 308