com.sencha.gxt.widget.core.client.container.HBoxLayoutContainer.java Source code

Java tutorial

Introduction

Here is the source code for com.sencha.gxt.widget.core.client.container.HBoxLayoutContainer.java

Source

/**
 * Sencha GXT 4.0.0 - Sencha for GWT
 * Copyright (c) 2006-2015, Sencha Inc.
 *
 * licensing@sencha.com
 * http://www.sencha.com/products/gxt/license/
 *
 * ================================================================================
 * Open Source License
 * ================================================================================
 * This version of Sencha GXT is licensed under the terms of the Open Source GPL v3
 * license. You may use this license only if you are prepared to distribute and
 * share the source code of your application under the GPL v3 license:
 * http://www.gnu.org/licenses/gpl.html
 *
 * If you are NOT prepared to distribute and share the source code of your
 * application under the GPL v3 license, other commercial and oem licenses
 * are available for an alternate download of Sencha GXT.
 *
 * Please see the Sencha GXT Licensing page at:
 * http://www.sencha.com/products/gxt/license/
 *
 * For clarification or additional options, please contact:
 * licensing@sencha.com
 * ================================================================================
 *
 *
 * ================================================================================
 * Disclaimer
 * ================================================================================
 * THIS SOFTWARE IS DISTRIBUTED "AS-IS" WITHOUT ANY WARRANTIES, CONDITIONS AND
 * REPRESENTATIONS WHETHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE
 * IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, MERCHANTABLE QUALITY,
 * FITNESS FOR A PARTICULAR PURPOSE, DURABILITY, NON-INFRINGEMENT, PERFORMANCE AND
 * THOSE ARISING BY STATUTE OR FROM CUSTOM OR USAGE OF TRADE OR COURSE OF DEALING.
 * ================================================================================
 */
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.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.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.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;

/**
 * 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/>
 * Code Snippet:
 * 
 * <pre>
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);
 * </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 int triggerWidth = 35;
    private Map<Widget, Integer> widthMap = new HashMap<Widget, Integer>();
    private Map<Widget, Integer> loopWidthMap = new HashMap<Widget, Integer>();
    private Map<Widget, Integer> loopHeightMap = 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());
            }

            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());
            }
            item.setEnabled(b.isEnabled());

            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());

            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;
                if (more != null && more.getParent() == this) {
                    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) {
                    clearMenu();

                    for (int i = 0, len = getWidgetCount(); i < len; 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() {
        if (more != null) {
            Scheduler.get().scheduleDeferred(new ScheduledCommand() {

                @Override
                public void execute() {
                    for (int i = 0; i < getWidgetCount(); i++) {
                        Widget widget = getWidget(i);
                        unhideComponent(widget);
                    }
                    remove(more);
                    forceLayout();
                }
            });
        }
    }

    protected void unhideComponent(Widget w) {
        if (hiddens.remove(w)) {
            widthMap.remove(w);
            w.setVisible(true);
        }
    }
}