| /* |
| * Copyright 2008 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.Element; |
| import com.google.gwt.dom.client.Style.TextAlign; |
| import com.google.gwt.resources.client.ClientBundle; |
| import com.google.gwt.resources.client.ImageResource; |
| import com.google.gwt.safehtml.shared.SafeHtmlBuilder; |
| import com.google.gwt.user.client.DOM; |
| import com.google.gwt.user.client.Timer; |
| |
| /** |
| * A panel that arranges two widgets in a single vertical column and allows the |
| * user to interactively change the proportion of the height dedicated to each |
| * of the two widgets. Widgets contained within a |
| * <code>VerticalSplitterPanel</code> will be automatically decorated with |
| * scrollbars when necessary. |
| * |
| * <p> |
| * This widget will <em>only</em> work in quirks mode. If your application is in |
| * Standards Mode, use {@link SplitLayoutPanel} instead. |
| * </p> |
| * |
| * <p> |
| * <img class='gallery' src='doc-files/VerticalSplitPanel.png'/> |
| * </p> |
| * |
| * <h3>CSS Style Rules</h3> |
| * <ul> |
| * <li>.gwt-VerticalSplitPanel { the panel itself }</li> |
| * <li>.gwt-VerticalSplitPanel vsplitter { the splitter }</li> |
| * </ul> |
| * |
| * @deprecated Use {@link SplitLayoutPanel} instead, but understand that it is |
| * not a drop in replacement for this class. It requires standards |
| * mode, and is most easily used under a {@link RootLayoutPanel} (as |
| * opposed to a {@link RootPanel} |
| * |
| * @see SplitLayoutPanel |
| */ |
| @Deprecated |
| public final class VerticalSplitPanel extends SplitPanel { |
| /** |
| * The default resources used by this widget. |
| */ |
| public interface Resources extends ClientBundle { |
| /** |
| * An image representing the drag thumb. |
| */ |
| @Source("splitPanelThumb.png") |
| ImageResource verticalSplitPanelThumb(); |
| } |
| |
| /** |
| * Provides a base implementation for splitter layout that relies on CSS |
| * positioned layout. |
| */ |
| private static class Impl { |
| private static void expandToFitParentHorizontally(Element elem) { |
| addAbsolutePositoning(elem); |
| elem.getStyle().setProperty("left", "0"); |
| elem.getStyle().setProperty("right", "0"); |
| } |
| |
| protected VerticalSplitPanel panel; |
| |
| public void init(VerticalSplitPanel panel) { |
| this.panel = panel; |
| |
| panel.getElement().getStyle().setProperty("position", "relative"); |
| |
| final Element topElem = panel.getElement(TOP); |
| final Element bottomElem = panel.getElement(BOTTOM); |
| |
| expandToFitParentHorizontally(topElem); |
| expandToFitParentHorizontally(bottomElem); |
| expandToFitParentHorizontally(panel.getSplitElement()); |
| |
| expandToFitParentUsingCssOffsets(panel.container); |
| |
| // Snap the bottom wrapper to the bottom side. |
| bottomElem.getStyle().setProperty("bottom", "0"); |
| } |
| |
| public void onAttach() { |
| } |
| |
| public void onDetach() { |
| } |
| |
| public void onSplitterResize(int px) { |
| setSplitPosition(px); |
| } |
| |
| public void setSplitPosition(int px) { |
| final Element splitElem = panel.getSplitElement(); |
| |
| final int rootElemHeight = getOffsetHeight(panel.container); |
| final int splitElemHeight = getOffsetHeight(splitElem); |
| |
| if (rootElemHeight < splitElemHeight) { |
| return; |
| } |
| |
| int newBottomHeight = rootElemHeight - px - splitElemHeight; |
| if (px < 0) { |
| px = 0; |
| newBottomHeight = rootElemHeight - splitElemHeight; |
| } else if (newBottomHeight < 0) { |
| px = rootElemHeight - splitElemHeight; |
| newBottomHeight = 0; |
| } |
| |
| updateElements(panel.getElement(TOP), splitElem, |
| panel.getElement(BOTTOM), px, px + splitElemHeight, newBottomHeight); |
| } |
| |
| /** |
| * @param topElem |
| * @param splitElem |
| * @param bottomElem |
| * @param topHeight |
| * @param bottomTop |
| * @param bottomHeight |
| */ |
| protected void updateElements(Element topElem, Element splitElem, |
| Element bottomElem, int topHeight, int bottomTop, int bottomHeight) { |
| setHeight(topElem, topHeight + "px"); |
| |
| setTop(splitElem, topHeight + "px"); |
| |
| setTop(bottomElem, bottomTop + "px"); |
| |
| // bottom's height is handled by CSS. |
| } |
| } |
| |
| /** |
| * Provides an implementation for IE6/7 that relies on 100% length in CSS. |
| */ |
| @SuppressWarnings("unused") |
| // will be used by IE6 permutation |
| private static class ImplIE6 extends Impl { |
| |
| private static void expandToFitParentHorizontally(Element elem) { |
| addAbsolutePositoning(elem); |
| setLeft(elem, "0"); |
| setWidth(elem, "100%"); |
| } |
| |
| private boolean isResizeInProgress = false; |
| |
| private int splitPosition; |
| |
| private boolean isTopHidden = false, isBottomHidden = false; |
| |
| @Override |
| public void init(VerticalSplitPanel panel) { |
| this.panel = panel; |
| |
| final Element elem = panel.getElement(); |
| |
| // Prevents inherited text-align settings from interfering with the |
| // panel's layout. |
| elem.getStyle().setTextAlign(TextAlign.LEFT); |
| elem.getStyle().setProperty("position", "relative"); |
| |
| final Element topElem = panel.getElement(TOP); |
| final Element bottomElem = panel.getElement(BOTTOM); |
| |
| expandToFitParentHorizontally(topElem); |
| expandToFitParentHorizontally(bottomElem); |
| expandToFitParentHorizontally(panel.getSplitElement()); |
| |
| expandToFitParentUsingPercentages(panel.container); |
| } |
| |
| @Override |
| public void onAttach() { |
| addResizeListener(panel.container); |
| onResize(); |
| } |
| |
| @Override |
| public void onDetach() { |
| panel.container.setPropertyString("onresize", null); |
| } |
| |
| @Override |
| public void onSplitterResize(int px) { |
| /* |
| * IE6/7 has event priority issues that will prevent the repaints from |
| * happening quickly enough causing the interaction to seem unresponsive. |
| * The following is simply a poor man's mouse event coalescing. |
| */ |
| final int resizeUpdatePeriod = 20; // ms |
| if (!isResizeInProgress) { |
| isResizeInProgress = true; |
| new Timer() { |
| @Override |
| public void run() { |
| setSplitPosition(splitPosition); |
| isResizeInProgress = false; |
| } |
| }.schedule(resizeUpdatePeriod); |
| } |
| splitPosition = px; |
| } |
| |
| @Override |
| protected void updateElements(Element topElem, Element splitElem, |
| Element bottomElem, int topHeight, int bottomTop, int bottomHeight) { |
| /* |
| * IE6/7 has a quirk where a zero height element with non-zero height |
| * children will expand larger than 100%. To prevent this, the width is |
| * explicitly set to zero when height is zero. |
| */ |
| if (topHeight == 0) { |
| setWidth(topElem, "0px"); |
| isTopHidden = true; |
| } else if (isTopHidden) { |
| setWidth(topElem, "100%"); |
| isTopHidden = false; |
| } |
| |
| if (bottomHeight == 0) { |
| setWidth(bottomElem, "0px"); |
| isBottomHidden = true; |
| } else if (isBottomHidden) { |
| setWidth(bottomElem, "100%"); |
| isBottomHidden = false; |
| } |
| |
| super.updateElements(topElem, splitElem, bottomElem, topHeight, |
| bottomTop, bottomHeight); |
| |
| // IE6/7 cannot update properly with CSS alone. |
| setHeight(bottomElem, bottomHeight + "px"); |
| } |
| |
| private native void addResizeListener(Element container) /*-{ |
| var self = this; |
| container.onresize = $entry(function() { |
| self.@com.google.gwt.user.client.ui.VerticalSplitPanel$ImplIE6::onResize()(); |
| }); |
| }-*/; |
| |
| private void onResize() { |
| setSplitPosition(getOffsetHeight(panel.getElement(TOP))); |
| } |
| } |
| |
| /** |
| * Constant makes for readable calls to {@link #getElement(int)} and |
| * {@link #getWidget(int)}. |
| */ |
| private static final int TOP = 0; |
| |
| /** |
| * Constant makes for readable calls to {@link #getElement(int)} and |
| * {@link #getWidget(int)}. |
| */ |
| private static final int BOTTOM = 1; |
| |
| // Captures the height of the top container when drag resizing starts. |
| private int initialTopHeight = 0; |
| |
| // Captures the offset of a user's mouse pointer during drag resizing. |
| private int initialThumbPos = 0; |
| |
| // A style-free element to serve as the root container. |
| private final Element container; |
| |
| private final Impl impl = GWT.create(Impl.class); |
| |
| private String lastSplitPosition; |
| |
| public VerticalSplitPanel() { |
| this(GWT.<Resources> create(Resources.class)); |
| } |
| |
| /** |
| * Creates an empty vertical split panel. |
| * @deprecated replaced by {@link #VerticalSplitPanel(Resources)} |
| */ |
| @Deprecated |
| public VerticalSplitPanel(VerticalSplitPanelImages images) { |
| this(images.verticalSplitPanelThumb()); |
| } |
| |
| public VerticalSplitPanel(Resources resources) { |
| this(AbstractImagePrototype.create(resources.verticalSplitPanelThumb())); |
| } |
| |
| private VerticalSplitPanel(AbstractImagePrototype thumbImage) { |
| super(DOM.createDiv(), DOM.createDiv(), preventBoxStyles(DOM.createDiv()), |
| preventBoxStyles(DOM.createDiv())); |
| |
| container = preventBoxStyles(DOM.createDiv()); |
| |
| buildDOM(thumbImage); |
| |
| setStyleName("gwt-VerticalSplitPanel"); |
| |
| impl.init(this); |
| |
| setSplitPosition("50%"); |
| } |
| |
| /** |
| * Gets the widget in the bottom of the panel. |
| * |
| * @return the widget, <code>null</code> if there is not one |
| */ |
| public Widget getBottomWidget() { |
| return getWidget(BOTTOM); |
| } |
| |
| /** |
| * Gets the widget in the top of the panel. |
| * |
| * @return the widget, <code>null</code> if there is not one |
| */ |
| public Widget getTopWidget() { |
| return getWidget(TOP); |
| } |
| |
| /** |
| * Sets the widget in the bottom of the panel. |
| * |
| * @param w the widget |
| */ |
| public void setBottomWidget(Widget w) { |
| setWidget(BOTTOM, w); |
| } |
| |
| @Override |
| public void setHeight(String height) { |
| super.setHeight(height); |
| } |
| |
| @Override |
| public void setSplitPosition(String pos) { |
| lastSplitPosition = pos; |
| final Element topElem = getElement(TOP); |
| setHeight(topElem, pos); |
| impl.setSplitPosition(getOffsetHeight(topElem)); |
| } |
| |
| /** |
| * Sets the widget in the top of the panel. |
| * |
| * @param w the widget |
| */ |
| public void setTopWidget(Widget w) { |
| setWidget(TOP, w); |
| } |
| |
| /** |
| * <b>Affected Elements:</b> |
| * <ul> |
| * <li>-splitter = the container containing the splitter element.</li> |
| * <li>-top = the container above the splitter.</li> |
| * <li>-bottom = the container below the splitter.</li> |
| * </ul> |
| * |
| * @see UIObject#onEnsureDebugId(String) |
| */ |
| @Override |
| protected void onEnsureDebugId(String baseID) { |
| super.onEnsureDebugId(baseID); |
| ensureDebugId(getElement(TOP), baseID, "top"); |
| ensureDebugId(getElement(BOTTOM), baseID, "bottom"); |
| } |
| |
| @Override |
| protected void onLoad() { |
| impl.onAttach(); |
| |
| /* |
| * Set the position realizing it might not work until after layout runs. |
| * This first call is simply to try to avoid a jitter effect if possible. |
| */ |
| setSplitPosition(lastSplitPosition); |
| Scheduler.get().scheduleDeferred(new ScheduledCommand() { |
| public void execute() { |
| setSplitPosition(lastSplitPosition); |
| } |
| }); |
| } |
| |
| @Override |
| protected void onUnload() { |
| impl.onDetach(); |
| } |
| |
| @Override |
| void onSplitterResize(int x, int y) { |
| impl.onSplitterResize(initialTopHeight + y - initialThumbPos); |
| } |
| |
| @Override |
| void onSplitterResizeStarted(int x, int y) { |
| initialThumbPos = y; |
| initialTopHeight = getOffsetHeight(getElement(TOP)); |
| } |
| |
| private void buildDOM(AbstractImagePrototype thumb) { |
| final Element topDiv = getElement(TOP); |
| final Element bottomDiv = getElement(BOTTOM); |
| final Element splitDiv = getSplitElement(); |
| |
| DOM.appendChild(getElement(), container); |
| |
| DOM.appendChild(container, topDiv); |
| DOM.appendChild(container, splitDiv); |
| DOM.appendChild(container, bottomDiv); |
| |
| /* |
| * The style name is placed on the table rather than splitElem to allow the |
| * splitter to be styled without interfering with layout. |
| */ |
| SafeHtmlBuilder sb = new SafeHtmlBuilder(); |
| sb.appendHtmlConstant("<div class='vsplitter' style='text-align:center;'>"); |
| sb.append(thumb.getSafeHtml()); |
| sb.appendHtmlConstant("</div>"); |
| splitDiv.setInnerSafeHtml(sb.toSafeHtml()); |
| |
| addScrolling(topDiv); |
| addScrolling(bottomDiv); |
| } |
| } |