1 /* See license.txt for terms of usage */
  2 
  3 FBL.ns(function() { with (FBL) {
  4 
  5 // ************************************************************************************************
  6 
  7 function LayoutPanel() {}
  8 
  9 LayoutPanel.prototype = extend(Firebug.Panel,
 10 {
 11     template: domplate(
 12     {
 13         tag:
 14             DIV({class: "outerLayoutBox"},
 15                 DIV({class: "positionLayoutBox $outerTopMode $outerRightMode $outerBottomMode $outerLeftMode focusGroup"},
 16                     DIV({class: "layoutEdgeTop layoutEdge"}),
 17                     DIV({class: "layoutEdgeRight layoutEdge"}),
 18                     DIV({class: "layoutEdgeBottom layoutEdge"}),
 19                     DIV({class: "layoutEdgeLeft layoutEdge"}),
 20 
 21                     DIV({class: "layoutLabelBottom layoutLabel layoutLabelPosition"},
 22                             SPAN({class: "layoutPosition layoutCaption", 'aria-label' : $STR('a11y.layout.position')}, $STR('position')+": "+'$position'),
 23                             SPAN({class: "layoutZIndex v$zIndex", 'aria-label' : $STR('a11y.layout.z-index')}, "z: "+'$zIndex')
 24                         ),
 25 
 26                     DIV({class: "layoutLabelTop layoutLabel v$outerTop"},
 27                         SPAN({class: "editable focusStart", 'aria-label' : $STR('a11y.layout.position top')}, '$outerTop')
 28                     ),
 29                     DIV({class: "layoutLabelRight layoutLabel v$outerRight"},
 30                         SPAN({class: "editable", 'aria-label' : $STR('a11y.layout.position right')}, '$outerRight')
 31                     ),
 32                     DIV({class: "layoutLabelBottom layoutLabel v$outerBottom"},
 33                         SPAN({class: "editable", 'aria-label' : $STR('a11y.layout.position bottom')}, '$outerBottom')
 34                     ),
 35                     DIV({class: "layoutLabelLeft layoutLabel v$outerLeft"},
 36                         SPAN({class: "editable", 'aria-label' : $STR('a11y.layout.position left')}, '$outerLeft')
 37                     ),
 38 
 39                     DIV({class: "layoutCaption"}, '$outerLabel'),
 40 
 41 
 42                     DIV({class: "marginLayoutBox layoutBox editGroup focusGroup"},
 43                         DIV({class: "layoutCaption"}, $STR("LayoutMargin")),
 44                         DIV({class: "layoutLabelTop layoutLabel v$marginTop"},
 45                             SPAN({class: "editable focusStart", 'aria-label' : $STR('a11y.layout.margin top')}, '$marginTop')
 46                         ),
 47                         DIV({class: "layoutLabelRight layoutLabel v$marginRight"},
 48                             SPAN({class: "editable", 'aria-label' : $STR('a11y.layout..margin right')}, '$marginRight')
 49                         ),
 50                         DIV({class: "layoutLabelBottom layoutLabel v$marginBottom"},
 51                             SPAN({class: "editable", 'aria-label' : $STR('a11y.layout.margin bottom')}, '$marginBottom')
 52                         ),
 53                         DIV({class: "layoutLabelLeft layoutLabel v$marginLeft"},
 54                             SPAN({class: "editable", 'aria-label' : $STR('a11y.layout.margin left')}, '$marginLeft')
 55                         ),
 56 
 57                         DIV({class: "borderLayoutBox layoutBox editGroup focusGroup"},
 58                             DIV({class: "layoutCaption"}, $STR("LayoutBorder")),
 59                             DIV({class: "layoutLabelTop layoutLabel v$borderTop"},
 60                                 SPAN({class: "editable  focusStart", 'aria-label' : $STR('a11y.layout.border top')}, '$borderTop')
 61                             ),
 62                             DIV({class: "layoutLabelRight layoutLabel v$borderRight"},
 63                                 SPAN({class: "editable", 'aria-label' : $STR('a11y.layout.border right')}, '$borderRight')
 64                             ),
 65                             DIV({class: "layoutLabelBottom layoutLabel v$borderBottom"},
 66                                 SPAN({class: "editable", 'aria-label' : $STR('a11y.layout.border bottom')}, '$borderBottom')
 67                             ),
 68                             DIV({class: "layoutLabelLeft layoutLabel v$borderLeft"},
 69                                 SPAN({class: "editable", 'aria-label' : $STR('a11y.layout.border left')}, '$borderLeft')
 70                             ),
 71 
 72                             DIV({class: "paddingLayoutBox layoutBox editGroup focusGroup"},
 73                                 DIV({class: "layoutCaption"}, $STR("LayoutPadding")),
 74                                 DIV({class: "layoutLabelTop layoutLabel v$paddingTop"},
 75                                     SPAN({class: "editable focusStart", 'aria-label' : $STR('a11y.layout.padding top')}, '$paddingTop')
 76                                 ),
 77                                 DIV({class: "layoutLabelRight layoutLabel v$paddingRight"},
 78                                     SPAN({class: "editable", 'aria-label' : $STR('a11y.layout.padding right')}, '$paddingRight')
 79                                 ),
 80                                 DIV({class: "layoutLabelBottom layoutLabel v$paddingBottom"},
 81                                     SPAN({class: "editable", 'aria-label' : $STR('a11y.layout.padding bottom')}, '$paddingBottom')
 82                                 ),
 83                                 DIV({class: "layoutLabelLeft layoutLabel v$paddingLeft"},
 84                                     SPAN({class: "editable", 'aria-label' : $STR('a11y.layout.padding left')}, '$paddingLeft')
 85                                 ),
 86 
 87                                 DIV({class: "contentLayoutBox layoutBox editGroup focusGroup"},
 88                                     DIV({class: "layoutLabelCenter layoutLabel"},
 89                                         SPAN({class: "layoutLabelWidth layoutLabel editable focusStart", 'aria-label' : $STR('a11y.layout.width')}, '$width'),
 90                                         " x ",
 91                                         SPAN({class: "layoutLabelHeight layoutLabel editable", 'aria-label' : $STR('a11y.layout.height')}, '$height')
 92                                     )
 93                                 )
 94                             )
 95                         )
 96                     )
 97                 )
 98             ),
 99 
100         getVerticalText: function(n)
101         {
102             return getVerticalText(n);
103         }
104     }),
105 
106     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
107 
108     onMouseOver: function(event)
109     {
110         var layoutBox = getAncestorByClass(event.target, "layoutBox");
111         var boxFrame = layoutBox ? getBoxFrame(layoutBox) : null;
112 
113         if (this.highlightedBox)
114             removeClass(this.highlightedBox, "highlighted");
115 
116         this.highlightedBox = layoutBox;
117 
118         if (layoutBox)
119             setClass(layoutBox, "highlighted");
120 
121         Firebug.Inspector.highlightObject(this.selection, this.context, "boxModel", boxFrame);
122     },
123 
124     onMouseOut: function(event)
125     {
126         var nextTarget = event.relatedTarget;
127         if (nextTarget && getAncestorByClass(nextTarget, "layoutBox"))
128             return;
129 
130         if (this.highlightedBox)
131             removeClass(this.highlightedBox, "highlighted");
132 
133         this.highlightedBox = null;
134 
135         Firebug.Inspector.highlightObject(null, null, "boxModel");
136     },
137 
138     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
139     // extends Panel
140 
141     name: "layout",
142     parentPanel: "html",
143     order: 2,
144 
145     initialize: function()
146     {
147         this.onMouseOver = bind(this.onMouseOver, this);
148         this.onMouseOut = bind(this.onMouseOut, this);
149 
150         Firebug.Panel.initialize.apply(this, arguments);
151     },
152 
153     initializeNode: function(oldPanelNode)
154     {
155         this.panelNode.addEventListener("mouseover", this.onMouseOver, false);
156         this.panelNode.addEventListener("mouseout", this.onMouseOut, false);
157         dispatch([Firebug.A11yModel], 'onInitializeNode', [this]);
158     },
159 
160     destroyNode: function()
161     {
162         this.panelNode.removeEventListener("mouseover", this.onMouseOver, false);
163         this.panelNode.removeEventListener("mouseout", this.onMouseOut, false);
164         dispatch([Firebug.A11yModel], 'onDestroyNode', [this]);
165     },
166 
167     supportsObject: function(object)
168     {
169         return object instanceof Element ? 1 : 0;
170     },
171 
172     refresh: function()
173     {
174         this.updateSelection(this.selection);
175     },
176 
177     updateSelection: function(element)
178     {
179         var view = element ? element.ownerDocument.defaultView : null;
180         if (!view)
181             return this.panelNode.innerHTML = "";
182 
183         var prev = getPreviousElement(element.previousSibling);
184         var next = getNextElement(element.nextSibling);
185 
186         var style = view.getComputedStyle(element, "");
187         var prevStyle = prev ? view.getComputedStyle(prev, "") : null;
188         var nextStyle = next ? view.getComputedStyle(next, "") : null;
189 
190         var args = getBoxFromStyles(style, element);
191 
192         args.outerLeft = args.outerRight = args.outerTop = args.outerBottom = '';
193         args.outerLeftMode = args.outerRightMode = args.outerTopMode = args.outerBottomMode = "";
194         args.zIndex = args.zIndex ? args.zIndex : "auto";
195 
196         var position = style.getPropertyCSSValue("position").cssText;
197         args.position = position;
198         args.outerLabel = '';
199         
200         if (isElementSVG(element) || isElementMathML(element) || isElementXUL(element))
201         {
202             var rect = element.getBoundingClientRect();
203             if (rect.wrappedJSObject)
204                 rect = rect.wrappedJSObject;
205 
206             args.width = Math.round(rect.width);
207             args.height = Math.round(rect.height);
208         }
209         
210         // these Modes are classes on the domplate
211         args.outerLeftMode = args.outerRightMode = args.outerTopMode
212         = args.outerBottomMode = "blankEdge";
213         
214         if (position == "absolute" || position == "fixed" || position == "relative")
215         {
216             function getStyle(style, name) { var v = style.getPropertyCSSValue(name); return (v && v.cssText) ? parseInt(v.cssText) : ' '; }
217 
218             args.outerLabel = $STR("LayoutPosition");
219             
220             args.outerLeft = getStyle(style,'left');
221             args.outerTop = getStyle(style,'top');
222             args.outerRight = getStyle(style,'right');
223             args.outerBottom = getStyle(style,'bottom');
224             
225             args.outerLeftMode = args.outerRightMode = args.outerTopMode
226                 = args.outerBottomMode = "absoluteEdge";
227         }
228         
229         var node = this.template.tag.replace(args, this.panelNode);
230         this.adjustCharWidth(this.getMaxCharWidth(args, node), this.panelNode);
231 
232         dispatch([Firebug.A11yModel], 'onLayoutBoxCreated', [this, node, args]);
233     },
234 
235     /*
236      * The nested boxes of the Layout panel have digits which need to fit between the boxes.
237      * @param maxWidth: pixels the largest digit string
238      * @param node: panelNode to be adjusted (from tag:)
239      */
240     adjustCharWidth: function(maxWidth, node)
241     {
242         maxWidth += 10; // margin
243         if (maxWidth < 20)
244             maxWidth = 20;
245 
246         this.adjustBoxWidth(node, "marginLayoutBox", maxWidth);
247         this.adjustBoxWidth(node, "borderLayoutBox", maxWidth);
248         this.adjustBoxWidth(node, "paddingLayoutBox", maxWidth);
249 
250         var box = node.getElementsByClassName("outerLayoutBox").item(0);
251         box.style.cssText = "width: "+(240 + 3*maxWidth)+"px;";  // defaults to 300px
252 
253         this.adjustLabelWidth(node, "layoutLabelLeft", maxWidth);
254         this.adjustLabelWidth(node, "layoutLabelRight", maxWidth);
255     },
256 
257     /*
258      * By adjusting this width, the labels can be centered.
259      */
260     adjustLabelWidth: function(node, labelName, maxWidth)
261     {
262         var labels = node.getElementsByClassName(labelName);
263         for (var i = 0; i < labels.length; i++)
264             labels[i].style.cssText = "width: "+maxWidth+"px;";
265     },
266 
267     adjustBoxWidth: function(node, boxName, width)
268     {
269         var box = node.getElementsByClassName(boxName).item(0);
270         box.style.cssText = "right: "+width + 'px;'+" left: "+width + 'px;';
271     },
272 
273     getMaxCharWidth: function(args, node)
274     {
275         Firebug.MeasureBox.startMeasuring(node);
276         var maxWidth = Math.max(
277                 Firebug.MeasureBox.measureText(args.marginLeft+"").width,
278                 Firebug.MeasureBox.measureText(args.marginRight+"").width,
279                 Firebug.MeasureBox.measureText(args.borderLeft+"").width,
280                 Firebug.MeasureBox.measureText(args.borderRight+"").width,
281                 Firebug.MeasureBox.measureText(args.paddingLeft+"").width,
282                 Firebug.MeasureBox.measureText(args.paddingRight+"").width
283                 );
284         Firebug.MeasureBox.stopMeasuring();
285         return maxWidth;
286     },
287 
288     updateOption: function(name, value)
289     {
290         /*
291         if (name == "newOptionHere")
292         {
293             this.updateSelection(this.selection);
294         }
295         */
296     },
297 
298     getOptionsMenuItems: function()
299     {
300         return [
301             optionMenu("ShowRulers", "showRulers")
302         ];
303     },
304 
305     getEditor: function(target, value)
306     {
307         if (!this.editor)
308             this.editor = new LayoutEditor(this.document);
309 
310         return this.editor;
311     }
312 });
313 
314 // ************************************************************************************************
315 // LayoutEditor
316 
317 function LayoutEditor(doc)
318 {
319     this.initializeInline(doc);
320 
321     this.noWrap = false;
322     this.numeric = true;
323 }
324 
325 LayoutEditor.prototype = domplate(Firebug.InlineEditor.prototype,
326 {
327     saveEdit: function(target, value, previousValue)
328     {
329         if (!this.panel.selection.style)
330             return;
331 
332         var labelBox = getAncestorByClass(target, "layoutLabel");
333         var layoutBox = getLayoutBox(labelBox);
334 
335         var boxFrame = getBoxFrame(layoutBox);
336         var boxEdge = getBoxEdge(labelBox);
337 
338         var styleName;
339         if (boxFrame == "content" || boxFrame == "position")
340             styleName = boxEdge.toLowerCase();
341         else if (boxFrame == "border")
342             styleName = boxFrame+boxEdge+"Width";
343         else
344             styleName = boxFrame+boxEdge;
345 
346         var intValue = value ? value : 0;
347         this.panel.selection.style[styleName] = intValue + "px";
348 
349         if (Firebug.Inspector.highlightedElement == this.panel.selection)
350         {
351             var boxFrame = this.highlightedBox ? getBoxFrame(this.highlightedBox) : null;
352             Firebug.Inspector.highlightObject(this.panel.selection, this.panel.context, "boxModel", boxFrame);
353         }
354 
355         if (hasClass(target, "layoutVerticalText"))
356             target.innerHTML = getVerticalText(intValue);
357         else
358             target.innerHTML = intValue;
359 
360         if (previousValue == "0" && !!value)
361             removeClass(target.parentNode, "v0");
362         else if (!value)
363             setClass(target.parentNode, "v0");
364     },
365 
366     endEditing: function(target, value, cancel)
367     {
368         // Don't remove groups
369         return false;
370     }
371 });
372 
373 // ************************************************************************************************
374 // Local Helpers
375 
376 function getLayoutBox(element)
377 {
378     var re = /([^\s]+)LayoutBox/;
379     for (var box = element; box; box = box.parentNode)
380     {
381         if (re.exec(box.className))
382             return box;
383     }
384 }
385 
386 function getBoxFrame(element)
387 {
388     var re = /([^\s]+)LayoutBox/;
389     var m = re.exec(element.className);
390     return m ? m[1] : "";
391 }
392 
393 function getBoxEdge(element)
394 {
395     var re = /layoutLabel([^\s]+)/;
396     var m = re.exec(element.className);
397     return m ? m[1] : "";
398 }
399 
400 function getVerticalText(n)
401 {
402     n = n+"";
403     var text = [];
404     for (var i = 0; i < n.length; ++i)
405         text.push(n[i]);
406     return text.join("<br>");
407 }
408 
409 // ************************************************************************************************
410 
411 Firebug.registerPanel(LayoutPanel);
412 
413 // ************************************************************************************************
414 
415 }});
416