0001: /*
0002: * Copyright 2007 Google Inc.
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
0005: * use this file except in compliance with the License. You may obtain a copy of
0006: * the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
0012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
0013: * License for the specific language governing permissions and limitations under
0014: * the License.
0015: */
0016: package com.google.gwt.user.client.ui;
0017:
0018: import com.google.gwt.user.client.DOM;
0019: import com.google.gwt.user.client.Element;
0020: import com.google.gwt.user.client.Event;
0021: import com.google.gwt.user.client.ui.HasHorizontalAlignment.HorizontalAlignmentConstant;
0022: import com.google.gwt.user.client.ui.HasVerticalAlignment.VerticalAlignmentConstant;
0023:
0024: import java.util.ArrayList;
0025: import java.util.Iterator;
0026: import java.util.NoSuchElementException;
0027:
0028: /**
0029: * HTMLTable contains the common table algorithms for
0030: * {@link com.google.gwt.user.client.ui.Grid} and
0031: * {@link com.google.gwt.user.client.ui.FlexTable}.
0032: * <p>
0033: * <img class='gallery' src='Table.png'/>
0034: * </p>
0035: */
0036: public abstract class HTMLTable extends Panel implements
0037: SourcesTableEvents {
0038: /**
0039: * This class contains methods used to format a table's cells.
0040: */
0041: public class CellFormatter {
0042: /**
0043: * Adds a style to the specified cell.
0044: *
0045: * @param row the cell's row
0046: * @param column the cell's column
0047: * @param styleName the style name to be added
0048: * @see UIObject#addStyleName(String)
0049: */
0050: public void addStyleName(int row, int column, String styleName) {
0051: prepareCell(row, column);
0052: Element td = getCellElement(bodyElem, row, column);
0053: UIObject.setStyleName(td, styleName, true);
0054: }
0055:
0056: /**
0057: * Gets the TD element representing the specified cell.
0058: *
0059: * @param row the row of the cell to be retrieved
0060: * @param column the column of the cell to be retrieved
0061: * @return the column's TD element
0062: * @throws IndexOutOfBoundsException
0063: */
0064: public Element getElement(int row, int column) {
0065: checkCellBounds(row, column);
0066: return getCellElement(bodyElem, row, column);
0067: }
0068:
0069: /**
0070: * Gets the style of a specified cell.
0071: *
0072: * @param row the cell's row
0073: * @param column the cell's column
0074: * @see UIObject#getStyleName()
0075: * @return returns the style name
0076: * @throws IndexOutOfBoundsException
0077: */
0078: public String getStyleName(int row, int column) {
0079: return UIObject.getStyleName(getElement(row, column));
0080: }
0081:
0082: /**
0083: * Gets the primary style of a specified cell.
0084: *
0085: * @param row the cell's row
0086: * @param column the cell's column
0087: * @see UIObject#getStylePrimaryName()
0088: * @return returns the style name
0089: * @throws IndexOutOfBoundsException
0090: */
0091: public String getStylePrimaryName(int row, int column) {
0092: return UIObject
0093: .getStylePrimaryName(getElement(row, column));
0094: }
0095:
0096: /**
0097: * Determines whether or not this cell is visible.
0098: *
0099: * @param row the row of the cell whose visibility is to be set
0100: * @param column the column of the cell whose visibility is to be set
0101: * @return <code>true</code> if the object is visible
0102: */
0103: public boolean isVisible(int row, int column) {
0104: Element e = getElement(row, column);
0105: return UIObject.isVisible(e);
0106: }
0107:
0108: /**
0109: * Removes a style from the specified cell.
0110: *
0111: * @param row the cell's row
0112: * @param column the cell's column
0113: * @param styleName the style name to be removed
0114: * @see UIObject#removeStyleName(String)
0115: * @throws IndexOutOfBoundsException
0116: */
0117: public void removeStyleName(int row, int column,
0118: String styleName) {
0119: checkCellBounds(row, column);
0120: Element td = getCellElement(bodyElem, row, column);
0121: UIObject.setStyleName(td, styleName, false);
0122: }
0123:
0124: /**
0125: * Sets the horizontal and vertical alignment of the specified cell's
0126: * contents.
0127: *
0128: * @param row the row of the cell whose alignment is to be set
0129: * @param column the cell whose alignment is to be set
0130: * @param hAlign the cell's new horizontal alignment as specified in
0131: * {@link HasHorizontalAlignment}
0132: * @param vAlign the cell's new vertical alignment as specified in
0133: * {@link HasVerticalAlignment}
0134: * @throws IndexOutOfBoundsException
0135: */
0136: public void setAlignment(int row, int column,
0137: HorizontalAlignmentConstant hAlign,
0138: VerticalAlignmentConstant vAlign) {
0139: setHorizontalAlignment(row, column, hAlign);
0140: setVerticalAlignment(row, column, vAlign);
0141: }
0142:
0143: /**
0144: * Sets the height of the specified cell.
0145: *
0146: * @param row the row of the cell whose height is to be set
0147: * @param column the cell whose height is to be set
0148: * @param height the cell's new height, in CSS units
0149: * @throws IndexOutOfBoundsException
0150: */
0151: public void setHeight(int row, int column, String height) {
0152: prepareCell(row, column);
0153: Element elem = getCellElement(bodyElem, row, column);
0154: DOM.setElementProperty(elem, "height", height);
0155: }
0156:
0157: /**
0158: * Sets the horizontal alignment of the specified cell.
0159: *
0160: * @param row the row of the cell whose alignment is to be set
0161: * @param column the cell whose alignment is to be set
0162: * @param align the cell's new horizontal alignment as specified in
0163: * {@link HasHorizontalAlignment}.
0164: * @throws IndexOutOfBoundsException
0165: */
0166: public void setHorizontalAlignment(int row, int column,
0167: HorizontalAlignmentConstant align) {
0168: prepareCell(row, column);
0169: Element elem = getCellElement(bodyElem, row, column);
0170: DOM.setElementProperty(elem, "align", align
0171: .getTextAlignString());
0172: }
0173:
0174: /**
0175: * Sets the style name associated with the specified cell.
0176: *
0177: * @param row the row of the cell whose style name is to be set
0178: * @param column the column of the cell whose style name is to be set
0179: * @param styleName the new style name
0180: * @see UIObject#setStyleName(String)
0181: * @throws IndexOutOfBoundsException
0182: */
0183: public void setStyleName(int row, int column, String styleName) {
0184: prepareCell(row, column);
0185: UIObject.setStyleName(
0186: getCellElement(bodyElem, row, column), styleName);
0187: }
0188:
0189: /**
0190: * Sets the primary style name associated with the specified cell.
0191: *
0192: * @param row the row of the cell whose style name is to be set
0193: * @param column the column of the cell whose style name is to be set
0194: * @param styleName the new style name
0195: * @see UIObject#setStylePrimaryName(String)
0196: * @throws IndexOutOfBoundsException
0197: */
0198: public void setStylePrimaryName(int row, int column,
0199: String styleName) {
0200: UIObject.setStylePrimaryName(getCellElement(bodyElem, row,
0201: column), styleName);
0202: }
0203:
0204: /**
0205: * Sets the vertical alignment of the specified cell.
0206: *
0207: * @param row the row of the cell whose alignment is to be set
0208: * @param column the cell whose alignment is to be set
0209: * @param align the cell's new vertical alignment as specified in
0210: * {@link HasVerticalAlignment}.
0211: * @throws IndexOutOfBoundsException
0212: */
0213: public void setVerticalAlignment(int row, int column,
0214: VerticalAlignmentConstant align) {
0215: prepareCell(row, column);
0216: DOM.setStyleAttribute(
0217: getCellElement(bodyElem, row, column),
0218: "verticalAlign", align.getVerticalAlignString());
0219: }
0220:
0221: /**
0222: * Sets whether this cell is visible via the display style property. The
0223: * other cells in the row will all shift left to fill the cell's space. So,
0224: * for example a table with (0,1,2) will become (1,2) if cell 1 is hidden.
0225: *
0226: * @param row the row of the cell whose visibility is to be set
0227: * @param column the column of the cell whose visibility is to be set
0228: * @param visible <code>true</code> to show the cell, <code>false</code>
0229: * to hide it
0230: */
0231: public void setVisible(int row, int column, boolean visible) {
0232: Element e = ensureElement(row, column);
0233: UIObject.setVisible(e, visible);
0234: }
0235:
0236: /**
0237: * Sets the width of the specified cell.
0238: *
0239: * @param row the row of the cell whose width is to be set
0240: * @param column the cell whose width is to be set
0241: * @param width the cell's new width, in CSS units
0242: * @throws IndexOutOfBoundsException
0243: */
0244: public void setWidth(int row, int column, String width) {
0245: // Give the subclass a chance to prepare the cell.
0246: prepareCell(row, column);
0247: DOM.setElementProperty(
0248: getCellElement(bodyElem, row, column), "width",
0249: width);
0250: }
0251:
0252: /**
0253: * Sets whether the specified cell will allow word wrapping of its contents.
0254: *
0255: * @param row the row of the cell whose word-wrap is to be set
0256: * @param column the cell whose word-wrap is to be set
0257: * @param wrap <code>false </code> to disable word wrapping in this cell
0258: * @throws IndexOutOfBoundsException
0259: */
0260: public void setWordWrap(int row, int column, boolean wrap) {
0261: prepareCell(row, column);
0262: String wrapValue = wrap ? "" : "nowrap";
0263: DOM.setStyleAttribute(getElement(row, column),
0264: "whiteSpace", wrapValue);
0265: }
0266:
0267: /**
0268: * Gets the element associated with a cell. If it does not exist and the
0269: * subtype allows creation of elements, creates it.
0270: *
0271: * @param row the cell's row
0272: * @param column the cell's column
0273: * @return the cell's element
0274: * @throws IndexOutOfBoundsException
0275: */
0276: protected Element ensureElement(int row, int column) {
0277: prepareCell(row, column);
0278: return getCellElement(bodyElem, row, column);
0279: }
0280:
0281: /**
0282: * Convenience methods to get an attribute on a cell.
0283: *
0284: * @param row cell's row
0285: * @param column cell's column
0286: * @param attr attribute to get
0287: * @return the attribute's value
0288: * @throws IndexOutOfBoundsException
0289: */
0290: protected String getAttr(int row, int column, String attr) {
0291: Element elem = getElement(row, column);
0292: return DOM.getElementAttribute(elem, attr);
0293: }
0294:
0295: /**
0296: * Convenience methods to set an attribute on a cell.
0297: *
0298: * @param row cell's row
0299: * @param column cell's column
0300: * @param attrName attribute to set
0301: * @param value value to set
0302: * @throws IndexOutOfBoundsException
0303: */
0304: protected void setAttr(int row, int column, String attrName,
0305: String value) {
0306: Element elem = ensureElement(row, column);
0307: DOM.setElementAttribute(elem, attrName, value);
0308: }
0309:
0310: /**
0311: * Native method to get a cell's element.
0312: *
0313: * @param table the table element
0314: * @param row the row of the cell
0315: * @param col the column of the cell
0316: * @return the element
0317: */
0318: private native Element getCellElement(Element table, int row,
0319: int col) /*-{
0320: var out = table.rows[row].cells[col];
0321: return (out == null ? null : out);
0322: }-*/;
0323:
0324: /**
0325: * Gets the TD element representing the specified cell unsafely (meaning
0326: * that it doesn't ensure that the row and column are valid).
0327: *
0328: * @param row the row of the cell to be retrieved
0329: * @param column the column of the cell to be retrieved
0330: * @return the column's TD element
0331: */
0332: private Element getRawElement(int row, int column) {
0333: return getCellElement(bodyElem, row, column);
0334: }
0335: }
0336:
0337: /**
0338: * This class contains methods used to format a table's columns. It is limited
0339: * by the support cross-browser HTML support for column formatting.
0340: */
0341: public class ColumnFormatter {
0342: protected Element columnGroup;
0343:
0344: /**
0345: * Adds a style to the specified column.
0346: *
0347: * @param col the col to which the style will be added
0348: * @param styleName the style name to be added
0349: * @see UIObject#addStyleName(String)
0350: * @throws IndexOutOfBoundsException
0351: */
0352: public void addStyleName(int col, String styleName) {
0353: UIObject.setStyleName(ensureColumn(col), styleName, true);
0354: }
0355:
0356: /**
0357: * Gets the style of the specified column.
0358: *
0359: * @param column the column to be queried
0360: * @return the style name
0361: * @see UIObject#getStyleName()
0362: * @throws IndexOutOfBoundsException
0363: */
0364: public String getStyleName(int column) {
0365: return UIObject.getStyleName(ensureColumn(column));
0366: }
0367:
0368: /**
0369: * Gets the primary style of the specified column.
0370: *
0371: * @param column the column to be queried
0372: * @return the style name
0373: * @see UIObject#getStylePrimaryName()
0374: * @throws IndexOutOfBoundsException
0375: */
0376: public String getStylePrimaryName(int column) {
0377: return UIObject.getStylePrimaryName(ensureColumn(column));
0378: }
0379:
0380: /**
0381: * Removes a style from the specified column.
0382: *
0383: * @param column the column from which the style will be removed
0384: * @param styleName the style name to be removed
0385: * @see UIObject#removeStyleName(String)
0386: * @throws IndexOutOfBoundsException
0387: */
0388: public void removeStyleName(int column, String styleName) {
0389: UIObject.setStyleName(ensureColumn(column), styleName,
0390: false);
0391: }
0392:
0393: /**
0394: * Sets the style name associated with the specified column.
0395: *
0396: * @param column the column whose style name is to be set
0397: * @param styleName the new style name
0398: * @see UIObject#setStyleName(String)
0399: * @throws IndexOutOfBoundsException
0400: */
0401: public void setStyleName(int column, String styleName) {
0402: UIObject.setStyleName(ensureColumn(column), styleName);
0403: }
0404:
0405: /**
0406: * Sets the primary style name associated with the specified column.
0407: *
0408: * @param column the column whose style name is to be set
0409: * @param styleName the new style name
0410: * @see UIObject#setStylePrimaryName(String)
0411: * @throws IndexOutOfBoundsException
0412: */
0413: public void setStylePrimaryName(int column, String styleName) {
0414: UIObject.setStylePrimaryName(ensureColumn(column),
0415: styleName);
0416: }
0417:
0418: /**
0419: * Sets the width of the specified column.
0420: *
0421: * @param column the column of the cell whose width is to be set
0422: * @param width the cell's new width, in percentage or pixel units
0423: * @throws IndexOutOfBoundsException
0424: */
0425: public void setWidth(int column, String width) {
0426: DOM
0427: .setElementProperty(ensureColumn(column), "width",
0428: width);
0429: }
0430:
0431: private Element ensureColumn(int col) {
0432: prepareColumn(col);
0433: prepareColumnGroup();
0434:
0435: int num = DOM.getChildCount(columnGroup);
0436: if (num <= col) {
0437: Element colElement = null;
0438: for (int i = num; i <= col; i++) {
0439: colElement = DOM.createElement("col");
0440: DOM.appendChild(columnGroup, colElement);
0441: }
0442: return colElement;
0443: }
0444: return DOM.getChild(columnGroup, col);
0445: }
0446:
0447: /**
0448: * Prepare the colgroup tag for the first time, guarenteeing that it
0449: * exists and has at least one col tag in it. This method corrects
0450: * a Mozilla issue where the col tag will affect the wrong column if
0451: * a col tag doesn't exist when the element is attached to the page.
0452: */
0453: private void prepareColumnGroup() {
0454: if (columnGroup == null) {
0455: columnGroup = DOM.createElement("colgroup");
0456: DOM.insertChild(tableElem, columnGroup, 0);
0457: DOM.appendChild(columnGroup, DOM.createElement("col"));
0458: }
0459: }
0460: }
0461:
0462: /**
0463: * This class contains methods used to format a table's rows.
0464: */
0465: public class RowFormatter {
0466:
0467: /**
0468: * Adds a style to the specified row.
0469: *
0470: * @param row the row to which the style will be added
0471: * @param styleName the style name to be added
0472: * @see UIObject#addStyleName(String)
0473: * @throws IndexOutOfBoundsException
0474: */
0475: public void addStyleName(int row, String styleName) {
0476: UIObject.setStyleName(ensureElement(row), styleName, true);
0477: }
0478:
0479: /**
0480: * Gets the TR element representing the specified row.
0481: *
0482: * @param row the row whose TR element is to be retrieved
0483: * @return the row's TR element
0484: * @throws IndexOutOfBoundsException
0485: */
0486: public Element getElement(int row) {
0487: checkRowBounds(row);
0488: return getRow(bodyElem, row);
0489: }
0490:
0491: /**
0492: * Gets the style of the specified row.
0493: *
0494: * @param row the row to be queried
0495: * @return the style name
0496: * @see UIObject#getStyleName()
0497: * @throws IndexOutOfBoundsException
0498: */
0499: public String getStyleName(int row) {
0500: return UIObject.getStyleName(getElement(row));
0501: }
0502:
0503: /**
0504: * Gets the primary style of the specified row.
0505: *
0506: * @param row the row to be queried
0507: * @return the style name
0508: * @see UIObject#getStylePrimaryName()
0509: * @throws IndexOutOfBoundsException
0510: */
0511: public String getStylePrimaryName(int row) {
0512: return UIObject.getStylePrimaryName(getElement(row));
0513: }
0514:
0515: /**
0516: * Determines whether or not this row is visible via the display style
0517: * attribute.
0518: *
0519: * @param row the row whose visibility is to be set
0520: * @return <code>true</code> if the row is visible
0521: */
0522: public boolean isVisible(int row) {
0523: Element e = getElement(row);
0524: return UIObject.isVisible(e);
0525: }
0526:
0527: /**
0528: * Removes a style from the specified row.
0529: *
0530: * @param row the row from which the style will be removed
0531: * @param styleName the style name to be removed
0532: * @see UIObject#removeStyleName(String)
0533: * @throws IndexOutOfBoundsException
0534: */
0535: public void removeStyleName(int row, String styleName) {
0536: UIObject.setStyleName(ensureElement(row), styleName, false);
0537: }
0538:
0539: /**
0540: * Sets the style name associated with the specified row.
0541: *
0542: * @param row the row whose style name is to be set
0543: * @param styleName the new style name
0544: * @see UIObject#setStyleName(String)
0545: * @throws IndexOutOfBoundsException
0546: */
0547: public void setStyleName(int row, String styleName) {
0548: UIObject.setStyleName(ensureElement(row), styleName);
0549: }
0550:
0551: /**
0552: * Sets the primary style name associated with the specified row.
0553: *
0554: * @param row the row whose style name is to be set
0555: * @param styleName the new style name
0556: * @see UIObject#setStylePrimaryName(String)
0557: * @throws IndexOutOfBoundsException
0558: */
0559: public void setStylePrimaryName(int row, String styleName) {
0560: UIObject.setStylePrimaryName(ensureElement(row), styleName);
0561: }
0562:
0563: /**
0564: * Sets the vertical alignment of the specified row.
0565: *
0566: * @param row the row whose alignment is to be set
0567: * @param align the row's new vertical alignment as specified in
0568: * {@link HasVerticalAlignment}
0569: * @throws IndexOutOfBoundsException
0570: */
0571: public void setVerticalAlign(int row,
0572: VerticalAlignmentConstant align) {
0573: DOM.setStyleAttribute(ensureElement(row), "verticalAlign",
0574: align.getVerticalAlignString());
0575: }
0576:
0577: /**
0578: * Sets whether this row is visible.
0579: *
0580: * @param row the row whose visibility is to be set
0581: * @param visible <code>true</code> to show the row, <code>false</code>
0582: * to hide it
0583: */
0584: public void setVisible(int row, boolean visible) {
0585: Element e = ensureElement(row);
0586: UIObject.setVisible(e, visible);
0587: }
0588:
0589: /**
0590: * Ensure the TR element representing the specified row exists for
0591: * subclasses that allow dynamic addition of elements.
0592: *
0593: * @param row the row whose TR element is to be retrieved
0594: * @return the row's TR element
0595: * @throws IndexOutOfBoundsException
0596: */
0597: protected Element ensureElement(int row) {
0598: prepareRow(row);
0599: return getRow(bodyElem, row);
0600: }
0601:
0602: protected native Element getRow(Element elem, int row)/*-{
0603: return elem.rows[row];
0604: }-*/;
0605:
0606: /**
0607: * Convenience methods to set an attribute on a row.
0608: *
0609: * @param row cell's row
0610: * @param attrName attribute to set
0611: * @param value value to set
0612: * @throws IndexOutOfBoundsException
0613: */
0614: protected void setAttr(int row, String attrName, String value) {
0615: Element elem = ensureElement(row);
0616: DOM.setElementAttribute(elem, attrName, value);
0617: }
0618: }
0619:
0620: /**
0621: * Creates a mapping from elements to their associated widgets.
0622: */
0623: private static class WidgetMapper {
0624:
0625: private static class FreeNode {
0626: int index;
0627: FreeNode next;
0628:
0629: public FreeNode(int index, FreeNode next) {
0630: this .index = index;
0631: this .next = next;
0632: }
0633: }
0634:
0635: private static native void clearWidgetIndex(Element elem) /*-{
0636: elem["__widgetID"] = null;
0637: }-*/;
0638:
0639: private static native int getWidgetIndex(Element elem) /*-{
0640: var index = elem["__widgetID"];
0641: return (index == null) ? -1 : index;
0642: }-*/;
0643:
0644: private static native void setWidgetIndex(Element elem,
0645: int index) /*-{
0646: elem["__widgetID"] = index;
0647: }-*/;
0648:
0649: private FreeNode freeList = null;
0650:
0651: private final ArrayList<Widget> widgetList = new ArrayList<Widget>();
0652:
0653: /**
0654: * Returns the widget associated with the given element.
0655: *
0656: * @param elem widget's element
0657: * @return the widget
0658: */
0659: public Widget getWidget(Element elem) {
0660: int index = getWidgetIndex(elem);
0661: if (index < 0) {
0662: return null;
0663: }
0664: return widgetList.get(index);
0665: }
0666:
0667: /**
0668: * Adds the Widget.
0669: *
0670: * @param widget widget to add
0671: */
0672: public void putWidget(Widget widget) {
0673: int index;
0674: if (freeList == null) {
0675: index = widgetList.size();
0676: widgetList.add(widget);
0677: } else {
0678: index = freeList.index;
0679: widgetList.set(index, widget);
0680: freeList = freeList.next;
0681: }
0682: setWidgetIndex(widget.getElement(), index);
0683: }
0684:
0685: /**
0686: * Remove the widget associated with the given element.
0687: *
0688: * @param elem the widget's element
0689: */
0690: public void removeWidgetByElement(Element elem) {
0691: int index = getWidgetIndex(elem);
0692: removeImpl(elem, index);
0693: }
0694:
0695: /**
0696: * Creates an iterator of widgets.
0697: *
0698: * @return the iterator
0699: */
0700: public Iterator<Widget> widgetIterator() {
0701: // TODO: look at using the WidgetIterators class!
0702: return new Iterator<Widget>() {
0703: int lastIndex = -1;
0704: int nextIndex = -1;
0705: {
0706: findNext();
0707: }
0708:
0709: public boolean hasNext() {
0710: return nextIndex < widgetList.size();
0711: }
0712:
0713: public Widget next() {
0714: if (!hasNext()) {
0715: throw new NoSuchElementException();
0716: }
0717: Widget result = widgetList.get(nextIndex);
0718: lastIndex = nextIndex;
0719: findNext();
0720: return result;
0721: }
0722:
0723: public void remove() {
0724: if (lastIndex < 0) {
0725: throw new IllegalStateException();
0726: }
0727: Widget w = widgetList.get(lastIndex);
0728: assert (w.getParent() instanceof HTMLTable);
0729: w.removeFromParent();
0730: lastIndex = -1;
0731: }
0732:
0733: private void findNext() {
0734: while (++nextIndex < widgetList.size()) {
0735: if (widgetList.get(nextIndex) != null) {
0736: return;
0737: }
0738: }
0739: }
0740: };
0741: }
0742:
0743: private void removeImpl(Element elem, int index) {
0744: clearWidgetIndex(elem);
0745: widgetList.set(index, null);
0746: freeList = new FreeNode(index, freeList);
0747: }
0748: }
0749:
0750: /**
0751: * Table's body.
0752: */
0753: private final Element bodyElem;
0754:
0755: /**
0756: * Current cell formatter.
0757: */
0758: private CellFormatter cellFormatter;
0759:
0760: /**
0761: * Column Formatter.
0762: */
0763: private ColumnFormatter columnFormatter;
0764:
0765: /**
0766: * Current row formatter.
0767: */
0768: private RowFormatter rowFormatter;
0769:
0770: /**
0771: * Table element.
0772: */
0773: private final Element tableElem;
0774:
0775: /**
0776: * Current table listener.
0777: */
0778: private TableListenerCollection tableListeners;
0779:
0780: private WidgetMapper widgetMap = new WidgetMapper();
0781:
0782: /**
0783: * Create a new empty HTML Table.
0784: */
0785: public HTMLTable() {
0786: tableElem = DOM.createTable();
0787: bodyElem = DOM.createTBody();
0788: DOM.appendChild(tableElem, bodyElem);
0789: setElement(tableElem);
0790: sinkEvents(Event.ONCLICK);
0791: }
0792:
0793: /**
0794: * Adds a listener to the current table.
0795: *
0796: * @param listener listener to add
0797: */
0798: public void addTableListener(TableListener listener) {
0799: if (tableListeners == null) {
0800: tableListeners = new TableListenerCollection();
0801: }
0802: tableListeners.add(listener);
0803: }
0804:
0805: /**
0806: * Removes all widgets from this table, but does not remove other HTML or text
0807: * contents of cells.
0808: */
0809: @Override
0810: public void clear() {
0811: for (int row = 0; row < getRowCount(); ++row) {
0812: for (int col = 0; col < getCellCount(row); ++col) {
0813: Widget child = getWidgetImpl(row, col);
0814: if (child != null) {
0815: remove(child);
0816: }
0817: }
0818: }
0819: }
0820:
0821: /**
0822: * Clears the given row and column. If it contains a Widget, it will be
0823: * removed from the table. If not, its contents will simply be cleared.
0824: *
0825: * @param row the widget's column
0826: * @param column the widget's column
0827: * @return true if a widget was removed
0828: * @throws IndexOutOfBoundsException
0829: */
0830: public boolean clearCell(int row, int column) {
0831: Element td = getCellFormatter().getElement(row, column);
0832: return internalClearCell(td, true);
0833: }
0834:
0835: /**
0836: * Gets the number of cells in a given row.
0837: *
0838: * @param row the row whose cells are to be counted
0839: * @return the number of cells present in the row
0840: */
0841: public abstract int getCellCount(int row);
0842:
0843: /**
0844: * Gets the {@link CellFormatter} associated with this table. Use casting to
0845: * get subclass-specific functionality
0846: *
0847: * @return this table's cell formatter
0848: */
0849: public CellFormatter getCellFormatter() {
0850: return cellFormatter;
0851: }
0852:
0853: /**
0854: * Gets the amount of padding that is added around all cells.
0855: *
0856: * @return the cell padding, in pixels
0857: */
0858: public int getCellPadding() {
0859: return DOM.getElementPropertyInt(tableElem, "cellPadding");
0860: }
0861:
0862: /**
0863: * Gets the amount of spacing that is added around all cells.
0864: *
0865: * @return the cell spacing, in pixels
0866: */
0867: public int getCellSpacing() {
0868: return DOM.getElementPropertyInt(tableElem, "cellSpacing");
0869: }
0870:
0871: /**
0872: * Gets the column formatter.
0873: *
0874: * @return the column formatter
0875: */
0876: public ColumnFormatter getColumnFormatter() {
0877: return columnFormatter;
0878: }
0879:
0880: /**
0881: * Gets the HTML contents of the specified cell.
0882: *
0883: * @param row the cell's row
0884: * @param column the cell's column
0885: * @return the cell's HTML contents
0886: * @throws IndexOutOfBoundsException
0887: */
0888: public String getHTML(int row, int column) {
0889: return DOM.getInnerHTML(cellFormatter.getElement(row, column));
0890: }
0891:
0892: /**
0893: * Gets the number of rows present in this table.
0894: *
0895: * @return the table's row count
0896: */
0897: public abstract int getRowCount();
0898:
0899: /**
0900: * Gets the RowFormatter associated with this table.
0901: *
0902: * @return the table's row formatter
0903: */
0904: public RowFormatter getRowFormatter() {
0905: return rowFormatter;
0906: }
0907:
0908: /**
0909: * Gets the text within the specified cell.
0910: *
0911: * @param row the cell's row
0912: * @param column the cell's column
0913: * @return the cell's text contents
0914: * @throws IndexOutOfBoundsException
0915: */
0916: public String getText(int row, int column) {
0917: checkCellBounds(row, column);
0918: Element e = cellFormatter.getElement(row, column);
0919: return DOM.getInnerText(e);
0920: }
0921:
0922: /**
0923: * Gets the widget in the specified cell.
0924: *
0925: * @param row the cell's row
0926: * @param column the cell's column
0927: * @return the widget in the specified cell, or <code>null</code> if none is
0928: * present
0929: * @throws IndexOutOfBoundsException
0930: */
0931: public Widget getWidget(int row, int column) {
0932: checkCellBounds(row, column);
0933: return getWidgetImpl(row, column);
0934: }
0935:
0936: /**
0937: * Determines whether the specified cell exists.
0938: *
0939: * @param row the cell's row
0940: * @param column the cell's column
0941: * @return <code>true</code> if the specified cell exists
0942: */
0943: public boolean isCellPresent(int row, int column) {
0944: if ((row >= getRowCount()) || (row < 0)) {
0945: return false;
0946: }
0947: if ((column < 0) || (column >= getCellCount(row))) {
0948: return false;
0949: } else {
0950: return true;
0951: }
0952: }
0953:
0954: /**
0955: * Returns an iterator containing all the widgets in this table.
0956: *
0957: * @return the iterator
0958: */
0959: public Iterator<Widget> iterator() {
0960: return widgetMap.widgetIterator();
0961: }
0962:
0963: /**
0964: * Method to process events generated from the browser.
0965: *
0966: * @param event the generated event
0967: */
0968: @Override
0969: public void onBrowserEvent(Event event) {
0970: switch (DOM.eventGetType(event)) {
0971: case Event.ONCLICK: {
0972: if (tableListeners != null) {
0973: // Find out which cell was actually clicked.
0974: Element td = getEventTargetCell(event);
0975: if (td == null) {
0976: return;
0977: }
0978: Element tr = DOM.getParent(td);
0979: Element body = DOM.getParent(tr);
0980: int row = DOM.getChildIndex(body, tr);
0981: int column = DOM.getChildIndex(tr, td);
0982: // Fire the event.
0983: tableListeners.fireCellClicked(this , row, column);
0984: }
0985: break;
0986: }
0987: default: {
0988: // Do nothing
0989: }
0990: }
0991: }
0992:
0993: /**
0994: * Remove the specified widget from the table.
0995: *
0996: * @param widget widget to remove
0997: * @return was the widget removed from the table.
0998: */
0999: @Override
1000: public boolean remove(Widget widget) {
1001: // Validate.
1002: if (widget.getParent() != this ) {
1003: return false;
1004: }
1005:
1006: // Orphan.
1007: orphan(widget);
1008:
1009: // Physical detach.
1010: Element elem = widget.getElement();
1011: DOM.removeChild(DOM.getParent(elem), elem);
1012:
1013: // Logical detach.
1014: widgetMap.removeWidgetByElement(elem);
1015: return true;
1016: }
1017:
1018: /**
1019: * Removes the specified table listener.
1020: *
1021: * @param listener listener to remove
1022: */
1023: public void removeTableListener(TableListener listener) {
1024: if (tableListeners != null) {
1025: tableListeners.remove(listener);
1026: }
1027: }
1028:
1029: /**
1030: * Sets the width of the table's border. This border is displayed around all
1031: * cells in the table.
1032: *
1033: * @param width the width of the border, in pixels
1034: */
1035: public void setBorderWidth(int width) {
1036: DOM.setElementProperty(tableElem, "border", "" + width);
1037: }
1038:
1039: /**
1040: * Sets the amount of padding to be added around all cells.
1041: *
1042: * @param padding the cell padding, in pixels
1043: */
1044: public void setCellPadding(int padding) {
1045: DOM.setElementPropertyInt(tableElem, "cellPadding", padding);
1046: }
1047:
1048: /**
1049: * Sets the amount of spacing to be added around all cells.
1050: *
1051: * @param spacing the cell spacing, in pixels
1052: */
1053: public void setCellSpacing(int spacing) {
1054: DOM.setElementPropertyInt(tableElem, "cellSpacing", spacing);
1055: }
1056:
1057: /**
1058: * Sets the HTML contents of the specified cell.
1059: *
1060: * @param row the cell's row
1061: * @param column the cell's column
1062: * @param html the cell's HTML contents
1063: * @throws IndexOutOfBoundsException
1064: */
1065: public void setHTML(int row, int column, String html) {
1066: prepareCell(row, column);
1067: Element td = cleanCell(row, column, html == null);
1068: if (html != null) {
1069: DOM.setInnerHTML(td, html);
1070: }
1071: }
1072:
1073: /**
1074: * Sets the text within the specified cell.
1075: *
1076: * @param row the cell's row
1077: * @param column cell's column
1078: * @param text the cell's text contents
1079: * @throws IndexOutOfBoundsException
1080: */
1081: public void setText(int row, int column, String text) {
1082: prepareCell(row, column);
1083: Element td;
1084: td = cleanCell(row, column, text == null);
1085: if (text != null) {
1086: DOM.setInnerText(td, text);
1087: }
1088: }
1089:
1090: /**
1091: * Sets the widget within the specified cell.
1092: * <p>
1093: * Inherited implementations may either throw IndexOutOfBounds exception if
1094: * the cell does not exist, or allocate a new cell to store the content.
1095: * </p>
1096: * <p>
1097: * FlexTable will automatically allocate the cell at the correct location and
1098: * then set the widget. Grid will set the widget if and only if the cell is
1099: * within the Grid's bounding box.
1100: * </p>
1101: *
1102: * @param widget The widget to be added
1103: * @param row the cell's row
1104: * @param column the cell's column
1105: * @throws IndexOutOfBoundsException
1106: */
1107: public void setWidget(int row, int column, Widget widget) {
1108: prepareCell(row, column);
1109: if (widget != null) {
1110: widget.removeFromParent();
1111:
1112: // Removes any existing widget.
1113: Element td = cleanCell(row, column, true);
1114:
1115: // Logical attach.
1116: widgetMap.putWidget(widget);
1117:
1118: // Physical attach.
1119: DOM.appendChild(td, widget.getElement());
1120:
1121: adopt(widget);
1122: }
1123: }
1124:
1125: /**
1126: * Bounds checks that the cell exists at the specified location.
1127: *
1128: * @param row cell's row
1129: * @param column cell's column
1130: * @throws IndexOutOfBoundsException
1131: */
1132: protected void checkCellBounds(int row, int column) {
1133: checkRowBounds(row);
1134: if (column < 0) {
1135: throw new IndexOutOfBoundsException("Column " + column
1136: + " must be non-negative: " + column);
1137: }
1138: int cellSize = getCellCount(row);
1139: if (cellSize <= column) {
1140: throw new IndexOutOfBoundsException("Column index: "
1141: + column + ", Column size: " + getCellCount(row));
1142: }
1143: }
1144:
1145: /**
1146: * Checks that the row is within the correct bounds.
1147: *
1148: * @param row row index to check
1149: * @throws IndexOutOfBoundsException
1150: */
1151: protected void checkRowBounds(int row) {
1152: int rowSize = getRowCount();
1153: if ((row >= rowSize) || (row < 0)) {
1154: throw new IndexOutOfBoundsException("Row index: " + row
1155: + ", Row size: " + rowSize);
1156: }
1157: }
1158:
1159: /**
1160: * Creates a new cell. Override this method if the cell should have initial
1161: * contents.
1162: *
1163: * @return the newly created TD
1164: */
1165: protected Element createCell() {
1166: return DOM.createTD();
1167: }
1168:
1169: /**
1170: * Gets the table's TBODY element.
1171: *
1172: * @return the TBODY element
1173: */
1174: protected Element getBodyElement() {
1175: return bodyElem;
1176: }
1177:
1178: /**
1179: * Directly ask the underlying DOM what the cell count on the given row is.
1180: *
1181: * @param tableBody the element
1182: * @param row the row
1183: * @return number of columns in the row
1184: */
1185: protected native int getDOMCellCount(Element tableBody, int row) /*-{
1186: return tableBody.rows[row].cells.length;
1187: }-*/;
1188:
1189: /**
1190: * Directly ask the underlying DOM what the cell count on the given row is.
1191: *
1192: * @param row the row
1193: * @return number of columns in the row
1194: */
1195: protected int getDOMCellCount(int row) {
1196: return getDOMCellCount(bodyElem, row);
1197: }
1198:
1199: /**
1200: * Directly ask the underlying DOM what the row count is.
1201: *
1202: * @return Returns the number of rows in the table
1203: */
1204: protected int getDOMRowCount() {
1205: return getDOMRowCount(bodyElem);
1206: }
1207:
1208: protected native int getDOMRowCount(Element elem) /*-{
1209: return elem.rows.length;
1210: }-*/;
1211:
1212: /**
1213: * Determines the TD associated with the specified event.
1214: *
1215: * @param event the event to be queried
1216: * @return the TD associated with the event, or <code>null</code> if none is
1217: * found.
1218: */
1219: protected Element getEventTargetCell(Event event) {
1220: Element td = DOM.eventGetTarget(event);
1221: for (; td != null; td = DOM.getParent(td)) {
1222: // If it's a TD, it might be the one we're looking for.
1223: if (DOM.getElementProperty(td, "tagName").equalsIgnoreCase(
1224: "td")) {
1225: // Make sure it's directly a part of this table before returning
1226: // it.
1227: Element tr = DOM.getParent(td);
1228: Element body = DOM.getParent(tr);
1229: if (DOM.compare(body, bodyElem)) {
1230: return td;
1231: }
1232: }
1233: // If we run into this table's body, we're out of options.
1234: if (DOM.compare(td, bodyElem)) {
1235: return null;
1236: }
1237: }
1238: return null;
1239: }
1240:
1241: /**
1242: * Inserts a new cell into the specified row.
1243: *
1244: * @param row the row into which the new cell will be inserted
1245: * @param column the column before which the cell will be inserted
1246: * @throws IndexOutOfBoundsException
1247: */
1248: protected void insertCell(int row, int column) {
1249: Element tr = rowFormatter.getRow(bodyElem, row);
1250: Element td = createCell();
1251: DOM.insertChild(tr, td, column);
1252: }
1253:
1254: /**
1255: * Inserts a number of cells before the specified cell.
1256: *
1257: * @param row the row into which the new cells will be inserted
1258: * @param column the column before which the new cells will be inserted
1259: * @param count number of cells to be inserted
1260: * @throws IndexOutOfBoundsException
1261: */
1262: protected void insertCells(int row, int column, int count) {
1263: Element tr = rowFormatter.getRow(bodyElem, row);
1264: for (int i = column; i < column + count; i++) {
1265: Element td = createCell();
1266: DOM.insertChild(tr, td, i);
1267: }
1268: }
1269:
1270: /**
1271: * Inserts a new row into the table.
1272: *
1273: * @param beforeRow the index before which the new row will be inserted
1274: * @return the index of the newly-created row
1275: * @throws IndexOutOfBoundsException
1276: */
1277: protected int insertRow(int beforeRow) {
1278: // Specifically allow the row count as an insert position.
1279: if (beforeRow != getRowCount()) {
1280: checkRowBounds(beforeRow);
1281: }
1282: Element tr = DOM.createTR();
1283: DOM.insertChild(bodyElem, tr, beforeRow);
1284: return beforeRow;
1285: }
1286:
1287: /**
1288: * Does actual clearing, used by clearCell and cleanCell. All HTMLTable
1289: * methods should use internalClearCell rather than clearCell, as clearCell
1290: * may be overridden in subclasses to format an empty cell.
1291: *
1292: * @param td element to clear
1293: * @param clearInnerHTML should the cell's inner html be cleared?
1294: * @return returns whether a widget was cleared
1295: */
1296: protected boolean internalClearCell(Element td,
1297: boolean clearInnerHTML) {
1298: Element maybeChild = DOM.getFirstChild(td);
1299: Widget widget = null;
1300: if (maybeChild != null) {
1301: widget = widgetMap.getWidget(maybeChild);
1302: }
1303: if (widget != null) {
1304: // If there is a widget, remove it.
1305: remove(widget);
1306: return true;
1307: } else {
1308: // Otherwise, simply clear whatever text and/or HTML may be there.
1309: if (clearInnerHTML) {
1310: DOM.setInnerHTML(td, "");
1311: }
1312: return false;
1313: }
1314: }
1315:
1316: /**
1317: * Subclasses must implement this method. It allows them to decide what to do
1318: * just before a cell is accessed. If the cell already exists, this method
1319: * must do nothing. Otherwise, a subclass must either ensure that the cell
1320: * exists or throw an {@link IndexOutOfBoundsException}.
1321: *
1322: * @param row the cell's row
1323: * @param column the cell's column
1324: */
1325: protected abstract void prepareCell(int row, int column);
1326:
1327: /**
1328: * Subclasses can implement this method. It allows them to decide what to do
1329: * just before a column is accessed. For classes, such as
1330: * <code>FlexTable</code>, that do not have a concept of a global column
1331: * length can ignore this method.
1332: *
1333: * @param column the cell's column
1334: * @throws IndexOutOfBoundsException
1335: */
1336: protected void prepareColumn(int column) {
1337: // By default, do nothing.
1338: }
1339:
1340: /**
1341: * Subclasses must implement this method. If the row already exists, this
1342: * method must do nothing. Otherwise, a subclass must either ensure that the
1343: * row exists or throw an {@link IndexOutOfBoundsException}.
1344: *
1345: * @param row the cell's row
1346: */
1347: protected abstract void prepareRow(int row);
1348:
1349: /**
1350: * Removes the specified cell from the table.
1351: *
1352: * @param row the row of the cell to remove
1353: * @param column the column of cell to remove
1354: * @throws IndexOutOfBoundsException
1355: */
1356: protected void removeCell(int row, int column) {
1357: checkCellBounds(row, column);
1358: Element td = cleanCell(row, column, false);
1359: Element tr = rowFormatter.getRow(bodyElem, row);
1360: DOM.removeChild(tr, td);
1361: }
1362:
1363: /**
1364: * Removes the specified row from the table.
1365: *
1366: * @param row the index of the row to be removed
1367: * @throws IndexOutOfBoundsException
1368: */
1369: protected void removeRow(int row) {
1370: int columnCount = getCellCount(row);
1371: for (int column = 0; column < columnCount; ++column) {
1372: cleanCell(row, column, false);
1373: }
1374: DOM.removeChild(bodyElem, rowFormatter.getRow(bodyElem, row));
1375: }
1376:
1377: /**
1378: * Sets the table's CellFormatter.
1379: *
1380: * @param cellFormatter the table's cell formatter
1381: */
1382: protected void setCellFormatter(CellFormatter cellFormatter) {
1383: this .cellFormatter = cellFormatter;
1384: }
1385:
1386: protected void setColumnFormatter(ColumnFormatter formatter) {
1387: columnFormatter = formatter;
1388: columnFormatter.prepareColumnGroup();
1389: }
1390:
1391: /**
1392: * Sets the table's RowFormatter.
1393: *
1394: * @param rowFormatter the table's row formatter
1395: */
1396: protected void setRowFormatter(RowFormatter rowFormatter) {
1397: this .rowFormatter = rowFormatter;
1398: }
1399:
1400: /**
1401: * Removes any widgets, text, and HTML within the cell. This method assumes
1402: * that the requested cell already exists.
1403: *
1404: * @param row the cell's row
1405: * @param column the cell's column
1406: * @param clearInnerHTML should the cell's inner html be cleared?
1407: * @return element that has been cleaned
1408: */
1409: private Element cleanCell(int row, int column,
1410: boolean clearInnerHTML) {
1411: // Clear whatever is in the cell.
1412: Element td = getCellFormatter().getRawElement(row, column);
1413: internalClearCell(td, clearInnerHTML);
1414: return td;
1415: }
1416:
1417: /**
1418: * Gets the Widget associated with the given cell.
1419: *
1420: * @param row the cell's row
1421: * @param column the cell's column
1422: * @return the widget
1423: */
1424: private Widget getWidgetImpl(int row, int column) {
1425: Element e = cellFormatter.getRawElement(row, column);
1426: Element child = DOM.getFirstChild(e);
1427: if (child == null) {
1428: return null;
1429: } else {
1430: return widgetMap.getWidget(child);
1431: }
1432: }
1433: }
|