/* * Copyright 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.google.gwt.layout.client; import static com.google.gwt.dom.client.Style.Unit.PX; import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.Display; import com.google.gwt.dom.client.Style.Overflow; import com.google.gwt.dom.client.Style.Position; import com.google.gwt.event.logical.shared.ResizeEvent; import com.google.gwt.event.logical.shared.ResizeHandler; import com.google.gwt.layout.client.Layout.Layer; import com.google.gwt.user.client.Window; /** * IE6-specific implementation, which uses the "onresize" event, along with a * series of measurement tools, to deal with several IE6 bugs. Specifically, * IE6 doesn't support simultaneous left-right and top-bottom values, and it * misplaces by one pixel elements whose right/bottom properties are set. * * Because this implementation gets compiled in for both IE6 and 7, it * dynamically detects IE7 and punts to the super implementation. */ class LayoutImplIE6 extends LayoutImplIE8 { private static Element createStyleRuler(Element parent) { DivElement styleRuler = Document.get().createDivElement(); DivElement styleInner = Document.get().createDivElement(); styleRuler.getStyle().setPosition(Position.ABSOLUTE); styleRuler.getStyle().setLeft(-10000, PX); parent.appendChild(styleRuler); styleRuler.appendChild(styleInner); return styleRuler; } private static void hookWindowResize(final Element elem) { Window.addResizeHandler(new ResizeHandler() { public void onResize(ResizeEvent event) { resizeRelativeToParent(elem); } }); } private static native void measureDecoration(Element elem) /*-{ var ruler = elem.__styleRuler; var inner = ruler.children[0]; var s = inner.style, cs = elem.currentStyle; s.borderLeftStyle = cs.borderLeftStyle; s.borderRightStyle = cs.borderRightStyle; s.borderTopStyle = cs.borderTopStyle; s.borderBottomStyle = cs.borderBottomStyle; s.borderLeftWidth = cs.borderLeftWidth; s.borderRightWidth = cs.borderRightWidth; s.borderTopWidth = cs.borderTopWidth; s.borderBottomWidth = cs.borderBottomWidth; // Oddly enough, allowing the word 'auto' to creep into the style // ruler's margin causes it to take up all of its parent's space. s.marginLeft = (cs.marginLeft == 'auto') ? '' : cs.marginLeft; s.marginRight = (cs.marginRight == 'auto') ? '' : cs.marginRight; s.marginTop = (cs.marginTop == 'auto') ? '' : cs.marginTop; s.marginBottom = (cs.marginBottom == 'auto') ? '' : cs.marginBottom; s.paddingLeft = cs.paddingLeft; s.paddingRight = cs.paddingRight; s.paddingTop = cs.paddingTop; s.paddingBottom = cs.paddingBottom; s.width = s.height = 32; elem.__decoWidth = ruler.offsetWidth - 32; elem.__decoHeight = ruler.offsetHeight - 32; }-*/; private static native void resizeRelativeToParent(Element elem) /*-{ var parent = elem.__resizeParent; if (parent) { @com.google.gwt.layout.client.LayoutImplIE6::measureDecoration(Lcom/google/gwt/dom/client/Element;)(elem); elem.style.left = 0; elem.style.top = 0; elem.style.width = parent.clientWidth - elem.__decoWidth; elem.style.height = parent.clientHeight - elem.__decoHeight; } }-*/; private static native void setLayer(Element container, Layer layer) /*-{ // Potential leak: This is cleaned up in detach(). container.__layer = layer; }-*/; private static native void setPropertyElement(Element elem, String name, Element value) /*-{ elem[name] = value; }-*/; @Override public Element attachChild(Element parent, Element child, Element before) { DivElement container = Document.get().createDivElement(); container.appendChild(child); container.getStyle().setPosition(Position.ABSOLUTE); container.getStyle().setOverflow(Overflow.HIDDEN); child.getStyle().setPosition(Position.ABSOLUTE); // Hang the style ruler from the container element, but associate it with // the child element, so that measureDecoration(child) will work. setPropertyElement(child, "__styleRuler", createStyleRuler(container)); Element beforeContainer = null; if (before != null) { beforeContainer = before.getParentElement(); assert beforeContainer.getParentElement() == parent : "Element to insert before must be a sibling"; } parent.insertBefore(container, beforeContainer); return container; } @Override public void fillParent(Element elem) { fillParentImpl(elem); } @Override public void finalizeLayout(Element parent) { resizeRelativeToParent(parent); resizeHandler(parent, true); } @Override public void initParent(Element parent) { super.initParent(parent); setPropertyElement(parent, "__styleRuler", createStyleRuler(parent)); } @Override public void layout(Layer layer) { Element elem = layer.container; Style style = elem.getStyle(); if (layer.visible) { style.clearDisplay(); } else { style.setDisplay(Display.NONE); } setLayer(elem, layer); } @Override public void onAttach(Element parent) { // No need to re-connect layer refs. This will be taken care of // automatically in layout(). initResizeHandler(parent); initUnitChangeHandler(parent, relativeRuler); } @Override public void onDetach(Element parent) { removeLayerRefs(parent); removeResizeHandler(parent); removeUnitChangeHandler(relativeRuler); } private native void fillParentImpl(Element elem) /*-{ // Hook the parent element's onresize event. If the parent is the <body>, // then we have to go through the Window class to get the resize event, // because IE6 requires a bunch of foul hackery to safely hook it. var parent = elem.parentElement; if (parent.tagName.toLowerCase() == 'body') { elem.style.position = 'absolute'; var docElem = parent.parentElement; elem.__resizeParent = docElem; @com.google.gwt.layout.client.LayoutImplIE6::hookWindowResize(Lcom/google/gwt/dom/client/Element;)(elem); return; } function resize() { @com.google.gwt.layout.client.LayoutImplIE6::resizeRelativeToParent(Lcom/google/gwt/dom/client/Element;)(elem); } elem.__resizeParent = parent; parent.onresize = resize; resize(); }-*/; /** * This does not call $entry() because no user code is reachable from * resizeHandler. */ private native void initResizeHandler(Element parent) /*-{ // Potential leak: This is cleaned up in detach(). var self = this; parent.onresize = function() { self.@com.google.gwt.layout.client.LayoutImplIE6::resizeHandler(Lcom/google/gwt/dom/client/Element;)(parent); }; }-*/; /** * This does not call $entry() because no user code is reachable from * resizeHandler. */ private native void initUnitChangeHandler(Element parent, Element ruler) /*-{ // Potential leak: This is cleaned up in detach(). var self = this; ruler.onresize = function() { self.@com.google.gwt.layout.client.LayoutImplIE6::resizeHandler(Lcom/google/gwt/dom/client/Element;Z)(parent, true); }; }-*/; private native void removeLayerRefs(Element parent) /*-{ for (var i = 0; i < parent.childNodes.length; ++i) { var container = parent.childNodes[i]; if (container.__layer) { container.__layer = null; } } }-*/; private native void removeResizeHandler(Element parent) /*-{ parent.onresize = null; }-*/; private native void removeUnitChangeHandler(Element ruler) /*-{ ruler.onresize = null; }-*/; private void resizeHandler(Element parent) { resizeHandler(parent, false); } private native void resizeHandler(Element parent, boolean force) /*-{ if (!force && ((parent.offsetWidth == parent.__oldWidth) && (parent.offsetHeight == parent.__oldHeight))) { return; } parent.__oldWidth = parent.offsetWidth; parent.__oldHeight = parent.offsetHeight; var parentWidth = parent.clientWidth; var parentHeight = parent.clientHeight; // Iterate over the children, assuming each of them is an unstyled // container element. for (var i = 0; i < parent.childNodes.length; ++i) { // Don't attempt to layout the rulers. var container = parent.childNodes[i]; var layer = container.__layer; if (!layer) { continue; } // This is ugly, but it's less ugly than writing all these JSNI refs inline. var _setLeft = layer.@com.google.gwt.layout.client.Layout.Layer::setLeft; var _setTop = layer.@com.google.gwt.layout.client.Layout.Layer::setTop; var _setWidth = layer.@com.google.gwt.layout.client.Layout.Layer::setWidth; var _setHeight = layer.@com.google.gwt.layout.client.Layout.Layer::setHeight; var _setRight = layer.@com.google.gwt.layout.client.Layout.Layer::setRight; var _setBottom = layer.@com.google.gwt.layout.client.Layout.Layer::setBottom; var _left = layer.@com.google.gwt.layout.client.Layout.Layer::left; var _top = layer.@com.google.gwt.layout.client.Layout.Layer::top; var _width = layer.@com.google.gwt.layout.client.Layout.Layer::width; var _height = layer.@com.google.gwt.layout.client.Layout.Layer::height; var _right = layer.@com.google.gwt.layout.client.Layout.Layer::right; var _bottom = layer.@com.google.gwt.layout.client.Layout.Layer::bottom; var _leftUnit = layer.@com.google.gwt.layout.client.Layout.Layer::leftUnit; var _topUnit = layer.@com.google.gwt.layout.client.Layout.Layer::topUnit; var _widthUnit = layer.@com.google.gwt.layout.client.Layout.Layer::widthUnit; var _heightUnit = layer.@com.google.gwt.layout.client.Layout.Layer::heightUnit; var _rightUnit = layer.@com.google.gwt.layout.client.Layout.Layer::rightUnit; var _bottomUnit = layer.@com.google.gwt.layout.client.Layout.Layer::bottomUnit; var _hPos = layer.@com.google.gwt.layout.client.Layout.Layer::hPos .@com.google.gwt.layout.client.Layout.Alignment::ordinal()(); var _vPos = layer.@com.google.gwt.layout.client.Layout.Layer::vPos .@com.google.gwt.layout.client.Layout.Alignment::ordinal()(); // Apply the requested position & size values to the element's style. var style = container.style; style.left = _setLeft ? (_left + _leftUnit.@com.google.gwt.dom.client.Style.Unit::getType()()) : ""; style.top = _setTop ? (_top + _topUnit.@com.google.gwt.dom.client.Style.Unit::getType()()) : ""; style.width = _setWidth ? (_width + _widthUnit.@com.google.gwt.dom.client.Style.Unit::getType()()) : ""; style.height = _setHeight ? (_height + _heightUnit.@com.google.gwt.dom.client.Style.Unit::getType()()) : ""; // If right is defined, reposition/size the container horizontally. if (_setRight) { var ratio = this.@com.google.gwt.layout.client.LayoutImplIE6::getUnitSizeInPixels(Lcom/google/gwt/dom/client/Element;Lcom/google/gwt/dom/client/Style$Unit;Z)(parent, _rightUnit, false); var right = parentWidth - (_right * ratio); if (!_setLeft) { // There's no left defined; adjust the left position move the element to the right edge. container.style.left = (right - container.offsetWidth) + 'px'; } else { // Both left and right are defined; calculate the width and set it. var leftRatio = this.@com.google.gwt.layout.client.LayoutImplIE6::getUnitSizeInPixels(Lcom/google/gwt/dom/client/Element;Lcom/google/gwt/dom/client/Style$Unit;Z)(parent, _leftUnit, false); var left = (_left * leftRatio); if (right > left) { container.style.width = (right - left) + 'px'; } } } // If bottom is defined, reposition/size the container vertically. if (_setBottom) { var ratio = this.@com.google.gwt.layout.client.LayoutImplIE6::getUnitSizeInPixels(Lcom/google/gwt/dom/client/Element;Lcom/google/gwt/dom/client/Style$Unit;Z)(parent, _bottomUnit, true); var bottom = parentHeight - (_bottom * ratio); if (!_setTop) { // There's no top defined; adjust the left position move the element to the bottom edge. container.style.top = (bottom - container.offsetHeight) + 'px'; } else { // Both top and bottom are defined; calculate the height and set it. var topRatio = this.@com.google.gwt.layout.client.LayoutImplIE6::getUnitSizeInPixels(Lcom/google/gwt/dom/client/Element;Lcom/google/gwt/dom/client/Style$Unit;Z)(parent, _topUnit, true); var top = (_top * topRatio); if (bottom > top) { container.style.height = (bottom - top) + 'px'; } } } // Resize and position the child based on the layer's [hv]Pos. var child = container.firstChild; @com.google.gwt.layout.client.LayoutImplIE6::measureDecoration(Lcom/google/gwt/dom/client/Element;)(child); var childDecoWidth = child.__decoWidth; var childDecoHeight = child.__decoHeight; if (container.offsetWidth > childDecoWidth) { switch (_hPos) { case 0: // BEGIN child.style.left = '0px'; break; case 1: // END child.style.left = (container.offsetWidth - childDecoWidth - child.offsetWidth) + 'px'; break; case 2: // STRETCH child.style.left = '0px'; child.style.width = (container.offsetWidth - childDecoWidth) + 'px'; break; } } if (container.offsetHeight > childDecoHeight) { switch (_vPos) { case 0: // BEGIN child.style.top = '0px'; break; case 1: // END child.style.top = (container.offsetHeight - childDecoHeight - child.offsetHeight) + 'px'; break; case 2: // STRETCH child.style.top = '0px'; child.style.height = (container.offsetHeight - childDecoHeight) + 'px'; break; } } } }-*/; }