Subversion Repository Public Repository

litesoft

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

Diff revisions: vs.
  @@ -15,41 +15,28 @@
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.gen2.event.dom.client.HasScrollHandlers;
20 - import com.google.gwt.gen2.event.dom.client.ScrollEvent;
21 - import com.google.gwt.gen2.event.dom.client.ScrollHandler;
22 - import com.google.gwt.gen2.event.shared.HandlerRegistration;
23 - import com.google.gwt.gen2.table.client.ColumnResizer.ColumnWidthInfo;
24 - import com.google.gwt.gen2.table.client.TableModelHelper.ColumnSortList;
25 - import com.google.gwt.gen2.table.client.property.MaximumWidthProperty;
26 - import com.google.gwt.gen2.table.event.client.ColumnSortEvent;
27 - import com.google.gwt.gen2.table.event.client.ColumnSortHandler;
28 - import com.google.gwt.gen2.table.client.HTMLTable.CellFormatter;
29 - import com.google.gwt.i18n.client.LocaleInfo;
30 - import com.google.gwt.user.client.Command;
31 - import com.google.gwt.user.client.DOM;
32 - import com.google.gwt.user.client.Element;
33 - import com.google.gwt.user.client.Event;
34 - import com.google.gwt.user.client.Timer;
35 - import com.google.gwt.user.client.Window;
36 - import com.google.gwt.user.client.ui.AbstractImagePrototype;
37 - import com.google.gwt.user.client.ui.Image;
38 - import com.google.gwt.user.client.ui.ImageBundle;
39 - import com.google.gwt.user.client.ui.RootPanel;
40 - import com.google.gwt.user.client.ui.Widget;
41 - import com.google.gwt.widgetideas.client.ResizableWidget;
42 - import com.google.gwt.widgetideas.client.ResizableWidgetCollection;
18 + import java.util.*;
43 19
44 - import java.util.ArrayList;
45 - import java.util.List;
20 + import com.google.gwt.core.client.*;
21 + import com.google.gwt.gen2.event.dom.client.*;
22 + import com.google.gwt.gen2.event.shared.*;
23 + import com.google.gwt.gen2.table.client.ColumnResizer.*;
24 + import com.google.gwt.gen2.table.client.HTMLTable.*;
25 + import com.google.gwt.gen2.table.client.TableModelHelper.*;
26 + import com.google.gwt.gen2.table.client.property.*;
27 + import com.google.gwt.gen2.table.event.client.*;
28 + import com.google.gwt.i18n.client.*;
29 + import com.google.gwt.user.client.*;
30 + import com.google.gwt.user.client.Timer;
31 + import com.google.gwt.user.client.ui.*;
32 + import com.google.gwt.widgetideas.client.*;
46 33
47 34 /**
48 35 * <p>
49 36 * A ScrollTable consists of a fixed header and footer (optional) that remain
50 37 * visible and a scrollable body that contains the data.
51 38 * </p>
52 - *
39 + * <p/>
53 40 * <p>
54 41 * In order for the columns in the header table and data table to line up, the
55 42 * two table must have the same margin, padding, and border widths. You can use
  @@ -57,7 +44,7 @@
57 44 * must keep the actual sizes consistent (especially with respect to the left
58 45 * and right side of the cells).
59 46 * </p>
60 - *
47 + * <p/>
61 48 * <p>
62 49 * NOTE: AbstractScrollTable does not resize correctly in older versions of
63 50 * Mozilla (specifically, Linux hosted mode). In use, the PagingScrollTable will
  @@ -66,9 +53,9 @@
66 53 * in percentages) on all modern browsers including IE6+, FF2+, Safari2+,
67 54 * Chrome, Opera 9.6.
68 55 * </p>
69 - *
56 + * <p/>
70 57 * <h3>CSS Style Rules</h3>
71 - *
58 + * <p/>
72 59 * <dl>
73 60 * <dt>.gwt-ScrollTable</dt>
74 61 * <dd>applied to the entire widget</dd>
  @@ -86,2039 +73,2290 @@
86 73 * <dd>wrapper around the footer table</dd>
87 74 * </dl>
88 75 */
89 - public abstract class AbstractScrollTable extends Gen2TableComplexPanel implements
90 - ResizableWidget, HasScrollHandlers {
91 - /**
92 - * Browser specific implementation class for {@link AbstractScrollTable}.
93 - */
94 - private static class Impl {
95 - /**
96 - * Create a spacer element that allows the header table to scroll over the
97 - * vertical scroll bar of the data table.
98 - *
99 - * @param wrapper the wrapper that contains the header table
100 - * @return the spacer element
101 - */
102 - public Element createSpacer(FixedWidthFlexTable table, Element wrapper) {
103 - resizeSpacer(table, null, 15);
104 - return null;
76 + public abstract class AbstractScrollTable extends Gen2TableComplexPanel implements ResizableWidget,
77 + HasScrollHandlers
78 + {
79 + /**
80 + * Browser specific implementation class for {@link AbstractScrollTable}.
81 + */
82 + private static class Impl
83 + {
84 + /**
85 + * Create a spacer element that allows the header table to scroll over the
86 + * vertical scroll bar of the data table.
87 + *
88 + * @param wrapper the wrapper that contains the header table
89 + *
90 + * @return the spacer element
91 + */
92 + public Element createSpacer( FixedWidthFlexTable table, Element wrapper )
93 + {
94 + resizeSpacer( table, null, 15 );
95 + return null;
96 + }
97 +
98 + /**
99 + * Returns the width of a table, minus any padding, in pixels.
100 + *
101 + * @param table the table
102 + * @param includeSpacer true to include the spacer width
103 + *
104 + * @return the width
105 + */
106 + public int getTableWidth( FixedWidthFlexTable table, boolean includeSpacer )
107 + {
108 + int scrollWidth = table.getElement().getScrollWidth();
109 + if ( !includeSpacer )
110 + {
111 + int spacerWidth = getSpacerWidth( table );
112 + if ( spacerWidth > 0 )
113 + {
114 + scrollWidth -= spacerWidth;
115 + }
116 + }
117 + return scrollWidth;
118 + }
119 +
120 + /**
121 + * Recalculate the ideal widths of columns.
122 + *
123 + * @param scrollTable the scroll table
124 + * @param command an optional command to execute while recalculating
125 + */
126 + public void recalculateIdealColumnWidths( AbstractScrollTable scrollTable, Command command )
127 + {
128 + FixedWidthFlexTable headerTable = scrollTable.getHeaderTable();
129 + FixedWidthFlexTable footerTable = scrollTable.getFooterTable();
130 + FixedWidthGrid dataTable = scrollTable.getDataTable();
131 +
132 + // Setup all inner tables
133 + dataTable.recalculateIdealColumnWidthsSetup();
134 + headerTable.recalculateIdealColumnWidthsSetup();
135 + if ( footerTable != null )
136 + {
137 + footerTable.recalculateIdealColumnWidthsSetup();
138 + }
139 +
140 + // Perform operations
141 + dataTable.recalculateIdealColumnWidthsImpl();
142 + headerTable.recalculateIdealColumnWidthsImpl();
143 + if ( footerTable != null )
144 + {
145 + footerTable.recalculateIdealColumnWidthsImpl();
146 + }
147 +
148 + // Execute the optional command
149 + if ( command != null )
150 + {
151 + command.execute();
152 + }
153 +
154 + // Teardown all inner tables
155 + dataTable.recalculateIdealColumnWidthsTeardown();
156 + headerTable.recalculateIdealColumnWidthsTeardown();
157 + if ( footerTable != null )
158 + {
159 + footerTable.recalculateIdealColumnWidthsTeardown();
160 + }
161 + }
162 +
163 + /**
164 + * Reposition the header spacer as needed.
165 + *
166 + * @param scrollTable the scroll table
167 + * @param force if true, ignore the scroll policy
168 + */
169 + public void repositionSpacer( AbstractScrollTable scrollTable, boolean force )
170 + {
171 + // Only ScrollPolicy.BOTH has a vertical scroll bar
172 + if ( !force && scrollTable.scrollPolicy != ScrollPolicy.BOTH )
173 + {
174 + return;
175 + }
176 +
177 + Element dataWrapper = scrollTable.dataWrapper;
178 + int spacerWidth = dataWrapper.getOffsetWidth() - dataWrapper.getPropertyInt( "clientWidth" );
179 + resizeSpacer( scrollTable.headerTable, scrollTable.headerSpacer, spacerWidth );
180 + if ( scrollTable.footerTable != null )
181 + {
182 + resizeSpacer( scrollTable.footerTable, scrollTable.footerSpacer, spacerWidth );
183 + }
184 + }
185 +
186 + /**
187 + * @return true if the scroll bar is on the right
188 + */
189 + boolean isScrollBarOnRight()
190 + {
191 + return true;
192 + }
193 +
194 + void resizeSpacer( FixedWidthFlexTable table, Element spacer, int spacerWidth )
195 + {
196 + // Exit early if the spacer is already the correct size
197 + if ( spacerWidth == getSpacerWidth( table ) )
198 + {
199 + return;
200 + }
201 +
202 + if ( isScrollBarOnRight() )
203 + {
204 + table.getElement().getStyle().setPropertyPx( "paddingRight", spacerWidth );
205 + }
206 + else
207 + {
208 + table.getElement().getStyle().setPropertyPx( "paddingLeft", spacerWidth );
209 + }
210 + }
211 +
212 + /**
213 + * Get the current width of the spacer element.
214 + *
215 + * @param table the table to check
216 + *
217 + * @return the current width
218 + */
219 + private int getSpacerWidth( FixedWidthFlexTable table )
220 + {
221 + // Get the padding string
222 + String paddingStr;
223 + if ( isScrollBarOnRight() )
224 + {
225 + paddingStr = table.getElement().getStyle().getProperty( "paddingRight" );
226 + }
227 + else
228 + {
229 + paddingStr = table.getElement().getStyle().getProperty( "paddingLeft" );
230 + }
231 +
232 + // Check the padding string
233 + if ( paddingStr == null || paddingStr.length() < 3 )
234 + {
235 + return -1;
236 + }
237 +
238 + // Parse the int from the padding
239 + try
240 + {
241 + return Integer.parseInt( paddingStr.substring( 0, paddingStr.length() - 2 ) );
242 + }
243 + catch ( NumberFormatException e )
244 + {
245 + return -1;
246 + }
247 + }
105 248 }
106 249
107 250 /**
108 - * Returns the width of a table, minus any padding, in pixels.
109 - *
110 - * @param table the table
111 - * @param includeSpacer true to include the spacer width
112 - * @return the width
113 - */
114 - public int getTableWidth(FixedWidthFlexTable table, boolean includeSpacer) {
115 - int scrollWidth = table.getElement().getScrollWidth();
116 - if (!includeSpacer) {
117 - int spacerWidth = getSpacerWidth(table);
118 - if (spacerWidth > 0) {
119 - scrollWidth -= spacerWidth;
120 - }
121 - }
122 - return scrollWidth;
251 + * Opera and Old Mozilla put the scroll bar on the left side in RTL mode.
252 + */
253 + private static class ImplLeftScrollBar extends Impl
254 + {
255 + @Override boolean isScrollBarOnRight()
256 + {
257 + return !LocaleInfo.getCurrentLocale().isRTL();
258 + }
123 259 }
124 260
125 261 /**
126 - * Recalculate the ideal widths of columns.
127 - *
128 - * @param scrollTable the scroll table
129 - * @param command an optional command to execute while recalculating
262 + * IE puts the scroll bar on the left side in RTL mode. The padding trick
263 + * doesn't work, so we use a separate element.
130 264 */
131 - public void recalculateIdealColumnWidths(AbstractScrollTable scrollTable,
132 - Command command) {
133 - FixedWidthFlexTable headerTable = scrollTable.getHeaderTable();
134 - FixedWidthFlexTable footerTable = scrollTable.getFooterTable();
135 - FixedWidthGrid dataTable = scrollTable.getDataTable();
136 -
137 - // Setup all inner tables
138 - dataTable.recalculateIdealColumnWidthsSetup();
139 - headerTable.recalculateIdealColumnWidthsSetup();
140 - if (footerTable != null) {
141 - footerTable.recalculateIdealColumnWidthsSetup();
142 - }
143 -
144 - // Perform operations
145 - dataTable.recalculateIdealColumnWidthsImpl();
146 - headerTable.recalculateIdealColumnWidthsImpl();
147 - if (footerTable != null) {
148 - footerTable.recalculateIdealColumnWidthsImpl();
149 - }
150 -
151 - // Execute the optional command
152 - if (command != null) {
153 - command.execute();
154 - }
155 -
156 - // Teardown all inner tables
157 - dataTable.recalculateIdealColumnWidthsTeardown();
158 - headerTable.recalculateIdealColumnWidthsTeardown();
159 - if (footerTable != null) {
160 - footerTable.recalculateIdealColumnWidthsTeardown();
161 - }
162 - }
163 -
164 - /**
165 - * Reposition the header spacer as needed.
166 - *
167 - * @param scrollTable the scroll table
168 - * @param force if true, ignore the scroll policy
169 - */
170 - public void repositionSpacer(AbstractScrollTable scrollTable, boolean force) {
171 - // Only ScrollPolicy.BOTH has a vertical scroll bar
172 - if (!force && scrollTable.scrollPolicy != ScrollPolicy.BOTH) {
173 - return;
174 - }
175 -
176 - Element dataWrapper = scrollTable.dataWrapper;
177 - int spacerWidth = dataWrapper.getOffsetWidth()
178 - - dataWrapper.getPropertyInt("clientWidth");
179 - resizeSpacer(scrollTable.headerTable, scrollTable.headerSpacer,
180 - spacerWidth);
181 - if (scrollTable.footerTable != null) {
182 - resizeSpacer(scrollTable.footerTable, scrollTable.footerSpacer,
183 - spacerWidth);
184 - }
185 - }
186 -
187 - /**
188 - * @return true if the scroll bar is on the right
189 - */
190 - boolean isScrollBarOnRight() {
191 - return true;
192 - }
193 -
194 - void resizeSpacer(FixedWidthFlexTable table, Element spacer, int spacerWidth) {
195 - // Exit early if the spacer is already the correct size
196 - if (spacerWidth == getSpacerWidth(table)) {
197 - return;
198 - }
199 -
200 - if (isScrollBarOnRight()) {
201 - table.getElement().getStyle().setPropertyPx("paddingRight", spacerWidth);
202 - } else {
203 - table.getElement().getStyle().setPropertyPx("paddingLeft", spacerWidth);
204 - }
205 - }
206 -
207 - /**
208 - * Get the current width of the spacer element.
209 - *
210 - * @param table the table to check
211 - * @return the current width
212 - */
213 - private int getSpacerWidth(FixedWidthFlexTable table) {
214 - // Get the padding string
215 - String paddingStr;
216 - if (isScrollBarOnRight()) {
217 - paddingStr = table.getElement().getStyle().getProperty("paddingRight");
218 - } else {
219 - paddingStr = table.getElement().getStyle().getProperty("paddingLeft");
220 - }
221 -
222 - // Check the padding string
223 - if (paddingStr == null || paddingStr.length() < 3) {
224 - return -1;
225 - }
226 -
227 - // Parse the int from the padding
228 - try {
229 - return Integer.parseInt(paddingStr.substring(0, paddingStr.length() - 2));
230 - } catch (NumberFormatException e) {
231 - return -1;
232 - }
233 - }
234 - }
235 -
236 - /**
237 - * Opera and Old Mozilla put the scroll bar on the left side in RTL mode.
238 - */
239 - private static class ImplLeftScrollBar extends Impl {
240 - @Override
241 - boolean isScrollBarOnRight() {
242 - return !LocaleInfo.getCurrentLocale().isRTL();
265 + @SuppressWarnings("unused")
266 + private static class ImplIE6 extends ImplLeftScrollBar
267 + {
268 + /**
269 + * Adding padding to a table in IE will mess up the layout, so we use an
270 + * absolutely positioned div to add padding. In RTL mode, the div needs to
271 + * be exactly the right width and position or scrollLeft will be affected.
272 + * In LTR mode, we can position it anywhere and set the width to a high
273 + * number, improving performance.
274 + */
275 + @Override
276 + public Element createSpacer( FixedWidthFlexTable table, Element wrapper )
277 + {
278 + Element spacer = DOM.createDiv();
279 + spacer.getStyle().setPropertyPx( "height", 1 );
280 + spacer.getStyle().setPropertyPx( "top", 1 );
281 + spacer.getStyle().setProperty( "position", "absolute" );
282 + if ( !LocaleInfo.getCurrentLocale().isRTL() )
283 + {
284 + spacer.getStyle().setPropertyPx( "left", 1 );
285 + spacer.getStyle().setPropertyPx( "width", 10000 );
286 + }
287 + wrapper.appendChild( spacer );
288 + return spacer;
289 + }
290 +
291 + @Override
292 + public int getTableWidth( FixedWidthFlexTable table, boolean includeSpacer )
293 + {
294 + return table.getElement().getScrollWidth();
295 + }
296 +
297 + /**
298 + * IE allows the table to resize as widely as needed unless we restrict the
299 + * width of a parent element.
300 + */
301 + @Override
302 + public void recalculateIdealColumnWidths( AbstractScrollTable scrollTable, Command command )
303 + {
304 + scrollTable.getAbsoluteElement().getStyle().setPropertyPx( "width", 1 );
305 + super.recalculateIdealColumnWidths( scrollTable, command );
306 + scrollTable.getAbsoluteElement().getStyle().setProperty( "width", "100%" );
307 + }
308 +
309 + @Override void resizeSpacer( FixedWidthFlexTable table, Element spacer, int width )
310 + {
311 + if ( LocaleInfo.getCurrentLocale().isRTL() )
312 + {
313 + int headerWidth = table.getOffsetWidth();
314 + spacer.getStyle().setPropertyPx( "width", width );
315 + spacer.getStyle().setPropertyPx( "right", headerWidth );
316 + }
317 + }
243 318 }
244 - }
245 319
246 - /**
247 - * IE puts the scroll bar on the left side in RTL mode. The padding trick
248 - * doesn't work, so we use a separate element.
249 - */
250 - @SuppressWarnings("unused")
251 - private static class ImplIE6 extends ImplLeftScrollBar {
252 - /**
253 - * Adding padding to a table in IE will mess up the layout, so we use an
254 - * absolutely positioned div to add padding. In RTL mode, the div needs to
255 - * be exactly the right width and position or scrollLeft will be affected.
256 - * In LTR mode, we can position it anywhere and set the width to a high
257 - * number, improving performance.
320 + /**
321 + * A helper class that handles some of the mouse events associated with
322 + * resizing columns.
258 323 */
259 - @Override
260 - public Element createSpacer(FixedWidthFlexTable table, Element wrapper) {
261 - Element spacer = DOM.createDiv();
262 - spacer.getStyle().setPropertyPx("height", 1);
263 - spacer.getStyle().setPropertyPx("top", 1);
264 - spacer.getStyle().setProperty("position", "absolute");
265 - if (!LocaleInfo.getCurrentLocale().isRTL()) {
266 - spacer.getStyle().setPropertyPx("left", 1);
267 - spacer.getStyle().setPropertyPx("width", 10000);
268 - }
269 - wrapper.appendChild(spacer);
270 - return spacer;
324 + private static class MouseResizeWorker
325 + {
326 + /**
327 + * The width of the area that is available for resize.
328 + */
329 + private static final int RESIZE_CURSOR_WIDTH = 15;
330 +
331 + /**
332 + * The current header cell that the mouse is affecting.
333 + */
334 + private Element curCell = null;
335 +
336 + /**
337 + * The columns under the colSpan of the current cell.
338 + */
339 + private List<ColumnWidthInfo> curCells = new ArrayList<ColumnWidthInfo>();
340 +
341 + /**
342 + * The index of the current header cell.
343 + */
344 + private int curCellIndex = 0;
345 +
346 + /**
347 + * The current x position of the mouse.
348 + */
349 + private int mouseXCurrent = 0;
350 +
351 + /**
352 + * The last x position of the mouse when we resized.
353 + */
354 + private int mouseXLast = 0;
355 +
356 + /**
357 + * The starting x position of the mouse when resizing a column.
358 + */
359 + private int mouseXStart = 0;
360 +
361 + /**
362 + * A timer used to resize the columns. As long as the timer is active, it
363 + * will poll for the new row size and resize the columns.
364 + */
365 + private Timer resizeTimer = new Timer()
366 + {
367 + @Override
368 + public void run()
369 + {
370 + resizeColumn();
371 + schedule( 100 );
372 + }
373 + };
374 +
375 + /**
376 + * A boolean indicating that we are resizing the current header cell.
377 + */
378 + private boolean resizing = false;
379 +
380 + /**
381 + * The index of the first column that will be sacrificed.
382 + */
383 + private int sacrificeCellIndex = -1;
384 +
385 + /**
386 + * The cells that will be sacrificed so the current cells can be resized.
387 + */
388 + private List<ColumnWidthInfo> sacrificeCells = new ArrayList<ColumnWidthInfo>();
389 +
390 + /**
391 + * The table that this worker affects.
392 + */
393 + private AbstractScrollTable table = null;
394 +
395 + /**
396 + * @return the current cell
397 + */
398 + public Element getCurrentCell()
399 + {
400 + return curCell;
401 + }
402 +
403 + /**
404 + * @return true if a header is currently being resized
405 + */
406 + public boolean isResizing()
407 + {
408 + return resizing;
409 + }
410 +
411 + /**
412 + * Resize the column on a mouse event. This method also marks the client as
413 + * busy so we do not try to change the size repeatedly.
414 + *
415 + * @param event the mouse event
416 + */
417 + public void resizeColumn( Event event )
418 + {
419 + mouseXCurrent = DOM.eventGetClientX( event );
420 + }
421 +
422 + /**
423 + * Set the current cell that will be resized based on the mouse event.
424 + *
425 + * @param event the event that triggered the new cell
426 + *
427 + * @return true if the cell was actually changed
428 + */
429 + public boolean setCurrentCell( Event event )
430 + {
431 + // Check the resize policy of the table
432 + Element cell = null;
433 + if ( table.columnResizePolicy == ColumnResizePolicy.MULTI_CELL )
434 + {
435 + cell = table.headerTable.getEventTargetCell( event );
436 + }
437 + else if ( table.columnResizePolicy == ColumnResizePolicy.SINGLE_CELL )
438 + {
439 + cell = table.headerTable.getEventTargetCell( event );
440 + if ( cell != null && cell.getPropertyInt( "colSpan" ) > 1 )
441 + {
442 + cell = null;
443 + }
444 + }
445 +
446 + // See if we are near the edge of the cell
447 + int clientX = event.getClientX();
448 + if ( cell != null )
449 + {
450 + int absLeft = cell.getAbsoluteLeft() - Window.getScrollLeft();
451 + if ( LocaleInfo.getCurrentLocale().isRTL() )
452 + {
453 + if ( clientX < absLeft || clientX > absLeft + RESIZE_CURSOR_WIDTH )
454 + {
455 + cell = null;
456 + }
457 + }
458 + else
459 + {
460 + int absRight = absLeft + cell.getOffsetWidth();
461 + if ( clientX < absRight - RESIZE_CURSOR_WIDTH || clientX > absRight )
462 + {
463 + cell = null;
464 + }
465 + }
466 + }
467 +
468 + // Change out the current cell
469 + if ( cell != curCell )
470 + {
471 + // Clear the old cell
472 + if ( curCell != null )
473 + {
474 + curCell.getStyle().setProperty( "cursor", "" );
475 + }
476 +
477 + // Set the new cell
478 + curCell = cell;
479 + if ( curCell != null )
480 + {
481 + // Check the cell index
482 + curCellIndex = getCellIndex( curCell );
483 + if ( curCellIndex < 0 )
484 + {
485 + curCell = null;
486 + return false;
487 + }
488 +
489 + // Check for resizable columns within one of the cells in the colspan
490 + boolean resizable = false;
491 + int colSpan = cell.getPropertyInt( "colSpan" );
492 + curCells = table.getColumnWidthInfo( curCellIndex, colSpan );
493 + for ( ColumnWidthInfo info : curCells )
494 + {
495 + if ( !info.hasMaximumWidth() || !info.hasMinimumWidth() || info.getMaximumWidth() != info.getMinimumWidth() )
496 + {
497 + resizable = true;
498 + }
499 + }
500 + if ( !resizable )
501 + {
502 + curCell = null;
503 + curCells = null;
504 + return false;
505 + }
506 +
507 + // Update the cursor on the cell
508 + curCell.getStyle().setProperty( "cursor", "e-resize" );
509 + }
510 + return true;
511 + }
512 +
513 + // The cell did not change
514 + return false;
515 + }
516 +
517 + /**
518 + * Set the ScrollTable table that this worker affects.
519 + *
520 + * @param table the scroll table
521 + */
522 + public void setScrollTable( AbstractScrollTable table )
523 + {
524 + this.table = table;
525 + }
526 +
527 + /**
528 + * Start resizing the current cell when the user clicks on the right edge of
529 + * the cell.
530 + *
531 + * @param event the mouse event
532 + */
533 + public void startResizing( Event event )
534 + {
535 + if ( curCell != null )
536 + {
537 + resizing = true;
538 + mouseXStart = event.getClientX();
539 + mouseXLast = mouseXStart;
540 + mouseXCurrent = mouseXStart;
541 +
542 + // Add the sacrifice cells
543 + int numColumns = table.getDataTable().getColumnCount();
544 + int colSpan = curCell.getPropertyInt( "colSpan" );
545 + sacrificeCellIndex = curCellIndex + colSpan;
546 + sacrificeCells = table.getColumnWidthInfo( sacrificeCellIndex, numColumns - sacrificeCellIndex );
547 +
548 + // Start the timer and listen for changes
549 + DOM.setCapture( table.headerWrapper );
550 + resizeTimer.schedule( 20 );
551 + }
552 + }
553 +
554 + /**
555 + * Stop resizing the current cell.
556 + *
557 + * @param event the mouse event
558 + */
559 + public void stopResizing( Event event )
560 + {
561 + if ( curCell != null && resizing )
562 + {
563 + curCell.getStyle().setProperty( "cursor", "" );
564 + curCell = null;
565 + resizing = false;
566 + DOM.releaseCapture( table.headerWrapper );
567 + resizeTimer.cancel();
568 + resizeColumn();
569 + curCells = null;
570 + sacrificeCells = null;
571 + table.resizeTablesVertically();
572 + }
573 + }
574 +
575 + /**
576 + * Get the scroll table.
577 + *
578 + * @return the scroll table
579 + */
580 + protected AbstractScrollTable getScrollTable()
581 + {
582 + return table;
583 + }
584 +
585 + /**
586 + * Get the actual cell index of a cell in the header table.
587 + *
588 + * @param cell the cell element
589 + *
590 + * @return the cell index
591 + */
592 + private int getCellIndex( Element cell )
593 + {
594 + int row = OverrideDOM.getRowIndex( DOM.getParent( cell ) ) - 1;
595 + int column = OverrideDOM.getCellIndex( cell );
596 + return table.headerTable.getColumnIndex( row, column ) - table.getHeaderOffset();
597 + }
598 +
599 + /**
600 + * Helper method that actually sets the column size. This method is called
601 + * periodically by a timer.
602 + */
603 + private void resizeColumn()
604 + {
605 + if ( mouseXLast != mouseXCurrent )
606 + {
607 + mouseXLast = mouseXCurrent;
608 +
609 + // Distribute to the cells being resized
610 + int totalDelta = mouseXCurrent - mouseXStart;
611 + if ( LocaleInfo.getCurrentLocale().isRTL() )
612 + {
613 + totalDelta *= -1;
614 + }
615 + totalDelta -= table.columnResizer.distributeWidth( curCells, totalDelta );
616 +
617 + // Distribute to the sacrifice cells
618 + if ( table.resizePolicy.isSacrificial() )
619 + {
620 + int remaining = table.columnResizer.distributeWidth( sacrificeCells, -totalDelta );
621 +
622 + // We don't have enough to sacrifice, redistribute the width
623 + if ( remaining != 0 && table.resizePolicy.isFixedWidth() )
624 + {
625 + totalDelta += remaining;
626 + table.columnResizer.distributeWidth( curCells, totalDelta );
627 + }
628 +
629 + // Apply the widths to the sacrifice column
630 + table.applyNewColumnWidths( sacrificeCellIndex, sacrificeCells, true );
631 + }
632 +
633 + // Set the new widths
634 + table.applyNewColumnWidths( curCellIndex, curCells, true );
635 +
636 + // Scroll to table back into alignment
637 + table.scrollTables( false );
638 + }
639 + }
271 640 }
272 641
273 - @Override
274 - public int getTableWidth(FixedWidthFlexTable table, boolean includeSpacer) {
275 - return table.getElement().getScrollWidth();
642 + /**
643 + * The Opera version of the mouse worker fixes an Opera bug where the cursor
644 + * isn't updated if the mouse is hovering over an element DOM object when its
645 + * cursor style is changed.
646 + */
647 + @SuppressWarnings("unused")
648 + private static class MouseResizeWorkerOpera extends MouseResizeWorker
649 + {
650 + /**
651 + * A div used to force the cursor to update.
652 + */
653 + private Element cursorUpdateDiv;
654 +
655 + /**
656 + * Constructor.
657 + */
658 + public MouseResizeWorkerOpera()
659 + {
660 + cursorUpdateDiv = DOM.createDiv();
661 + DOM.setStyleAttribute( cursorUpdateDiv, "position", "absolute" );
662 + }
663 +
664 + /**
665 + * Set the current cell that will be resized based on the mouse event.
666 + *
667 + * @param event the event that triggered the new cell
668 + *
669 + * @return true if the cell was actually changed
670 + */
671 + @Override
672 + public boolean setCurrentCell( Event event )
673 + {
674 + // Check if cursor update div is active
675 + if ( DOM.eventGetTarget( event ) == cursorUpdateDiv )
676 + {
677 + removeCursorUpdateDiv();
678 + return false;
679 + }
680 +
681 + // Use the parent method
682 + boolean cellChanged = super.setCurrentCell( event );
683 +
684 + // Position a div that forces a cursor redraw in Opera
685 + if ( cellChanged )
686 + {
687 + DOM.setCapture( getScrollTable().headerWrapper );
688 + DOM.setStyleAttribute( cursorUpdateDiv, "height", (Window.getClientHeight() - 1) + "px" );
689 + DOM.setStyleAttribute( cursorUpdateDiv, "width", (Window.getClientWidth() - 1) + "px" );
690 + DOM.setStyleAttribute( cursorUpdateDiv, "left", "0px" );
691 + DOM.setStyleAttribute( cursorUpdateDiv, "top", "0px" );
692 + DOM.appendChild( RootPanel.getBodyElement(), cursorUpdateDiv );
693 + }
694 + return cellChanged;
695 + }
696 +
697 + /**
698 + * Start resizing the current cell.
699 + *
700 + * @param event the mouse event
701 + */
702 + @Override
703 + public void startResizing( Event event )
704 + {
705 + removeCursorUpdateDiv();
706 + super.startResizing( event );
707 + }
708 +
709 + /**
710 + * Remove the cursor update div from the page.
711 + */
712 + private void removeCursorUpdateDiv()
713 + {
714 + if ( DOM.getCaptureElement() != null )
715 + {
716 + DOM.removeChild( RootPanel.getBodyElement(), cursorUpdateDiv );
717 + DOM.releaseCapture( getScrollTable().headerWrapper );
718 + }
719 + }
276 720 }
277 721
278 722 /**
279 - * IE allows the table to resize as widely as needed unless we restrict the
280 - * width of a parent element.
723 + * Information about the height of the inner tables.
281 724 */
282 - @Override
283 - public void recalculateIdealColumnWidths(AbstractScrollTable scrollTable,
284 - Command command) {
285 - scrollTable.getAbsoluteElement().getStyle().setPropertyPx("width", 1);
286 - super.recalculateIdealColumnWidths(scrollTable, command);
287 - scrollTable.getAbsoluteElement().getStyle().setProperty("width", "100%");
725 + private class TableHeightInfo
726 + {
727 + private int headerTableHeight;
728 + private int dataTableHeight;
729 + private int footerTableHeight;
730 +
731 + /**
732 + * Construct a new {@link TableHeightInfo}.
733 + */
734 + public TableHeightInfo()
735 + {
736 + int totalHeight = DOM.getElementPropertyInt( getElement(), "clientHeight" );
737 + headerTableHeight = headerTable.getOffsetHeight();
738 + if ( footerTable != null )
739 + {
740 + footerTableHeight = footerTable.getOffsetHeight();
741 + }
742 + dataTableHeight = totalHeight - headerTableHeight - footerTableHeight;
743 + }
288 744 }
289 745
290 - @Override
291 - void resizeSpacer(FixedWidthFlexTable table, Element spacer, int width) {
292 - if (LocaleInfo.getCurrentLocale().isRTL()) {
293 - int headerWidth = table.getOffsetWidth();
294 - spacer.getStyle().setPropertyPx("width", width);
295 - spacer.getStyle().setPropertyPx("right", headerWidth);
296 - }
746 + /**
747 + * Information about the width of the inner tables.
748 + */
749 + private class TableWidthInfo
750 + {
751 + private int headerTableWidth;
752 + private int dataTableWidth;
753 + private int footerTableWidth;
754 + private int availableWidth;
755 +
756 + /**
757 + * Construct a new {@link TableWidthInfo}.
758 + *
759 + * @param includeSpacer true to include spacer in calculations
760 + */
761 + public TableWidthInfo( boolean includeSpacer )
762 + {
763 + availableWidth = getAvailableWidth();
764 + headerTableWidth = impl.getTableWidth( headerTable, includeSpacer );
765 + dataTableWidth = dataTable.getElement().getScrollWidth();
766 + if ( footerTable != null )
767 + {
768 + footerTableWidth = impl.getTableWidth( footerTable, includeSpacer );
769 + }
770 + }
771 + }
772 +
773 + /**
774 + * An {@link ImageBundle} that provides images for {@link AbstractScrollTable}
775 + * .
776 + */
777 + public static interface ScrollTableImages extends ImageBundle
778 + {
779 + /**
780 + * An image used to fill the available width.
781 + *
782 + * @return a prototype of this image
783 + */
784 + AbstractImagePrototype scrollTableFillWidth();
785 +
786 + /**
787 + * An image indicating that a column is sorted in ascending order.
788 + *
789 + * @return a prototype of this image
790 + */
791 + AbstractImagePrototype scrollTableAscending();
792 +
793 + /**
794 + * An image indicating a column is sorted in descending order.
795 + *
796 + * @return a prototype of this image
797 + */
798 + AbstractImagePrototype scrollTableDescending();
799 + }
800 +
801 + /**
802 + * The default style name.
803 + */
804 + public static final String DEFAULT_STYLE_NAME = "gwt-ScrollTable";
805 +
806 + /**
807 + * The resize policies related to user resizing.
808 + * <p/>
809 + * <ul>
810 + * <li>DISABLED - Columns cannot be resized by the user</li>
811 + * <li>SINGLE_CELL - Only cells with a colspan of 1 can be resized</li>
812 + * <li>MULTI_CELL - All cells can be resized by the user</li>
813 + * </ul>
814 + */
815 + public static enum ColumnResizePolicy
816 + {
817 + DISABLED, SINGLE_CELL, MULTI_CELL
297 818 }
298 - }
299 819
300 - /**
301 - * A helper class that handles some of the mouse events associated with
302 - * resizing columns.
303 - */
304 - private static class MouseResizeWorker {
305 820 /**
306 - * The width of the area that is available for resize.
821 + * The resize policies of table cells.
822 + * <p/>
823 + * <ul>
824 + * <li>UNCONSTRAINED - Columns shrink and expand independently of each other</li>
825 + * <li>FLOW - As one column expands or shrinks, the columns to the right will
826 + * do the opposite, trying to maintain the same size. The user can still
827 + * expand the grid if there is no more space to take from the columns on the
828 + * right.</li>
829 + * <li>FIXED_WIDTH - As one column expands or shrinks, the columns to the
830 + * right will do the opposite, trying to maintain the same size. The width of
831 + * the grid will remain constant, ignoring column resizing that would result
832 + * in the grid growing in size.</li>
833 + * <li>FILL_WIDTH - Same as FIXED_WIDTH, but the grid will always fill the
834 + * available width, even if the widget is resized.</li>
835 + * </ul>
307 836 */
308 - private static final int RESIZE_CURSOR_WIDTH = 15;
837 + public static enum ResizePolicy
838 + {
839 + UNCONSTRAINED( false, false ), FLOW( false, true ), FIXED_WIDTH( true, true ), FILL_WIDTH( true, true );
840 +
841 + private boolean isSacrificial;
842 + private boolean isFixedWidth;
843 +
844 + private ResizePolicy( boolean isFixedWidth, boolean isSacrificial )
845 + {
846 + this.isFixedWidth = isFixedWidth;
847 + this.isSacrificial = isSacrificial;
848 + }
849 +
850 + private boolean isFixedWidth()
851 + {
852 + return isFixedWidth;
853 + }
854 +
855 + private boolean isSacrificial()
856 + {
857 + return isSacrificial;
858 + }
859 + }
309 860
310 861 /**
311 - * The current header cell that the mouse is affecting.
862 + * The scroll policy of the table.
863 + * <p/>
864 + * <ul>
865 + * <li>HORIZONTAL - Only a horizontal scrollbar will be present.</li>
866 + * <li>BOTH - Both a vertical and horizontal scrollbar will be present.</li>
867 + * <li>DISABLED - Scrollbars will not appear, even if content doesn't fit</li>
868 + * </ul>
312 869 */
313 - private Element curCell = null;
870 + public static enum ScrollPolicy
871 + {
872 + HORIZONTAL, BOTH, DISABLED
873 + }
314 874
315 875 /**
316 - * The columns under the colSpan of the current cell.
876 + * The sorting policies related to user column sorting.
877 + * <p/>
878 + * <ul>
879 + * <li>DISABLED - Columns cannot be sorted by the user</li>
880 + * <li>SINGLE_CELL - Only cells with a colspan of 1 can be sorted</li>
881 + * <li>MULTI_CELL - All cells can be sorted by the user</li>
882 + * </ul>
317 883 */
318 - private List<ColumnWidthInfo> curCells = new ArrayList<ColumnWidthInfo>();
884 + public static enum SortPolicy
885 + {
886 + DISABLED, SINGLE_CELL, MULTI_CELL
887 + }
319 888
320 889 /**
321 - * The index of the current header cell.
890 + * The div that wraps the table wrappers.
322 891 */
323 - private int curCellIndex = 0;
892 + private Element absoluteElem;
324 893
325 894 /**
326 - * The current x position of the mouse.
895 + * The helper class used to resize columns.
327 896 */
328 - private int mouseXCurrent = 0;
897 + private ColumnResizer columnResizer = new ColumnResizer();
329 898
330 899 /**
331 - * The last x position of the mouse when we resized.
900 + * The policy applied to user actions that resize columns.
332 901 */
333 - private int mouseXLast = 0;
902 + private ColumnResizePolicy columnResizePolicy = ColumnResizePolicy.MULTI_CELL;
334 903
335 904 /**
336 - * The starting x position of the mouse when resizing a column.
905 + * The data table.
337 906 */
338 - private int mouseXStart = 0;
907 + private FixedWidthGrid dataTable;
339 908
340 909 /**
341 - * A timer used to resize the columns. As long as the timer is active, it
342 - * will poll for the new row size and resize the columns.
910 + * The scrollable wrapper div around the data table.
343 911 */
344 - private Timer resizeTimer = new Timer() {
345 - @Override
346 - public void run() {
347 - resizeColumn();
348 - schedule(100);
349 - }
350 - };
912 + private Element dataWrapper;
351 913
352 914 /**
353 - * A boolean indicating that we are resizing the current header cell.
915 + * An image used to show a fill width button.
354 916 */
355 - private boolean resizing = false;
917 + private Image fillWidthImage;
356 918
357 919 /**
358 - * The index of the first column that will be sacrificed.
920 + * A spacer used to stretch the footerTable area so we can scroll past the
921 + * edge of the footer table.
359 922 */
360 - private int sacrificeCellIndex = -1;
923 + private Element footerSpacer = null;
361 924
362 925 /**
363 - * The cells that will be sacrificed so the current cells can be resized.
926 + * The footer table.
364 927 */
365 - private List<ColumnWidthInfo> sacrificeCells = new ArrayList<ColumnWidthInfo>();
928 + private FixedWidthFlexTable footerTable = null;
366 929
367 930 /**
368 - * The table that this worker affects.
931 + * The non-scrollable wrapper div around the footer table.
369 932 */
370 - private AbstractScrollTable table = null;
933 + private Element footerWrapper = null;
371 934
372 935 /**
373 - * @return the current cell
936 + * A spacer used to stretch the headerTable area so we can scroll past the
937 + * edge of the header table.
374 938 */
375 - public Element getCurrentCell() {
376 - return curCell;
377 - }
939 + private Element headerSpacer;
378 940
379 941 /**
380 - * @return true if a header is currently being resized
942 + * The header table.
381 943 */
382 - public boolean isResizing() {
383 - return resizing;
384 - }
385 944
945 + private FixedWidthFlexTable headerTable = null;
386 946 /**
387 - * Resize the column on a mouse event. This method also marks the client as
388 - * busy so we do not try to change the size repeatedly.
389 - *
390 - * @param event the mouse event
947 + * The non-scrollable wrapper div around the header table.
391 948 */
392 - public void resizeColumn(Event event) {
393 - mouseXCurrent = DOM.eventGetClientX(event);
394 - }
949 + private Element headerWrapper;
395 950
396 951 /**
397 - * Set the current cell that will be resized based on the mouse event.
398 - *
399 - * @param event the event that triggered the new cell
400 - * @return true if the cell was actually changed
952 + * The images applied to the table.
401 953 */
402 - public boolean setCurrentCell(Event event) {
403 - // Check the resize policy of the table
404 - Element cell = null;
405 - if (table.columnResizePolicy == ColumnResizePolicy.MULTI_CELL) {
406 - cell = table.headerTable.getEventTargetCell(event);
407 - } else if (table.columnResizePolicy == ColumnResizePolicy.SINGLE_CELL) {
408 - cell = table.headerTable.getEventTargetCell(event);
409 - if (cell != null && cell.getPropertyInt("colSpan") > 1) {
410 - cell = null;
411 - }
412 - }
954 + private ScrollTableImages images;
413 955
414 - // See if we are near the edge of the cell
415 - int clientX = event.getClientX();
416 - if (cell != null) {
417 - int absLeft = cell.getAbsoluteLeft() - Window.getScrollLeft();
418 - if (LocaleInfo.getCurrentLocale().isRTL()) {
419 - if (clientX < absLeft || clientX > absLeft + RESIZE_CURSOR_WIDTH) {
420 - cell = null;
421 - }
422 - } else {
423 - int absRight = absLeft + cell.getOffsetWidth();
424 - if (clientX < absRight - RESIZE_CURSOR_WIDTH || clientX > absRight) {
425 - cell = null;
426 - }
427 - }
428 - }
956 + /**
957 + * The implementation class for this widget.
958 + */
959 + private Impl impl = GWT.create( Impl.class );
429 960
430 - // Change out the current cell
431 - if (cell != curCell) {
432 - // Clear the old cell
433 - if (curCell != null) {
434 - curCell.getStyle().setProperty("cursor", "");
435 - }
961 + /**
962 + * The last known height of this widget that the user set.
963 + */
964 + private String lastHeight = null;
436 965
437 - // Set the new cell
438 - curCell = cell;
439 - if (curCell != null) {
440 - // Check the cell index
441 - curCellIndex = getCellIndex(curCell);
442 - if (curCellIndex < 0) {
443 - curCell = null;
444 - return false;
445 - }
966 + /**
967 + * The last scrollLeft position.
968 + */
969 + private int lastScrollLeft;
446 970
447 - // Check for resizable columns within one of the cells in the colspan
448 - boolean resizable = false;
449 - int colSpan = cell.getPropertyInt("colSpan");
450 - curCells = table.getColumnWidthInfo(curCellIndex, colSpan);
451 - for (ColumnWidthInfo info : curCells) {
452 - if (!info.hasMaximumWidth() || !info.hasMinimumWidth()
453 - || info.getMaximumWidth() != info.getMinimumWidth()) {
454 - resizable = true;
455 - }
456 - }
457 - if (!resizable) {
458 - curCell = null;
459 - curCells = null;
460 - return false;
461 - }
971 + /**
972 + * An element used to determine the width of the scroll bar.
973 + */
974 + private com.google.gwt.dom.client.Element mockScrollable;
975 +
976 + /**
977 + * A boolean indicating whether or not the grid should try to maintain its
978 + * width as much as possible.
979 + */
980 + private ResizePolicy resizePolicy = ResizePolicy.FLOW;
462 981
463 - // Update the cursor on the cell
464 - curCell.getStyle().setProperty("cursor", "e-resize");
982 + /**
983 + * The worker that helps with mouse resize events.
984 + */
985 + private MouseResizeWorker resizeWorker = GWT.create( MouseResizeWorker.class );
986 +
987 + /**
988 + * The scrolling policy.
989 + */
990 + private ScrollPolicy scrollPolicy = ScrollPolicy.BOTH;
991 +
992 + /**
993 + * The current {@link SortPolicy}.
994 + */
995 + private SortPolicy sortPolicy = SortPolicy.SINGLE_CELL;
996 +
997 + /**
998 + * The cell index of the TD cell that initiated a column sort operation.
999 + */
1000 + private int sortedCellIndex = -1;
1001 +
1002 + /**
1003 + * The row index of the TD cell that initiated a column sort operation.
1004 + */
1005 + private int sortedRowIndex = -1;
1006 +
1007 + /**
1008 + * The wrapper around the image indicator.
1009 + */
1010 + private Element sortedColumnWrapper = null;
1011 +
1012 + /**
1013 + * Constructor.
1014 + *
1015 + * @param dataTable the data table
1016 + * @param headerTable the header table
1017 + */
1018 + public AbstractScrollTable( FixedWidthGrid dataTable, FixedWidthFlexTable headerTable )
1019 + {
1020 + this( dataTable, headerTable, (ScrollTableImages) GWT.create( ScrollTableImages.class ) );
1021 + }
1022 +
1023 + /**
1024 + * Constructor.
1025 + *
1026 + * @param dataTable the data table
1027 + * @param headerTable the header table
1028 + * @param images the images to use in the table
1029 + */
1030 + public AbstractScrollTable( FixedWidthGrid dataTable, final FixedWidthFlexTable headerTable, ScrollTableImages images )
1031 + {
1032 + super();
1033 + this.dataTable = dataTable;
1034 + this.headerTable = headerTable;
1035 + this.images = images;
1036 + resizeWorker.setScrollTable( this );
1037 +
1038 + // Prepare the header and data tables
1039 + prepareTable( dataTable, "dataTable" );
1040 + prepareTable( headerTable, "headerTable" );
1041 + if ( dataTable.getSelectionPolicy().hasInputColumn() )
1042 + {
1043 + headerTable.setColumnWidth( 0, dataTable.getInputColumnWidth() );
465 1044 }
466 - return true;
467 - }
468 1045
469 - // The cell did not change
470 - return false;
1046 + // Create the main div container
1047 + Element mainElem = DOM.createDiv();
1048 + setElement( mainElem );
1049 + setStylePrimaryName( DEFAULT_STYLE_NAME );
1050 + DOM.setStyleAttribute( mainElem, "padding", "0px" );
1051 + DOM.setStyleAttribute( mainElem, "overflow", "hidden" );
1052 + DOM.setStyleAttribute( mainElem, "position", "relative" );
1053 +
1054 + // Wrap the table wrappers in another div
1055 + absoluteElem = DOM.createDiv();
1056 + absoluteElem.getStyle().setProperty( "position", "absolute" );
1057 + absoluteElem.getStyle().setProperty( "top", "0px" );
1058 + absoluteElem.getStyle().setProperty( "left", "0px" );
1059 + absoluteElem.getStyle().setProperty( "width", "100%" );
1060 + absoluteElem.getStyle().setProperty( "padding", "0px" );
1061 + absoluteElem.getStyle().setProperty( "margin", "0px" );
1062 + absoluteElem.getStyle().setProperty( "border", "0px" );
1063 + absoluteElem.getStyle().setProperty( "overflow", "hidden" );
1064 + mainElem.appendChild( absoluteElem );
1065 +
1066 + // Create the table wrapper and spacer
1067 + headerWrapper = createWrapper( "headerWrapper" );
1068 + headerSpacer = impl.createSpacer( headerTable, headerWrapper );
1069 + dataWrapper = createWrapper( "dataWrapper" );
1070 +
1071 + // Create an element to determine the scroll bar width
1072 + mockScrollable = com.google.gwt.dom.client.Element.as( dataWrapper.cloneNode( false ) );
1073 + mockScrollable.getStyle().setProperty( "position", "absolute" );
1074 + mockScrollable.getStyle().setProperty( "top", "0px" );
1075 + mockScrollable.getStyle().setProperty( "left", "0px" );
1076 + mockScrollable.getStyle().setProperty( "width", "100px" );
1077 + mockScrollable.getStyle().setProperty( "height", "100px" );
1078 + mockScrollable.getStyle().setProperty( "visibility", "hidden" );
1079 + mockScrollable.getStyle().setProperty( "overflow", "scroll" );
1080 + mockScrollable.getStyle().setProperty( "zIndex", "-1" );
1081 + absoluteElem.appendChild( mockScrollable );
1082 +
1083 + // Create image to fill width
1084 + fillWidthImage = new Image()
1085 + {
1086 + @Override
1087 + public void onBrowserEvent( Event event )
1088 + {
1089 + super.onBrowserEvent( event );
1090 + if ( DOM.eventGetType( event ) == Event.ONCLICK )
1091 + {
1092 + fillWidth();
1093 + }
1094 + }
1095 + };
1096 + fillWidthImage.setTitle( "Shrink/Expand to fill visible area" );
1097 + images.scrollTableFillWidth().applyTo( fillWidthImage );
1098 + Element fillWidthImageElem = fillWidthImage.getElement();
1099 + DOM.setStyleAttribute( fillWidthImageElem, "cursor", "pointer" );
1100 + DOM.setStyleAttribute( fillWidthImageElem, "position", "absolute" );
1101 + DOM.setStyleAttribute( fillWidthImageElem, "top", "0px" );
1102 + DOM.setStyleAttribute( fillWidthImageElem, "right", "0px" );
1103 + DOM.setStyleAttribute( fillWidthImageElem, "zIndex", "1" );
1104 + add( fillWidthImage, getElement() );
1105 +
1106 + // Adopt the header and data tables into the panel
1107 + adoptTable( headerTable, headerWrapper, 0 );
1108 + adoptTable( dataTable, dataWrapper, 1 );
1109 +
1110 + // Create the sort indicator Image
1111 + sortedColumnWrapper = DOM.createSpan();
1112 +
1113 + // Add some event handling
1114 + sinkEvents( Event.ONMOUSEOUT );
1115 + DOM.setEventListener( dataWrapper, this );
1116 + DOM.sinkEvents( dataWrapper, Event.ONSCROLL );
1117 + DOM.setEventListener( headerWrapper, this );
1118 + DOM.sinkEvents( headerWrapper, Event.ONMOUSEMOVE | Event.ONMOUSEDOWN | Event.ONMOUSEUP | Event.ONCLICK );
1119 +
1120 + // Listen for sorting events in the data table
1121 + dataTable.addColumnSortHandler( new ColumnSortHandler()
1122 + {
1123 + public void onColumnSorted( ColumnSortEvent event )
1124 + {
1125 + // Get the primary column and sort order
1126 + int column = -1;
1127 + boolean ascending = true;
1128 + ColumnSortList sortList = event.getColumnSortList();
1129 + if ( sortList != null )
1130 + {
1131 + column = sortList.getPrimaryColumn();
1132 + ascending = sortList.isPrimaryAscending();
1133 + }
1134 +
1135 + // Remove the sorted column indicator
1136 + if ( isColumnSortable( column ) )
1137 + {
1138 + Element parent = DOM.getParent( sortedColumnWrapper );
1139 + if ( parent != null )
1140 + {
1141 + parent.removeChild( sortedColumnWrapper );
1142 + }
1143 +
1144 + // Re-add the sorted column indicator
1145 + if ( column < 0 )
1146 + {
1147 + sortedCellIndex = -1;
1148 + sortedRowIndex = -1;
1149 + }
1150 + else if ( sortedCellIndex >= 0 && sortedRowIndex >= 0 && headerTable.getRowCount() > sortedRowIndex && headerTable.getCellCount( sortedRowIndex ) > sortedCellIndex )
1151 + {
1152 + CellFormatter formatter = headerTable.getCellFormatter();
1153 + Element td = formatter.getElement( sortedRowIndex, sortedCellIndex );
1154 + applySortedColumnIndicator( td, ascending );
1155 + }
1156 + }
1157 + }
1158 + } );
1159 + }
1160 +
1161 + public HandlerRegistration addScrollHandler( ScrollHandler handler )
1162 + {
1163 + return addHandler( ScrollEvent.TYPE, handler );
471 1164 }
472 1165
473 1166 /**
474 - * Set the ScrollTable table that this worker affects.
475 - *
476 - * @param table the scroll table
1167 + * Adjust all column widths so they take up the maximum amount of space
1168 + * without needing a horizontal scroll bar. The distribution will be
1169 + * proportional to the current width of each column.
1170 + * <p/>
1171 + * The {@link AbstractScrollTable} must be visible on the page for this method
1172 + * to work.
477 1173 */
478 - public void setScrollTable(AbstractScrollTable table) {
479 - this.table = table;
1174 + public void fillWidth()
1175 + {
1176 + List<ColumnWidthInfo> colWidths = getFillColumnWidths( null );
1177 + applyNewColumnWidths( 0, colWidths, false );
1178 + scrollTables( false );
480 1179 }
481 1180
482 1181 /**
483 - * Start resizing the current cell when the user clicks on the right edge of
484 - * the cell.
485 - *
486 - * @param event the mouse event
1182 + * @return the cell padding of the tables, in pixels
487 1183 */
488 - public void startResizing(Event event) {
489 - if (curCell != null) {
490 - resizing = true;
491 - mouseXStart = event.getClientX();
492 - mouseXLast = mouseXStart;
493 - mouseXCurrent = mouseXStart;
1184 + public int getCellPadding()
1185 + {
1186 + return dataTable.getCellPadding();
1187 + }
494 1188
495 - // Add the sacrifice cells
496 - int numColumns = table.getDataTable().getColumnCount();
497 - int colSpan = curCell.getPropertyInt("colSpan");
498 - sacrificeCellIndex = curCellIndex + colSpan;
499 - sacrificeCells = table.getColumnWidthInfo(sacrificeCellIndex,
500 - numColumns - sacrificeCellIndex);
1189 + /**
1190 + * @return the cell spacing of the tables, in pixels
1191 + */
1192 + public int getCellSpacing()
1193 + {
1194 + return dataTable.getCellSpacing();
1195 + }
501 1196
502 - // Start the timer and listen for changes
503 - DOM.setCapture(table.headerWrapper);
504 - resizeTimer.schedule(20);
505 - }
1197 + /**
1198 + * @return the column resize policy
1199 + */
1200 + public ColumnResizePolicy getColumnResizePolicy()
1201 + {
1202 + return columnResizePolicy;
506 1203 }
507 1204
508 1205 /**
509 - * Stop resizing the current cell.
510 - *
511 - * @param event the mouse event
1206 + * Return the column width for a given column index.
1207 + *
1208 + * @param column the column index
1209 + *
1210 + * @return the column width in pixels
512 1211 */
513 - public void stopResizing(Event event) {
514 - if (curCell != null && resizing) {
515 - curCell.getStyle().setProperty("cursor", "");
516 - curCell = null;
517 - resizing = false;
518 - DOM.releaseCapture(table.headerWrapper);
519 - resizeTimer.cancel();
520 - resizeColumn();
521 - curCells = null;
522 - sacrificeCells = null;
523 - table.resizeTablesVertically();
524 - }
1212 + public int getColumnWidth( int column )
1213 + {
1214 + return dataTable.getColumnWidth( column );
525 1215 }
526 1216
527 1217 /**
528 - * Get the scroll table.
529 - *
530 - * @return the scroll table
1218 + * @return the data table
531 1219 */
532 - protected AbstractScrollTable getScrollTable() {
533 - return table;
1220 + public FixedWidthGrid getDataTable()
1221 + {
1222 + return dataTable;
534 1223 }
535 1224
536 1225 /**
537 - * Get the actual cell index of a cell in the header table.
538 - *
539 - * @param cell the cell element
540 - * @return the cell index
1226 + * @return the footer table
541 1227 */
542 - private int getCellIndex(Element cell) {
543 - int row = OverrideDOM.getRowIndex(DOM.getParent(cell)) - 1;
544 - int column = OverrideDOM.getCellIndex(cell);
545 - return table.headerTable.getColumnIndex(row, column)
546 - - table.getHeaderOffset();
1228 + public FixedWidthFlexTable getFooterTable()
1229 + {
1230 + return footerTable;
547 1231 }
548 1232
549 1233 /**
550 - * Helper method that actually sets the column size. This method is called
551 - * periodically by a timer.
1234 + * @return the header table
552 1235 */
553 - private void resizeColumn() {
554 - if (mouseXLast != mouseXCurrent) {
555 - mouseXLast = mouseXCurrent;
1236 + public FixedWidthFlexTable getHeaderTable()
1237 + {
1238 + return headerTable;
1239 + }
556 1240
557 - // Distribute to the cells being resized
558 - int totalDelta = mouseXCurrent - mouseXStart;
559 - if (LocaleInfo.getCurrentLocale().isRTL()) {
560 - totalDelta *= -1;
561 - }
562 - totalDelta -= table.columnResizer.distributeWidth(curCells, totalDelta);
1241 + /**
1242 + * Get the absolute maximum width of a column.
1243 + *
1244 + * @param column the column index
1245 + *
1246 + * @return the maximum allowable width of the column
1247 + */
1248 + public abstract int getMaximumColumnWidth( int column );
563 1249
564 - // Distribute to the sacrifice cells
565 - if (table.resizePolicy.isSacrificial()) {
566 - int remaining = table.columnResizer.distributeWidth(sacrificeCells,
567 - -totalDelta);
1250 + /**
1251 + * Get the absolute minimum width of a column.
1252 + *
1253 + * @param column the column index
1254 + *
1255 + * @return the minimum allowable width of the column
1256 + */
1257 + public abstract int getMinimumColumnWidth( int column );
568 1258
569 - // We don't have enough to sacrifice, redistribute the width
570 - if (remaining != 0 && table.resizePolicy.isFixedWidth()) {
571 - totalDelta += remaining;
572 - table.columnResizer.distributeWidth(curCells, totalDelta);
573 - }
1259 + /**
1260 + * Get the minimum offset width of the largest inner table given the
1261 + * constraints on the minimum and ideal column widths. Note that this does not
1262 + * account for the vertical scroll bar.
1263 + *
1264 + * @return the tables minimum offset width, or -1 if it cannot be calculated
1265 + */
1266 + public int getMinimumOffsetWidth()
1267 + {
1268 + if ( !isAttached() )
1269 + {
1270 + return -1;
1271 + }
574 1272
575 - // Apply the widths to the sacrifice column
576 - table.applyNewColumnWidths(sacrificeCellIndex, sacrificeCells, true);
1273 + // Determine the width and column count of the largest table
1274 + TableWidthInfo redrawInfo = new TableWidthInfo( true );
1275 + maybeRecalculateIdealColumnWidths( null );
1276 + if ( redrawInfo.availableWidth < 1 )
1277 + {
1278 + return -1;
577 1279 }
578 1280
579 - // Set the new widths
580 - table.applyNewColumnWidths(curCellIndex, curCells, true);
1281 + int scrollWidth = 0;
1282 + int numColumns = 0;
1283 + {
1284 + int numHeaderCols = headerTable.getColumnCount() - getHeaderOffset();
1285 + int numDataCols = dataTable.getColumnCount();
1286 + int numFooterCols = (footerTable == null) ? -1 : footerTable.getColumnCount() - getHeaderOffset();
1287 + if ( numHeaderCols >= numDataCols && numHeaderCols >= numFooterCols )
1288 + {
1289 + numColumns = numHeaderCols;
1290 + scrollWidth = redrawInfo.headerTableWidth;
1291 + }
1292 + else if ( numFooterCols >= numDataCols && numFooterCols >= numHeaderCols )
1293 + {
1294 + numColumns = numFooterCols;
1295 + scrollWidth = redrawInfo.footerTableWidth;
1296 + }
1297 + else if ( numDataCols > 0 )
1298 + {
1299 + numColumns = numDataCols;
1300 + scrollWidth = redrawInfo.dataTableWidth;
1301 + }
1302 + }
1303 + if ( numColumns <= 0 )
1304 + {
1305 + return -1;
1306 + }
581 1307
582 - // Scroll to table back into alignment
583 - table.scrollTables(false);
584 - }
1308 + // Calculate the available diff
1309 + List<ColumnWidthInfo> colWidthInfos = getColumnWidthInfo( 0, numColumns );
1310 + return -columnResizer.distributeWidth( colWidthInfos, -scrollWidth );
585 1311 }
586 - }
587 1312
588 - /**
589 - * The Opera version of the mouse worker fixes an Opera bug where the cursor
590 - * isn't updated if the mouse is hovering over an element DOM object when its
591 - * cursor style is changed.
592 - */
593 - @SuppressWarnings("unused")
594 - private static class MouseResizeWorkerOpera extends MouseResizeWorker {
595 1313 /**
596 - * A div used to force the cursor to update.
1314 + * Get the preferred width of a column.
1315 + *
1316 + * @param column the column index
1317 + *
1318 + * @return the preferred width of the column
597 1319 */
598 - private Element cursorUpdateDiv;
1320 + public abstract int getPreferredColumnWidth( int column );
599 1321
600 1322 /**
601 - * Constructor.
1323 + * @return the resize policy
602 1324 */
603 - public MouseResizeWorkerOpera() {
604 - cursorUpdateDiv = DOM.createDiv();
605 - DOM.setStyleAttribute(cursorUpdateDiv, "position", "absolute");
1325 + public ResizePolicy getResizePolicy()
1326 + {
1327 + return resizePolicy;
606 1328 }
607 1329
608 1330 /**
609 - * Set the current cell that will be resized based on the mouse event.
610 - *
611 - * @param event the event that triggered the new cell
612 - * @return true if the cell was actually changed
1331 + * @return the current scroll policy
613 1332 */
614 - @Override
615 - public boolean setCurrentCell(Event event) {
616 - // Check if cursor update div is active
617 - if (DOM.eventGetTarget(event) == cursorUpdateDiv) {
618 - removeCursorUpdateDiv();
619 - return false;
620 - }
621 -
622 - // Use the parent method
623 - boolean cellChanged = super.setCurrentCell(event);
624 -
625 - // Position a div that forces a cursor redraw in Opera
626 - if (cellChanged) {
627 - DOM.setCapture(getScrollTable().headerWrapper);
628 - DOM.setStyleAttribute(cursorUpdateDiv, "height",
629 - (Window.getClientHeight() - 1) + "px");
630 - DOM.setStyleAttribute(cursorUpdateDiv, "width",
631 - (Window.getClientWidth() - 1) + "px");
632 - DOM.setStyleAttribute(cursorUpdateDiv, "left", "0px");
633 - DOM.setStyleAttribute(cursorUpdateDiv, "top", "0px");
634 - DOM.appendChild(RootPanel.getBodyElement(), cursorUpdateDiv);
635 - }
636 - return cellChanged;
1333 + public ScrollPolicy getScrollPolicy()
1334 + {
1335 + return scrollPolicy;
637 1336 }
638 1337
639 1338 /**
640 - * Start resizing the current cell.
641 - *
642 - * @param event the mouse event
1339 + * @return the current sort policy
643 1340 */
644 - @Override
645 - public void startResizing(Event event) {
646 - removeCursorUpdateDiv();
647 - super.startResizing(event);
648 - }
649 -
650 - /**
651 - * Remove the cursor update div from the page.
652 - */
653 - private void removeCursorUpdateDiv() {
654 - if (DOM.getCaptureElement() != null) {
655 - DOM.removeChild(RootPanel.getBodyElement(), cursorUpdateDiv);
656 - DOM.releaseCapture(getScrollTable().headerWrapper);
657 - }
658 - }
659 - }
660 -
661 - /**
662 - * Information about the height of the inner tables.
663 - */
664 - private class TableHeightInfo {
665 - private int headerTableHeight;
666 - private int dataTableHeight;
667 - private int footerTableHeight;
668 -
669 - /**
670 - * Construct a new {@link TableHeightInfo}.
671 - */
672 - public TableHeightInfo() {
673 - int totalHeight = DOM.getElementPropertyInt(getElement(), "clientHeight");
674 - headerTableHeight = headerTable.getOffsetHeight();
675 - if (footerTable != null) {
676 - footerTableHeight = footerTable.getOffsetHeight();
677 - }
678 - dataTableHeight = totalHeight - headerTableHeight - footerTableHeight;
679 - }
680 - }
681 -
682 - /**
683 - * Information about the width of the inner tables.
684 - */
685 - private class TableWidthInfo {
686 - private int headerTableWidth;
687 - private int dataTableWidth;
688 - private int footerTableWidth;
689 - private int availableWidth;
690 -
691 - /**
692 - * Construct a new {@link TableWidthInfo}.
693 - *
694 - * @param includeSpacer true to include spacer in calculations
695 - */
696 - public TableWidthInfo(boolean includeSpacer) {
697 - availableWidth = getAvailableWidth();
698 - headerTableWidth = impl.getTableWidth(headerTable, includeSpacer);
699 - dataTableWidth = dataTable.getElement().getScrollWidth();
700 - if (footerTable != null) {
701 - footerTableWidth = impl.getTableWidth(footerTable, includeSpacer);
702 - }
703 - }
704 - }
705 -
706 - /**
707 - * An {@link ImageBundle} that provides images for {@link AbstractScrollTable}
708 - * .
709 - */
710 - public static interface ScrollTableImages extends ImageBundle {
711 - /**
712 - * An image used to fill the available width.
713 - *
714 - * @return a prototype of this image
715 - */
716 - AbstractImagePrototype scrollTableFillWidth();
717 -
718 - /**
719 - * An image indicating that a column is sorted in ascending order.
720 - *
721 - * @return a prototype of this image
722 - */
723 - AbstractImagePrototype scrollTableAscending();
724 -
725 - /**
726 - * An image indicating a column is sorted in descending order.
727 - *
728 - * @return a prototype of this image
729 - */
730 - AbstractImagePrototype scrollTableDescending();
731 - }
732 -
733 - /**
734 - * The default style name.
735 - */
736 - public static final String DEFAULT_STYLE_NAME = "gwt-ScrollTable";
737 -
738 - /**
739 - * The resize policies related to user resizing.
740 - *
741 - * <ul>
742 - * <li>DISABLED - Columns cannot be resized by the user</li>
743 - * <li>SINGLE_CELL - Only cells with a colspan of 1 can be resized</li>
744 - * <li>MULTI_CELL - All cells can be resized by the user</li>
745 - * </ul>
746 - */
747 - public static enum ColumnResizePolicy {
748 - DISABLED, SINGLE_CELL, MULTI_CELL
749 - }
750 -
751 - /**
752 - * The resize policies of table cells.
753 - *
754 - * <ul>
755 - * <li>UNCONSTRAINED - Columns shrink and expand independently of each other</li>
756 - * <li>FLOW - As one column expands or shrinks, the columns to the right will
757 - * do the opposite, trying to maintain the same size. The user can still
758 - * expand the grid if there is no more space to take from the columns on the
759 - * right.</li>
760 - * <li>FIXED_WIDTH - As one column expands or shrinks, the columns to the
761 - * right will do the opposite, trying to maintain the same size. The width of
762 - * the grid will remain constant, ignoring column resizing that would result
763 - * in the grid growing in size.</li>
764 - * <li>FILL_WIDTH - Same as FIXED_WIDTH, but the grid will always fill the
765 - * available width, even if the widget is resized.</li>
766 - * </ul>
767 - */
768 - public static enum ResizePolicy {
769 - UNCONSTRAINED(false, false), FLOW(false, true), FIXED_WIDTH(true, true), FILL_WIDTH(
770 - true, true);
771 -
772 - private boolean isSacrificial;
773 - private boolean isFixedWidth;
774 -
775 - private ResizePolicy(boolean isFixedWidth, boolean isSacrificial) {
776 - this.isFixedWidth = isFixedWidth;
777 - this.isSacrificial = isSacrificial;
778 - }
779 -
780 - private boolean isFixedWidth() {
781 - return isFixedWidth;
782 - }
783 -
784 - private boolean isSacrificial() {
785 - return isSacrificial;
786 - }
787 - }
788 -
789 - /**
790 - * The scroll policy of the table.
791 - *
792 - * <ul>
793 - * <li>HORIZONTAL - Only a horizontal scrollbar will be present.</li>
794 - * <li>BOTH - Both a vertical and horizontal scrollbar will be present.</li>
795 - * <li>DISABLED - Scrollbars will not appear, even if content doesn't fit</li>
796 - * </ul>
797 - */
798 - public static enum ScrollPolicy {
799 - HORIZONTAL, BOTH, DISABLED
800 - }
801 -
802 - /**
803 - * The sorting policies related to user column sorting.
804 - *
805 - * <ul>
806 - * <li>DISABLED - Columns cannot be sorted by the user</li>
807 - * <li>SINGLE_CELL - Only cells with a colspan of 1 can be sorted</li>
808 - * <li>MULTI_CELL - All cells can be sorted by the user</li>
809 - * </ul>
810 - */
811 - public static enum SortPolicy {
812 - DISABLED, SINGLE_CELL, MULTI_CELL
813 - }
814 -
815 - /**
816 - * The div that wraps the table wrappers.
817 - */
818 - private Element absoluteElem;
819 -
820 - /**
821 - * The helper class used to resize columns.
822 - */
823 - private ColumnResizer columnResizer = new ColumnResizer();
824 -
825 - /**
826 - * The policy applied to user actions that resize columns.
827 - */
828 - private ColumnResizePolicy columnResizePolicy = ColumnResizePolicy.MULTI_CELL;
829 -
830 - /**
831 - * The data table.
832 - */
833 - private FixedWidthGrid dataTable;
834 -
835 - /**
836 - * The scrollable wrapper div around the data table.
837 - */
838 - private Element dataWrapper;
839 -
840 - /**
841 - * An image used to show a fill width button.
842 - */
843 - private Image fillWidthImage;
844 -
845 - /**
846 - * A spacer used to stretch the footerTable area so we can scroll past the
847 - * edge of the footer table.
848 - */
849 - private Element footerSpacer = null;
850 -
851 - /**
852 - * The footer table.
853 - */
854 - private FixedWidthFlexTable footerTable = null;
855 -
856 - /**
857 - * The non-scrollable wrapper div around the footer table.
858 - */
859 - private Element footerWrapper = null;
860 -
861 - /**
862 - * A spacer used to stretch the headerTable area so we can scroll past the
863 - * edge of the header table.
864 - */
865 - private Element headerSpacer;
866 -
867 - /**
868 - * The header table.
869 - */
870 -
871 - private FixedWidthFlexTable headerTable = null;
872 - /**
873 - * The non-scrollable wrapper div around the header table.
874 - */
875 - private Element headerWrapper;
876 -
877 - /**
878 - * The images applied to the table.
879 - */
880 - private ScrollTableImages images;
881 -
882 - /**
883 - * The implementation class for this widget.
884 - */
885 - private Impl impl = GWT.create(Impl.class);
886 -
887 - /**
888 - * The last known height of this widget that the user set.
889 - */
890 - private String lastHeight = null;
891 -
892 - /**
893 - * The last scrollLeft position.
894 - */
895 - private int lastScrollLeft;
896 -
897 - /**
898 - * An element used to determine the width of the scroll bar.
899 - */
900 - private com.google.gwt.dom.client.Element mockScrollable;
901 -
902 - /**
903 - * A boolean indicating whether or not the grid should try to maintain its
904 - * width as much as possible.
905 - */
906 - private ResizePolicy resizePolicy = ResizePolicy.FLOW;
907 -
908 - /**
909 - * The worker that helps with mouse resize events.
910 - */
911 - private MouseResizeWorker resizeWorker = GWT.create(MouseResizeWorker.class);
912 -
913 - /**
914 - * The scrolling policy.
915 - */
916 - private ScrollPolicy scrollPolicy = ScrollPolicy.BOTH;
917 -
918 - /**
919 - * The current {@link SortPolicy}.
920 - */
921 - private SortPolicy sortPolicy = SortPolicy.SINGLE_CELL;
922 -
923 - /**
924 - * The cell index of the TD cell that initiated a column sort operation.
925 - */
926 - private int sortedCellIndex = -1;
927 -
928 - /**
929 - * The row index of the TD cell that initiated a column sort operation.
930 - */
931 - private int sortedRowIndex = -1;
932 -
933 - /**
934 - * The wrapper around the image indicator.
935 - */
936 - private Element sortedColumnWrapper = null;
937 -
938 - /**
939 - * Constructor.
940 - *
941 - * @param dataTable the data table
942 - * @param headerTable the header table
943 - */
944 - public AbstractScrollTable(FixedWidthGrid dataTable,
945 - FixedWidthFlexTable headerTable) {
946 - this(dataTable, headerTable,
947 - (ScrollTableImages) GWT.create(ScrollTableImages.class));
948 - }
949 -
950 - /**
951 - * Constructor.
952 - *
953 - * @param dataTable the data table
954 - * @param headerTable the header table
955 - * @param images the images to use in the table
956 - */
957 - public AbstractScrollTable(FixedWidthGrid dataTable,
958 - final FixedWidthFlexTable headerTable, ScrollTableImages images) {
959 - super();
960 - this.dataTable = dataTable;
961 - this.headerTable = headerTable;
962 - this.images = images;
963 - resizeWorker.setScrollTable(this);
964 -
965 - // Prepare the header and data tables
966 - prepareTable(dataTable, "dataTable");
967 - prepareTable(headerTable, "headerTable");
968 - if (dataTable.getSelectionPolicy().hasInputColumn()) {
969 - headerTable.setColumnWidth(0, dataTable.getInputColumnWidth());
970 - }
971 -
972 - // Create the main div container
973 - Element mainElem = DOM.createDiv();
974 - setElement(mainElem);
975 - setStylePrimaryName(DEFAULT_STYLE_NAME);
976 - DOM.setStyleAttribute(mainElem, "padding", "0px");
977 - DOM.setStyleAttribute(mainElem, "overflow", "hidden");
978 - DOM.setStyleAttribute(mainElem, "position", "relative");
979 -
980 - // Wrap the table wrappers in another div
981 - absoluteElem = DOM.createDiv();
982 - absoluteElem.getStyle().setProperty("position", "absolute");
983 - absoluteElem.getStyle().setProperty("top", "0px");
984 - absoluteElem.getStyle().setProperty("left", "0px");
985 - absoluteElem.getStyle().setProperty("width", "100%");
986 - absoluteElem.getStyle().setProperty("padding", "0px");
987 - absoluteElem.getStyle().setProperty("margin", "0px");
988 - absoluteElem.getStyle().setProperty("border", "0px");
989 - absoluteElem.getStyle().setProperty("overflow", "hidden");
990 - mainElem.appendChild(absoluteElem);
991 -
992 - // Create the table wrapper and spacer
993 - headerWrapper = createWrapper("headerWrapper");
994 - headerSpacer = impl.createSpacer(headerTable, headerWrapper);
995 - dataWrapper = createWrapper("dataWrapper");
996 -
997 - // Create an element to determine the scroll bar width
998 - mockScrollable = com.google.gwt.dom.client.Element.as(dataWrapper.cloneNode(false));
999 - mockScrollable.getStyle().setProperty("position", "absolute");
1000 - mockScrollable.getStyle().setProperty("top", "0px");
1001 - mockScrollable.getStyle().setProperty("left", "0px");
1002 - mockScrollable.getStyle().setProperty("width", "100px");
1003 - mockScrollable.getStyle().setProperty("height", "100px");
1004 - mockScrollable.getStyle().setProperty("visibility", "hidden");
1005 - mockScrollable.getStyle().setProperty("overflow", "scroll");
1006 - mockScrollable.getStyle().setProperty("zIndex", "-1");
1007 - absoluteElem.appendChild(mockScrollable);
1008 -
1009 - // Create image to fill width
1010 - fillWidthImage = new Image() {
1011 - @Override
1012 - public void onBrowserEvent(Event event) {
1013 - super.onBrowserEvent(event);
1014 - if (DOM.eventGetType(event) == Event.ONCLICK) {
1015 - fillWidth();
1016 - }
1017 - }
1018 - };
1019 - fillWidthImage.setTitle("Shrink/Expand to fill visible area");
1020 - images.scrollTableFillWidth().applyTo(fillWidthImage);
1021 - Element fillWidthImageElem = fillWidthImage.getElement();
1022 - DOM.setStyleAttribute(fillWidthImageElem, "cursor", "pointer");
1023 - DOM.setStyleAttribute(fillWidthImageElem, "position", "absolute");
1024 - DOM.setStyleAttribute(fillWidthImageElem, "top", "0px");
1025 - DOM.setStyleAttribute(fillWidthImageElem, "right", "0px");
1026 - DOM.setStyleAttribute(fillWidthImageElem, "zIndex", "1");
1027 - add(fillWidthImage, getElement());
1028 -
1029 - // Adopt the header and data tables into the panel
1030 - adoptTable(headerTable, headerWrapper, 0);
1031 - adoptTable(dataTable, dataWrapper, 1);
1032 -
1033 - // Create the sort indicator Image
1034 - sortedColumnWrapper = DOM.createSpan();
1035 -
1036 - // Add some event handling
1037 - sinkEvents(Event.ONMOUSEOUT);
1038 - DOM.setEventListener(dataWrapper, this);
1039 - DOM.sinkEvents(dataWrapper, Event.ONSCROLL);
1040 - DOM.setEventListener(headerWrapper, this);
1041 - DOM.sinkEvents(headerWrapper, Event.ONMOUSEMOVE | Event.ONMOUSEDOWN
1042 - | Event.ONMOUSEUP | Event.ONCLICK);
1043 -
1044 - // Listen for sorting events in the data table
1045 - dataTable.addColumnSortHandler(new ColumnSortHandler() {
1046 - public void onColumnSorted(ColumnSortEvent event) {
1047 - // Get the primary column and sort order
1048 - int column = -1;
1049 - boolean ascending = true;
1050 - ColumnSortList sortList = event.getColumnSortList();
1051 - if (sortList != null) {
1052 - column = sortList.getPrimaryColumn();
1053 - ascending = sortList.isPrimaryAscending();
1054 - }
1055 -
1056 - // Remove the sorted column indicator
1057 - if (isColumnSortable(column)) {
1058 - Element parent = DOM.getParent(sortedColumnWrapper);
1059 - if (parent != null) {
1060 - parent.removeChild(sortedColumnWrapper);
1061 - }
1062 -
1063 - // Re-add the sorted column indicator
1064 - if (column < 0) {
1065 - sortedCellIndex = -1;
1066 - sortedRowIndex = -1;
1067 - } else if (sortedCellIndex >= 0 && sortedRowIndex >= 0
1068 - && headerTable.getRowCount() > sortedRowIndex
1069 - && headerTable.getCellCount(sortedRowIndex) > sortedCellIndex) {
1070 - CellFormatter formatter = headerTable.getCellFormatter();
1071 - Element td = formatter.getElement(sortedRowIndex, sortedCellIndex);
1072 - applySortedColumnIndicator(td, ascending);
1073 - }
1074 - }
1075 - }
1076 - });
1077 - }
1078 -
1079 - public HandlerRegistration addScrollHandler(ScrollHandler handler) {
1080 - return addHandler(ScrollEvent.TYPE, handler);
1081 - }
1082 -
1083 - /**
1084 - * Adjust all column widths so they take up the maximum amount of space
1085 - * without needing a horizontal scroll bar. The distribution will be
1086 - * proportional to the current width of each column.
1087 - *
1088 - * The {@link AbstractScrollTable} must be visible on the page for this method
1089 - * to work.
1090 - */
1091 - public void fillWidth() {
1092 - List<ColumnWidthInfo> colWidths = getFillColumnWidths(null);
1093 - applyNewColumnWidths(0, colWidths, false);
1094 - scrollTables(false);
1095 - }
1096 -
1097 - /**
1098 - * @return the cell padding of the tables, in pixels
1099 - */
1100 - public int getCellPadding() {
1101 - return dataTable.getCellPadding();
1102 - }
1103 -
1104 - /**
1105 - * @return the cell spacing of the tables, in pixels
1106 - */
1107 - public int getCellSpacing() {
1108 - return dataTable.getCellSpacing();
1109 - }
1110 -
1111 - /**
1112 - * @return the column resize policy
1113 - */
1114 - public ColumnResizePolicy getColumnResizePolicy() {
1115 - return columnResizePolicy;
1116 - }
1117 -
1118 - /**
1119 - * Return the column width for a given column index.
1120 - *
1121 - * @param column the column index
1122 - * @return the column width in pixels
1123 - */
1124 - public int getColumnWidth(int column) {
1125 - return dataTable.getColumnWidth(column);
1126 - }
1127 -
1128 - /**
1129 - * @return the data table
1130 - */
1131 - public FixedWidthGrid getDataTable() {
1132 - return dataTable;
1133 - }
1134 -
1135 - /**
1136 - * @return the footer table
1137 - */
1138 - public FixedWidthFlexTable getFooterTable() {
1139 - return footerTable;
1140 - }
1141 -
1142 - /**
1143 - * @return the header table
1144 - */
1145 - public FixedWidthFlexTable getHeaderTable() {
1146 - return headerTable;
1147 - }
1148 -
1149 - /**
1150 - * Get the absolute maximum width of a column.
1151 - *
1152 - * @param column the column index
1153 - * @return the maximum allowable width of the column
1154 - */
1155 - public abstract int getMaximumColumnWidth(int column);
1156 -
1157 - /**
1158 - * Get the absolute minimum width of a column.
1159 - *
1160 - * @param column the column index
1161 - * @return the minimum allowable width of the column
1162 - */
1163 - public abstract int getMinimumColumnWidth(int column);
1164 -
1165 - /**
1166 - * Get the minimum offset width of the largest inner table given the
1167 - * constraints on the minimum and ideal column widths. Note that this does not
1168 - * account for the vertical scroll bar.
1169 - *
1170 - * @return the tables minimum offset width, or -1 if it cannot be calculated
1171 - */
1172 - public int getMinimumOffsetWidth() {
1173 - if (!isAttached()) {
1174 - return -1;
1175 - }
1176 -
1177 - // Determine the width and column count of the largest table
1178 - TableWidthInfo redrawInfo = new TableWidthInfo(true);
1179 - maybeRecalculateIdealColumnWidths(null);
1180 - if (redrawInfo.availableWidth < 1) {
1181 - return -1;
1182 - }
1183 -
1184 - int scrollWidth = 0;
1185 - int numColumns = 0;
1186 - {
1187 - int numHeaderCols = headerTable.getColumnCount() - getHeaderOffset();
1188 - int numDataCols = dataTable.getColumnCount();
1189 - int numFooterCols = (footerTable == null) ? -1
1190 - : footerTable.getColumnCount() - getHeaderOffset();
1191 - if (numHeaderCols >= numDataCols && numHeaderCols >= numFooterCols) {
1192 - numColumns = numHeaderCols;
1193 - scrollWidth = redrawInfo.headerTableWidth;
1194 - } else if (numFooterCols >= numDataCols && numFooterCols >= numHeaderCols) {
1195 - numColumns = numFooterCols;
1196 - scrollWidth = redrawInfo.footerTableWidth;
1197 - } else if (numDataCols > 0) {
1198 - numColumns = numDataCols;
1199 - scrollWidth = redrawInfo.dataTableWidth;
1200 - }
1201 - }
1202 - if (numColumns <= 0) {
1203 - return -1;
1204 - }
1205 -
1206 - // Calculate the available diff
1207 - List<ColumnWidthInfo> colWidthInfos = getColumnWidthInfo(0, numColumns);
1208 - return -columnResizer.distributeWidth(colWidthInfos, -scrollWidth);
1209 - }
1210 -
1211 - /**
1212 - * Get the preferred width of a column.
1213 - *
1214 - * @param column the column index
1215 - * @return the preferred width of the column
1216 - */
1217 - public abstract int getPreferredColumnWidth(int column);
1218 -
1219 - /**
1220 - * @return the resize policy
1221 - */
1222 - public ResizePolicy getResizePolicy() {
1223 - return resizePolicy;
1224 - }
1225 -
1226 - /**
1227 - * @return the current scroll policy
1228 - */
1229 - public ScrollPolicy getScrollPolicy() {
1230 - return scrollPolicy;
1231 - }
1232 -
1233 - /**
1234 - * @return the current sort policy
1235 - */
1236 - public SortPolicy getSortPolicy() {
1237 - return sortPolicy;
1238 - }
1239 -
1240 - /**
1241 - * Returns true if the specified column is sortable.
1242 - *
1243 - * @param column the column index
1244 - * @return true if the column is sortable, false if it is not sortable
1245 - */
1246 - public abstract boolean isColumnSortable(int column);
1247 -
1248 - /**
1249 - * Returns true if the specified column can be truncated. If it cannot be
1250 - * truncated, its minimum width will be adjusted to ensure the cell content is
1251 - * visible.
1252 - *
1253 - * @param column the column index
1254 - * @return true if the column is truncatable, false if it is not
1255 - */
1256 - public abstract boolean isColumnTruncatable(int column);
1257 -
1258 - /**
1259 - * Returns true if the specified column in the footer table can be truncated.
1260 - * If it cannot be truncated, its minimum width will be adjusted to ensure the
1261 - * cell content is visible.
1262 - *
1263 - * @param column the column index
1264 - * @return true if the column is truncatable, false if it is not
1265 - */
1266 - public abstract boolean isFooterColumnTruncatable(int column);
1267 -
1268 - /**
1269 - * Returns true if the specified column in the header table can be truncated.
1270 - * If it cannot be truncated, its minimum width will be adjusted to ensure the
1271 - * cell content is visible.
1272 - *
1273 - * @param column the column index
1274 - * @return true if the column is truncatable, false if it is not
1275 - */
1276 - public abstract boolean isHeaderColumnTruncatable(int column);
1277 -
1278 - @Override
1279 - public void onBrowserEvent(Event event) {
1280 - super.onBrowserEvent(event);
1281 - Element target = DOM.eventGetTarget(event);
1282 - switch (DOM.eventGetType(event)) {
1283 - case Event.ONSCROLL:
1284 - // Reposition the tables on scroll
1285 - lastScrollLeft = dataWrapper.getScrollLeft();
1286 - scrollTables(false);
1287 - if (dataWrapper.isOrHasChild(target)) {
1288 - fireEvent(new ScrollEvent(event));
1289 - }
1290 - break;
1291 -
1292 - case Event.ONMOUSEDOWN:
1293 - // Start resizing a header column
1294 - if (DOM.eventGetButton(event) != Event.BUTTON_LEFT) {
1295 - return;
1296 - }
1297 - if (resizeWorker.getCurrentCell() != null) {
1298 - DOM.eventPreventDefault(event);
1299 - DOM.eventCancelBubble(event, true);
1300 - resizeWorker.startResizing(event);
1301 - }
1302 - break;
1303 - case Event.ONMOUSEUP:
1304 - if (DOM.eventGetButton(event) != Event.BUTTON_LEFT) {
1305 - return;
1306 - }
1307 - // Stop resizing the header column
1308 - if (resizeWorker.isResizing()) {
1309 - resizeWorker.stopResizing(event);
1310 - } else {
1311 - // Scroll tables if needed
1312 - if (DOM.isOrHasChild(headerWrapper, target)) {
1313 - scrollTables(true);
1314 - } else {
1315 - scrollTables(false);
1316 - }
1317 -
1318 - // Get the actual column index
1319 - Element cellElem = headerTable.getEventTargetCell(event);
1320 - if (cellElem != null) {
1321 - // Sorting is disabled
1322 - if (sortPolicy == SortPolicy.DISABLED) {
1323 - return;
1324 - }
1325 -
1326 - // Check the colSpan
1327 - int colSpan = cellElem.getPropertyInt("colSpan");
1328 - if (colSpan > 1 && getSortPolicy() != SortPolicy.MULTI_CELL) {
1329 - return;
1330 - }
1331 -
1332 - // Sort the column
1333 - sortedRowIndex = OverrideDOM.getRowIndex(DOM.getParent(cellElem)) - 1;
1334 - sortedCellIndex = OverrideDOM.getCellIndex(cellElem);
1335 - int column = headerTable.getColumnIndex(sortedRowIndex,
1336 - sortedCellIndex)
1337 - - getHeaderOffset();
1338 - if (column >= 0 && isColumnSortable(column)) {
1339 - if (dataTable.getColumnCount() > column
1340 - && onHeaderSort(sortedRowIndex, column)) {
1341 - dataTable.sortColumn(column);
1342 - }
1343 - }
1344 - }
1345 - }
1346 - break;
1347 - case Event.ONMOUSEMOVE:
1348 - // Resize the header column
1349 - if (resizeWorker.isResizing()) {
1350 - resizeWorker.resizeColumn(event);
1351 - } else {
1352 - resizeWorker.setCurrentCell(event);
1353 - }
1354 - break;
1355 - case Event.ONMOUSEOUT:
1356 - // Unhighlight if the mouse leaves the table
1357 - Element toElem = DOM.eventGetToElement(event);
1358 - if (toElem == null || !dataWrapper.isOrHasChild(toElem)) {
1359 - // Check that the coordinates are not directly over the table
1360 - int clientX = event.getClientX() + Window.getScrollLeft();
1361 - int clientY = event.getClientY() + Window.getScrollTop();
1362 - int tableLeft = dataWrapper.getAbsoluteLeft();
1363 - int tableTop = dataWrapper.getAbsoluteTop();
1364 - int tableWidth = dataWrapper.getOffsetWidth();
1365 - int tableHeight = dataWrapper.getOffsetHeight();
1366 - int tableBottom = tableTop + tableHeight;
1367 - int tableRight = tableLeft + tableWidth;
1368 - if (clientX > tableLeft && clientX < tableRight && clientY > tableTop
1369 - && clientY < tableBottom) {
1370 - return;
1371 - }
1341 + public SortPolicy getSortPolicy()
1342 + {
1343 + return sortPolicy;
1344 + }
1345 +
1346 + /**
1347 + * Returns true if the specified column is sortable.
1348 + *
1349 + * @param column the column index
1350 + *
1351 + * @return true if the column is sortable, false if it is not sortable
1352 + */
1353 + public abstract boolean isColumnSortable( int column );
1354 +
1355 + /**
1356 + * Returns true if the specified column can be truncated. If it cannot be
1357 + * truncated, its minimum width will be adjusted to ensure the cell content is
1358 + * visible.
1359 + *
1360 + * @param column the column index
1361 + *
1362 + * @return true if the column is truncatable, false if it is not
1363 + */
1364 + public abstract boolean isColumnTruncatable( int column );
1365 +
1366 + /**
1367 + * Returns true if the specified column in the footer table can be truncated.
1368 + * If it cannot be truncated, its minimum width will be adjusted to ensure the
1369 + * cell content is visible.
1370 + *
1371 + * @param column the column index
1372 + *
1373 + * @return true if the column is truncatable, false if it is not
1374 + */
1375 + public abstract boolean isFooterColumnTruncatable( int column );
1372 1376
1373 - dataTable.highlightCell(null);
1377 + /**
1378 + * Returns true if the specified column in the header table can be truncated.
1379 + * If it cannot be truncated, its minimum width will be adjusted to ensure the
1380 + * cell content is visible.
1381 + *
1382 + * @param column the column index
1383 + *
1384 + * @return true if the column is truncatable, false if it is not
1385 + */
1386 + public abstract boolean isHeaderColumnTruncatable( int column );
1387 +
1388 + @Override
1389 + public void onBrowserEvent( Event event )
1390 + {
1391 + super.onBrowserEvent( event );
1392 + Element target = DOM.eventGetTarget( event );
1393 + switch ( DOM.eventGetType( event ) )
1394 + {
1395 + case Event.ONSCROLL:
1396 + // Reposition the tables on scroll
1397 + lastScrollLeft = dataWrapper.getScrollLeft();
1398 + scrollTables( false );
1399 + if ( dataWrapper.isOrHasChild( target ) )
1400 + {
1401 + fireEvent( new ScrollEvent( event ) );
1402 + }
1403 + break;
1404 +
1405 + case Event.ONMOUSEDOWN:
1406 + // Start resizing a header column
1407 + if ( DOM.eventGetButton( event ) != Event.BUTTON_LEFT )
1408 + {
1409 + return;
1410 + }
1411 + if ( resizeWorker.getCurrentCell() != null )
1412 + {
1413 + DOM.eventPreventDefault( event );
1414 + DOM.eventCancelBubble( event, true );
1415 + resizeWorker.startResizing( event );
1416 + }
1417 + break;
1418 + case Event.ONMOUSEUP:
1419 + if ( DOM.eventGetButton( event ) != Event.BUTTON_LEFT )
1420 + {
1421 + return;
1422 + }
1423 + // Stop resizing the header column
1424 + if ( resizeWorker.isResizing() )
1425 + {
1426 + resizeWorker.stopResizing( event );
1427 + }
1428 + else
1429 + {
1430 + // Scroll tables if needed
1431 + if ( DOM.isOrHasChild( headerWrapper, target ) )
1432 + {
1433 + scrollTables( true );
1434 + }
1435 + else
1436 + {
1437 + scrollTables( false );
1438 + }
1439 +
1440 + // Get the actual column index
1441 + Element cellElem = headerTable.getEventTargetCell( event );
1442 + if ( cellElem != null )
1443 + {
1444 + // Sorting is disabled
1445 + if ( sortPolicy == SortPolicy.DISABLED )
1446 + {
1447 + return;
1448 + }
1449 +
1450 + // Check the colSpan
1451 + int colSpan = cellElem.getPropertyInt( "colSpan" );
1452 + if ( colSpan > 1 && getSortPolicy() != SortPolicy.MULTI_CELL )
1453 + {
1454 + return;
1455 + }
1456 +
1457 + // Sort the column
1458 + sortedRowIndex = OverrideDOM.getRowIndex( DOM.getParent( cellElem ) ) - 1;
1459 + sortedCellIndex = OverrideDOM.getCellIndex( cellElem );
1460 + int column = headerTable.getColumnIndex( sortedRowIndex, sortedCellIndex ) - getHeaderOffset();
1461 + if ( column >= 0 && isColumnSortable( column ) )
1462 + {
1463 + if ( dataTable.getColumnCount() > column && onHeaderSort( sortedRowIndex, column ) )
1464 + {
1465 + dataTable.sortColumn( column );
1466 + }
1467 + }
1468 + }
1469 + }
1470 + break;
1471 + case Event.ONMOUSEMOVE:
1472 + // Resize the header column
1473 + if ( resizeWorker.isResizing() )
1474 + {
1475 + resizeWorker.resizeColumn( event );
1476 + }
1477 + else
1478 + {
1479 + resizeWorker.setCurrentCell( event );
1480 + }
1481 + break;
1482 + case Event.ONMOUSEOUT:
1483 + // Unhighlight if the mouse leaves the table
1484 + Element toElem = DOM.eventGetToElement( event );
1485 + if ( toElem == null || !dataWrapper.isOrHasChild( toElem ) )
1486 + {
1487 + // Check that the coordinates are not directly over the table
1488 + int clientX = event.getClientX() + Window.getScrollLeft();
1489 + int clientY = event.getClientY() + Window.getScrollTop();
1490 + int tableLeft = dataWrapper.getAbsoluteLeft();
1491 + int tableTop = dataWrapper.getAbsoluteTop();
1492 + int tableWidth = dataWrapper.getOffsetWidth();
1493 + int tableHeight = dataWrapper.getOffsetHeight();
1494 + int tableBottom = tableTop + tableHeight;
1495 + int tableRight = tableLeft + tableWidth;
1496 + if ( clientX > tableLeft && clientX < tableRight && clientY > tableTop && clientY < tableBottom )
1497 + {
1498 + return;
1499 + }
1500 +
1501 + dataTable.highlightCell( null );
1502 + }
1503 + break;
1374 1504 }
1375 - break;
1376 1505 }
1377 - }
1378 1506
1379 - /**
1380 - * This method is called when the dimensions of the parent element change.
1381 - * Subclasses should override this method as needed.
1382 - *
1383 - * @param width the new client width of the element
1384 - * @param height the new client height of the element
1385 - */
1386 - public void onResize(int width, int height) {
1387 - redraw();
1388 - }
1389 -
1390 - /**
1391 - * Redraw the table.
1392 - */
1393 - public void redraw() {
1394 - if (!isAttached()) {
1395 - return;
1396 - }
1397 -
1398 - // Create a command to execute while recalculating widths. Using this
1399 - // command prevents an extra browser layout by grouping read operations.
1400 - TableWidthInfo redrawInfo = new TableWidthInfo(false);
1401 - Command command = new Command() {
1402 - public void execute() {
1403 - // We update the ResizableWidgetCollection before changing the size of
1404 - // the ScrollTable, because change the size of the scroll table could
1405 - // require an additional layout (ex. if window scroll bars show up).
1406 - ResizableWidgetCollection.get().updateWidgetSize(
1407 - AbstractScrollTable.this);
1408 - }
1409 - };
1410 -
1411 - // Recalculate the ideal table widths of each column.
1412 - maybeRecalculateIdealColumnWidths(command);
1413 -
1414 - // Calculate the new widths of the columns
1415 - List<ColumnWidthInfo> colWidths = null;
1416 - if (resizePolicy == ResizePolicy.FILL_WIDTH) {
1417 - colWidths = getFillColumnWidths(redrawInfo);
1418 - } else {
1419 - colWidths = getBoundedColumnWidths(true);
1420 - }
1421 - applyNewColumnWidths(0, colWidths, true);
1422 -
1423 - // Update the overall height of the scroll table. This can only happen
1424 - // after the widths have been set because setting the width of cells can
1425 - // cause word wrap, which increases the height of the inner tables.
1426 - resizeTablesVertically();
1427 -
1428 - // Reset the scroll position, which might be lost when we change the layout.
1429 - scrollTables(false);
1430 - }
1431 -
1432 - /**
1433 - * Unsupported.
1434 - *
1435 - * @param child the widget to be removed
1436 - * @return false
1437 - * @throws UnsupportedOperationException
1438 - */
1439 - @Override
1440 - public boolean remove(Widget child) {
1441 - throw new UnsupportedOperationException(
1442 - "This panel does not support remove()");
1443 - }
1444 -
1445 - /**
1446 - * Reset the widths of all columns to their preferred sizes.
1447 - */
1448 - public void resetColumnWidths() {
1449 - applyNewColumnWidths(0, getBoundedColumnWidths(false), false);
1450 - scrollTables(false);
1451 - }
1452 -
1453 - /**
1454 - * Sets the amount of padding to be added around all cells.
1455 - *
1456 - * @param padding the cell padding, in pixels
1457 - */
1458 - public void setCellPadding(int padding) {
1459 - headerTable.setCellPadding(padding);
1460 - dataTable.setCellPadding(padding);
1461 - if (footerTable != null) {
1462 - footerTable.setCellPadding(padding);
1463 - }
1464 - redraw();
1465 - }
1466 -
1467 - /**
1468 - * Sets the amount of spacing to be added around all cells.
1469 - *
1470 - * @param spacing the cell spacing, in pixels
1471 - */
1472 - public void setCellSpacing(int spacing) {
1473 - headerTable.setCellSpacing(spacing);
1474 - dataTable.setCellSpacing(spacing);
1475 - if (footerTable != null) {
1476 - footerTable.setCellSpacing(spacing);
1477 - }
1478 - redraw();
1479 - }
1480 -
1481 - /**
1482 - * Set the resize policy applied to user actions that resize columns.
1483 - *
1484 - * @param columnResizePolicy the resize policy
1485 - */
1486 - public void setColumnResizePolicy(ColumnResizePolicy columnResizePolicy) {
1487 - this.columnResizePolicy = columnResizePolicy;
1488 - updateFillWidthImage();
1489 - }
1490 -
1491 - /**
1492 - * Set the width of a column.
1493 - *
1494 - * @param column the index of the column
1495 - * @param width the width in pixels
1496 - * @return the new column width
1497 - */
1498 - public int setColumnWidth(int column, int width) {
1499 - // Constrain the size of the column
1500 - ColumnWidthInfo info = getColumnWidthInfo(column);
1501 - if (info.hasMaximumWidth()) {
1502 - width = Math.min(width, info.getMaximumWidth());
1503 - }
1504 - if (info.hasMinimumWidth()) {
1505 - width = Math.max(width, info.getMinimumWidth());
1506 - }
1507 -
1508 - // Try to constrain the size of the grid
1509 - if (resizePolicy.isSacrificial()) {
1510 - // Get the sacrifice columns
1511 - int sacrificeColumn = column + 1;
1512 - int numColumns = dataTable.getColumnCount();
1513 - int remainingColumns = numColumns - sacrificeColumn;
1514 - List<ColumnWidthInfo> infos = getColumnWidthInfo(sacrificeColumn,
1515 - remainingColumns);
1516 -
1517 - // Distribute the width over the sacrifice columns
1518 - int diff = width - getColumnWidth(column);
1519 - int undistributed = columnResizer.distributeWidth(infos, -diff);
1520 -
1521 - // Set the new column widths
1522 - applyNewColumnWidths(sacrificeColumn, infos, false);
1523 -
1524 - // Prevent over resizing
1525 - if (resizePolicy.isFixedWidth()) {
1526 - width += undistributed;
1527 - }
1528 - }
1529 -
1530 - // Resize the column
1531 - int offset = getHeaderOffset();
1532 - dataTable.setColumnWidth(column, width);
1533 - headerTable.setColumnWidth(column + offset, width);
1534 - if (footerTable != null) {
1535 - footerTable.setColumnWidth(column + offset, width);
1536 - }
1537 -
1538 - // Reposition things as needed
1539 - impl.repositionSpacer(this, false);
1540 - resizeTablesVertically();
1541 - scrollTables(false);
1542 - return width;
1543 - }
1544 -
1545 - /**
1546 - * Set the footer table that appears under the data table. If set to null, the
1547 - * footer table will not be shown.
1548 - *
1549 - * @param footerTable the table to use in the footer
1550 - */
1551 - public void setFooterTable(FixedWidthFlexTable footerTable) {
1552 - // Disown the old footer table
1553 - if (this.footerTable != null) {
1554 - super.remove(this.footerTable);
1555 - DOM.removeChild(absoluteElem, footerWrapper);
1556 - }
1557 -
1558 - // Set the new footer table
1559 - this.footerTable = footerTable;
1560 - if (footerTable != null) {
1561 - footerTable.setCellSpacing(getCellSpacing());
1562 - footerTable.setCellPadding(getCellPadding());
1563 - prepareTable(footerTable, "footerTable");
1564 - if (dataTable.getSelectionPolicy().hasInputColumn()) {
1565 - footerTable.setColumnWidth(0, dataTable.getInputColumnWidth());
1566 - }
1567 -
1568 - // Create the footer wrapper and spacer
1569 - if (footerWrapper == null) {
1570 - footerWrapper = createWrapper("footerWrapper");
1571 - footerSpacer = impl.createSpacer(footerTable, footerWrapper);
1572 - DOM.setEventListener(footerWrapper, this);
1573 - DOM.sinkEvents(footerWrapper, Event.ONMOUSEUP);
1574 - }
1575 -
1576 - // Adopt the header table into the panel
1577 - adoptTable(footerTable, footerWrapper,
1578 - absoluteElem.getChildNodes().getLength());
1579 - }
1580 - redraw();
1581 - }
1582 -
1583 - @Override
1584 - public void setHeight(String height) {
1585 - this.lastHeight = height;
1586 - super.setHeight(height);
1587 - resizeTablesVertically();
1588 - }
1589 -
1590 - /**
1591 - * Set the resize policy of the table.
1592 - *
1593 - * @param resizePolicy the resize policy
1594 - */
1595 - public void setResizePolicy(ResizePolicy resizePolicy) {
1596 - this.resizePolicy = resizePolicy;
1597 - updateFillWidthImage();
1598 - redraw();
1599 - }
1600 -
1601 - /**
1602 - * Set the scroll policy of the table.
1603 - *
1604 - * @param scrollPolicy the new scroll policy
1605 - */
1606 - public void setScrollPolicy(ScrollPolicy scrollPolicy) {
1607 - if (scrollPolicy == this.scrollPolicy) {
1608 - return;
1609 - }
1610 - this.scrollPolicy = scrollPolicy;
1611 -
1612 - // Clear the heights of the wrappers
1613 - headerWrapper.getStyle().setProperty("height", "");
1614 - dataWrapper.getStyle().setProperty("height", "");
1615 - if (footerWrapper != null) {
1616 - footerWrapper.getStyle().setProperty("height", "");
1617 - }
1618 -
1619 - if (scrollPolicy == ScrollPolicy.DISABLED) {
1620 - // Disabled scroll bars
1621 - dataWrapper.getStyle().setProperty("height", "auto");
1622 - dataWrapper.getStyle().setProperty("overflow", "");
1623 - } else if (scrollPolicy == ScrollPolicy.HORIZONTAL) {
1624 - // Only show horizontal scroll bar
1625 - dataWrapper.getStyle().setProperty("height", "auto");
1626 - dataWrapper.getStyle().setProperty("overflow", "auto");
1627 - } else if (scrollPolicy == ScrollPolicy.BOTH) {
1628 - // Show both scroll bars
1629 - if (lastHeight != null) {
1630 - super.setHeight(lastHeight);
1631 - } else {
1632 - super.setHeight("");
1633 - }
1634 - dataWrapper.getStyle().setProperty("overflow", "auto");
1635 - }
1636 -
1637 - // Resize the tables
1638 - impl.repositionSpacer(this, true);
1639 - redraw();
1640 - }
1641 -
1642 - /**
1643 - * Set the {@link SortPolicy} that defines what columns users can sort.
1644 - *
1645 - * @param sortPolicy the {@link SortPolicy}
1646 - */
1647 - public void setSortPolicy(SortPolicy sortPolicy) {
1648 - this.sortPolicy = sortPolicy;
1649 -
1650 - // Remove the sorted indicator image
1651 - applySortedColumnIndicator(null, true);
1652 - }
1653 -
1654 - /**
1655 - * Apply the sorted column indicator to a specific table cell in the header
1656 - * table.
1657 - *
1658 - * @param tdElem the cell in the header table, or null to remove it
1659 - * @param ascending true to apply the ascending indicator, false for
1660 - * descending
1661 - */
1662 - protected void applySortedColumnIndicator(Element tdElem, boolean ascending) {
1663 - // Remove the sort indicator
1664 - if (tdElem == null) {
1665 - Element parent = DOM.getParent(sortedColumnWrapper);
1666 - if (parent != null) {
1667 - parent.removeChild(sortedColumnWrapper);
1668 - headerTable.clearIdealWidths();
1669 - }
1670 - return;
1507 + /**
1508 + * This method is called when the dimensions of the parent element change.
1509 + * Subclasses should override this method as needed.
1510 + *
1511 + * @param width the new client width of the element
1512 + * @param height the new client height of the element
1513 + */
1514 + public void onResize( int width, int height )
1515 + {
1516 + redraw();
1671 1517 }
1672 1518
1673 - tdElem.appendChild(sortedColumnWrapper);
1674 - if (ascending) {
1675 - sortedColumnWrapper.setInnerHTML("&nbsp;"
1676 - + images.scrollTableAscending().getHTML());
1677 - } else {
1678 - sortedColumnWrapper.setInnerHTML("&nbsp;"
1679 - + images.scrollTableDescending().getHTML());
1680 - }
1681 - sortedRowIndex = -1;
1682 - sortedCellIndex = -1;
1683 -
1684 - // The column with the indicator now has a new ideal width
1685 - headerTable.clearIdealWidths();
1686 - redraw();
1687 - }
1688 -
1689 - /**
1690 - * Create a wrapper element that will hold a table.
1691 - *
1692 - * @param cssName the style name added to the base name
1693 - * @return a new wrapper element
1694 - */
1695 - protected Element createWrapper(String cssName) {
1696 - Element wrapper = DOM.createDiv();
1697 - wrapper.getStyle().setProperty("width", "100%");
1698 - wrapper.getStyle().setProperty("overflow", "hidden");
1699 - wrapper.getStyle().setPropertyPx("padding", 0);
1700 - wrapper.getStyle().setPropertyPx("margin", 0);
1701 - wrapper.getStyle().setPropertyPx("border", 0);
1702 - if (cssName != null) {
1703 - setStyleName(wrapper, cssName);
1704 - }
1705 - return wrapper;
1706 - }
1707 -
1708 - /**
1709 - * @return the wrapper element around the data table
1710 - */
1711 - protected Element getDataWrapper() {
1712 - return dataWrapper;
1713 - }
1714 -
1715 - /**
1716 - * Extend the columns to exactly fill the available space, if the current
1717 - * {@link ResizePolicy} requires it.
1718 - *
1719 - * @deprecated use {@link #redraw()} instead
1720 - */
1721 - @Deprecated
1722 - protected void maybeFillWidth() {
1723 - redraw();
1724 - }
1725 -
1726 - /**
1727 - * Called just before a column is sorted because of a user click on the header
1728 - * row.
1729 - *
1730 - * @param row the row index that was clicked
1731 - * @param column the column index that was clicked
1732 - * @return true to sort, false to ignore
1733 - */
1734 - protected boolean onHeaderSort(int row, int column) {
1735 - return true;
1736 - }
1737 -
1738 - @Override
1739 - protected void onLoad() {
1740 - ResizableWidgetCollection.get().add(this);
1741 - redraw();
1742 - }
1743 -
1744 - @Override
1745 - protected void onUnload() {
1746 - ResizableWidgetCollection.get().remove(this);
1747 - }
1748 -
1749 - /**
1750 - * Fixes the table heights so the header is visible and the data takes up the
1751 - * remaining vertical space.
1752 - */
1753 - protected void resizeTablesVertically() {
1754 - if (scrollPolicy == ScrollPolicy.DISABLED) {
1755 - dataWrapper.getStyle().setProperty("overflow", "auto");
1756 - dataWrapper.getStyle().setProperty("overflow", "");
1757 - int height = Math.max(1, absoluteElem.getOffsetHeight());
1758 - super.setHeight(height + "px");
1759 - } else if (scrollPolicy == ScrollPolicy.HORIZONTAL) {
1760 - dataWrapper.getStyle().setProperty("overflow", "hidden");
1761 - dataWrapper.getStyle().setProperty("overflow", "auto");
1762 - int height = Math.max(1, absoluteElem.getOffsetHeight());
1763 - super.setHeight(height + "px");
1764 - } else {
1765 - applyTableWrapperSizes(getTableWrapperSizes());
1766 - dataWrapper.getStyle().setProperty("width", "100%");
1767 - }
1768 - }
1769 -
1770 - /**
1771 - * Helper method that actually performs the vertical resizing.
1772 - *
1773 - * @deprecated use {@link #redraw()} instead
1774 - */
1775 - @Deprecated
1776 - protected void resizeTablesVerticallyNow() {
1777 - redraw();
1778 - }
1779 -
1780 - /**
1781 - * Sets the scroll property of the header and footers wrappers when scrolling
1782 - * so that the header, footer, and data tables line up.
1783 - *
1784 - * @param baseHeader true to scroll the data table as well
1785 - */
1786 - protected void scrollTables(boolean baseHeader) {
1787 - if (scrollPolicy == ScrollPolicy.DISABLED) {
1788 - return;
1789 - }
1790 -
1791 - if (lastScrollLeft >= 0) {
1792 - headerWrapper.setScrollLeft(lastScrollLeft);
1793 - if (baseHeader) {
1794 - dataWrapper.setScrollLeft(lastScrollLeft);
1795 - }
1796 - if (footerWrapper != null) {
1797 - footerWrapper.setScrollLeft(lastScrollLeft);
1798 - }
1799 - }
1800 - }
1801 -
1802 - /**
1803 - * @return the absolutely positioned wrapper element
1804 - */
1805 - Element getAbsoluteElement() {
1806 - return absoluteElem;
1807 - }
1808 -
1809 - /**
1810 - * Adopt a table into this {@link AbstractScrollTable} within its wrapper.
1811 - *
1812 - * @param table the table to adopt
1813 - * @param wrapper the wrapper element
1814 - * @param index the index to insert the wrapper in the main element
1815 - */
1816 - private void adoptTable(Widget table, Element wrapper, int index) {
1817 - DOM.insertChild(absoluteElem, wrapper, index);
1818 - add(table, wrapper);
1819 - }
1820 -
1821 - /**
1822 - * Apply the new widths to a list of columns.
1823 - *
1824 - * @param startIndex the index of the first column
1825 - * @param infos the new column width info
1826 - * @param forced if false, only set column widths that have changed
1827 - */
1828 - private void applyNewColumnWidths(int startIndex,
1829 - List<ColumnWidthInfo> infos, boolean forced) {
1830 - // Infos can be null if the widths cannot be calculated
1831 - if (infos == null) {
1832 - return;
1833 - }
1834 -
1835 - int offset = getHeaderOffset();
1836 - int numColumns = infos.size();
1837 - for (int i = 0; i < numColumns; i++) {
1838 - ColumnWidthInfo info = infos.get(i);
1839 - int newWidth = info.getNewWidth();
1840 - if (forced || info.getCurrentWidth() != newWidth) {
1841 - dataTable.setColumnWidth(startIndex + i, newWidth);
1842 - headerTable.setColumnWidth(startIndex + i + offset, newWidth);
1843 - if (footerTable != null) {
1844 - footerTable.setColumnWidth(startIndex + i + offset, newWidth);
1845 - }
1846 - }
1847 - }
1848 - impl.repositionSpacer(this, false);
1849 - }
1850 -
1851 - /**
1852 - * Apply the new sizes to the table wrappers.
1853 - *
1854 - * @param sizes the sizes to apply
1855 - */
1856 - private void applyTableWrapperSizes(TableHeightInfo sizes) {
1857 - if (sizes == null) {
1858 - return;
1859 - }
1860 -
1861 - headerWrapper.getStyle().setPropertyPx("height", sizes.headerTableHeight);
1862 - if (footerWrapper != null) {
1863 - footerWrapper.getStyle().setPropertyPx("height", sizes.footerTableHeight);
1864 - }
1865 - dataWrapper.getStyle().setPropertyPx("height",
1866 - Math.max(sizes.dataTableHeight, 0));
1867 - dataWrapper.getStyle().setProperty("overflow", "hidden");
1868 - dataWrapper.getStyle().setProperty("overflow", "auto");
1869 - }
1870 -
1871 - /**
1872 - * Get the width available for the tables.
1873 - *
1874 - * @return the available width, or -1 if not defined
1875 - */
1876 - private int getAvailableWidth() {
1877 - int clientWidth = absoluteElem.getPropertyInt("clientWidth");
1878 - if (scrollPolicy == ScrollPolicy.BOTH) {
1879 - int scrollbarWidth = mockScrollable.getOffsetWidth()
1880 - - mockScrollable.getPropertyInt("clientWidth");
1881 - clientWidth = absoluteElem.getPropertyInt("clientWidth") - scrollbarWidth
1882 - - 1;
1883 - }
1884 - return Math.max(clientWidth, -1);
1885 - }
1886 -
1887 - /**
1888 - * Get the widths of all columns, either to their preferred sizes or just
1889 - * ensure that they are within their min/max boundaries.
1890 - *
1891 - * @param boundsOnly true to only ensure the widths are within the bounds
1892 - * @return the column widths
1893 - */
1894 - private List<ColumnWidthInfo> getBoundedColumnWidths(boolean boundsOnly) {
1895 - if (!isAttached()) {
1896 - return null;
1897 - }
1898 -
1899 - // Calculate the new column widths
1900 - int numColumns = dataTable.getColumnCount();
1901 - int totalWidth = 0;
1902 - List<ColumnWidthInfo> colWidthInfos = getColumnWidthInfo(0, numColumns);
1903 -
1904 - // If we are reseting to original widths, set all widths to 0
1905 - if (!boundsOnly) {
1906 - for (ColumnWidthInfo info : colWidthInfos) {
1907 - totalWidth += info.getCurrentWidth();
1908 - info.setCurrentWidth(0);
1909 - }
1910 - }
1911 -
1912 - // Run the resize algorithm
1913 - columnResizer.distributeWidth(colWidthInfos, totalWidth);
1914 -
1915 - // Set the new column widths
1916 - return colWidthInfos;
1917 - }
1918 -
1919 - /**
1920 - * Get info about the width of a column.
1921 - *
1922 - * @param column the column index
1923 - * @return the info about the column width
1924 - */
1925 - private ColumnWidthInfo getColumnWidthInfo(int column) {
1926 - int minWidth = getMinimumColumnWidth(column);
1927 - int maxWidth = getMaximumColumnWidth(column);
1928 - int preferredWidth = getPreferredColumnWidth(column);
1929 - int curWidth = getColumnWidth(column);
1930 -
1931 - // Adjust the widths if the columns are not truncatable, up to maxWidth
1932 - if (!isColumnTruncatable(column)) {
1933 - maybeRecalculateIdealColumnWidths(null);
1934 - int idealWidth = getDataTable().getIdealColumnWidth(column);
1935 - if (maxWidth != MaximumWidthProperty.NO_MAXIMUM_WIDTH) {
1936 - idealWidth = Math.min(idealWidth, maxWidth);
1937 - }
1938 - minWidth = Math.max(minWidth, idealWidth);
1939 - }
1940 - if (!isHeaderColumnTruncatable(column)) {
1941 - maybeRecalculateIdealColumnWidths(null);
1942 - int idealWidth = getHeaderTable().getIdealColumnWidth(
1943 - column + getHeaderOffset());
1944 - if (maxWidth != MaximumWidthProperty.NO_MAXIMUM_WIDTH) {
1945 - idealWidth = Math.min(idealWidth, maxWidth);
1946 - }
1947 - minWidth = Math.max(minWidth, idealWidth);
1948 - }
1949 - if (footerTable != null && !isFooterColumnTruncatable(column)) {
1950 - maybeRecalculateIdealColumnWidths(null);
1951 - int idealWidth = getFooterTable().getIdealColumnWidth(
1952 - column + getHeaderOffset());
1953 - if (maxWidth != MaximumWidthProperty.NO_MAXIMUM_WIDTH) {
1954 - idealWidth = Math.min(idealWidth, maxWidth);
1955 - }
1956 - minWidth = Math.max(minWidth, idealWidth);
1957 - }
1958 -
1959 - return new ColumnWidthInfo(minWidth, maxWidth, preferredWidth, curWidth);
1960 - }
1961 -
1962 - /**
1963 - * Get info about the width of multiple columns.
1964 - *
1965 - * @param column the start column index
1966 - * @param numColumns the number of columns
1967 - * @return the info about the column widths of the columns
1968 - */
1969 - private List<ColumnWidthInfo> getColumnWidthInfo(int column, int numColumns) {
1970 - List<ColumnWidthInfo> infos = new ArrayList<ColumnWidthInfo>();
1971 - for (int i = 0; i < numColumns; i++) {
1972 - infos.add(getColumnWidthInfo(column + i));
1973 - }
1974 - return infos;
1975 - }
1976 -
1977 - /**
1978 - * Get the column widths needed to fill with available ScrollTable width.
1979 - *
1980 - * @param info the optional precomputed sizes
1981 - * @return the column widths
1982 - */
1983 - private List<ColumnWidthInfo> getFillColumnWidths(TableWidthInfo info) {
1984 - if (!isAttached()) {
1985 - return null;
1986 - }
1987 -
1988 - // Precompute some sizes
1989 - if (info == null) {
1990 - info = new TableWidthInfo(false);
1991 - }
1992 -
1993 - // Calculate how much room we have to work with
1994 - int clientWidth = info.availableWidth;
1995 - if (clientWidth <= 0) {
1996 - return null;
1997 - }
1998 -
1999 - // Calculate the difference and number of column to resize
2000 - int diff = 0;
2001 - int numColumns = 0;
2002 - {
2003 - // Calculate the number of columns in each table
2004 - int numHeaderCols = 0;
2005 - int numDataCols = 0;
2006 - int numFooterCols = 0;
2007 - if (info.headerTableWidth > 0) {
2008 - numHeaderCols = headerTable.getColumnCount() - getHeaderOffset();
2009 - }
2010 - if (info.dataTableWidth > 0) {
2011 - numDataCols = dataTable.getColumnCount();
2012 - }
2013 - if (footerTable != null && info.footerTableWidth > 0) {
2014 - numFooterCols = footerTable.getColumnCount() - getHeaderOffset();
2015 - }
2016 -
2017 - // Determine the largest table
2018 - if (numHeaderCols >= numDataCols && numHeaderCols >= numFooterCols) {
2019 - numColumns = numHeaderCols;
2020 - diff = clientWidth - info.headerTableWidth;
2021 - } else if (numFooterCols >= numDataCols && numFooterCols >= numHeaderCols) {
2022 - numColumns = numFooterCols;
2023 - diff = clientWidth - info.footerTableWidth;
2024 - } else if (numDataCols > 0) {
2025 - numColumns = numDataCols;
2026 - diff = clientWidth - info.dataTableWidth;
2027 - }
2028 - }
2029 - if (numColumns <= 0) {
2030 - return null;
2031 - }
2032 -
2033 - // Calculate the new column widths
2034 - List<ColumnWidthInfo> colWidthInfos = getColumnWidthInfo(0, numColumns);
2035 - columnResizer.distributeWidth(colWidthInfos, diff);
2036 - return colWidthInfos;
2037 - }
2038 -
2039 - /**
2040 - * Get the offset between the data and header and footer tables. An offset of
2041 - * one means that the header and footer table indexes are one greater than the
2042 - * data table indexes, probably because the data table contains a checkbox
2043 - * column.
2044 - *
2045 - * @return the offset
2046 - */
2047 - private int getHeaderOffset() {
2048 - if (dataTable.getSelectionPolicy().hasInputColumn()) {
2049 - return 1;
2050 - }
2051 - return 0;
2052 - }
2053 -
2054 - /**
2055 - * Returns the new heights of the header, data, and footer tables based on the
2056 - * {@link ScrollPolicy}.
2057 - *
2058 - * @return the new table heights, or null
2059 - */
2060 - private TableHeightInfo getTableWrapperSizes() {
2061 - // If we aren't attached, return immediately
2062 - if (!isAttached()) {
2063 - return null;
2064 - }
2065 -
2066 - // Heights only apply with vertical scrolling
2067 - if (scrollPolicy == ScrollPolicy.DISABLED
2068 - || scrollPolicy == ScrollPolicy.HORIZONTAL) {
2069 - return null;
2070 - }
2071 -
2072 - // Give the data wrapper all remaining height
2073 - return new TableHeightInfo();
2074 - }
2075 -
2076 - /**
2077 - * Recalculate the ideal columns widths of all inner tables.
2078 - *
2079 - * @param command an optional command to execute while recalculating
2080 - */
2081 - private void maybeRecalculateIdealColumnWidths(Command command) {
2082 - // Calculations require that we are attached
2083 - if (!isAttached()) {
2084 - return;
2085 - }
2086 -
2087 - // Check if a recalculation is needed.
2088 - if (headerTable.isIdealColumnWidthsCalculated()
2089 - && dataTable.isIdealColumnWidthsCalculated()
2090 - && (footerTable == null || footerTable.isIdealColumnWidthsCalculated())) {
2091 - if (command != null) {
2092 - command.execute();
2093 - }
2094 - return;
2095 - }
2096 -
2097 - impl.recalculateIdealColumnWidths(this, command);
2098 - }
2099 -
2100 - /**
2101 - * Prepare a table to be added to the {@link AbstractScrollTable}.
2102 - *
2103 - * @param table the table to prepare
2104 - * @param cssName the style name added to the base name
2105 - */
2106 - private void prepareTable(Widget table, String cssName) {
2107 - Element tableElem = table.getElement();
2108 - DOM.setStyleAttribute(tableElem, "margin", "0px");
2109 - DOM.setStyleAttribute(tableElem, "border", "0px");
2110 - table.addStyleName(cssName);
2111 - }
2112 -
2113 - /**
2114 - * Show or hide to fillWidthImage depending on current policies.
2115 - */
2116 - private void updateFillWidthImage() {
2117 - if (columnResizePolicy == ColumnResizePolicy.DISABLED
2118 - || resizePolicy.isFixedWidth()) {
2119 - fillWidthImage.setVisible(false);
2120 - } else {
2121 - fillWidthImage.setVisible(true);
1519 + /**
1520 + * Redraw the table.
1521 + */
1522 + public void redraw()
1523 + {
1524 + if ( !isAttached() )
1525 + {
1526 + return;
1527 + }
1528 +
1529 + // Create a command to execute while recalculating widths. Using this
1530 + // command prevents an extra browser layout by grouping read operations.
1531 + TableWidthInfo redrawInfo = new TableWidthInfo( false );
1532 + Command command = new Command()
1533 + {
1534 + public void execute()
1535 + {
1536 + // We update the ResizableWidgetCollection before changing the size of
1537 + // the ScrollTable, because change the size of the scroll table could
1538 + // require an additional layout (ex. if window scroll bars show up).
1539 + ResizableWidgetCollection.get().updateWidgetSize( AbstractScrollTable.this );
1540 + }
1541 + };
1542 +
1543 + // Recalculate the ideal table widths of each column.
1544 + maybeRecalculateIdealColumnWidths( command );
1545 +
1546 + // Calculate the new widths of the columns
1547 + List<ColumnWidthInfo> colWidths = null;
1548 + if ( resizePolicy == ResizePolicy.FILL_WIDTH )
1549 + {
1550 + colWidths = getFillColumnWidths( redrawInfo );
1551 + }
1552 + else
1553 + {
1554 + colWidths = getBoundedColumnWidths( true );
1555 + }
1556 + applyNewColumnWidths( 0, colWidths, true );
1557 +
1558 + // Update the overall height of the scroll table. This can only happen
1559 + // after the widths have been set because setting the width of cells can
1560 + // cause word wrap, which increases the height of the inner tables.
1561 + resizeTablesVertically();
1562 +
1563 + // Reset the scroll position, which might be lost when we change the layout.
1564 + scrollTables( false );
1565 + }
1566 +
1567 + /**
1568 + * Unsupported.
1569 + *
1570 + * @param child the widget to be removed
1571 + *
1572 + * @return false
1573 + *
1574 + * @throws UnsupportedOperationException
1575 + */
1576 + @Override
1577 + public boolean remove( Widget child )
1578 + {
1579 + throw new UnsupportedOperationException( "This panel does not support remove()" );
1580 + }
1581 +
1582 + /**
1583 + * Reset the widths of all columns to their preferred sizes.
1584 + */
1585 + public void resetColumnWidths()
1586 + {
1587 + applyNewColumnWidths( 0, getBoundedColumnWidths( false ), false );
1588 + scrollTables( false );
1589 + }
1590 +
1591 + /**
1592 + * Sets the amount of padding to be added around all cells.
1593 + *
1594 + * @param padding the cell padding, in pixels
1595 + */
1596 + public void setCellPadding( int padding )
1597 + {
1598 + headerTable.setCellPadding( padding );
1599 + dataTable.setCellPadding( padding );
1600 + if ( footerTable != null )
1601 + {
1602 + footerTable.setCellPadding( padding );
1603 + }
1604 + redraw();
1605 + }
1606 +
1607 + /**
1608 + * Sets the amount of spacing to be added around all cells.
1609 + *
1610 + * @param spacing the cell spacing, in pixels
1611 + */
1612 + public void setCellSpacing( int spacing )
1613 + {
1614 + headerTable.setCellSpacing( spacing );
1615 + dataTable.setCellSpacing( spacing );
1616 + if ( footerTable != null )
1617 + {
1618 + footerTable.setCellSpacing( spacing );
1619 + }
1620 + redraw();
1621 + }
1622 +
1623 + /**
1624 + * Set the resize policy applied to user actions that resize columns.
1625 + *
1626 + * @param columnResizePolicy the resize policy
1627 + */
1628 + public void setColumnResizePolicy( ColumnResizePolicy columnResizePolicy )
1629 + {
1630 + this.columnResizePolicy = columnResizePolicy;
1631 + updateFillWidthImage();
1632 + }
1633 +
1634 + /**
1635 + * Set the width of a column.
1636 + *
1637 + * @param column the index of the column
1638 + * @param width the width in pixels
1639 + *
1640 + * @return the new column width
1641 + */
1642 + public int setColumnWidth( int column, int width )
1643 + {
1644 + // Constrain the size of the column
1645 + ColumnWidthInfo info = getColumnWidthInfo( column );
1646 + if ( info.hasMaximumWidth() )
1647 + {
1648 + width = Math.min( width, info.getMaximumWidth() );
1649 + }
1650 + if ( info.hasMinimumWidth() )
1651 + {
1652 + width = Math.max( width, info.getMinimumWidth() );
1653 + }
1654 +
1655 + // Try to constrain the size of the grid
1656 + if ( resizePolicy.isSacrificial() )
1657 + {
1658 + // Get the sacrifice columns
1659 + int sacrificeColumn = column + 1;
1660 + int numColumns = dataTable.getColumnCount();
1661 + int remainingColumns = numColumns - sacrificeColumn;
1662 + List<ColumnWidthInfo> infos = getColumnWidthInfo( sacrificeColumn, remainingColumns );
1663 +
1664 + // Distribute the width over the sacrifice columns
1665 + int diff = width - getColumnWidth( column );
1666 + int undistributed = columnResizer.distributeWidth( infos, -diff );
1667 +
1668 + // Set the new column widths
1669 + applyNewColumnWidths( sacrificeColumn, infos, false );
1670 +
1671 + // Prevent over resizing
1672 + if ( resizePolicy.isFixedWidth() )
1673 + {
1674 + width += undistributed;
1675 + }
1676 + }
1677 +
1678 + // Resize the column
1679 + int offset = getHeaderOffset();
1680 + dataTable.setColumnWidth( column, width );
1681 + headerTable.setColumnWidth( column + offset, width );
1682 + if ( footerTable != null )
1683 + {
1684 + footerTable.setColumnWidth( column + offset, width );
1685 + }
1686 +
1687 + // Reposition things as needed
1688 + impl.repositionSpacer( this, false );
1689 + resizeTablesVertically();
1690 + scrollTables( false );
1691 + return width;
1692 + }
1693 +
1694 + /**
1695 + * Set the footer table that appears under the data table. If set to null, the
1696 + * footer table will not be shown.
1697 + *
1698 + * @param footerTable the table to use in the footer
1699 + */
1700 + public void setFooterTable( FixedWidthFlexTable footerTable )
1701 + {
1702 + // Disown the old footer table
1703 + if ( this.footerTable != null )
1704 + {
1705 + super.remove( this.footerTable );
1706 + DOM.removeChild( absoluteElem, footerWrapper );
1707 + }
1708 +
1709 + // Set the new footer table
1710 + this.footerTable = footerTable;
1711 + if ( footerTable != null )
1712 + {
1713 + footerTable.setCellSpacing( getCellSpacing() );
1714 + footerTable.setCellPadding( getCellPadding() );
1715 + prepareTable( footerTable, "footerTable" );
1716 + if ( dataTable.getSelectionPolicy().hasInputColumn() )
1717 + {
1718 + footerTable.setColumnWidth( 0, dataTable.getInputColumnWidth() );
1719 + }
1720 +
1721 + // Create the footer wrapper and spacer
1722 + if ( footerWrapper == null )
1723 + {
1724 + footerWrapper = createWrapper( "footerWrapper" );
1725 + footerSpacer = impl.createSpacer( footerTable, footerWrapper );
1726 + DOM.setEventListener( footerWrapper, this );
1727 + DOM.sinkEvents( footerWrapper, Event.ONMOUSEUP );
1728 + }
1729 +
1730 + // Adopt the header table into the panel
1731 + adoptTable( footerTable, footerWrapper, absoluteElem.getChildNodes().getLength() );
1732 + }
1733 + redraw();
1734 + }
1735 +
1736 + @Override
1737 + public void setHeight( String height )
1738 + {
1739 + this.lastHeight = height;
1740 + super.setHeight( height );
1741 + resizeTablesVertically();
1742 + }
1743 +
1744 + /**
1745 + * Set the resize policy of the table.
1746 + *
1747 + * @param resizePolicy the resize policy
1748 + */
1749 + public void setResizePolicy( ResizePolicy resizePolicy )
1750 + {
1751 + this.resizePolicy = resizePolicy;
1752 + updateFillWidthImage();
1753 + redraw();
1754 + }
1755 +
1756 + /**
1757 + * Set the scroll policy of the table.
1758 + *
1759 + * @param scrollPolicy the new scroll policy
1760 + */
1761 + public void setScrollPolicy( ScrollPolicy scrollPolicy )
1762 + {
1763 + if ( scrollPolicy == this.scrollPolicy )
1764 + {
1765 + return;
1766 + }
1767 + this.scrollPolicy = scrollPolicy;
1768 +
1769 + // Clear the heights of the wrappers
1770 + headerWrapper.getStyle().setProperty( "height", "" );
1771 + dataWrapper.getStyle().setProperty( "height", "" );
1772 + if ( footerWrapper != null )
1773 + {
1774 + footerWrapper.getStyle().setProperty( "height", "" );
1775 + }
1776 +
1777 + if ( scrollPolicy == ScrollPolicy.DISABLED )
1778 + {
1779 + // Disabled scroll bars
1780 + dataWrapper.getStyle().setProperty( "height", "auto" );
1781 + dataWrapper.getStyle().setProperty( "overflow", "" );
1782 + }
1783 + else if ( scrollPolicy == ScrollPolicy.HORIZONTAL )
1784 + {
1785 + // Only show horizontal scroll bar
1786 + dataWrapper.getStyle().setProperty( "height", "auto" );
1787 + dataWrapper.getStyle().setProperty( "overflow", "auto" );
1788 + }
1789 + else if ( scrollPolicy == ScrollPolicy.BOTH )
1790 + {
1791 + // Show both scroll bars
1792 + if ( lastHeight != null )
1793 + {
1794 + super.setHeight( lastHeight );
1795 + }
1796 + else
1797 + {
1798 + super.setHeight( "" );
1799 + }
1800 + dataWrapper.getStyle().setProperty( "overflow", "auto" );
1801 + }
1802 +
1803 + // Resize the tables
1804 + impl.repositionSpacer( this, true );
1805 + redraw();
1806 + }
1807 +
1808 + /**
1809 + * Set the {@link SortPolicy} that defines what columns users can sort.
1810 + *
1811 + * @param sortPolicy the {@link SortPolicy}
1812 + */
1813 + public void setSortPolicy( SortPolicy sortPolicy )
1814 + {
1815 + this.sortPolicy = sortPolicy;
1816 +
1817 + // Remove the sorted indicator image
1818 + applySortedColumnIndicator( null, true );
1819 + }
1820 +
1821 + /**
1822 + * Apply the sorted column indicator to a specific table cell in the header
1823 + * table.
1824 + *
1825 + * @param tdElem the cell in the header table, or null to remove it
1826 + * @param ascending true to apply the ascending indicator, false for
1827 + * descending
1828 + */
1829 + protected void applySortedColumnIndicator( Element tdElem, boolean ascending )
1830 + {
1831 + // Remove the sort indicator
1832 + if ( tdElem == null )
1833 + {
1834 + Element parent = DOM.getParent( sortedColumnWrapper );
1835 + if ( parent != null )
1836 + {
1837 + parent.removeChild( sortedColumnWrapper );
1838 + headerTable.clearIdealWidths();
1839 + }
1840 + return;
1841 + }
1842 +
1843 + tdElem.appendChild( sortedColumnWrapper );
1844 + if ( ascending )
1845 + {
1846 + sortedColumnWrapper.setInnerHTML( "&nbsp;" + images.scrollTableAscending().getHTML() );
1847 + }
1848 + else
1849 + {
1850 + sortedColumnWrapper.setInnerHTML( "&nbsp;" + images.scrollTableDescending().getHTML() );
1851 + }
1852 + sortedRowIndex = -1;
1853 + sortedCellIndex = -1;
1854 +
1855 + // The column with the indicator now has a new ideal width
1856 + headerTable.clearIdealWidths();
1857 + redraw();
1858 + }
1859 +
1860 + /**
1861 + * Create a wrapper element that will hold a table.
1862 + *
1863 + * @param cssName the style name added to the base name
1864 + *
1865 + * @return a new wrapper element
1866 + */
1867 + protected Element createWrapper( String cssName )
1868 + {
1869 + Element wrapper = DOM.createDiv();
1870 + wrapper.getStyle().setProperty( "width", "100%" );
1871 + wrapper.getStyle().setProperty( "overflow", "hidden" );
1872 + wrapper.getStyle().setPropertyPx( "padding", 0 );
1873 + wrapper.getStyle().setPropertyPx( "margin", 0 );
1874 + wrapper.getStyle().setPropertyPx( "border", 0 );
1875 + if ( cssName != null )
1876 + {
1877 + setStyleName( wrapper, cssName );
1878 + }
1879 + return wrapper;
1880 + }
1881 +
1882 + /**
1883 + * @return the wrapper element around the data table
1884 + */
1885 + protected Element getDataWrapper()
1886 + {
1887 + return dataWrapper;
1888 + }
1889 +
1890 + /**
1891 + * Extend the columns to exactly fill the available space, if the current
1892 + * {@link ResizePolicy} requires it.
1893 + *
1894 + * @deprecated use {@link #redraw()} instead
1895 + */
1896 + @Deprecated
1897 + protected void maybeFillWidth()
1898 + {
1899 + redraw();
1900 + }
1901 +
1902 + /**
1903 + * Called just before a column is sorted because of a user click on the header
1904 + * row.
1905 + *
1906 + * @param row the row index that was clicked
1907 + * @param column the column index that was clicked
1908 + *
1909 + * @return true to sort, false to ignore
1910 + */
1911 + protected boolean onHeaderSort( int row, int column )
1912 + {
1913 + return true;
1914 + }
1915 +
1916 + @Override
1917 + protected void onLoad()
1918 + {
1919 + ResizableWidgetCollection.get().add( this );
1920 + redraw();
1921 + }
1922 +
1923 + @Override
1924 + protected void onUnload()
1925 + {
1926 + ResizableWidgetCollection.get().remove( this );
1927 + }
1928 +
1929 + /**
1930 + * Fixes the table heights so the header is visible and the data takes up the
1931 + * remaining vertical space.
1932 + */
1933 + protected void resizeTablesVertically()
1934 + {
1935 + if ( scrollPolicy == ScrollPolicy.DISABLED )
1936 + {
1937 + dataWrapper.getStyle().setProperty( "overflow", "auto" );
1938 + dataWrapper.getStyle().setProperty( "overflow", "" );
1939 + int height = Math.max( 1, absoluteElem.getOffsetHeight() );
1940 + super.setHeight( height + "px" );
1941 + }
1942 + else if ( scrollPolicy == ScrollPolicy.HORIZONTAL )
1943 + {
1944 + dataWrapper.getStyle().setProperty( "overflow", "hidden" );
1945 + dataWrapper.getStyle().setProperty( "overflow", "auto" );
1946 + int height = Math.max( 1, absoluteElem.getOffsetHeight() );
1947 + super.setHeight( height + "px" );
1948 + }
1949 + else
1950 + {
1951 + applyTableWrapperSizes( getTableWrapperSizes() );
1952 + dataWrapper.getStyle().setProperty( "width", "100%" );
1953 + }
1954 + }
1955 +
1956 + /**
1957 + * Helper method that actually performs the vertical resizing.
1958 + *
1959 + * @deprecated use {@link #redraw()} instead
1960 + */
1961 + @Deprecated
1962 + protected void resizeTablesVerticallyNow()
1963 + {
1964 + redraw();
1965 + }
1966 +
1967 + /**
1968 + * Sets the scroll property of the header and footers wrappers when scrolling
1969 + * so that the header, footer, and data tables line up.
1970 + *
1971 + * @param baseHeader true to scroll the data table as well
1972 + */
1973 + protected void scrollTables( boolean baseHeader )
1974 + {
1975 + if ( scrollPolicy == ScrollPolicy.DISABLED )
1976 + {
1977 + return;
1978 + }
1979 +
1980 + if ( lastScrollLeft >= 0 )
1981 + {
1982 + headerWrapper.setScrollLeft( lastScrollLeft );
1983 + if ( baseHeader )
1984 + {
1985 + dataWrapper.setScrollLeft( lastScrollLeft );
1986 + }
1987 + if ( footerWrapper != null )
1988 + {
1989 + footerWrapper.setScrollLeft( lastScrollLeft );
1990 + }
1991 + }
1992 + }
1993 +
1994 + /**
1995 + * @return the absolutely positioned wrapper element
1996 + */
1997 + Element getAbsoluteElement()
1998 + {
1999 + return absoluteElem;
2000 + }
2001 +
2002 + /**
2003 + * Adopt a table into this {@link AbstractScrollTable} within its wrapper.
2004 + *
2005 + * @param table the table to adopt
2006 + * @param wrapper the wrapper element
2007 + * @param index the index to insert the wrapper in the main element
2008 + */
2009 + private void adoptTable( Widget table, Element wrapper, int index )
2010 + {
2011 + DOM.insertChild( absoluteElem, wrapper, index );
2012 + add( table, wrapper );
2013 + }
2014 +
2015 + /**
2016 + * Apply the new widths to a list of columns.
2017 + *
2018 + * @param startIndex the index of the first column
2019 + * @param infos the new column width info
2020 + * @param forced if false, only set column widths that have changed
2021 + */
2022 + private void applyNewColumnWidths( int startIndex, List<ColumnWidthInfo> infos, boolean forced )
2023 + {
2024 + // Infos can be null if the widths cannot be calculated
2025 + if ( infos == null )
2026 + {
2027 + return;
2028 + }
2029 +
2030 + int offset = getHeaderOffset();
2031 + int numColumns = infos.size();
2032 + for ( int i = 0; i < numColumns; i++ )
2033 + {
2034 + ColumnWidthInfo info = infos.get( i );
2035 + int newWidth = info.getNewWidth();
2036 + if ( forced || info.getCurrentWidth() != newWidth )
2037 + {
2038 + dataTable.setColumnWidth( startIndex + i, newWidth );
2039 + headerTable.setColumnWidth( startIndex + i + offset, newWidth );
2040 + if ( footerTable != null )
2041 + {
2042 + footerTable.setColumnWidth( startIndex + i + offset, newWidth );
2043 + }
2044 + }
2045 + }
2046 + impl.repositionSpacer( this, false );
2047 + }
2048 +
2049 + /**
2050 + * Apply the new sizes to the table wrappers.
2051 + *
2052 + * @param sizes the sizes to apply
2053 + */
2054 + private void applyTableWrapperSizes( TableHeightInfo sizes )
2055 + {
2056 + if ( sizes == null )
2057 + {
2058 + return;
2059 + }
2060 +
2061 + headerWrapper.getStyle().setPropertyPx( "height", sizes.headerTableHeight );
2062 + if ( footerWrapper != null )
2063 + {
2064 + footerWrapper.getStyle().setPropertyPx( "height", sizes.footerTableHeight );
2065 + }
2066 + dataWrapper.getStyle().setPropertyPx( "height", Math.max( sizes.dataTableHeight, 0 ) );
2067 + dataWrapper.getStyle().setProperty( "overflow", "hidden" );
2068 + dataWrapper.getStyle().setProperty( "overflow", "auto" );
2069 + }
2070 +
2071 + /**
2072 + * Get the width available for the tables.
2073 + *
2074 + * @return the available width, or -1 if not defined
2075 + */
2076 + private int getAvailableWidth()
2077 + {
2078 + int clientWidth = absoluteElem.getPropertyInt( "clientWidth" );
2079 + if ( scrollPolicy == ScrollPolicy.BOTH )
2080 + {
2081 + int scrollbarWidth = mockScrollable.getOffsetWidth() - mockScrollable.getPropertyInt( "clientWidth" );
2082 + clientWidth = absoluteElem.getPropertyInt( "clientWidth" ) - scrollbarWidth - 1;
2083 + }
2084 + return Math.max( clientWidth, -1 );
2085 + }
2086 +
2087 + /**
2088 + * Get the widths of all columns, either to their preferred sizes or just
2089 + * ensure that they are within their min/max boundaries.
2090 + *
2091 + * @param boundsOnly true to only ensure the widths are within the bounds
2092 + *
2093 + * @return the column widths
2094 + */
2095 + private List<ColumnWidthInfo> getBoundedColumnWidths( boolean boundsOnly )
2096 + {
2097 + if ( !isAttached() )
2098 + {
2099 + return null;
2100 + }
2101 +
2102 + // Calculate the new column widths
2103 + int numColumns = dataTable.getColumnCount();
2104 + int totalWidth = 0;
2105 + List<ColumnWidthInfo> colWidthInfos = getColumnWidthInfo( 0, numColumns );
2106 +
2107 + // If we are reseting to original widths, set all widths to 0
2108 + if ( !boundsOnly )
2109 + {
2110 + for ( ColumnWidthInfo info : colWidthInfos )
2111 + {
2112 + totalWidth += info.getCurrentWidth();
2113 + info.setCurrentWidth( 0 );
2114 + }
2115 + }
2116 +
2117 + // Run the resize algorithm
2118 + columnResizer.distributeWidth( colWidthInfos, totalWidth );
2119 +
2120 + // Set the new column widths
2121 + return colWidthInfos;
2122 + }
2123 +
2124 + /**
2125 + * Get info about the width of a column.
2126 + *
2127 + * @param column the column index
2128 + *
2129 + * @return the info about the column width
2130 + */
2131 + private ColumnWidthInfo getColumnWidthInfo( int column )
2132 + {
2133 + int minWidth = getMinimumColumnWidth( column );
2134 + int maxWidth = getMaximumColumnWidth( column );
2135 + int preferredWidth = getPreferredColumnWidth( column );
2136 + int curWidth = getColumnWidth( column );
2137 +
2138 + // Adjust the widths if the columns are not truncatable, up to maxWidth
2139 + if ( !isColumnTruncatable( column ) )
2140 + {
2141 + maybeRecalculateIdealColumnWidths( null );
2142 + int idealWidth = getDataTable().getIdealColumnWidth( column );
2143 + if ( maxWidth != MaximumWidthProperty.NO_MAXIMUM_WIDTH )
2144 + {
2145 + idealWidth = Math.min( idealWidth, maxWidth );
2146 + }
2147 + minWidth = Math.max( minWidth, idealWidth );
2148 + }
2149 + if ( !isHeaderColumnTruncatable( column ) )
2150 + {
2151 + maybeRecalculateIdealColumnWidths( null );
2152 + int idealWidth = getHeaderTable().getIdealColumnWidth( column + getHeaderOffset() );
2153 + if ( maxWidth != MaximumWidthProperty.NO_MAXIMUM_WIDTH )
2154 + {
2155 + idealWidth = Math.min( idealWidth, maxWidth );
2156 + }
2157 + minWidth = Math.max( minWidth, idealWidth );
2158 + }
2159 + if ( footerTable != null && !isFooterColumnTruncatable( column ) )
2160 + {
2161 + maybeRecalculateIdealColumnWidths( null );
2162 + int idealWidth = getFooterTable().getIdealColumnWidth( column + getHeaderOffset() );
2163 + if ( maxWidth != MaximumWidthProperty.NO_MAXIMUM_WIDTH )
2164 + {
2165 + idealWidth = Math.min( idealWidth, maxWidth );
2166 + }
2167 + minWidth = Math.max( minWidth, idealWidth );
2168 + }
2169 +
2170 + return new ColumnWidthInfo( minWidth, maxWidth, preferredWidth, curWidth );
2171 + }
2172 +
2173 + /**
2174 + * Get info about the width of multiple columns.
2175 + *
2176 + * @param column the start column index
2177 + * @param numColumns the number of columns
2178 + *
2179 + * @return the info about the column widths of the columns
2180 + */
2181 + private List<ColumnWidthInfo> getColumnWidthInfo( int column, int numColumns )
2182 + {
2183 + List<ColumnWidthInfo> infos = new ArrayList<ColumnWidthInfo>();
2184 + for ( int i = 0; i < numColumns; i++ )
2185 + {
2186 + infos.add( getColumnWidthInfo( column + i ) );
2187 + }
2188 + return infos;
2189 + }
2190 +
2191 + /**
2192 + * Get the column widths needed to fill with available ScrollTable width.
2193 + *
2194 + * @param info the optional precomputed sizes
2195 + *
2196 + * @return the column widths
2197 + */
2198 + private List<ColumnWidthInfo> getFillColumnWidths( TableWidthInfo info )
2199 + {
2200 + if ( !isAttached() )
2201 + {
2202 + return null;
2203 + }
2204 +
2205 + // Precompute some sizes
2206 + if ( info == null )
2207 + {
2208 + info = new TableWidthInfo( false );
2209 + }
2210 +
2211 + // Calculate how much room we have to work with
2212 + int clientWidth = info.availableWidth;
2213 + if ( clientWidth <= 0 )
2214 + {
2215 + return null;
2216 + }
2217 +
2218 + // Calculate the difference and number of column to resize
2219 + int diff = 0;
2220 + int numColumns = 0;
2221 + {
2222 + // Calculate the number of columns in each table
2223 + int numHeaderCols = 0;
2224 + int numDataCols = 0;
2225 + int numFooterCols = 0;
2226 + if ( info.headerTableWidth > 0 )
2227 + {
2228 + numHeaderCols = headerTable.getColumnCount() - getHeaderOffset();
2229 + }
2230 + if ( info.dataTableWidth > 0 )
2231 + {
2232 + numDataCols = dataTable.getColumnCount();
2233 + }
2234 + if ( footerTable != null && info.footerTableWidth > 0 )
2235 + {
2236 + numFooterCols = footerTable.getColumnCount() - getHeaderOffset();
2237 + }
2238 +
2239 + // Determine the largest table
2240 + if ( numHeaderCols >= numDataCols && numHeaderCols >= numFooterCols )
2241 + {
2242 + numColumns = numHeaderCols;
2243 + diff = clientWidth - info.headerTableWidth;
2244 + }
2245 + else if ( numFooterCols >= numDataCols && numFooterCols >= numHeaderCols )
2246 + {
2247 + numColumns = numFooterCols;
2248 + diff = clientWidth - info.footerTableWidth;
2249 + }
2250 + else if ( numDataCols > 0 )
2251 + {
2252 + numColumns = numDataCols;
2253 + diff = clientWidth - info.dataTableWidth;
2254 + }
2255 + }
2256 + if ( numColumns <= 0 )
2257 + {
2258 + return null;
2259 + }
2260 +
2261 + // Calculate the new column widths
2262 + List<ColumnWidthInfo> colWidthInfos = getColumnWidthInfo( 0, numColumns );
2263 + columnResizer.distributeWidth( colWidthInfos, diff );
2264 + return colWidthInfos;
2265 + }
2266 +
2267 + /**
2268 + * Get the offset between the data and header and footer tables. An offset of
2269 + * one means that the header and footer table indexes are one greater than the
2270 + * data table indexes, probably because the data table contains a checkbox
2271 + * column.
2272 + *
2273 + * @return the offset
2274 + */
2275 + private int getHeaderOffset()
2276 + {
2277 + if ( dataTable.getSelectionPolicy().hasInputColumn() )
2278 + {
2279 + return 1;
2280 + }
2281 + return 0;
2282 + }
2283 +
2284 + /**
2285 + * Returns the new heights of the header, data, and footer tables based on the
2286 + * {@link ScrollPolicy}.
2287 + *
2288 + * @return the new table heights, or null
2289 + */
2290 + private TableHeightInfo getTableWrapperSizes()
2291 + {
2292 + // If we aren't attached, return immediately
2293 + if ( !isAttached() )
2294 + {
2295 + return null;
2296 + }
2297 +
2298 + // Heights only apply with vertical scrolling
2299 + if ( scrollPolicy == ScrollPolicy.DISABLED || scrollPolicy == ScrollPolicy.HORIZONTAL )
2300 + {
2301 + return null;
2302 + }
2303 +
2304 + // Give the data wrapper all remaining height
2305 + return new TableHeightInfo();
2306 + }
2307 +
2308 + /**
2309 + * Recalculate the ideal columns widths of all inner tables.
2310 + *
2311 + * @param command an optional command to execute while recalculating
2312 + */
2313 + private void maybeRecalculateIdealColumnWidths( Command command )
2314 + {
2315 + // Calculations require that we are attached
2316 + if ( !isAttached() )
2317 + {
2318 + return;
2319 + }
2320 +
2321 + // Check if a recalculation is needed.
2322 + if ( headerTable.isIdealColumnWidthsCalculated() && dataTable.isIdealColumnWidthsCalculated() && (footerTable == null || footerTable.isIdealColumnWidthsCalculated()) )
2323 + {
2324 + if ( command != null )
2325 + {
2326 + command.execute();
2327 + }
2328 + return;
2329 + }
2330 +
2331 + impl.recalculateIdealColumnWidths( this, command );
2332 + }
2333 +
2334 + /**
2335 + * Prepare a table to be added to the {@link AbstractScrollTable}.
2336 + *
2337 + * @param table the table to prepare
2338 + * @param cssName the style name added to the base name
2339 + */
2340 + private void prepareTable( Widget table, String cssName )
2341 + {
2342 + Element tableElem = table.getElement();
2343 + DOM.setStyleAttribute( tableElem, "margin", "0px" );
2344 + DOM.setStyleAttribute( tableElem, "border", "0px" );
2345 + table.addStyleName( cssName );
2346 + }
2347 +
2348 + /**
2349 + * Show or hide to fillWidthImage depending on current policies.
2350 + */
2351 + private void updateFillWidthImage()
2352 + {
2353 + if ( columnResizePolicy == ColumnResizePolicy.DISABLED || resizePolicy.isFixedWidth() )
2354 + {
2355 + fillWidthImage.setVisible( false );
2356 + }
2357 + else
2358 + {
2359 + fillWidthImage.setVisible( true );
2360 + }
2122 2361 }
2123 - }
2124 2362 }