001    /*
002     * Copyright 2006 Google Inc.
003     * 
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005     * use this file except in compliance with the License. You may obtain a copy of
006     * the License at
007     * 
008     * http://www.apache.org/licenses/LICENSE-2.0
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013     * License for the specific language governing permissions and limitations under
014     * the License.
015     */
016    package com.google.gwt.user.client.ui;
017    
018    import com.google.gwt.user.client.DOM;
019    import com.google.gwt.user.client.Element;
020    import com.google.gwt.user.client.Event;
021    import com.google.gwt.user.client.ui.HasHorizontalAlignment.HorizontalAlignmentConstant;
022    import com.google.gwt.user.client.ui.HasVerticalAlignment.VerticalAlignmentConstant;
023    
024    import java.util.HashMap;
025    import java.util.Iterator;
026    import java.util.Map;
027    
028    /**
029     * HTMLTable contains the common table algorithms for
030     * {@link com.google.gwt.user.client.ui.Grid} and
031     * {@link com.google.gwt.user.client.ui.FlexTable}.
032     * 
033     * <p>
034     * <img class='gallery' src='Table.png'/>
035     * </p>
036     */
037    public abstract class HTMLTable extends Panel implements SourcesTableEvents {
038    
039      /**
040       * This class contains methods used to format a table's cells.
041       */
042      public class CellFormatter {
043    
044        /**
045         * Adds a style to the specified cell.
046         * 
047         * @param row the cell's row
048         * @param column the cell's column
049         * @param styleName the style name to be added
050         * @see UIObject#addStyleName(String)
051         */
052        public void addStyleName(int row, int column, String styleName) {
053          prepareCell(row, column);
054          UIObject.setStyleName(getElement(row, column), styleName, true);
055        }
056    
057        /**
058         * Gets the TD element representing the specified cell.
059         * 
060         * @param row the row of the cell to be retrieved
061         * @param column the column of the cell to be retrieved
062         * @return the column's TD element
063         * @throws IndexOutOfBoundsException
064         */
065        public Element getElement(int row, int column) {
066          checkCellBounds(row, column);
067          return DOM.getChild(rowFormatter.getElement(row), column);
068        }
069    
070        /**
071         * Gets a style from a specified row.
072         * 
073         * @param row the row of the cell which the style while be added to
074         * @param column the column of the cell which the style will be added to
075         * @param styleName the style name to be added
076         * @see UIObject#getStyleName(String)
077         * @throws IndexOutOfBoundsException
078         */
079        public String getStyleName(int row, int column) {
080          return DOM.getAttribute(getElement(row, column), "className");
081        }
082    
083        /**
084         * Removes a style from the specified cell.
085         * 
086         * @param row the cell's row
087         * @param column the cell's column
088         * @param styleName the style name to be removed
089         * @see UIObject#removeStyleName(String)
090         * @throws IndexOutOfBoundsException
091         */
092        public void removeStyleName(int row, int column, String styleName) {
093          checkCellBounds(row, column);
094          UIObject.setStyleName(getElement(row, column), styleName, false);
095        }
096    
097        /**
098         * Sets the horizontal and vertical alignment of the specified cell's
099         * contents.
100         * 
101         * @param row the row of the cell whose alignment is to be set
102         * @param column the cell whose alignment is to be set
103         * @param hAlign the cell's new horizontal alignment as specified in
104         *          {@link HasHorizontalAlignment}
105         * @param vAlign the cell's new vertical alignment as specified in
106         *          {@link HasVerticalAlignment}
107         * @throws IndexOutOfBoundsException
108         */
109        public void setAlignment(int row, int column,
110            HorizontalAlignmentConstant hAlign, VerticalAlignmentConstant vAlign) {
111          prepareCell(row, column);
112          setHorizontalAlignment(row, column, hAlign);
113          setVerticalAlignment(row, column, vAlign);
114        }
115    
116        /**
117         * Sets the height of the specified cell.
118         * 
119         * @param row the row of the cell whose height is to be set
120         * @param column the cell whose height is to be set
121         * @param height the cell's new height, in CSS units
122         * @throws IndexOutOfBoundsException
123         */
124        public void setHeight(int row, int column, String height) {
125          prepareCell(row, column);
126          DOM
127            .setAttribute(getCellElement(tableElem, row, column), "height", height);
128        }
129    
130        /**
131         * Sets the horizontal alignment of the specified cell.
132         * 
133         * @param row the row of the cell whose alignment is to be set
134         * @param column the cell whose alignment is to be set
135         * @param align the cell's new horizontal alignment as specified in
136         *          {@link HasHorizontalAlignment}.
137         * @throws IndexOutOfBoundsException
138         */
139        public void setHorizontalAlignment(int row, int column,
140            HorizontalAlignmentConstant align) {
141          prepareCell(row, column);
142          DOM.setStyleAttribute(getCellElement(tableElem, row, column),
143            "textAlign", align.getTextAlignString());
144        }
145    
146        /**
147         * Sets the style name associated with the specified cell.
148         * 
149         * @param row the row of the cell whose style name is to be set
150         * @param column the column of the cell whose style name is to be set
151         * @param styleName the new style name
152         * @see UIObject#setStyleName(String)
153         * @throws IndexOutOfBoundsException
154         */
155        public void setStyleName(int row, int column, String styleName) {
156          prepareCell(row, column);
157          setAttr(row, column, "className", styleName);
158        }
159    
160        /**
161         * Sets the vertical alignment of the specified cell.
162         * 
163         * @param row the row of the cell whose alignment is to be set
164         * @param column the cell whose alignment is to be set
165         * @param align the cell's new vertical alignment as specified in
166         *          {@link HasVerticalAlignment}.
167         * @throws IndexOutOfBoundsException
168         */
169        public void setVerticalAlignment(int row, int column,
170            VerticalAlignmentConstant align) {
171          prepareCell(row, column);
172          DOM.setStyleAttribute(getCellElement(tableElem, row, column),
173            "verticalAlign", align.getVerticalAlignString());
174        }
175    
176        /**
177         * Sets the width of the specified cell.
178         * 
179         * @param row the row of the cell whose width is to be set
180         * @param column the cell whose width is to be set
181         * @param width the cell's new width, in CSS units
182         * @throws IndexOutOfBoundsException
183         */
184        public void setWidth(int row, int column, String width) {
185          // Give the subclass a chance to prepare the cell.
186          prepareCell(row, column);
187          DOM.setAttribute(getCellElement(tableElem, row, column), "width", width);
188        }
189    
190        /**
191         * Sets whether the specified cell will allow word wrapping of its contents.
192         * 
193         * @param row the row of the cell whose word-wrap is to be set
194         * @param column the cell whose word-wrap is to be set
195         * @param wrap <code>false </code> to disable word wrapping in this cell
196         * @throws IndexOutOfBoundsException
197         */
198        public void setWordWrap(int row, int column, boolean wrap) {
199          prepareCell(row, column);
200          DOM.setStyleAttribute(getElement(row, column), "whiteSpace", wrap ? ""
201            : "nowrap");
202        }
203    
204        /**
205         * Gets the element associated with a cell. If it does not exist and the
206         * subtype allows creation of elements, creates it.
207         * 
208         * @param row the cell's row
209         * @param column the cell's column
210         * @return the cell's element
211         * @throws IndexOutOfBoundsException
212         */
213        protected Element ensureElement(int row, int column) {
214          prepareCell(row, column);
215          return DOM.getChild(rowFormatter.ensureElement(row), column);
216        }
217    
218        /**
219         * Convenience methods to get an attribute on a cell.
220         * 
221         * @param row cell's row
222         * @param column cell's column
223         * @param attr attribute to get
224         * @return the attribute's value
225         * @throws IndexOutOfBoundsException
226         */
227        protected String getAttr(int row, int column, String attr) {
228          Element elem = getElement(row, column);
229          return DOM.getAttribute(elem, attr);
230        }
231    
232        /**
233         * Convenience methods to set an attribute on a cell.
234         * 
235         * @param row cell's row
236         * @param column cell's column
237         * @param attr attribute to set
238         * @param value value to set
239         * @throws IndexOutOfBoundsException
240         */
241        protected void setAttr(int row, int column, String attrName, String value) {
242          Element elem = ensureElement(row, column);
243          DOM.setAttribute(elem, attrName, value);
244        }
245    
246        /**
247         * Native method to get a cell's element.
248         * 
249         * @param table the table element
250         * @param row the row of the cell
251         * @param col the column of the cell
252         * @return the element
253         */
254        private native Element getCellElement(Element table, int row, int col) /*-{
255         return table.rows[row].cells[col];
256         }-*/;
257    
258        /**
259         * Gets the TD element representing the specified cell unsafely (meaning
260         * that it doesn't ensure that the row and column are valid).
261         * 
262         * @param row the row of the cell to be retrieved
263         * @param column the column of the cell to be retrieved
264         * @return the column's TD element
265         */
266        private Element getRawElement(int row, int column) {
267          return getCellElement(tableElem, row, column);
268        }
269      }
270    
271      /**
272       * This class contains methods used to format a table's rows.
273       */
274      public class RowFormatter {
275    
276        /**
277         * Adds a style to the specified row.
278         * 
279         * @param row the row to which the style while be added
280         * @param styleName the style name to be added
281         * @see UIObject#addStyleName(String)
282         * @throws IndexOutOfBoundsException
283         */
284        public void addStyleName(int row, String styleName) {
285          UIObject.setStyleName(ensureElement(row), styleName, true);
286        }
287    
288        /**
289         * Gets the TR element representing the specified row.
290         * 
291         * @param row the row whose TR element is to be retrieved
292         * @return the row's TR element
293         * @throws IndexOutOfBoundsException
294         */
295        public Element getElement(int row) {
296          checkRowBounds(row);
297          return DOM.getChild(bodyElem, row);
298        }
299    
300        /**
301         * Gets a style from a specified row.
302         * 
303         * @param row the row to which the style while be added
304         * @see UIObject#getStyleName(String)
305         * @throws IndexOutOfBoundsException
306         */
307        public String getStyleName(int row) {
308          return DOM.getAttribute(getElement(row), "className");
309        }
310    
311        /**
312         * Removes a style from the specified row.
313         * 
314         * @param row the row to which the style while be removed
315         * @param styleName the style name to be removed
316         * @see UIObject#removeStyleName(String)
317         * @throws IndexOutOfBoundsException
318         */
319        public void removeStyleName(int row, String styleName) {
320          UIObject.setStyleName(getElement(row), styleName, false);
321        }
322    
323        /**
324         * Sets the style name associated with the specified row.
325         * 
326         * @param row the row whose style name is to be set
327         * @param styleName the new style name
328         * @see UIObject#setStyleName(String)
329         * @throws IndexOutOfBoundsException
330         */
331        public void setStyleName(int row, String styleName) {
332          Element elem = ensureElement(row);
333          DOM.setAttribute(elem, "className", styleName);
334        }
335    
336    
337        /**
338         * Sets the vertical alignment of the specified row.
339         * 
340         * @param row the row whose alignment is to be set
341         * @param align the row's new vertical alignment as specified in
342         *          {@link HasVerticalAlignment}
343         * @throws IndexOutOfBoundsException
344         */
345        public void setVerticalAlign(int row, VerticalAlignmentConstant align) {
346          DOM.setStyleAttribute(ensureElement(row), "verticalAlign", align
347            .getVerticalAlignString());
348        }
349    
350        /**
351         * Ensure the TR element representing the specified row exists for
352         * subclasses that allow dynamic addition of elements.
353         * 
354         * @param row the row whose TR element is to be retrieved
355         * @return the row's TR element
356         * @throws IndexOutOfBoundsException
357         */
358        protected Element ensureElement(int row) {
359          prepareRow(row);
360          return DOM.getChild(bodyElem, row);
361        }
362      }
363    
364      /**
365       * Table's body
366       */
367      private Element bodyElem;
368    
369      /**
370       * Current cell formatter
371       */
372      private CellFormatter cellFormatter;
373    
374      /**
375       * Current row formatter
376       */
377      private RowFormatter rowFormatter;
378    
379      /**
380       * Table element
381       */
382      private Element tableElem;
383    
384      /**
385       * Current table listener
386       */
387      private TableListenerCollection tableListeners;
388    
389      /**
390       * The element map, used to quickly look up the Widget in a particular cell.
391       * We have to use a map here, because hanging references to Widgets from
392       * Elements would cause memory leaks.
393       */
394      private Map widgetMap = new HashMap();
395    
396      public HTMLTable() {
397        tableElem = DOM.createTable();
398        bodyElem = DOM.createTBody();
399        DOM.appendChild(tableElem, bodyElem);
400        setElement(tableElem);
401        sinkEvents(Event.ONCLICK);
402      }
403    
404      /**
405       * This method is not implemented, as widgets must be added to specific cells
406       * in the table.
407       */
408      public boolean add(Widget w) {
409        return false;
410      }
411    
412      public void addTableListener(TableListener listener) {
413        if (tableListeners == null)
414          tableListeners = new TableListenerCollection();
415        tableListeners.add(listener);
416      }
417    
418      /**
419       * Removes all widgets from this table, but does not remove other HTML or text
420       * contents of cells.
421       */
422      public void clear() {
423        for (int row = 0; row < getRowCount(); ++row) {
424          for (int col = 0; col < getCellCount(row); ++col) {
425            Widget child = getWidget(row, col);
426            if (child != null) {
427              removeWidget(row, col, child);
428            }
429          }
430        }
431      }
432    
433      /**
434       * Clears the given row and column. If it contains a Widget, it will be
435       * removed from the table. If not, its contents will simply be cleared.
436       * 
437       * @param row the widget's column
438       * @param column the widget's column
439       * @return true if a widget was removed
440       * @throws IndexOutOfBoundsException
441       */
442      public boolean clearCell(int row, int column) {
443        Element td = getCellFormatter().getElement(row, column);
444        boolean b = internalClearCell(row, column, td);
445        return b;
446      }
447    
448    
449      /**
450       * Gets the number of cells in a given row.
451       * 
452       * @param row the row whose cells are to be counted
453       * @return the number of cells present in the row
454       */
455      public abstract int getCellCount(int row);
456    
457      /**
458       * Gets the {@link CellFormatter} associated with this table.
459       * 
460       * @return this table's cell formatter
461       */
462      public CellFormatter getCellFormatter() {
463        return cellFormatter;
464      }
465    
466      /**
467       * Gets the amount of padding that is added around all cells.
468       * 
469       * @return the cell padding, in pixels
470       */
471      public int getCellPadding() {
472        return DOM.getIntAttribute(tableElem, "cellPadding");
473      }
474    
475      /**
476       * Gets the amount of spacing that is added around all cells.
477       * 
478       * @return the cell spacing, in pixels
479       */
480      public int getCellSpacing() {
481        return DOM.getIntAttribute(tableElem, "cellSpacing");
482      }
483    
484      /**
485       * Gets the HTML contents of the specified cell.
486       * 
487       * @param row the cell's row
488       * @param column the cell's column
489       * @return the cell's HTML contents
490       * @throws IndexOutOfBoundsException
491       */
492      public String getHTML(int row, int column) {
493        return DOM.getInnerHTML(cellFormatter.getElement(row, column));
494      }
495    
496      /**
497       * Gets the number of rows present in this table.
498       * 
499       * @return the table's row count
500       */
501      public abstract int getRowCount();
502    
503      /**
504       * Gets the RowFormatter associated with this table.
505       * 
506       * @return the table's row formatter
507       */
508      public RowFormatter getRowFormatter() {
509        return rowFormatter;
510      }
511    
512      /**
513       * Gets the text within the specified cell.
514       * 
515       * @param row the cell's row
516       * @param column the cell's column
517       * @return the cell's text contents
518       * @throws IndexOutOfBoundsException
519       */
520      public String getText(int row, int column) {
521        checkCellBounds(row, column);
522        Element e = cellFormatter.getElement(row, column);
523        return DOM.getInnerText(e);
524      }
525    
526      /**
527       * Gets the widget in the specified cell.
528       * 
529       * @param row the cell's row
530       * @param column the cell's column
531       * @return the widget in the specified cell, or <code>null</code> if none is
532       *         present
533       * @throws IndexOutOfBoundsException
534       */
535      public Widget getWidget(int row, int column) {
536        checkCellBounds(row, column);
537        String key = computeKey(row, column);
538        return (Widget) widgetMap.get(key);
539      }
540    
541      /**
542       * Determines whether the specified cell exists.
543       * 
544       * @param row the cell's row
545       * @param column the cell's column
546       * @return <code>true</code> if the specified cell exists
547       */
548      public boolean isCellPresent(int row, int column) {
549        if (row >= getRowCount() && row < 0) {
550          return false;
551        }
552        if (column < 0 || column >= getCellCount(row)) {
553          return false;
554        } else {
555          return true;
556        }
557      }
558    
559      public Iterator iterator() {
560        return widgetMap.values().iterator();
561      }
562    
563      public void onBrowserEvent(Event event) {
564        switch (DOM.eventGetType(event)) {
565          case Event.ONCLICK: {
566            if (tableListeners != null) {
567              // Find out which cell was actually clicked.
568              Element td = getEventTargetCell(event);
569              if (td == null)
570                return;
571              Element tr = DOM.getParent(td);
572              Element body = DOM.getParent(tr);
573              int row = DOM.getChildIndex(body, tr);
574              int column = DOM.getChildIndex(tr, td);
575    
576              // Fire the event.
577              tableListeners.fireCellClicked(this, row, column);
578            }
579            break;
580          }
581        }
582      }
583    
584      public boolean remove(Widget widget) {
585        // Make sure the Widget is actually contained in this table.
586        if (widget.getParent() != this)
587          return false;
588    
589        // Get the row and column of the cell containing this widget.
590        Element td = DOM.getParent(widget.getElement());
591        Element tr = DOM.getParent(td);
592        int row = DOM.getChildIndex(bodyElem, tr);
593        int column = DOM.getChildIndex(tr, td);
594    
595        removeWidget(row, column, widget);
596        return true;
597      }
598    
599      public void removeTableListener(TableListener listener) {
600        if (tableListeners != null)
601          tableListeners.remove(listener);
602      }
603    
604      /**
605       * Sets the width of the table's border. This border is displayed around all
606       * cells in the table.
607       * 
608       * @param width the width of the border, in pixels
609       */
610      public void setBorderWidth(int width) {
611        DOM.setAttribute(tableElem, "border", "" + width);
612      }
613    
614      /**
615       * Sets the amount of padding to be added around all cells.
616       * 
617       * @param padding the cell padding, in pixels
618       */
619      public void setCellPadding(int padding) {
620        DOM.setIntAttribute(tableElem, "cellPadding", padding);
621      }
622    
623      /**
624       * Sets the amount of spacing to be added around all cells.
625       * 
626       * @return the cell spacing, in pixels
627       */
628      public void setCellSpacing(int spacing) {
629        DOM.setIntAttribute(tableElem, "cellSpacing", spacing);
630      }
631    
632      /**
633       * Sets the HTML contents of the specified cell.
634       * 
635       * @param row the cell's row
636       * @param column the cell's column
637       * @param html the cell's HTML contents
638       * @throws IndexOutOfBoundsException
639       */
640      public void setHTML(int row, int column, String html) {
641        prepareCell(row, column);
642        Element td = cleanCell(row, column);
643        DOM.setInnerHTML(td, html);
644      }
645    
646      /**
647       * Sets the text within the specified cell.
648       * 
649       * @param row the cell's row
650       * @param column cell's column
651       * @param text the cell's text contents
652       * @throws IndexOutOfBoundsException
653       */
654      public void setText(int row, int column, String text) {
655        prepareCell(row, column);
656        Element td = cleanCell(row, column);
657        DOM.setInnerText(td, text);
658      }
659    
660      /**
661       * Sets the widget within the specified cell.
662       * 
663       * <p>
664       * Inherited implementations may either throw IndexOutOfBounds exception if
665       * the cell does not exist, or allocate a new cell to store the content
666       * </p>
667       * 
668       * @param widget The widget to be added
669       * @param row the cell's row
670       * @param column the cell's column
671       * @throws IndexOutOfBoundsException
672       */
673      public void setWidget(int row, int column, Widget widget) {
674        prepareCell(row, column);
675    
676        // Attach it to the cell's TD.
677        Element td = cleanCell(row, column);
678        DOM.appendChild(td, widget.getElement());
679    
680        // Add the widget to the map.
681        widgetMap.put(computeKey(row, column), widget);
682    
683        // Set the widget's parent.
684        adopt(widget);
685      }
686    
687      /**
688       * Bounds checks that the cell exists at the specified location
689       * 
690       * @param row cell's row
691       * @param column cell's column
692       * @throws IndexOutOfBoundsException
693       */
694      protected void checkCellBounds(int row, int column) {
695        checkRowBounds(row);
696        if (column < 0) {
697          throw new IndexOutOfBoundsException("Column " + column
698            + " must be non-negative: " + column);
699        }
700        int cellSize = getCellCount(row);
701        if (cellSize < column) {
702          throw new IndexOutOfBoundsException("Column " + column
703            + " does not exist, col at row " + row + " size is "
704            + getCellCount(row) + "cell(s)");
705        }
706      }
707    
708      /**
709       * Checks that the row is within the correct bounds.
710       * 
711       * @param row row index to check
712       * @throws IndexOutOfBoundsException
713       */
714      protected void checkRowBounds(int row) {
715        int rowSize = getRowCount();
716        if (row >= rowSize || row < 0) {
717          throw new IndexOutOfBoundsException("Row " + row
718            + " does not exist, row size is " + getRowCount());
719        }
720      }
721    
722      /**
723       * Gets the key associated with the cell. This key is used within the widget
724       * map.
725       * 
726       * @param row the cell's row
727       * @param column the cell's column
728       * @return the associated key
729       * @skip
730       */
731      protected String computeKey(int row, int column) {
732        return row + "-" + column;
733      }
734    
735      /**
736       * Creates a new cell. Override this method if the cell should have initial
737       * contents.
738       * 
739       * @return the newly created TD
740       */
741      protected Element createCell() {
742        return DOM.createTD();
743      }
744    
745      /**
746       * Gets the table's TBODY element.
747       * 
748       * @return the TBODY element
749       */
750      protected Element getBodyElement() {
751        return bodyElem;
752      }
753    
754      /**
755       * Directly ask the underlying DOM what the cell count on the given row is.
756       * 
757       * @param row the row
758       * @return number of columns in the row
759       */
760      protected int getDOMCellCount(int row) {
761        return DOM.getChildCount(DOM.getChild(bodyElem, row));
762      }
763    
764      /**
765       * Directly ask the underlying DOM what the row count is.
766       * 
767       * @return Returns the number of rows in the table
768       */
769      protected int getDOMRowCount() {
770        return DOM.getChildCount(bodyElem);
771      }
772    
773      /**
774       * Inserts a new cell into the specified row.
775       * 
776       * @param row the row into which the new cell will be inserted
777       * @param column the column before which the cell will be inserted
778       * @throws IndexOutOfBoundsException
779       */
780      protected void insertCell(int row, int column) {
781        Element tr = DOM.getChild(bodyElem, row);
782        Element td = createCell();
783        DOM.insertChild(tr, td, column);
784      }
785    
786      /**
787       * Inserts a number of cells before the specified cell.
788       * 
789       * @param row the row into which the new cells will be inserted
790       * @param column the column before which the new cells will be inserted
791       * @param count number of cells to be inserted
792       * @throws IndexOutOfBoundsException
793       */
794      protected void insertCells(int row, int column, int count) {
795        Element tr = DOM.getChild(bodyElem, row);
796    
797        for (int i = column; i < column + count; i++) {
798          Element td = createCell();
799          DOM.insertChild(tr, td, i);
800        }
801      }
802    
803      /**
804       * Inserts a new row into the table.
805       * 
806       * @param beforeRow the index before which the new row will be inserted
807       * @return the index of the newly-created row
808       * @throws IndexOutOfBoundsException
809       */
810      protected int insertRow(int beforeRow) {
811        // Specifically allow the row count as an insert position.
812        if (beforeRow != getRowCount())
813          checkRowBounds(beforeRow);
814    
815        Element tr = DOM.createTR();
816        DOM.insertChild(bodyElem, tr, beforeRow);
817        return beforeRow;
818      }
819    
820      /**
821       * Does actual clearing, used by clearCell and cleanCell. All HTMLTable
822       * methods should use internalClearCell rather than clearCell, as clearCell
823       * may be overridden in subclasses to format an empty cell.
824       */
825      protected boolean internalClearCell(int row, int column, Element td) {
826        Widget widget = (Widget) widgetMap.get(computeKey(row, column));
827        if (widget != null) {
828          // If there is a widget, remove it.
829          removeWidget(row, column, widget);
830          return true;
831        } else {
832          // Otherwise, simply clear whatever text and/or HTML may be there.
833          DOM.setInnerHTML(td, "");
834          return false;
835        }
836      }
837    
838      /**
839       * Subclasses must implement this method. It allows them to decide what to do
840       * just before a cell is accessed. If the cell already exists, this method
841       * must do nothing. Otherwise, a subclass must either ensure that the cell
842       * exists or throw an {@link IndexOutOfBoundsException}.
843       * 
844       * @param row the cell's row
845       * @param column the cell's column
846       */
847      protected abstract void prepareCell(int row, int column);
848    
849      /**
850       * Subclasses must implement this method. It allows them to decide what to do
851       * just before a row is accessed. If the row already exists, this method must
852       * do nothing. Otherwise, a subclass must either ensure that the row exists or
853       * throw an {@link IndexOutOfBoundsException}.
854       * 
855       * @param row the cell's row
856       */
857      protected abstract void prepareRow(int row);
858    
859      /**
860       * Removes the specified cell from the table.
861       * 
862       * @param row the row of the cell to remove
863       * @param column the column of cell to remove
864       * @throws IndexOutOfBoundsException
865       */
866      protected void removeCell(int row, int column) {
867        checkCellBounds(row, column);
868        Element td = cleanCell(row, column);
869        Element tr = DOM.getChild(bodyElem, row);
870        DOM.removeChild(tr, td);
871      }
872    
873      /**
874       * Removes the specified row from the table.
875       * 
876       * @param row the index of the row to be removed
877       * @throws IndexOutOfBoundsException
878       */
879      protected void removeRow(int row) {
880        int columnCount = getCellCount(row);
881        for (int column = 0; column < columnCount; ++column) {
882          cleanCell(row, column);
883        }
884        DOM.removeChild(bodyElem, DOM.getChild(bodyElem, row));
885      }
886    
887      /**
888       * Sets the table's CellFormatter.
889       * 
890       * @param cellFormatter the table's cell formatter
891       */
892      protected void setCellFormatter(CellFormatter cellFormatter) {
893        this.cellFormatter = cellFormatter;
894      }
895    
896      /**
897       * Sets the table's RowFormatter.
898       * 
899       * @param rowFormatter the table's row formatter
900       */
901      protected void setRowFormatter(RowFormatter rowFormatter) {
902        this.rowFormatter = rowFormatter;
903      }
904    
905      /**
906       * Removes any widgets, text, and HTML within the cell. This method assumes
907       * that the requested cell already exists.
908       * 
909       * @param row the cell's row
910       * @param column the cell's column
911       */
912      private Element cleanCell(int row, int column) {
913        // Clear whatever is in the cell.
914        Element td = getCellFormatter().getRawElement(row, column);
915        internalClearCell(row, column, td);
916        return td;
917      }
918    
919      /**
920       * Determines the TD associated with the specified event.
921       * 
922       * @param event the event to be queried
923       * @return the TD associated with the event, or <code>null</code> if none is
924       *         found.
925       */
926      private Element getEventTargetCell(Event event) {
927        Element td = DOM.eventGetTarget(event);
928        while (!DOM.getAttribute(td, "tagName").equalsIgnoreCase("td")) {
929          // If we run out of elements, or run into the table itself, then give up.
930          if ((td == null) || DOM.compare(td, getElement()))
931            return null;
932          td = DOM.getParent(td);
933        }
934    
935        return td;
936      }
937    
938      /**
939       * Removes the given widget from a cell. The widget must not be
940       * <code>null</code>.
941       * 
942       * @param row cell's row
943       * @param column cell's column
944       * @param widget widget to be removed
945       */
946      private boolean removeWidget(int row, int column, Widget widget) {
947        // Clear the widget's parent.
948        disown(widget);
949    
950        // Remove the widget from the map.
951        String key = computeKey(row, column);
952        widgetMap.remove(key);
953    
954        // And disconnect it from the TD it's contained in.
955        Element td = getCellFormatter().getRawElement(row, column);
956        Element child = widget.getElement();
957        DOM.removeChild(td, child);
958        return true;
959      }
960    }