Subversion Repository Public Repository

litesoft

Diff Revisions 282 vs 475 for /trunk/Java/GWT/Client/src/com/google/gwt/gen2/table/client/PagingScrollTable.java

Diff revisions: vs.
  @@ -15,1426 +15,1587 @@
15 15 */
16 16 package com.google.gwt.gen2.table.client;
17 17
18 - import com.google.gwt.core.client.GWT;
19 - import com.google.gwt.event.dom.client.ClickEvent;
20 - import com.google.gwt.event.dom.client.ClickHandler;
21 - import com.google.gwt.gen2.event.shared.HandlerRegistration;
22 - import com.google.gwt.gen2.table.client.CellEditor.CellEditInfo;
23 - import com.google.gwt.gen2.table.client.SelectionGrid.SelectionPolicy;
24 - import com.google.gwt.gen2.table.client.SortableGrid.ColumnSorter;
25 - import com.google.gwt.gen2.table.client.SortableGrid.ColumnSorterCallback;
26 - import com.google.gwt.gen2.table.client.TableDefinition.AbstractCellView;
27 - import com.google.gwt.gen2.table.client.TableDefinition.AbstractRowView;
18 + import java.util.*;
19 +
20 + import com.google.gwt.core.client.*;
21 + import com.google.gwt.event.dom.client.*;
22 + import com.google.gwt.gen2.event.shared.*;
23 + import com.google.gwt.gen2.table.client.CellEditor.*;
24 + import com.google.gwt.gen2.table.client.FlexTable.*;
25 + import com.google.gwt.gen2.table.client.SelectionGrid.*;
26 + import com.google.gwt.gen2.table.client.SortableGrid.*;
27 + import com.google.gwt.gen2.table.client.TableDefinition.*;
28 28 import com.google.gwt.gen2.table.client.TableModel.Callback;
29 - import com.google.gwt.gen2.table.client.TableModelHelper.ColumnSortList;
30 - import com.google.gwt.gen2.table.client.TableModelHelper.Request;
31 - import com.google.gwt.gen2.table.client.TableModelHelper.Response;
32 - import com.google.gwt.gen2.table.client.property.FooterProperty;
33 - import com.google.gwt.gen2.table.client.property.HeaderProperty;
34 - import com.google.gwt.gen2.table.client.property.MaximumWidthProperty;
35 - import com.google.gwt.gen2.table.client.property.MinimumWidthProperty;
36 - import com.google.gwt.gen2.table.client.property.PreferredWidthProperty;
37 - import com.google.gwt.gen2.table.client.property.SortableProperty;
38 - import com.google.gwt.gen2.table.client.property.TruncationProperty;
39 - import com.google.gwt.gen2.table.event.client.HasPageChangeHandlers;
40 - import com.google.gwt.gen2.table.event.client.HasPageCountChangeHandlers;
41 - import com.google.gwt.gen2.table.event.client.HasPageLoadHandlers;
42 - import com.google.gwt.gen2.table.event.client.HasPagingFailureHandlers;
43 - import com.google.gwt.gen2.table.event.client.HasRowInsertionHandlers;
44 - import com.google.gwt.gen2.table.event.client.HasRowRemovalHandlers;
45 - import com.google.gwt.gen2.table.event.client.HasRowValueChangeHandlers;
46 - import com.google.gwt.gen2.table.event.client.PageChangeEvent;
47 - import com.google.gwt.gen2.table.event.client.PageChangeHandler;
48 - import com.google.gwt.gen2.table.event.client.PageCountChangeEvent;
49 - import com.google.gwt.gen2.table.event.client.PageCountChangeHandler;
50 - import com.google.gwt.gen2.table.event.client.PageLoadEvent;
51 - import com.google.gwt.gen2.table.event.client.PageLoadHandler;
52 - import com.google.gwt.gen2.table.event.client.PagingFailureEvent;
53 - import com.google.gwt.gen2.table.event.client.PagingFailureHandler;
54 - import com.google.gwt.gen2.table.event.client.RowCountChangeEvent;
55 - import com.google.gwt.gen2.table.event.client.RowCountChangeHandler;
56 - import com.google.gwt.gen2.table.event.client.RowInsertionEvent;
57 - import com.google.gwt.gen2.table.event.client.RowInsertionHandler;
58 - import com.google.gwt.gen2.table.event.client.RowRemovalEvent;
59 - import com.google.gwt.gen2.table.event.client.RowRemovalHandler;
60 - import com.google.gwt.gen2.table.event.client.RowSelectionEvent;
61 - import com.google.gwt.gen2.table.event.client.RowSelectionHandler;
62 - import com.google.gwt.gen2.table.event.client.RowValueChangeEvent;
63 - import com.google.gwt.gen2.table.event.client.RowValueChangeHandler;
64 - import com.google.gwt.gen2.table.event.client.TableEvent.Row;
65 - import com.google.gwt.gen2.table.client.FlexTable.FlexCellFormatter;
66 - import com.google.gwt.user.client.ui.CheckBox;
67 - import com.google.gwt.user.client.ui.HasHorizontalAlignment;
68 - import com.google.gwt.user.client.ui.SimplePanel;
69 - import com.google.gwt.user.client.ui.SourcesTableEvents;
70 - import com.google.gwt.user.client.ui.TableListener;
71 - import com.google.gwt.user.client.ui.Widget;
72 - import com.google.gwt.user.client.ui.HasHorizontalAlignment.HorizontalAlignmentConstant;
73 - import com.google.gwt.user.client.ui.HasVerticalAlignment.VerticalAlignmentConstant;
74 -
75 - import java.util.ArrayList;
76 - import java.util.HashSet;
77 - import java.util.Iterator;
78 - import java.util.List;
79 - import java.util.NoSuchElementException;
80 - import java.util.Set;
29 + import com.google.gwt.gen2.table.client.TableModelHelper.*;
30 + import com.google.gwt.gen2.table.client.property.*;
31 + import com.google.gwt.gen2.table.event.client.*;
32 + import com.google.gwt.gen2.table.event.client.TableEvent.*;
33 + import com.google.gwt.user.client.ui.*;
34 + import com.google.gwt.user.client.ui.HasHorizontalAlignment.*;
35 + import com.google.gwt.user.client.ui.HasVerticalAlignment.*;
81 36
82 37 /**
83 38 * An {@link AbstractScrollTable} that acts as a view for an underlying
84 39 * {@link MutableTableModel}.
85 - *
40 + *
86 41 * @param <RowType> the data type of the row values
87 42 */
88 - public class PagingScrollTable<RowType> extends AbstractScrollTable implements
89 - HasTableDefinition<RowType>, HasPageCountChangeHandlers,
90 - HasPageLoadHandlers, HasPageChangeHandlers, HasPagingFailureHandlers {
91 - /**
92 - * A custom {@link AbstractCellView} used by the {@link PagingScrollTable}.
93 - *
94 - * @param <RowType> the type of the row values
95 - */
96 - protected static class PagingScrollTableCellView<RowType> extends
97 - AbstractCellView<RowType> {
98 - private PagingScrollTable<RowType> table;
99 -
100 - public PagingScrollTableCellView(PagingScrollTable<RowType> table) {
101 - super(table);
102 - this.table = table;
43 + public class PagingScrollTable<RowType> extends AbstractScrollTable implements HasTableDefinition<RowType>,
44 + HasPageCountChangeHandlers,
45 + HasPageLoadHandlers,
46 + HasPageChangeHandlers,
47 + HasPagingFailureHandlers
48 + {
49 + /**
50 + * A custom {@link AbstractCellView} used by the {@link PagingScrollTable}.
51 + *
52 + * @param <RowType> the type of the row values
53 + */
54 + protected static class PagingScrollTableCellView<RowType> extends AbstractCellView<RowType>
55 + {
56 + private PagingScrollTable<RowType> table;
57 +
58 + public PagingScrollTableCellView( PagingScrollTable<RowType> table )
59 + {
60 + super( table );
61 + this.table = table;
62 + }
63 +
64 + @Override
65 + public void setHorizontalAlignment( HorizontalAlignmentConstant align )
66 + {
67 + table.getDataTable().getCellFormatter().setHorizontalAlignment( getRowIndex(), getCellIndex(), align );
68 + }
69 +
70 + @Override
71 + public void setHTML( String html )
72 + {
73 + table.getDataTable().setHTML( getRowIndex(), getCellIndex(), html );
74 + }
75 +
76 + @Override
77 + public void setStyleAttribute( String attr, String value )
78 + {
79 + table.getDataTable().getFixedWidthGridCellFormatter().getRawElement( getRowIndex(), getCellIndex() ).getStyle().setProperty( attr, value );
80 + }
81 +
82 + @Override
83 + public void setStyleName( String stylename )
84 + {
85 + table.getDataTable().getCellFormatter().setStyleName( getRowIndex(), getCellIndex(), stylename );
86 + }
87 +
88 + @Override
89 + public void setText( String text )
90 + {
91 + table.getDataTable().setText( getRowIndex(), getCellIndex(), text );
92 + }
93 +
94 + @Override
95 + public void setVerticalAlignment( VerticalAlignmentConstant align )
96 + {
97 + table.getDataTable().getCellFormatter().setVerticalAlignment( getRowIndex(), getCellIndex(), align );
98 + }
99 +
100 + @Override
101 + public void setWidget( Widget widget )
102 + {
103 + table.getDataTable().setWidget( getRowIndex(), getCellIndex(), widget );
104 + }
103 105 }
104 106
105 - @Override
106 - public void setHorizontalAlignment(HorizontalAlignmentConstant align) {
107 - table.getDataTable().getCellFormatter().setHorizontalAlignment(
108 - getRowIndex(), getCellIndex(), align);
107 + /**
108 + * A custom {@link AbstractRowView} used by the {@link PagingScrollTable}.
109 + *
110 + * @param <RowType> the type of the row values
111 + */
112 + protected static class PagingScrollTableRowView<RowType> extends AbstractRowView<RowType>
113 + {
114 + private PagingScrollTable<RowType> table;
115 +
116 + public PagingScrollTableRowView( PagingScrollTable<RowType> table )
117 + {
118 + super( new PagingScrollTableCellView<RowType>( table ) );
119 + this.table = table;
120 + }
121 +
122 + @Override
123 + public void setStyleAttribute( String attr, String value )
124 + {
125 + table.getDataTable().getFixedWidthGridRowFormatter().getRawElement( getRowIndex() ).getStyle().setProperty( attr, value );
126 + }
127 +
128 + @Override
129 + public void setStyleName( String stylename )
130 + {
131 + // If the row is selected, add the selected style name back
132 + if ( table.getDataTable().isRowSelected( getRowIndex() ) )
133 + {
134 + stylename += " selected";
135 + }
136 + table.getDataTable().getRowFormatter().setStyleName( getRowIndex(), stylename );
137 + }
109 138 }
110 139
111 - @Override
112 - public void setHTML(String html) {
113 - table.getDataTable().setHTML(getRowIndex(), getCellIndex(), html);
140 + /**
141 + * Information about a column header.
142 + */
143 + private static class ColumnHeaderInfo
144 + {
145 + private int rowSpan = 1;
146 + private Object header;
147 +
148 + public ColumnHeaderInfo( Object header )
149 + {
150 + this.header = (header == null) ? "" : header;
151 + }
152 +
153 + public ColumnHeaderInfo( Object header, int rowSpan )
154 + {
155 + this.header = (header == null) ? "&nbsp;" : header;
156 + this.rowSpan = rowSpan;
157 + }
158 +
159 + @Override
160 + public boolean equals( Object o )
161 + {
162 + if ( o == null )
163 + {
164 + return false;
165 + }
166 + if ( o instanceof ColumnHeaderInfo )
167 + {
168 + ColumnHeaderInfo info = (ColumnHeaderInfo) o;
169 + return (rowSpan == info.rowSpan) && header.equals( info.header );
170 + }
171 + return false;
172 + }
173 +
174 + public Object getHeader()
175 + {
176 + return header;
177 + }
178 +
179 + public int getRowSpan()
180 + {
181 + return rowSpan;
182 + }
183 +
184 + public void incrementRowSpan()
185 + {
186 + rowSpan++;
187 + }
188 + }
189 +
190 + /**
191 + * An iterator over the visible rows in an iterator over many rows.
192 + */
193 + private class VisibleRowsIterator implements Iterator<RowType>
194 + {
195 + /**
196 + * The iterator of row data.
197 + */
198 + private Iterator<RowType> rows;
199 +
200 + /**
201 + * The current row of the rows iterator.
202 + */
203 + private int curRow;
204 +
205 + /**
206 + * The last visible row in the grid.
207 + */
208 + private int lastVisibleRow;
209 +
210 + /**
211 + * Constructor.
212 + *
213 + * @param rows the iterator over row data
214 + * @param firstRow the first absolute row of the rows iterator
215 + * @param firstVisibleRow the first visible row in this grid
216 + * @param lastVisibleRow the last visible row in this grid
217 + */
218 + public VisibleRowsIterator( Iterator<RowType> rows, int firstRow, int firstVisibleRow, int lastVisibleRow )
219 + {
220 + this.curRow = firstRow;
221 + this.lastVisibleRow = lastVisibleRow;
222 +
223 + // Iterate up to the first row
224 + while ( curRow < firstVisibleRow && rows.hasNext() )
225 + {
226 + rows.next();
227 + curRow++;
228 + }
229 + this.rows = rows;
230 + }
231 +
232 + public boolean hasNext()
233 + {
234 + return (curRow <= lastVisibleRow && rows.hasNext());
235 + }
236 +
237 + public RowType next()
238 + {
239 + // Check that the next row exists
240 + if ( !hasNext() )
241 + {
242 + throw new NoSuchElementException();
243 + }
244 + return rows.next();
245 + }
246 +
247 + public void remove()
248 + {
249 + throw new UnsupportedOperationException( "Remove not supported" );
250 + }
251 + }
252 +
253 + /**
254 + * The bulk render used to render the contents of this table.
255 + */
256 + private FixedWidthGridBulkRenderer<RowType> bulkRenderer = null;
257 +
258 + /**
259 + * The wrapper around the empty table widget.
260 + */
261 + private SimplePanel emptyTableWidgetWrapper = new SimplePanel();
262 +
263 + /**
264 + * The definition of the columns in the table.
265 + */
266 + private TableDefinition<RowType> tableDefinition = null;
267 +
268 + /**
269 + * The current visible page.
270 + */
271 + private int currentPage = -1;
272 +
273 + /**
274 + * The last request that was sent to the {@link TableModel}.
275 + */
276 + private Request lastRequest = null;
277 +
278 + /**
279 + * A boolean indicating that cross page selection is enabled.
280 + */
281 + private boolean isCrossPageSelectionEnabled;
282 +
283 + /**
284 + * The set of selected row values.
285 + */
286 + private Set<RowType> selectedRowValues = new HashSet<RowType>();
287 +
288 + /**
289 + * A boolean indicating that the footer should be generated automatically.
290 + */
291 + private boolean isFooterGenerated;
292 +
293 + /**
294 + * A boolean indicating that the header should be generated automatically.
295 + */
296 + private boolean isHeaderGenerated;
297 +
298 + /**
299 + * A boolean indicating that the page is currently being loaded.
300 + */
301 + private boolean isPageLoading;
302 +
303 + /**
304 + * The old page count, used to detect when the number of pages changes.
305 + */
306 + private int oldPageCount;
307 +
308 + /**
309 + * The number of rows per page. If the number of rows per page is equal to the
310 + * number of rows, paging is disabled because only one page exists.
311 + */
312 + private int pageSize = 0;
313 +
314 + /**
315 + * The callback that handles page requests.
316 + */
317 + private Callback<RowType> pagingCallback = new Callback<RowType>()
318 + {
319 + public void onFailure( Throwable caught )
320 + {
321 + isPageLoading = false;
322 + fireEvent( new PagingFailureEvent( caught ) );
323 + }
324 +
325 + public void onRowsReady( Request request, Response<RowType> response )
326 + {
327 + if ( lastRequest == request )
328 + {
329 + setData( request.getStartRow(), response.getRowValues() );
330 + lastRequest = null;
331 + }
332 + }
333 + };
334 +
335 + /**
336 + * The values associated with each row. This is an optional list of data that
337 + * ties the visible content in each row to an underlying object.
338 + */
339 + private List<RowType> rowValues = new ArrayList<RowType>();
340 +
341 + /**
342 + * The view of this table.
343 + */
344 + private AbstractRowView<RowType> rowView = new PagingScrollTableRowView<RowType>( this );
345 +
346 + /**
347 + * The {@link CheckBox} used to select all rows.
348 + */
349 + private Widget selectAllWidget;
350 +
351 + /**
352 + * The underlying table model.
353 + */
354 + private TableModel<RowType> tableModel;
355 +
356 + /**
357 + * The {@link RendererCallback} used when table rendering completes.
358 + */
359 + private RendererCallback tableRendererCallback = new RendererCallback()
360 + {
361 + public void onRendered()
362 + {
363 + onDataTableRendered();
364 + }
365 + };
366 +
367 + /**
368 + * The columns that are currently visible.
369 + */
370 + private List<ColumnDefinition<RowType, ?>> visibleColumns = new ArrayList<ColumnDefinition<RowType, ?>>();
371 +
372 + /**
373 + * The boolean indicating that the header tables are obsolete.
374 + */
375 + private boolean headersObsolete;
376 +
377 + /**
378 + * Construct a new {@link PagingScrollTable}.
379 + *
380 + * @param tableModel the underlying table model
381 + * @param tableDefinition the column definitions
382 + */
383 + public PagingScrollTable( TableModel<RowType> tableModel, TableDefinition<RowType> tableDefinition )
384 + {
385 + this( tableModel, new FixedWidthGrid(), new FixedWidthFlexTable(), tableDefinition );
386 + isHeaderGenerated = true;
387 + isFooterGenerated = true;
388 + }
389 +
390 + /**
391 + * Construct a new {@link PagingScrollTable}.
392 + *
393 + * @param tableModel the underlying table model
394 + * @param dataTable the table used to display data
395 + * @param headerTable the header table
396 + * @param tableDefinition the column definitions
397 + */
398 + public PagingScrollTable( TableModel<RowType> tableModel, FixedWidthGrid dataTable, FixedWidthFlexTable headerTable, TableDefinition<RowType> tableDefinition )
399 + {
400 + this( tableModel, dataTable, headerTable, tableDefinition, GWT.<ScrollTableImages>create( ScrollTableImages.class ) );
401 + }
402 +
403 + /**
404 + * Construct a new {@link PagingScrollTable} with custom images.
405 + *
406 + * @param tableModel the underlying table model
407 + * @param dataTable the table used to display data
408 + * @param headerTable the header table
409 + * @param tableDefinition the column definitions
410 + * @param images the images to use in the table
411 + */
412 + public PagingScrollTable( TableModel<RowType> tableModel, FixedWidthGrid dataTable, FixedWidthFlexTable headerTable, TableDefinition<RowType> tableDefinition, ScrollTableImages images )
413 + {
414 + super( dataTable, headerTable, images );
415 + this.tableModel = tableModel;
416 + setTableDefinition( tableDefinition );
417 + refreshVisibleColumnDefinitions();
418 + oldPageCount = getPageCount();
419 +
420 + // Setup the empty table widget wrapper
421 + emptyTableWidgetWrapper.getElement().getStyle().setProperty( "width", "100%" );
422 + emptyTableWidgetWrapper.getElement().getStyle().setProperty( "overflow", "hidden" );
423 + emptyTableWidgetWrapper.getElement().getStyle().setPropertyPx( "border", 0 );
424 + emptyTableWidgetWrapper.getElement().getStyle().setPropertyPx( "margin", 0 );
425 + emptyTableWidgetWrapper.getElement().getStyle().setPropertyPx( "padding", 0 );
426 + insert( emptyTableWidgetWrapper, getAbsoluteElement(), 2, true );
427 + setEmptyTableWidgetVisible( false );
428 +
429 + // Listen to table model events
430 + tableModel.addRowCountChangeHandler( new RowCountChangeHandler()
431 + {
432 + public void onRowCountChange( RowCountChangeEvent event )
433 + {
434 + int pageCount = getPageCount();
435 + if ( pageCount != oldPageCount )
436 + {
437 + fireEvent( new PageCountChangeEvent( oldPageCount, pageCount ) );
438 + oldPageCount = pageCount;
439 + }
440 + }
441 + } );
442 + if ( tableModel instanceof HasRowInsertionHandlers )
443 + {
444 + ((HasRowInsertionHandlers) tableModel).addRowInsertionHandler( new RowInsertionHandler()
445 + {
446 + public void onRowInsertion( RowInsertionEvent event )
447 + {
448 + insertAbsoluteRow( event.getRowIndex() );
449 + }
450 + } );
451 + }
452 + if ( tableModel instanceof HasRowRemovalHandlers )
453 + {
454 + ((HasRowRemovalHandlers) tableModel).addRowRemovalHandler( new RowRemovalHandler()
455 + {
456 + public void onRowRemoval( RowRemovalEvent event )
457 + {
458 + removeAbsoluteRow( event.getRowIndex() );
459 + }
460 + } );
461 + }
462 + if ( tableModel instanceof HasRowValueChangeHandlers )
463 + {
464 + ((HasRowValueChangeHandlers<RowType>) tableModel).addRowValueChangeHandler( new RowValueChangeHandler<RowType>()
465 + {
466 + public void onRowValueChange( RowValueChangeEvent<RowType> event )
467 + {
468 + int rowIndex = event.getRowIndex();
469 + if ( rowIndex < getAbsoluteFirstRowIndex() || rowIndex > getAbsoluteLastRowIndex() )
470 + {
471 + return;
472 + }
473 + setRowValue( rowIndex - getAbsoluteFirstRowIndex(), event.getRowValue() );
474 + }
475 + } );
476 + }
477 +
478 + // Listen for cell click events
479 + dataTable.addTableListener( new TableListener()
480 + {
481 + public void onCellClicked( SourcesTableEvents sender, int row, int cell )
482 + {
483 + editCell( row, cell );
484 + }
485 + } );
486 +
487 + // Override the column sorter
488 + if ( dataTable.getColumnSorter() == null )
489 + {
490 + ColumnSorter sorter = new ColumnSorter()
491 + {
492 + @Override
493 + public void onSortColumn( SortableGrid grid, ColumnSortList sortList, ColumnSorterCallback callback )
494 + {
495 + reloadPage();
496 + callback.onSortingComplete();
497 + }
498 + };
499 + dataTable.setColumnSorter( sorter );
500 + }
501 +
502 + // Listen for selection events
503 + dataTable.addRowSelectionHandler( new RowSelectionHandler()
504 + {
505 + public void onRowSelection( RowSelectionEvent event )
506 + {
507 + if ( isPageLoading )
508 + {
509 + return;
510 + }
511 + Set<Row> deselected = event.getDeselectedRows();
512 + for ( Row row : deselected )
513 + {
514 + selectedRowValues.remove( getRowValue( row.getRowIndex() ) );
515 + }
516 + Set<Row> selected = event.getSelectedRows();
517 + for ( Row row : selected )
518 + {
519 + selectedRowValues.add( getRowValue( row.getRowIndex() ) );
520 + }
521 + }
522 + } );
523 + }
524 +
525 + public HandlerRegistration addPageChangeHandler( PageChangeHandler handler )
526 + {
527 + return addHandler( PageChangeEvent.TYPE, handler );
528 + }
529 +
530 + public HandlerRegistration addPageCountChangeHandler( PageCountChangeHandler handler )
531 + {
532 + return addHandler( PageCountChangeEvent.TYPE, handler );
533 + }
534 +
535 + public HandlerRegistration addPageLoadHandler( PageLoadHandler handler )
536 + {
537 + return addHandler( PageLoadEvent.TYPE, handler );
538 + }
539 +
540 + public HandlerRegistration addPagingFailureHandler( PagingFailureHandler handler )
541 + {
542 + return addHandler( PagingFailureEvent.TYPE, handler );
543 + }
544 +
545 + /**
546 + * @return the absolute index of the first visible row
547 + */
548 + public int getAbsoluteFirstRowIndex()
549 + {
550 + return currentPage * pageSize;
551 + }
552 +
553 + /**
554 + * @return the absolute index of the last visible row
555 + */
556 + public int getAbsoluteLastRowIndex()
557 + {
558 + if ( tableModel.getRowCount() < 0 )
559 + {
560 + // Unknown row count, so just return based on current page
561 + return (currentPage + 1) * pageSize - 1;
562 + }
563 + else if ( pageSize == 0 )
564 + {
565 + // Only one page, so return row count
566 + return tableModel.getRowCount() - 1;
567 + }
568 + return Math.min( tableModel.getRowCount(), (currentPage + 1) * pageSize ) - 1;
569 + }
570 +
571 + /**
572 + * @return the current page
573 + */
574 + public int getCurrentPage()
575 + {
576 + return currentPage;
577 + }
578 +
579 + /**
580 + * @return the widget displayed when the data table is empty
581 + */
582 + public Widget getEmptyTableWidget()
583 + {
584 + return emptyTableWidgetWrapper.getWidget();
114 585 }
115 586
116 587 @Override
117 - public void setStyleAttribute(String attr, String value) {
118 - table.getDataTable().getFixedWidthGridCellFormatter().getRawElement(
119 - getRowIndex(), getCellIndex()).getStyle().setProperty(attr, value);
588 + public int getMaximumColumnWidth( int column )
589 + {
590 + ColumnDefinition<RowType, ?> colDef = getColumnDefinition( column );
591 + if ( colDef == null )
592 + {
593 + return -1;
594 + }
595 + return colDef.getColumnProperty( MaximumWidthProperty.TYPE ).getMaximumColumnWidth();
120 596 }
121 597
122 598 @Override
123 - public void setStyleName(String stylename) {
124 - table.getDataTable().getCellFormatter().setStyleName(getRowIndex(),
125 - getCellIndex(), stylename);
599 + public int getMinimumColumnWidth( int column )
600 + {
601 + ColumnDefinition<RowType, ?> colDef = getColumnDefinition( column );
602 + if ( colDef == null )
603 + {
604 + return FixedWidthGrid.MIN_COLUMN_WIDTH;
605 + }
606 + int minWidth = colDef.getColumnProperty( MinimumWidthProperty.TYPE ).getMinimumColumnWidth();
607 + return Math.max( FixedWidthGrid.MIN_COLUMN_WIDTH, minWidth );
608 + }
609 +
610 + /**
611 + * @return the number of pages, or -1 if not known
612 + */
613 + public int getPageCount()
614 + {
615 + if ( pageSize < 1 )
616 + {
617 + return 1;
618 + }
619 + else
620 + {
621 + int numDataRows = tableModel.getRowCount();
622 + if ( numDataRows < 0 )
623 + {
624 + return -1;
625 + }
626 + return (int) Math.ceil( numDataRows / (pageSize + 0.0) );
627 + }
628 + }
629 +
630 + /**
631 + * @return the number of rows per page
632 + */
633 + public int getPageSize()
634 + {
635 + return pageSize;
126 636 }
127 637
128 638 @Override
129 - public void setText(String text) {
130 - table.getDataTable().setText(getRowIndex(), getCellIndex(), text);
639 + public int getPreferredColumnWidth( int column )
640 + {
641 + ColumnDefinition<RowType, ?> colDef = getColumnDefinition( column );
642 + if ( colDef == null )
643 + {
644 + return FixedWidthGrid.DEFAULT_COLUMN_WIDTH;
645 + }
646 + return colDef.getColumnProperty( PreferredWidthProperty.TYPE ).getPreferredColumnWidth();
647 + }
648 +
649 + /**
650 + * Get the value associated with a row.
651 + *
652 + * @param row the row index
653 + *
654 + * @return the value associated with the row
655 + */
656 + public RowType getRowValue( int row )
657 + {
658 + if ( rowValues.size() <= row )
659 + {
660 + return null;
661 + }
662 + return rowValues.get( row );
663 + }
664 +
665 + /**
666 + * Get the selected row values. If cross page selection is enabled, this will
667 + * include row values selected on all pages.
668 + *
669 + * @return the selected row values
670 + *
671 + * @see #setCrossPageSelectionEnabled(boolean)
672 + */
673 + public Set<RowType> getSelectedRowValues()
674 + {
675 + return selectedRowValues;
676 + }
677 +
678 + public TableDefinition<RowType> getTableDefinition()
679 + {
680 + return tableDefinition;
681 + }
682 +
683 + /**
684 + * @return the table model
685 + */
686 + public TableModel<RowType> getTableModel()
687 + {
688 + return tableModel;
689 + }
690 +
691 + /**
692 + * Go to the first page.
693 + */
694 + public void gotoFirstPage()
695 + {
696 + gotoPage( 0, false );
697 + }
698 +
699 + /**
700 + * Go to the last page. If the number of pages is not known, this method is
701 + * ignored.
702 + */
703 + public void gotoLastPage()
704 + {
705 + if ( getPageCount() >= 0 )
706 + {
707 + gotoPage( getPageCount(), false );
708 + }
709 + }
710 +
711 + /**
712 + * Go to the next page.
713 + */
714 + public void gotoNextPage()
715 + {
716 + gotoPage( currentPage + 1, false );
717 + }
718 +
719 + /**
720 + * Set the current page. If the page is out of bounds, it will be
721 + * automatically set to zero or the last page without throwing any errors.
722 + *
723 + * @param page the page
724 + * @param forced reload the page even if it is already loaded
725 + */
726 + public void gotoPage( int page, boolean forced )
727 + {
728 + int oldPage = currentPage;
729 + int numPages = getPageCount();
730 + if ( numPages >= 0 )
731 + {
732 + currentPage = Math.max( 0, Math.min( page, numPages - 1 ) );
733 + }
734 + else
735 + {
736 + currentPage = page;
737 + }
738 +
739 + if ( currentPage != oldPage || forced )
740 + {
741 + isPageLoading = true;
742 +
743 + // Deselect rows when switching pages
744 + FixedWidthGrid dataTable = getDataTable();
745 + dataTable.deselectAllRows();
746 + if ( !isCrossPageSelectionEnabled )
747 + {
748 + selectedRowValues = new HashSet<RowType>();
749 + }
750 +
751 + // Fire listeners
752 + fireEvent( new PageChangeEvent( oldPage, currentPage ) );
753 +
754 + // Clear out existing data if we aren't bulk rendering
755 + if ( bulkRenderer == null )
756 + {
757 + int rowCount = getAbsoluteLastRowIndex() - getAbsoluteFirstRowIndex() + 1;
758 + if ( rowCount != dataTable.getRowCount() )
759 + {
760 + dataTable.resizeRows( rowCount );
761 + }
762 + dataTable.clearAll();
763 + }
764 +
765 + // Request the new data from the table model
766 + int firstRow = getAbsoluteFirstRowIndex();
767 + int lastRow = pageSize == 0 ? tableModel.getRowCount() : pageSize;
768 + lastRequest = new Request( firstRow, lastRow, dataTable.getColumnSortList() );
769 + tableModel.requestRows( lastRequest, pagingCallback );
770 + }
771 + }
772 +
773 + /**
774 + * Go to the previous page.
775 + */
776 + public void gotoPreviousPage()
777 + {
778 + gotoPage( currentPage - 1, false );
131 779 }
132 780
133 781 @Override
134 - public void setVerticalAlignment(VerticalAlignmentConstant align) {
135 - table.getDataTable().getCellFormatter().setVerticalAlignment(
136 - getRowIndex(), getCellIndex(), align);
782 + public boolean isColumnSortable( int column )
783 + {
784 + ColumnDefinition<RowType, ?> colDef = getColumnDefinition( column );
785 + if ( colDef == null )
786 + {
787 + return true;
788 + }
789 + if ( getSortPolicy() == SortPolicy.DISABLED )
790 + {
791 + return false;
792 + }
793 + return colDef.getColumnProperty( SortableProperty.TYPE ).isColumnSortable();
137 794 }
138 795
139 796 @Override
140 - public void setWidget(Widget widget) {
141 - table.getDataTable().setWidget(getRowIndex(), getCellIndex(), widget);
797 + public boolean isColumnTruncatable( int column )
798 + {
799 + ColumnDefinition<RowType, ?> colDef = getColumnDefinition( column );
800 + if ( colDef == null )
801 + {
802 + return true;
803 + }
804 + return colDef.getColumnProperty( TruncationProperty.TYPE ).isColumnTruncatable();
142 805 }
143 - }
144 806
145 - /**
146 - * A custom {@link AbstractRowView} used by the {@link PagingScrollTable}.
147 - *
148 - * @param <RowType> the type of the row values
149 - */
150 - protected static class PagingScrollTableRowView<RowType> extends
151 - AbstractRowView<RowType> {
152 - private PagingScrollTable<RowType> table;
153 -
154 - public PagingScrollTableRowView(PagingScrollTable<RowType> table) {
155 - super(new PagingScrollTableCellView<RowType>(table));
156 - this.table = table;
807 + /**
808 + * @return true if cross page selection is enabled
809 + */
810 + public boolean isCrossPageSelectionEnabled()
811 + {
812 + return isCrossPageSelectionEnabled;
157 813 }
158 814
159 815 @Override
160 - public void setStyleAttribute(String attr, String value) {
161 - table.getDataTable().getFixedWidthGridRowFormatter().getRawElement(
162 - getRowIndex()).getStyle().setProperty(attr, value);
816 + public boolean isFooterColumnTruncatable( int column )
817 + {
818 + ColumnDefinition<RowType, ?> colDef = getColumnDefinition( column );
819 + if ( colDef == null )
820 + {
821 + return true;
822 + }
823 + return colDef.getColumnProperty( TruncationProperty.TYPE ).isFooterTruncatable();
163 824 }
164 825
165 - @Override
166 - public void setStyleName(String stylename) {
167 - // If the row is selected, add the selected style name back
168 - if (table.getDataTable().isRowSelected(getRowIndex())) {
169 - stylename += " selected";
170 - }
171 - table.getDataTable().getRowFormatter().setStyleName(getRowIndex(),
172 - stylename);
173 - }
174 - }
175 -
176 - /**
177 - * Information about a column header.
178 - */
179 - private static class ColumnHeaderInfo {
180 - private int rowSpan = 1;
181 - private Object header;
182 -
183 - public ColumnHeaderInfo(Object header) {
184 - this.header = (header == null) ? "" : header;
185 - }
186 -
187 - public ColumnHeaderInfo(Object header, int rowSpan) {
188 - this.header = (header == null) ? "&nbsp;" : header;
189 - this.rowSpan = rowSpan;
826 + /**
827 + * @return true if the footer table is automatically generated
828 + */
829 + public boolean isFooterGenerated()
830 + {
831 + return isFooterGenerated;
190 832 }
191 833
192 834 @Override
193 - public boolean equals(Object o) {
194 - if (o == null) {
195 - return false;
196 - }
197 - if (o instanceof ColumnHeaderInfo) {
198 - ColumnHeaderInfo info = (ColumnHeaderInfo) o;
199 - return (rowSpan == info.rowSpan) && header.equals(info.header);
200 - }
201 - return false;
202 - }
203 -
204 - public Object getHeader() {
205 - return header;
206 - }
207 -
208 - public int getRowSpan() {
209 - return rowSpan;
210 - }
211 -
212 - public void incrementRowSpan() {
213 - rowSpan++;
214 - }
215 - }
216 -
217 - /**
218 - * An iterator over the visible rows in an iterator over many rows.
219 - */
220 - private class VisibleRowsIterator implements Iterator<RowType> {
221 - /**
222 - * The iterator of row data.
223 - */
224 - private Iterator<RowType> rows;
225 -
226 - /**
227 - * The current row of the rows iterator.
228 - */
229 - private int curRow;
230 -
231 - /**
232 - * The last visible row in the grid.
233 - */
234 - private int lastVisibleRow;
235 -
236 - /**
237 - * Constructor.
238 - *
239 - * @param rows the iterator over row data
240 - * @param firstRow the first absolute row of the rows iterator
241 - * @param firstVisibleRow the first visible row in this grid
242 - * @param lastVisibleRow the last visible row in this grid
243 - */
244 - public VisibleRowsIterator(Iterator<RowType> rows, int firstRow,
245 - int firstVisibleRow, int lastVisibleRow) {
246 - this.curRow = firstRow;
247 - this.lastVisibleRow = lastVisibleRow;
248 -
249 - // Iterate up to the first row
250 - while (curRow < firstVisibleRow && rows.hasNext()) {
251 - rows.next();
252 - curRow++;
253 - }
254 - this.rows = rows;
255 - }
256 -
257 - public boolean hasNext() {
258 - return (curRow <= lastVisibleRow && rows.hasNext());
259 - }
260 -
261 - public RowType next() {
262 - // Check that the next row exists
263 - if (!hasNext()) {
264 - throw new NoSuchElementException();
265 - }
266 - return rows.next();
267 - }
268 -
269 - public void remove() {
270 - throw new UnsupportedOperationException("Remove not supported");
271 - }
272 - }
273 -
274 - /**
275 - * The bulk render used to render the contents of this table.
276 - */
277 - private FixedWidthGridBulkRenderer<RowType> bulkRenderer = null;
278 -
279 - /**
280 - * The wrapper around the empty table widget.
281 - */
282 - private SimplePanel emptyTableWidgetWrapper = new SimplePanel();
283 -
284 - /**
285 - * The definition of the columns in the table.
286 - */
287 - private TableDefinition<RowType> tableDefinition = null;
288 -
289 - /**
290 - * The current visible page.
291 - */
292 - private int currentPage = -1;
293 -
294 - /**
295 - * The last request that was sent to the {@link TableModel}.
296 - */
297 - private Request lastRequest = null;
298 -
299 - /**
300 - * A boolean indicating that cross page selection is enabled.
301 - */
302 - private boolean isCrossPageSelectionEnabled;
303 -
304 - /**
305 - * The set of selected row values.
306 - */
307 - private Set<RowType> selectedRowValues = new HashSet<RowType>();
308 -
309 - /**
310 - * A boolean indicating that the footer should be generated automatically.
311 - */
312 - private boolean isFooterGenerated;
313 -
314 - /**
315 - * A boolean indicating that the header should be generated automatically.
316 - */
317 - private boolean isHeaderGenerated;
318 -
319 - /**
320 - * A boolean indicating that the page is currently being loaded.
321 - */
322 - private boolean isPageLoading;
323 -
324 - /**
325 - * The old page count, used to detect when the number of pages changes.
326 - */
327 - private int oldPageCount;
328 -
329 - /**
330 - * The number of rows per page. If the number of rows per page is equal to the
331 - * number of rows, paging is disabled because only one page exists.
332 - */
333 - private int pageSize = 0;
334 -
335 - /**
336 - * The callback that handles page requests.
337 - */
338 - private Callback<RowType> pagingCallback = new Callback<RowType>() {
339 - public void onFailure(Throwable caught) {
340 - isPageLoading = false;
341 - fireEvent(new PagingFailureEvent(caught));
342 - }
343 -
344 - public void onRowsReady(Request request, Response<RowType> response) {
345 - if (lastRequest == request) {
346 - setData(request.getStartRow(), response.getRowValues());
347 - lastRequest = null;
348 - }
349 - }
350 - };
351 -
352 - /**
353 - * The values associated with each row. This is an optional list of data that
354 - * ties the visible content in each row to an underlying object.
355 - */
356 - private List<RowType> rowValues = new ArrayList<RowType>();
357 -
358 - /**
359 - * The view of this table.
360 - */
361 - private AbstractRowView<RowType> rowView = new PagingScrollTableRowView<RowType>(
362 - this);
363 -
364 - /**
365 - * The {@link CheckBox} used to select all rows.
366 - */
367 - private Widget selectAllWidget;
368 -
369 - /**
370 - * The underlying table model.
371 - */
372 - private TableModel<RowType> tableModel;
373 -
374 - /**
375 - * The {@link RendererCallback} used when table rendering completes.
376 - */
377 - private RendererCallback tableRendererCallback = new RendererCallback() {
378 - public void onRendered() {
379 - onDataTableRendered();
380 - }
381 - };
382 -
383 - /**
384 - * The columns that are currently visible.
385 - */
386 - private List<ColumnDefinition<RowType, ?>> visibleColumns = new ArrayList<ColumnDefinition<RowType, ?>>();
387 -
388 - /**
389 - * The boolean indicating that the header tables are obsolete.
390 - */
391 - private boolean headersObsolete;
392 -
393 - /**
394 - * Construct a new {@link PagingScrollTable}.
395 - *
396 - * @param tableModel the underlying table model
397 - * @param tableDefinition the column definitions
398 - */
399 - public PagingScrollTable(TableModel<RowType> tableModel,
400 - TableDefinition<RowType> tableDefinition) {
401 - this(tableModel, new FixedWidthGrid(), new FixedWidthFlexTable(),
402 - tableDefinition);
403 - isHeaderGenerated = true;
404 - isFooterGenerated = true;
405 - }
406 -
407 - /**
408 - * Construct a new {@link PagingScrollTable}.
409 - *
410 - * @param tableModel the underlying table model
411 - * @param dataTable the table used to display data
412 - * @param headerTable the header table
413 - * @param tableDefinition the column definitions
414 - */
415 - public PagingScrollTable(TableModel<RowType> tableModel,
416 - FixedWidthGrid dataTable, FixedWidthFlexTable headerTable,
417 - TableDefinition<RowType> tableDefinition) {
418 - this(tableModel, dataTable, headerTable, tableDefinition,
419 - GWT.<ScrollTableImages> create(ScrollTableImages.class));
420 - }
421 -
422 - /**
423 - * Construct a new {@link PagingScrollTable} with custom images.
424 - *
425 - * @param tableModel the underlying table model
426 - * @param dataTable the table used to display data
427 - * @param headerTable the header table
428 - * @param tableDefinition the column definitions
429 - * @param images the images to use in the table
430 - */
431 - public PagingScrollTable(TableModel<RowType> tableModel,
432 - FixedWidthGrid dataTable, FixedWidthFlexTable headerTable,
433 - TableDefinition<RowType> tableDefinition, ScrollTableImages images) {
434 - super(dataTable, headerTable, images);
435 - this.tableModel = tableModel;
436 - setTableDefinition(tableDefinition);
437 - refreshVisibleColumnDefinitions();
438 - oldPageCount = getPageCount();
439 -
440 - // Setup the empty table widget wrapper
441 - emptyTableWidgetWrapper.getElement().getStyle().setProperty("width", "100%");
442 - emptyTableWidgetWrapper.getElement().getStyle().setProperty("overflow",
443 - "hidden");
444 - emptyTableWidgetWrapper.getElement().getStyle().setPropertyPx("border", 0);
445 - emptyTableWidgetWrapper.getElement().getStyle().setPropertyPx("margin", 0);
446 - emptyTableWidgetWrapper.getElement().getStyle().setPropertyPx("padding", 0);
447 - insert(emptyTableWidgetWrapper, getAbsoluteElement(), 2, true);
448 - setEmptyTableWidgetVisible(false);
449 -
450 - // Listen to table model events
451 - tableModel.addRowCountChangeHandler(new RowCountChangeHandler() {
452 - public void onRowCountChange(RowCountChangeEvent event) {
835 + public boolean isHeaderColumnTruncatable( int column )
836 + {
837 + ColumnDefinition<RowType, ?> colDef = getColumnDefinition( column );
838 + if ( colDef == null )
839 + {
840 + return true;
841 + }
842 + return colDef.getColumnProperty( TruncationProperty.TYPE ).isHeaderTruncatable();
843 + }
844 +
845 + /**
846 + * @return true if the header table is automatically generated
847 + */
848 + public boolean isHeaderGenerated()
849 + {
850 + return isHeaderGenerated;
851 + }
852 +
853 + /**
854 + * @return true if a page load is pending
855 + */
856 + public boolean isPageLoading()
857 + {
858 + return isPageLoading;
859 + }
860 +
861 + /**
862 + * Reload the current page.
863 + */
864 + public void reloadPage()
865 + {
866 + if ( currentPage >= 0 )
867 + {
868 + gotoPage( currentPage, true );
869 + }
870 + else
871 + {
872 + gotoPage( 0, true );
873 + }
874 + }
875 +
876 + /**
877 + * Set the {@link FixedWidthGridBulkRenderer} used to render the data table.
878 + *
879 + * @param bulkRenderer the table renderer
880 + */
881 + public void setBulkRenderer( FixedWidthGridBulkRenderer<RowType> bulkRenderer )
882 + {
883 + this.bulkRenderer = bulkRenderer;
884 + }
885 +
886 + /**
887 + * Enable or disable cross page selection. When enabled, row value selections
888 + * are maintained across page loads. Selections are remembered by type (not by
889 + * row index), so row values can move around and still maintain their
890 + * selection.
891 + *
892 + * @param enabled true to enable, false to disable
893 + */
894 + public void setCrossPageSelectionEnabled( boolean enabled )
895 + {
896 + if ( isCrossPageSelectionEnabled != enabled )
897 + {
898 + this.isCrossPageSelectionEnabled = enabled;
899 +
900 + // Reselected only the rows on this page
901 + if ( !enabled )
902 + {
903 + selectedRowValues = new HashSet<RowType>();
904 + Set<Integer> selectedRows = getDataTable().getSelectedRows();
905 + for ( Integer selectedRow : selectedRows )
906 + {
907 + selectedRowValues.add( getRowValue( selectedRow ) );
908 + }
909 + }
910 + }
911 + }
912 +
913 + /**
914 + * Set the {@link Widget} that will be displayed in place of the data table
915 + * when the data table has no data to display.
916 + *
917 + * @param emptyTableWidget the widget to display when the data table is empty
918 + */
919 + public void setEmptyTableWidget( Widget emptyTableWidget )
920 + {
921 + emptyTableWidgetWrapper.setWidget( emptyTableWidget );
922 + }
923 +
924 + /**
925 + * Set whether or not the footer table should be automatically generated.
926 + *
927 + * @param isGenerated true to enable, false to disable
928 + */
929 + public void setFooterGenerated( boolean isGenerated )
930 + {
931 + this.isFooterGenerated = isGenerated;
932 + if ( isGenerated )
933 + {
934 + refreshFooterTable();
935 + }
936 + }
937 +
938 + /**
939 + * Set whether or not the header table should be automatically generated.
940 + *
941 + * @param isGenerated true to enable, false to disable
942 + */
943 + public void setHeaderGenerated( boolean isGenerated )
944 + {
945 + this.isHeaderGenerated = isGenerated;
946 + if ( isGenerated )
947 + {
948 + refreshHeaderTable();
949 + }
950 + }
951 +
952 + /**
953 + * Set the number of rows per page.
954 + * <p/>
955 + * By default, the page size is zero, which indicates that all rows should be
956 + * shown on the page.
957 + *
958 + * @param pageSize the number of rows per page
959 + */
960 + public void setPageSize( int pageSize )
961 + {
962 + pageSize = Math.max( 0, pageSize );
963 + this.pageSize = pageSize;
964 +
453 965 int pageCount = getPageCount();
454 - if (pageCount != oldPageCount) {
455 - fireEvent(new PageCountChangeEvent(oldPageCount, pageCount));
456 - oldPageCount = pageCount;
457 - }
458 - }
459 - });
460 - if (tableModel instanceof HasRowInsertionHandlers) {
461 - ((HasRowInsertionHandlers) tableModel).addRowInsertionHandler(new RowInsertionHandler() {
462 - public void onRowInsertion(RowInsertionEvent event) {
463 - insertAbsoluteRow(event.getRowIndex());
464 - }
465 - });
466 - }
467 - if (tableModel instanceof HasRowRemovalHandlers) {
468 - ((HasRowRemovalHandlers) tableModel).addRowRemovalHandler(new RowRemovalHandler() {
469 - public void onRowRemoval(RowRemovalEvent event) {
470 - removeAbsoluteRow(event.getRowIndex());
471 - }
472 - });
473 - }
474 - if (tableModel instanceof HasRowValueChangeHandlers) {
475 - ((HasRowValueChangeHandlers<RowType>) tableModel).addRowValueChangeHandler(new RowValueChangeHandler<RowType>() {
476 - public void onRowValueChange(RowValueChangeEvent<RowType> event) {
477 - int rowIndex = event.getRowIndex();
478 - if (rowIndex < getAbsoluteFirstRowIndex()
479 - || rowIndex > getAbsoluteLastRowIndex()) {
966 + if ( pageCount != oldPageCount )
967 + {
968 + fireEvent( new PageCountChangeEvent( oldPageCount, pageCount ) );
969 + oldPageCount = pageCount;
970 + }
971 +
972 + // Reset the page
973 + if ( currentPage >= 0 )
974 + {
975 + gotoPage( currentPage, true );
976 + }
977 + }
978 +
979 + /**
980 + * Associate a row in the table with a value.
981 + *
982 + * @param row the row index
983 + * @param value the value to associate
984 + */
985 + public void setRowValue( int row, RowType value )
986 + {
987 + // Make sure the list can fit the row
988 + for ( int i = rowValues.size(); i <= row; i++ )
989 + {
990 + rowValues.add( null );
991 + }
992 +
993 + // Set the row value
994 + rowValues.set( row, value );
995 +
996 + // Render the new row value
997 + refreshRow( row );
998 + }
999 +
1000 + /**
1001 + * Set the {@link TableDefinition} used to define the columns.
1002 + *
1003 + * @param tableDefinition the new table definition.
1004 + */
1005 + public void setTableDefinition( TableDefinition<RowType> tableDefinition )
1006 + {
1007 + assert tableDefinition != null : "tableDefinition cannot be null";
1008 + this.tableDefinition = tableDefinition;
1009 + }
1010 +
1011 + /**
1012 + * Invoke the cell editor on a cell, if one is set. If a cell editor is not
1013 + * specified, this method has no effect.
1014 + */
1015 + protected void editCell( int row, int column )
1016 + {
1017 + // Get the cell editor
1018 + final ColumnDefinition colDef = getColumnDefinition( column );
1019 + if ( colDef == null )
1020 + {
480 1021 return;
481 - }
482 - setRowValue(rowIndex - getAbsoluteFirstRowIndex(),
483 - event.getRowValue());
484 - }
485 - });
486 - }
487 -
488 - // Listen for cell click events
489 - dataTable.addTableListener(new TableListener() {
490 - public void onCellClicked(SourcesTableEvents sender, int row, int cell) {
491 - editCell(row, cell);
492 - }
493 - });
494 -
495 - // Override the column sorter
496 - if (dataTable.getColumnSorter() == null) {
497 - ColumnSorter sorter = new ColumnSorter() {
498 - @Override
499 - public void onSortColumn(SortableGrid grid, ColumnSortList sortList,
500 - ColumnSorterCallback callback) {
501 - reloadPage();
502 - callback.onSortingComplete();
503 - }
504 - };
505 - dataTable.setColumnSorter(sorter);
506 - }
507 -
508 - // Listen for selection events
509 - dataTable.addRowSelectionHandler(new RowSelectionHandler() {
510 - public void onRowSelection(RowSelectionEvent event) {
511 - if (isPageLoading) {
512 - return;
513 - }
514 - Set<Row> deselected = event.getDeselectedRows();
515 - for (Row row : deselected) {
516 - selectedRowValues.remove(getRowValue(row.getRowIndex()));
517 - }
518 - Set<Row> selected = event.getSelectedRows();
519 - for (Row row : selected) {
520 - selectedRowValues.add(getRowValue(row.getRowIndex()));
521 - }
522 - }
523 - });
524 - }
525 -
526 - public HandlerRegistration addPageChangeHandler(PageChangeHandler handler) {
527 - return addHandler(PageChangeEvent.TYPE, handler);
528 - }
529 -
530 - public HandlerRegistration addPageCountChangeHandler(
531 - PageCountChangeHandler handler) {
532 - return addHandler(PageCountChangeEvent.TYPE, handler);
533 - }
534 -
535 - public HandlerRegistration addPageLoadHandler(PageLoadHandler handler) {
536 - return addHandler(PageLoadEvent.TYPE, handler);
537 - }
538 -
539 - public HandlerRegistration addPagingFailureHandler(
540 - PagingFailureHandler handler) {
541 - return addHandler(PagingFailureEvent.TYPE, handler);
542 - }
543 -
544 - /**
545 - * @return the absolute index of the first visible row
546 - */
547 - public int getAbsoluteFirstRowIndex() {
548 - return currentPage * pageSize;
549 - }
550 -
551 - /**
552 - * @return the absolute index of the last visible row
553 - */
554 - public int getAbsoluteLastRowIndex() {
555 - if (tableModel.getRowCount() < 0) {
556 - // Unknown row count, so just return based on current page
557 - return (currentPage + 1) * pageSize - 1;
558 - } else if (pageSize == 0) {
559 - // Only one page, so return row count
560 - return tableModel.getRowCount() - 1;
561 - }
562 - return Math.min(tableModel.getRowCount(), (currentPage + 1) * pageSize) - 1;
563 - }
564 -
565 - /**
566 - * @return the current page
567 - */
568 - public int getCurrentPage() {
569 - return currentPage;
570 - }
571 -
572 - /**
573 - * @return the widget displayed when the data table is empty
574 - */
575 - public Widget getEmptyTableWidget() {
576 - return emptyTableWidgetWrapper.getWidget();
577 - }
578 -
579 - @Override
580 - public int getMaximumColumnWidth(int column) {
581 - ColumnDefinition<RowType, ?> colDef = getColumnDefinition(column);
582 - if (colDef == null) {
583 - return -1;
584 - }
585 - return colDef.getColumnProperty(MaximumWidthProperty.TYPE).getMaximumColumnWidth();
586 - }
587 -
588 - @Override
589 - public int getMinimumColumnWidth(int column) {
590 - ColumnDefinition<RowType, ?> colDef = getColumnDefinition(column);
591 - if (colDef == null) {
592 - return FixedWidthGrid.MIN_COLUMN_WIDTH;
593 - }
594 - int minWidth = colDef.getColumnProperty(MinimumWidthProperty.TYPE).getMinimumColumnWidth();
595 - return Math.max(FixedWidthGrid.MIN_COLUMN_WIDTH, minWidth);
596 - }
597 -
598 - /**
599 - * @return the number of pages, or -1 if not known
600 - */
601 - public int getPageCount() {
602 - if (pageSize < 1) {
603 - return 1;
604 - } else {
605 - int numDataRows = tableModel.getRowCount();
606 - if (numDataRows < 0) {
607 - return -1;
608 - }
609 - return (int) Math.ceil(numDataRows / (pageSize + 0.0));
610 - }
611 - }
612 -
613 - /**
614 - * @return the number of rows per page
615 - */
616 - public int getPageSize() {
617 - return pageSize;
618 - }
619 -
620 - @Override
621 - public int getPreferredColumnWidth(int column) {
622 - ColumnDefinition<RowType, ?> colDef = getColumnDefinition(column);
623 - if (colDef == null) {
624 - return FixedWidthGrid.DEFAULT_COLUMN_WIDTH;
625 - }
626 - return colDef.getColumnProperty(PreferredWidthProperty.TYPE).getPreferredColumnWidth();
627 - }
628 -
629 - /**
630 - * Get the value associated with a row.
631 - *
632 - * @param row the row index
633 - * @return the value associated with the row
634 - */
635 - public RowType getRowValue(int row) {
636 - if (rowValues.size() <= row) {
637 - return null;
638 - }
639 - return rowValues.get(row);
640 - }
641 -
642 - /**
643 - * Get the selected row values. If cross page selection is enabled, this will
644 - * include row values selected on all pages.
645 - *
646 - * @return the selected row values
647 - * @see #setCrossPageSelectionEnabled(boolean)
648 - */
649 - public Set<RowType> getSelectedRowValues() {
650 - return selectedRowValues;
651 - }
652 -
653 - public TableDefinition<RowType> getTableDefinition() {
654 - return tableDefinition;
655 - }
656 -
657 - /**
658 - * @return the table model
659 - */
660 - public TableModel<RowType> getTableModel() {
661 - return tableModel;
662 - }
663 -
664 - /**
665 - * Go to the first page.
666 - */
667 - public void gotoFirstPage() {
668 - gotoPage(0, false);
669 - }
670 -
671 - /**
672 - * Go to the last page. If the number of pages is not known, this method is
673 - * ignored.
674 - */
675 - public void gotoLastPage() {
676 - if (getPageCount() >= 0) {
677 - gotoPage(getPageCount(), false);
678 - }
679 - }
680 -
681 - /**
682 - * Go to the next page.
683 - */
684 - public void gotoNextPage() {
685 - gotoPage(currentPage + 1, false);
686 - }
687 -
688 - /**
689 - * Set the current page. If the page is out of bounds, it will be
690 - * automatically set to zero or the last page without throwing any errors.
691 - *
692 - * @param page the page
693 - * @param forced reload the page even if it is already loaded
694 - */
695 - public void gotoPage(int page, boolean forced) {
696 - int oldPage = currentPage;
697 - int numPages = getPageCount();
698 - if (numPages >= 0) {
699 - currentPage = Math.max(0, Math.min(page, numPages - 1));
700 - } else {
701 - currentPage = page;
702 - }
703 -
704 - if (currentPage != oldPage || forced) {
705 - isPageLoading = true;
706 -
707 - // Deselect rows when switching pages
708 - FixedWidthGrid dataTable = getDataTable();
709 - dataTable.deselectAllRows();
710 - if (!isCrossPageSelectionEnabled) {
711 - selectedRowValues = new HashSet<RowType>();
712 - }
713 -
714 - // Fire listeners
715 - fireEvent(new PageChangeEvent(oldPage, currentPage));
716 -
717 - // Clear out existing data if we aren't bulk rendering
718 - if (bulkRenderer == null) {
719 - int rowCount = getAbsoluteLastRowIndex() - getAbsoluteFirstRowIndex()
720 - + 1;
721 - if (rowCount != dataTable.getRowCount()) {
722 - dataTable.resizeRows(rowCount);
723 - }
724 - dataTable.clearAll();
725 - }
726 -
727 - // Request the new data from the table model
728 - int firstRow = getAbsoluteFirstRowIndex();
729 - int lastRow = pageSize == 0 ? tableModel.getRowCount() : pageSize;
730 - lastRequest = new Request(firstRow, lastRow,
731 - dataTable.getColumnSortList());
732 - tableModel.requestRows(lastRequest, pagingCallback);
733 - }
734 - }
735 -
736 - /**
737 - * Go to the previous page.
738 - */
739 - public void gotoPreviousPage() {
740 - gotoPage(currentPage - 1, false);
741 - }
742 -
743 - @Override
744 - public boolean isColumnSortable(int column) {
745 - ColumnDefinition<RowType, ?> colDef = getColumnDefinition(column);
746 - if (colDef == null) {
747 - return true;
748 - }
749 - if (getSortPolicy() == SortPolicy.DISABLED) {
750 - return false;
751 - }
752 - return colDef.getColumnProperty(SortableProperty.TYPE).isColumnSortable();
753 - }
754 -
755 - @Override
756 - public boolean isColumnTruncatable(int column) {
757 - ColumnDefinition<RowType, ?> colDef = getColumnDefinition(column);
758 - if (colDef == null) {
759 - return true;
760 - }
761 - return colDef.getColumnProperty(TruncationProperty.TYPE).isColumnTruncatable();
762 - }
763 -
764 - /**
765 - * @return true if cross page selection is enabled
766 - */
767 - public boolean isCrossPageSelectionEnabled() {
768 - return isCrossPageSelectionEnabled;
769 - }
770 -
771 - @Override
772 - public boolean isFooterColumnTruncatable(int column) {
773 - ColumnDefinition<RowType, ?> colDef = getColumnDefinition(column);
774 - if (colDef == null) {
775 - return true;
776 - }
777 - return colDef.getColumnProperty(TruncationProperty.TYPE).isFooterTruncatable();
778 - }
779 -
780 - /**
781 - * @return true if the footer table is automatically generated
782 - */
783 - public boolean isFooterGenerated() {
784 - return isFooterGenerated;
785 - }
786 -
787 - @Override
788 - public boolean isHeaderColumnTruncatable(int column) {
789 - ColumnDefinition<RowType, ?> colDef = getColumnDefinition(column);
790 - if (colDef == null) {
791 - return true;
792 - }
793 - return colDef.getColumnProperty(TruncationProperty.TYPE).isHeaderTruncatable();
794 - }
795 -
796 - /**
797 - * @return true if the header table is automatically generated
798 - */
799 - public boolean isHeaderGenerated() {
800 - return isHeaderGenerated;
801 - }
802 -
803 - /**
804 - * @return true if a page load is pending
805 - */
806 - public boolean isPageLoading() {
807 - return isPageLoading;
808 - }
809 -
810 - /**
811 - * Reload the current page.
812 - */
813 - public void reloadPage() {
814 - if (currentPage >= 0) {
815 - gotoPage(currentPage, true);
816 - } else {
817 - gotoPage(0, true);
818 - }
819 - }
820 -
821 - /**
822 - * Set the {@link FixedWidthGridBulkRenderer} used to render the data table.
823 - *
824 - * @param bulkRenderer the table renderer
825 - */
826 - public void setBulkRenderer(FixedWidthGridBulkRenderer<RowType> bulkRenderer) {
827 - this.bulkRenderer = bulkRenderer;
828 - }
829 -
830 - /**
831 - * Enable or disable cross page selection. When enabled, row value selections
832 - * are maintained across page loads. Selections are remembered by type (not by
833 - * row index), so row values can move around and still maintain their
834 - * selection.
835 - *
836 - * @param enabled true to enable, false to disable
837 - */
838 - public void setCrossPageSelectionEnabled(boolean enabled) {
839 - if (isCrossPageSelectionEnabled != enabled) {
840 - this.isCrossPageSelectionEnabled = enabled;
841 -
842 - // Reselected only the rows on this page
843 - if (!enabled) {
844 - selectedRowValues = new HashSet<RowType>();
845 - Set<Integer> selectedRows = getDataTable().getSelectedRows();
846 - for (Integer selectedRow : selectedRows) {
847 - selectedRowValues.add(getRowValue(selectedRow));
848 - }
849 - }
850 - }
851 - }
852 -
853 - /**
854 - * Set the {@link Widget} that will be displayed in place of the data table
855 - * when the data table has no data to display.
856 - *
857 - * @param emptyTableWidget the widget to display when the data table is empty
858 - */
859 - public void setEmptyTableWidget(Widget emptyTableWidget) {
860 - emptyTableWidgetWrapper.setWidget(emptyTableWidget);
861 - }
862 -
863 - /**
864 - * Set whether or not the footer table should be automatically generated.
865 - *
866 - * @param isGenerated true to enable, false to disable
867 - */
868 - public void setFooterGenerated(boolean isGenerated) {
869 - this.isFooterGenerated = isGenerated;
870 - if (isGenerated) {
871 - refreshFooterTable();
872 - }
873 - }
874 -
875 - /**
876 - * Set whether or not the header table should be automatically generated.
877 - *
878 - * @param isGenerated true to enable, false to disable
879 - */
880 - public void setHeaderGenerated(boolean isGenerated) {
881 - this.isHeaderGenerated = isGenerated;
882 - if (isGenerated) {
883 - refreshHeaderTable();
884 - }
885 - }
886 -
887 - /**
888 - * Set the number of rows per page.
889 - *
890 - * By default, the page size is zero, which indicates that all rows should be
891 - * shown on the page.
892 - *
893 - * @param pageSize the number of rows per page
894 - */
895 - public void setPageSize(int pageSize) {
896 - pageSize = Math.max(0, pageSize);
897 - this.pageSize = pageSize;
898 -
899 - int pageCount = getPageCount();
900 - if (pageCount != oldPageCount) {
901 - fireEvent(new PageCountChangeEvent(oldPageCount, pageCount));
902 - oldPageCount = pageCount;
903 - }
904 -
905 - // Reset the page
906 - if (currentPage >= 0) {
907 - gotoPage(currentPage, true);
908 - }
909 - }
910 -
911 - /**
912 - * Associate a row in the table with a value.
913 - *
914 - * @param row the row index
915 - * @param value the value to associate
916 - */
917 - public void setRowValue(int row, RowType value) {
918 - // Make sure the list can fit the row
919 - for (int i = rowValues.size(); i <= row; i++) {
920 - rowValues.add(null);
921 - }
922 -
923 - // Set the row value
924 - rowValues.set(row, value);
925 -
926 - // Render the new row value
927 - refreshRow(row);
928 - }
929 -
930 - /**
931 - * Set the {@link TableDefinition} used to define the columns.
932 - *
933 - * @param tableDefinition the new table definition.
934 - */
935 - public void setTableDefinition(TableDefinition<RowType> tableDefinition) {
936 - assert tableDefinition != null : "tableDefinition cannot be null";
937 - this.tableDefinition = tableDefinition;
938 - }
939 -
940 - /**
941 - * Invoke the cell editor on a cell, if one is set. If a cell editor is not
942 - * specified, this method has no effect.
943 - */
944 - protected void editCell(int row, int column) {
945 - // Get the cell editor
946 - final ColumnDefinition colDef = getColumnDefinition(column);
947 - if (colDef == null) {
948 - return;
949 - }
950 - CellEditor cellEditor = colDef.getCellEditor();
951 - if (cellEditor == null) {
952 - return;
953 - }
954 -
955 - // Forward the request to the cell editor
956 - final RowType rowValue = getRowValue(row);
957 - CellEditInfo editInfo = new CellEditInfo(getDataTable(), row, column);
958 - cellEditor.editCell(editInfo, colDef.getCellValue(rowValue),
959 - new CellEditor.Callback() {
960 - public void onCancel(CellEditInfo cellEditInfo) {
961 - }
962 -
963 - public void onComplete(CellEditInfo cellEditInfo, Object cellValue) {
964 - colDef.setCellValue(rowValue, cellValue);
965 - if (tableModel instanceof MutableTableModel) {
966 - int row = getAbsoluteFirstRowIndex() + cellEditInfo.getRowIndex();
967 - ((MutableTableModel<RowType>) tableModel).setRowValue(row,
968 - rowValue);
969 - } else {
970 - refreshRow(cellEditInfo.getRowIndex());
971 - }
972 - }
973 - });
974 - }
975 -
976 - /**
977 - * Get the {@link ColumnDefinition} currently associated with a column.
978 - *
979 - * @param colIndex the index of the column
980 - * @return the {@link ColumnDefinition} associated with the column, or null
981 - */
982 - protected ColumnDefinition<RowType, ?> getColumnDefinition(int colIndex) {
983 - if (colIndex < visibleColumns.size()) {
984 - return visibleColumns.get(colIndex);
985 - }
986 - return null;
987 - }
988 -
989 - /**
990 - * @return the index of the first visible row
991 - * @deprecated use {@link #getAbsoluteFirstRowIndex()} instead
992 - */
993 - @Deprecated
994 - protected int getFirstRow() {
995 - return getAbsoluteFirstRowIndex();
996 - }
997 -
998 - /**
999 - * @return the index of the last visible row
1000 - * @deprecated use {@link #getAbsoluteLastRowIndex()} instead
1001 - */
1002 - @Deprecated
1003 - protected int getLastRow() {
1004 - return getAbsoluteLastRowIndex();
1005 - }
1006 -
1007 - /**
1008 - * Get the list of row values associated with the table.
1009 - *
1010 - * @return the list of row value
1011 - */
1012 - protected List<RowType> getRowValues() {
1013 - return rowValues;
1014 - }
1015 -
1016 - /**
1017 - * @return the header widget used to select all rows
1018 - */
1019 - protected Widget getSelectAllWidget() {
1020 - if (selectAllWidget == null) {
1021 - final CheckBox box = new CheckBox();
1022 - selectAllWidget = box;
1023 - box.addClickHandler(new ClickHandler() {
1024 - public void onClick(ClickEvent event) {
1025 - if (box.getValue()) {
1026 - getDataTable().selectAllRows();
1027 - } else {
1028 - getDataTable().deselectAllRows();
1029 - }
1030 - }
1031 - });
1032 - }
1033 - return selectAllWidget;
1034 - }
1035 -
1036 - /**
1037 - * @return the list of current visible column definitions
1038 - */
1039 - protected List<ColumnDefinition<RowType, ?>> getVisibleColumnDefinitions() {
1040 - return visibleColumns;
1041 - }
1042 -
1043 - /**
1044 - * Insert a row into the table relative to the total number of rows.
1045 - *
1046 - * @param beforeRow the row index
1047 - */
1048 - protected void insertAbsoluteRow(int beforeRow) {
1049 - // Physically insert the row
1050 - int lastRow = getAbsoluteLastRowIndex() + 1;
1051 - if (beforeRow <= lastRow) {
1052 - int firstRow = getAbsoluteFirstRowIndex();
1053 - if (beforeRow >= firstRow) {
1054 - // Insert row in the middle of the page
1055 - getDataTable().insertRow(beforeRow - firstRow);
1056 - } else {
1057 - // Insert zero row because row is before this page
1058 - getDataTable().insertRow(0);
1059 - }
1060 - if (getDataTable().getRowCount() > pageSize) {
1061 - getDataTable().removeRow(pageSize);
1062 - }
1063 - }
1064 - }
1065 -
1066 - /**
1067 - * Called when the data table has finished rendering.
1068 - */
1069 - protected void onDataTableRendered() {
1070 - // Refresh the headers if needed
1071 - if (headersObsolete) {
1072 - refreshHeaderTable();
1073 - refreshFooterTable();
1074 - headersObsolete = false;
1075 - }
1076 -
1077 - // Select rows
1078 - FixedWidthGrid dataTable = getDataTable();
1079 - int rowCount = dataTable.getRowCount();
1080 - for (int i = 0; i < rowCount; i++) {
1081 - if (selectedRowValues.contains(getRowValue(i))) {
1082 - dataTable.selectRow(i, false);
1083 - }
1084 - }
1085 -
1086 - // Update the UI of the table
1087 - dataTable.clearIdealWidths();
1088 - redraw();
1089 - isPageLoading = false;
1090 - fireEvent(new PageLoadEvent(currentPage));
1091 - }
1092 -
1093 - /**
1094 - * Update the footer table based on the new {@link ColumnDefinition}.
1095 - */
1096 - protected void refreshFooterTable() {
1097 - if (!isFooterGenerated) {
1098 - return;
1099 - }
1100 -
1101 - // Generate the list of lists of ColumnHeaderInfo.
1102 - List<List<ColumnHeaderInfo>> allInfos = new ArrayList<List<ColumnHeaderInfo>>();
1103 - int columnCount = visibleColumns.size();
1104 - int footerCounts[] = new int[columnCount];
1105 - int maxFooterCount = 0;
1106 - for (int col = 0; col < columnCount; col++) {
1107 - // Get the header property.
1108 - ColumnDefinition<RowType, ?> colDef = visibleColumns.get(col);
1109 - FooterProperty prop = colDef.getColumnProperty(FooterProperty.TYPE);
1110 - int footerCount = prop.getFooterCount();
1111 - footerCounts[col] = footerCount;
1112 - maxFooterCount = Math.max(maxFooterCount, footerCount);
1113 -
1114 - // Add each ColumnHeaderInfo
1115 - List<ColumnHeaderInfo> infos = new ArrayList<ColumnHeaderInfo>();
1116 - ColumnHeaderInfo prev = null;
1117 - for (int row = 0; row < footerCount; row++) {
1118 - Object footer = prop.getFooter(row, col);
1119 - if (prev != null && prev.header.equals(footer)) {
1120 - prev.incrementRowSpan();
1121 - } else {
1122 - prev = new ColumnHeaderInfo(footer);
1123 - infos.add(prev);
1124 - }
1125 - }
1126 - allInfos.add(infos);
1127 - }
1128 -
1129 - // Return early if there is no footer
1130 - if (maxFooterCount == 0) {
1131 - return;
1132 - }
1133 -
1134 - // Fill in missing rows
1135 - for (int col = 0; col < columnCount; col++) {
1136 - int footerCount = footerCounts[col];
1137 - if (footerCount < maxFooterCount) {
1138 - allInfos.get(col).add(
1139 - new ColumnHeaderInfo(null, maxFooterCount - footerCount));
1140 - }
1141 - }
1142 -
1143 - // Ensure that we have a footer table
1144 - if (getFooterTable() == null) {
1145 - setFooterTable(new FixedWidthFlexTable());
1146 - }
1147 -
1148 - // Refresh the table
1149 - refreshHeaderTable(getFooterTable(), allInfos, false);
1150 - }
1151 -
1152 - /**
1153 - * Update the header table based on the new {@link ColumnDefinition}.
1154 - */
1155 - protected void refreshHeaderTable() {
1156 - if (!isHeaderGenerated) {
1157 - return;
1158 - }
1159 -
1160 - // Generate the list of lists of ColumnHeaderInfo.
1161 - List<List<ColumnHeaderInfo>> allInfos = new ArrayList<List<ColumnHeaderInfo>>();
1162 - int columnCount = visibleColumns.size();
1163 - int headerCounts[] = new int[columnCount];
1164 - int maxHeaderCount = 0;
1165 - for (int col = 0; col < columnCount; col++) {
1166 - // Get the header property.
1167 - ColumnDefinition<RowType, ?> colDef = visibleColumns.get(col);
1168 - HeaderProperty prop = colDef.getColumnProperty(HeaderProperty.TYPE);
1169 - int headerCount = prop.getHeaderCount();
1170 - headerCounts[col] = headerCount;
1171 - maxHeaderCount = Math.max(maxHeaderCount, headerCount);
1172 -
1173 - // Add each ColumnHeaderInfo
1174 - List<ColumnHeaderInfo> infos = new ArrayList<ColumnHeaderInfo>();
1175 - ColumnHeaderInfo prev = null;
1176 - for (int row = 0; row < headerCount; row++) {
1177 - Object header = prop.getHeader(row, col);
1178 - if (prev != null && prev.header.equals(header)) {
1179 - prev.incrementRowSpan();
1180 - } else {
1181 - prev = new ColumnHeaderInfo(header);
1182 - infos.add(0, prev);
1183 - }
1184 - }
1185 - allInfos.add(infos);
1186 - }
1187 -
1188 - // Return early if there is no header
1189 - if (maxHeaderCount == 0) {
1190 - return;
1191 - }
1192 -
1193 - // Fill in missing rows
1194 - for (int col = 0; col < columnCount; col++) {
1195 - int headerCount = headerCounts[col];
1196 - if (headerCount < maxHeaderCount) {
1197 - allInfos.get(col).add(0,
1198 - new ColumnHeaderInfo(null, maxHeaderCount - headerCount));
1199 - }
1200 - }
1201 -
1202 - // Refresh the table
1203 - refreshHeaderTable(getHeaderTable(), allInfos, true);
1204 - }
1205 -
1206 - /**
1207 - * Refresh the list of the currently visible column definitions based on the
1208 - * {@link TableDefinition}.
1209 - */
1210 - protected void refreshVisibleColumnDefinitions() {
1211 - List<ColumnDefinition<RowType, ?>> colDefs = new ArrayList<ColumnDefinition<RowType, ?>>(
1212 - tableDefinition.getVisibleColumnDefinitions());
1213 - if (!colDefs.equals(visibleColumns)) {
1214 - visibleColumns = colDefs;
1215 - headersObsolete = true;
1216 - } else {
1217 - // Check if any of the headers are dynamic
1218 - for (ColumnDefinition<RowType, ?> colDef : colDefs) {
1219 - if (colDef.getColumnProperty(HeaderProperty.TYPE).isDynamic()
1220 - || colDef.getColumnProperty(FooterProperty.TYPE).isDynamic()) {
1221 - headersObsolete = true;
1222 - return;
1223 - }
1224 - }
1225 - }
1226 - }
1227 -
1228 - /**
1229 - * Remove a row from the table relative to the total number of rows.
1230 - *
1231 - * @param row the row index
1232 - */
1233 - protected void removeAbsoluteRow(int row) {
1234 - // Physically remove the row if it is in the middle of the data table
1235 - int firstRow = getAbsoluteFirstRowIndex();
1236 - int lastRow = getAbsoluteLastRowIndex();
1237 - if (row <= lastRow && row >= firstRow) {
1238 - FixedWidthGrid dataTable = getDataTable();
1239 - int relativeRow = row - firstRow;
1240 - if (relativeRow < dataTable.getRowCount()) {
1241 - dataTable.removeRow(relativeRow);
1242 - }
1243 - }
1244 - }
1245 -
1246 - /**
1247 - * Set a block of data. This method is used when responding to data requests.
1248 - *
1249 - * This method takes an iterator of iterators, where each iterator represents
1250 - * one row of data starting with the first row.
1251 - *
1252 - * @param firstRow the row index that the rows iterator starts with
1253 - * @param rows the values associated with each row
1254 - */
1255 - protected void setData(int firstRow, Iterator<RowType> rows) {
1256 - getDataTable().deselectAllRows();
1257 - rowValues = new ArrayList<RowType>();
1258 - if (rows != null && rows.hasNext()) {
1259 - setEmptyTableWidgetVisible(false);
1260 -
1261 - // Get an iterator over the visible rows
1262 - int firstVisibleRow = getAbsoluteFirstRowIndex();
1263 - int lastVisibleRow = getAbsoluteLastRowIndex();
1264 - Iterator<RowType> visibleIter = new VisibleRowsIterator(rows, firstRow,
1265 - firstVisibleRow, lastVisibleRow);
1266 -
1267 - // Set the row values
1268 - while (visibleIter.hasNext()) {
1269 - rowValues.add(visibleIter.next());
1270 - }
1271 -
1272 - // Copy the visible column definitions
1273 - refreshVisibleColumnDefinitions();
1274 -
1275 - // Render using the bulk renderer
1276 - if (bulkRenderer != null) {
1277 - bulkRenderer.renderRows(rowValues.iterator(), tableRendererCallback);
1278 - return;
1279 - }
1280 -
1281 - // Get rid of unneeded rows and columns
1282 - int rowCount = rowValues.size();
1283 - int colCount = visibleColumns.size();
1284 - getDataTable().resize(rowCount, colCount);
1285 -
1286 - // Render the rows
1287 - tableDefinition.renderRows(0, rowValues.iterator(), rowView);
1288 - } else {
1289 - setEmptyTableWidgetVisible(true);
1290 - }
1291 -
1292 - // Fire page loaded event
1293 - onDataTableRendered();
1294 - }
1295 -
1296 - /**
1297 - * Set whether or not the empty table widget is visible.
1298 - *
1299 - * @param visible true to show the empty table widget
1300 - */
1301 - protected void setEmptyTableWidgetVisible(boolean visible) {
1302 - emptyTableWidgetWrapper.setVisible(visible);
1303 - if (visible) {
1304 - getDataWrapper().getStyle().setProperty("display", "none");
1305 - } else {
1306 - getDataWrapper().getStyle().setProperty("display", "");
1307 - }
1308 - }
1309 -
1310 - /**
1311 - * Update the header or footer tables based on the new
1312 - * {@link ColumnDefinition}.
1313 - *
1314 - * @param table the header or footer table
1315 - * @param allInfos the header info
1316 - * @param isHeader false if refreshing the footer table
1317 - */
1318 - private void refreshHeaderTable(FixedWidthFlexTable table,
1319 - List<List<ColumnHeaderInfo>> allInfos, boolean isHeader) {
1320 - // Return if we have no column definitions.
1321 - if (visibleColumns == null) {
1322 - return;
1323 - }
1324 -
1325 - // Reset the header table.
1326 - int rowCount = table.getRowCount();
1327 - for (int i = 0; i < rowCount; i++) {
1328 - table.removeRow(0);
1329 - }
1330 -
1331 - // Generate the header table
1332 - int columnCount = allInfos.size();
1333 - FlexCellFormatter formatter = table.getFlexCellFormatter();
1334 - List<ColumnHeaderInfo> prevInfos = null;
1335 - for (int col = 0; col < columnCount; col++) {
1336 - List<ColumnHeaderInfo> infos = allInfos.get(col);
1337 - int row = 0;
1338 - for (ColumnHeaderInfo info : infos) {
1339 - // Get the actual row and cell index
1340 - int rowSpan = info.getRowSpan();
1341 - int cell = 0;
1342 - if (table.getRowCount() > row) {
1343 - cell = table.getCellCount(row);
1344 - }
1345 -
1346 - // Compare to the cell in the previous column
1347 - if (prevInfos != null) {
1348 - boolean headerAdded = false;
1349 - int prevRow = 0;
1350 - for (ColumnHeaderInfo prevInfo : prevInfos) {
1351 - // Increase the colSpan of the previous cell
1352 - if (prevRow == row && info.equals(prevInfo)) {
1353 - int colSpan = formatter.getColSpan(row, cell - 1);
1354 - formatter.setColSpan(row, cell - 1, colSpan + 1);
1355 - headerAdded = true;
1356 - break;
1357 - }
1358 - prevRow += prevInfo.getRowSpan();
1359 - }
1360 -
1361 - if (headerAdded) {
1362 - row += rowSpan;
1363 - continue;
1364 - }
1365 - }
1366 -
1367 - // Set the new header
1368 - Object header = info.getHeader();
1369 - if (header instanceof Widget) {
1370 - table.setWidget(row, cell, (Widget) header);
1371 - } else {
1372 - table.setHTML(row, cell, header.toString());
1373 - }
1374 -
1375 - // Update the rowSpan
1376 - if (rowSpan > 1) {
1377 - formatter.setRowSpan(row, cell, rowSpan);
1378 - }
1379 -
1380 - // Increment the row
1381 - row += rowSpan;
1382 - }
1383 -
1384 - // Increment the previous info
1385 - prevInfos = infos;
1386 - }
1387 -
1388 - // Insert the checkbox column
1389 - SelectionPolicy selectionPolicy = getDataTable().getSelectionPolicy();
1390 - if (selectionPolicy.hasInputColumn()) {
1391 - // Get the select all box
1392 - Widget box = null;
1393 - if (isHeader
1394 - && getDataTable().getSelectionPolicy() == SelectionPolicy.CHECKBOX) {
1395 - box = getSelectAllWidget();
1396 - }
1397 -
1398 - // Add the offset column
1399 - table.insertCell(0, 0);
1400 - if (box != null) {
1401 - table.setWidget(0, 0, box);
1402 - } else {
1403 - table.setHTML(0, 0, "&nbsp;");
1404 - }
1405 - formatter.setRowSpan(0, 0, table.getRowCount());
1406 - formatter.setHorizontalAlignment(0, 0,
1407 - HasHorizontalAlignment.ALIGN_CENTER);
1408 - table.setColumnWidth(0, getDataTable().getInputColumnWidth());
1409 - }
1410 - }
1411 -
1412 - /**
1413 - * Refresh a single row in the table.
1414 - *
1415 - * @param rowIndex the index of the row
1416 - */
1417 - private void refreshRow(int rowIndex) {
1418 - final RowType rowValue = getRowValue(rowIndex);
1419 - Iterator<RowType> singleIterator = new Iterator<RowType>() {
1420 - private boolean nextCalled = false;
1421 -
1422 - public boolean hasNext() {
1423 - return !nextCalled;
1424 - }
1425 -
1426 - public RowType next() {
1427 - if (!hasNext()) {
1428 - throw new NoSuchElementException();
1429 - }
1430 - nextCalled = true;
1431 - return rowValue;
1432 - }
1433 -
1434 - public void remove() {
1435 - throw new UnsupportedOperationException();
1436 - }
1437 - };
1438 - tableDefinition.renderRows(rowIndex, singleIterator, rowView);
1439 - }
1022 + }
1023 + CellEditor cellEditor = colDef.getCellEditor();
1024 + if ( cellEditor == null )
1025 + {
1026 + return;
1027 + }
1028 +
1029 + // Forward the request to the cell editor
1030 + final RowType rowValue = getRowValue( row );
1031 + CellEditInfo editInfo = new CellEditInfo( getDataTable(), row, column );
1032 + cellEditor.editCell( editInfo, colDef.getCellValue( rowValue ), new CellEditor.Callback()
1033 + {
1034 + public void onCancel( CellEditInfo cellEditInfo )
1035 + {
1036 + }
1037 +
1038 + public void onComplete( CellEditInfo cellEditInfo, Object cellValue )
1039 + {
1040 + colDef.setCellValue( rowValue, cellValue );
1041 + if ( tableModel instanceof MutableTableModel )
1042 + {
1043 + int row = getAbsoluteFirstRowIndex() + cellEditInfo.getRowIndex();
1044 + ((MutableTableModel<RowType>) tableModel).setRowValue( row, rowValue );
1045 + }
1046 + else
1047 + {
1048 + refreshRow( cellEditInfo.getRowIndex() );
1049 + }
1050 + }
1051 + } );
1052 + }
1053 +
1054 + /**
1055 + * Get the {@link ColumnDefinition} currently associated with a column.
1056 + *
1057 + * @param colIndex the index of the column
1058 + *
1059 + * @return the {@link ColumnDefinition} associated with the column, or null
1060 + */
1061 + protected ColumnDefinition<RowType, ?> getColumnDefinition( int colIndex )
1062 + {
1063 + if ( colIndex < visibleColumns.size() )
1064 + {
1065 + return visibleColumns.get( colIndex );
1066 + }
1067 + return null;
1068 + }
1069 +
1070 + /**
1071 + * @return the index of the first visible row
1072 + *
1073 + * @deprecated use {@link #getAbsoluteFirstRowIndex()} instead
1074 + */
1075 + @Deprecated
1076 + protected int getFirstRow()
1077 + {
1078 + return getAbsoluteFirstRowIndex();
1079 + }
1080 +
1081 + /**
1082 + * @return the index of the last visible row
1083 + *
1084 + * @deprecated use {@link #getAbsoluteLastRowIndex()} instead
1085 + */
1086 + @Deprecated
1087 + protected int getLastRow()
1088 + {
1089 + return getAbsoluteLastRowIndex();
1090 + }
1091 +
1092 + /**
1093 + * Get the list of row values associated with the table.
1094 + *
1095 + * @return the list of row value
1096 + */
1097 + protected List<RowType> getRowValues()
1098 + {
1099 + return rowValues;
1100 + }
1101 +
1102 + /**
1103 + * @return the header widget used to select all rows
1104 + */
1105 + protected Widget getSelectAllWidget()
1106 + {
1107 + if ( selectAllWidget == null )
1108 + {
1109 + final CheckBox box = new CheckBox();
1110 + selectAllWidget = box;
1111 + box.addClickHandler( new ClickHandler()
1112 + {
1113 + public void onClick( ClickEvent event )
1114 + {
1115 + if ( box.getValue() )
1116 + {
1117 + getDataTable().selectAllRows();
1118 + }
1119 + else
1120 + {
1121 + getDataTable().deselectAllRows();
1122 + }
1123 + }
1124 + } );
1125 + }
1126 + return selectAllWidget;
1127 + }
1128 +
1129 + /**
1130 + * @return the list of current visible column definitions
1131 + */
1132 + protected List<ColumnDefinition<RowType, ?>> getVisibleColumnDefinitions()
1133 + {
1134 + return visibleColumns;
1135 + }
1136 +
1137 + /**
1138 + * Insert a row into the table relative to the total number of rows.
1139 + *
1140 + * @param beforeRow the row index
1141 + */
1142 + protected void insertAbsoluteRow( int beforeRow )
1143 + {
1144 + // Physically insert the row
1145 + int lastRow = getAbsoluteLastRowIndex() + 1;
1146 + if ( beforeRow <= lastRow )
1147 + {
1148 + int firstRow = getAbsoluteFirstRowIndex();
1149 + if ( beforeRow >= firstRow )
1150 + {
1151 + // Insert row in the middle of the page
1152 + getDataTable().insertRow( beforeRow - firstRow );
1153 + }
1154 + else
1155 + {
1156 + // Insert zero row because row is before this page
1157 + getDataTable().insertRow( 0 );
1158 + }
1159 + if ( getDataTable().getRowCount() > pageSize )
1160 + {
1161 + getDataTable().removeRow( pageSize );
1162 + }
1163 + }
1164 + }
1165 +
1166 + /**
1167 + * Called when the data table has finished rendering.
1168 + */
1169 + protected void onDataTableRendered()
1170 + {
1171 + // Refresh the headers if needed
1172 + if ( headersObsolete )
1173 + {
1174 + refreshHeaderTable();
1175 + refreshFooterTable();
1176 + headersObsolete = false;
1177 + }
1178 +
1179 + // Select rows
1180 + FixedWidthGrid dataTable = getDataTable();
1181 + int rowCount = dataTable.getRowCount();
1182 + for ( int i = 0; i < rowCount; i++ )
1183 + {
1184 + if ( selectedRowValues.contains( getRowValue( i ) ) )
1185 + {
1186 + dataTable.selectRow( i, false );
1187 + }
1188 + }
1189 +
1190 + // Update the UI of the table
1191 + dataTable.clearIdealWidths();
1192 + redraw();
1193 + isPageLoading = false;
1194 + fireEvent( new PageLoadEvent( currentPage ) );
1195 + }
1196 +
1197 + /**
1198 + * Update the footer table based on the new {@link ColumnDefinition}.
1199 + */
1200 + protected void refreshFooterTable()
1201 + {
1202 + if ( !isFooterGenerated )
1203 + {
1204 + return;
1205 + }
1206 +
1207 + // Generate the list of lists of ColumnHeaderInfo.
1208 + List<List<ColumnHeaderInfo>> allInfos = new ArrayList<List<ColumnHeaderInfo>>();
1209 + int columnCount = visibleColumns.size();
1210 + int footerCounts[] = new int[columnCount];
1211 + int maxFooterCount = 0;
1212 + for ( int col = 0; col < columnCount; col++ )
1213 + {
1214 + // Get the header property.
1215 + ColumnDefinition<RowType, ?> colDef = visibleColumns.get( col );
1216 + FooterProperty prop = colDef.getColumnProperty( FooterProperty.TYPE );
1217 + int footerCount = prop.getFooterCount();
1218 + footerCounts[col] = footerCount;
1219 + maxFooterCount = Math.max( maxFooterCount, footerCount );
1220 +
1221 + // Add each ColumnHeaderInfo
1222 + List<ColumnHeaderInfo> infos = new ArrayList<ColumnHeaderInfo>();
1223 + ColumnHeaderInfo prev = null;
1224 + for ( int row = 0; row < footerCount; row++ )
1225 + {
1226 + Object footer = prop.getFooter( row, col );
1227 + if ( prev != null && prev.header.equals( footer ) )
1228 + {
1229 + prev.incrementRowSpan();
1230 + }
1231 + else
1232 + {
1233 + prev = new ColumnHeaderInfo( footer );
1234 + infos.add( prev );
1235 + }
1236 + }
1237 + allInfos.add( infos );
1238 + }
1239 +
1240 + // Return early if there is no footer
1241 + if ( maxFooterCount == 0 )
1242 + {
1243 + return;
1244 + }
1245 +
1246 + // Fill in missing rows
1247 + for ( int col = 0; col < columnCount; col++ )
1248 + {
1249 + int footerCount = footerCounts[col];
1250 + if ( footerCount < maxFooterCount )
1251 + {
1252 + allInfos.get( col ).add( new ColumnHeaderInfo( null, maxFooterCount - footerCount ) );
1253 + }
1254 + }
1255 +
1256 + // Ensure that we have a footer table
1257 + if ( getFooterTable() == null )
1258 + {
1259 + setFooterTable( new FixedWidthFlexTable() );
1260 + }
1261 +
1262 + // Refresh the table
1263 + refreshHeaderTable( getFooterTable(), allInfos, false );
1264 + }
1265 +
1266 + /**
1267 + * Update the header table based on the new {@link ColumnDefinition}.
1268 + */
1269 + protected void refreshHeaderTable()
1270 + {
1271 + if ( !isHeaderGenerated )
1272 + {
1273 + return;
1274 + }
1275 +
1276 + // Generate the list of lists of ColumnHeaderInfo.
1277 + List<List<ColumnHeaderInfo>> allInfos = new ArrayList<List<ColumnHeaderInfo>>();
1278 + int columnCount = visibleColumns.size();
1279 + int headerCounts[] = new int[columnCount];
1280 + int maxHeaderCount = 0;
1281 + for ( int col = 0; col < columnCount; col++ )
1282 + {
1283 + // Get the header property.
1284 + ColumnDefinition<RowType, ?> colDef = visibleColumns.get( col );
1285 + HeaderProperty prop = colDef.getColumnProperty( HeaderProperty.TYPE );
1286 + int headerCount = prop.getHeaderCount();
1287 + headerCounts[col] = headerCount;
1288 + maxHeaderCount = Math.max( maxHeaderCount, headerCount );
1289 +
1290 + // Add each ColumnHeaderInfo
1291 + List<ColumnHeaderInfo> infos = new ArrayList<ColumnHeaderInfo>();
1292 + ColumnHeaderInfo prev = null;
1293 + for ( int row = 0; row < headerCount; row++ )
1294 + {
1295 + Object header = prop.getHeader( row, col );
1296 + if ( prev != null && prev.header.equals( header ) )
1297 + {
1298 + prev.incrementRowSpan();
1299 + }
1300 + else
1301 + {
1302 + prev = new ColumnHeaderInfo( header );
1303 + infos.add( 0, prev );
1304 + }
1305 + }
1306 + allInfos.add( infos );
1307 + }
1308 +
1309 + // Return early if there is no header
1310 + if ( maxHeaderCount == 0 )
1311 + {
1312 + return;
1313 + }
1314 +
1315 + // Fill in missing rows
1316 + for ( int col = 0; col < columnCount; col++ )
1317 + {
1318 + int headerCount = headerCounts[col];
1319 + if ( headerCount < maxHeaderCount )
1320 + {
1321 + allInfos.get( col ).add( 0, new ColumnHeaderInfo( null, maxHeaderCount - headerCount ) );
1322 + }
1323 + }
1324 +
1325 + // Refresh the table
1326 + refreshHeaderTable( getHeaderTable(), allInfos, true );
1327 + }
1328 +
1329 + /**
1330 + * Refresh the list of the currently visible column definitions based on the
1331 + * {@link TableDefinition}.
1332 + */
1333 + protected void refreshVisibleColumnDefinitions()
1334 + {
1335 + List<ColumnDefinition<RowType, ?>> colDefs = new ArrayList<ColumnDefinition<RowType, ?>>( tableDefinition.getVisibleColumnDefinitions() );
1336 + if ( !colDefs.equals( visibleColumns ) )
1337 + {
1338 + visibleColumns = colDefs;
1339 + headersObsolete = true;
1340 + }
1341 + else
1342 + {
1343 + // Check if any of the headers are dynamic
1344 + for ( ColumnDefinition<RowType, ?> colDef : colDefs )
1345 + {
1346 + if ( colDef.getColumnProperty( HeaderProperty.TYPE ).isDynamic() || colDef.getColumnProperty( FooterProperty.TYPE ).isDynamic() )
1347 + {
1348 + headersObsolete = true;
1349 + return;
1350 + }
1351 + }
1352 + }
1353 + }
1354 +
1355 + /**
1356 + * Remove a row from the table relative to the total number of rows.
1357 + *
1358 + * @param row the row index
1359 + */
1360 + protected void removeAbsoluteRow( int row )
1361 + {
1362 + // Physically remove the row if it is in the middle of the data table
1363 + int firstRow = getAbsoluteFirstRowIndex();
1364 + int lastRow = getAbsoluteLastRowIndex();
1365 + if ( row <= lastRow && row >= firstRow )
1366 + {
1367 + FixedWidthGrid dataTable = getDataTable();
1368 + int relativeRow = row - firstRow;
1369 + if ( relativeRow < dataTable.getRowCount() )
1370 + {
1371 + dataTable.removeRow( relativeRow );
1372 + }
1373 + }
1374 + }
1375 +
1376 + /**
1377 + * Set a block of data. This method is used when responding to data requests.
1378 + * <p/>
1379 + * This method takes an iterator of iterators, where each iterator represents
1380 + * one row of data starting with the first row.
1381 + *
1382 + * @param firstRow the row index that the rows iterator starts with
1383 + * @param rows the values associated with each row
1384 + */
1385 + protected void setData( int firstRow, Iterator<RowType> rows )
1386 + {
1387 + getDataTable().deselectAllRows();
1388 + rowValues = new ArrayList<RowType>();
1389 + if ( rows != null && rows.hasNext() )
1390 + {
1391 + setEmptyTableWidgetVisible( false );
1392 +
1393 + // Get an iterator over the visible rows
1394 + int firstVisibleRow = getAbsoluteFirstRowIndex();
1395 + int lastVisibleRow = getAbsoluteLastRowIndex();
1396 + Iterator<RowType> visibleIter = new VisibleRowsIterator( rows, firstRow, firstVisibleRow, lastVisibleRow );
1397 +
1398 + // Set the row values
1399 + while ( visibleIter.hasNext() )
1400 + {
1401 + rowValues.add( visibleIter.next() );
1402 + }
1403 +
1404 + // Copy the visible column definitions
1405 + refreshVisibleColumnDefinitions();
1406 +
1407 + // Render using the bulk renderer
1408 + if ( bulkRenderer != null )
1409 + {
1410 + bulkRenderer.renderRows( rowValues.iterator(), tableRendererCallback );
1411 + return;
1412 + }
1413 +
1414 + // Get rid of unneeded rows and columns
1415 + int rowCount = rowValues.size();
1416 + int colCount = visibleColumns.size();
1417 + getDataTable().resize( rowCount, colCount );
1418 +
1419 + // Render the rows
1420 + tableDefinition.renderRows( 0, rowValues.iterator(), rowView );
1421 + }
1422 + else
1423 + {
1424 + setEmptyTableWidgetVisible( true );
1425 + }
1426 +
1427 + // Fire page loaded event
1428 + onDataTableRendered();
1429 + }
1430 +
1431 + /**
1432 + * Set whether or not the empty table widget is visible.
1433 + *
1434 + * @param visible true to show the empty table widget
1435 + */
1436 + protected void setEmptyTableWidgetVisible( boolean visible )
1437 + {
1438 + emptyTableWidgetWrapper.setVisible( visible );
1439 + if ( visible )
1440 + {
1441 + getDataWrapper().getStyle().setProperty( "display", "none" );
1442 + }
1443 + else
1444 + {
1445 + getDataWrapper().getStyle().setProperty( "display", "" );
1446 + }
1447 + }
1448 +
1449 + /**
1450 + * Update the header or footer tables based on the new
1451 + * {@link ColumnDefinition}.
1452 + *
1453 + * @param table the header or footer table
1454 + * @param allInfos the header info
1455 + * @param isHeader false if refreshing the footer table
1456 + */
1457 + private void refreshHeaderTable( FixedWidthFlexTable table, List<List<ColumnHeaderInfo>> allInfos, boolean isHeader )
1458 + {
1459 + // Return if we have no column definitions.
1460 + if ( visibleColumns == null )
1461 + {
1462 + return;
1463 + }
1464 +
1465 + // Reset the header table.
1466 + int rowCount = table.getRowCount();
1467 + for ( int i = 0; i < rowCount; i++ )
1468 + {
1469 + table.removeRow( 0 );
1470 + }
1471 +
1472 + // Generate the header table
1473 + int columnCount = allInfos.size();
1474 + FlexCellFormatter formatter = table.getFlexCellFormatter();
1475 + List<ColumnHeaderInfo> prevInfos = null;
1476 + for ( int col = 0; col < columnCount; col++ )
1477 + {
1478 + List<ColumnHeaderInfo> infos = allInfos.get( col );
1479 + int row = 0;
1480 + for ( ColumnHeaderInfo info : infos )
1481 + {
1482 + // Get the actual row and cell index
1483 + int rowSpan = info.getRowSpan();
1484 + int cell = 0;
1485 + if ( table.getRowCount() > row )
1486 + {
1487 + cell = table.getCellCount( row );
1488 + }
1489 +
1490 + // Compare to the cell in the previous column
1491 + if ( prevInfos != null )
1492 + {
1493 + boolean headerAdded = false;
1494 + int prevRow = 0;
1495 + for ( ColumnHeaderInfo prevInfo : prevInfos )
1496 + {
1497 + // Increase the colSpan of the previous cell
1498 + if ( prevRow == row && info.equals( prevInfo ) )
1499 + {
1500 + int colSpan = formatter.getColSpan( row, cell - 1 );
1501 + formatter.setColSpan( row, cell - 1, colSpan + 1 );
1502 + headerAdded = true;
1503 + break;
1504 + }
1505 + prevRow += prevInfo.getRowSpan();
1506 + }
1507 +
1508 + if ( headerAdded )
1509 + {
1510 + row += rowSpan;
1511 + continue;
1512 + }
1513 + }
1514 +
1515 + // Set the new header
1516 + Object header = info.getHeader();
1517 + if ( header instanceof Widget )
1518 + {
1519 + table.setWidget( row, cell, (Widget) header );
1520 + }
1521 + else
1522 + {
1523 + table.setHTML( row, cell, header.toString() );
1524 + }
1525 +
1526 + // Update the rowSpan
1527 + if ( rowSpan > 1 )
1528 + {
1529 + formatter.setRowSpan( row, cell, rowSpan );
1530 + }
1531 +
1532 + // Increment the row
1533 + row += rowSpan;
1534 + }
1535 +
1536 + // Increment the previous info
1537 + prevInfos = infos;
1538 + }
1539 +
1540 + // Insert the checkbox column
1541 + SelectionPolicy selectionPolicy = getDataTable().getSelectionPolicy();
1542 + if ( selectionPolicy.hasInputColumn() )
1543 + {
1544 + // Get the select all box
1545 + Widget box = null;
1546 + if ( isHeader && getDataTable().getSelectionPolicy() == SelectionPolicy.CHECKBOX )
1547 + {
1548 + box = getSelectAllWidget();
1549 + }
1550 +
1551 + // Add the offset column
1552 + table.insertCell( 0, 0 );
1553 + if ( box != null )
1554 + {
1555 + table.setWidget( 0, 0, box );
1556 + }
1557 + else
1558 + {
1559 + table.setHTML( 0, 0, "&nbsp;" );
1560 + }
1561 + formatter.setRowSpan( 0, 0, table.getRowCount() );
1562 + formatter.setHorizontalAlignment( 0, 0, HasHorizontalAlignment.ALIGN_CENTER );
1563 + table.setColumnWidth( 0, getDataTable().getInputColumnWidth() );
1564 + }
1565 + }
1566 +
1567 + /**
1568 + * Refresh a single row in the table.
1569 + *
1570 + * @param rowIndex the index of the row
1571 + */
1572 + private void refreshRow( int rowIndex )
1573 + {
1574 + final RowType rowValue = getRowValue( rowIndex );
1575 + Iterator<RowType> singleIterator = new Iterator<RowType>()
1576 + {
1577 + private boolean nextCalled = false;
1578 +
1579 + public boolean hasNext()
1580 + {
1581 + return !nextCalled;
1582 + }
1583 +
1584 + public RowType next()
1585 + {
1586 + if ( !hasNext() )
1587 + {
1588 + throw new NoSuchElementException();
1589 + }
1590 + nextCalled = true;
1591 + return rowValue;
1592 + }
1593 +
1594 + public void remove()
1595 + {
1596 + throw new UnsupportedOperationException();
1597 + }
1598 + };
1599 + tableDefinition.renderRows( rowIndex, singleIterator, rowView );
1600 + }
1440 1601 }