/*
* Ext GWT - Ext for GWT
* Copyright(c) 2007-2009, Ext JS, LLC.
* licensing@extjs.com
*
* http://extjs.com/license
*/
package com.extjs.gxt.ui.client.widget.layout;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.extjs.gxt.ui.client.Style.LayoutRegion;
import com.extjs.gxt.ui.client.core.El;
import com.extjs.gxt.ui.client.event.BaseEvent;
import com.extjs.gxt.ui.client.event.ComponentEvent;
import com.extjs.gxt.ui.client.event.EventType;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.event.SplitBarEvent;
import com.extjs.gxt.ui.client.util.Margins;
import com.extjs.gxt.ui.client.util.Rectangle;
import com.extjs.gxt.ui.client.widget.BoxComponent;
import com.extjs.gxt.ui.client.widget.CollapsePanel;
import com.extjs.gxt.ui.client.widget.Component;
import com.extjs.gxt.ui.client.widget.ComponentHelper;
import com.extjs.gxt.ui.client.widget.Container;
import com.extjs.gxt.ui.client.widget.ContentPanel;
import com.extjs.gxt.ui.client.widget.Layout;
import com.extjs.gxt.ui.client.widget.LayoutContainer;
import com.extjs.gxt.ui.client.widget.SplitBar;
import com.extjs.gxt.ui.client.widget.button.ToolButton;
/**
* This is a multi-pane, application-oriented UI layout style that supports
* multiple regions, automatic split bars between regions and built-in expanding
* and collapsing of regions.
*
* <p />
* The children of the container using a border layout positions it's children
* absolutely. Because of this, a specific height and width must be set on any
* container using a border layout. The size can be set directly, or by a parent
* layout.
*
* <p />
* Rather then act on the child components directly, expanding, collapsing,
* hiding, and showing use the methods provided by border layout (
* {@link #expand}, {@link #collapse}, {@link #hide}, and {@link #collapse}.
*
* <p />
* Be default, this layout adds a CSS style to the parent container (defaults to
* 'x-border-layout-ct') which gives the container a background color.
*
* <p />
* Code snippet:
*
* <pre>
* public class BorderLayoutExample extends LayoutContainer {
*
* public BorderLayoutExample() {
* setLayout(new BorderLayout());
*
* ContentPanel west = new ContentPanel();
* ContentPanel center = new ContentPanel();
*
* BorderLayoutData westData = new BorderLayoutData(LayoutRegion.WEST, 200);
* westData.setSplit(true);
* westData.setCollapsible(true);
* westData.setMargins(new Margins(5));
*
* BorderLayoutData centerData = new BorderLayoutData(LayoutRegion.CENTER);
* centerData.setMargins(new Margins(5, 0, 5, 0));
*
* add(west, westData);
* add(center, centerData);
* }
* }
* </pre>
*
* </p>
*/
public class BorderLayout extends Layout {
protected Map<LayoutRegion, SplitBar> splitBars;
private boolean enableState = true;
private Listener<ComponentEvent> collapseListener;
private BoxComponent north, south;
private BoxComponent west, east, center;
private boolean rendered;
private LayoutContainer layoutContainer;
private String containerStyle = "x-border-layout-ct";
private Rectangle lastCenter;
public BorderLayout() {
monitorResize = true;
splitBars = new HashMap<LayoutRegion, SplitBar>();
collapseListener = new Listener<ComponentEvent>() {
public void handleEvent(ComponentEvent e) {
EventType type = e.getType();
if (type == Events.BeforeCollapse) {
e.setCancelled(true);
onCollapse(e.<ContentPanel> getComponent());
} else if (type == Events.BeforeExpand) {
e.setCancelled(true);
onExpand(e.<ContentPanel> getComponent());
}
}
};
}
/**
* Collapses the panel in the given region.
*
* @param region the region to be collapsed
*/
public void collapse(LayoutRegion region) {
Component c = getRegionWidget(region);
if (c != null && c instanceof ContentPanel && !(c instanceof CollapsePanel)) {
onCollapse((ContentPanel) c);
}
}
/**
* Expands the panel in the given region.
*
* @param region the region to expand
*/
public void expand(LayoutRegion region) {
Component c = getRegionWidget(region);
if (c != null && c instanceof CollapsePanel) {
ContentPanel cp = (ContentPanel) c.getData("panel");
onExpand(cp);
}
}
/**
* Returns true if state is enabled.
*
* @return the enabled state flag
*/
public boolean getEnableState() {
return enableState;
}
public void hide(LayoutRegion region) {
Component c = getRegionWidget(region);
if (c != null) {
BorderLayoutData data = (BorderLayoutData) getLayoutData(c);
data.setHidden(true);
layout();
}
}
/**
* Sets the CSS style name to be added to the layout's container (defaults to
* 'x-border-layout-ct').
*
* @param style the style name
*/
public void setContainerStyle(String style) {
this.containerStyle = style;
}
/**
* True to enabled state (defaults to true). When true, expand / collapse and
* size state is persisted across user sessions.
*
* @param enableState true to enable state
*/
public void setEnableState(boolean enableState) {
this.enableState = enableState;
}
public void show(LayoutRegion region) {
Component c = getRegionWidget(region);
if (c != null) {
BorderLayoutData data = (BorderLayoutData) getLayoutData(c);
data.setHidden(false);
layout();
}
}
protected SplitBar createSplitBar(LayoutRegion region, BoxComponent component) {
return new SplitBar(region, component);
}
@Override
protected void onLayout(Container<?> container, El target) {
super.onLayout(container, target);
layoutContainer = (LayoutContainer) container;
if (!rendered) {
target.makePositionable();
target.addStyleName(containerStyle);
List<Component> list = new ArrayList<Component>(container.getItems());
for (int i = 0; i < list.size(); i++) {
Component c = list.get(i);
if (!c.isRendered()) {
c.addStyleName("x-border-panel");
c.render(target.dom, i);
}
if (enableState) {
BorderLayoutData data = (BorderLayoutData) getLayoutData(c);
Map<String, Object> st = c.getState();
if (st.containsKey("collapsed") && (c instanceof ContentPanel)) {
switchPanels((ContentPanel) c);
} else if (st.containsKey("size") && (c instanceof BoxComponent)
&& (!(c instanceof CollapsePanel))) {
data.setSize((Float) st.get("size"));
}
}
}
rendered = true;
}
Rectangle rect = target.getBounds();
int w = rect.width - target.getBorderWidth("lr");
int h = rect.height - target.getBorderWidth("tb");
int centerW = w, centerH = h, centerY = 0, centerX = 0;
north = getRegionWidget(LayoutRegion.NORTH);
south = getRegionWidget(LayoutRegion.SOUTH);
west = getRegionWidget(LayoutRegion.WEST);
east = getRegionWidget(LayoutRegion.EAST);
center = getRegionWidget(LayoutRegion.CENTER);
if (north != null) {
BorderLayoutData data = (BorderLayoutData) getLayoutData(north);
north.setVisible(!data.isHidden());
if (!data.isHidden()) {
if (north.getData("init") == null) {
initPanel(north);
}
if (data.isSplit()) {
initSplitBar(LayoutRegion.SOUTH, north, data);
} else {
removeSplitBar(LayoutRegion.SOUTH);
}
Rectangle b = new Rectangle();
Margins m = data.getMargins();
float s = data.getSize() < 1 ? data.getSize() * rect.height : data.getSize();
b.height = (int) s;
b.width = w - (m.left + m.right);
b.x = m.left;
b.y = m.top;
centerY = b.height + b.y + m.bottom;
centerH -= centerY;
applyLayout(north, b);
}
}
if (south != null) {
BorderLayoutData data = (BorderLayoutData) getLayoutData(south);
south.setVisible(!data.isHidden());
if (!data.isHidden()) {
if (south.getData("init") == null) {
initPanel(south);
}
if (data.isSplit()) {
initSplitBar(LayoutRegion.NORTH, south, data);
} else {
removeSplitBar(LayoutRegion.NORTH);
}
Rectangle b = south.getBounds(false);
Margins m = data.getMargins();
float s = data.getSize() < 1 ? data.getSize() * rect.height : data.getSize();
b.height = (int) s;
b.width = w - (m.left + m.right);
b.x = m.left;
int totalHeight = (b.height + m.top + m.bottom);
b.y = h - totalHeight + m.top;
centerH -= totalHeight;
applyLayout(south, b);
}
}
if (west != null) {
BorderLayoutData data = (BorderLayoutData) getLayoutData(west);
west.setVisible(!data.isHidden());
if (!data.isHidden()) {
if (west.getData("init") == null) {
initPanel(west);
}
if (data.isSplit()) {
initSplitBar(LayoutRegion.EAST, west, data);
} else {
removeSplitBar(LayoutRegion.EAST);
}
Rectangle box = new Rectangle();
Margins m = data.getMargins();
float s = data.getSize() < 1 ? data.getSize() * rect.width : data.getSize();
box.width = (int) s;
box.height = centerH - (m.top + m.bottom);
box.x = m.left;
box.y = centerY + m.top;
int totalWidth = (box.width + m.left + m.right);
centerX += totalWidth;
centerW -= totalWidth;
applyLayout(west, box);
}
}
if (east != null) {
BorderLayoutData data = (BorderLayoutData) getLayoutData(east);
east.setVisible(!data.isHidden());
if (!data.isHidden()) {
if (east.getData("init") == null) {
initPanel(east);
}
if (data.isSplit()) {
initSplitBar(LayoutRegion.WEST, east, data);
} else {
removeSplitBar(LayoutRegion.WEST);
}
Rectangle b = east.getBounds(false);
Margins m = data.getMargins();
float s = data.getSize() < 1 ? data.getSize() * rect.width : data.getSize();
b.width = (int) s;
b.height = centerH - (m.top + m.bottom);
int totalWidth = (b.width + m.left + m.right);
b.x = w - totalWidth + m.left;
b.y = centerY + m.top;
centerW -= totalWidth;
applyLayout(east, b);
}
}
lastCenter = new Rectangle(centerX, centerY, centerW, centerH);
if (center != null) {
BorderLayoutData data = (BorderLayoutData) getLayoutData(center);
Margins m = data.getMargins();
lastCenter.x = centerX + m.left;
lastCenter.y = centerY + m.top;
lastCenter.width = centerW - (m.left + m.right);
lastCenter.height = centerH - (m.top + m.bottom);
applyLayout(center, lastCenter);
}
}
@Override
protected void onRemove(Component component) {
super.onRemove(component);
if (component instanceof ContentPanel) {
ContentPanel panel = (ContentPanel) component;
if (panel.getData("collapseBtn") != null) {
Component tool = (Component) panel.getData("collapseBtn");
tool.removeAllListeners();
panel.getHeader().removeTool(tool);
}
panel.removeListener(Events.BeforeCollapse, collapseListener);
panel.removeListener(Events.BeforeExpand, collapseListener);
}
component.setData("init", null);
component.setData("collapseBtn", null);
component.setData("collapse", null);
SplitBar splitBar = component.getData("splitBar");
if (splitBar != null) {
splitBar.release();
component.setData("splitBar", null);
}
}
private void applyLayout(BoxComponent component, Rectangle box) {
component.el().makePositionable(true);
component.el().setLeftTop(box.x, box.y);
component.setSize(box.width, box.height);
}
private CollapsePanel createCollapsePanel(ContentPanel panel, BorderLayoutData data) {
CollapsePanel cp = new CollapsePanel(panel, data) {
protected void onExpandButton(BaseEvent be) {
if (isExpanded()) {
setExpanded(false);
}
onExpandClick(this);
}
};
BorderLayoutData collapseData = new BorderLayoutData(data.getRegion());
collapseData.setSize(24);
collapseData.setMargins(data.getMargins());
ComponentHelper.setLayoutData(cp, collapseData);
cp.setData("panel", panel);
panel.setData("collapse", cp);
return cp;
}
private BoxComponent getRegionWidget(LayoutRegion region) {
for (int i = 0; i < container.getItemCount(); i++) {
BoxComponent w = (BoxComponent) container.getItem(i);
if (getLayoutData(w) != null && getLayoutData(w) instanceof BorderLayoutData) {
BorderLayoutData data = (BorderLayoutData) getLayoutData(w);
if (data.getRegion() == region) {
w.el().makePositionable(true);
return w;
}
}
}
return null;
}
private void initPanel(BoxComponent component) {
BorderLayoutData data = (BorderLayoutData) getLayoutData(component);
String icon = null;
switch (data.getRegion()) {
case WEST:
icon = "left";
break;
case EAST:
icon = "right";
break;
case NORTH:
icon = "up";
break;
case SOUTH:
icon = "down";
break;
}
if (data.isCollapsible() && component instanceof ContentPanel) {
final ContentPanel panel = (ContentPanel) component;
ToolButton collapse = (ToolButton) panel.getData("collapseBtn");
if (!data.getHideCollapseTool() && collapse == null) {
collapse = new ToolButton("x-tool-" + icon);
collapse.addListener(Events.Select, new Listener<ComponentEvent>() {
public void handleEvent(ComponentEvent be) {
panel.collapse();
}
});
panel.setData("collapseBtn", collapse);
panel.getHeader().addTool(collapse);
collapse.setData("panel", panel);
}
panel.removeListener(Events.BeforeCollapse, collapseListener);
panel.removeListener(Events.BeforeExpand, collapseListener);
panel.addListener(Events.BeforeCollapse, collapseListener);
panel.addListener(Events.BeforeExpand, collapseListener);
panel.setData("init", "true");
}
}
private void initSplitBar(final LayoutRegion region, final BoxComponent component,
final BorderLayoutData data) {
SplitBar bar = (SplitBar) component.getData("splitBar");
if (bar == null || bar.getResizeWidget() != component) {
bar = createSplitBar(region, component);
final SplitBar fBar = bar;
Listener<ComponentEvent> splitBarListener = new Listener<ComponentEvent>() {
public void handleEvent(ComponentEvent ce) {
boolean side = region == LayoutRegion.WEST || region == LayoutRegion.EAST;
int size = side ? component.getOffsetWidth() : component.getOffsetHeight();
int centerSize = side ? lastCenter.width : lastCenter.height;
fBar.setMinSize(data.getMinSize());
fBar.setMaxSize(Math.min(size + centerSize, data.getMaxSize()));
}
};
component.setData("splitBar", bar);
bar.addListener(Events.DragStart, splitBarListener);
bar.setMinSize(data.getMinSize());
bar.setMaxSize(data.getMaxSize() == 0 ? bar.getMaxSize() : data.getMaxSize());
bar.setAutoSize(false);
bar.addListener(Events.Resize, new Listener<SplitBarEvent>() {
public void handleEvent(SplitBarEvent sbe) {
if (sbe.getSize() < 1) {
return;
}
data.setSize(sbe.getSize());
Component c = sbe.getSplitBar().getResizeWidget();
Map<String, Object> state = c.getState();
state.put("size", data.getSize());
c.saveState();
layout();
}
});
component.setData("splitBar", bar);
}
}
private void onCollapse(ContentPanel panel) {
if (!layoutContainer.getItems().contains(panel)) {
return;
}
BorderLayoutData data = (BorderLayoutData) getLayoutData(panel);
layoutContainer.remove(panel);
Map<String, Object> st = panel.getState();
st.put("collapsed", true);
panel.saveState();
setCollapsed(panel, true);
CollapsePanel cp = (CollapsePanel) panel.getData("collapse");
if (cp == null) {
cp = createCollapsePanel(panel, data);
}
layoutContainer.add(cp);
layoutContainer.layout();
}
private void onExpand(ContentPanel panel) {
setCollapsed(panel, false);
CollapsePanel cp = panel.getData("collapse");
Map<String, Object> st = panel.getState();
st.remove("collapsed");
panel.saveState();
layoutContainer.remove(cp);
layoutContainer.add(panel);
layoutContainer.layout();
}
private void onExpandClick(CollapsePanel cp) {
ContentPanel panel = cp.getContentPanel();
onExpand(panel);
}
private void removeSplitBar(LayoutRegion region) {
splitBars.put(region, null);
}
private native void setCollapsed(ContentPanel panel, boolean collapse) /*-{
panel.@com.extjs.gxt.ui.client.widget.ContentPanel::collapsed = collapse;
}-*/;
private void switchPanels(ContentPanel panel) {
BorderLayoutData data = (BorderLayoutData) getLayoutData(panel);
layoutContainer.remove(panel);
CollapsePanel cp = (CollapsePanel) panel.getData("collapse");
if (cp == null) {
cp = createCollapsePanel(panel, data);
}
initPanel(panel);
setCollapsed(panel, true);
layoutContainer.add(cp);
renderComponent(cp, 0, layoutContainer.getLayoutTarget());
}
}