001 /*
002 * Copyright 2006 Mat Gessel <mat.gessel@gmail.com>
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 asquare.gwt.tk.client.ui;
017
018 import java.util.Vector;
019
020 import asquare.gwt.tk.client.util.GwtUtil;
021
022 import com.google.gwt.user.client.DOM;
023 import com.google.gwt.user.client.Element;
024 import com.google.gwt.user.client.ui.CellPanel;
025 import com.google.gwt.user.client.ui.HasAlignment;
026 import com.google.gwt.user.client.ui.Widget;
027
028 /**
029 * A table-based panel which {@link #getCellElement(int) exposes} the TD element
030 * and supports:
031 * <ul>
032 * <li>setting styles on the TD</li>
033 * <li>multiple widgets per cell</li>
034 * <li>empty cells</li>
035 * </ul>
036 */
037 public abstract class ExposedCellPanel extends CellPanel implements HasAlignment
038 {
039 /**
040 * Maps the index of each cell to an ordered list of child widgets.
041 * Mapping may be null if the cell contains no widgets. <br/>
042 */
043 private final Vector m_cellMap = new Vector();
044
045 private HorizontalAlignmentConstant m_horizontalAlignment = ALIGN_LEFT;
046 private VerticalAlignmentConstant m_verticalAlignment = ALIGN_TOP;
047
048 /**
049 * Creates an empty panel with no cells.
050 */
051 ExposedCellPanel()
052 {
053 DOM.setAttribute(getTable(), "cellSpacing", "0");
054 DOM.setAttribute(getTable(), "cellPadding", "0");
055 }
056
057 /**
058 * Gets the number of cells in this panel.
059 */
060 public int getCellCount()
061 {
062 return m_cellMap.size();
063 }
064
065 /**
066 * Adds a new cell to the panel.
067 */
068 public void addCell()
069 {
070 insertCell(m_cellMap.size());
071 }
072
073 /**
074 * Creates a new cell and inserts it at the specified index. Existing cells
075 * with an index >= <code>cellIndex</code> will be shifted over by 1.
076 *
077 * @param cellIndex the index where the cell will be inserted
078 * @throws IndexOutOfBoundsException if <code>cellIndex</code> is less
079 * than 0 or greater than the number of cells
080 */
081 public void insertCell(int cellIndex)
082 {
083 GwtUtil.rangeCheck(m_cellMap, cellIndex, true);
084
085 m_cellMap.add(cellIndex, null);
086 insertCellStructure(cellIndex);
087 if (m_horizontalAlignment != null)
088 {
089 setCellHorizontalAlignment(cellIndex, m_horizontalAlignment);
090 }
091 if (m_verticalAlignment != null)
092 {
093 setCellVerticalAlignment(cellIndex, m_verticalAlignment);
094 }
095 }
096
097 /**
098 * A <em>template method</em> which creates a td and inserts it into the
099 * underlying table. If a cell exists at <code>cellIndex</code> it will be
100 * shifted up by 1. Implementors may assume <code>cellIndex</code> is
101 * greater than 0 and less than or equal to the number of cells.
102 *
103 * @param cellIndex the index at which the td will be inserted.
104 */
105 protected abstract void insertCellStructure(int cellIndex);
106
107 /**
108 * Removes a cell from the panel, including any child widets.
109 *
110 * @param cellIndex the index of the cell
111 * @throws IndexOutOfBoundsException if the cell specified by
112 * <code>cellIndex</code> does not exist
113 */
114 public void removeCell(int cellIndex)
115 {
116 GwtUtil.rangeCheck(m_cellMap, cellIndex, false);
117
118 clearCell(cellIndex);
119 removeCellStructure(cellIndex);
120 m_cellMap.remove(cellIndex);
121 }
122
123 /**
124 * Removes all widgets and child elements from the cell
125 *
126 * @param cellIndex the index of the cell
127 * @throws IndexOutOfBoundsException if cellIndex does not specify a valid
128 * cell
129 */
130 public void clearCell(int cellIndex)
131 {
132 GwtUtil.rangeCheck(m_cellMap, cellIndex, false);
133
134 Vector cellWidgets = (Vector) m_cellMap.get(cellIndex);
135 if (cellWidgets != null)
136 {
137 while (! cellWidgets.isEmpty())
138 {
139 removeWidget((Widget) cellWidgets.lastElement(), cellIndex);
140 }
141 }
142 DOM.setInnerHTML(getCellElement(cellIndex), "");
143 m_cellMap.set(cellIndex, null);
144 }
145
146 /**
147 * A <em>template method</em> which removes a td from the underlying
148 * table. Cells with indexes greater than <code>cellIndex</code> will be
149 * shifted down by 1. Implementors may assume <code>cellIndex</code> is
150 * greater than 0 and less than the number of cells.
151 *
152 * @param cellIndex the index at which the td will be inserted.
153 */
154 protected abstract void removeCellStructure(int cellIndex);
155
156 /**
157 * Removes all cells and child widgets from the panel.
158 */
159 public void clear()
160 {
161 while(! m_cellMap.isEmpty())
162 {
163 removeCell(m_cellMap.size() - 1);
164 }
165 }
166
167 /**
168 * Gets the number of child widgets added to the panel
169 *
170 * @return the number of child widgets
171 */
172 public int getWidgetCount()
173 {
174 return getChildren().size();
175 }
176
177 /**
178 * Gets a at the specified index in the specified cell. <code>wIndex</code>
179 * does not include non-widget child elements.
180 *
181 * @param cellIndex the index of the cell, starting with 0
182 * @param wIndex the index of the widget in the specified cell, starting at
183 * 0
184 * @return the widget
185 * @throws IndexOutOfBoundsException if the cell specified by
186 * <code>cellIndex</code> does not exist
187 * @throws IndexOutOfBoundsException if the widget specified by
188 * <code>wIndex</code> does not exist
189 */
190 public Widget getWidgetAt(int cellIndex, int wIndex)
191 {
192 GwtUtil.rangeCheck(m_cellMap, cellIndex, false);
193
194 Vector childWidgets = (Vector) m_cellMap.get(cellIndex);
195
196 if (childWidgets == null)
197 throw new IndexOutOfBoundsException(Integer.toString(wIndex));
198
199 GwtUtil.rangeCheck(childWidgets, wIndex, false);
200
201 return (Widget) childWidgets.get(wIndex);
202 }
203
204 /**
205 * Get the index of the cell which contains the specified widget.
206 *
207 * @param w a child widget
208 * @return the index or <code>-1</code> if the widget is not a child of
209 * this panel
210 */
211 public int getCellIndexOf(Widget w)
212 {
213 int result = -1;
214 int cell = 0, size = m_cellMap.size();
215 while (cell < size)
216 {
217 Vector cellWidgets = (Vector) m_cellMap.get(cell);
218 if (cellWidgets != null && cellWidgets.contains(w))
219 {
220 result = cell;
221 break;
222 }
223 cell++;
224 }
225 return result;
226 }
227
228 /**
229 * Creates a new cell and appends to it the specified widget.
230 *
231 * @param w a widget
232 */
233 public void add(Widget w)
234 {
235 addWidget(w, true);
236 }
237
238 /**
239 * Adds a widget to the panel, optionally creating a new cell.
240 *
241 * @param w a widget
242 * @param newCell <code>true</code> to create a new cell,
243 * <code>false</code> to append to the last cell
244 * @throws IndexOutOfBoundsException if <code>newCell</code> is
245 * <code>false</code> and no cells exist
246 */
247 public void addWidget(Widget w, boolean newCell)
248 {
249 /*
250 * if w is a child of this panel and the only widget in a cell the cell
251 * will be removed
252 */
253 w.removeFromParent();
254
255 if (newCell)
256 {
257 insertCell(m_cellMap.size());
258 }
259 addWidgetTo(w, m_cellMap.size() - 1);
260 }
261
262 /**
263 * Adds a widget to the specified cell. <code>w</code> will be appended
264 * after any other widgets in the cell.
265 *
266 * @param w a widget
267 * @param cellIndex the index of the cell
268 * @throws IndexOutOfBoundsException if the cell specified by
269 * <code>cellIndex</code> does not exist
270 */
271 public void addWidgetTo(Widget w, int cellIndex)
272 {
273 GwtUtil.rangeCheck(m_cellMap, cellIndex, false);
274
275 Vector cellWidgets = (Vector) m_cellMap.get(cellIndex);
276 int wIndex = (cellWidgets != null) ? cellWidgets.size() : 0;
277 insertWidgetAt(w, cellIndex, wIndex);
278 }
279
280 /**
281 * Inserts a new cell at the specified index and appends the widget to the
282 * cell.
283 *
284 * @param w a widget
285 * @param cellIndex the index of the cell
286 * @throws IndexOutOfBoundsException if <code>cellIndex</code> is less
287 * than 0 or greater than the number of cells
288 */
289 public void insert(Widget w, int cellIndex)
290 {
291 /*
292 * if w is a child of this panel and the only widget in a cell the cell
293 * will be removed
294 */
295 w.removeFromParent();
296
297 insertCell(cellIndex);
298 insertWidgetAt(w, cellIndex, 0);
299 }
300
301 /**
302 * Inserts a widget into a an existing cell. Any widgets with indexes
303 * greater than or equal to <code>wIndex</code> will be shifted over.
304 *
305 * @param w a widget
306 * @param cellIndex the index of the cell
307 * @param wIndex the index of the widget before which <code>w</code> will
308 * be inserted
309 * @throws IndexOutOfBoundsException if the cell specified by
310 * <code>cellIndex</code> does not exist
311 * @throws IndexOutOfBoundsException if <code>wIndex</code> is less than 0
312 * or greater than the number of widgets in the specified cell
313 */
314 public void insertWidgetAt(Widget w, int cellIndex, int wIndex)
315 {
316 /*
317 * The cell will be removed if w is a child of this panel and w is the
318 * only widget in the cell
319 */
320 w.removeFromParent();
321
322 GwtUtil.rangeCheck(m_cellMap, cellIndex, false);
323
324 Vector cellWidgets = (Vector) m_cellMap.get(cellIndex);
325
326 if (cellWidgets == null)
327 {
328 if (wIndex != 0)
329 throw new IndexOutOfBoundsException(Integer.toString(wIndex));
330
331 cellWidgets = new Vector();
332 m_cellMap.set(cellIndex, cellWidgets); // pre: cellIndex has been inserted
333 }
334
335 GwtUtil.rangeCheck(cellWidgets, wIndex, true);
336
337 cellWidgets.insertElementAt(w, wIndex);
338
339 /*
340 * Pass null and append the element manually so that we can specify
341 * insertion order. Also note that the order of the "children"
342 * WidgetCollection is not maintained.
343 */
344 insert(w, null, getChildren().size());
345 Element td = getCellElement(cellIndex);
346 DOM.insertChild(td, w.getElement(), wIndex);
347 }
348
349 /**
350 * Removes a widget from the panel.
351 * Automatically removes the cell if it becomes empty.
352 *
353 * @param w a child widget
354 * @return false if <code>w</code> is not a child of this panel
355 */
356 public boolean remove(Widget w)
357 {
358 return remove(w, true);
359 }
360
361 /**
362 * Removes a widget from the panel, optionally removing the cell if it
363 * becomes empty.
364 *
365 * @param w a child widget
366 * @param removeEmptyCell <code>true</code> to remove the cell if it
367 * becomes empty
368 * @return false if <code>w</code> is not a child of this panel
369 */
370 public boolean remove(Widget w, boolean removeEmptyCell)
371 {
372 if (! getChildren().contains(w))
373 return false;
374
375 int cellIndex = getCellIndexOf(w);
376 assert cellIndex >= 0;
377
378 removeWidget(w, cellIndex);
379 Vector childWidgets = (Vector) m_cellMap.get(cellIndex);
380 if (removeEmptyCell && childWidgets.size() == 0)
381 {
382 removeCell(cellIndex);
383 }
384 return true;
385 }
386
387 /*
388 * Removes the widget from the cellMap & DOM tree and clears out its
389 * listener
390 */
391 private void removeWidget(Widget w, int cellIndex)
392 {
393 Vector childWidgets = (Vector) m_cellMap.get(cellIndex);
394
395 super.remove(w);
396 boolean present = (childWidgets).remove(w);
397 assert present;
398 }
399
400 /**
401 * Gets the table <code>td</code> element corresponding to the specified
402 * cell.
403 *
404 * @param cellIndex the index of the cell
405 * @return the <code>td</code> element of the specified cell
406 * @throws IndexOutOfBoundsException if <code>cellIndex</code> does not
407 * specify an existing cell
408 */
409 public abstract Element getCellElement(int cellIndex);
410
411 /**
412 * Gets the style name(s) for the cell specified by
413 * <code>cellIndex</code>.
414 *
415 * @return the CSS class name(s) (space delimited)
416 * @param cellIndex the index of the cell
417 * @throws IndexOutOfBoundsException if the cell specified by
418 * <code>cellIndex</code> does not exist
419 */
420 public String getCellStyleName(int cellIndex)
421 {
422 GwtUtil.rangeCheck(m_cellMap, cellIndex, false);
423
424 return DOM.getAttribute(getCellElement(cellIndex), "className");
425 }
426
427 /**
428 * Adds a style name to the last cell.
429 *
430 * @param styleName a CSS class name
431 * @throws IllegalStateException if no cells exist
432 * @throws IllegalArgumentException if <code>styleName</code> is <code>""</code>
433 */
434 public void addCellStyleName(String styleName)
435 {
436 if (getCellCount() == 0)
437 throw new IllegalStateException();
438
439 addCellStyleName(getCellCount() - 1, styleName);
440 }
441
442 /**
443 * Adds a style name to the <code>class</code> attribute of the cell
444 * specified by the <code>cellIndex</code>.
445 *
446 * @param cellIndex the index of a cell
447 * @param styleName a CSS class name
448 * @throws IndexOutOfBoundsException if the cell specified by
449 * <code>cellIndex</code> does not exist
450 * @throws IllegalArgumentException if <code>styleName</code> is <code>""</code>
451 */
452 public void addCellStyleName(int cellIndex, String styleName)
453 {
454 GwtUtil.rangeCheck(m_cellMap, cellIndex, false);
455
456 setStyleName(getCellElement(cellIndex), styleName, true);
457 }
458
459 /**
460 * Sets the style name for the last cell. Other style names will be
461 * overwritten.
462 *
463 * @param styleName a CSS class name
464 * @throws IllegalStateException if no cells exist
465 */
466 public void setCellStyleName(String styleName)
467 {
468 if (m_cellMap.size() == 0)
469 throw new IllegalStateException();
470
471 setCellStyleName(getCellCount() - 1, styleName);
472 }
473
474 /**
475 * Sets the style name for the cell specified by
476 * <code>cellIndex</code>. Other style names will be
477 * overwritten.
478 *
479 * @param cellIndex the index of the cell
480 * @param styleName a CSS class name
481 * @throws IndexOutOfBoundsException if the cell specified by
482 * <code>cellIndex</code> does not exist
483 */
484 public void setCellStyleName(int cellIndex, String styleName)
485 {
486 GwtUtil.rangeCheck(m_cellMap, cellIndex, false);
487
488 DOM.setAttribute(getCellElement(cellIndex), "className", styleName);
489 }
490
491 /**
492 * Removes the specified style name from the last cell. Does nothing if
493 * <code>styleName</code> is not present.
494 *
495 * @param styleName a CSS class name
496 * @throws IllegalStateException if no cells exist
497 * @throws IllegalArgumentException if <code>styleName</code> is
498 * <code>""</code>
499 */
500 public void removeCellStyleName(String styleName)
501 {
502 if (getCellCount() == 0)
503 throw new IllegalStateException();
504
505 removeCellStyleName(getCellCount() - 1, styleName);
506 }
507
508 /**
509 * Adds a style name to the cell specified by
510 * <code><cellIndex/code>. Does nothing if
511 * <code>styleName</code> is not present.
512 *
513 * @param cellIndex the index of the cell
514 * @param styleName a CSS class name
515 * @throws IndexOutOfBoundsException if the cell specified by
516 * <code>cellIndex</code> does not exist
517 * @throws IllegalArgumentException if <code>styleName</code> is <code>""</code>
518 */
519 public void removeCellStyleName(int cellIndex, String styleName)
520 {
521 GwtUtil.rangeCheck(m_cellMap, cellIndex, false);
522
523 setStyleName(getCellElement(cellIndex), styleName, false);
524 }
525
526 /**
527 * Sets the width of the last cell.
528 *
529 * @param width a CSS measurement
530 */
531 public void setCellWidth(String width)
532 {
533 if (m_cellMap.size() == 0)
534 throw new IllegalStateException();
535
536 setCellWidth(getCellCount() - 1, width);
537 }
538
539 /**
540 * Sets the width of the specified cell.
541 *
542 * @param cellIndex the index of a cell
543 * @param width a CSS measurement
544 * @throws IndexOutOfBoundsException if the cell specified by
545 * <code>cellIndex</code> does not exist
546 */
547 public void setCellWidth(int cellIndex, String width)
548 {
549 GwtUtil.rangeCheck(m_cellMap, cellIndex, false);
550
551 DOM.setAttribute(getCellElement(cellIndex), "width", width);
552 }
553
554 /**
555 * Sets the height of the last cell.
556 *
557 * @param height a CSS measurement
558 */
559 public void setCellHeight(String height)
560 {
561 if (m_cellMap.size() == 0)
562 throw new IllegalStateException();
563
564 setCellHeight(getCellCount() - 1, height);
565 }
566
567 /**
568 * Sets the height of the specified cell.
569 *
570 * @param cellIndex the index of a cell
571 * @param height a CSS measurement
572 * @throws IndexOutOfBoundsException if the cell specified by
573 * <code>cellIndex</code> does not exist
574 */
575 public void setCellHeight(int cellIndex, String height)
576 {
577 GwtUtil.rangeCheck(m_cellMap, cellIndex, false);
578
579 DOM.setAttribute(getCellElement(cellIndex), "height", height);
580 }
581
582 /**
583 * Sets the horizontal alignment of the last cell.
584 *
585 * @param hAlign a constant representing left, center or right alignment
586 * @see HasAlignment
587 */
588 public void setCellHorizontalAlignment(HorizontalAlignmentConstant hAlign)
589 {
590 if (m_cellMap.size() == 0)
591 throw new IllegalStateException();
592
593 setCellHorizontalAlignment(getCellCount() - 1, hAlign);
594 }
595
596 /**
597 * Sets the horizontal alignment of the specified cell.
598 *
599 * @param cellIndex the index of a cell
600 * @param hAlign a constant representing left, center or right alignment
601 * @throws IndexOutOfBoundsException if the cell specified by
602 * <code>cellIndex</code> does not exist
603 * @see HasAlignment
604 */
605 public void setCellHorizontalAlignment(int cellIndex, HorizontalAlignmentConstant hAlign)
606 {
607 GwtUtil.rangeCheck(m_cellMap, cellIndex, false);
608
609 DOM.setAttribute(getCellElement(cellIndex), "align", hAlign.getTextAlignString());
610 }
611
612 /**
613 * Sets the vertical alignment of the last cell.
614 *
615 * @param vAlign a constant representing top, middle or bottom alignment
616 * @see HasAlignment
617 */
618 public void setCellVerticalAlignment(VerticalAlignmentConstant vAlign)
619 {
620 if (m_cellMap.size() == 0)
621 throw new IllegalStateException();
622
623 setCellVerticalAlignment(getCellCount() - 1, vAlign);
624 }
625
626 /**
627 * Sets the vertical alignment of the specified cell.
628 *
629 * @param cellIndex the index of a cell
630 * @param vAlign a constant representing top, middle or bottom alignment
631 * @throws IndexOutOfBoundsException if the cell specified by
632 * <code>cellIndex</code> does not exist
633 * @see HasAlignment
634 */
635 public void setCellVerticalAlignment(int cellIndex, VerticalAlignmentConstant vAlign)
636 {
637 GwtUtil.rangeCheck(m_cellMap, cellIndex, false);
638
639 DOM.setStyleAttribute(getCellElement(cellIndex), "verticalAlign", vAlign.getVerticalAlignString());
640 }
641
642 /**
643 * Gets the default horizontal alignment for newly created cells.
644 *
645 * @return an alignment constant or null
646 * @see HasAlignment
647 */
648 public HorizontalAlignmentConstant getHorizontalAlignment()
649 {
650 return m_horizontalAlignment;
651 }
652
653 /**
654 * Sets the default horizontal alignment for newly created cells.
655 *
656 * @param align an alignment constant or null
657 * @see HasAlignment
658 */
659 public void setHorizontalAlignment(HorizontalAlignmentConstant align)
660 {
661 m_horizontalAlignment = align;
662 }
663
664 /**
665 * Gets the default vertical alignment for newly created cells.
666 *
667 * @return an alignment constant or null
668 * @see HasAlignment
669 */
670 public VerticalAlignmentConstant getVerticalAlignment()
671 {
672 return m_verticalAlignment;
673 }
674
675 /**
676 * Sets the default vertical alignment for newly created cells.
677 *
678 * @param align an alignment constant or null
679 * @see HasAlignment
680 */
681 public void setVerticalAlignment(VerticalAlignmentConstant align)
682 {
683 m_verticalAlignment = align;
684 }
685 }