| /* | |
| * 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; | |
| } | |
| } | |
| } | |
| }-*/; | |
| } |