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:
021: import java.util.Iterator;
022:
023: /**
024: * A panel that lays its child widgets out "docked" at its outer edges, and
025: * allows its last widget to take up the remaining space in its center.
026: *
027: * <p>
028: * <img class='gallery' src='DockPanel.png'/>
029: * </p>
030: */
031: public class DockPanel extends CellPanel implements HasAlignment {
032:
033: /**
034: * DockPanel layout constant, used in
035: * {@link DockPanel#add(Widget, DockPanel.DockLayoutConstant)}.
036: */
037: public static class DockLayoutConstant {
038: private DockLayoutConstant() {
039: }
040: }
041:
042: /*
043: * This class is package-protected for use with DockPanelTest.
044: */
045: static class LayoutData {
046: public DockLayoutConstant direction;
047: public String hAlign = "left";
048: public String height = "";
049: public Element td;
050: public String vAlign = "top";
051: public String width = "";
052:
053: public LayoutData(DockLayoutConstant dir) {
054: direction = dir;
055: }
056: }
057:
058: private static class TmpRow {
059: public int center;
060: public Element tr;
061: }
062:
063: /**
064: * Specifies that a widget be added at the center of the dock.
065: */
066: public static final DockLayoutConstant CENTER = new DockLayoutConstant();
067:
068: /**
069: * Specifies that a widget be added at the east edge of the dock.
070: */
071: public static final DockLayoutConstant EAST = new DockLayoutConstant();
072:
073: /**
074: * Specifies that a widget be added at the north edge of the dock.
075: */
076: public static final DockLayoutConstant NORTH = new DockLayoutConstant();
077:
078: /**
079: * Specifies that a widget be added at the south edge of the dock.
080: */
081: public static final DockLayoutConstant SOUTH = new DockLayoutConstant();
082:
083: /**
084: * Specifies that a widget be added at the west edge of the dock.
085: */
086: public static final DockLayoutConstant WEST = new DockLayoutConstant();
087:
088: private HorizontalAlignmentConstant horzAlign = ALIGN_LEFT;
089: private VerticalAlignmentConstant vertAlign = ALIGN_TOP;
090: private Widget center;
091:
092: /**
093: * Creates an empty dock panel.
094: */
095: public DockPanel() {
096: DOM.setElementPropertyInt(getTable(), "cellSpacing", 0);
097: DOM.setElementPropertyInt(getTable(), "cellPadding", 0);
098: }
099:
100: /**
101: * Adds a widget to the specified edge of the dock. If the widget is already a
102: * child of this panel, this method behaves as though {@link #remove(Widget)}
103: * had already been called.
104: *
105: * @param widget the widget to be added
106: * @param direction the widget's direction in the dock
107: *
108: * @throws IllegalArgumentException when adding to the {@link #CENTER} and
109: * there is already a different widget there
110: */
111: public void add(Widget widget, DockLayoutConstant direction) {
112: // Validate
113: if (direction == CENTER) {
114: // Early out on the case of reinserting the center at the center.
115: if (widget == center) {
116: return;
117: } else if (center != null) {
118: // Ensure a second 'center' widget is not being added.
119: throw new IllegalArgumentException(
120: "Only one CENTER widget may be added");
121: }
122: }
123:
124: // Detach new child.
125: widget.removeFromParent();
126:
127: // Logical attach.
128: getChildren().add(widget);
129: if (direction == CENTER) {
130: center = widget;
131: }
132:
133: // Physical attach.
134: LayoutData layout = new LayoutData(direction);
135: widget.setLayoutData(layout);
136: setCellHorizontalAlignment(widget, horzAlign);
137: setCellVerticalAlignment(widget, vertAlign);
138: realizeTable();
139:
140: // Adopt.
141: adopt(widget);
142: }
143:
144: public HorizontalAlignmentConstant getHorizontalAlignment() {
145: return horzAlign;
146: }
147:
148: public VerticalAlignmentConstant getVerticalAlignment() {
149: return vertAlign;
150: }
151:
152: /**
153: * Gets the layout direction of the given child widget.
154: *
155: * @param w the widget to be queried
156: * @return the widget's layout direction, or <code>null</code> if it is not
157: * a child of this panel
158: */
159: public DockLayoutConstant getWidgetDirection(Widget w) {
160: if (w.getParent() != this ) {
161: return null;
162: }
163: return ((LayoutData) w.getLayoutData()).direction;
164: }
165:
166: @Override
167: public boolean remove(Widget w) {
168: boolean removed = super .remove(w);
169: if (removed) {
170: // Clear the center widget.
171: if (w == center) {
172: center = null;
173: }
174: realizeTable();
175: }
176: return removed;
177: }
178:
179: @Override
180: public void setCellHeight(Widget w, String height) {
181: LayoutData data = (LayoutData) w.getLayoutData();
182: data.height = height;
183: if (data.td != null) {
184: DOM.setStyleAttribute(data.td, "height", data.height);
185: }
186: }
187:
188: @Override
189: public void setCellHorizontalAlignment(Widget w,
190: HorizontalAlignmentConstant align) {
191: LayoutData data = (LayoutData) w.getLayoutData();
192: data.hAlign = align.getTextAlignString();
193: if (data.td != null) {
194: DOM.setElementProperty(data.td, "align", data.hAlign);
195: }
196: }
197:
198: @Override
199: public void setCellVerticalAlignment(Widget w,
200: VerticalAlignmentConstant align) {
201: LayoutData data = (LayoutData) w.getLayoutData();
202: data.vAlign = align.getVerticalAlignString();
203: if (data.td != null) {
204: DOM
205: .setStyleAttribute(data.td, "verticalAlign",
206: data.vAlign);
207: }
208: }
209:
210: @Override
211: public void setCellWidth(Widget w, String width) {
212: LayoutData data = (LayoutData) w.getLayoutData();
213: data.width = width;
214: if (data.td != null) {
215: DOM.setStyleAttribute(data.td, "width", data.width);
216: }
217: }
218:
219: /**
220: * Sets the default horizontal alignment to be used for widgets added to this
221: * panel. It only applies to widgets added after this property is set.
222: *
223: * @see HasHorizontalAlignment#setHorizontalAlignment(HasHorizontalAlignment.HorizontalAlignmentConstant)
224: */
225: public void setHorizontalAlignment(HorizontalAlignmentConstant align) {
226: horzAlign = align;
227: }
228:
229: /**
230: * Sets the default vertical alignment to be used for widgets added to this
231: * panel. It only applies to widgets added after this property is set.
232: *
233: * @see HasVerticalAlignment#setVerticalAlignment(HasVerticalAlignment.VerticalAlignmentConstant)
234: */
235: public void setVerticalAlignment(VerticalAlignmentConstant align) {
236: vertAlign = align;
237: }
238:
239: /**
240: * (Re)creates the DOM structure of the table representing the DockPanel,
241: * based on the order and layout of the children.
242: */
243: private void realizeTable() {
244: Element bodyElem = getBody();
245: while (DOM.getChildCount(bodyElem) > 0) {
246: DOM.removeChild(bodyElem, DOM.getChild(bodyElem, 0));
247: }
248:
249: int rowCount = 1, colCount = 1;
250: for (Iterator<Widget> it = getChildren().iterator(); it
251: .hasNext();) {
252: Widget child = it.next();
253: DockLayoutConstant dir = ((LayoutData) child
254: .getLayoutData()).direction;
255: if ((dir == NORTH) || (dir == SOUTH)) {
256: ++rowCount;
257: } else if ((dir == EAST) || (dir == WEST)) {
258: ++colCount;
259: }
260: }
261:
262: TmpRow[] rows = new TmpRow[rowCount];
263: for (int i = 0; i < rowCount; ++i) {
264: rows[i] = new TmpRow();
265: rows[i].tr = DOM.createTR();
266: DOM.appendChild(bodyElem, rows[i].tr);
267: }
268:
269: int westCol = 0, eastCol = colCount - 1;
270: int northRow = 0, southRow = rowCount - 1;
271: Element centerTd = null;
272:
273: for (Iterator<Widget> it = getChildren().iterator(); it
274: .hasNext();) {
275: Widget child = it.next();
276: LayoutData layout = (LayoutData) child.getLayoutData();
277:
278: Element td = DOM.createTD();
279: layout.td = td;
280: DOM.setElementProperty(layout.td, "align", layout.hAlign);
281: DOM.setStyleAttribute(layout.td, "verticalAlign",
282: layout.vAlign);
283: DOM.setElementProperty(layout.td, "width", layout.width);
284: DOM.setElementProperty(layout.td, "height", layout.height);
285:
286: if (layout.direction == NORTH) {
287: DOM.insertChild(rows[northRow].tr, td,
288: rows[northRow].center);
289: DOM.appendChild(td, child.getElement());
290: DOM.setElementPropertyInt(td, "colSpan", eastCol
291: - westCol + 1);
292: ++northRow;
293: } else if (layout.direction == SOUTH) {
294: DOM.insertChild(rows[southRow].tr, td,
295: rows[southRow].center);
296: DOM.appendChild(td, child.getElement());
297: DOM.setElementPropertyInt(td, "colSpan", eastCol
298: - westCol + 1);
299: --southRow;
300: } else if (layout.direction == WEST) {
301: TmpRow row = rows[northRow];
302: DOM.insertChild(row.tr, td, row.center++);
303: DOM.appendChild(td, child.getElement());
304: DOM.setElementPropertyInt(td, "rowSpan", southRow
305: - northRow + 1);
306: ++westCol;
307: } else if (layout.direction == EAST) {
308: TmpRow row = rows[northRow];
309: DOM.insertChild(row.tr, td, row.center);
310: DOM.appendChild(td, child.getElement());
311: DOM.setElementPropertyInt(td, "rowSpan", southRow
312: - northRow + 1);
313: --eastCol;
314: } else if (layout.direction == CENTER) {
315: // Defer adding the center widget, so that it can be added after all
316: // the others are complete.
317: centerTd = td;
318: }
319: }
320:
321: // If there is a center widget, add it at the end (centerTd is guaranteed
322: // to be initialized because it will have been set in the CENTER case in
323: // the above loop).
324: if (center != null) {
325: TmpRow row = rows[northRow];
326: DOM.insertChild(row.tr, centerTd, row.center);
327: DOM.appendChild(centerTd, center.getElement());
328: }
329: }
330: }
|