| /* |
| * 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.user.client.DOM; |
| import com.google.gwt.user.client.Element; |
| import com.google.gwt.user.client.Event; |
| |
| import java.util.Iterator; |
| |
| /** |
| * Abstract base class for {@link HorizontalSplitPanel} and |
| * {@link VerticalSplitPanel}. |
| * |
| * @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} |
| */ |
| @Deprecated |
| abstract class SplitPanel extends Panel { |
| /** |
| * The element that masks the screen so we can catch mouse events over |
| * iframes. |
| */ |
| private static Element glassElem = null; |
| |
| /** |
| * Sets an elements positioning to absolute. |
| * |
| * @param elem the element |
| */ |
| static void addAbsolutePositoning(Element elem) { |
| DOM.setStyleAttribute(elem, "position", "absolute"); |
| } |
| |
| /** |
| * Adds clipping to an element. |
| * |
| * @param elem the element |
| */ |
| static final void addClipping(final Element elem) { |
| DOM.setStyleAttribute(elem, "overflow", "hidden"); |
| } |
| |
| /** |
| * Adds as-needed scrolling to an element. |
| * |
| * @param elem the element |
| */ |
| static final void addScrolling(final Element elem) { |
| DOM.setStyleAttribute(elem, "overflow", "auto"); |
| } |
| |
| /** |
| * Sizes and element to consume the full area of its parent using the CSS |
| * properties left, right, top, and bottom. This method is used for all |
| * browsers except IE6/7. |
| * |
| * @param elem the element |
| */ |
| static final void expandToFitParentUsingCssOffsets(Element elem) { |
| final String zeroSize = "0px"; |
| |
| addAbsolutePositoning(elem); |
| setLeft(elem, zeroSize); |
| setRight(elem, zeroSize); |
| setTop(elem, zeroSize); |
| setBottom(elem, zeroSize); |
| } |
| |
| /** |
| * Sizes an element to consume the full areas of its parent using 100% width |
| * and height. This method is used on IE6/7 where CSS offsets don't work |
| * reliably. |
| * |
| * @param elem the element |
| */ |
| static final void expandToFitParentUsingPercentages(Element elem) { |
| final String zeroSize = "0px"; |
| final String fullSize = "100%"; |
| |
| addAbsolutePositoning(elem); |
| setTop(elem, zeroSize); |
| setLeft(elem, zeroSize); |
| setWidth(elem, fullSize); |
| setHeight(elem, fullSize); |
| } |
| |
| /** |
| * Returns the offsetHeight element property. |
| * |
| * @param elem the element |
| * @return the offsetHeight property |
| */ |
| static final int getOffsetHeight(Element elem) { |
| return DOM.getElementPropertyInt(elem, "offsetHeight"); |
| } |
| |
| /** |
| * Returns the offsetWidth element property. |
| * |
| * @param elem the element |
| * @return the offsetWidth property |
| */ |
| static final int getOffsetWidth(Element elem) { |
| return DOM.getElementPropertyInt(elem, "offsetWidth"); |
| } |
| |
| /** |
| * Adds zero or none CSS values for padding, margin and border to prevent |
| * stylesheet overrides. Returns the element for convenience to support |
| * builder pattern. |
| * |
| * @param elem the element |
| * @return the element |
| */ |
| static final Element preventBoxStyles(final Element elem) { |
| DOM.setIntStyleAttribute(elem, "padding", 0); |
| DOM.setIntStyleAttribute(elem, "margin", 0); |
| DOM.setStyleAttribute(elem, "border", "none"); |
| return elem; |
| } |
| |
| /** |
| * Convenience method to set bottom offset of an element. |
| * |
| * @param elem the element |
| * @param size a CSS length value for bottom |
| */ |
| static void setBottom(Element elem, String size) { |
| DOM.setStyleAttribute(elem, "bottom", size); |
| } |
| |
| /** |
| * Sets the elements css class name. |
| * |
| * @param elem the element |
| * @param className the class name |
| */ |
| static final void setClassname(final Element elem, final String className) { |
| DOM.setElementProperty(elem, "className", className); |
| } |
| |
| /** |
| * Convenience method to set the height of an element. |
| * |
| * @param elem the element |
| * @param height a CSS length value for the height |
| */ |
| static final void setHeight(Element elem, String height) { |
| DOM.setStyleAttribute(elem, "height", height); |
| } |
| |
| /** |
| * Convenience method to set the left offset of an element. |
| * |
| * @param elem the element |
| * @param left a CSS length value for left |
| */ |
| static final void setLeft(Element elem, String left) { |
| DOM.setStyleAttribute(elem, "left", left); |
| } |
| |
| /** |
| * Convenience method to set the right offset of an element. |
| * |
| * @param elem the element |
| * @param right a CSS length value for right |
| */ |
| static final void setRight(Element elem, String right) { |
| DOM.setStyleAttribute(elem, "right", right); |
| } |
| |
| /** |
| * Convenience method to set the top offset of an element. |
| * |
| * @param elem the element |
| * @param top a CSS length value for top |
| */ |
| static final void setTop(Element elem, String top) { |
| DOM.setStyleAttribute(elem, "top", top); |
| } |
| |
| /** |
| * Convenience method to set the width of an element. |
| * |
| * @param elem the element |
| * @param width a CSS length value for the width |
| */ |
| static final void setWidth(Element elem, String width) { |
| DOM.setStyleAttribute(elem, "width", width); |
| } |
| |
| // The enclosed widgets. |
| private final Widget[] widgets = new Widget[2]; |
| |
| // The elements containing the widgets. |
| private final Element[] elements = new Element[2]; |
| |
| // The element that acts as the splitter. |
| private final Element splitElem; |
| |
| // Indicates whether drag resizing is active. |
| private boolean isResizing = false; |
| |
| /** |
| * Initializes the split panel. |
| * |
| * @param mainElem the root element for the split panel |
| * @param splitElem the element that acts as the splitter |
| * @param headElem the element to contain the top or left most widget |
| * @param tailElem the element to contain the bottom or right most widget |
| */ |
| SplitPanel(Element mainElem, Element splitElem, Element headElem, |
| Element tailElem) { |
| setElement(mainElem); |
| this.splitElem = splitElem; |
| elements[0] = headElem; |
| elements[1] = tailElem; |
| sinkEvents(Event.MOUSEEVENTS | Event.ONLOSECAPTURE); |
| |
| if (glassElem == null) { |
| glassElem = DOM.createDiv(); |
| glassElem.getStyle().setProperty("position", "absolute"); |
| glassElem.getStyle().setProperty("top", "0px"); |
| glassElem.getStyle().setProperty("left", "0px"); |
| glassElem.getStyle().setProperty("margin", "0px"); |
| glassElem.getStyle().setProperty("padding", "0px"); |
| glassElem.getStyle().setProperty("border", "0px"); |
| |
| // We need to set the background color or mouse events will go right |
| // through the glassElem. If the SplitPanel contains an iframe, the |
| // iframe will capture the event and the slider will stop moving. |
| glassElem.getStyle().setProperty("background", "white"); |
| glassElem.getStyle().setProperty("opacity", "0.0"); |
| glassElem.getStyle().setProperty("filter", "alpha(opacity=0)"); |
| } |
| } |
| |
| @Override |
| public void add(Widget w) { |
| if (getWidget(0) == null) { |
| setWidget(0, w); |
| } else if (getWidget(1) == null) { |
| setWidget(1, w); |
| } else { |
| throw new IllegalStateException( |
| "A Splitter can only contain two Widgets."); |
| } |
| } |
| |
| /** |
| * Indicates whether the split panel is being resized. |
| * |
| * @return <code>true</code> if the user is dragging the splitter, |
| * <code>false</code> otherwise |
| */ |
| public boolean isResizing() { |
| return isResizing; |
| } |
| |
| public Iterator<Widget> iterator() { |
| return WidgetIterators.createWidgetIterator(this, widgets); |
| } |
| |
| @Override |
| public void onBrowserEvent(Event event) { |
| switch (DOM.eventGetType(event)) { |
| |
| case Event.ONMOUSEDOWN: { |
| Element target = DOM.eventGetTarget(event); |
| if (DOM.isOrHasChild(splitElem, target)) { |
| startResizingFrom(DOM.eventGetClientX(event) - getAbsoluteLeft(), |
| DOM.eventGetClientY(event) - getAbsoluteTop()); |
| DOM.setCapture(getElement()); |
| DOM.eventPreventDefault(event); |
| } |
| break; |
| } |
| |
| case Event.ONMOUSEUP: { |
| if (isResizing()) { |
| // The order of these two lines is important. If we release capture |
| // first, then we might trigger an onLoseCapture event before we set |
| // isResizing to false. |
| stopResizing(); |
| DOM.releaseCapture(getElement()); |
| } |
| break; |
| } |
| |
| case Event.ONMOUSEMOVE: { |
| if (isResizing()) { |
| assert DOM.getCaptureElement() != null; |
| onSplitterResize(DOM.eventGetClientX(event) - getAbsoluteLeft(), |
| DOM.eventGetClientY(event) - getAbsoluteTop()); |
| DOM.eventPreventDefault(event); |
| } |
| break; |
| } |
| |
| // IE automatically releases capture if the user switches windows, so we |
| // need to catch the event and stop resizing. |
| case Event.ONLOSECAPTURE: { |
| if (isResizing()) { |
| stopResizing(); |
| } |
| break; |
| } |
| } |
| super.onBrowserEvent(event); |
| } |
| |
| @Override |
| public boolean remove(Widget widget) { |
| if (widgets[0] == widget) { |
| setWidget(0, null); |
| return true; |
| } else if (widgets[1] == widget) { |
| setWidget(1, null); |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Moves the position of the splitter. |
| * |
| * @param size the new size of the left region in CSS units (e.g. "10px", |
| * "1em") |
| */ |
| public abstract void setSplitPosition(String size); |
| |
| /** |
| * Gets the content element for the given index. |
| * |
| * @param index the index of the element, only 0 and 1 are valid. |
| * @return the element |
| */ |
| protected Element getElement(int index) { |
| return elements[index]; |
| } |
| |
| /** |
| * Gets the element that is acting as the splitter. |
| * |
| * @return the element |
| */ |
| protected Element getSplitElement() { |
| return splitElem; |
| } |
| |
| /** |
| * Gets one of the contained widgets. |
| * |
| * @param index the index of the widget, only 0 and 1 are valid. |
| * @return the widget |
| */ |
| protected Widget getWidget(int index) { |
| return widgets[index]; |
| } |
| |
| /** |
| * <b>Affected Elements:</b> |
| * <ul> |
| * <li>-splitter = the container containing the splitter element.</li> |
| * </ul> |
| * |
| * @see UIObject#onEnsureDebugId(String) |
| */ |
| @Override |
| protected void onEnsureDebugId(String baseID) { |
| super.onEnsureDebugId(baseID); |
| ensureDebugId(splitElem, baseID, "splitter"); |
| } |
| |
| /** |
| * Sets one of the contained widgets. |
| * |
| * @param index the index, only 0 and 1 are valid |
| * @param w the widget |
| */ |
| protected final void setWidget(int index, Widget w) { |
| Widget oldWidget = widgets[index]; |
| |
| // Validate. |
| if (oldWidget == w) { |
| return; |
| } |
| |
| // Detach the new child. |
| if (w != null) { |
| w.removeFromParent(); |
| } |
| |
| // Remove the old child. |
| if (oldWidget != null) { |
| // Orphan old. |
| try { |
| orphan(oldWidget); |
| } finally { |
| // Physical detach old. |
| DOM.removeChild(elements[index], oldWidget.getElement()); |
| widgets[index] = null; |
| } |
| } |
| |
| // Logical attach new. |
| widgets[index] = w; |
| |
| if (w != null) { |
| // Physical attach new. |
| DOM.appendChild(elements[index], w.getElement()); |
| |
| // Adopt new. |
| adopt(w); |
| } |
| } |
| |
| /** |
| * Called on each mouse drag event as the user is dragging the splitter. |
| * |
| * @param x the x coordinate of the mouse relative to the panel's extent |
| * @param y the y coordinate of the mosue relative to the panel's extent |
| */ |
| abstract void onSplitterResize(int x, int y); |
| |
| /** |
| * Called when the user starts dragging the splitter. |
| * |
| * @param x the x coordinate of the mouse relative to the panel's extent |
| * @param y the y coordinate of the mouse relative to the panel's extent |
| */ |
| abstract void onSplitterResizeStarted(int x, int y); |
| |
| private void startResizingFrom(int x, int y) { |
| isResizing = true; |
| onSplitterResizeStarted(x, y); |
| |
| // Resize glassElem to take up the entire scrollable window area |
| int height = RootPanel.getBodyElement().getScrollHeight() - 1; |
| int width = RootPanel.getBodyElement().getScrollWidth() - 1; |
| glassElem.getStyle().setProperty("height", height + "px"); |
| glassElem.getStyle().setProperty("width", width + "px"); |
| RootPanel.getBodyElement().appendChild(glassElem); |
| } |
| |
| private void stopResizing() { |
| isResizing = false; |
| RootPanel.getBodyElement().removeChild(glassElem); |
| } |
| } |