001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.user.client.ui;
017:
018: import com.google.gwt.core.client.GWT;
019: import com.google.gwt.user.client.Command;
020: import com.google.gwt.user.client.DOM;
021: import com.google.gwt.user.client.DeferredCommand;
022: import com.google.gwt.user.client.Element;
023: import com.google.gwt.user.client.Timer;
024:
025: /**
026: * A panel that arranges two widgets in a single vertical column and allows the
027: * user to interactively change the proportion of the height dedicated to each
028: * of the two widgets. Widgets contained within a
029: * <code>VerticalSplitterPanel</code> will be automatically decorated with
030: * scrollbars when necessary.
031: *
032: * <p>
033: * <img class='gallery' src='VerticalSplitPanel.png'/>
034: * </p>
035: *
036: * <h3>CSS Style Rules</h3>
037: * <ul class='css'>
038: * <li>.gwt-VerticalSplitPanel { the panel itself }</li>
039: * <li>.gwt-VerticalSplitPanel vsplitter { the splitter }</li>
040: * </ul>
041: */
042: public final class VerticalSplitPanel extends SplitPanel {
043:
044: /**
045: * Provides a base implementation for splitter layout that relies on CSS
046: * positioned layout.
047: */
048: private static class Impl {
049: private static void expandToFitParentHorizontally(Element elem) {
050: addAbsolutePositoning(elem);
051: DOM.setStyleAttribute(elem, "left", "0");
052: DOM.setStyleAttribute(elem, "right", "0");
053: }
054:
055: protected VerticalSplitPanel panel;
056:
057: public void init(VerticalSplitPanel panel) {
058: this .panel = panel;
059:
060: DOM.setStyleAttribute(panel.getElement(), "position",
061: "relative");
062:
063: final Element topElem = panel.getElement(TOP);
064: final Element bottomElem = panel.getElement(BOTTOM);
065:
066: expandToFitParentHorizontally(topElem);
067: expandToFitParentHorizontally(bottomElem);
068: expandToFitParentHorizontally(panel.getSplitElement());
069:
070: expandToFitParentUsingCssOffsets(panel.container);
071:
072: // Snap the bottom wrapper to the bottom side.
073: DOM.setStyleAttribute(bottomElem, "bottom", "0");
074: }
075:
076: public void onAttach() {
077: }
078:
079: public void onDetach() {
080: }
081:
082: public void onSplitterResize(int px) {
083: setSplitPosition(px);
084: }
085:
086: public void setSplitPosition(int px) {
087: final Element splitElem = panel.getSplitElement();
088:
089: final int rootElemHeight = getOffsetHeight(panel.container);
090: final int splitElemHeight = getOffsetHeight(splitElem);
091:
092: if (rootElemHeight < splitElemHeight) {
093: return;
094: }
095:
096: int newBottomHeight = rootElemHeight - px - splitElemHeight;
097: if (px < 0) {
098: px = 0;
099: newBottomHeight = rootElemHeight - splitElemHeight;
100: } else if (newBottomHeight < 0) {
101: px = rootElemHeight - splitElemHeight;
102: newBottomHeight = 0;
103: }
104:
105: updateElements(panel.getElement(TOP), splitElem, panel
106: .getElement(BOTTOM), px, px + splitElemHeight,
107: newBottomHeight);
108: }
109:
110: protected void updateElements(Element topElem,
111: Element splitElem, Element bottomElem, int topHeight,
112: int bottomTop, int bottomHeight) {
113: setHeight(topElem, topHeight + "px");
114:
115: setTop(splitElem, topHeight + "px");
116:
117: setTop(bottomElem, bottomTop + "px");
118:
119: // bottom's height is handled by CSS.
120: }
121: }
122:
123: /**
124: * Provides an implementation for IE6/7 that relies on 100% length in CSS.
125: */
126: private static class ImplIE6 extends Impl {
127:
128: private static void expandToFitParentHorizontally(Element elem) {
129: addAbsolutePositoning(elem);
130: setLeft(elem, "0");
131: setWidth(elem, "100%");
132: }
133:
134: private boolean isResizeInProgress = false;
135:
136: private int splitPosition;
137:
138: private boolean isTopHidden = false, isBottomHidden = false;
139:
140: @Override
141: public void init(VerticalSplitPanel panel) {
142: this .panel = panel;
143:
144: final Element elem = panel.getElement();
145:
146: // Prevents inherited text-align settings from interfering with the
147: // panel's layout.
148: DOM.setStyleAttribute(elem, "textAlign", "left");
149: DOM.setStyleAttribute(elem, "position", "relative");
150:
151: final Element topElem = panel.getElement(TOP);
152: final Element bottomElem = panel.getElement(BOTTOM);
153:
154: expandToFitParentHorizontally(topElem);
155: expandToFitParentHorizontally(bottomElem);
156: expandToFitParentHorizontally(panel.getSplitElement());
157:
158: expandToFitParentUsingPercentages(panel.container);
159: }
160:
161: @Override
162: public void onAttach() {
163: addResizeListener(panel.container);
164: onResize();
165: }
166:
167: @Override
168: public void onDetach() {
169: DOM.setElementProperty(panel.container, "onresize", null);
170: }
171:
172: @Override
173: public void onSplitterResize(int px) {
174: /*
175: * IE6/7 has event priority issues that will prevent the repaints from
176: * happening quickly enough causing the interaction to seem unresponsive.
177: * The following is simply a poor man's mouse event coalescing.
178: */
179: final int resizeUpdatePeriod = 20; // ms
180: if (!isResizeInProgress) {
181: isResizeInProgress = true;
182: new Timer() {
183: @Override
184: public void run() {
185: setSplitPosition(splitPosition);
186: isResizeInProgress = false;
187: }
188: }.schedule(resizeUpdatePeriod);
189: }
190: splitPosition = px;
191: }
192:
193: @Override
194: protected void updateElements(Element topElem,
195: Element splitElem, Element bottomElem, int topHeight,
196: int bottomTop, int bottomHeight) {
197: /*
198: * IE6/7 has a quirk where a zero height element with non-zero height
199: * children will expand larger than 100%. To prevent this, the width is
200: * explicitly set to zero when height is zero.
201: */
202: if (topHeight == 0) {
203: setWidth(topElem, "0px");
204: isTopHidden = true;
205: } else if (isTopHidden) {
206: setWidth(topElem, "100%");
207: isTopHidden = false;
208: }
209:
210: if (bottomHeight == 0) {
211: setWidth(bottomElem, "0px");
212: isBottomHidden = true;
213: } else if (isBottomHidden) {
214: setWidth(bottomElem, "100%");
215: isBottomHidden = false;
216: }
217:
218: super .updateElements(topElem, splitElem, bottomElem,
219: topHeight, bottomTop, bottomHeight);
220:
221: // IE6/7 cannot update properly with CSS alone.
222: setHeight(bottomElem, bottomHeight + "px");
223: }
224:
225: private native void addResizeListener(Element container) /*-{
226: var self = this;
227: container.onresize = function() {
228: self.@com.google.gwt.user.client.ui.VerticalSplitPanel$ImplIE6::onResize()();
229: };
230: }-*/;
231:
232: private void onResize() {
233: setSplitPosition(getOffsetHeight(panel.getElement(TOP)));
234: }
235: }
236:
237: /**
238: * Constant makes for readable calls to {@link #getElement(int)} and
239: * {@link #getWidget(int)}.
240: */
241: private static final int TOP = 0;
242:
243: /**
244: * Constant makes for readable calls to {@link #getElement(int)} and
245: * {@link #getWidget(int)}.
246: */
247: private static final int BOTTOM = 1;
248:
249: // Captures the height of the top container when drag resizing starts.
250: private int initialTopHeight = 0;
251:
252: // Captures the offset of a user's mouse pointer during drag resizing.
253: private int initialThumbPos = 0;
254:
255: // A style-free element to serve as the root container.
256: private final Element container;
257:
258: private final Impl impl = GWT.create(Impl.class);
259:
260: private String lastSplitPosition;
261:
262: public VerticalSplitPanel() {
263: this (
264: GWT
265: .<VerticalSplitPanelImages> create(VerticalSplitPanelImages.class));
266: }
267:
268: /**
269: * Creates an empty vertical split panel.
270: */
271: public VerticalSplitPanel(VerticalSplitPanelImages images) {
272: super (DOM.createDiv(), DOM.createDiv(), preventBoxStyles(DOM
273: .createDiv()), preventBoxStyles(DOM.createDiv()));
274:
275: container = preventBoxStyles(DOM.createDiv());
276:
277: buildDOM(images.verticalSplitPanelThumb());
278:
279: setStyleName("gwt-VerticalSplitPanel");
280:
281: impl.init(this );
282:
283: setSplitPosition("50%");
284: }
285:
286: /**
287: * Gets the widget in the bottom of the panel.
288: *
289: * @return the widget, <code>null</code> if there is not one
290: */
291: public final Widget getBottomWidget() {
292: return getWidget(BOTTOM);
293: }
294:
295: /**
296: * Gets the widget in the top of the panel.
297: *
298: * @return the widget, <code>null</code> if there is not one
299: */
300: public final Widget getTopWidget() {
301: return getWidget(TOP);
302: }
303:
304: /**
305: * Sets the widget in the bottom of the panel.
306: *
307: * @param w the widget
308: */
309: public final void setBottomWidget(Widget w) {
310: setWidget(BOTTOM, w);
311: }
312:
313: @Override
314: public void setHeight(String height) {
315: super .setHeight(height);
316: }
317:
318: @Override
319: public final void setSplitPosition(String pos) {
320: lastSplitPosition = pos;
321: final Element topElem = getElement(TOP);
322: setHeight(topElem, pos);
323: impl.setSplitPosition(getOffsetHeight(topElem));
324: }
325:
326: /**
327: * Sets the widget in the top of the panel.
328: *
329: * @param w the widget
330: */
331: public final void setTopWidget(Widget w) {
332: setWidget(TOP, w);
333: }
334:
335: @Override
336: protected void onLoad() {
337: impl.onAttach();
338:
339: /*
340: * Set the position realizing it might not work until after layout runs.
341: * This first call is simply to try to avoid a jitter effect if possible.
342: */
343: setSplitPosition(lastSplitPosition);
344: DeferredCommand.addCommand(new Command() {
345: public void execute() {
346: setSplitPosition(lastSplitPosition);
347: }
348: });
349: }
350:
351: @Override
352: protected void onUnload() {
353: impl.onDetach();
354: }
355:
356: @Override
357: final void onSplitterResize(int x, int y) {
358: impl.onSplitterResize(initialTopHeight + y - initialThumbPos);
359: }
360:
361: @Override
362: final void onSplitterResizeStarted(int x, int y) {
363: initialThumbPos = y;
364: initialTopHeight = getOffsetHeight(getElement(TOP));
365: }
366:
367: private void buildDOM(AbstractImagePrototype thumb) {
368: final Element topDiv = getElement(TOP);
369: final Element bottomDiv = getElement(BOTTOM);
370: final Element splitDiv = getSplitElement();
371:
372: DOM.appendChild(getElement(), container);
373:
374: DOM.appendChild(container, topDiv);
375: DOM.appendChild(container, splitDiv);
376: DOM.appendChild(container, bottomDiv);
377:
378: /*
379: * The style name is placed on the table rather than splitElem to allow the
380: * splitter to be styled without interfering with layout.
381: */
382: DOM.setInnerHTML(splitDiv, "<div class='vsplitter' "
383: + "style='text-align:center;'>" + thumb.getHTML()
384: + "</div>");
385:
386: addScrolling(topDiv);
387: addScrolling(bottomDiv);
388: }
389: }
|