1 /* See license.txt for terms of usage */ 2 3 define([ 4 "firebug/lib/object", 5 "firebug/firebug", 6 "firebug/lib/domplate", 7 "firebug/lib/locale", 8 "firebug/lib/events", 9 "firebug/lib/css", 10 "firebug/lib/dom", 11 "firebug/lib/xml", 12 "firebug/chrome/menu", 13 "firebug/editor/editor", 14 ], 15 function(Obj, Firebug, Domplate, Locale, Events, Css, Dom, Xml, Menu) { 16 17 // ************************************************************************************************ 18 19 function LayoutPanel() {} 20 21 with (Domplate) { 22 LayoutPanel.prototype = Obj.extend(Firebug.Panel, 23 { 24 template: domplate( 25 { 26 tag: 27 DIV({"class": "outerLayoutBox"}, 28 DIV({"class": "positionLayoutBox $outerTopMode $outerRightMode $outerBottomMode "+ 29 "$outerLeftMode focusGroup"}, 30 DIV({"class": "layoutEdgeTop layoutEdge"}), 31 DIV({"class": "layoutEdgeRight layoutEdge"}), 32 DIV({"class": "layoutEdgeBottom layoutEdge"}), 33 DIV({"class": "layoutEdgeLeft layoutEdge"}), 34 35 DIV({"class": "layoutLabelBottom layoutLabel layoutLabelPosition"}, 36 SPAN({"class": "layoutPosition layoutCaption", 37 "aria-label": Locale.$STR("a11y.layout.position")}, 38 Locale.$STR("position") + ": " + "$position"), 39 SPAN({"class": "layoutBoxSizing layoutCaption", 40 "aria-label": Locale.$STR("a11y.layout.box-sizing")}, 41 Locale.$STR("a11y.layout.box-sizing") + ": " + "$boxSizing"), 42 SPAN({"class": "layoutZIndex", $invisible: "$zIndex|isInvisible", 43 "aria-label": Locale.$STR("a11y.layout.z-index")}, 44 "z: " + "$zIndex") 45 ), 46 47 DIV({"class": "layoutLabelTop layoutLabel", 48 $invisible: "$outerTop|isInvisible"}, 49 SPAN({"class": "layoutLabelOuterTop editable focusStart", 50 "aria-label": Locale.$STR("a11y.layout.position top")}, 51 "$outerTop" 52 ) 53 ), 54 DIV({"class": "layoutLabelRight layoutLabel", 55 $invisible: "$outerRight|isInvisible"}, 56 SPAN({"class": "layoutLabelOuterRight editable", 57 "aria-label": Locale.$STR("a11y.layout.position right")}, 58 "$outerRight" 59 ) 60 ), 61 DIV({"class": "layoutLabelBottom layoutLabel", 62 $invisible: "$outerBottom|isInvisible"}, 63 SPAN({"class": "layoutLabelOuterBottom editable", 64 "aria-label": Locale.$STR("a11y.layout.position bottom")}, 65 "$outerBottom" 66 ) 67 ), 68 DIV({"class": "layoutLabelLeft layoutLabel", 69 $invisible: "$outerLeft|isInvisible"}, 70 SPAN({"class": "layoutLabelOuterLeft editable", 71 "aria-label": Locale.$STR("a11y.layout.position left")}, 72 "$outerLeft" 73 ) 74 ), 75 76 DIV({"class": "outerLabel layoutCaption"}, "$outerLabel"), 77 78 79 DIV({"class": "marginLayoutBox layoutBox editGroup focusGroup"}, 80 DIV({"class": "layoutCaption"}, Locale.$STR("LayoutMargin")), 81 DIV({"class": "layoutLabelTop layoutLabel", 82 $invisible: "$marginTop|isInvisible"}, 83 SPAN({"class": "layoutLabelMarginTop editable focusStart", 84 "aria-label": Locale.$STR("a11y.layout.margin top")}, 85 "$marginTop" 86 ) 87 ), 88 DIV({"class": "layoutLabelRight layoutLabel", 89 $invisible: "$marginRight|isInvisible"}, 90 SPAN({"class": "layoutLabelMarginRight editable", 91 "aria-label": Locale.$STR("a11y.layout.margin right")}, 92 "$marginRight" 93 ) 94 ), 95 DIV({"class": "layoutLabelBottom layoutLabel", 96 $invisible: "$marginBottom|isInvisible"}, 97 SPAN({"class": "layoutLabelMarginBottom editable", 98 "aria-label": Locale.$STR("a11y.layout.margin bottom")}, 99 "$marginBottom" 100 ) 101 ), 102 DIV({"class": "layoutLabelLeft layoutLabel", 103 $invisible: "$marginLeft|isInvisible"}, 104 SPAN({"class": "layoutLabelMarginLeft editable", 105 "aria-label": Locale.$STR("a11y.layout.margin left")}, 106 "$marginLeft" 107 ) 108 ), 109 110 DIV({"class": "borderLayoutBox layoutBox editGroup focusGroup"}, 111 DIV({"class": "layoutCaption"}, Locale.$STR("LayoutBorder")), 112 DIV({"class": "layoutLabelTop layoutLabel", 113 $invisible: "$borderTop|isInvisible"}, 114 SPAN({"class": "layoutLabelBorderTop editable focusStart", 115 "aria-label": Locale.$STR("a11y.layout.border top")}, 116 "$borderTop" 117 ) 118 ), 119 DIV({"class": "layoutLabelRight layoutLabel", 120 $invisible: "$borderRight|isInvisible"}, 121 SPAN({"class": "layoutLabelBorderRight editable", 122 "aria-label": Locale.$STR("a11y.layout.border right")}, 123 "$borderRight" 124 ) 125 ), 126 DIV({"class": "layoutLabelBottom layoutLabel", 127 $invisible: "$borderBottom|isInvisible"}, 128 SPAN({"class": "layoutLabelBorderBottom editable", 129 "aria-label": Locale.$STR("a11y.layout.border bottom")}, 130 "$borderBottom" 131 ) 132 ), 133 DIV({"class": "layoutLabelLeft layoutLabel", 134 $invisible: "$borderLeft|isInvisible"}, 135 SPAN({"class": "layoutLabelBorderLeft editable", 136 "aria-label": Locale.$STR("a11y.layout.border left")}, 137 "$borderLeft" 138 ) 139 ), 140 141 DIV({"class": "paddingLayoutBox layoutBox editGroup focusGroup"}, 142 DIV({"class": "layoutCaption"}, Locale.$STR("LayoutPadding")), 143 DIV({"class": "layoutLabelTop layoutLabel", 144 $invisible: "$paddingTop|isInvisible"}, 145 SPAN({"class": "layoutLabelPaddingTop editable focusStart", 146 "aria-label": Locale.$STR("a11y.layout.padding top")}, 147 "$paddingTop" 148 ) 149 ), 150 DIV({"class": "layoutLabelRight layoutLabel", 151 $invisible: "$paddingRight|isInvisible"}, 152 SPAN( 153 { 154 "class": "layoutLabelPaddingRight editable", 155 "aria-label": 156 Locale.$STR("a11y.layout.padding right") 157 }, 158 "$paddingRight" 159 ) 160 ), 161 DIV({"class": "layoutLabelBottom layoutLabel", 162 $invisible: "$paddingBottom|isInvisible"}, 163 SPAN( 164 { 165 "class": "layoutLabelPaddingBottom editable", 166 "aria-label": 167 Locale.$STR("a11y.layout.padding bottom") 168 }, 169 "$paddingBottom" 170 ) 171 ), 172 DIV({"class": "layoutLabelLeft layoutLabel", 173 $invisible: "$paddingLeft|isInvisible"}, 174 SPAN({"class": "layoutLabelPaddingLeft editable", 175 "aria-label": Locale.$STR("a11y.layout.padding left")}, 176 "$paddingLeft" 177 ) 178 ), 179 180 DIV({"class": "contentLayoutBox layoutBox editGroup focusGroup"}, 181 DIV({"class": "layoutLabelCenter layoutLabel"}, 182 SPAN({"class": "layoutLabelWidth layoutLabel editable "+ 183 "focusStart", 184 "aria-label": Locale.$STR("a11y.layout.width")}, 185 "$width" 186 ), 187 " x ", 188 SPAN({"class": "layoutLabelHeight layoutLabel editable", 189 "aria-label": Locale.$STR("a11y.layout.height")}, 190 "$height" 191 ) 192 ) 193 ) 194 ) 195 ) 196 ) 197 ) 198 ), 199 200 isInvisible: function(value) 201 { 202 return value == 0; 203 }, 204 205 getVerticalText: function(n) 206 { 207 return getVerticalText(n); 208 } 209 }), 210 211 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 212 213 onMouseOver: function(event) 214 { 215 var layoutBox = Dom.getAncestorByClass(event.target, "layoutBox"); 216 var boxFrame = layoutBox ? getBoxFrame(layoutBox) : null; 217 218 if (this.highlightedBox) 219 Css.removeClass(this.highlightedBox, "highlighted"); 220 221 this.highlightedBox = layoutBox; 222 223 if (layoutBox) 224 Css.setClass(layoutBox, "highlighted"); 225 226 Firebug.Inspector.highlightObject(this.selection, this.context, "boxModel", boxFrame); 227 }, 228 229 onMouseOut: function(event) 230 { 231 var nextTarget = event.relatedTarget; 232 if (nextTarget && Dom.getAncestorByClass(nextTarget, "layoutBox")) 233 return; 234 235 if (this.highlightedBox) 236 Css.removeClass(this.highlightedBox, "highlighted"); 237 238 this.highlightedBox = null; 239 240 Firebug.Inspector.highlightObject(null, null, "boxModel"); 241 }, 242 243 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 244 // extends Panel 245 246 name: "layout", 247 parentPanel: "html", 248 order: 2, 249 enableA11y: true, 250 251 initialize: function() 252 { 253 this.onMouseOver = Obj.bind(this.onMouseOver, this); 254 this.onMouseOut = Obj.bind(this.onMouseOut, this); 255 this.onAfterPaint = Obj.bindFixed(this.refresh, this); 256 257 Firebug.Panel.initialize.apply(this, arguments); 258 }, 259 260 initializeNode: function(oldPanelNode) 261 { 262 Events.addEventListener(this.panelNode, "mouseover", this.onMouseOver, false); 263 Events.addEventListener(this.panelNode, "mouseout", this.onMouseOut, false); 264 265 Firebug.Panel.initializeNode.apply(this, arguments); 266 }, 267 268 destroyNode: function() 269 { 270 Events.removeEventListener(this.panelNode, "mouseover", this.onMouseOver, false); 271 Events.removeEventListener(this.panelNode, "mouseout", this.onMouseOut, false); 272 273 Firebug.Panel.destroyNode.apply(this, arguments); 274 }, 275 276 show: function(state) 277 { 278 Events.addEventListener(this.context.browser, "MozAfterPaint", this.onAfterPaint, true); 279 }, 280 281 hide: function() 282 { 283 Events.removeEventListener(this.context.browser, "MozAfterPaint", this.onAfterPaint, true); 284 }, 285 286 supportsObject: function(object, type) 287 { 288 return object instanceof window.Element ? 1 : 0; 289 }, 290 291 refresh: function() 292 { 293 this.updateSelection(this.selection); 294 }, 295 296 updateSelection: function(element) 297 { 298 var view = element ? element.ownerDocument.defaultView : null; 299 if (!view) 300 return this.panelNode.textContent = ""; 301 302 var prev = Dom.getPreviousElement(element.previousSibling); 303 var next = Dom.getNextElement(element.nextSibling); 304 305 var style = view.getComputedStyle(element, ""); 306 307 var args = Css.getBoxFromStyles(style, element); 308 309 args.outerLeft = args.outerRight = args.outerTop = args.outerBottom = ''; 310 args.outerLeftMode = args.outerRightMode = args.outerTopMode = args.outerBottomMode = ""; 311 args.zIndex = args.zIndex ? args.zIndex : "auto"; 312 313 var boxSizing = style.getPropertyCSSValue("box-sizing") || 314 style.getPropertyCSSValue("-moz-box-sizing"); 315 args.boxSizing = boxSizing.cssText; 316 317 var position = style.getPropertyCSSValue("position").cssText; 318 args.position = position; 319 args.outerLabel = ""; 320 321 if (Xml.isElementSVG(element) || Xml.isElementMathML(element) || Xml.isElementXUL(element)) 322 { 323 var rect = element.getBoundingClientRect(); 324 // XXXjjb I believe this is incorrect. We should use the value as given by the call 325 //if (rect.wrappedJSObject) 326 // rect = rect.wrappedJSObject; 327 328 args.width = Math.round(rect.width); 329 args.height = Math.round(rect.height); 330 } 331 332 // these Modes are classes on the domplate 333 args.outerLeftMode = args.outerRightMode = args.outerTopMode = args.outerBottomMode = 334 "blankEdge"; 335 336 if (position == "absolute" || position == "fixed" || position == "relative") 337 { 338 function getStyle(style, name) 339 { 340 var value = style.getPropertyCSSValue(name); 341 return value && value.cssText ? parseInt(value.cssText) : " "; 342 } 343 344 args.outerLabel = Locale.$STR("LayoutPosition"); 345 346 args.outerLeft = getStyle(style, "left"); 347 args.outerTop = getStyle(style, "top"); 348 args.outerRight = getStyle(style, "right"); 349 args.outerBottom = getStyle(style, "bottom"); 350 351 args.outerLeftMode = args.outerRightMode = args.outerTopMode = args.outerBottomMode = 352 "absoluteEdge"; 353 } 354 355 var node; 356 // If the layout panel content was already created, just fill in the new values 357 if (this.panelNode.getElementsByClassName("outerLayoutBox").item(0)) 358 { 359 // The styles for the positionLayoutBox need to be set manually 360 var positionLayoutBox = this.panelNode.getElementsByClassName("positionLayoutBox"). 361 item(0); 362 positionLayoutBox.className = "positionLayoutBox "+args.outerTopMode+" "+ 363 args.outerRightMode+" "+args.outerBottomMode+" "+args.outerLeftMode+" focusGroup"; 364 365 var values = 366 { 367 layoutPosition: {label: Locale.$STR("position"), value: "position"}, 368 layoutBoxSizing: {label: Locale.$STR("a11y.layout.box-sizing"), 369 value: "boxSizing"}, 370 layoutZIndex: {label: "z", value: "zIndex"}, 371 layoutLabelOuterTop: {value: "outerTop"}, 372 layoutLabelOuterRight: {value: "outerRight"}, 373 layoutLabelOuterBottom: {value: "outerBottom"}, 374 layoutLabelOuterLeft: {value: "outerLeft"}, 375 layoutLabelMarginTop: {value: "marginTop"}, 376 layoutLabelMarginRight: {value: "marginRight"}, 377 layoutLabelMarginBottom: {value: "marginBottom"}, 378 layoutLabelMarginLeft: {value: "marginLeft"}, 379 layoutLabelBorderTop: {value: "borderTop"}, 380 layoutLabelBorderRight: {value: "borderRight"}, 381 layoutLabelBorderBottom: {value: "borderBottom"}, 382 layoutLabelBorderLeft: {value: "borderLeft"}, 383 layoutLabelPaddingTop: {value: "paddingTop"}, 384 layoutLabelPaddingRight: {value: "paddingRight"}, 385 layoutLabelPaddingBottom: {value: "paddingBottom"}, 386 layoutLabelPaddingLeft: {value: "paddingLeft"}, 387 layoutLabelWidth: {value: "width"}, 388 layoutLabelHeight: {value: "height"}, 389 outerLabel: {value: "outerLabel"}, 390 } 391 392 for (val in values) 393 { 394 var element = this.panelNode.getElementsByClassName(val).item(0); 395 396 element.textContent = values[val].label ? 397 values[val].label+": "+args[values[val].value] : args[values[val].value]; 398 399 if (this.template.isInvisible(args[values[val].value])) 400 Css.setClass(element.parentNode, "invisible"); 401 else 402 Css.removeClass(element.parentNode, "invisible"); 403 } 404 } 405 else 406 { 407 node = this.template.tag.replace(args, this.panelNode); 408 } 409 410 this.adjustCharWidth(this.getMaxCharWidth(args, node), this.panelNode); 411 412 Events.dispatch(this.fbListeners, "onLayoutBoxCreated", [this, node, args]); 413 }, 414 415 /* 416 * The nested boxes of the Layout panel have digits which need to fit between the boxes. 417 * @param maxWidth: pixels the largest digit string 418 * @param node: panelNode to be adjusted (from tag:) 419 */ 420 adjustCharWidth: function(maxWidth, node) 421 { 422 maxWidth += 10; // margin 423 if (maxWidth < 20) 424 maxWidth = 20; 425 426 this.adjustBoxWidth(node, "marginLayoutBox", maxWidth); 427 this.adjustBoxWidth(node, "borderLayoutBox", maxWidth); 428 this.adjustBoxWidth(node, "paddingLayoutBox", maxWidth); 429 430 var box = node.getElementsByClassName("outerLayoutBox").item(0); 431 box.style.cssText = "width: "+(240 + 3*maxWidth)+"px;"; // defaults to 300px 432 433 this.adjustLabelWidth(node, "layoutLabelLeft", maxWidth); 434 this.adjustLabelWidth(node, "layoutLabelRight", maxWidth); 435 }, 436 437 /* 438 * By adjusting this width, the labels can be centered. 439 */ 440 adjustLabelWidth: function(node, labelName, maxWidth) 441 { 442 var labels = node.getElementsByClassName(labelName); 443 for (var i = 0; i < labels.length; i++) 444 labels[i].style.cssText = "width: "+maxWidth+"px;"; 445 }, 446 447 adjustBoxWidth: function(node, boxName, width) 448 { 449 var box = node.getElementsByClassName(boxName).item(0); 450 box.style.cssText = "right: "+width + 'px;'+" left: "+width + "px;"; 451 }, 452 453 getMaxCharWidth: function(args, node) 454 { 455 Firebug.MeasureBox.startMeasuring(node); 456 var maxWidth = Math.max( 457 Firebug.MeasureBox.measureText(args.marginLeft+"").width, 458 Firebug.MeasureBox.measureText(args.marginRight+"").width, 459 Firebug.MeasureBox.measureText(args.borderLeft+"").width, 460 Firebug.MeasureBox.measureText(args.borderRight+"").width, 461 Firebug.MeasureBox.measureText(args.paddingLeft+"").width, 462 Firebug.MeasureBox.measureText(args.paddingRight+"").width 463 ); 464 Firebug.MeasureBox.stopMeasuring(); 465 return maxWidth; 466 }, 467 468 getOptionsMenuItems: function() 469 { 470 return [ 471 Menu.optionMenu("ShowRulers", "showRulers", 472 "layout.option.tip.Show_Rulers") 473 ]; 474 }, 475 476 getEditor: function(target, value) 477 { 478 if (!this.editor) 479 this.editor = new LayoutEditor(this.document); 480 481 return this.editor; 482 } 483 }); 484 485 // ************************************************************************************************ 486 // LayoutEditor 487 488 function LayoutEditor(doc) 489 { 490 this.initializeInline(doc); 491 492 this.noWrap = false; 493 this.numeric = true; 494 } 495 496 LayoutEditor.prototype = domplate(Firebug.InlineEditor.prototype, 497 { 498 saveEdit: function(target, value, previousValue) 499 { 500 if (!this.panel.selection.style) 501 return; 502 503 var labelBox = Dom.getAncestorByClass(target, "layoutLabel"); 504 var layoutBox = getLayoutBox(labelBox); 505 506 var boxFrame = getBoxFrame(layoutBox); 507 var boxEdge = getBoxEdge(labelBox); 508 509 var styleName; 510 if (boxFrame == "content" || boxFrame == "position") 511 styleName = boxEdge.toLowerCase(); 512 else if (boxFrame == "border") 513 styleName = boxFrame+boxEdge+"Width"; 514 else 515 styleName = boxFrame+boxEdge; 516 517 var intValue = value ? value : 0; 518 this.panel.selection.style[styleName] = intValue + "px"; 519 520 if (Firebug.Inspector.highlightedElement == this.panel.selection) 521 { 522 var boxFrame = this.highlightedBox ? getBoxFrame(this.highlightedBox) : null; 523 Firebug.Inspector.highlightObject(this.panel.selection, this.panel.context, "boxModel", boxFrame); 524 } 525 526 if (Css.hasClass(target, "layoutVerticalText")) 527 target.innerHTML = getVerticalText(intValue); 528 else 529 target.textContent = intValue; 530 }, 531 532 endEditing: function(target, value, cancel) 533 { 534 // Don't remove groups 535 return false; 536 } 537 })}; 538 539 // ************************************************************************************************ 540 // Local Helpers 541 542 function getLayoutBox(element) 543 { 544 var re = /([^\s]+)LayoutBox/; 545 for (var box = element; box; box = box.parentNode) 546 { 547 if (re.exec(box.className)) 548 return box; 549 } 550 } 551 552 function getBoxFrame(element) 553 { 554 var re = /([^\s]+)LayoutBox/; 555 var m = re.exec(element.className); 556 return m ? m[1] : ""; 557 } 558 559 function getBoxEdge(element) 560 { 561 var re = /layoutLabel([^\s]+)/; 562 var m = re.exec(element.className); 563 return m ? m[1] : ""; 564 } 565 566 function getVerticalText(n) 567 { 568 n = n+""; 569 var text = []; 570 for (var i = 0; i < n.length; ++i) 571 text.push(n[i]); 572 return text.join("<br>"); 573 } 574 575 // ************************************************************************************************ 576 // Registration 577 578 Firebug.registerPanel(LayoutPanel); 579 580 return LayoutPanel; // move into Firebug.Layout ? 581 582 // ************************************************************************************************ 583 }); 584