blob: 70a0856143f4f10d13bd33395d1492dce65f2c79 [file] [log] [blame]
/*
* 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;
}
@SuppressWarnings("unused") // called from JSNI
private static void hookWindowResize(final Element elem) {
Window.addResizeHandler(new ResizeHandler() {
public void onResize(ResizeEvent event) {
resizeRelativeToParent(elem);
}
});
}
@SuppressWarnings("unused") // called from JSNI
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;
}-*/;
@SuppressWarnings("unused") // called from JSNI
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;
}
}
}
}-*/;
}