com.google.gwt.user.client.ui.DockPanel Maven / Gradle / Ivy
/*
* 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.dom.client.Element;
import com.google.gwt.user.client.DOM;
import com.google.gwt.i18n.client.LocaleInfo;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* A panel that lays its child widgets out "docked" at its outer edges, and
* allows its last widget to take up the remaining space in its center.
*
*
* This widget has limitations in standards mode that did not exist in quirks
* mode. The child Widgets contained within a DockPanel cannot be sized using
* percentages. Setting a child widget's height to 100%
will
* NOT cause the child to fill the available height.
*
*
*
* If you need to work around these limitations, use {@link DockLayoutPanel}
* 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 DockLayoutPanel
*/
public class DockPanel extends CellPanel implements HasAlignment {
/**
* DockPanel layout constant, used in
* {@link DockPanel#add(Widget, DockPanel.DockLayoutConstant)}.
*/
public static class DockLayoutConstant {
private DockLayoutConstant() {
}
}
/*
* This class is package-protected for use with DockPanelTest.
*/
static class LayoutData {
public DockLayoutConstant direction;
public String hAlign = ALIGN_DEFAULT.getTextAlignString();
public String height = "";
public Element td;
public String vAlign = ALIGN_TOP.getVerticalAlignString();
public String width = "";
public LayoutData(DockLayoutConstant dir) {
direction = dir;
}
}
private static class TmpRow {
public int center;
public Element tr;
}
/**
* Specifies that a widget be added at the center of the dock.
*/
public static final DockLayoutConstant CENTER = new DockLayoutConstant();
/**
* Specifies that a widget be added at the beginning of the line direction
* for the layout.
*/
public static final DockLayoutConstant LINE_START = new DockLayoutConstant();
/**
* Specifies that a widget be added at the end of the line direction
* for the layout.
*/
public static final DockLayoutConstant LINE_END = new DockLayoutConstant();
/**
* Specifies that a widget be added at the east edge of the dock.
*/
public static final DockLayoutConstant EAST = new DockLayoutConstant();
/**
* Specifies that a widget be added at the north edge of the dock.
*/
public static final DockLayoutConstant NORTH = new DockLayoutConstant();
/**
* Specifies that a widget be added at the south edge of the dock.
*/
public static final DockLayoutConstant SOUTH = new DockLayoutConstant();
/**
* Specifies that a widget be added at the west edge of the dock.
*/
public static final DockLayoutConstant WEST = new DockLayoutConstant();
/**
* Generate a debug ID for the {@link Widget} given the direction and number
* of occurrences of the direction.
*
* @param direction the direction of the widget
* @param count the number of widgets in that direction
*/
private static String generateDebugId(DockLayoutConstant direction, int count) {
if (direction == NORTH) {
return "north" + count;
} else if (direction == SOUTH) {
return "south" + count;
} else if (direction == WEST) {
return "west" + count;
} else if (direction == EAST) {
return "east" + count;
} else if (direction == LINE_START) {
return "linestart" + count;
} else if (direction == LINE_END) {
return "lineend" + count;
} else {
return "center";
}
}
private HorizontalAlignmentConstant horzAlign = ALIGN_DEFAULT;
private VerticalAlignmentConstant vertAlign = ALIGN_TOP;
private Widget center;
/**
* Creates an empty dock panel.
*/
public DockPanel() {
getTable().setPropertyInt("cellSpacing", 0);
getTable().setPropertyInt("cellPadding", 0);
}
/**
* Adds a widget to the specified edge of the dock. If the widget is already a
* child of this panel, this method behaves as though {@link #remove(Widget)}
* had already been called.
*
* @param widget the widget to be added
* @param direction the widget's direction in the dock
*
* @throws IllegalArgumentException when adding to the {@link #CENTER} and
* there is already a different widget there
*/
public void add(Widget widget, DockLayoutConstant direction) {
// Validate
if (direction == CENTER) {
// Early out on the case of reinserting the center at the center.
if (widget == center) {
return;
} else if (center != null) {
// Ensure a second 'center' widget is not being added.
throw new IllegalArgumentException(
"Only one CENTER widget may be added");
}
}
// Detach new child.
widget.removeFromParent();
// Logical attach.
getChildren().add(widget);
if (direction == CENTER) {
center = widget;
}
// Physical attach.
LayoutData layout = new LayoutData(direction);
widget.setLayoutData(layout);
setCellHorizontalAlignment(widget, horzAlign);
setCellVerticalAlignment(widget, vertAlign);
realizeTable();
// Adopt.
adopt(widget);
}
/**
* Overloaded version for IsWidget.
*
* @see #add(Widget,DockLayoutConstant)
*/
public void add(IsWidget widget, DockLayoutConstant direction) {
this.add(widget.asWidget(), direction);
}
public HorizontalAlignmentConstant getHorizontalAlignment() {
return horzAlign;
}
public VerticalAlignmentConstant getVerticalAlignment() {
return vertAlign;
}
/**
* Gets the layout direction of the given child widget.
*
* @param w the widget to be queried
* @return the widget's layout direction, or null
if it is not
* a child of this panel
*/
public DockLayoutConstant getWidgetDirection(Widget w) {
if (w.getParent() != this) {
return null;
}
return ((LayoutData) w.getLayoutData()).direction;
}
@Override
public boolean remove(Widget w) {
boolean removed = super.remove(w);
if (removed) {
// Clear the center widget.
if (w == center) {
center = null;
}
realizeTable();
}
return removed;
}
@Override
public void setCellHeight(Widget w, String height) {
LayoutData data = (LayoutData) w.getLayoutData();
data.height = height;
if (data.td != null) {
data.td.getStyle().setProperty("height", data.height);
}
}
@Override
public void setCellHorizontalAlignment(Widget w,
HorizontalAlignmentConstant align) {
LayoutData data = (LayoutData) w.getLayoutData();
data.hAlign = align.getTextAlignString();
if (data.td != null) {
setCellHorizontalAlignment(data.td, align);
}
}
@Override
public void setCellVerticalAlignment(Widget w, VerticalAlignmentConstant align) {
LayoutData data = (LayoutData) w.getLayoutData();
data.vAlign = align.getVerticalAlignString();
if (data.td != null) {
setCellVerticalAlignment(data.td, align);
}
}
@Override
public void setCellWidth(Widget w, String width) {
LayoutData data = (LayoutData) w.getLayoutData();
data.width = width;
if (data.td != null) {
data.td.getStyle().setProperty("width", data.width);
}
}
/**
* Sets the default horizontal alignment to be used for widgets added to this
* panel. It only applies to widgets added after this property is set.
*
* @see HasHorizontalAlignment#setHorizontalAlignment(HasHorizontalAlignment.HorizontalAlignmentConstant)
*/
public void setHorizontalAlignment(HorizontalAlignmentConstant align) {
horzAlign = align;
}
/**
* Sets the default vertical alignment to be used for widgets added to this
* panel. It only applies to widgets added after this property is set.
*
* @see HasVerticalAlignment#setVerticalAlignment(HasVerticalAlignment.VerticalAlignmentConstant)
*/
public void setVerticalAlignment(VerticalAlignmentConstant align) {
vertAlign = align;
}
/**
* {@link DockPanel} supports adding more than one cell in a direction, so an
* integer will be appended to the end of the debug id. For example, the first
* north cell is labeled "north1", the second is "north2", and the third is
* "north3".
*
* This widget recreates its structure every time a {@link Widget} is added,
* so you must call this method after adding a new {@link Widget} or all debug
* IDs will be lost.
*
*
* Affected Elements:
*
* - -center = the center cell.
* - -north# = the northern cell.
* - -south# = the southern cell.
* - -east# = the eastern cell.
* - -west# = the western cell.
*
*
*
* @see UIObject#onEnsureDebugId(String)
*/
@Override
protected void onEnsureDebugId(String baseID) {
super.onEnsureDebugId(baseID);
Map dirCount = new HashMap();
Iterator it = getChildren().iterator();
while (it.hasNext()) {
Widget child = it.next();
DockLayoutConstant dir = ((LayoutData) child.getLayoutData()).direction;
// Get a debug id
Integer countObj = dirCount.get(dir);
int count = countObj == null ? 1 : countObj.intValue();
String debugID = generateDebugId(dir, count);
ensureDebugId(DOM.getParent(child.getElement()), baseID, debugID);
// Increment the count
dirCount.put(dir, count + 1);
}
}
/**
* (Re)creates the DOM structure of the table representing the DockPanel,
* based on the order and layout of the children.
*/
private void realizeTable() {
Element bodyElem = getBody();
while (DOM.getChildCount(bodyElem) > 0) {
bodyElem.removeChild(DOM.getChild(bodyElem, 0));
}
int rowCount = 1, colCount = 1;
for (Iterator it = getChildren().iterator(); it.hasNext();) {
Widget child = it.next();
DockLayoutConstant dir = ((LayoutData) child.getLayoutData()).direction;
if ((dir == NORTH) || (dir == SOUTH)) {
++rowCount;
} else if ((dir == EAST) || (dir == WEST) || (dir == LINE_START) || (dir == LINE_END)) {
++colCount;
}
}
TmpRow[] rows = new TmpRow[rowCount];
for (int i = 0; i < rowCount; ++i) {
rows[i] = new TmpRow();
rows[i].tr = DOM.createTR();
DOM.appendChild(bodyElem, rows[i].tr);
}
int logicalLeftCol = 0, logicalRightCol = colCount - 1;
int northRow = 0, southRow = rowCount - 1;
Element centerTd = null;
for (Iterator it = getChildren().iterator(); it.hasNext();) {
Widget child = it.next();
LayoutData layout = (LayoutData) child.getLayoutData();
Element td = DOM.createTD();
layout.td = td;
layout.td.setPropertyString("align", layout.hAlign);
layout.td.getStyle().setProperty("verticalAlign", layout.vAlign);
layout.td.setPropertyString("width", layout.width);
layout.td.setPropertyString("height", layout.height);
if (layout.direction == NORTH) {
DOM.insertChild(rows[northRow].tr, td, rows[northRow].center);
DOM.appendChild(td, child.getElement());
td.setPropertyInt("colSpan", logicalRightCol - logicalLeftCol + 1);
++northRow;
} else if (layout.direction == SOUTH) {
DOM.insertChild(rows[southRow].tr, td, rows[southRow].center);
DOM.appendChild(td, child.getElement());
td.setPropertyInt("colSpan", logicalRightCol - logicalLeftCol + 1);
--southRow;
} else if (layout.direction == CENTER) {
// Defer adding the center widget, so that it can be added after all
// the others are complete.
centerTd = td;
} else if (shouldAddToLogicalLeftOfTable(layout.direction)) {
TmpRow row = rows[northRow];
DOM.insertChild(row.tr, td, row.center++);
DOM.appendChild(td, child.getElement());
td.setPropertyInt("rowSpan", southRow - northRow + 1);
++logicalLeftCol;
} else if (shouldAddToLogicalRightOfTable(layout.direction)) {
TmpRow row = rows[northRow];
DOM.insertChild(row.tr, td, row.center);
DOM.appendChild(td, child.getElement());
td.setPropertyInt("rowSpan", southRow - northRow + 1);
--logicalRightCol;
}
}
// If there is a center widget, add it at the end (centerTd is guaranteed
// to be initialized because it will have been set in the CENTER case in
// the above loop).
if (center != null) {
TmpRow row = rows[northRow];
DOM.insertChild(row.tr, centerTd, row.center);
DOM.appendChild(centerTd, center.getElement());
}
}
private boolean shouldAddToLogicalLeftOfTable(DockLayoutConstant widgetDirection) {
assert (widgetDirection == LINE_START || widgetDirection == LINE_END ||
widgetDirection == EAST || widgetDirection == WEST);
// In a bidi-sensitive environment, adding a widget to the logical left
// column (think DOM order) means that it will be displayed at the start
// of the line direction for the current layout. This is because HTML
// tables are bidi-sensitive; the column order switches depending on
// the line direction.
if (widgetDirection == LINE_START) {
return true;
}
if (LocaleInfo.getCurrentLocale().isRTL()) {
// In an RTL layout, the logical left columns will be displayed on the right hand
// side. When the direction for the widget is EAST, adding the widget to the logical
// left columns will have the desired effect of displaying the widget on the 'eastern'
// side of the screen.
return (widgetDirection == EAST);
}
// In an LTR layout, the logical left columns are displayed on the left hand
// side. When the direction for the widget is WEST, adding the widget to the
// logical left columns will have the desired effect of displaying the widget on the
// 'western' side of the screen.
return (widgetDirection == WEST);
}
private boolean shouldAddToLogicalRightOfTable(DockLayoutConstant widgetDirection) {
// See comments for shouldAddToLogicalLeftOfTable for clarification
assert (widgetDirection == LINE_START || widgetDirection == LINE_END ||
widgetDirection == EAST || widgetDirection == WEST);
if (widgetDirection == LINE_END) {
return true;
}
if (LocaleInfo.getCurrentLocale().isRTL()) {
return (widgetDirection == WEST);
}
return (widgetDirection == EAST);
}
}