| /* |
| * 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.i18n.client.LocaleInfo; |
| 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; |
| |
| /** |
| * A panel that arranges two widgets in a single horizontal row and allows the |
| * user to interactively change the proportion of the width dedicated to each of |
| * the two widgets. Widgets contained within a <code>HorizontalSplitPanel</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/HorizontalSplitPanel.png'/> |
| * </p> |
| * |
| * <h3>CSS Style Rules</h3> |
| * <ul> |
| * <li>.gwt-HorizontalSplitPanel { the panel itself }</li> |
| * <li>.gwt-HorizontalSplitPanel hsplitter { 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 HorizontalSplitPanel extends SplitPanel { |
| /** |
| * The default resources used by this widget. |
| */ |
| public interface Resources extends ClientBundle { |
| /** |
| * An image representing the drag thumb. |
| */ |
| @Source("splitPanelThumb.png") |
| ImageResource horizontalSplitPanelThumb(); |
| } |
| |
| /** |
| * The standard implementation for horizontal split panels. |
| */ |
| private static class Impl { |
| private static void expandToFitParentHorizontally(Element elem) { |
| addAbsolutePositoning(elem); |
| final String zeroSize = "0px"; |
| setTop(elem, zeroSize); |
| setBottom(elem, zeroSize); |
| } |
| |
| protected HorizontalSplitPanel panel; |
| |
| public void init(HorizontalSplitPanel panel) { |
| this.panel = panel; |
| |
| panel.getElement().getStyle().setProperty("position", "relative"); |
| |
| expandToFitParentHorizontally(panel.getElement(LEFT)); |
| expandToFitParentHorizontally(panel.getElement(RIGHT)); |
| expandToFitParentHorizontally(panel.getSplitElement()); |
| |
| expandToFitParentUsingCssOffsets(panel.container); |
| |
| // Right now, both panes are stacked on top of each other |
| // on either the left side or the right side of the containing |
| // panel. This happens because both panes have position:absolute |
| // and no left/top values. The panes will be on the left side |
| // if the directionality is LTR, and on the right side if the |
| // directionality is RTL. In the LTR case, we need to snap the |
| // right pane to the right of the container, and in the RTL case, |
| // we need to snap the left pane to the left of the container. |
| if (LocaleInfo.getCurrentLocale().isRTL()) { |
| setLeft(panel.getElement(LEFT), "0px"); |
| } else { |
| setRight(panel.getElement(RIGHT), "0px"); |
| } |
| } |
| |
| public void onAttach() { |
| } |
| |
| public void onDetach() { |
| } |
| |
| public void onSplitResize(int px) { |
| setSplitPositionUsingPixels(px); |
| } |
| |
| public void setSplitPosition(String pos) { |
| final Element leftElem = panel.getElement(LEFT); |
| setWidth(leftElem, pos); |
| setSplitPositionUsingPixels(getOffsetWidth(leftElem)); |
| } |
| |
| /** |
| * Set the splitter's position in units of pixels. |
| * |
| * px represents the splitter's position as a distance of px pixels from the |
| * left edge of the container. This is true even in a bidi environment. |
| * Callers of this method must be aware of this constraint. |
| */ |
| public void setSplitPositionUsingPixels(int px) { |
| final Element splitElem = panel.getSplitElement(); |
| |
| final int rootElemWidth = getOffsetWidth(panel.container); |
| final int splitElemWidth = getOffsetWidth(splitElem); |
| |
| // This represents an invalid state where layout is incomplete. This |
| // typically happens before DOM attachment, but I leave it here as a |
| // precaution because negative width/height style attributes produce |
| // errors on IE. |
| if (rootElemWidth < splitElemWidth) { |
| return; |
| } |
| |
| // Compute the new right side width. |
| int newRightWidth = rootElemWidth - px - splitElemWidth; |
| |
| // Constrain the dragging to the physical size of the panel. |
| if (px < 0) { |
| px = 0; |
| newRightWidth = rootElemWidth - splitElemWidth; |
| } else if (newRightWidth < 0) { |
| px = rootElemWidth - splitElemWidth; |
| newRightWidth = 0; |
| } |
| |
| final Element rightElem = panel.getElement(RIGHT); |
| |
| // Set the width of the left side. |
| setWidth(panel.getElement(LEFT), px + "px"); |
| |
| // Move the splitter to the right edge of the left element. |
| setLeft(splitElem, px + "px"); |
| |
| // Move the right element to the right of the splitter. |
| setLeft(rightElem, (px + splitElemWidth) + "px"); |
| |
| updateRightWidth(rightElem, newRightWidth); |
| } |
| |
| /** |
| * Implemented by subclasses. |
| * |
| * @param rightElem |
| * @param newRightWidth |
| */ |
| public void updateRightWidth(Element rightElem, int newRightWidth) { |
| // No need to update the width of the right side; this will be |
| // recomputed automatically by CSS. This is helpful, as we do not |
| // have to worry about watching for resize events and adjusting the |
| // right-side width. |
| } |
| } |
| |
| /** |
| * The Safari implementation which owes its existence entirely to a single |
| * WebKit bug: http://bugs.webkit.org/show_bug.cgi?id=9137. |
| */ |
| @SuppressWarnings("unused") |
| // will be used by Safari permutation |
| private static class ImplSafari extends Impl { |
| @Override |
| public void init(HorizontalSplitPanel panel) { |
| this.panel = panel; |
| final String fullSize = "100%"; |
| super.init(panel); |
| setHeight(panel.container, fullSize); |
| setHeight(panel.getElement(LEFT), fullSize); |
| setHeight(panel.getElement(RIGHT), fullSize); |
| setHeight(panel.getSplitElement(), fullSize); |
| } |
| } |
| |
| /** |
| * Constant makes for readable calls to {@link #getElement(int)} and |
| * {@link #getWidget(int)}. |
| */ |
| private static final int LEFT = 0; |
| |
| /** |
| * Constant makes for readable calls to {@link #getElement(int)} and |
| * {@link #getWidget(int)}. |
| */ |
| private static final int RIGHT = 1; |
| |
| // A style-free element to serve as the root container. |
| private final Element container; |
| |
| private final Impl impl = GWT.create(Impl.class); |
| |
| /** |
| * If the split position is set while the split panel is not attached, save it |
| * here to be applied when the panel is attached to the document. |
| */ |
| private String lastSplitPosition = "50%"; |
| |
| private int initialThumbPos; |
| |
| private int initialLeftWidth; |
| |
| public HorizontalSplitPanel() { |
| this(GWT.<Resources> create(Resources.class)); |
| } |
| |
| /** |
| * Creates an empty horizontal split panel. |
| * |
| * @param images ImageBundle containing an image for the splitter's drag thumb |
| * @deprecated replaced by {@link #HorizontalSplitPanel(Resources)} |
| */ |
| @Deprecated |
| public HorizontalSplitPanel(HorizontalSplitPanelImages images) { |
| this(images.horizontalSplitPanelThumb()); |
| } |
| |
| /** |
| * Creates an empty horizontal split panel. |
| * |
| * @param resources ClientBundle containing an image for the splitter's drag |
| * thumb |
| */ |
| public HorizontalSplitPanel(Resources resources) { |
| this(AbstractImagePrototype.create(resources.horizontalSplitPanelThumb())); |
| } |
| |
| private HorizontalSplitPanel(AbstractImagePrototype thumbImage) { |
| super(DOM.createDiv(), DOM.createDiv(), preventBoxStyles(DOM.createDiv()), |
| preventBoxStyles(DOM.createDiv())); |
| |
| container = preventBoxStyles(DOM.createDiv()); |
| |
| buildDOM(thumbImage); |
| |
| setStyleName("gwt-HorizontalSplitPanel"); |
| |
| impl.init(this); |
| |
| // By default, the panel will fill its parent vertically and horizontally. |
| // The horizontal case is covered by the fact that the top level div is |
| // block display. |
| setHeight("100%"); |
| } |
| |
| /** |
| * Adds a widget to a pane in the HorizontalSplitPanel. The method will first |
| * attempt to add the widget to the left pane. If a widget is already in that |
| * position, it will attempt to add the widget to the right pane. If a widget |
| * is already in that position, an exception will be thrown, as a |
| * HorizontalSplitPanel can contain at most two widgets. |
| * |
| * Note that this method is bidi-sensitive. In an RTL environment, this method |
| * will first attempt to add the widget to the right pane, and if a widget is |
| * already in that position, it will attempt to add the widget to the left |
| * pane. |
| * |
| * @param w the widget to be added |
| * @throws IllegalStateException |
| */ |
| @Override |
| public void add(Widget w) { |
| if (getStartOfLineWidget() == null) { |
| setStartOfLineWidget(w); |
| } else if (getEndOfLineWidget() == null) { |
| setEndOfLineWidget(w); |
| } else { |
| throw new IllegalStateException( |
| "A Splitter can only contain two Widgets."); |
| } |
| } |
| |
| /** |
| * Gets the widget in the pane that is at the end of the line direction for |
| * the layout. That is, in an RTL layout, gets the widget in the left pane, |
| * and in an LTR layout, gets the widget in the right pane. |
| * |
| * @return the widget, <code>null</code> if there is not one. |
| */ |
| public Widget getEndOfLineWidget() { |
| return getWidget(getEndOfLinePos()); |
| } |
| |
| /** |
| * Gets the widget in the left side of the panel. |
| * |
| * @return the widget, <code>null</code> if there is not one. |
| */ |
| public Widget getLeftWidget() { |
| return getWidget(LEFT); |
| } |
| |
| /** |
| * Gets the widget in the right side of the panel. |
| * |
| * @return the widget, <code>null</code> if there is not one. |
| */ |
| public Widget getRightWidget() { |
| return getWidget(RIGHT); |
| } |
| |
| /** |
| * Gets the widget in the pane that is at the start of the line direction for |
| * the layout. That is, in an RTL environment, gets the widget in the right |
| * pane, and in an LTR environment, gets the widget in the left pane. |
| * |
| * @return the widget, <code>null</code> if there is not one. |
| */ |
| public Widget getStartOfLineWidget() { |
| return getWidget(getStartOfLinePos()); |
| } |
| |
| /** |
| * Sets the widget in the pane that is at the end of the line direction for |
| * the layout. That is, in an RTL layout, sets the widget in the left pane, |
| * and in and RTL layout, sets the widget in the right pane. |
| * |
| * @param w the widget |
| */ |
| public void setEndOfLineWidget(Widget w) { |
| setWidget(getEndOfLinePos(), w); |
| } |
| |
| /** |
| * Sets the widget in the left side of the panel. |
| * |
| * @param w the widget |
| */ |
| public void setLeftWidget(Widget w) { |
| setWidget(LEFT, w); |
| } |
| |
| /** |
| * Sets the widget in the right side of the panel. |
| * |
| * @param w the widget |
| */ |
| public void setRightWidget(Widget w) { |
| setWidget(RIGHT, w); |
| } |
| |
| /** |
| * Moves the position of the splitter. |
| * |
| * This method is not bidi-sensitive. The size specified is always the size of |
| * the left region, regardless of directionality. |
| * |
| * @param pos the new size of the left region in CSS units (e.g. "10px", |
| * "1em") |
| */ |
| @Override |
| public void setSplitPosition(String pos) { |
| lastSplitPosition = pos; |
| impl.setSplitPosition(pos); |
| } |
| |
| /** |
| * Sets the widget in the pane that is at the start of the line direction for |
| * the layout. That is, in an RTL layout, sets the widget in the right pane, |
| * and in and RTL layout, sets the widget in the left pane. |
| * |
| * @param w the widget |
| */ |
| public void setStartOfLineWidget(Widget w) { |
| setWidget(getStartOfLinePos(), w); |
| } |
| |
| /** |
| * <b>Affected Elements:</b> |
| * <ul> |
| * <li>-splitter = the container containing the splitter element.</li> |
| * <li>-right = the container on the right side of the splitter.</li> |
| * <li>-left = the container on the left side of the splitter.</li> |
| * </ul> |
| * |
| * @see UIObject#onEnsureDebugId(String) |
| */ |
| @Override |
| protected void onEnsureDebugId(String baseID) { |
| super.onEnsureDebugId(baseID); |
| ensureDebugId(getElement(LEFT), baseID, "left"); |
| ensureDebugId(getElement(RIGHT), baseID, "right"); |
| } |
| |
| @Override |
| protected void onLoad() { |
| impl.onAttach(); |
| |
| /* |
| * If the split position has been changed while detached, apply the change. |
| * Set the position realizing that 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.onSplitResize(initialLeftWidth + x - initialThumbPos); |
| } |
| |
| @Override |
| void onSplitterResizeStarted(int x, int y) { |
| initialThumbPos = x; |
| initialLeftWidth = getOffsetWidth(getElement(LEFT)); |
| } |
| |
| private void buildDOM(AbstractImagePrototype thumbImage) { |
| final Element leftDiv = getElement(LEFT); |
| final Element rightDiv = getElement(RIGHT); |
| final Element splitDiv = getSplitElement(); |
| |
| DOM.appendChild(getElement(), container); |
| |
| DOM.appendChild(container, leftDiv); |
| DOM.appendChild(container, splitDiv); |
| DOM.appendChild(container, rightDiv); |
| |
| /* |
| * Sadly, this is the only way I've found to get vertical centering in this |
| * case. The usually CSS hacks (display: table-cell, vertical-align: middle) |
| * don't work in an absolute positioned DIV. |
| */ |
| SafeHtmlBuilder sb = new SafeHtmlBuilder(); |
| sb.appendHtmlConstant("<table class='hsplitter' height='100%' cellpadding='0' " |
| + "cellspacing='0'><tr><td align='center' valign='middle'>"); |
| sb.append(thumbImage.getSafeHtml()); |
| splitDiv.setInnerSafeHtml(sb.toSafeHtml()); |
| |
| addScrolling(leftDiv); |
| addScrolling(rightDiv); |
| } |
| |
| private int getEndOfLinePos() { |
| return (LocaleInfo.getCurrentLocale().isRTL() ? LEFT : RIGHT); |
| } |
| |
| private int getStartOfLinePos() { |
| return (LocaleInfo.getCurrentLocale().isRTL() ? RIGHT : LEFT); |
| } |
| } |