Revision: 9576
Author: gwt.mirror...@gmail.com
Date: Thu Jan 20 06:21:05 2011
Log: Adding a new widget ResizeLayoutPanel that can trigger a resize event
when it changes size. The widget uses hidden scrollable divs to detect
resize on non-IE browsers. This allow users to embed widgets that
RequireResize in their app without having an unbroken chain of
ProvidesResize up to the RootLayoutPanel.
Also adding a new widget HeaderPanel that uses the same resize
implementation to layout content between naturally sized headers and
footers. This is similar to a DockLayoutPanel (north/south/center only),
but it doesn't require users to specify a specific height for the header
and footer.
Review at http://gwt-code-reviews.appspot.com/1301801
Review by: p...@google.com
http://code.google.com/p/google-web-toolkit/source/detail?r=9576
Added:
/trunk/user/src/com/google/gwt/user/ResizeLayoutPanel.gwt.xml
/trunk/user/src/com/google/gwt/user/client/ui/HeaderPanel.java
/trunk/user/src/com/google/gwt/user/client/ui/ResizeLayoutPanel.java
/trunk/user/test/com/google/gwt/user/client/ui/HeaderPanelTest.java
/trunk/user/test/com/google/gwt/user/client/ui/ResizeLayoutPanelTest.java
Modified:
/trunk/user/src/com/google/gwt/user/User.gwt.xml
/trunk/user/test/com/google/gwt/user/UISuite.java
=======================================
--- /dev/null
+++ /trunk/user/src/com/google/gwt/user/ResizeLayoutPanel.gwt.xml Thu Jan
20 06:21:05 2011
@@ -0,0 +1,39 @@
+<!--
-->
+<!-- Copyright 2011 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 -->
+<!-- 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. License for the specific language governing permissions
and -->
+<!-- limitations under the
License. -->
+
+<!-- Deferred binding rules for
ResizeLayoutPanel. -->
+<!--
-->
+<!-- This module is typically inherited via
com.google.gwt.user.User -->
+<!--
-->
+<module>
+ <inherits name="com.google.gwt.core.Core"/>
+ <inherits name="com.google.gwt.user.UserAgent"/>
+
+ <!-- Most browsers do not support onresize. -->
+ <replace-with
class="com.google.gwt.user.client.ui.ResizeLayoutPanel.ImplStandard">
+ <when-type-is
class="com.google.gwt.user.client.ui.ResizeLayoutPanel.Impl"/>
+ </replace-with>
+
+ <!-- IE supports onresize. -->
+ <replace-with
class="com.google.gwt.user.client.ui.ResizeLayoutPanel.ImplTrident">
+ <when-type-is
class="com.google.gwt.user.client.ui.ResizeLayoutPanel.Impl"/>
+ <when-property-is name="user.agent" value="ie8"/>
+ </replace-with>
+
+ <!-- IE6 needs to be kicked to render correctly. -->
+ <replace-with
class="com.google.gwt.user.client.ui.ResizeLayoutPanel.ImplIE6">
+ <when-type-is
class="com.google.gwt.user.client.ui.ResizeLayoutPanel.Impl"/>
+ <when-property-is name="user.agent" value="ie6"/>
+ </replace-with>
+</module>
=======================================
--- /dev/null
+++ /trunk/user/src/com/google/gwt/user/client/ui/HeaderPanel.java Thu Jan
20 06:21:05 2011
@@ -0,0 +1,376 @@
+/*
+ * Copyright 2011 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.user.client.ui;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Document;
+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.dom.client.Style.Unit;
+import com.google.gwt.user.client.Element;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * A panel that includes a header (top), footer (bottom), and content
(middle)
+ * area. The header and footer areas resize naturally. The content area is
+ * allocated all of the remaining space between the header and footer area.
+ */
+public class HeaderPanel extends Panel implements RequiresResize {
+
+ private Widget content;
+ private final Element contentContainer;
+ private Widget footer;
+ private final Element footerContainer;
+ private final ResizeLayoutPanel.Impl footerImpl =
+ GWT.create(ResizeLayoutPanel.Impl.class);
+ private Widget header;
+ private final Element headerContainer;
+ private final ResizeLayoutPanel.Impl headerImpl =
+ GWT.create(ResizeLayoutPanel.Impl.class);
+ private final ScheduledCommand layoutCmd = new ScheduledCommand() {
+ public void execute() {
+ layoutScheduled = false;
+ forceLayout();
+ }
+ };
+ private boolean layoutScheduled = false;
+
+ public HeaderPanel() {
+ // Create the outer element
+ Element elem = Document.get().createDivElement().cast();
+ elem.getStyle().setPosition(Position.RELATIVE);
+ elem.getStyle().setOverflow(Overflow.HIDDEN);
+ setElement(elem);
+
+ // Create a delegate to handle resize from the header and footer.
+ ResizeLayoutPanel.Impl.Delegate resizeDelegate = new
ResizeLayoutPanel.Impl.Delegate() {
+ public void onResize() {
+ scheduledLayout();
+ }
+ };
+
+ // Create the header container.
+ headerContainer = createContainer();
+ headerContainer.getStyle().setTop(0.0, Unit.PX);
+ headerImpl.init(headerContainer, resizeDelegate);
+ elem.appendChild(headerContainer);
+
+ // Create the footer container.
+ footerContainer = createContainer();
+ footerContainer.getStyle().setBottom(0.0, Unit.PX);
+ footerImpl.init(footerContainer, resizeDelegate);
+ elem.appendChild(footerContainer);
+
+ // Create the content container.
+ contentContainer = createContainer();
+ contentContainer.getStyle().setOverflow(Overflow.HIDDEN);
+ contentContainer.getStyle().setTop(0.0, Unit.PX);
+ contentContainer.getStyle().setHeight(0.0, Unit.PX);
+ elem.appendChild(contentContainer);
+ }
+
+ /**
+ * Adds a widget to this panel.
+ *
+ * @param w the child widget to be added
+ */
+ @Override
+ public void add(Widget w) {
+ // Add widgets in the order that they appear.
+ if (header == null) {
+ setHeaderWidget(w);
+ } else if (content == null) {
+ setContentWidget(w);
+ } else if (footer == null) {
+ setFooterWidget(w);
+ } else {
+ throw new IllegalStateException(
+ "HeaderPanel already contains header, content, and footer
widgets.");
+ }
+ }
+
+ /**
+ * Get the content widget that appears between the header and footer.
+ *
+ * @return the content {@link Widget}
+ */
+ public Widget getContentWidget() {
+ return content;
+ }
+
+ /**
+ * Get the footer widget at the bottom of the panel.
+ *
+ * @return the footer {@link Widget}
+ */
+ public Widget getFooterWidget() {
+ return footer;
+ }
+
+ /**
+ * Get the header widget at the top of the panel.
+ *
+ * @return the header {@link Widget}
+ */
+ public Widget getHeaderWidget() {
+ return header;
+ }
+
+ public Iterator<Widget> iterator() {
+ // Return a simple iterator that iterates over the header, content, and
+ // footer in order.
+ return new Iterator<Widget>() {
+ private int index = -1;
+
+ public boolean hasNext() {
+ switch (index) {
+ case -1:
+ if (header != null) {
+ return true;
+ }
+ case 0: // Intentional fallthrough.
+ if (content != null) {
+ return true;
+ }
+ case 1: // Intentional fallthrough.
+ if (footer != null) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public Widget next() {
+ switch (index) {
+ case -1:
+ index++;
+ if (header != null) {
+ return header;
+ }
+ case 0: // Intentional fallthrough.
+ index++;
+ if (content != null) {
+ return content;
+ }
+ case 1: // Intentional fallthrough.
+ index++;
+ if (footer != null) {
+ return footer;
+ }
+ }
+ throw new NoSuchElementException();
+ }
+
+ public void remove() {
+ switch (index) {
+ case 0:
+ doRemove(header, "Header");
+ break;
+ case 1:
+ doRemove(content, "Content");
+ break;
+ case 2:
+ doRemove(footer, "Footer");
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+ private void doRemove(Widget widget, String position) {
+ if (widget == null) {
+ throw new IllegalStateException(position + " was already
removed.");
+ }
+ HeaderPanel.this.remove(widget);
+ }
+ };
+ }
+
+ @Override
+ public void onAttach() {
+ super.onAttach();
+ headerImpl.onAttach();
+ footerImpl.onAttach();
+ scheduledLayout();
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ headerImpl.onDetach();
+ footerImpl.onDetach();
+ }
+
+ public void onResize() {
+ // Handle the outer element resizing.
+ scheduledLayout();
+ }
+
+ @Override
+ public boolean remove(Widget w) {
+ // Validate.
+ if (w.getParent() != this) {
+ return false;
+ }
+ // Orphan.
+ try {
+ orphan(w);
+ } finally {
+ // Physical detach.
+ w.getElement().removeFromParent();
+
+ // Logical detach.
+ if (w == content) {
+ content = null;
+ contentContainer.getStyle().setDisplay(Display.NONE);
+ } else if (w == header) {
+ header = null;
+ headerContainer.getStyle().setDisplay(Display.NONE);
+ } else if (w == footer) {
+ footer = null;
+ footerContainer.getStyle().setDisplay(Display.NONE);
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Set the widget in the content portion between the header and footer.
+ *
+ * @param w the widget to use as the content
+ */
+ public void setContentWidget(Widget w) {
+ contentContainer.getStyle().clearDisplay();
+ add(w, content, contentContainer);
+
+ // Logical attach.
+ content = w;
+ scheduledLayout();
+ }
+
+ /**
+ * Set the widget in the footer portion at the bottom of the panel.
+ *
+ * @param w the widget to use as the footer
+ */
+ public void setFooterWidget(Widget w) {
+ footerContainer.getStyle().clearDisplay();
+ add(w, footer, footerContainer);
+
+ // Logical attach.
+ footer = w;
+ scheduledLayout();
+ }
+
+ /**
+ * Set the widget in the header portion at the top of the panel.
+ *
+ * @param w the widget to use as the header
+ */
+ public void setHeaderWidget(Widget w) {
+ headerContainer.getStyle().clearDisplay();
+ add(w, header, headerContainer);
+
+ // Logical attach.
+ header = w;
+ scheduledLayout();
+ }
+
+ /**
+ * Add a widget to the panel in the specified container. Note that this
method
+ * does not do the logical attach.
+ *
+ * @param w the widget to add
+ * @param toReplace the widget to replace
+ * @param container the container in which to place the widget
+ */
+ private void add(Widget w, Widget toReplace, Element container) {
+ // Validate.
+ if (w == toReplace) {
+ return;
+ }
+
+ // Detach new child.
+ if (w != null) {
+ w.removeFromParent();
+ }
+
+ // Remove old child.
+ if (toReplace != null) {
+ remove(toReplace);
+ }
+
+ if (w != null) {
+ // Physical attach.
+ container.appendChild(w.getElement());
+
+ adopt(w);
+ }
+ }
+
+ private Element createContainer() {
+ Element container = Document.get().createDivElement().cast();
+ container.getStyle().setPosition(Position.ABSOLUTE);
+ container.getStyle().setDisplay(Display.NONE);
+ container.getStyle().setLeft(0.0, Unit.PX);
+ container.getStyle().setWidth(100.0, Unit.PCT);
+ return container;
+ }
+
+ /**
+ * Update the layout.
+ */
+ private void forceLayout() {
+ // No sense in doing layout if we aren't attached or have no content.
+ if (!isAttached() || content == null) {
+ return;
+ }
+
+ // Resize the content area to fit between the header and footer.
+ int remainingHeight = getElement().getClientHeight();
+ if (header != null) {
+ int height = Math.max(0, headerContainer.getOffsetHeight());
+ remainingHeight -= height;
+ contentContainer.getStyle().setTop(height, Unit.PX);
+ } else {
+ contentContainer.getStyle().setTop(0.0, Unit.PX);
+ }
+ if (footer != null) {
+ remainingHeight -= footerContainer.getOffsetHeight();
+ }
+ contentContainer.getStyle().setHeight(Math.max(0, remainingHeight),
Unit.PX);
+
+ // Provide resize to child.
+ if (content instanceof RequiresResize) {
+ ((RequiresResize) content).onResize();
+ }
+ }
+
+ /**
+ * Schedule layout to adjust the height of the content area.
+ */
+ private void scheduledLayout() {
+ if (isAttached() && !layoutScheduled) {
+ layoutScheduled = true;
+ Scheduler.get().scheduleDeferred(layoutCmd);
+ }
+ }
+}
=======================================
--- /dev/null
+++ /trunk/user/src/com/google/gwt/user/client/ui/ResizeLayoutPanel.java
Thu Jan 20 06:21:05 2011
@@ -0,0 +1,440 @@
+/*
+ * Copyright 2011 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.user.client.ui;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.EventTarget;
+import com.google.gwt.dom.client.Style.Overflow;
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.dom.client.Style.Visibility;
+import com.google.gwt.event.logical.shared.HasResizeHandlers;
+import com.google.gwt.event.logical.shared.ResizeEvent;
+import com.google.gwt.event.logical.shared.ResizeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.layout.client.Layout;
+import com.google.gwt.layout.client.Layout.Layer;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.EventListener;
+import com.google.gwt.user.client.ui.ResizeLayoutPanel.Impl.Delegate;
+
+/**
+ * A simple panel that {@link ProvidesResize} to its one child, but does
not
+ * {@link RequiresResize}. Use this to embed layout panels in any location
+ * within your application.
+ */
+public class ResizeLayoutPanel extends SimplePanel implements
ProvidesResize,
+ HasResizeHandlers {
+
+ /**
+ * Implementation of resize event.
+ */
+ abstract static class Impl {
+ /**
+ * Delegate event handler.
+ */
+ abstract static interface Delegate {
+ /**
+ * Called when the element is resized.
+ */
+ void onResize();
+ }
+
+ boolean isAttached;
+ Element parent;
+ private Delegate delegate;
+
+ /**
+ * Initialize the implementation.
+ *
+ * @param elem the element to listen for resize
+ * @param delegate the {@link Delegate} to inform when resize occurs
+ */
+ public void init(Element elem, Delegate delegate) {
+ this.parent = elem;
+ this.delegate = delegate;
+ }
+
+ /**
+ * Called on attach.
+ */
+ public void onAttach() {
+ isAttached = true;
+ }
+
+ /**
+ * Called on detach.
+ *
+ * @param panel the panel
+ */
+ public void onDetach() {
+ isAttached = false;
+ }
+
+ /**
+ * Handle a resize event.
+ *
+ * @param panel the panel
+ */
+ protected void handleResize() {
+ if (isAttached && delegate != null) {
+ delegate.onResize();
+ }
+ }
+ }
+
+ /**
+ * Implementation of resize event.
+ */
+ static class ImplStandard extends Impl implements EventListener {
+ /**
+ * Chrome does not fire an onresize event if the dimensions are too
small to
+ * render a scrollbar.
+ */
+ private static final String MIN_SIZE = "20px";
+
+ private Element collapsible;
+ private Element collapsibleInner;
+ private Element expandable;
+ private Element expandableInner;
+ private int lastOffsetHeight = -1;
+ private int lastOffsetWidth = -1;
+
+ @Override
+ public void init(Element elem, Delegate delegate) {
+ super.init(elem, delegate);
+
+ /*
+ * Set the minimum dimensions to ensure that scrollbars are rendered
and
+ * fire onscroll events.
+ */
+ elem.getStyle().setProperty("minWidth", MIN_SIZE);
+ elem.getStyle().setProperty("minHeight", MIN_SIZE);
+
+ /*
+ * Detect expansion. In order to detect an increase in the size of
the
+ * widget, we create an absolutely positioned, scrollable div with
+ * height=width=100%. We then add an inner div that has fixed height
and
+ * width equal to 100% (converted to pixels) and set
scrollLeft/scrollTop
+ * to their maximum. When the outer div expands, scrollLeft/scrollTop
+ * automatically becomes a smaller number and trigger an onscroll
event.
+ */
+ expandable = Document.get().createDivElement().cast();
+ expandable.getStyle().setVisibility(Visibility.HIDDEN);
+ expandable.getStyle().setPosition(Position.ABSOLUTE);
+ expandable.getStyle().setHeight(100.0, Unit.PCT);
+ expandable.getStyle().setWidth(100.0, Unit.PCT);
+ expandable.getStyle().setOverflow(Overflow.SCROLL);
+ elem.appendChild(expandable);
+ expandableInner = Document.get().createDivElement().cast();
+ expandable.appendChild(expandableInner);
+ DOM.sinkEvents(expandable, Event.ONSCROLL);
+
+ /*
+ * Detect collapse. In order to detect a decrease in the size of the
+ * widget, we create an absolutely positioned, scrollable div with
+ * height=width=100%. We then add an inner div that has
height=width=200%
+ * and max out the scrollTop/scrollLeft. When the height or width
+ * decreases, the inner div loses 2px for every 1px that the
scrollable
+ * div loses, so the scrollTop/scrollLeft decrease and we get an
onscroll
+ * event.
+ */
+ collapsible = Document.get().createDivElement().cast();
+ collapsible.getStyle().setVisibility(Visibility.HIDDEN);
+ collapsible.getStyle().setPosition(Position.ABSOLUTE);
+ collapsible.getStyle().setHeight(100.0, Unit.PCT);
+ collapsible.getStyle().setWidth(100.0, Unit.PCT);
+ collapsible.getStyle().setOverflow(Overflow.SCROLL);
+ elem.appendChild(collapsible);
+ collapsibleInner = Document.get().createDivElement().cast();
+ collapsibleInner.getStyle().setWidth(200, Unit.PCT);
+ collapsibleInner.getStyle().setHeight(200, Unit.PCT);
+ collapsible.appendChild(collapsibleInner);
+ DOM.sinkEvents(collapsible, Event.ONSCROLL);
+ }
+
+ @Override
+ public void onAttach() {
+ super.onAttach();
+ DOM.setEventListener(expandable, this);
+ DOM.setEventListener(collapsible, this);
+
+ /*
+ * Update the scrollables in a deferred command so the browser
calculates
+ * the offsetHeight/Width correctly.
+ */
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ public void execute() {
+ resetScrollables();
+ }
+ });
+ }
+
+ public void onBrowserEvent(Event event) {
+ if (Event.ONSCROLL == event.getTypeInt()) {
+ EventTarget eventTarget = event.getEventTarget();
+ if (!Element.is(eventTarget)) {
+ return;
+ }
+ Element target = eventTarget.cast();
+ if (target == collapsible || target == expandable) {
+ handleResize();
+ }
+ }
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ DOM.setEventListener(expandable, null);
+ DOM.setEventListener(collapsible, null);
+ lastOffsetHeight = -1;
+ lastOffsetWidth = -1;
+ }
+
+ @Override
+ protected void handleResize() {
+ if (resetScrollables()) {
+ super.handleResize();
+ }
+ }
+
+ /**
+ * Reset the positions of the scrollable elements.
+ *
+ * @return true if the size changed, false if not
+ */
+ private boolean resetScrollables() {
+ /*
+ * Reset expandable element. Scrollbars are not rendered if the div
is too
+ * small, so we need to set the dimensions of the inner div to a
value
+ * greater than the offsetWidth/Height.
+ */
+ int offsetHeight = parent.getOffsetHeight();
+ int offsetWidth = parent.getOffsetWidth();
+ int height = offsetHeight + 100;
+ int width = offsetWidth + 100;
+ expandableInner.getStyle().setHeight(height, Unit.PX);
+ expandableInner.getStyle().setWidth(width, Unit.PX);
+ expandable.setScrollTop(height);
+ expandable.setScrollLeft(width);
+
+ // Reset collapsible element.
+ collapsible.setScrollTop(collapsible.getScrollHeight() + 100);
+ collapsible.setScrollLeft(collapsible.getScrollWidth() + 100);
+
+ if (lastOffsetHeight != offsetHeight || lastOffsetWidth !=
offsetWidth) {
+ lastOffsetHeight = offsetHeight;
+ lastOffsetWidth = offsetWidth;
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Implementation of resize event used by IE.
+ */
+ static class ImplTrident extends Impl {
+
+ @Override
+ public void init(Element elem, Delegate delegate) {
+ super.init(elem, delegate);
+ initResizeEventListener(elem);
+ }
+
+ @Override
+ public void onAttach() {
+ super.onAttach();
+ setResizeEventListener(parent, this);
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ setResizeEventListener(parent, null);
+ }
+
+ /**
+ * Initalize the onresize listener. This method doesn't create a
memory leak
+ * because we don't set a back reference to the Impl class until we
attach
+ * to the DOM.
+ */
+ private native void initResizeEventListener(Element elem) /*-{
+ var theElem = elem;
+ var handleResize = $entry(function() {
+ if (theElem.__resizeImpl) {
+
theelem.__resizeim...@com.google.gwt.user.client.ui.resizelayoutpanel.impl::handleResize()();
+ }
+ });
+ elem.attachEvent('onresize', handleResize);
+ }-*/;
+
+ /**
+ * Set the event listener that handles resize events.
+ */
+ private native void setResizeEventListener(Element elem, Impl
listener) /*-{
+ elem.__resizeImpl = listener;
+ }-*/;
+ }
+
+ /**
+ * Implementation of resize event used by IE6.
+ */
+ static class ImplIE6 extends ImplTrident {
+ @Override
+ public void onAttach() {
+ super.onAttach();
+
+ /*
+ * IE6 doesn't render this panel unless you kick it after its been
+ * attached.
+ */
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ public void execute() {
+ if (isAttached) {
+ parent.getStyle().setProperty("zoom", "1");
+ }
+ }
+ });
+ }
+ }
+
+ private final Impl impl = GWT.create(Impl.class);
+ private Layer layer;
+ private final Layout layout;
+ private final ScheduledCommand resizeCmd = new ScheduledCommand() {
+ public void execute() {
+ resizeCmdScheduled = false;
+ handleResize();
+ }
+ };
+ private boolean resizeCmdScheduled = false;
+
+ public ResizeLayoutPanel() {
+ layout = new Layout(getElement());
+ impl.init(getElement(), new Delegate() {
+ public void onResize() {
+ scheduleResize();
+ }
+ });
+ }
+
+ public HandlerRegistration addResizeHandler(ResizeHandler handler) {
+ return addHandler(handler, ResizeEvent.getType());
+ }
+
+ @Override
+ public boolean remove(Widget w) {
+ // Validate.
+ if (widget != w) {
+ return false;
+ }
+
+ // Orphan.
+ try {
+ orphan(w);
+ } finally {
+ // Physical detach.
+ layout.removeChild(layer);
+ layer = null;
+
+ // Logical detach.
+ widget = null;
+ }
+ return true;
+ }
+
+ @Override
+ public void setWidget(Widget w) {
+ // Validate
+ if (w == widget) {
+ return;
+ }
+
+ // Detach new child.
+ if (w != null) {
+ w.removeFromParent();
+ }
+
+ // Remove old child.
+ if (widget != null) {
+ remove(widget);
+ }
+
+ // Logical attach.
+ widget = w;
+
+ if (w != null) {
+ // Physical attach.
+ layer = layout.attachChild(widget.getElement(), widget);
+ layer.setTopHeight(0.0, Unit.PX, 100.0, Unit.PCT);
+ layer.setLeftWidth(0.0, Unit.PX, 100.0, Unit.PCT);
+
+ adopt(w);
+
+ // Update the layout.
+ layout.layout();
+ scheduleResize();
+ }
+ }
+
+ @Override
+ protected void onAttach() {
+ super.onAttach();
+ impl.onAttach();
+ layout.onAttach();
+ scheduleResize();
+ }
+
+ @Override
+ protected void onDetach() {
+ super.onDetach();
+ impl.onDetach();
+ layout.onDetach();
+ }
+
+ private void handleResize() {
+ if (!isAttached()) {
+ return;
+ }
+
+ // Provide resize to child.
+ if (widget instanceof RequiresResize) {
+ ((RequiresResize) widget).onResize();
+ }
+
+ // Fire resize event.
+ ResizeEvent.fire(this, getOffsetWidth(), getOffsetHeight());
+ }
+
+ /**
+ * Schedule a resize handler. We schedule the event so the DOM has time
to
+ * update the offset sizes, and to avoid duplicate resize events from
both a
+ * height and width resize.
+ */
+ private void scheduleResize() {
+ if (isAttached() && !resizeCmdScheduled) {
+ resizeCmdScheduled = true;
+ Scheduler.get().scheduleDeferred(resizeCmd);
+ }
+ }
+}
=======================================
--- /dev/null
+++ /trunk/user/test/com/google/gwt/user/client/ui/HeaderPanelTest.java Thu
Jan 20 06:21:05 2011
@@ -0,0 +1,249 @@
+/*
+ * Copyright 2011 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.user.client.ui;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.junit.DoNotRunWith;
+import com.google.gwt.junit.Platform;
+import com.google.gwt.user.client.Timer;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Tests for {@link HeaderPanel}.
+ */
+public class HeaderPanelTest extends PanelTestBase<HeaderPanel> {
+
+ public void testAdd() {
+ HeaderPanel panel = createPanel();
+
+ // Add the header first.
+ Label header = new Label("header");
+ panel.add(header);
+ assertEquals(header, panel.getHeaderWidget());
+ assertEquals(null, panel.getContentWidget());
+ assertEquals(null, panel.getFooterWidget());
+
+ // Add the content second.
+ Label content = new Label("content");
+ panel.add(content);
+ assertEquals(header, panel.getHeaderWidget());
+ assertEquals(content, panel.getContentWidget());
+ assertEquals(null, panel.getFooterWidget());
+
+ // Add the footer third.
+ Label footer = new Label("footer");
+ panel.add(footer);
+ assertEquals(header, panel.getHeaderWidget());
+ assertEquals(content, panel.getContentWidget());
+ assertEquals(footer, panel.getFooterWidget());
+
+ // Cannot add a fourth widget.
+ try {
+ panel.add(new Label());
+ fail("Expected IllegalStateException");
+ } catch (IllegalStateException e) {
+ // Expected.
+ }
+ }
+
+ public void testIterator() {
+ final HeaderPanel panel = createPanel();
+
+ // Empty iterator.
+ Iterator<Widget> iter = panel.iterator();
+ assertFalse(iter.hasNext());
+ try {
+ iter.next();
+ fail("Expected NoSuchElementException");
+ } catch (NoSuchElementException e) {
+ // Expected.
+ }
+ try {
+ iter.remove();
+ fail("Expected IllegalStateException");
+ } catch (IllegalStateException e) {
+ // Expected.
+ }
+
+ // Only a content.
+ Label content = new Label("content");
+ panel.setContentWidget(content);
+ iter = panel.iterator();
+ assertTrue(iter.hasNext());
+ assertEquals(content, iter.next());
+ assertFalse(iter.hasNext());
+ try {
+ iter.next();
+ fail("Expected NoSuchElementException");
+ } catch (NoSuchElementException e) {
+ // Expected.
+ }
+
+ // Header, Content, Footer.
+ Label header = new Label("header");
+ Label footer = new Label("footer");
+ panel.setHeaderWidget(header);
+ panel.setFooterWidget(footer);
+ iter = panel.iterator();
+ assertTrue(iter.hasNext());
+ assertEquals(header, iter.next());
+ assertTrue(iter.hasNext());
+ assertEquals(content, iter.next());
+ assertTrue(iter.hasNext());
+ assertEquals(footer, iter.next());
+ assertFalse(iter.hasNext());
+ try {
+ iter.next();
+ fail("Expected NoSuchElementException");
+ } catch (NoSuchElementException e) {
+ // Expected.
+ }
+
+ // Remove Content.
+ iter = panel.iterator();
+ assertEquals(header, iter.next());
+ assertEquals(content, iter.next());
+ iter.remove();
+ assertEquals(header, panel.getHeaderWidget());
+ assertNull(panel.getContentWidget());
+ assertEquals(footer, panel.getFooterWidget());
+ try {
+ iter.remove();
+ fail("Expected IllegalStateException");
+ } catch (IllegalStateException e) {
+ // Expected - cannot remove twice.
+ }
+ assertEquals(footer, iter.next());
+ }
+
+ @DoNotRunWith(Platform.HtmlUnitLayout)
+ public void testResizeFooter() {
+ final HeaderPanel panel = createPanel();
+ panel.setSize("200px", "400px");
+ RootPanel.get().add(panel);
+
+ final Label header = new Label();
+ header.setSize("100%", "50px");
+ panel.setHeaderWidget(header);
+
+ final Label content = new Label();
+ content.setHeight("100%");
+ panel.setContentWidget(content);
+
+ final Label footer = new Label();
+ footer.setSize("100%", "50px");
+ panel.setFooterWidget(footer);
+
+ delayTestFinish(5000);
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ public void execute() {
+ assertEquals(300, content.getOffsetHeight());
+
+ // Resize the footer.
+ footer.setHeight("75px");
+ new Timer() {
+ @Override
+ public void run() {
+ assertEquals(275, content.getOffsetHeight());
+ RootPanel.get().remove(panel);
+ finishTest();
+ }
+ }.schedule(250);;
+ }
+ });
+ }
+
+ @DoNotRunWith(Platform.HtmlUnitLayout)
+ public void testResizeHeader() {
+ final HeaderPanel panel = createPanel();
+ panel.setSize("200px", "400px");
+ RootPanel.get().add(panel);
+
+ final Label header = new Label();
+ header.setSize("100%", "50px");
+ panel.setHeaderWidget(header);
+
+ final Label content = new Label();
+ content.setHeight("100%");
+ panel.setContentWidget(content);
+
+ final Label footer = new Label();
+ footer.setSize("100%", "50px");
+ panel.setFooterWidget(footer);
+
+ delayTestFinish(5000);
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ public void execute() {
+ assertEquals(300, content.getOffsetHeight());
+
+ // Resize the header.
+ header.setHeight("75px");
+ new Timer() {
+ @Override
+ public void run() {
+ assertEquals(275, content.getOffsetHeight());
+ RootPanel.get().remove(panel);
+ finishTest();
+ }
+ }.schedule(250);;
+ }
+ });
+ }
+
+ public void testSetContentWidget() {
+ HeaderPanel panel = createPanel();
+ Label widget = new Label("hello world");
+ panel.setContentWidget(widget);
+ assertEquals(widget, panel.getContentWidget());
+
+ panel.remove(widget);
+ assertNull(panel.getContentWidget());
+ }
+
+ public void testSetFooterWidget() {
+ HeaderPanel panel = createPanel();
+ Label widget = new Label("hello world");
+ panel.setHeaderWidget(widget);
+ assertEquals(widget, panel.getHeaderWidget());
+
+ panel.remove(widget);
+ assertNull(panel.getHeaderWidget());
+ }
+
+ public void testSetHeaderWidget() {
+ HeaderPanel panel = createPanel();
+ Label widget = new Label("hello world");
+ panel.setFooterWidget(widget);
+ assertEquals(widget, panel.getFooterWidget());
+
+ panel.remove(widget);
+ assertNull(panel.getFooterWidget());
+ }
+
+ @Override
+ protected HeaderPanel createPanel() {
+ return new HeaderPanel();
+ }
+
+ @Override
+ protected boolean supportsMultipleWidgets() {
+ // HeaderPanel supports up to 3 widgets, but not an unbounded number.
+ return false;
+ }
+}
=======================================
--- /dev/null
+++
/trunk/user/test/com/google/gwt/user/client/ui/ResizeLayoutPanelTest.java
Thu Jan 20 06:21:05 2011
@@ -0,0 +1,306 @@
+/*
+ * Copyright 2011 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.user.client.ui;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.logical.shared.ResizeEvent;
+import com.google.gwt.event.logical.shared.ResizeHandler;
+import com.google.gwt.junit.DoNotRunWith;
+import com.google.gwt.junit.Platform;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Timer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for {@link ResizeLayoutPanel}.
+ */
+public class ResizeLayoutPanelTest extends
+ SimplePanelTestBase<ResizeLayoutPanel> {
+
+ /**
+ * A custom implementation of {@link ResizeHandler} used for testing.
+ */
+ private static class CustomResizeHandler implements ResizeHandler {
+
+ private boolean resizeFired;
+
+ public void assertResizeFired(boolean expected) {
+ assertEquals(expected, resizeFired);
+ resizeFired = false;
+ }
+
+ public void onResize(ResizeEvent event) {
+ assertFalse(resizeFired);
+ resizeFired = true;
+ }
+ }
+
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.user.User";
+ }
+
+ /**
+ * Test that a resize event is fired on attach.
+ */
+ public void testAttach() {
+ final ResizeLayoutPanel panel = createPanel();
+ panel.setWidget(new Label("hello world"));
+ final CustomResizeHandler handler = new CustomResizeHandler();
+ panel.addResizeHandler(handler);
+ handler.assertResizeFired(false);
+
+ delayTestFinish(10000);
+ RootPanel.get().add(panel);
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ public void execute() {
+ handler.assertResizeFired(true);
+ panel.removeFromParent();
+ finishTest();
+ }
+ });
+ }
+
+ /**
+ * Test that changing the font size triggers a resize event.
+ */
+ @DoNotRunWith(Platform.HtmlUnitLayout)
+ public void testChangeFontSize() {
+ // Create a panel and add a handler.
+ ResizeLayoutPanel panel = createPanel();
+ panel.setWidget(new Label("hello world"));
+ panel.setWidth("10em");
+ panel.setHeight("10em");
+ final CustomResizeHandler handler = new CustomResizeHandler();
+ panel.addResizeHandler(handler);
+ handler.assertResizeFired(false);
+
+ // Create an outer container and attach it.
+ final SimplePanel container = new SimplePanel();
+ container.getElement().getStyle().setFontSize(10, Unit.PT);
+ container.setHeight("10em");
+ container.setWidth("10em");
+ container.setWidget(panel);
+ RootPanel.get().add(container);
+
+ // Wait for the resize event from attaching.
+ delayTestFinish(10000);
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ public void execute() {
+ handler.assertResizeFired(true); // Triggered by attach.
+ handler.assertResizeFired(false);
+
+ // Change the font size.
+ container.getElement().getStyle().setFontSize(12, Unit.PT);
+ new Timer() {
+ @Override
+ public void run() {
+ handler.assertResizeFired(true);
+ container.removeFromParent();
+ finishTest();
+ }
+ }.schedule(250);
+ }
+ });
+ }
+
+ /**
+ * Test that resizing the outer container triggers a resize event.
+ */
+ @DoNotRunWith(Platform.HtmlUnitLayout)
+ public void testEnlargeContainerHeight() {
+ final SimplePanel container = new SimplePanel();
+ container.setPixelSize(100, 100);
+ testResizeContainer(container, new Command() {
+ public void execute() {
+ container.setHeight("101px");
+ }
+ });
+ }
+
+ /**
+ * Test that resizing the outer container triggers a resize event.
+ */
+ @DoNotRunWith(Platform.HtmlUnitLayout)
+ public void testEnlargeContainerWidth() {
+ final SimplePanel container = new SimplePanel();
+ container.setPixelSize(100, 100);
+ testResizeContainer(container, new Command() {
+ public void execute() {
+ container.setWidth("101px");
+ }
+ });
+ }
+
+ /**
+ * Test that resizing the outer container triggers a resize event even
if the
+ * dimensions are too small to render a scrollbar.
+ */
+ @DoNotRunWith(Platform.HtmlUnitLayout)
+ public void testEnlargeSmallContainerHeight() {
+ final SimplePanel container = new SimplePanel();
+ container.setPixelSize(20, 20);
+ testResizeContainer(container, new Command() {
+ public void execute() {
+ container.setHeight("21px");
+ }
+ });
+ }
+
+ /**
+ * Test that resizing the outer container triggers a resize event even
if the
+ * dimensions are too small to render a scrollbar.
+ */
+ @DoNotRunWith(Platform.HtmlUnitLayout)
+ public void testEnlargeSmallContainerWidth() {
+ final SimplePanel container = new SimplePanel();
+ container.setPixelSize(20, 20);
+ testResizeContainer(container, new Command() {
+ public void execute() {
+ container.setWidth("21px");
+ }
+ });
+ }
+
+ public void testProvidesResize() {
+ final List<String> resized = new ArrayList<String>();
+ ResizeLayoutPanel panel = createPanel();
+ panel.setWidget(new LayoutPanel() {
+ @Override
+ public void onResize() {
+ super.onResize();
+ resized.add("resized");
+ }
+ });
+
+ delayTestFinish(10000);
+ RootPanel.get().add(panel);
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ public void execute() {
+ assertEquals(1, resized.size());
+ finishTest();
+ }
+ });
+ }
+
+ /**
+ * Test that resizing the outer container triggers a resize event.
+ */
+ @DoNotRunWith(Platform.HtmlUnitLayout)
+ public void testShrinkContainerHeight() {
+ final SimplePanel container = new SimplePanel();
+ container.setPixelSize(100, 100);
+ testResizeContainer(container, new Command() {
+ public void execute() {
+ container.setHeight("99px");
+ }
+ });
+ }
+
+ /**
+ * Test that resizing the outer container triggers a resize event.
+ */
+ @DoNotRunWith(Platform.HtmlUnitLayout)
+ public void testShrinkContainerWidth() {
+ final SimplePanel container = new SimplePanel();
+ container.setPixelSize(100, 100);
+ testResizeContainer(container, new Command() {
+ public void execute() {
+ container.setWidth("99px");
+ }
+ });
+ }
+
+ /**
+ * Test that resizing the outer container triggers a resize event even
if the
+ * dimensions are too small to render a scrollbar.
+ */
+ @DoNotRunWith(Platform.HtmlUnitLayout)
+ public void testShrinkSmallContainerHeight() {
+ final SimplePanel container = new SimplePanel();
+ container.setPixelSize(21, 21);
+ testResizeContainer(container, new Command() {
+ public void execute() {
+ container.setHeight("20px");
+ }
+ });
+ }
+
+ /**
+ * Test that resizing the outer container triggers a resize event even
if the
+ * dimensions are too small to render a scrollbar.
+ */
+ @DoNotRunWith(Platform.HtmlUnitLayout)
+ public void testShrinkSmallContainerWidth() {
+ final SimplePanel container = new SimplePanel();
+ container.setPixelSize(21, 21);
+ testResizeContainer(container, new Command() {
+ public void execute() {
+ container.setWidth("20px");
+ }
+ });
+ }
+
+ @Override
+ protected ResizeLayoutPanel createPanel() {
+ return new ResizeLayoutPanel();
+ }
+
+ /**
+ * Test that resizing the outer container triggers a resize event.
+ *
+ * @param container the container that will hold the panel
+ * @param resizeCommand the command that resizes the container
+ */
+ private void testResizeContainer(final SimplePanel container,
+ final Command resizeCommand) {
+ // Create a panel and add a handler.
+ ResizeLayoutPanel panel = createPanel();
+ panel.setWidget(new Label("hello world"));
+ panel.setWidth("100%");
+ panel.setHeight("100%");
+ final CustomResizeHandler handler = new CustomResizeHandler();
+ panel.addResizeHandler(handler);
+ handler.assertResizeFired(false);
+
+ // Create an outer container and attach it.
+ container.setWidget(panel);
+ RootPanel.get().add(container);
+
+ // Wait for the resize event from attaching.
+ delayTestFinish(10000);
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ public void execute() {
+ handler.assertResizeFired(true); // Triggered by attach.
+
+ // Change the size of the container.
+ resizeCommand.execute();
+ new Timer() {
+ @Override
+ public void run() {
+ handler.assertResizeFired(true);
+ container.removeFromParent();
+ finishTest();
+ }
+ }.schedule(250);
+ }
+ });
+ }
+}
=======================================
--- /trunk/user/src/com/google/gwt/user/User.gwt.xml Tue Nov 30 06:11:17
2010
+++ /trunk/user/src/com/google/gwt/user/User.gwt.xml Thu Jan 20 06:21:05
2011
@@ -48,6 +48,7 @@
<inherits name="com.google.gwt.user.Tree"/>
<inherits name="com.google.gwt.user.Hyperlink"/>
<inherits name="com.google.gwt.user.FileUpload"/>
+ <inherits name="com.google.gwt.user.ResizeLayoutPanel"/>
<inherits name="com.google.gwt.user.datepicker.DatePicker"/>
<inherits name="com.google.gwt.user.cellview.CellView"/>
<inherits name="com.google.gwt.safehtml.SafeHtml" />
=======================================
--- /trunk/user/test/com/google/gwt/user/UISuite.java Fri Jan 14 05:54:41
2011
+++ /trunk/user/test/com/google/gwt/user/UISuite.java Thu Jan 20 06:21:05
2011
@@ -62,6 +62,7 @@
import com.google.gwt.user.client.ui.GridTest;
import com.google.gwt.user.client.ui.HTMLPanelTest;
import com.google.gwt.user.client.ui.HTMLTest;
+import com.google.gwt.user.client.ui.HeaderPanelTest;
import com.google.gwt.user.client.ui.HiddenTest;
import com.google.gwt.user.client.ui.HistoryTest;
import com.google.gwt.user.client.ui.HorizontalPanelTest;
@@ -82,6 +83,7 @@
import com.google.gwt.user.client.ui.PrefixTreeTest;
import com.google.gwt.user.client.ui.RadioButtonTest;
import com.google.gwt.user.client.ui.ResetButtonTest;
+import com.google.gwt.user.client.ui.ResizeLayoutPanelTest;
import com.google.gwt.user.client.ui.RichTextAreaTest;
import com.google.gwt.user.client.ui.RootPanelTest;
import com.google.gwt.user.client.ui.ScrollPanelTest;
@@ -169,6 +171,7 @@
suite.addTestSuite(FormPanelTest.class);
suite.addTestSuite(GestureEventSinkTest.class);
suite.addTestSuite(GridTest.class);
+ suite.addTestSuite(HeaderPanelTest.class);
suite.addTestSuite(HiddenTest.class);
suite.addTestSuite(HistoryTest.class);
suite.addTestSuite(HistoryDisabledTest.class);
@@ -194,6 +197,7 @@
suite.addTestSuite(PrefixTreeTest.class);
suite.addTestSuite(RadioButtonTest.class);
suite.addTestSuite(ResetButtonTest.class);
+ suite.addTestSuite(ResizeLayoutPanelTest.class);
suite.addTestSuite(RichTextAreaTest.class);
suite.addTestSuite(RootPanelTest.class);
suite.addTestSuite(ScrollPanelTest.class);
--
http://groups.google.com/group/Google-Web-Toolkit-Contributors