| /* |
| * 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 com.google.gwt.user.client.ui.FiniteWidgetIterator.WidgetProvider; |
| |
| import java.util.Iterator; |
| |
| /** |
| * 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 { |
| |
| /** |
| * The widget provider for this panel. |
| * |
| * <p> |
| * Widgets are returned in the following order: |
| * <ol> |
| * <li>Header widget</li> |
| * <li>Content widget</li> |
| * <li>Footer widget</li> |
| * </ol> |
| */ |
| private class WidgetProviderImpl implements WidgetProvider { |
| |
| public Widget get(int index) { |
| switch (index) { |
| case 0: |
| return header; |
| case 1: |
| return content; |
| case 2: |
| return footer; |
| } |
| throw new ArrayIndexOutOfBoundsException(index); |
| } |
| } |
| |
| 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 new FiniteWidgetIterator(new WidgetProviderImpl(), 3); |
| } |
| |
| @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); |
| } |
| } |
| } |