Skip to content

Instantly share code, notes, and snippets.

@branflake2267
Last active April 20, 2016 00:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save branflake2267/bc9df05a719700dfff7100b48f8a1f4c to your computer and use it in GitHub Desktop.
Save branflake2267/bc9df05a719700dfff7100b48f8a1f4c to your computer and use it in GitHub Desktop.
Will work with GXT 4.0.1. This container has a fix for recursive layout issue that appears in logging. (Copied from fix in 4.0.2-SNAPSHOT)
package com.sencha.gxt.widget.core.client.container;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.Widget;
import com.sencha.gxt.core.client.GXTLogConfiguration;
import com.sencha.gxt.core.client.Style;
import com.sencha.gxt.core.client.dom.XElement;
import com.sencha.gxt.core.client.resources.CommonStyles;
import com.sencha.gxt.core.client.util.Margins;
import com.sencha.gxt.core.client.util.Size;
import com.sencha.gxt.core.client.util.Util;
import com.sencha.gxt.core.shared.event.GroupingHandlerRegistration;
import com.sencha.gxt.widget.core.client.Component;
import com.sencha.gxt.widget.core.client.button.ButtonGroup;
import com.sencha.gxt.widget.core.client.button.SplitButton;
import com.sencha.gxt.widget.core.client.button.TextButton;
import com.sencha.gxt.widget.core.client.button.ToggleButton;
import com.sencha.gxt.widget.core.client.event.BeforeShowEvent;
import com.sencha.gxt.widget.core.client.event.BeforeShowEvent.BeforeShowHandler;
import com.sencha.gxt.widget.core.client.event.CheckChangeEvent;
import com.sencha.gxt.widget.core.client.event.CheckChangeEvent.CheckChangeHandler;
import com.sencha.gxt.widget.core.client.event.OverflowEvent;
import com.sencha.gxt.widget.core.client.event.OverflowEvent.HasOverflowHandlers;
import com.sencha.gxt.widget.core.client.event.OverflowEvent.OverflowHandler;
import com.sencha.gxt.widget.core.client.event.SelectEvent;
import com.sencha.gxt.widget.core.client.menu.CheckMenuItem;
import com.sencha.gxt.widget.core.client.menu.ColorMenu;
import com.sencha.gxt.widget.core.client.menu.HeaderMenuItem;
import com.sencha.gxt.widget.core.client.menu.Item;
import com.sencha.gxt.widget.core.client.menu.Menu;
import com.sencha.gxt.widget.core.client.menu.MenuItem;
import com.sencha.gxt.widget.core.client.menu.SeparatorMenuItem;
import com.sencha.gxt.widget.core.client.toolbar.FillToolItem;
import com.sencha.gxt.widget.core.client.toolbar.SeparatorToolItem;
import com.sencha.gxt.widget.core.client.toolbar.ToolBar;
/**
* <p>A layout container for horizontal rows of widgets. Provides support for automatic overflow (i.e. when there are too
* many widgets to display in the available width -- see {@link #setEnableOverflow(boolean)}).</p>
*
* <p>Code Snippet:</p>
*
* <pre><code>
* HBoxLayoutContainer c = new HBoxLayoutContainer();
* c.setHBoxLayoutAlign(HBoxLayoutAlign.TOP);
* BoxLayoutData layoutData = new BoxLayoutData(new Margins(5, 0, 0, 5));
* c.add(new TextButton("Button 1"), layoutData);
* c.add(new TextButton("Button 2"), layoutData);
* c.add(new TextButton("Button 3"), layoutData);
* Viewport v = new Viewport();
* v.add(c);
* RootPanel.get().add(v);
* </code></pre>
*
* @see ToolBar
*/
public class HBoxLayoutContainer extends BoxLayoutContainer implements HasOverflowHandlers {
/**
* The vertical alignment of the horizontal widgets.
*/
public enum HBoxLayoutAlign {
/**
* Children are aligned horizontally at the <b>bottom</b> side of the container.
*/
BOTTOM,
/**
* Children are aligned horizontally at the <b>mid-height</b> of the container.
*/
MIDDLE,
/**
* Children are stretched vertically to fill the height of the container.
*/
STRETCH,
/**
* Children heights are set the size of the largest child's height.
*/
STRETCHMAX,
/**
* Children are aligned horizontally at the <b>top</b> side of the container.
*/
TOP
}
public interface HBoxLayoutContainerAppearance extends BoxLayoutContainerAppearance {
ImageResource moreIcon();
String moreButtonStyle();
}
protected List<Widget> hiddens = new ArrayList<Widget>();
protected boolean hasOverflow;
protected TextButton more;
protected Menu moreMenu;
private boolean enableOverflow = true;
private HBoxLayoutAlign hBoxLayoutAlign;
private Map<Widget, Integer> loopWidthMap = new HashMap<Widget, Integer>();
private Map<Widget, Integer> loopHeightMap = new HashMap<Widget, Integer>();
private GroupingHandlerRegistration handlerRegistration = new GroupingHandlerRegistration();
private int triggerWidth = 35;
private Map<Widget, Integer> widthMap = new HashMap<Widget, Integer>();
private static Logger logger = Logger.getLogger(HBoxLayoutContainer.class.getName());
/**
* Creates a new HBoxlayout.
*/
public HBoxLayoutContainer() {
this(HBoxLayoutAlign.TOP);
}
/**
* Creates a new HBoxlayout.
*
* @param appearance the hbox appearance
*/
public HBoxLayoutContainer(HBoxLayoutContainerAppearance appearance) {
this(HBoxLayoutAlign.TOP, appearance);
}
/**
* Creates a new HBoxlayout.
*
* @param align the horizontal alignment
*/
public HBoxLayoutContainer(HBoxLayoutAlign align) {
this(align, GWT.<HBoxLayoutContainerAppearance>create(HBoxLayoutContainerAppearance.class));
}
protected HBoxLayoutContainer(HBoxLayoutAlign align, HBoxLayoutContainerAppearance appearance) {
super(appearance);
setHBoxLayoutAlign(align);
}
@Override
public HandlerRegistration addOverflowHandler(OverflowHandler handler) {
return addHandler(handler, OverflowEvent.getType());
}
/**
* Returns the horizontal layout appearance.
*
* @return the appearance
*/
@Override
public HBoxLayoutContainerAppearance getAppearance() {
return (HBoxLayoutContainerAppearance) super.getAppearance();
}
/**
* Returns the horizontal alignment.
*
* @return the horizontal alignment
*/
public HBoxLayoutAlign getHBoxLayoutAlign() {
return hBoxLayoutAlign;
}
/**
* Returns true if overflow is enabled.
*
* @return the overflow state
*/
public boolean isEnableOverflow() {
return enableOverflow;
}
/**
* True to show a drop down icon when the available width is less than the required width (defaults to true).
*
* @param enableOverflow true to enable overflow support
*/
public void setEnableOverflow(boolean enableOverflow) {
this.enableOverflow = enableOverflow;
}
/**
* Sets the vertical alignment for child items (defaults to TOP).
*
* @param hBoxLayoutAlign the vertical alignment
*/
public void setHBoxLayoutAlign(HBoxLayoutAlign hBoxLayoutAlign) {
this.hBoxLayoutAlign = hBoxLayoutAlign;
}
protected void addWidgetToMenu(Menu menu, Widget w) {
// TODO do we really want all these types referenced here?
if (w instanceof SeparatorToolItem) {
menu.add(new SeparatorMenuItem());
} else if (w instanceof SplitButton) {
final SplitButton sb = (SplitButton) w;
MenuItem item = new MenuItem(sb.getText(), sb.getIcon());
item.setEnabled(sb.isEnabled());
item.setItemId(sb.getItemId());
if (sb.getData("gxt-menutext") != null) {
item.setText(sb.getData("gxt-menutext").toString());
}
if (sb.getMenu() != null) {
item.setSubMenu(sb.getMenu());
}
handlerRegistration.add(item.addSelectionHandler(new SelectionHandler<Item>() {
@Override
public void onSelection(SelectionEvent<Item> event) {
sb.fireEvent(new SelectEvent());
}
}));
menu.add(item);
} else if (w instanceof TextButton) {
final TextButton b = (TextButton) w;
MenuItem item = new MenuItem(b.getText(), b.getIcon());
item.setItemId(b.getItemId());
if (b.getData("gxt-menutext") != null) {
item.setText(b.getData("gxt-menutext").toString());
}
if (b.getMenu() != null) {
item.setHideOnClick(false);
item.setSubMenu(b.getMenu());
if (item.getSubMenu() instanceof ColorMenu) {
ColorMenu colorMenu = (ColorMenu) item.getSubMenu();
handlerRegistration.add(colorMenu.getPalette().addValueChangeHandler(new ValueChangeHandler<String>() {
@Override
public void onValueChange(ValueChangeEvent<String> valueChangeEvent) {
moreMenu.hide();
}
}));
}
}
item.setEnabled(b.isEnabled());
handlerRegistration.add(item.addSelectionHandler(new SelectionHandler<Item>() {
@Override
public void onSelection(SelectionEvent<Item> event) {
b.fireEvent(new SelectEvent());
}
}));
menu.add(item);
} else if (w instanceof ButtonGroup) {
ButtonGroup g = (ButtonGroup) w;
g.setItemId(g.getItemId());
menu.add(new SeparatorMenuItem());
menu.add(new HeaderMenuItem(g.getHeading()));
Widget con = g.getWidget();
if (con != null && con instanceof HasWidgets) {
HasWidgets widgets = (HasWidgets) con;
Iterator<Widget> it = widgets.iterator();
while (it.hasNext()) {
addWidgetToMenu(menu, it.next());
}
}
menu.add(new SeparatorMenuItem());
} else if (w instanceof ToggleButton) {
final ToggleButton b = (ToggleButton) w;
final CheckMenuItem item = new CheckMenuItem(b.getText());
item.setItemId(b.getItemId());
item.setChecked(b.getValue());
if (b.getData("gxt-menutext") != null) {
item.setText(b.getData("gxt-menutext").toString());
}
item.setEnabled(b.isEnabled());
handlerRegistration.add(item.addCheckChangeHandler(new CheckChangeHandler<CheckMenuItem>() {
@Override
public void onCheckChange(CheckChangeEvent<CheckMenuItem> event) {
// must pass true to cause value change event to fire
b.setValue(event.getItem().isChecked(), true);
b.fireEvent(new SelectEvent());
}
}));
menu.add(item);
}
if (menu.getWidgetCount() > 0) {
if (menu.getWidget(0) instanceof SeparatorMenuItem) {
menu.remove(menu.getWidget(0));
}
if (menu.getWidgetCount() > 0) {
if (menu.getWidget(menu.getWidgetCount() - 1) instanceof SeparatorMenuItem) {
menu.remove(menu.getWidget(menu.getWidgetCount() - 1));
}
}
}
}
protected void clearMenu() {
moreMenu.clear();
}
@Override
protected void doLayout() {
Size size = getElement().getStyleSize();
if (GXTLogConfiguration.loggingIsEnabled()) {
logger.finest(getId() + " doLayout size: " + size);
}
if ((size.getHeight() == 0 && size.getWidth() == 0) || size.getWidth() == 0) {
return;
}
int w = size.getWidth() - getScrollOffset();
int h = size.getHeight();
int styleHeight = Util.parseInt(getElement().getStyle().getProperty("height"), Style.DEFAULT);
int styleWidth = Util.parseInt(getElement().getStyle().getProperty("width"), Style.DEFAULT);
boolean findWidth = styleWidth == -1;
boolean findHeight = styleHeight == -1;
if (GXTLogConfiguration.loggingIsEnabled()) {
logger.finest(getId() + " findWidth: " + findWidth + " findHeight: " + findHeight);
}
int calculateWidth = 0;
int maxWidgetHeight = 0;
int maxMarginTop = 0;
int maxMarginBottom = 0;
for (int i = 0, len = getWidgetCount(); i < len; i++) {
Widget widget = getWidget(i);
BoxLayoutData layoutData = null;
Object d = widget.getLayoutData();
if (d instanceof BoxLayoutData) {
layoutData = (BoxLayoutData) d;
} else {
layoutData = new BoxLayoutData();
widget.setLayoutData(layoutData);
}
Margins cm = layoutData.getMargins();
if (cm == null) {
cm = new Margins(0);
layoutData.setMargins(cm);
}
}
if (findWidth || findHeight) {
for (int i = 0, len = getWidgetCount(); i < len; i++) {
Widget widget = getWidget(i);
if (!widget.isVisible()) {
continue;
}
BoxLayoutData layoutData = (BoxLayoutData) widget.getLayoutData();
Margins cm = layoutData.getMargins();
calculateWidth += widget.getOffsetWidth();
maxWidgetHeight = Math.max(maxWidgetHeight, widget.getOffsetHeight());
calculateWidth += (cm.getLeft() + cm.getRight());
maxMarginTop = Math.max(maxMarginTop, cm.getTop());
maxMarginBottom = Math.max(maxMarginBottom, cm.getBottom());
}
maxWidgetHeight += (maxMarginTop + maxMarginBottom);
if (findWidth) {
w = calculateWidth;
}
if (findHeight) {
h = maxWidgetHeight;
}
}
int pl = 0;
int pt = 0;
int pb = 0;
int pr = 0;
if (getPadding() != null) {
pl = getPadding().getLeft();
pt = getPadding().getTop();
pb = getPadding().getBottom();
pr = getPadding().getRight();
}
if (findHeight) {
h += pt + pb;
}
if (findWidth) {
w += pl + pr;
}
int stretchHeight = h - pt - pb;
int totalFlex = 0;
int totalWidth = 0;
int maxHeight = 0;
for (int i = 0, len = getWidgetCount(); i < len; i++) {
Widget widget = getWidget(i);
widget.addStyleName(CommonStyles.get().positionable());
widget.getElement().getStyle().setMargin(0, Unit.PX);
if (!widget.isVisible()) {
continue;
}
if (widget == more) {
triggerWidth = widget.getOffsetWidth() + 10;
}
BoxLayoutData layoutData = (BoxLayoutData) widget.getLayoutData();
Margins cm = layoutData.getMargins();
// TODO strange issue where getOffsetWidth call in 2nd loop is returning smaller number than actual offset
// when packing CENTER or END so we cache the offsetWidth for use in 2nd loop
// with buttons, the button is word wrapping causing the button to be narrower and taller
int ww = widget.getOffsetWidth();
loopWidthMap.put(widget, ww);
loopHeightMap.put(widget, widget.getOffsetHeight());
totalFlex += layoutData.getFlex();
totalWidth += (ww + cm.getLeft() + cm.getRight());
maxHeight = Math.max(maxHeight, widget.getOffsetHeight() + cm.getTop() + cm.getBottom());
}
int innerCtHeight = maxHeight + pt + pb;
if (hBoxLayoutAlign.equals(HBoxLayoutAlign.STRETCH)) {
getContainerTarget().setSize(w, h, true);
} else if (hBoxLayoutAlign.equals(HBoxLayoutAlign.MIDDLE) || hBoxLayoutAlign.equals(HBoxLayoutAlign.BOTTOM)) {
getContainerTarget().setSize(w, h = Math.max(h, innerCtHeight), true);
} else {
getContainerTarget().setSize(w, innerCtHeight, true);
}
int extraWidth = w - totalWidth - pl - pr;
int allocated = 0;
int componentWidth, componentHeight, componentTop;
int availableHeight = h - pt - pb;
if (getPack().equals(BoxLayoutPack.CENTER)) {
pl += extraWidth / 2;
} else if (getPack().equals(BoxLayoutPack.END)) {
pl += extraWidth;
}
for (int i = 0, len = getWidgetCount(); i < len; i++) {
Widget widget = getWidget(i);
if (!widget.isVisible()) {
continue;
}
BoxLayoutData layoutData = (BoxLayoutData) widget.getLayoutData();
Margins cm = layoutData.getMargins();
componentWidth = loopWidthMap.remove(widget);
componentHeight = loopHeightMap.remove(widget);
pl += cm.getLeft();
pl = Math.max(0, pl);
if (hBoxLayoutAlign.equals(HBoxLayoutAlign.MIDDLE)) {
int diff = availableHeight - (componentHeight + cm.getTop() + cm.getBottom());
if (diff == 0) {
componentTop = pt + cm.getTop();
} else {
componentTop = pt + cm.getTop() + (diff / 2);
}
} else {
if (hBoxLayoutAlign.equals(HBoxLayoutAlign.BOTTOM)) {
componentTop = h - (pb + cm.getBottom() + componentHeight);
} else {
componentTop = pt + cm.getTop();
}
}
boolean component = widget instanceof Component;
Component c = null;
if (component) {
c = (Component) widget;
}
int width = -1;
if (component) {
c.setPosition(pl, componentTop);
} else {
XElement.as(widget.getElement()).setLeftTop(pl, componentTop);
}
if (getPack().equals(BoxLayoutPack.START) && layoutData.getFlex() > 0) {
int add = (int) Math.floor(extraWidth * (layoutData.getFlex() / totalFlex));
allocated += add;
if (isAdjustForFlexRemainder() && i == getWidgetCount() - 1) {
add += (extraWidth - allocated);
}
componentWidth += add;
width = componentWidth;
}
if (hBoxLayoutAlign.equals(HBoxLayoutAlign.STRETCH)) {
applyLayout(
widget,
width,
Util.constrain(stretchHeight - cm.getTop() - cm.getBottom(), layoutData.getMinSize(),
layoutData.getMaxSize()));
} else if (hBoxLayoutAlign.equals(HBoxLayoutAlign.STRETCHMAX)) {
applyLayout(widget, width,
Util.constrain(maxHeight - cm.getTop() - cm.getBottom(), layoutData.getMinSize(), layoutData.getMaxSize()));
} else if (width > 0) {
applyLayout(widget, width, -1);
}
pl += componentWidth + cm.getRight();
}
// do we need overflow
if (enableOverflow) {
int runningWidth = 0;
for (int i = 0, len = getWidgetCount(); i < len; i++) {
Widget widget = getWidget(i);
if (widget == more) {
continue;
}
BoxLayoutData layoutData = null;
Object d = widget.getLayoutData();
if (d instanceof BoxLayoutData) {
layoutData = (BoxLayoutData) d;
} else {
layoutData = new BoxLayoutData();
}
Margins cm = layoutData.getMargins();
if (cm == null) {
cm = new Margins(0);
}
runningWidth += getWidgetWidth(widget);
runningWidth += cm.getLeft();
runningWidth += cm.getRight();
}
if (runningWidth > w) {
hasOverflow = true;
onOverflow();
} else {
hasOverflow = false;
onUnoverflow();
}
}
}
protected int getWidgetWidth(Widget widget) {
Integer w = widthMap.get(widget);
if (w != null) {
return w;
} else {
return widget.getOffsetWidth();
}
}
protected void hideComponent(Widget w) {
widthMap.put(w, w.getOffsetWidth());
hiddens.add(w);
w.setVisible(false);
}
protected void initMore() {
if (more == null) {
moreMenu = new Menu();
moreMenu.addBeforeShowHandler(new BeforeShowHandler() {
@Override
public void onBeforeShow(BeforeShowEvent event) {
handlerRegistration.removeHandler();
clearMenu();
int widgetCount = getWidgetCount();
for (int i = 0; i < widgetCount; i++) {
Widget w = getWidget(i);
if (isHidden(w) && w != more) {
addWidgetToMenu(moreMenu, w);
}
}
HBoxLayoutContainer.this.fireEvent(new OverflowEvent(moreMenu));
}
});
more = new TextButton();
more.addStyleName("x-toolbar-more");
more.addStyleName(getAppearance().moreButtonStyle());
more.setData("x-ignore-width", true);
more.setData("gxt-more", "true");
more.setIcon(getAppearance().moreIcon());
more.setMenu(moreMenu);
}
if (more.getParent() != this) {
add(more);
}
}
protected boolean isHidden(Widget w) {
return hiddens != null && hiddens.contains(w);
}
@Override
protected void onInsert(int index, Widget child) {
super.onInsert(index, child);
child.addStyleName(CommonStyles.get().floatLeft());
}
@Override
protected void onRemove(Widget child) {
super.onRemove(child);
child.removeStyleName(CommonStyles.get().floatLeft());
}
protected void onOverflow() {
Size size = getElement().getStyleSize();
final int w = size.getWidth() - getScrollOffset() - triggerWidth;
boolean change = false;
int loopWidth = 0;
for (int i = 0; i < getWidgetCount(); i++) {
Widget widget = getWidget(i);
if (widget == more) continue;
if (!(widget instanceof FillToolItem)) {
loopWidth += getWidgetWidth(widget);
BoxLayoutData data = (BoxLayoutData) widget.getLayoutData();
if (data != null && data.getMargins() != null) {
loopWidth += (data.getMargins().getLeft() + data.getMargins().getRight());
}
if (loopWidth >= w) {
if (!isHidden(widget)) {
change = true;
hideComponent(widget);
}
} else {
if (isHidden(widget)) {
change = true;
unhideComponent(widget);
}
}
}
}
if (hiddens != null && hiddens.size() > 0) {
initMore();
}
if (change) {
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
forceLayout();
}
});
}
}
protected void onUnoverflow() {
boolean change = false;
for (int i = 0; i < getWidgetCount(); i++) {
Widget widget = getWidget(i);
if (widget == more) continue;
if (isHidden(widget)) {
change = true;
unhideComponent(widget);
}
}
if (more != null && more.getParent() == HBoxLayoutContainer.this) {
change = true;
remove(more);
}
if (change) {
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
forceLayout();
}
});
}
}
protected void unhideComponent(Widget w) {
if (hiddens.remove(w)) {
widthMap.remove(w);
w.setVisible(true);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment