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.user.client.DOM;
019: import com.google.gwt.user.client.Element;
020: import com.google.gwt.user.client.Event;
021:
022: import java.util.Iterator;
023:
024: /**
025: * Abstract base class for {@link HorizontalSplitPanel} and
026: * {@link VerticalSplitPanel}.
027: */
028: abstract class SplitPanel extends Panel {
029:
030: /**
031: * Sets an elements positioning to absolute.
032: *
033: * @param elem the element
034: */
035: static void addAbsolutePositoning(Element elem) {
036: DOM.setStyleAttribute(elem, "position", "absolute");
037: }
038:
039: /**
040: * Adds clipping to an element.
041: *
042: * @param elem the element
043: */
044: static final void addClipping(final Element elem) {
045: DOM.setStyleAttribute(elem, "overflow", "hidden");
046: }
047:
048: /**
049: * Adds as-needed scrolling to an element.
050: *
051: * @param elem the element
052: */
053: static final void addScrolling(final Element elem) {
054: DOM.setStyleAttribute(elem, "overflow", "auto");
055: }
056:
057: /**
058: * Sizes and element to consume the full area of its parent using the CSS
059: * properties left, right, top, and bottom. This method is used for all
060: * browsers except IE6/7.
061: *
062: * @param elem the element
063: */
064: static final void expandToFitParentUsingCssOffsets(Element elem) {
065: final String zeroSize = "0px";
066:
067: addAbsolutePositoning(elem);
068: setLeft(elem, zeroSize);
069: setRight(elem, zeroSize);
070: setTop(elem, zeroSize);
071: setBottom(elem, zeroSize);
072: }
073:
074: /**
075: * Sizes an element to consume the full areas of its parent using 100% width
076: * and height. This method is used on IE6/7 where CSS offsets don't work
077: * reliably.
078: *
079: * @param elem the element
080: */
081: static final void expandToFitParentUsingPercentages(Element elem) {
082: final String zeroSize = "0px";
083: final String fullSize = "100%";
084:
085: addAbsolutePositoning(elem);
086: setTop(elem, zeroSize);
087: setLeft(elem, zeroSize);
088: setWidth(elem, fullSize);
089: setHeight(elem, fullSize);
090: }
091:
092: /**
093: * Returns the offsetHeight element property.
094: *
095: * @param elem the element
096: * @return the offsetHeight property
097: */
098: static final int getOffsetHeight(Element elem) {
099: return DOM.getElementPropertyInt(elem, "offsetHeight");
100: }
101:
102: /**
103: * Returns the offsetWidth element property.
104: *
105: * @param elem the element
106: * @return the offsetWidth property
107: */
108: static final int getOffsetWidth(Element elem) {
109: return DOM.getElementPropertyInt(elem, "offsetWidth");
110: }
111:
112: /**
113: * Adds zero or none CSS values for padding, margin and border to prevent
114: * stylesheet overrides. Returns the element for convenience to support
115: * builder pattern.
116: *
117: * @param elem the element
118: * @return the element
119: */
120: static final Element preventBoxStyles(final Element elem) {
121: DOM.setIntStyleAttribute(elem, "padding", 0);
122: DOM.setIntStyleAttribute(elem, "margin", 0);
123: DOM.setStyleAttribute(elem, "border", "none");
124: return elem;
125: }
126:
127: /**
128: * Convenience method to set bottom offset of an element.
129: *
130: * @param elem the element
131: * @param size a CSS length value for bottom
132: */
133: static void setBottom(Element elem, String size) {
134: DOM.setStyleAttribute(elem, "bottom", size);
135: }
136:
137: /**
138: * Sets the elements css class name.
139: *
140: * @param elem the element
141: * @param className the class name
142: */
143: static final void setClassname(final Element elem,
144: final String className) {
145: DOM.setElementProperty(elem, "className", className);
146: }
147:
148: /**
149: * Convenience method to set the height of an element.
150: *
151: * @param elem the element
152: * @param height a CSS length value for the height
153: */
154: static final void setHeight(Element elem, String height) {
155: DOM.setStyleAttribute(elem, "height", height);
156: }
157:
158: /**
159: * Convenience method to set the left offset of an element.
160: *
161: * @param elem the element
162: * @param height a CSS length value for left
163: */
164: static final void setLeft(Element elem, String left) {
165: DOM.setStyleAttribute(elem, "left", left);
166: }
167:
168: /**
169: * Convenience method to set the right offset of an element.
170: *
171: * @param elem the element
172: * @param height a CSS length value for right
173: */
174: static final void setRight(Element elem, String right) {
175: DOM.setStyleAttribute(elem, "right", right);
176: }
177:
178: /**
179: * Convenience method to set the top offset of an element.
180: *
181: * @param elem the element
182: * @param height a CSS length value for top
183: */
184: static final void setTop(Element elem, String top) {
185: DOM.setStyleAttribute(elem, "top", top);
186: }
187:
188: /**
189: * Convenience method to set the width of an element.
190: *
191: * @param elem the element
192: * @param height a CSS length value for the width
193: */
194: static final void setWidth(Element elem, String width) {
195: DOM.setStyleAttribute(elem, "width", width);
196: }
197:
198: // The enclosed widgets.
199: private final Widget[] widgets = new Widget[2];
200:
201: // The elements containing the widgets.
202: private final Element[] elements = new Element[2];
203:
204: // The element that acts as the splitter.
205: private final Element splitElem;
206:
207: // Indicates whether drag resizing is active.
208: private boolean isResizing = false;
209:
210: /**
211: * Initializes the split panel.
212: *
213: * @param mainElem the root element for the split panel
214: * @param splitElem the element that acts as the splitter
215: * @param headElem the element to contain the top or left most widget
216: * @param tailElem the element to contain the bottom or right most widget
217: */
218: SplitPanel(Element mainElem, Element splitElem, Element headElem,
219: Element tailElem) {
220: setElement(mainElem);
221: this .splitElem = splitElem;
222: elements[0] = headElem;
223: elements[1] = tailElem;
224: sinkEvents(Event.MOUSEEVENTS);
225: }
226:
227: @Override
228: public void add(Widget w) {
229: if (getWidget(0) == null) {
230: setWidget(0, w);
231: } else if (getWidget(1) == null) {
232: setWidget(1, w);
233: } else {
234: throw new IllegalStateException(
235: "A Splitter can only contain two Widgets.");
236: }
237: }
238:
239: /**
240: * Indicates whether the split panel is being resized.
241: *
242: * @return <code>true</code> if the user is dragging the splitter,
243: * <code>false</code> otherwise
244: */
245: public boolean isResizing() {
246: return isResizing;
247: }
248:
249: public Iterator<Widget> iterator() {
250: return WidgetIterators.createWidgetIterator(this , widgets);
251: }
252:
253: @Override
254: public void onBrowserEvent(Event event) {
255: switch (DOM.eventGetType(event)) {
256:
257: case Event.ONMOUSEDOWN: {
258: Element target = DOM.eventGetTarget(event);
259: if (DOM.isOrHasChild(splitElem, target)) {
260: startResizingFrom(DOM.eventGetClientX(event)
261: - getAbsoluteLeft(), DOM.eventGetClientY(event)
262: - getAbsoluteTop());
263: DOM.setCapture(getElement());
264: DOM.eventPreventDefault(event);
265: }
266: break;
267: }
268:
269: case Event.ONMOUSEUP: {
270: DOM.releaseCapture(getElement());
271: stopResizing();
272: break;
273: }
274:
275: case Event.ONMOUSEMOVE: {
276: if (isResizing()) {
277: assert DOM.getCaptureElement() != null;
278: onSplitterResize(DOM.eventGetClientX(event)
279: - getAbsoluteLeft(), DOM.eventGetClientY(event)
280: - getAbsoluteTop());
281: DOM.eventPreventDefault(event);
282: }
283: break;
284: }
285: }
286: }
287:
288: @Override
289: public boolean remove(Widget widget) {
290: if (widgets[0] == widget) {
291: setWidget(0, null);
292: return true;
293: } else if (widgets[1] == widget) {
294: setWidget(1, null);
295: return true;
296: }
297: return false;
298: }
299:
300: /**
301: * Moves the position of the splitter.
302: *
303: * @param size the new size of the left region in CSS units (e.g. "10px",
304: * "1em")
305: */
306: public abstract void setSplitPosition(String size);
307:
308: /**
309: * Gets the content element for the given index.
310: *
311: * @param index the index of the element, only 0 and 1 are valid.
312: * @return the element
313: */
314: protected Element getElement(int index) {
315: return elements[index];
316: }
317:
318: /**
319: * Gets the element that is acting as the splitter.
320: *
321: * @return the element
322: */
323: protected Element getSplitElement() {
324: return splitElem;
325: }
326:
327: /**
328: * Gets one of the contained widgets.
329: *
330: * @param index the index of the widget, only 0 and 1 are valid.
331: * @return the widget
332: */
333: protected Widget getWidget(int index) {
334: return widgets[index];
335: }
336:
337: /**
338: * Sets one of the contained widgets.
339: *
340: * @param index the index, only 0 and 1 are valid
341: * @param w the widget
342: */
343: protected final void setWidget(int index, Widget w) {
344: Widget oldWidget = widgets[index];
345:
346: // Validate.
347: if (oldWidget == w) {
348: return;
349: }
350:
351: // Detach the new child.
352: if (w != null) {
353: w.removeFromParent();
354: }
355:
356: // Remove the old child.
357: if (oldWidget != null) {
358: // Orphan old.
359: orphan(oldWidget);
360: // Physical detach old.
361: DOM.removeChild(elements[index], oldWidget.getElement());
362: }
363:
364: // Logical detach old / attach new.
365: widgets[index] = w;
366:
367: if (w != null) {
368: // Physical attach new.
369: DOM.appendChild(elements[index], w.getElement());
370:
371: // Adopt new.
372: adopt(w);
373: }
374: }
375:
376: /**
377: * Called on each mouse drag event as the user is dragging the splitter.
378: *
379: * @param x the x coordinate of the mouse relative to the panel's extent
380: * @param y the y coordinate of the mosue relative to the panel's extent
381: */
382: abstract void onSplitterResize(int x, int y);
383:
384: /**
385: * Called when the user starts dragging the splitter.
386: *
387: * @param x the x coordinate of the mouse relative to the panel's extent
388: * @param y the y coordinate of the mouse relative to the panel's extent
389: */
390: abstract void onSplitterResizeStarted(int x, int y);
391:
392: private void startResizingFrom(int x, int y) {
393: isResizing = true;
394: onSplitterResizeStarted(x, y);
395: }
396:
397: private void stopResizing() {
398: isResizing = false;
399: }
400: }
|