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 }