blob: b0b84b2d623077b8cc87fbe27d133e809b32a1aa [file] [log] [blame]
/*
* 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);
}
}