Package gwt.mosaic.client.ui

Source Code of gwt.mosaic.client.ui.Grid

package gwt.mosaic.client.ui;

import gwt.mosaic.client.style.BoxModel;

import java.util.ArrayList;

import com.google.gwt.dom.client.Style.Visibility;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.Widget;

/**
* Container that arranges widgets in a two-dimensional grid, optionally
* spanning multiple rows and columns, much like an HTML <tt>&lt;table&gt;</tt>
* element.
* <p>
* Note that unlike a HTML <tt>&lt;table&gt;</tt>, widgets that span multiple
* rows or columns will not "push" other widgets out of their way. Instead, the
* spanning widgets will simply overlay the cells into which they span. This
* means that application developers may have to use filler cells in the cells
* that are spanned.
*/
public class Grid extends LayoutPanel {

  /**
   * Represents a table row.
   */
  public static class Row extends Layer {
    public Row() {
      Element elem = getElement();
      DOM.setStyleAttribute(elem, "margin", "0px");
      DOM.setStyleAttribute(elem, "border", "0px none");
      DOM.setStyleAttribute(elem, "padding", "0px");

      // !!! Very important in order to support rowSpan
      DOM.setStyleAttribute(elem, "overflow", "visible");
    }

    @Override
    protected void setWidgetPositionImpl(Widget w, int left, int top) {
      Element h = w.getElement();
      // if (left == -1 && top == -1) {
      // changeToStaticPositioning(h);
      // } else {
      DOM.setStyleAttribute(h, "position", "absolute");
      DOM.setStyleAttribute(h, "left", left + "px");
      DOM.setStyleAttribute(h, "top", top + "px");
      // }
    }

    public void setPreferredHeight(String preferredHeight) {
      WidgetHelper.setPreferredHeight(this, preferredHeight);
    }

    public int getWeight() {
      return WidgetHelper.getWeight(this);
    }

    public void setWeight(int weight) {
      WidgetHelper.setWeight(this, weight);
    }

    public boolean isRelative() {
      return WidgetHelper.getWeight(this) > 0;
    }
  }

  public static class Column extends Widget {
    private boolean highlighted;

    public Column() {
      setElement(DOM.createDiv());
    }

    public void setPreferredWidth(String preferredWidth) {
      WidgetHelper.setPreferredWidth(this, preferredWidth);
    }

    public int getWeight() {
      return WidgetHelper.getWeight(this);
    }

    public void setWeight(int weight) {
      WidgetHelper.setWeight(this, weight);
    }

    public boolean isRelative() {
      return WidgetHelper.getWeight(this) > 0;
    }

    public boolean isHighlighted() {
      return highlighted;
    }

    public void setHighlighted(boolean highlighted) {
      this.highlighted = highlighted;
    }
  }

  private final ArrayList<Grid.Row> rows = new ArrayList<Grid.Row>();
  private final ArrayList<Grid.Column> columns = new ArrayList<Grid.Column>();

  // skin --------------------------------------------------------------------

  private int horizontalSpacing = 0;
  private int verticalSpacing = 0;

  // -------------------------------------------------------------------------

  private transient int[] columnWidths = null;
  private transient int[] rowHeights = null;

  public Grid() {
    // do nothing
  }

  /**
   * Gets the widget at the specified cell in this grid pane.
   *
   * @param rowIndex
   *            The row index of the cell
   * @param columnIndex
   *            The column index of the cell
   * @return The widget in the specified cell, or <tt>null</tt> if the cell is
   *         empty
   */
  public Widget getCellWidget(int rowIndex, int columnIndex) {
    Grid.Row row = (Grid.Row) getWidget(rowIndex);

    Widget widget = null;

    if (row.getWidgetCount() > columnIndex) {
      widget = row.getWidget(columnIndex);
    }

    return widget;
  }

  @Override
  public void add(Widget w) {
    if (!(w instanceof Grid.Row || w instanceof Grid.Column)) {
      throw new IllegalArgumentException(
          "widget is not a Table.Row or Table.Column");
    }
    if (w instanceof Grid.Column) {
      w.getElement().getStyle().setVisibility(Visibility.HIDDEN);
      columns.add((Grid.Column) w);
    } else {
      rows.add((Grid.Row) w);
    }
    super.add(w);
  }

  @Override
  public void insert(Widget w, int beforeIndex) {
    throw new UnsupportedOperationException(
        "use Table.add(Widget w) instead");
  }

  @Override
  public int getPreferredWidth(int clientHeight) {

    if (clientHeight == -1) {
      // First, check if preferredWidth is cached
      if (isValid() && (preferredWidth != -1)) {
        return preferredWidth;
      }

      // then check if there is a fixed preferredWidth hint stored in
      // layoutData
      LayoutData layoutData = WidgetHelper.getLayoutData(this);
      if (layoutData.getPreferredWidth() != null) {
        return WidgetHelper.getPreferredWidthImpl(this, clientHeight);
      }
    }

    int rowCount = rows.size();
    int columnCount = columns.size();

    int[] columnWidths = new int[columnCount];
    int[] relativeWeights = new int[columnCount];
    boolean[] defaultWidthColumns = new boolean[columnCount];

    int totalRelativeWeight = 0;

    BoxModel boxModel = WidgetHelper.getBoxModel(this);

    // First, we calculate the base widths of the columns, giving relative
    // columns their preferred width

    for (int i = 0; i < columnCount; i++) {
      Grid.Column column = columns.get(i);
      boolean isRelative = column.isRelative();

      LayoutData layoutData = WidgetHelper.getLayoutData(column);
      String widthHint = layoutData.getPreferredWidth();

      defaultWidthColumns[i] = (widthHint == null);

      if (isRelative) {
        relativeWeights[i] = column.getWeight();
        totalRelativeWeight += relativeWeights[i];
      }

      int columnWidth;

      if (widthHint == null || isRelative) {
        columnWidth = getPreferredColumnWidth(i);
      } else {
        columnWidth = WidgetHelper.getPreferredWidth(column);
      }

      columnWidths[i] = columnWidth;
    }

    // Next, we adjust the widths of the relative columns upwards where
    // necessary to reconcile their widths relative to one another while
    // ensuring that they still get at least their preferred width

    if (totalRelativeWeight > 0) {
      int totalRelativeWidth = 0;

      // Calculate the total relative width after the required upward
      // adjustments

      for (int i = 0; i < columnCount; i++) {
        int columnWidth = columnWidths[i];
        int relativeWeight = relativeWeights[i];

        if (relativeWeight > 0) {
          float weightPercentage = relativeWeight
              / (float) totalRelativeWeight;
          totalRelativeWidth = Math.max(totalRelativeWidth,
              (int) (columnWidth / weightPercentage));
        }
      }

      // Perform the upward adjustments using the total relative width

      for (int i = 0; i < columnCount; i++) {
        int relativeWeight = relativeWeights[i];

        if (relativeWeight > 0) {
          float weightPercentage = relativeWeight
              / (float) totalRelativeWeight;
          columnWidths[i] = (int) (weightPercentage * totalRelativeWidth);
        }
      }
    }

    // Finally, we account for spanning cells, which have been ignored thus
    // far. If any spanned cell is default-width (including relative width
    // columns), then we ensure that the sum of the widths of the spanned
    // cells is enough to satisfy the preferred width of the spanning
    // content

    for (int i = 0; i < rowCount; i++) {
      Grid.Row row = rows.get(i);

      for (int j = 0, n = row.getWidgetCount(); j < n && j < columnCount; j++) {
        Widget widget = row.getWidget(j);

        if (widget != null && widget.isVisible()) {
          int columnSpan = WidgetHelper.getColumnSpan(widget);

          if (columnSpan > 1) {
            // We might need to adjust column widths to accommodate
            // this spanning cell. First, we find out if any of the
            // spanned cells are default width and how much space
            // we've allocated thus far for those cells

            int spannedDefaultWidthCellCount = 0;
            int spannedRelativeWeight = 0;
            int spannedWidth = 0;

            for (int k = 0; k < columnSpan && j + k < columnCount; k++) {
              if (defaultWidthColumns[j + k]) {
                spannedDefaultWidthCellCount++;
              }

              spannedRelativeWeight += relativeWeights[j + k];
              spannedWidth += columnWidths[j + k];
            }

            if (spannedRelativeWeight > 0
                || spannedDefaultWidthCellCount > 0) {
              int rowHeight = row.isRelative() ? -1
                  : WidgetHelper.getPreferredHeight(row);
              int widgetPreferredWidth = WidgetHelper
                  .getPreferredWidth(widget, rowHeight);

              if (widgetPreferredWidth > spannedWidth) {
                // The widget's preferred width is larger than
                // the width we've allocated thus far, so an
                // adjustment is necessary
                int adjustment = widgetPreferredWidth
                    - spannedWidth;

                if (spannedRelativeWeight > 0) {
                  // We'll distribute the adjustment across
                  // the spanned relative columns and adjust
                  // other relative column widths to keep all
                  // relative column widths reconciled
                  float unitAdjustment = adjustment
                      / (float) spannedRelativeWeight;

                  for (int k = 0; k < columnCount; k++) {
                    int relativeWeight = relativeWeights[k];

                    if (relativeWeight > 0) {
                      int columnAdjustment = Math
                          .round(unitAdjustment
                              * relativeWeight);

                      columnWidths[k] += columnAdjustment;
                    }
                  }
                } else {
                  // We'll distribute the adjustment evenly
                  // among the default-width columns
                  for (int k = 0; k < columnSpan
                      && j + k < columnCount; k++) {
                    if (defaultWidthColumns[j + k]) {
                      int columnAdjustment = adjustment
                          / spannedDefaultWidthCellCount;

                      columnWidths[j + k] += columnAdjustment;

                      // Adjust these to avoid rounding
                      // errors
                      adjustment -= columnAdjustment;
                      spannedDefaultWidthCellCount--;
                    }
                  }
                }
              }
            }
          }
        }
      }
    }

    // The preferred width of the table is the sum of the column widths,
    // plus margin+border+padding and spacing

    boolean[][] occupiedCells = getOccupiedCells();
    int visibleColumnCount = 0;

    int preferredWidth = boxModel.getWidthContribution();

    for (int j = 0; j < columnCount; j++) {
      boolean columnVisible = false;

      for (int i = 0; i < rowCount; i++) {
        if (occupiedCells[i][j]) {
          columnVisible = true;
          break;
        }
      }

      if (columnVisible) {
        preferredWidth += columnWidths[j];
        visibleColumnCount++;
      }
    }

    if (visibleColumnCount > 1) {
      preferredWidth += (visibleColumnCount - 1) * horizontalSpacing;
    }

    return (this.preferredWidth = preferredWidth);
  }

  @Override
  public int getPreferredHeight(int clientWidth) {

    if (clientWidth == -1) {
      // First, check if preferredHeight is cached
      if ((clientWidth == -1) && isValid() && (preferredHeight != -1)) {
        return preferredHeight;
      }

      // then check if there is a fixed preferredHeight hint stored in
      // layoutData
      LayoutData layoutData = WidgetHelper.getLayoutData(this);
      if (layoutData.getPreferredHeight() != null) {
        return WidgetHelper.getPreferredHeightImpl(this, clientWidth);
      }
    }

    int rowCount = rows.size();
    int columnCount = columns.size();

    int[] rowHeights = new int[rowCount];
    int[] relativeWeights = new int[rowCount];
    boolean[] defaultHeightRows = new boolean[rowCount];

    int totalRelativeWeight = 0;

    BoxModel boxModel = WidgetHelper.getBoxModel(this);

    if (clientWidth < 0) {
      clientWidth = getPreferredWidth(-1);
    }

    int[] columnWidths = getColumnWidths(clientWidth);

    // First, we calculate the base heights of the rows, giving relative
    // rows their preferred height

    for (int i = 0; i < rowCount; i++) {
      Grid.Row row = rows.get(i);
      boolean isRelative = row.isRelative();

      LayoutData layoutData = WidgetHelper.getLayoutData(row);
      String heightHint = layoutData.getPreferredHeight();

      defaultHeightRows[i] = (heightHint == null);

      if (isRelative) {
        relativeWeights[i] = row.getWeight();
        totalRelativeWeight += relativeWeights[i];
      }

      int rowHeight;

      if (layoutData.getPreferredHeight() == null || isRelative) {
        rowHeight = getPreferredRowHeight(i, columnWidths);
      } else {
        rowHeight = WidgetHelper.getPreferredHeight(row);
      }

      rowHeights[i] = rowHeight;
    }

    // Next, we adjust the heights of the relative rows upwards where
    // necessary to reconcile their heights relative to one another while
    // ensuring that they still get at least their preferred height

    if (totalRelativeWeight > 0) {
      int totalRelativeHeight = 0;

      // Calculate the total relative height after the required upward
      // adjustments

      for (int i = 0; i < rowCount; i++) {
        int rowHeight = rowHeights[i];
        int relativeWeight = relativeWeights[i];

        if (relativeWeight > 0) {
          float weightPercentage = relativeWeight
              / (float) totalRelativeWeight;
          totalRelativeHeight = Math.max(totalRelativeHeight,
              (int) (rowHeight / weightPercentage));
        }
      }

      // Perform the upward adjustments using the total relative height

      for (int i = 0; i < rowCount; i++) {
        int relativeWeight = relativeWeights[i];

        if (relativeWeight > 0) {
          float weightPercentage = relativeWeight
              / (float) totalRelativeWeight;
          rowHeights[i] = (int) (weightPercentage * totalRelativeHeight);
        }
      }
    }

    // Finally, we account for spanning cells, which have been ignored thus
    // far. If any spanned cell is default-height (including relative height
    // rows), then we ensure that the sum of the heights of the spanned
    // cells is enough to satisfy the preferred height of the spanning
    // content

    for (int i = 0; i < rowCount; i++) {
      Grid.Row row = rows.get(i);

      for (int j = 0, n = row.getWidgetCount(); j < n && j < columnCount; j++) {
        Widget widget = row.getWidget(j);

        if (widget != null && widget.isVisible()) {
          int rowSpan = WidgetHelper.getRowSpan(widget);

          if (rowSpan > 1) {
            // We might need to adjust row heights to accomodate
            // this spanning cell. First, we find out if any of the
            // spanned cells are default height and how much space
            // we've allocated thus far for those cells

            int spannedDefaultHeightCellCount = 0;
            int spannedRelativeWeight = 0;
            int spannedHeight = 0;

            for (int k = 0; k < rowSpan && i + k < rowCount; k++) {
              if (defaultHeightRows[i + k]) {
                spannedDefaultHeightCellCount++;
              }

              spannedRelativeWeight += relativeWeights[i + k];
              spannedHeight += rowHeights[i + k];
            }

            if (spannedRelativeWeight > 0
                || spannedDefaultHeightCellCount > 0) {
              int widgetPreferredHeight = WidgetHelper
                  .getPreferredHeight(widget, columnWidths[j]);

              if (widgetPreferredHeight > spannedHeight) {
                // The widget's preferred height is larger
                // than the height we've allocated thus far, so
                // an adjustment is necessary
                int adjustment = widgetPreferredHeight
                    - spannedHeight;

                if (spannedRelativeWeight > 0) {
                  // We'll distribute the adjustment across
                  // the spanned relative rows and adjust
                  // other relative row heights to keep all
                  // relative row heights reconciled
                  float unitAdjustment = adjustment
                      / (float) spannedRelativeWeight;

                  for (int k = 0; k < rowCount; k++) {
                    int relativeWeight = relativeWeights[k];

                    if (relativeWeight > 0) {
                      int rowAdjustment = Math
                          .round(unitAdjustment
                              * relativeWeight);

                      rowHeights[k] += rowAdjustment;
                    }
                  }
                } else {
                  // We'll distribute the adjustment evenly
                  // among the default-height rows
                  for (int k = 0; k < rowSpan
                      && i + k < rowCount; k++) {
                    if (defaultHeightRows[i + k]) {
                      int rowAdjustment = adjustment
                          / spannedDefaultHeightCellCount;

                      rowHeights[i + k] += rowAdjustment;

                      // Adjust these to avoid rounding
                      // errors
                      adjustment -= rowAdjustment;
                      spannedDefaultHeightCellCount--;
                    }
                  }
                }
              }
            }
          }
        }
      }
    }

    // The preferred height of the table pane is the sum of the row
    // heights, plus padding and spacing

    boolean[][] occupiedCells = getOccupiedCells();
    int visibleRowCount = 0;

    int preferredHeight = boxModel.getHeightContribution();

    for (int i = 0; i < rowCount; i++) {
      boolean rowVisible = false;

      for (int j = 0; j < columnCount; j++) {
        if (occupiedCells[i][j]) {
          rowVisible = true;
          break;
        }
      }

      if (rowVisible) {
        preferredHeight += rowHeights[i];
        visibleRowCount++;
      }
    }

    if (visibleRowCount > 1) {
      preferredHeight += (visibleRowCount - 1) * verticalSpacing;
    }

    return (this.preferredHeight = preferredHeight);
  }

  @Override
  public Dimensions getPreferredSize() {

    // Check if preferred size is cached
    if (isValid() && (preferredSize != null)) {
      return preferredSize;
    }

    // TODO Optimize by performing calculations here
    int preferredWidth = getPreferredWidth(-1);
    int preferredHeight = getPreferredHeight(preferredWidth);
    return (this.preferredSize = new Dimensions(preferredWidth,
        preferredHeight));
  }

  @Override
  public int getBaseline(int width, int height) {
    int rowCount = rows.size();
    int columnCount = columns.size();

    int[] columnWidths = getColumnWidths(width);
    int[] rowHeights = getRowHeights(height, columnWidths);
    boolean[][] occupiedCells = getOccupiedCells();

    int baseline = -1;

    BoxModel boxModel = WidgetHelper.getBoxModel(this);

    int rowY = boxModel.getPadding().top; // XXX

    for (int i = 0; i < rowCount && baseline == -1; i++) {
      Grid.Row row = rows.get(i);
      boolean rowVisible = false;

      for (int j = 0, n = row.getWidgetCount(); j < n && j < columnCount
          && baseline == -1; j++) {
        Widget widget = row.getWidget(j);

        if (widget != null && widget.isVisible()) {
          int columnSpan = Math
              .min(WidgetHelper.getColumnSpan(widget),
                  columnCount - j);
          int widgetWidth = (columnSpan - 1) * horizontalSpacing;
          for (int k = 0; k < columnSpan && j + k < columnCount; k++) {
            widgetWidth += columnWidths[j + k];
          }

          int rowSpan = Math.min(WidgetHelper.getRowSpan(widget),
              rowCount - i);
          int widgetHeight = (rowSpan - 1) * verticalSpacing;
          for (int k = 0; k < rowSpan && i + k < rowCount; k++) {
            widgetHeight += rowHeights[i + k];
          }

          baseline = WidgetHelper
              .getBaseline(widget, Math.max(widgetWidth, 0),
                  Math.max(widgetHeight, 0));

          if (baseline != -1) {
            baseline += rowY;
          }
        }

        rowVisible |= occupiedCells[i][j];
      }

      if (rowVisible) {
        rowY += (rowHeights[i] + verticalSpacing);
      }
    }

    return baseline;
  }

  @Override
  protected void doLayout() {
    int rowCount = rows.size();
    int columnCount = columns.size();

    int width = getElement().getClientWidth();
    int height = getElement().getClientHeight();

    BoxModel boxModel = WidgetHelper.getBoxModel(this);

    // NOTE We cache column widths and row heights to make getColumnAt()
    // and getRowAt() more efficient
    columnWidths = getColumnWidths(width);
    rowHeights = getRowHeights(height, columnWidths);

    // Determine which rows and column should be visible so we know which
    // ones should be collapsed
    boolean[] visibleRows = new boolean[rowCount];
    boolean[] visibleColumns = new boolean[columnCount];

    boolean[][] occupiedCells = getOccupiedCells();
    for (int i = 0; i < rowCount; i++) {
      for (int j = 0; j < columnCount; j++) {
        if (occupiedCells[i][j]) {
          visibleRows[i] = true;
          visibleColumns[j] = true;
        }
      }
    }

    int widgetY = boxModel.getPadding().top;
    for (int i = 0; i < rowCount; i++) {
      Grid.Row row = rows.get(i);

      int widgetX = boxModel.getPadding().left;

      WidgetHelper.setLocation(row, 0, 0);
      WidgetHelper.setSize(row, width, 1);

      for (int j = 0, n = row.getWidgetCount(); j < n && j < columnCount; j++) {
        Widget child = row.getWidget(j);

        if (child != null && child.isVisible()) {
          WidgetHelper.setLocation(child, widgetX, widgetY);

          int columnSpan = WidgetHelper.getColumnSpan(child);
          columnSpan = Math.min(columnSpan, columnCount - j);
          int childWidth = (columnSpan - 1) * horizontalSpacing;
          for (int k = 0; k < columnSpan && j + k < columnCount; k++) {
            childWidth += columnWidths[j + k];
          }

          int rowSpan = WidgetHelper.getRowSpan(child);
          rowSpan = Math.min(rowSpan, rowCount - i);
          int childHeight = (rowSpan - 1) * verticalSpacing;
          for (int k = 0; k < rowSpan && i + k < rowCount; k++) {
            childHeight += rowHeights[i + k];
          }

          // Set the component's size
          WidgetHelper.setSize(child, Math.max(childWidth, 0),
              Math.max(childHeight, 0));
        }

        if (visibleColumns[j]) {
          widgetX += (columnWidths[j] + horizontalSpacing);
        }
      }

      if (visibleRows[i]) {
        widgetY += (rowHeights[i] + verticalSpacing);
      }
    }
  }

  /**
   * Returns a grid indicating which cells are occupied. A widget is said to
   * occupy a cell if it is visible and either lives in the cell directly or
   * spans the cell. Conversely, vacant cells do not have visible widgets
   * within them or spanning them.
   *
   * @return A grid of booleans, where occupied cells are denoted by
   *         <tt>true</tt>, and vacant cells are denoted by <tt>false</tt>
   */
  private boolean[][] getOccupiedCells() {
    int rowCount = rows.size();
    int columnCount = columns.size();

    boolean[][] occupiedCells = new boolean[rowCount][columnCount];

    for (int i = 0; i < rowCount; i++) {
      Grid.Row row = rows.get(i);

      for (int j = 0, n = row.getWidgetCount(); j < n && j < columnCount; j++) {
        Widget widget = row.getWidget(j);

        if (widget != null && widget.isVisible()) {

          int rowSpan = WidgetHelper.getRowSpan(widget);
          int columnSpan = WidgetHelper.getColumnSpan(widget);

          for (int k = 0; k < rowSpan && i + k < rowCount; k++) {
            for (int l = 0; l < columnSpan && j + l < columnCount; l++) {
              occupiedCells[i + k][j + l] = true;
            }
          }
        }
      }
    }

    return occupiedCells;
  }

  /**
   * Gets the preferred width of a table column, which is defined as the
   * maximum preferred width of the column's visible widgets.
   * <p>
   * Because their preferred width relates to the preferred widths of other
   * columns, widgets that span multiple columns will not considered in this
   * calculation (even if they live in the column directly). It is up to the
   * caller to factor such widgets into the column widths calculation.
   *
   * @param columnIndex
   *            The index of the column whose preferred width we're
   *            calculating
   * @return The width of a table column defined as the maximum preferred
   *         width of the column's visible widgets
   */
  private int getPreferredColumnWidth(int columnIndex) {
    int preferredWidth = 0;

    for (int i = 0, n = rows.size(); i < n; i++) {
      Grid.Row row = rows.get(i);

      if (row.getWidgetCount() > columnIndex) {
        Widget widget = row.getWidget(columnIndex);

        if (widget != null && widget.isVisible()
            && WidgetHelper.getColumnSpan(widget) == 1) {
          preferredWidth = Math.max(preferredWidth,
              WidgetHelper.getPreferredWidth(widget));
        }
      }
    }

    return preferredWidth;
  }

  /**
   * Tells whether or not the specified column is visible. A column is visible
   * if and only if one or more visible components occupies it. A component is
   * said to occupy a cell if it either lives in the cell directly or spans
   * the cell.
   *
   * @param columnIndex
   *            The index of the column within the table pane
   *
   * @return <tt>true</tt> if the column is visible; <tt>false</tt> otherwise
   */
  private boolean isColumnVisible(int columnIndex) {
    boolean visible = false;

    boolean[][] occupiedCells = getOccupiedCells();

    for (int i = 0; i < occupiedCells.length; i++) {
      if (occupiedCells[i][columnIndex]) {
        visible = true;
        break;
      }
    }

    return visible;
  }

  /**
   * Gets the preferred height of a table pane row, which is defined as the
   * maximum preferred height of the row's visible components. The preferred
   * height of each constituent component will be constrained by the width of
   * the column that the component occupies (as specified in the array of
   * column widths).
   * <p>
   * Because their preferred height relates to the preferred heights of other
   * rows, components that span multiple rows will not be considered in this
   * calculation (even if they live in the column directly). It is up to the
   * caller to factor such components into the row heights calculation.
   *
   * @param rowIndex
   *            The index of the row whose preferred height we're calculating
   *
   * @param columnWidths
   *            An array of column width values corresponding to the columns
   *            of the table pane
   */
  private int getPreferredRowHeight(int rowIndex, int[] columnWidths) {
    if (columnWidths == null) {
      throw new IllegalArgumentException("columnWidths is null");
    }

    Grid.Row row = rows.get(rowIndex);

    int preferredHeight = 0;

    for (int j = 0, n = row.getWidgetCount(), m = columns.size(); j < n
        && j < m; j++) {
      Widget widget = row.getWidget(j);

      if (widget != null && widget.isVisible()
          && WidgetHelper.getRowSpan(widget) == 1) {
        preferredHeight = Math.max(preferredHeight, WidgetHelper
            .getPreferredHeight(widget, columnWidths[j]));
      }
    }

    return preferredHeight;
  }

  /**
   * Tells whether or not the specified row is visible. A row is visible if
   * and only if one or more visible components occupies it. A component is
   * said to occupy a cell if it either lives in the cell directly or spans
   * the cell.
   *
   * @param rowIndex
   *            The index of the row within the table pane
   *
   * @return <tt>true</tt> if the row is visible; <tt>false</tt> otherwise
   */
  private boolean isRowVisible(int rowIndex) {
    boolean visible = false;

    boolean[][] occupiedCells = getOccupiedCells();

    for (int j = 0; j < occupiedCells[rowIndex].length; j++) {
      if (occupiedCells[rowIndex][j]) {
        visible = true;
        break;
      }
    }

    return visible;
  }

  /**
   * Gets the width of each table pane column given the specified table pane
   * width.
   *
   * @param width
   *            The width constraint of the table pane
   *
   * @return An array containing the width of each column in the table pane
   *         given the specified constraint
   */
  private int[] getColumnWidths(int width) {
    int rowCount = rows.size();
    int columnCount = columns.size();

    int[] columnWidths = new int[columnCount];

    boolean[] defaultWidthColumns = new boolean[columnCount];
    int totalRelativeWeight = 0;
    int visibleColumnCount = 0;

    BoxModel boxModel = WidgetHelper.getBoxModel(this);

    int reservedWidth = boxModel.getPaddingWidthContribution();

    // First, we allocate the widths of non-relative columns. We store the
    // widths of relative columns as negative values for later processing

    for (int j = 0; j < columnCount; j++) {
      if (isColumnVisible(j)) {
        Grid.Column column = columns.get(j);

        LayoutData layoutData = WidgetHelper.getLayoutData(column);
        String widthHint = layoutData.getPreferredWidth();

        if (column.isRelative()) {
          // XXX columnWidths[j] = -columnWidth;
          columnWidths[j] = -column.getWeight();
          totalRelativeWeight += column.getWeight();
        } else {
          int columnWidth;

          if (widthHint == null) {
            // Default width column; we must calculate the width
            columnWidth = getPreferredColumnWidth(j);
            defaultWidthColumns[j] = true;
          } else {
            columnWidth = WidgetHelper.getPreferredWidth(column);
          }

          columnWidths[j] = columnWidth;
          reservedWidth += columnWidth;
        }

        visibleColumnCount++;
      } else {
        columnWidths[j] = 0;
      }
    }

    if (visibleColumnCount > 1) {
      reservedWidth += (visibleColumnCount - 1) * horizontalSpacing;
    }

    // Next, we we account for default-width columns containing spanning
    // cells, which have been ignored thus far. We ensure that the sum of
    // the widths of the spanned cells is enough to satisfy the preferred
    // width of the spanning content.

    for (int i = 0; i < rowCount; i++) {
      Grid.Row row = rows.get(i);

      for (int j = 0, n = row.getWidgetCount(); j < n && j < columnCount; j++) {
        Widget component = row.getWidget(j);

        if (component != null && component.isVisible()) {
          int columnSpan = WidgetHelper.getColumnSpan(component);

          if (columnSpan > 1) {
            // We might need to adjust column widths to accommodate
            // this spanning cell. First, we find out if any of the
            // spanned cells are default width and how much space
            // we've allocated thus far for those cells

            boolean adjustCells = true;
            int spannedDefaultWidthCellCount = 0;
            int spannedWidth = 0;

            for (int k = 0; k < columnSpan && j + k < columnCount; k++) {
              if (columnWidths[j + k] < 0) {
                adjustCells = false;
                break;
              }

              if (defaultWidthColumns[j + k]) {
                spannedDefaultWidthCellCount++;
              }

              spannedWidth += columnWidths[j + k];
            }

            // If we span any relative-width columns, we assume
            // that we'll achieve the desired spanning width when
            // we dive up the remaining space, so there's no need
            // to make an adjustment here. This assumption is safe
            // because our preferred width policy is to *either*
            // divide the adjustment among the relative-width
            // columns *or* among the default-width columns if we
            // don't span any relative-width columns

            if (adjustCells && spannedDefaultWidthCellCount > 0) {
              int componentPreferredWidth = WidgetHelper
                  .getPreferredWidth(component);

              if (componentPreferredWidth > spannedWidth) {
                // The component's preferred width is larger
                // than the width we've allocated thus far, so
                // an adjustment is necessary
                int adjustment = componentPreferredWidth
                    - spannedWidth;

                // We'll distribute the adjustment evenly
                // among the default-width columns
                for (int k = 0; k < columnSpan
                    && j + k < columnCount; k++) {
                  if (defaultWidthColumns[j + k]) {
                    int columnAdjustment = adjustment
                        / spannedDefaultWidthCellCount;

                    columnWidths[j + k] += columnAdjustment;
                    reservedWidth += columnAdjustment;

                    // Adjust these to avoid rounding errors
                    adjustment -= columnAdjustment;
                    spannedDefaultWidthCellCount--;
                  }
                }
              }
            }
          }
        }
      }
    }

    // Finally, we allocate the widths of the relative columns by divvying
    // up the remaining width

    int remainingWidth = Math.max(width - reservedWidth, 0);
    if (totalRelativeWeight > 0 && remainingWidth > 0) {
      for (int j = 0; j < columnCount; j++) {
        if (columnWidths[j] < 0) {
          int relativeWeight = -columnWidths[j];
          float weightPercentage = relativeWeight
              / (float) totalRelativeWeight;
          int columnWidth = (int) (remainingWidth * weightPercentage);

          columnWidths[j] = columnWidth;

          // NOTE we adjust remainingWidth and totalRelativeWeight as
          // we go to avoid potential rounding errors in the
          // columnWidth calculation
          remainingWidth -= columnWidth;
          totalRelativeWeight -= relativeWeight;
        }
      }
    }

    return columnWidths;
  }

  /**
   * Gets the height of each row of a table pane given the specified
   * constraints.
   *
   * @param height
   *            The height constraint of the table pane
   *
   * @param columnWidths
   *            The widths of the table pane's columns, which will be used as
   *            width constraints to the row heights when necessary, or
   *            <tt>null</tt> if the column widths are not yet known (the row
   *            heights will be unconstrained)
   *
   * @return An array containing the height of each row in the table pane
   *         given the specified constraints
   */
  private int[] getRowHeights(int height, int[] columnWidths) {
    if (columnWidths == null) {
      throw new IllegalArgumentException("columnWidths is null");
    }

    int rowCount = rows.size();
    int columnCount = columns.size();

    int rowHeights[] = new int[rowCount];

    boolean[] defaultHeightRows = new boolean[rowCount];
    int totalRelativeWeight = 0;
    int visibleRowCount = 0;

    BoxModel boxModel = WidgetHelper.getBoxModel(this);

    int reservedHeight = boxModel.getPaddingHeightContribution();

    // First, we allocate the heights of non-relative rows. We store the
    // heights of relative rows as negative values for later processing

    for (int i = 0; i < rowCount; i++) {
      if (isRowVisible(i)) {
        Grid.Row row = rows.get(i);

        LayoutData layoutData = WidgetHelper.getLayoutData(row);
        String heightHint = layoutData.getPreferredHeight();

        if (row.isRelative()) {
          // XXX rowHeights[i] = -rowHeight;
          rowHeights[i] = -row.getWeight();
          totalRelativeWeight += row.getWeight();
        } else {
          int rowHeight;

          if (heightHint == null) {
            // Default height row; we must calculate the height
            rowHeight = getPreferredRowHeight(i, columnWidths);
            defaultHeightRows[i] = true;
          } else {
            rowHeight = WidgetHelper.getPreferredHeight(row);
          }

          rowHeights[i] = rowHeight;
          reservedHeight += rowHeight;
        }

        visibleRowCount++;
      } else {
        rowHeights[i] = 0;
      }
    }

    if (visibleRowCount > 1) {
      reservedHeight += (visibleRowCount - 1) * verticalSpacing;
    }

    // Next, we we account for default-width columns containing spanning
    // cells, which have been ignored thus far. We ensure that the sum of
    // the widths of the spanned cells is enough to satisfy the preferred
    // width of the spanning content.

    for (int i = 0; i < rowCount; i++) {
      Grid.Row row = rows.get(i);

      for (int j = 0, n = row.getWidgetCount(); j < n && j < columnCount; j++) {
        Widget component = row.getWidget(j);

        if (component != null && component.isVisible()) {
          int rowSpan = WidgetHelper.getRowSpan(component);

          if (rowSpan > 1) {
            // We might need to adjust row heights to accomodate
            // this spanning cell. First, we find out if any of the
            // spanned cells are default height and how much space
            // we've allocated thus far for those cells

            boolean adjustCells = true;
            int spannedDefaultHeightCellCount = 0;
            int spannedHeight = 0;

            for (int k = 0; k < rowSpan && i + k < rowCount; k++) {
              if (rowHeights[i + k] < 0) {
                adjustCells = false;
                break;
              }

              if (defaultHeightRows[i + k]) {
                spannedDefaultHeightCellCount++;
              }

              spannedHeight += rowHeights[i + k];
            }

            // If we span any relative-height rows, we assume
            // that we'll achieve the desired spanning height when
            // we divvy up the remaining space, so there's no need
            // to make an adjustment here. This assumption is safe
            // because our preferred height policy is to *either*
            // divide the adjustment among the relative-height
            // rows *or* among the default-height rows if we
            // don't span any relative-height rows

            if (adjustCells && spannedDefaultHeightCellCount > 0) {
              int componentPreferredHeight = WidgetHelper
                  .getPreferredHeight(component,
                      columnWidths[j]);

              if (componentPreferredHeight > spannedHeight) {
                // The component's preferred height is larger
                // than the height we've allocated thus far, so
                // an adjustment is necessary
                int adjustment = componentPreferredHeight
                    - spannedHeight;

                // We'll distribute the adjustment evenly
                // among the default-height rows
                for (int k = 0; k < rowSpan && i + k < rowCount; k++) {
                  if (defaultHeightRows[i + k]) {
                    int rowAdjustment = adjustment
                        / spannedDefaultHeightCellCount;

                    rowHeights[i + k] += rowAdjustment;
                    reservedHeight += rowAdjustment;

                    // Adjust these to avoid rounding errors
                    adjustment -= rowAdjustment;
                    spannedDefaultHeightCellCount--;
                  }
                }
              }
            }
          }
        }
      }
    }

    // Finally, we allocate the heights of the relative rows by diving
    // up the remaining height

    int remainingHeight = Math.max(height - reservedHeight, 0);
    if (totalRelativeWeight > 0 && remainingHeight > 0) {
      for (int i = 0; i < rowCount; i++) {
        if (rowHeights[i] < 0) {
          int relativeWeight = -rowHeights[i];
          float weightPercentage = relativeWeight
              / (float) totalRelativeWeight;
          int rowHeight = (int) (remainingHeight * weightPercentage);

          rowHeights[i] = rowHeight;

          // NOTE we adjust remainingHeight and totalRelativeWeight as
          // we go to avoid potential rounding errors in the rowHeight
          // calculation
          remainingHeight -= rowHeight;
          totalRelativeWeight -= relativeWeight;
        }
      }
    }

    return rowHeights;
  }

}
TOP

Related Classes of gwt.mosaic.client.ui.Grid

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.