com.ritchey.client.view.DockPanel.java Source code

Java tutorial

Introduction

Here is the source code for com.ritchey.client.view.DockPanel.java

Source

package com.ritchey.client.view;

/*
 * 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.
 */

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import com.google.gwt.dom.client.Element;
import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.google.gwt.user.client.ui.HasAlignment;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasHorizontalAlignment.HorizontalAlignmentConstant;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HasVerticalAlignment.VerticalAlignmentConstant;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.Widget;
import com.ritchey.client.view.DockPanel.DockLayoutConstant;

/**
 * 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.
 *
 * <p>
 * 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 <code>100%</code> will
 * <em>NOT</em> cause the child to fill the available height.
 * </p>
 *
 * <p>
 * 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}).
 * </p>
 *
 * <p>
 * <img class='gallery' src='doc-files/DockPanel.png'/>
 * </p>
 *
 * @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 <code>null</code> 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.
     *
     * <p>
     * <b>Affected Elements:</b>
     * <ul>
     * <li>-center = the center cell.</li>
     * <li>-north# = the northern cell.</li>
     * <li>-south# = the southern cell.</li>
     * <li>-east# = the eastern cell.</li>
     * <li>-west# = the western cell.</li>
     * </ul>
     * </p>
     *
     * @see UIObject#onEnsureDebugId(String)
     */
    @Override
    protected void onEnsureDebugId(String baseID) {
        super.onEnsureDebugId(baseID);

        Map<DockLayoutConstant, Integer> dirCount = new HashMap<DockLayoutConstant, Integer>();
        Iterator<Widget> 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<Widget> 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<Widget> 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);
    }
}