org.metawidget.gwt.client.ui.layout.FlexTableLayout.java Source code

Java tutorial

Introduction

Here is the source code for org.metawidget.gwt.client.ui.layout.FlexTableLayout.java

Source

// Metawidget
//
// This file is dual licensed under both the LGPL
// (http://www.gnu.org/licenses/lgpl-2.1.html) and the EPL
// (http://www.eclipse.org/org/documents/epl-v10.php). As a
// recipient of Metawidget, you may choose to receive it under either
// the LGPL or the EPL.
//
// Commercial licenses are also available. See http://metawidget.org
// for details.

package org.metawidget.gwt.client.ui.layout;

import static org.metawidget.inspector.InspectionResultConstants.*;

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

import org.metawidget.gwt.client.ui.Facet;
import org.metawidget.gwt.client.ui.GwtMetawidget;
import org.metawidget.gwt.client.ui.Stub;
import org.metawidget.layout.iface.AdvancedLayout;
import org.metawidget.util.simple.SimpleLayoutUtils;
import org.metawidget.util.simple.StringUtils;

import com.google.gwt.user.client.ui.ComplexPanel;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.Widget;

/**
 * Layout to arrange widgets in a <code>FlexTable</code>, with one column for labels and another for
 * the widget.
 *
 * @author <a href="http://kennardconsulting.com">Richard Kennard</a>
 */

public class FlexTableLayout implements AdvancedLayout<Widget, Panel, GwtMetawidget> {

    //
    // Private statics
    //

    private static final int LABEL_AND_COMPONENT_AND_REQUIRED = 3;

    //
    // Private members
    //

    private final int mNumberOfColumns;

    private final String mTableStyleName;

    private final String[] mColumnStyleNames;

    private final String mFooterStyleName;

    //
    // Constructor
    //

    public FlexTableLayout() {

        this(new FlexTableLayoutConfig());
    }

    public FlexTableLayout(FlexTableLayoutConfig config) {

        mNumberOfColumns = config.getNumberOfColumns();
        mTableStyleName = config.getTableStyleName();
        mColumnStyleNames = config.getColumnStyleNames();
        mFooterStyleName = config.getFooterStyleName();
    }

    //
    // Public methods
    //

    public void onStartBuild(GwtMetawidget metawidget) {

        // Do nothing
    }

    public void startContainerLayout(Panel container, GwtMetawidget metawidget) {

        State state = getState(container, metawidget);
        FlexTable flexTable = new FlexTable();
        flexTable.setStyleName(mTableStyleName);
        container.add(flexTable);

        state.formatter = flexTable.getFlexCellFormatter();
    }

    public void layoutWidget(Widget widget, String elementName, Map<String, String> attributes, Panel container,
            GwtMetawidget metawidget) {

        // Do not render empty stubs

        if (widget instanceof Stub && ((Stub) widget).getWidgetCount() == 0) {
            return;
        }

        // Calculate row and actualColumn. Note FlexTable doesn't work quite as might be
        // expected. Specifically, it doesn't understand 'colspan' in relation to previous rows. So
        // if you do...
        //
        // layout.setWidget( row, 0, widget1 );
        // layout.setColSpan( row, 0, 2 );
        // layout.setWidget( row, 2, widget2 );
        //
        // ...you'll actually get...
        //
        // <td colspan="2">widget1</td>
        // <td/>
        // <td>widget2</td>
        //
        // ...note FlexTable inserts an extra <td/> because it thinks column 1 is missing. Therefore
        // the actualColumn is always just the next column, regardless of what state.mCurrentColumn
        // is

        int actualColumn;
        FlexTable flexTable = (FlexTable) ((ComplexPanel) container).getWidget(0);
        int row = flexTable.getRowCount();

        int numberOfColumns = getEffectiveNumberOfColumns(container);

        State state = getState(container, metawidget);

        if (state.currentColumn < numberOfColumns && row > 0) {
            row--;
            actualColumn = flexTable.getCellCount(row);
        } else {
            state.currentColumn = 0;
            actualColumn = 0;
        }

        // Special support for large components

        boolean spanAllColumns = willFillHorizontally(widget, attributes);

        if (spanAllColumns && state.currentColumn > 0) {
            state.currentColumn = 0;
            actualColumn = 0;
            row++;
        }

        // Label

        String labelText = metawidget.getLabelString(attributes);

        if (SimpleLayoutUtils.needsLabel(labelText, elementName)) {

            // Note: GWT Labels are not real HTML labels, and have no 'for' attribute

            Label label = new Label(labelText + StringUtils.SEPARATOR_COLON);
            String styleName = getStyleName(state.currentColumn * LABEL_AND_COMPONENT_AND_REQUIRED, metawidget);

            if (styleName != null) {
                state.formatter.setStyleName(row, actualColumn, styleName);
            }

            flexTable.setWidget(row, actualColumn, label);
        }

        // Widget

        // Widget column (null labels get collapsed, blank Strings get preserved)

        if (labelText != null) {
            // Zero-column layouts need an extra row

            if (numberOfColumns == 0) {
                state.currentColumn = 0;
                actualColumn = 0;
                row++;
            } else {
                actualColumn++;
            }
        }

        String styleName = getStyleName((state.currentColumn * LABEL_AND_COMPONENT_AND_REQUIRED) + 1, metawidget);

        if (styleName != null) {
            state.formatter.setStyleName(row, actualColumn, styleName);
        }

        flexTable.setWidget(row, actualColumn, widget);

        // Colspan

        int colspan;

        // Metawidgets and large components span all columns

        if (spanAllColumns) {
            colspan = (numberOfColumns * LABEL_AND_COMPONENT_AND_REQUIRED) - 2;
            state.currentColumn = numberOfColumns;

            if (labelText == null) {
                colspan++;
            }

            // Metawidgets span the required column too

            if (widget instanceof GwtMetawidget) {
                colspan++;
            }
        } else if (labelText == null) {

            // Components without labels span two columns

            colspan = 2;
        } else {

            // Everyone else spans just one

            colspan = 1;
        }

        if (colspan > 1) {
            state.formatter.setColSpan(row, actualColumn, colspan);
        }

        // Required

        if (!(widget instanceof GwtMetawidget)) {
            layoutRequired(attributes, container, metawidget);
        }

        state.currentColumn++;
    }

    public void endContainerLayout(Panel container, GwtMetawidget metawidget) {

        // Do nothing
    }

    public void onEndBuild(GwtMetawidget metawidget) {

        Facet facet = metawidget.getFacet("buttons");

        if (facet != null) {
            State state = getState(metawidget, metawidget);
            FlexTable flexTable = (FlexTable) metawidget.getWidget(0);
            int row = flexTable.getRowCount();

            int numberOfColumns = getEffectiveNumberOfColumns(metawidget);

            if (numberOfColumns > 0) {
                state.formatter.setColSpan(row, 0, numberOfColumns * LABEL_AND_COMPONENT_AND_REQUIRED);
            }

            if (mFooterStyleName != null) {
                state.formatter.setStyleName(row, 0, mFooterStyleName);
            }

            flexTable.setWidget(row, 0, facet);
        }
    }

    //
    // Protected methods
    //

    protected void layoutRequired(Map<String, String> attributes, Widget container, GwtMetawidget metawidget) {

        State state = getState(container, metawidget);
        FlexTable flexTable = (FlexTable) ((ComplexPanel) container).getWidget(0);
        int row = flexTable.getRowCount() - 1;
        int column = flexTable.getCellCount(row);

        state.formatter.setStyleName(row, column,
                getStyleName((state.currentColumn * LABEL_AND_COMPONENT_AND_REQUIRED) + 2, metawidget));

        if (attributes != null && TRUE.equals(attributes.get(REQUIRED)) && !TRUE.equals(attributes.get(READ_ONLY))
                && !metawidget.isReadOnly()) {
            flexTable.setText(row, column, "*");
            return;
        }

        // Render an empty div, so that the CSS can force it to a certain
        // width if desired for the layout (browsers seem to not respect
        // widths set on empty table columns)
        //
        // Note: don't do <div/>, as we may not be XHTML

        flexTable.setHTML(row, column, "<div></div>");
    }

    /**
     * @param metawidget
     *            the Metawidget doing the layout
     */

    protected String getStyleName(int styleName, GwtMetawidget metawidget) {

        if (mColumnStyleNames == null || mColumnStyleNames.length <= styleName) {
            return null;
        }

        return mColumnStyleNames[styleName];
    }

    protected boolean willFillHorizontally(Widget widget, Map<String, String> attributes) {

        if (widget instanceof GwtMetawidget) {
            return true;
        }

        return SimpleLayoutUtils.isSpanAllColumns(attributes);
    }

    //
    // Private methods
    //

    /**
     * Get the number of columns to use in the layout.
     * <p>
     * Nested Metawidgets are always just single column.
     */

    private int getEffectiveNumberOfColumns(Widget container) {

        if (container.getParent() instanceof FlexTable
                && container.getParent().getParent() instanceof GwtMetawidget) {
            return 1;
        }

        return mNumberOfColumns;
    }

    private State getState(Widget container, GwtMetawidget metawidget) {

        @SuppressWarnings("unchecked")
        Map<Widget, State> stateMap = (Map<Widget, State>) metawidget.getClientProperty(getClass());

        if (stateMap == null) {
            stateMap = new HashMap<Widget, State>();
            metawidget.putClientProperty(getClass(), stateMap);
        }

        State state = stateMap.get(container);

        if (state == null) {
            state = new State();
            stateMap.put(container, state);
        }

        return state;
    }

    //
    // Inner class
    //

    /**
     * Simple, lightweight structure for saving state.
     */

    /* package private */static class State {

        /* package private */FlexCellFormatter formatter;

        /* package private */int currentColumn;
    }
}