| /* |
| * Copyright 2007 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.user.client.ui; |
| |
| 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.HasHorizontalAlignment.HorizontalAlignmentConstant; |
| import com.google.gwt.user.client.ui.HasVerticalAlignment.VerticalAlignmentConstant; |
| |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.NoSuchElementException; |
| |
| /** |
| * HTMLTable contains the common table algorithms for |
| * {@link com.google.gwt.user.client.ui.Grid} and |
| * {@link com.google.gwt.user.client.ui.FlexTable}. |
| * <p> |
| * <img class='gallery' src='Table.png'/> |
| * </p> |
| */ |
| public abstract class HTMLTable extends Panel implements SourcesTableEvents { |
| /** |
| * This class contains methods used to format a table's cells. |
| */ |
| public class CellFormatter { |
| /** |
| * Adds a style to the specified cell. |
| * |
| * @param row the cell's row |
| * @param column the cell's column |
| * @param styleName the style name to be added |
| * @see UIObject#addStyleName(String) |
| */ |
| public void addStyleName(int row, int column, String styleName) { |
| prepareCell(row, column); |
| Element td = getCellElement(bodyElem, row, column); |
| UIObject.setStyleName(td, styleName, true); |
| } |
| |
| /** |
| * Gets the TD element representing the specified cell. |
| * |
| * @param row the row of the cell to be retrieved |
| * @param column the column of the cell to be retrieved |
| * @return the column's TD element |
| * @throws IndexOutOfBoundsException |
| */ |
| public Element getElement(int row, int column) { |
| checkCellBounds(row, column); |
| return getCellElement(bodyElem, row, column); |
| } |
| |
| /** |
| * Gets the style of a specified cell. |
| * |
| * @param row the cell's row |
| * @param column the cell's column |
| * @see UIObject#getStyleName() |
| * @return returns the style name |
| * @throws IndexOutOfBoundsException |
| */ |
| public String getStyleName(int row, int column) { |
| return UIObject.getStyleName(getElement(row, column)); |
| } |
| |
| /** |
| * Gets the primary style of a specified cell. |
| * |
| * @param row the cell's row |
| * @param column the cell's column |
| * @see UIObject#getStylePrimaryName() |
| * @return returns the style name |
| * @throws IndexOutOfBoundsException |
| */ |
| public String getStylePrimaryName(int row, int column) { |
| return UIObject.getStylePrimaryName(getElement(row, column)); |
| } |
| |
| /** |
| * Determines whether or not this cell is visible. |
| * |
| * @param row the row of the cell whose visibility is to be set |
| * @param column the column of the cell whose visibility is to be set |
| * @return <code>true</code> if the object is visible |
| */ |
| public boolean isVisible(int row, int column) { |
| Element e = getElement(row, column); |
| return UIObject.isVisible(e); |
| } |
| |
| /** |
| * Removes a style from the specified cell. |
| * |
| * @param row the cell's row |
| * @param column the cell's column |
| * @param styleName the style name to be removed |
| * @see UIObject#removeStyleName(String) |
| * @throws IndexOutOfBoundsException |
| */ |
| public void removeStyleName(int row, int column, String styleName) { |
| checkCellBounds(row, column); |
| Element td = getCellElement(bodyElem, row, column); |
| UIObject.setStyleName(td, styleName, false); |
| } |
| |
| /** |
| * Sets the horizontal and vertical alignment of the specified cell's |
| * contents. |
| * |
| * @param row the row of the cell whose alignment is to be set |
| * @param column the cell whose alignment is to be set |
| * @param hAlign the cell's new horizontal alignment as specified in |
| * {@link HasHorizontalAlignment} |
| * @param vAlign the cell's new vertical alignment as specified in |
| * {@link HasVerticalAlignment} |
| * @throws IndexOutOfBoundsException |
| */ |
| public void setAlignment(int row, int column, |
| HorizontalAlignmentConstant hAlign, VerticalAlignmentConstant vAlign) { |
| setHorizontalAlignment(row, column, hAlign); |
| setVerticalAlignment(row, column, vAlign); |
| } |
| |
| /** |
| * Sets the height of the specified cell. |
| * |
| * @param row the row of the cell whose height is to be set |
| * @param column the cell whose height is to be set |
| * @param height the cell's new height, in CSS units |
| * @throws IndexOutOfBoundsException |
| */ |
| public void setHeight(int row, int column, String height) { |
| prepareCell(row, column); |
| Element elem = getCellElement(bodyElem, row, column); |
| DOM.setElementProperty(elem, "height", height); |
| } |
| |
| /** |
| * Sets the horizontal alignment of the specified cell. |
| * |
| * @param row the row of the cell whose alignment is to be set |
| * @param column the cell whose alignment is to be set |
| * @param align the cell's new horizontal alignment as specified in |
| * {@link HasHorizontalAlignment}. |
| * @throws IndexOutOfBoundsException |
| */ |
| public void setHorizontalAlignment(int row, int column, |
| HorizontalAlignmentConstant align) { |
| prepareCell(row, column); |
| Element elem = getCellElement(bodyElem, row, column); |
| DOM.setElementProperty(elem, "align", align.getTextAlignString()); |
| } |
| |
| /** |
| * Sets the style name associated with the specified cell. |
| * |
| * @param row the row of the cell whose style name is to be set |
| * @param column the column of the cell whose style name is to be set |
| * @param styleName the new style name |
| * @see UIObject#setStyleName(String) |
| * @throws IndexOutOfBoundsException |
| */ |
| public void setStyleName(int row, int column, String styleName) { |
| prepareCell(row, column); |
| UIObject.setStyleName(getCellElement(bodyElem, row, column), styleName); |
| } |
| |
| /** |
| * Sets the primary style name associated with the specified cell. |
| * |
| * @param row the row of the cell whose style name is to be set |
| * @param column the column of the cell whose style name is to be set |
| * @param styleName the new style name |
| * @see UIObject#setStylePrimaryName(String) |
| * @throws IndexOutOfBoundsException |
| */ |
| public void setStylePrimaryName(int row, int column, String styleName) { |
| UIObject.setStylePrimaryName(getCellElement(bodyElem, row, column), |
| styleName); |
| } |
| |
| /** |
| * Sets the vertical alignment of the specified cell. |
| * |
| * @param row the row of the cell whose alignment is to be set |
| * @param column the cell whose alignment is to be set |
| * @param align the cell's new vertical alignment as specified in |
| * {@link HasVerticalAlignment}. |
| * @throws IndexOutOfBoundsException |
| */ |
| public void setVerticalAlignment(int row, int column, |
| VerticalAlignmentConstant align) { |
| prepareCell(row, column); |
| DOM.setStyleAttribute(getCellElement(bodyElem, row, column), |
| "verticalAlign", align.getVerticalAlignString()); |
| } |
| |
| /** |
| * Sets whether this cell is visible via the display style property. The |
| * other cells in the row will all shift left to fill the cell's space. So, |
| * for example a table with (0,1,2) will become (1,2) if cell 1 is hidden. |
| * |
| * @param row the row of the cell whose visibility is to be set |
| * @param column the column of the cell whose visibility is to be set |
| * @param visible <code>true</code> to show the cell, <code>false</code> |
| * to hide it |
| */ |
| public void setVisible(int row, int column, boolean visible) { |
| Element e = ensureElement(row, column); |
| UIObject.setVisible(e, visible); |
| } |
| |
| /** |
| * Sets the width of the specified cell. |
| * |
| * @param row the row of the cell whose width is to be set |
| * @param column the cell whose width is to be set |
| * @param width the cell's new width, in CSS units |
| * @throws IndexOutOfBoundsException |
| */ |
| public void setWidth(int row, int column, String width) { |
| // Give the subclass a chance to prepare the cell. |
| prepareCell(row, column); |
| DOM.setElementProperty(getCellElement(bodyElem, row, column), "width", |
| width); |
| } |
| |
| /** |
| * Sets whether the specified cell will allow word wrapping of its contents. |
| * |
| * @param row the row of the cell whose word-wrap is to be set |
| * @param column the cell whose word-wrap is to be set |
| * @param wrap <code>false </code> to disable word wrapping in this cell |
| * @throws IndexOutOfBoundsException |
| */ |
| public void setWordWrap(int row, int column, boolean wrap) { |
| prepareCell(row, column); |
| String wrapValue = wrap ? "" : "nowrap"; |
| DOM.setStyleAttribute(getElement(row, column), "whiteSpace", wrapValue); |
| } |
| |
| /** |
| * Gets the element associated with a cell. If it does not exist and the |
| * subtype allows creation of elements, creates it. |
| * |
| * @param row the cell's row |
| * @param column the cell's column |
| * @return the cell's element |
| * @throws IndexOutOfBoundsException |
| */ |
| protected Element ensureElement(int row, int column) { |
| prepareCell(row, column); |
| return getCellElement(bodyElem, row, column); |
| } |
| |
| /** |
| * Convenience methods to get an attribute on a cell. |
| * |
| * @param row cell's row |
| * @param column cell's column |
| * @param attr attribute to get |
| * @return the attribute's value |
| * @throws IndexOutOfBoundsException |
| */ |
| protected String getAttr(int row, int column, String attr) { |
| Element elem = getElement(row, column); |
| return DOM.getElementAttribute(elem, attr); |
| } |
| |
| /** |
| * Convenience methods to set an attribute on a cell. |
| * |
| * @param row cell's row |
| * @param column cell's column |
| * @param attrName attribute to set |
| * @param value value to set |
| * @throws IndexOutOfBoundsException |
| */ |
| protected void setAttr(int row, int column, String attrName, String value) { |
| Element elem = ensureElement(row, column); |
| DOM.setElementAttribute(elem, attrName, value); |
| } |
| |
| /** |
| * Native method to get a cell's element. |
| * |
| * @param table the table element |
| * @param row the row of the cell |
| * @param col the column of the cell |
| * @return the element |
| */ |
| private native Element getCellElement(Element table, int row, int col) /*-{ |
| var out = table.rows[row].cells[col]; |
| return (out == null ? null : out); |
| }-*/; |
| |
| /** |
| * Gets the TD element representing the specified cell unsafely (meaning |
| * that it doesn't ensure that the row and column are valid). |
| * |
| * @param row the row of the cell to be retrieved |
| * @param column the column of the cell to be retrieved |
| * @return the column's TD element |
| */ |
| private Element getRawElement(int row, int column) { |
| return getCellElement(bodyElem, row, column); |
| } |
| } |
| |
| /** |
| * This class contains methods used to format a table's columns. It is limited |
| * by the support cross-browser HTML support for column formatting. |
| */ |
| public class ColumnFormatter { |
| protected Element columnGroup; |
| |
| /** |
| * Adds a style to the specified column. |
| * |
| * @param col the col to which the style will be added |
| * @param styleName the style name to be added |
| * @see UIObject#addStyleName(String) |
| * @throws IndexOutOfBoundsException |
| */ |
| public void addStyleName(int col, String styleName) { |
| UIObject.setStyleName(ensureColumn(col), styleName, true); |
| } |
| |
| /** |
| * Gets the style of the specified column. |
| * |
| * @param column the column to be queried |
| * @return the style name |
| * @see UIObject#getStyleName() |
| * @throws IndexOutOfBoundsException |
| */ |
| public String getStyleName(int column) { |
| return UIObject.getStyleName(ensureColumn(column)); |
| } |
| |
| /** |
| * Gets the primary style of the specified column. |
| * |
| * @param column the column to be queried |
| * @return the style name |
| * @see UIObject#getStylePrimaryName() |
| * @throws IndexOutOfBoundsException |
| */ |
| public String getStylePrimaryName(int column) { |
| return UIObject.getStylePrimaryName(ensureColumn(column)); |
| } |
| |
| /** |
| * Removes a style from the specified column. |
| * |
| * @param column the column from which the style will be removed |
| * @param styleName the style name to be removed |
| * @see UIObject#removeStyleName(String) |
| * @throws IndexOutOfBoundsException |
| */ |
| public void removeStyleName(int column, String styleName) { |
| UIObject.setStyleName(ensureColumn(column), styleName, false); |
| } |
| |
| /** |
| * Sets the style name associated with the specified column. |
| * |
| * @param column the column whose style name is to be set |
| * @param styleName the new style name |
| * @see UIObject#setStyleName(String) |
| * @throws IndexOutOfBoundsException |
| */ |
| public void setStyleName(int column, String styleName) { |
| UIObject.setStyleName(ensureColumn(column), styleName); |
| } |
| |
| /** |
| * Sets the primary style name associated with the specified column. |
| * |
| * @param column the column whose style name is to be set |
| * @param styleName the new style name |
| * @see UIObject#setStylePrimaryName(String) |
| * @throws IndexOutOfBoundsException |
| */ |
| public void setStylePrimaryName(int column, String styleName) { |
| UIObject.setStylePrimaryName(ensureColumn(column), styleName); |
| } |
| |
| /** |
| * Sets the width of the specified column. |
| * |
| * @param column the column of the cell whose width is to be set |
| * @param width the cell's new width, in percentage or pixel units |
| * @throws IndexOutOfBoundsException |
| */ |
| public void setWidth(int column, String width) { |
| DOM.setElementProperty(ensureColumn(column), "width", width); |
| } |
| |
| private Element ensureColumn(int col) { |
| prepareColumn(col); |
| prepareColumnGroup(); |
| |
| int num = DOM.getChildCount(columnGroup); |
| if (num <= col) { |
| Element colElement = null; |
| for (int i = num; i <= col; i++) { |
| colElement = DOM.createElement("col"); |
| DOM.appendChild(columnGroup, colElement); |
| } |
| return colElement; |
| } |
| return DOM.getChild(columnGroup, col); |
| } |
| |
| /** |
| * Prepare the colgroup tag for the first time, guarenteeing that it |
| * exists and has at least one col tag in it. This method corrects |
| * a Mozilla issue where the col tag will affect the wrong column if |
| * a col tag doesn't exist when the element is attached to the page. |
| */ |
| private void prepareColumnGroup() { |
| if (columnGroup == null) { |
| columnGroup = DOM.createElement("colgroup"); |
| DOM.insertChild(tableElem, columnGroup, 0); |
| DOM.appendChild(columnGroup, DOM.createElement("col")); |
| } |
| } |
| } |
| |
| /** |
| * This class contains methods used to format a table's rows. |
| */ |
| public class RowFormatter { |
| |
| /** |
| * Adds a style to the specified row. |
| * |
| * @param row the row to which the style will be added |
| * @param styleName the style name to be added |
| * @see UIObject#addStyleName(String) |
| * @throws IndexOutOfBoundsException |
| */ |
| public void addStyleName(int row, String styleName) { |
| UIObject.setStyleName(ensureElement(row), styleName, true); |
| } |
| |
| /** |
| * Gets the TR element representing the specified row. |
| * |
| * @param row the row whose TR element is to be retrieved |
| * @return the row's TR element |
| * @throws IndexOutOfBoundsException |
| */ |
| public Element getElement(int row) { |
| checkRowBounds(row); |
| return getRow(bodyElem, row); |
| } |
| |
| /** |
| * Gets the style of the specified row. |
| * |
| * @param row the row to be queried |
| * @return the style name |
| * @see UIObject#getStyleName() |
| * @throws IndexOutOfBoundsException |
| */ |
| public String getStyleName(int row) { |
| return UIObject.getStyleName(getElement(row)); |
| } |
| |
| /** |
| * Gets the primary style of the specified row. |
| * |
| * @param row the row to be queried |
| * @return the style name |
| * @see UIObject#getStylePrimaryName() |
| * @throws IndexOutOfBoundsException |
| */ |
| public String getStylePrimaryName(int row) { |
| return UIObject.getStylePrimaryName(getElement(row)); |
| } |
| |
| /** |
| * Determines whether or not this row is visible via the display style |
| * attribute. |
| * |
| * @param row the row whose visibility is to be set |
| * @return <code>true</code> if the row is visible |
| */ |
| public boolean isVisible(int row) { |
| Element e = getElement(row); |
| return UIObject.isVisible(e); |
| } |
| |
| /** |
| * Removes a style from the specified row. |
| * |
| * @param row the row from which the style will be removed |
| * @param styleName the style name to be removed |
| * @see UIObject#removeStyleName(String) |
| * @throws IndexOutOfBoundsException |
| */ |
| public void removeStyleName(int row, String styleName) { |
| UIObject.setStyleName(ensureElement(row), styleName, false); |
| } |
| |
| /** |
| * Sets the style name associated with the specified row. |
| * |
| * @param row the row whose style name is to be set |
| * @param styleName the new style name |
| * @see UIObject#setStyleName(String) |
| * @throws IndexOutOfBoundsException |
| */ |
| public void setStyleName(int row, String styleName) { |
| UIObject.setStyleName(ensureElement(row), styleName); |
| } |
| |
| /** |
| * Sets the primary style name associated with the specified row. |
| * |
| * @param row the row whose style name is to be set |
| * @param styleName the new style name |
| * @see UIObject#setStylePrimaryName(String) |
| * @throws IndexOutOfBoundsException |
| */ |
| public void setStylePrimaryName(int row, String styleName) { |
| UIObject.setStylePrimaryName(ensureElement(row), styleName); |
| } |
| |
| /** |
| * Sets the vertical alignment of the specified row. |
| * |
| * @param row the row whose alignment is to be set |
| * @param align the row's new vertical alignment as specified in |
| * {@link HasVerticalAlignment} |
| * @throws IndexOutOfBoundsException |
| */ |
| public void setVerticalAlign(int row, VerticalAlignmentConstant align) { |
| DOM.setStyleAttribute(ensureElement(row), "verticalAlign", |
| align.getVerticalAlignString()); |
| } |
| |
| /** |
| * Sets whether this row is visible. |
| * |
| * @param row the row whose visibility is to be set |
| * @param visible <code>true</code> to show the row, <code>false</code> |
| * to hide it |
| */ |
| public void setVisible(int row, boolean visible) { |
| Element e = ensureElement(row); |
| UIObject.setVisible(e, visible); |
| } |
| |
| /** |
| * Ensure the TR element representing the specified row exists for |
| * subclasses that allow dynamic addition of elements. |
| * |
| * @param row the row whose TR element is to be retrieved |
| * @return the row's TR element |
| * @throws IndexOutOfBoundsException |
| */ |
| protected Element ensureElement(int row) { |
| prepareRow(row); |
| return getRow(bodyElem, row); |
| } |
| |
| protected native Element getRow(Element elem, int row)/*-{ |
| return elem.rows[row]; |
| }-*/; |
| |
| /** |
| * Convenience methods to set an attribute on a row. |
| * |
| * @param row cell's row |
| * @param attrName attribute to set |
| * @param value value to set |
| * @throws IndexOutOfBoundsException |
| */ |
| protected void setAttr(int row, String attrName, String value) { |
| Element elem = ensureElement(row); |
| DOM.setElementAttribute(elem, attrName, value); |
| } |
| } |
| |
| /** |
| * Creates a mapping from elements to their associated widgets. |
| */ |
| private static class WidgetMapper { |
| |
| private static class FreeNode { |
| int index; |
| FreeNode next; |
| |
| public FreeNode(int index, FreeNode next) { |
| this.index = index; |
| this.next = next; |
| } |
| } |
| |
| private static native void clearWidgetIndex(Element elem) /*-{ |
| elem["__widgetID"] = null; |
| }-*/; |
| |
| private static native int getWidgetIndex(Element elem) /*-{ |
| var index = elem["__widgetID"]; |
| return (index == null) ? -1 : index; |
| }-*/; |
| |
| private static native void setWidgetIndex(Element elem, int index) /*-{ |
| elem["__widgetID"] = index; |
| }-*/; |
| |
| private FreeNode freeList = null; |
| |
| private final ArrayList<Widget> widgetList = new ArrayList<Widget>(); |
| |
| /** |
| * Returns the widget associated with the given element. |
| * |
| * @param elem widget's element |
| * @return the widget |
| */ |
| public Widget getWidget(Element elem) { |
| int index = getWidgetIndex(elem); |
| if (index < 0) { |
| return null; |
| } |
| return widgetList.get(index); |
| } |
| |
| /** |
| * Adds the Widget. |
| * |
| * @param widget widget to add |
| */ |
| public void putWidget(Widget widget) { |
| int index; |
| if (freeList == null) { |
| index = widgetList.size(); |
| widgetList.add(widget); |
| } else { |
| index = freeList.index; |
| widgetList.set(index, widget); |
| freeList = freeList.next; |
| } |
| setWidgetIndex(widget.getElement(), index); |
| } |
| |
| /** |
| * Remove the widget associated with the given element. |
| * |
| * @param elem the widget's element |
| */ |
| public void removeWidgetByElement(Element elem) { |
| int index = getWidgetIndex(elem); |
| removeImpl(elem, index); |
| } |
| |
| /** |
| * Creates an iterator of widgets. |
| * |
| * @return the iterator |
| */ |
| public Iterator<Widget> widgetIterator() { |
| // TODO: look at using the WidgetIterators class! |
| return new Iterator<Widget>() { |
| int lastIndex = -1; |
| int nextIndex = -1; |
| { |
| findNext(); |
| } |
| |
| public boolean hasNext() { |
| return nextIndex < widgetList.size(); |
| } |
| |
| public Widget next() { |
| if (!hasNext()) { |
| throw new NoSuchElementException(); |
| } |
| Widget result = widgetList.get(nextIndex); |
| lastIndex = nextIndex; |
| findNext(); |
| return result; |
| } |
| |
| public void remove() { |
| if (lastIndex < 0) { |
| throw new IllegalStateException(); |
| } |
| Widget w = widgetList.get(lastIndex); |
| assert (w.getParent() instanceof HTMLTable); |
| w.removeFromParent(); |
| lastIndex = -1; |
| } |
| |
| private void findNext() { |
| while (++nextIndex < widgetList.size()) { |
| if (widgetList.get(nextIndex) != null) { |
| return; |
| } |
| } |
| } |
| }; |
| } |
| |
| private void removeImpl(Element elem, int index) { |
| clearWidgetIndex(elem); |
| widgetList.set(index, null); |
| freeList = new FreeNode(index, freeList); |
| } |
| } |
| |
| /** |
| * Table's body. |
| */ |
| private final Element bodyElem; |
| |
| /** |
| * Current cell formatter. |
| */ |
| private CellFormatter cellFormatter; |
| |
| /** |
| * Column Formatter. |
| */ |
| private ColumnFormatter columnFormatter; |
| |
| /** |
| * Current row formatter. |
| */ |
| private RowFormatter rowFormatter; |
| |
| /** |
| * Table element. |
| */ |
| private final Element tableElem; |
| |
| /** |
| * Current table listener. |
| */ |
| private TableListenerCollection tableListeners; |
| |
| private WidgetMapper widgetMap = new WidgetMapper(); |
| |
| /** |
| * Create a new empty HTML Table. |
| */ |
| public HTMLTable() { |
| tableElem = DOM.createTable(); |
| bodyElem = DOM.createTBody(); |
| DOM.appendChild(tableElem, bodyElem); |
| setElement(tableElem); |
| } |
| |
| /** |
| * Adds a listener to the current table. |
| * |
| * @param listener listener to add |
| */ |
| public void addTableListener(TableListener listener) { |
| if (tableListeners == null) { |
| tableListeners = new TableListenerCollection(); |
| sinkEvents(Event.ONCLICK); |
| } |
| tableListeners.add(listener); |
| } |
| |
| /** |
| * Removes all widgets from this table, but does not remove other HTML or text |
| * contents of cells. |
| */ |
| @Override |
| public void clear() { |
| for (int row = 0; row < getRowCount(); ++row) { |
| for (int col = 0; col < getCellCount(row); ++col) { |
| Widget child = getWidgetImpl(row, col); |
| if (child != null) { |
| remove(child); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Clears the given row and column. If it contains a Widget, it will be |
| * removed from the table. If not, its contents will simply be cleared. |
| * |
| * @param row the widget's column |
| * @param column the widget's column |
| * @return true if a widget was removed |
| * @throws IndexOutOfBoundsException |
| */ |
| public boolean clearCell(int row, int column) { |
| Element td = getCellFormatter().getElement(row, column); |
| return internalClearCell(td, true); |
| } |
| |
| /** |
| * Gets the number of cells in a given row. |
| * |
| * @param row the row whose cells are to be counted |
| * @return the number of cells present in the row |
| */ |
| public abstract int getCellCount(int row); |
| |
| /** |
| * Gets the {@link CellFormatter} associated with this table. Use casting to |
| * get subclass-specific functionality |
| * |
| * @return this table's cell formatter |
| */ |
| public CellFormatter getCellFormatter() { |
| return cellFormatter; |
| } |
| |
| /** |
| * Gets the amount of padding that is added around all cells. |
| * |
| * @return the cell padding, in pixels |
| */ |
| public int getCellPadding() { |
| return DOM.getElementPropertyInt(tableElem, "cellPadding"); |
| } |
| |
| /** |
| * Gets the amount of spacing that is added around all cells. |
| * |
| * @return the cell spacing, in pixels |
| */ |
| public int getCellSpacing() { |
| return DOM.getElementPropertyInt(tableElem, "cellSpacing"); |
| } |
| |
| /** |
| * Gets the column formatter. |
| * |
| * @return the column formatter |
| */ |
| public ColumnFormatter getColumnFormatter() { |
| return columnFormatter; |
| } |
| |
| /** |
| * Gets the HTML contents of the specified cell. |
| * |
| * @param row the cell's row |
| * @param column the cell's column |
| * @return the cell's HTML contents |
| * @throws IndexOutOfBoundsException |
| */ |
| public String getHTML(int row, int column) { |
| return DOM.getInnerHTML(cellFormatter.getElement(row, column)); |
| } |
| |
| /** |
| * Gets the number of rows present in this table. |
| * |
| * @return the table's row count |
| */ |
| public abstract int getRowCount(); |
| |
| /** |
| * Gets the RowFormatter associated with this table. |
| * |
| * @return the table's row formatter |
| */ |
| public RowFormatter getRowFormatter() { |
| return rowFormatter; |
| } |
| |
| /** |
| * Gets the text within the specified cell. |
| * |
| * @param row the cell's row |
| * @param column the cell's column |
| * @return the cell's text contents |
| * @throws IndexOutOfBoundsException |
| */ |
| public String getText(int row, int column) { |
| checkCellBounds(row, column); |
| Element e = cellFormatter.getElement(row, column); |
| return DOM.getInnerText(e); |
| } |
| |
| /** |
| * Gets the widget in the specified cell. |
| * |
| * @param row the cell's row |
| * @param column the cell's column |
| * @return the widget in the specified cell, or <code>null</code> if none is |
| * present |
| * @throws IndexOutOfBoundsException |
| */ |
| public Widget getWidget(int row, int column) { |
| checkCellBounds(row, column); |
| return getWidgetImpl(row, column); |
| } |
| |
| /** |
| * Determines whether the specified cell exists. |
| * |
| * @param row the cell's row |
| * @param column the cell's column |
| * @return <code>true</code> if the specified cell exists |
| */ |
| public boolean isCellPresent(int row, int column) { |
| if ((row >= getRowCount()) || (row < 0)) { |
| return false; |
| } |
| if ((column < 0) || (column >= getCellCount(row))) { |
| return false; |
| } else { |
| return true; |
| } |
| } |
| |
| /** |
| * Returns an iterator containing all the widgets in this table. |
| * |
| * @return the iterator |
| */ |
| public Iterator<Widget> iterator() { |
| return widgetMap.widgetIterator(); |
| } |
| |
| /** |
| * Method to process events generated from the browser. |
| * |
| * @param event the generated event |
| */ |
| @Override |
| public void onBrowserEvent(Event event) { |
| switch (DOM.eventGetType(event)) { |
| case Event.ONCLICK: { |
| if (tableListeners != null) { |
| // Find out which cell was actually clicked. |
| Element td = getEventTargetCell(event); |
| if (td == null) { |
| return; |
| } |
| Element tr = DOM.getParent(td); |
| Element body = DOM.getParent(tr); |
| int row = DOM.getChildIndex(body, tr); |
| int column = DOM.getChildIndex(tr, td); |
| // Fire the event. |
| tableListeners.fireCellClicked(this, row, column); |
| } |
| break; |
| } |
| default: { |
| // Do nothing |
| } |
| } |
| } |
| |
| /** |
| * Remove the specified widget from the table. |
| * |
| * @param widget widget to remove |
| * @return was the widget removed from the table. |
| */ |
| @Override |
| public boolean remove(Widget widget) { |
| // Validate. |
| if (widget.getParent() != this) { |
| return false; |
| } |
| |
| // Orphan. |
| orphan(widget); |
| |
| // Physical detach. |
| Element elem = widget.getElement(); |
| DOM.removeChild(DOM.getParent(elem), elem); |
| |
| // Logical detach. |
| widgetMap.removeWidgetByElement(elem); |
| return true; |
| } |
| |
| /** |
| * Removes the specified table listener. |
| * |
| * @param listener listener to remove |
| */ |
| public void removeTableListener(TableListener listener) { |
| if (tableListeners != null) { |
| tableListeners.remove(listener); |
| } |
| } |
| |
| /** |
| * Sets the width of the table's border. This border is displayed around all |
| * cells in the table. |
| * |
| * @param width the width of the border, in pixels |
| */ |
| public void setBorderWidth(int width) { |
| DOM.setElementProperty(tableElem, "border", "" + width); |
| } |
| |
| /** |
| * Sets the amount of padding to be added around all cells. |
| * |
| * @param padding the cell padding, in pixels |
| */ |
| public void setCellPadding(int padding) { |
| DOM.setElementPropertyInt(tableElem, "cellPadding", padding); |
| } |
| |
| /** |
| * Sets the amount of spacing to be added around all cells. |
| * |
| * @param spacing the cell spacing, in pixels |
| */ |
| public void setCellSpacing(int spacing) { |
| DOM.setElementPropertyInt(tableElem, "cellSpacing", spacing); |
| } |
| |
| /** |
| * Sets the HTML contents of the specified cell. |
| * |
| * @param row the cell's row |
| * @param column the cell's column |
| * @param html the cell's HTML contents |
| * @throws IndexOutOfBoundsException |
| */ |
| public void setHTML(int row, int column, String html) { |
| prepareCell(row, column); |
| Element td = cleanCell(row, column, html == null); |
| if (html != null) { |
| DOM.setInnerHTML(td, html); |
| } |
| } |
| |
| /** |
| * Sets the text within the specified cell. |
| * |
| * @param row the cell's row |
| * @param column cell's column |
| * @param text the cell's text contents |
| * @throws IndexOutOfBoundsException |
| */ |
| public void setText(int row, int column, String text) { |
| prepareCell(row, column); |
| Element td; |
| td = cleanCell(row, column, text == null); |
| if (text != null) { |
| DOM.setInnerText(td, text); |
| } |
| } |
| |
| /** |
| * Sets the widget within the specified cell. |
| * <p> |
| * Inherited implementations may either throw IndexOutOfBounds exception if |
| * the cell does not exist, or allocate a new cell to store the content. |
| * </p> |
| * <p> |
| * FlexTable will automatically allocate the cell at the correct location and |
| * then set the widget. Grid will set the widget if and only if the cell is |
| * within the Grid's bounding box. |
| * </p> |
| * |
| * @param widget The widget to be added |
| * @param row the cell's row |
| * @param column the cell's column |
| * @throws IndexOutOfBoundsException |
| */ |
| public void setWidget(int row, int column, Widget widget) { |
| prepareCell(row, column); |
| if (widget != null) { |
| widget.removeFromParent(); |
| |
| // Removes any existing widget. |
| Element td = cleanCell(row, column, true); |
| |
| // Logical attach. |
| widgetMap.putWidget(widget); |
| |
| // Physical attach. |
| DOM.appendChild(td, widget.getElement()); |
| |
| adopt(widget); |
| } |
| } |
| |
| /** |
| * Bounds checks that the cell exists at the specified location. |
| * |
| * @param row cell's row |
| * @param column cell's column |
| * @throws IndexOutOfBoundsException |
| */ |
| protected void checkCellBounds(int row, int column) { |
| checkRowBounds(row); |
| if (column < 0) { |
| throw new IndexOutOfBoundsException("Column " + column |
| + " must be non-negative: " + column); |
| } |
| int cellSize = getCellCount(row); |
| if (cellSize <= column) { |
| throw new IndexOutOfBoundsException("Column index: " + column |
| + ", Column size: " + getCellCount(row)); |
| } |
| } |
| |
| /** |
| * Checks that the row is within the correct bounds. |
| * |
| * @param row row index to check |
| * @throws IndexOutOfBoundsException |
| */ |
| protected void checkRowBounds(int row) { |
| int rowSize = getRowCount(); |
| if ((row >= rowSize) || (row < 0)) { |
| throw new IndexOutOfBoundsException("Row index: " + row + ", Row size: " |
| + rowSize); |
| } |
| } |
| |
| /** |
| * Creates a new cell. Override this method if the cell should have initial |
| * contents. |
| * |
| * @return the newly created TD |
| */ |
| protected Element createCell() { |
| return DOM.createTD(); |
| } |
| |
| /** |
| * Gets the table's TBODY element. |
| * |
| * @return the TBODY element |
| */ |
| protected Element getBodyElement() { |
| return bodyElem; |
| } |
| |
| /** |
| * Directly ask the underlying DOM what the cell count on the given row is. |
| * |
| * @param tableBody the element |
| * @param row the row |
| * @return number of columns in the row |
| */ |
| protected native int getDOMCellCount(Element tableBody, int row) /*-{ |
| return tableBody.rows[row].cells.length; |
| }-*/; |
| |
| /** |
| * Directly ask the underlying DOM what the cell count on the given row is. |
| * |
| * @param row the row |
| * @return number of columns in the row |
| */ |
| protected int getDOMCellCount(int row) { |
| return getDOMCellCount(bodyElem, row); |
| } |
| |
| /** |
| * Directly ask the underlying DOM what the row count is. |
| * |
| * @return Returns the number of rows in the table |
| */ |
| protected int getDOMRowCount() { |
| return getDOMRowCount(bodyElem); |
| } |
| |
| protected native int getDOMRowCount(Element elem) /*-{ |
| return elem.rows.length; |
| }-*/; |
| |
| /** |
| * Determines the TD associated with the specified event. |
| * |
| * @param event the event to be queried |
| * @return the TD associated with the event, or <code>null</code> if none is |
| * found. |
| */ |
| protected Element getEventTargetCell(Event event) { |
| Element td = DOM.eventGetTarget(event); |
| for (; td != null; td = DOM.getParent(td)) { |
| // If it's a TD, it might be the one we're looking for. |
| if (DOM.getElementProperty(td, "tagName").equalsIgnoreCase("td")) { |
| // Make sure it's directly a part of this table before returning |
| // it. |
| Element tr = DOM.getParent(td); |
| Element body = DOM.getParent(tr); |
| if (body == bodyElem) { |
| return td; |
| } |
| } |
| // If we run into this table's body, we're out of options. |
| if (td == bodyElem) { |
| return null; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Inserts a new cell into the specified row. |
| * |
| * @param row the row into which the new cell will be inserted |
| * @param column the column before which the cell will be inserted |
| * @throws IndexOutOfBoundsException |
| */ |
| protected void insertCell(int row, int column) { |
| Element tr = rowFormatter.getRow(bodyElem, row); |
| Element td = createCell(); |
| DOM.insertChild(tr, td, column); |
| } |
| |
| /** |
| * Inserts a number of cells before the specified cell. |
| * |
| * @param row the row into which the new cells will be inserted |
| * @param column the column before which the new cells will be inserted |
| * @param count number of cells to be inserted |
| * @throws IndexOutOfBoundsException |
| */ |
| protected void insertCells(int row, int column, int count) { |
| Element tr = rowFormatter.getRow(bodyElem, row); |
| for (int i = column; i < column + count; i++) { |
| Element td = createCell(); |
| DOM.insertChild(tr, td, i); |
| } |
| } |
| |
| /** |
| * Inserts a new row into the table. |
| * |
| * @param beforeRow the index before which the new row will be inserted |
| * @return the index of the newly-created row |
| * @throws IndexOutOfBoundsException |
| */ |
| protected int insertRow(int beforeRow) { |
| // Specifically allow the row count as an insert position. |
| if (beforeRow != getRowCount()) { |
| checkRowBounds(beforeRow); |
| } |
| Element tr = DOM.createTR(); |
| DOM.insertChild(bodyElem, tr, beforeRow); |
| return beforeRow; |
| } |
| |
| /** |
| * Does actual clearing, used by clearCell and cleanCell. All HTMLTable |
| * methods should use internalClearCell rather than clearCell, as clearCell |
| * may be overridden in subclasses to format an empty cell. |
| * |
| * @param td element to clear |
| * @param clearInnerHTML should the cell's inner html be cleared? |
| * @return returns whether a widget was cleared |
| */ |
| protected boolean internalClearCell(Element td, boolean clearInnerHTML) { |
| Element maybeChild = DOM.getFirstChild(td); |
| Widget widget = null; |
| if (maybeChild != null) { |
| widget = widgetMap.getWidget(maybeChild); |
| } |
| if (widget != null) { |
| // If there is a widget, remove it. |
| remove(widget); |
| return true; |
| } else { |
| // Otherwise, simply clear whatever text and/or HTML may be there. |
| if (clearInnerHTML) { |
| DOM.setInnerHTML(td, ""); |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * <b>Affected Elements:</b> |
| * <ul> |
| * <li>-(row)#-(cell)# = the cell at the given row and cell index.</li> |
| * </ul> |
| * |
| * @see UIObject#onEnsureDebugId(String) |
| */ |
| @Override |
| protected void onEnsureDebugId(String baseID) { |
| super.onEnsureDebugId(baseID); |
| |
| int rowCount = getRowCount(); |
| for (int row = 0; row < rowCount; row++) { |
| int cellCount = getCellCount(row); |
| for (int cell = 0; cell < cellCount; cell++) { |
| Element cellElem = cellFormatter.getRawElement(row, cell); |
| ensureDebugId(cellElem, baseID, row + "-" + cell); |
| } |
| } |
| } |
| |
| /** |
| * Subclasses must implement this method. It allows them to decide what to do |
| * just before a cell is accessed. If the cell already exists, this method |
| * must do nothing. Otherwise, a subclass must either ensure that the cell |
| * exists or throw an {@link IndexOutOfBoundsException}. |
| * |
| * @param row the cell's row |
| * @param column the cell's column |
| */ |
| protected abstract void prepareCell(int row, int column); |
| |
| /** |
| * Subclasses can implement this method. It allows them to decide what to do |
| * just before a column is accessed. For classes, such as |
| * <code>FlexTable</code>, that do not have a concept of a global column |
| * length can ignore this method. |
| * |
| * @param column the cell's column |
| * @throws IndexOutOfBoundsException |
| */ |
| protected void prepareColumn(int column) { |
| // By default, do nothing. |
| } |
| |
| /** |
| * Subclasses must implement this method. If the row already exists, this |
| * method must do nothing. Otherwise, a subclass must either ensure that the |
| * row exists or throw an {@link IndexOutOfBoundsException}. |
| * |
| * @param row the cell's row |
| */ |
| protected abstract void prepareRow(int row); |
| |
| /** |
| * Removes the specified cell from the table. |
| * |
| * @param row the row of the cell to remove |
| * @param column the column of cell to remove |
| * @throws IndexOutOfBoundsException |
| */ |
| protected void removeCell(int row, int column) { |
| checkCellBounds(row, column); |
| Element td = cleanCell(row, column, false); |
| Element tr = rowFormatter.getRow(bodyElem, row); |
| DOM.removeChild(tr, td); |
| } |
| |
| /** |
| * Removes the specified row from the table. |
| * |
| * @param row the index of the row to be removed |
| * @throws IndexOutOfBoundsException |
| */ |
| protected void removeRow(int row) { |
| int columnCount = getCellCount(row); |
| for (int column = 0; column < columnCount; ++column) { |
| cleanCell(row, column, false); |
| } |
| DOM.removeChild(bodyElem, rowFormatter.getRow(bodyElem, row)); |
| } |
| |
| /** |
| * Sets the table's CellFormatter. |
| * |
| * @param cellFormatter the table's cell formatter |
| */ |
| protected void setCellFormatter(CellFormatter cellFormatter) { |
| this.cellFormatter = cellFormatter; |
| } |
| |
| protected void setColumnFormatter(ColumnFormatter formatter) { |
| columnFormatter = formatter; |
| columnFormatter.prepareColumnGroup(); |
| } |
| |
| /** |
| * Sets the table's RowFormatter. |
| * |
| * @param rowFormatter the table's row formatter |
| */ |
| protected void setRowFormatter(RowFormatter rowFormatter) { |
| this.rowFormatter = rowFormatter; |
| } |
| |
| /** |
| * Removes any widgets, text, and HTML within the cell. This method assumes |
| * that the requested cell already exists. |
| * |
| * @param row the cell's row |
| * @param column the cell's column |
| * @param clearInnerHTML should the cell's inner html be cleared? |
| * @return element that has been cleaned |
| */ |
| private Element cleanCell(int row, int column, boolean clearInnerHTML) { |
| // Clear whatever is in the cell. |
| Element td = getCellFormatter().getRawElement(row, column); |
| internalClearCell(td, clearInnerHTML); |
| return td; |
| } |
| |
| /** |
| * Gets the Widget associated with the given cell. |
| * |
| * @param row the cell's row |
| * @param column the cell's column |
| * @return the widget |
| */ |
| private Widget getWidgetImpl(int row, int column) { |
| Element e = cellFormatter.getRawElement(row, column); |
| Element child = DOM.getFirstChild(e); |
| if (child == null) { |
| return null; |
| } else { |
| return widgetMap.getWidget(child); |
| } |
| } |
| } |