/* * 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. */ package com.google.gwt.gen2.table.client; import com.google.gwt.gen2.table.client.FixedWidthTableImpl.IdealColumnWidthInfo; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.Widget; import java.util.HashMap; import java.util.Map; /** * A variation of the {@link com.google.gwt.gen2.table.override.client.Grid} * that resizes columns using a fixed table width. */ public class FixedWidthGrid extends SortableGrid { /** * This class contains methods used to format a table's cells. */ public class FixedWidthGridCellFormatter extends SelectionGridCellFormatter { @Override public void setWidth(int row, int column, String width) { throw new UnsupportedOperationException("setWidth is not supported. " + "Use FixedWidthGrid.setColumnWidth(int, int) instead."); } @Override protected Element getRawElement(int row, int column) { return super.getRawElement(row + 1, column); } } /** * This class contains methods used to format a table's columns. */ public class FixedWidthGridColumnFormatter extends ColumnFormatter { @Override public void setWidth(int column, String width) { throw new UnsupportedOperationException("setWidth is not supported. " + "Use FixedWidthdGrid.setColumnWidth(int, int) instead."); } } /** * This class contains methods used to format a table's rows. */ public class FixedWidthGridRowFormatter extends SelectionGridRowFormatter { @Override protected Element getRawElement(int row) { return super.getRawElement(row + 1); } } /** * The default width of a column in pixels. */ public static final int DEFAULT_COLUMN_WIDTH = 80; /** * The minimum width of any column. */ public static final int MIN_COLUMN_WIDTH = 1; /** * A mapping of column indexes to their widths in pixels. */ private Map<Integer, Integer> colWidths = new HashMap<Integer, Integer>(); /** * The hidden, ghost row used for sizing the columns. */ private Element ghostRow = null; /** * The ideal widths of all columns (that are available). */ private int[] idealWidths; /** * Info used to calculate ideal column width. */ private IdealColumnWidthInfo idealColumnWidthInfo; /** * Constructor. */ public FixedWidthGrid() { super(); setClearText(" "); // Setup the table element Element tableElem = getElement(); DOM.setStyleAttribute(tableElem, "tableLayout", "fixed"); DOM.setStyleAttribute(tableElem, "width", "0px"); // Replace the formatters setRowFormatter(new FixedWidthGridRowFormatter()); setCellFormatter(new FixedWidthGridCellFormatter()); setColumnFormatter(new FixedWidthGridColumnFormatter()); // Create the ghost row for sizing ghostRow = FixedWidthTableImpl.get().createGhostRow(); DOM.insertChild(getBodyElement(), ghostRow, 0); // Sink highlight and selection events sinkEvents(Event.ONMOUSEOVER | Event.ONMOUSEDOWN | Event.ONCLICK); } /** * Constructs a {@link FixedWidthGrid} with the requested size. * * @param rows the number of rows * @param columns the number of columns * @throws IndexOutOfBoundsException */ public FixedWidthGrid(int rows, int columns) { this(); resize(rows, columns); } @Override public void clear() { super.clear(); clearIdealWidths(); } /** * Return the column width for a given column index. If a width has not been * assigned, the default width is returned. * * @param column the column index * @return the column width in pixels */ public int getColumnWidth(int column) { Integer colWidth = colWidths.get(new Integer(column)); if (colWidth == null) { return DEFAULT_COLUMN_WIDTH; } else { return colWidth.intValue(); } } /** * <p> * Calculate the ideal width required to tightly wrap the specified column. If * the ideal column width cannot be calculated (eg. if the table is not * attached), -1 is returned. * </p> * <p> * Note that this method requires an expensive operation whenever the content * of the table is changed, so you should only call it after you've completely * modified the contents of your table. * </p> * * @return the ideal column width, or -1 if it is not applicable */ public int getIdealColumnWidth(int column) { maybeRecalculateIdealColumnWidths(); if (idealWidths.length > column) { return idealWidths[column]; } return -1; } @Override public boolean remove(Widget widget) { if (super.remove(widget)) { clearIdealWidths(); return true; } return false; } @Override public void removeRow(int row) { super.removeRow(row); clearIdealWidths(); } @Override public void resizeColumns(int columns) { super.resizeColumns(columns); updateGhostRow(); clearIdealWidths(); } @Override public void resizeRows(int rows) { super.resizeRows(rows); clearIdealWidths(); } @Override public void setCellPadding(int padding) { super.setCellPadding(padding); // Reset the width of all columns for (Map.Entry<Integer, Integer> entry : colWidths.entrySet()) { setColumnWidth(entry.getKey(), entry.getValue()); } if (getSelectionPolicy().hasInputColumn()) { setColumnWidthImpl(-1, getInputColumnWidth()); } } @Override public void setCellSpacing(int spacing) { super.setCellSpacing(spacing); // Reset the width of all columns for (Map.Entry<Integer, Integer> entry : colWidths.entrySet()) { setColumnWidth(entry.getKey(), entry.getValue()); } if (getSelectionPolicy().hasInputColumn()) { setColumnWidthImpl(-1, getInputColumnWidth()); } } /** * Set the width of a column. * * @param column the index of the column * @param width the width in pixels * @throws IndexOutOfBoundsException */ public void setColumnWidth(int column, int width) { // Ensure that the indices are not negative. if (column < 0) { throw new IndexOutOfBoundsException( "Cannot access a column with a negative index: " + column); } // Add the width to the map width = Math.max(MIN_COLUMN_WIDTH, width); colWidths.put(new Integer(column), new Integer(width)); // Update the cell width if possible if (column >= numColumns) { return; } // Set the actual column width setColumnWidthImpl(column, width); } @Override public void setHTML(int row, int column, String html) { super.setHTML(row, column, html); clearIdealWidths(); } @Override public void setSelectionPolicy(SelectionPolicy selectionPolicy) { // Update the input column in the ghost row if (selectionPolicy.hasInputColumn() && !getSelectionPolicy().hasInputColumn()) { // Add ghost input column Element tr = getGhostRow(); Element td = FixedWidthTableImpl.get().createGhostCell(null); tr.insertBefore(td, tr.getFirstChildElement()); super.setSelectionPolicy(selectionPolicy); setColumnWidthImpl(-1, getInputColumnWidth()); } else if (!selectionPolicy.hasInputColumn() && getSelectionPolicy().hasInputColumn()) { // Remove ghost input column Element tr = getGhostRow(); tr.removeChild(tr.getFirstChildElement()); super.setSelectionPolicy(selectionPolicy); } else { super.setSelectionPolicy(selectionPolicy); } } @Override public void setText(int row, int column, String text) { super.setText(row, column, text); clearIdealWidths(); } @Override public void setWidget(int row, int column, Widget widget) { super.setWidget(row, column, widget); clearIdealWidths(); } @Override protected int getDOMCellCount(int row) { return super.getDOMCellCount(row + 1); } @Override protected int getDOMRowCount() { return super.getDOMRowCount() - 1; } /** * Explicitly gets the {@link FixedWidthGridCellFormatter}. The results of * {@link com.google.gwt.user.client.ui.HTMLTable#getCellFormatter()} may also * be downcast to a {@link FixedWidthGridCellFormatter}. * * @return the {@link FixedWidthGrid}'s cell formatter */ protected FixedWidthGridCellFormatter getFixedWidthGridCellFormatter() { return (FixedWidthGridCellFormatter) getCellFormatter(); } /** * Explicitly gets the {@link FixedWidthGridRowFormatter}. The results of * {@link com.google.gwt.user.client.ui.HTMLTable#getCellFormatter()} may also * be downcast to a {@link FixedWidthGridRowFormatter}. * * @return the {@link FixedWidthGrid}'s cell formatter */ protected FixedWidthGridRowFormatter getFixedWidthGridRowFormatter() { return (FixedWidthGridRowFormatter) getRowFormatter(); } /** * @return the number of columns in the ghost row */ protected int getGhostColumnCount() { return super.getDOMCellCount(0); } /** * @return the ghost row element */ protected Element getGhostRow() { return ghostRow; } /** * Get the width of the input column used in the current * {@link SelectionGrid.SelectionPolicy}. * * @return the width of the input element */ protected int getInputColumnWidth() { return 30; } @Override protected int getRowIndex(Element rowElem) { int rowIndex = super.getRowIndex(rowElem); if (rowIndex < 0) { return rowIndex; } return rowIndex - 1; } @Override protected boolean internalClearCell(Element td, boolean clearInnerHTML) { clearIdealWidths(); return super.internalClearCell(td, clearInnerHTML); } @Override protected void onAttach() { super.onAttach(); clearIdealWidths(); } /** * Recalculate the ideal column widths of each column in the data table. */ protected void recalculateIdealColumnWidths() { // We need at least one cell to do any calculations int columnCount = getColumnCount(); if (!isAttached() || getRowCount() == 0 || columnCount < 1) { idealWidths = new int[0]; return; } recalculateIdealColumnWidthsSetup(); recalculateIdealColumnWidthsImpl(); recalculateIdealColumnWidthsTeardown(); } /** * Sets the ghost row variable. This does not change the underlying structure * of the table. * * @param ghostRow the new ghost row */ protected void setGhostRow(Element ghostRow) { this.ghostRow = ghostRow; } /** * Add or remove ghost cells when the table size changes. */ protected void updateGhostRow() { int numGhosts = getGhostColumnCount(); if (numColumns > numGhosts) { // Add ghosts as needed for (int i = numGhosts; i < numColumns; i++) { Element td = FixedWidthTableImpl.get().createGhostCell(null); DOM.appendChild(ghostRow, td); setColumnWidth(i, getColumnWidth(i)); } } else if (numColumns < numGhosts) { int cellsToRemove = numGhosts - numColumns; for (int i = 0; i < cellsToRemove; i++) { Element td = getGhostCellElement(numColumns); DOM.removeChild(ghostRow, td); } } } @Override void applySort(Element[] trElems) { // Move the rows to their new positions Element bodyElem = getBodyElement(); for (int i = trElems.length - 1; i >= 0; i--) { if (trElems[i] != null) { DOM.removeChild(bodyElem, trElems[i]); // Need to insert below the ghost row DOM.insertChild(bodyElem, trElems[i], 1); } } } /** * Clear the idealWidths field when the ideal widths change. */ void clearIdealWidths() { idealWidths = null; } /** * @return true if the ideal column widths have already been calculated */ boolean isIdealColumnWidthsCalculated() { return idealWidths != null; } /** * Recalculate the ideal column widths of each column in the data table. This * method assumes that the tableLayout has already been changed. */ void recalculateIdealColumnWidthsImpl() { idealWidths = FixedWidthTableImpl.get().recalculateIdealColumnWidths( idealColumnWidthInfo); } /** * Setup to recalculate column widths. */ void recalculateIdealColumnWidthsSetup() { int offset = 0; if (getSelectionPolicy().hasInputColumn()) { offset++; } idealColumnWidthInfo = FixedWidthTableImpl.get().recalculateIdealColumnWidthsSetup( this, getColumnCount(), offset); } /** * Tear down after recalculating column widths. */ void recalculateIdealColumnWidthsTeardown() { FixedWidthTableImpl.get().recalculateIdealColumnWidthsTeardown( idealColumnWidthInfo); idealColumnWidthInfo = null; } /** * Returns a cell in the ghost row. * * @param column the cell's column * @return the ghost cell */ private Element getGhostCellElement(int column) { if (getSelectionPolicy().hasInputColumn()) { column++; } return FixedWidthTableImpl.get().getGhostCell(ghostRow, column); } /** * Recalculate the ideal column widths of each column in the data table if * they have changed since the last calculation. */ private void maybeRecalculateIdealColumnWidths() { if (idealWidths == null) { recalculateIdealColumnWidths(); } } /** * Set the width of a column. * * @param column the index of the column * @param width the width in pixels */ private void setColumnWidthImpl(int column, int width) { if (getSelectionPolicy().hasInputColumn()) { column++; } FixedWidthTableImpl.get().setColumnWidth(this, ghostRow, column, width); } }