FileDocCategorySizeDatePackage
ListView.javaAPI DocAzureus 3.0.3.498874Mon Oct 01 14:58:00 BST 2007com.aelitis.azureus.ui.swt.views.list

ListView

public class ListView extends com.aelitis.azureus.ui.common.table.impl.TableViewImpl implements UIUpdatable, Listener, TableStructureModificationListener, KeyListener, TableViewSWT
author
TuxPaper
created
Jun 12, 2006

Fields Summary
public static int
COLUMN_MARGIN_WIDTH
public static int
COLUMN_PADDING_WIDTH
public static int
ROW_MARGIN_HEIGHT
private static final org.gudy.azureus2.core3.logging.LogIDs
LOGID
private static final boolean
DEBUGPAINT
private static final boolean
DEBUG_SORTER
private static final boolean
DEBUG_COLUMNSIZE
private static final boolean
DEMO_DRAGROW
private static final boolean
DELAY_SCROLL
private static final org.gudy.azureus2.core3.config.impl.ConfigurationManager
configMan
private static final String
CFG_SORTDIRECTION
private Canvas
listCanvas
private boolean
isPaintingCanvas
private com.aelitis.azureus.ui.swt.skin.SWTSkinProperties
skinProperties
private TableColumnCore[]
lastVisibleColumns
private int
lastClientWidth
private ArrayList
selectedRows
ArrayList of ListRow
private AEMonitor
selectedRows_mon
private ArrayList
rows
private Map
mapDataSourceToRow
private AEMonitor
row_mon
Monitor for both rows and mapDataSourceToRow since these two are linked
private ListRow
rowFocused
private final String
sTableID
private List
dataSourcesToAdd
Queue added datasources and add them on refresh
private List
dataSourcesToRemove
Queue removed datasources and add them on refresh
private long
lCancelSelectionTriggeredOn
private List
listenersCountChange
private boolean
bMouseClickIsDefaultSelection
private int
iGraphicRefresh
protected int
graphicsUpdate
private TableColumnCore
sortColumn
Sorting functions
private long
lLastSortedOn
TimeStamp of when last sorted all the rows was
private Composite
headerArea
private TableColumnCore[]
allColumns
private ScrollBar
vBar
private Image
imgView
private GC
gcImgView
private int
iLastVBarPos
protected Object[]
restartRefreshVisible
private boolean
bInRefreshVisible
protected boolean
viewVisible
private List
listenersMenuFill
private ArrayList
listenersKey
private final int
style
private Composite
listParent
private Image
imgSortAsc
private Image
imgSortDesc
private boolean
bTitleIsMinWidth
private TableViewSWTPanelCreator
mainPanelCreator
private Map
mapColumnMetrics
private boolean
bSkipSelectionTrigger
private ArrayList
rowsToRefresh
private AEMonitor
rowsToRefresh_mon
private Rectangle
lastBounds
boolean
adjustingColumns
int
lastVisColumnWidth
Constructors Summary
public ListView(String sTableID, com.aelitis.azureus.ui.swt.skin.SWTSkinProperties skinProperties, Composite parent, Composite headerArea, int style)


	     
			      
		this.skinProperties = skinProperties;
		this.sTableID = sTableID;
		this.style = style;
		this.headerArea = headerArea;
		if (headerArea != null) {
			ImageLoader imgLoader = ImageLoaderFactory.getInstance();
			if (imgLoader != null) {
				imgSortAsc = imgLoader.getImage("image.sort.asc");
				imgSortDesc = imgLoader.getImage("image.sort.desc");
			}
		}
		initialize(parent);
		UIUpdaterFactory.getInstance().addUpdater(this);
	
public ListView(String sTableID, int style)

		this.sTableID = sTableID;
		this.style = style;
	
Methods Summary
public boolean_cellRefresh(ListCell cell, boolean bDoGraphics, boolean bForceRedraw)

		// assume cell if being refreshed if there's already a GC
		if (gcImgView != null || imgView == null) {
			return true;
		}

		try {
			gcImgView = new GC(imgView);

			cell.doPaint(gcImgView);

			Rectangle rect = cell.getBounds();
			if (rect != null) {
				listCanvas.redraw(rect.x, rect.y, rect.width, rect.height, false);
			}
		} catch (Exception e) {
			if (cell instanceof TableCellCore) {
				Debug.out(((TableCellCore) cell).getTableColumn().getName(), e);
			} else {
				Debug.out(e);
			}
		} finally {
			if (gcImgView != null) {
				gcImgView.dispose();
				gcImgView = null;
			}
		}

		return true;
	
public boolean_isRowVisible(ListRow row)

		if (listCanvas == null || listCanvas.isDisposed()
				|| !listCanvas.isVisible()) {
			return false;
		}

		Rectangle clientArea = listCanvas.getClientArea();
		int iTopIndex = iLastVBarPos / ListRow.ROW_HEIGHT;
		int iBottomIndex = (iLastVBarPos + clientArea.height - 1)
				/ ListRow.ROW_HEIGHT;

		int size = iBottomIndex - iTopIndex + 1;
		if (size <= 0) {
			return false;
		}

		int i = row.getIndex();
		return (i >= iTopIndex && i <= iBottomIndex);
	
private java.util.List_rowRefresh(ListRow row, boolean bDoGraphics, boolean bForceRedraw)

		if (listCanvas == null || listCanvas.isDisposed()) {
			return new ArrayList();
		}

		Rectangle clientArea = listCanvas.getClientArea();
		int iTopIndex = getTopIndex();

		int i = row.getIndex();
		boolean changed = false;
		List changedItems = null;
		if (i >= iTopIndex) {
			int ofs = getOffset(iLastVBarPos);
			int y = (i - iTopIndex) * ListRow.ROW_HEIGHT - ofs;

			Rectangle rect = new Rectangle(clientArea.x, y, clientArea.width,
					ListRow.ROW_HEIGHT);

			if (imgView != null) {
				boolean isOurGC = gcImgView == null;
				/*
				 * 1) Refresh the row
				 * 2) Paint any columns (or full row) if they visually changed
				 */
				try {
					if (isOurGC) {
						gcImgView = new GC(imgView);
					}
					gcImgView.setClipping(rect);

					if (!row.isVisible()) {
						// XXX turn this back on after release
						//System.out.println("asked for row refresh but not visible "
						//	+ row.getIndex() + ";" + Debug.getCompressedStackTrace());
						return new ArrayList();
					}

					changedItems = row._refresh(bDoGraphics, true);
					boolean thisChanged = changedItems.size() > 0;
					changed |= thisChanged;

					if (bForceRedraw) {
						row.doPaint(gcImgView, true);
					} else if (thisChanged) {
						String sChanged = "" + row.getIndex() + " ";
						for (Iterator iter = changedItems.iterator(); iter.hasNext();) {
							Object item = iter.next();
							if (item instanceof TableRowSWT) {
								sChanged += ", r" + ((TableRowSWT) item).getIndex();
								((TableRowSWT) item).doPaint(gcImgView, true);
								break;
							}
							if (item instanceof TableCellSWT) {
								sChanged += "," + item;
								((TableCellSWT) item).doPaint(gcImgView);
							}
						}
						//log("rowRefresh: Items changed: " + sChanged);
					}
				} catch (Exception e) {
					if (!(e instanceof IllegalArgumentException)) {
						// IllegalArgumentException happens when we are already drawing 
						// to the image.  This is "normal" as we may be in a paint event,
						// and something forces a repaint
						Debug.out(e);
					} else {
						log("Already drawing on image: " + Debug.getCompressedStackTrace());
					}
				} finally {
					if (isOurGC && gcImgView != null) {
						gcImgView.dispose();
						gcImgView = null;
					}
				}
			}

			if (changed || bForceRedraw) {
				// paint the image onto the canvas
				listCanvas.redraw(rect.x, rect.y, rect.width, rect.height, false);

				// prevent recursion
				if (!isPaintingCanvas) {
					//listCanvas.update();
				}

				//System.out.println("redrawing row " + i + "/" + row.getIndex() 
				//	+ "; (" + clientArea.x + "," + y + ","
				//	+ clientArea.width + "," + ListRow.ROW_HEIGHT + ") via "
				//	+ Debug.getCompressedStackTrace());
			}
		}
		return changedItems == null ? new ArrayList() : changedItems;
	
private void_runDefaultAction()

		// plugin may have cancelled the default action

		if (lCancelSelectionTriggeredOn > 0
				&& System.currentTimeMillis() - lCancelSelectionTriggeredOn < 200) {
			lCancelSelectionTriggeredOn = -1;
		} else {
			triggerDefaultSelectedListeners(getSelectedRows());
		}
	
public voidaddCountChangeListener(TableCountChangeListener listener)

		listenersCountChange.add(listener);
	
public voidaddDataSource(java.lang.Object dataSource)

		addDataSources(new Object[] {
			dataSource
		}, false);
	
public voidaddDataSource(java.lang.Object datasource, boolean bImmediate)

		addDataSources(new Object[] {
			datasource
		}, bImmediate);
	
public voidaddDataSources(java.lang.Object[] dataSources)

		addDataSources(dataSources, false);
	
public voidaddDataSources(java.lang.Object[] dataSources, boolean bImmediate)

		long lTimeStart = System.currentTimeMillis();

		if (dataSources == null) {
			return;
		}

		// In order to save time, we cache entries to be added and process them
		// in a refresh cycle.  This is a huge benefit to tables that have
		// many rows being added and removed in rapid succession
		if (!bImmediate) {
			int count = 0;

			try {
				row_mon.enter();

				if (dataSourcesToAdd == null) {
					dataSourcesToAdd = new ArrayList(4);
				}
				for (int i = 0; i < dataSources.length; i++) {
					if (!mapDataSourceToRow.containsKey(dataSources[i])) {
						dataSourcesToAdd.add(dataSources[i]);
						if (DEBUGADDREMOVE) {
							count++;
						}
					}
				}

				if (DEBUGADDREMOVE && count > 0) {
					logADDREMOVE(sTableID + ": Queueing " + count + " dataSources to add");
				}
				return;

			} finally {
				row_mon.exit();
			}
		}

		Utils.execSWTThread(new Runnable() {
			public void run() {
				int iFirstChange = -1;

				try {
					row_mon.enter();

					for (int i = 0; i < dataSources.length; i++) {
						Object datasource = dataSources[i];

						if (datasource == null
								|| mapDataSourceToRow.containsKey(datasource)) {
							continue;
						}

						ListRow row = new ListRow(ListView.this, listCanvas, datasource);

						if (sortColumn != null) {
							TableCellCore cell = row.getTableCellCore(sortColumn.getName());
							if (cell != null) {
								try {
									cell.invalidate();
									cell.refresh(true);
								} catch (Exception e) {
									Logger.log(new LogEvent(LOGID,
											"Minor error adding a row to table " + sTableID, e));
								}
							}
						}

						int index;
						// If we are >= to the last item, then just add it to the end
						// instead of relying on binarySearch, which may return an item
						// in the middle that also is equal.
						if (rows.size() > 0
								&& sortColumn.compare(row, rows.get(rows.size() - 1)) >= 0) {
							index = rows.size();
						} else {
							index = Collections.binarySearch(rows, row, sortColumn);
							if (index < 0) {
								index = -1 * index - 1; // best guess
							}

							if (index > rows.size()) {
								index = rows.size();
							}
						}

						if (iFirstChange < 0 || iFirstChange > index) {
							iFirstChange = index;
						}

						rows.add(index, row);
						logADDREMOVE("addDS pos " + index);

						mapDataSourceToRow.put(datasource, row);

						triggerListenerRowAdded(row);
					}
				} finally {
					row_mon.exit();

					if (iFirstChange >= 0) {
						for (int i = iFirstChange; i < rows.size(); i++) {
							ListRow row = (ListRow) rows.get(i);
							row.fixupPosition();
						}
					}

					refreshScrollbar();
					refreshVisible(true, true, true);
				}
				//System.out.println(Debug.getCompressedStackTrace());
			}
		});
		long diff = System.currentTimeMillis() - lTimeStart;
		if (diff > 20) {
			logADDREMOVE("addDS(" + dataSources.length + "): " + diff + "ms");
		}
	
public voidaddKeyListener(KeyListener listener)

		if (listenersKey.contains(listener)) {
			return;
		}

		listenersKey.add(listener);
	
public voidaddMenuFillListener(TableViewSWTMenuFillListener l)

		listenersMenuFill.add(l);
	
public voidcellInvalidate(TableColumnCore tableColumn, java.lang.Object data_source, boolean bMustRefresh)

		final String sColumnName = tableColumn.getName();

		runForAllRows(new TableGroupRowRunner() {
			public void run(TableRowCore row) {
				TableCellSWT cell = ((TableRowSWT) row).getTableCellSWT(sColumnName);
				if (cell != null && cell.getDataSource() != null
						&& cell.getDataSource().equals(data_source)) {
					cell.invalidate(bMustRefresh);
				}
			}
		});
	
public voidcellInvalidate(TableColumnCore tableColumn, java.lang.Object data_source)

		cellInvalidate(tableColumn, data_source, true);
	
public booleancellRefresh(ListCell cell, boolean bDoGraphics, boolean bForceRedraw)

		final Boolean[] b = new Boolean[1];

		Utils.execSWTThread(new AERunnable() {
			public void runSupport() {
				b[0] = new Boolean(_cellRefresh(cell, bDoGraphics, bForceRedraw));
			}
		}, false);

		return b[0] == null ? false : b[0].booleanValue();
	
private voidchangeColumnIndicator()

		if (headerArea != null && !headerArea.isDisposed()) {
			headerArea.redraw();
		}
	
public voidclipboardSelected()

	
public voidcolumnInvalidate(TableColumnCore tableColumn)

		if (tableColumn.isVisible()) {
			columnInvalidate(tableColumn, true);
		} else {
			// TODO
		}
	
public voidcolumnInvalidate(TableColumnCore tableColumn, boolean bMustRefresh)

		if (tableColumn == null) {
			return;
		}

		final String sColumnName = tableColumn.getName();

		runForAllRows(new TableGroupRowRunner() {
			public void run(TableRowCore row) {
				TableCellSWT cell = ((TableRowSWT) row).getTableCellSWT(sColumnName);
				if (cell != null) {
					cell.invalidate(bMustRefresh);
				}
			}
		});
		if (tableColumn.equals(getSortColumn())) {
			sortTable(true);
		}
	
public voidcolumnInvalidate(java.lang.String columnName)

		TableColumnCore tc = TableColumnManager.getInstance().getTableColumnCore(
				sTableID, columnName);
		if (tc != null) {
			columnInvalidate(tc, tc.getType() == TableColumnCore.TYPE_TEXT_ONLY);
		}
	
public voidcolumnOrderChanged(int[] iPositions)

		// TODO Auto-generated method stub

	
public voidcolumnSizeChanged(TableColumnCore tableColumn)

		if (adjustingColumns) {
			return;
		}
		if (isDisposed()) {
			return;
		}
		if (tableColumn.getPosition() < 0) {
			return;
		}
		lastClientWidth = 0;
		getVisibleColumns();
	
public org.eclipse.swt.dnd.DragSourcecreateDragSource(int style)

		return null;
	
public org.eclipse.swt.dnd.DropTargetcreateDropTarget(int style)

		return null;
	
public MenucreateMenu()
Creates the Context Menu.

return
a new Menu object

		final Menu menu = new Menu(listCanvas.getShell(), SWT.POP_UP);
		menu.addMenuListener(new MenuListener() {
			boolean bShown = false;

			public void menuHidden(MenuEvent e) {
				bShown = false;

				if (Constants.isOSX) {
					return;
				}

				// Must dispose in an asyncExec, otherwise SWT.Selection doesn't
				// get fired (async workaround provided by Eclipse Bug #87678)
				e.widget.getDisplay().asyncExec(new AERunnable() {
					public void runSupport() {
						if (bShown || menu.isDisposed()) {
							return;
						}
						MenuItem[] items = menu.getItems();
						for (int i = 0; i < items.length; i++) {
							items[i].dispose();
						}
					}
				});
			}

			public void menuShown(MenuEvent e) {
				MenuItem[] items = menu.getItems();
				for (int i = 0; i < items.length; i++) {
					items[i].dispose();
				}

				bShown = true;

				fillMenu(menu);
				//       addThisColumnSubMenu(getColumnNo(iMouseX));
			}
		});

		return menu;
	
private org.gudy.azureus2.plugins.ui.tables.TableCellMouseEventcreateMouseEvent(TableCellSWT cell, Event e, int type)

		TableCellMouseEvent event = new TableCellMouseEvent();
		event.cell = cell;
		if (cell != null) {
			event.row = cell.getTableRow();
		}
		event.eventType = type;
		event.button = e.button;
		// TODO: Change to not use SWT masks
		event.keyboardState = e.stateMask;
		event.skipCoreFunctionality = false;

		if (cell != null) {
			Rectangle r = cell.getBounds();
			event.x = e.x - r.x;
			event.y = e.y - r.y;
			if (event.x < 0 || event.y < 0 || event.x >= r.width
					|| event.y >= r.height) {
				//			return null; // borks mouseenter/exit
			}
		}
		return event;
	
public booleandataSourceExists(java.lang.Object dataSource)

		return mapDataSourceToRow.containsKey(dataSource)
				|| dataSourcesToAdd.contains(dataSource);
	
public voiddelete()

		triggerLifeCycleListener(TableLifeCycleListener.EVENT_DESTROYED);

		UIUpdaterFactory.getInstance().removeUpdater(this);
		TableStructureEventDispatcher.getInstance(sTableID).removeListener(this);

		Utils.disposeSWTObjects(new Object[] {
			headerArea,
			listCanvas
		});
	
public voidfillMenu(Menu menu)

		Object[] listeners = listenersMenuFill.toArray();
		for (int i = 0; i < listeners.length; i++) {
			TableViewSWTMenuFillListener l = (TableViewSWTMenuFillListener) listeners[i];
			l.fillMenu(menu);
		}
	
public voidgenerate(IndentWriter writer)

	
public TableColumnCore[]getAllColumns()

		return allColumns;
	
private intgetBottomRowHeight()

		int ofs = (iLastVBarPos + listCanvas.getClientArea().height)
				% ListRow.ROW_HEIGHT;

		return ofs;
	
public RectanglegetBounds()

		Rectangle clientArea = listCanvas.getClientArea();
		return new Rectangle(clientArea.x, -iLastVBarPos, clientArea.width,
				clientArea.height);
	
public RectanglegetClientArea()

		return listCanvas.getClientArea();
	
public TableCellCore[]getColumnCells(java.lang.String sColumnName)

		TableCellCore[] cells = new TableCellCore[rows.size()];

		try {
			row_mon.enter();

			int i = 0;
			for (Iterator iter = rows.iterator(); iter.hasNext();) {
				TableRowCore row = (TableRowCore) iter.next();
				cells[i++] = row.getTableCellCore(sColumnName);
			}

		} finally {
			row_mon.exit();
		}

		return cells;
	
public TableColumnMetricsgetColumnMetrics(org.gudy.azureus2.plugins.ui.tables.TableColumn column)

		TableColumnMetrics metrics = (TableColumnMetrics) mapColumnMetrics.get(column);
		return metrics;
	
public CompositegetComposite()

		return listParent;
	
public ControlgetControl()
Retrieve the control that the rows are added to.

return

		return listCanvas;
	
public org.gudy.azureus2.ui.swt.views.IView[]getCoreTabViews()

		return new IView[0];
	
public java.lang.Object[]getDataSources()

		return mapDataSourceToRow.keySet().toArray();
	
public java.lang.ObjectgetFirstSelectedDataSource()

		Object[] selectedDataSources = getSelectedDataSources();
		if (selectedDataSources.length > 0) {
			return selectedDataSources[0];
		}
		return null;
	
public TableRowCoregetFocusedRow()

		return getRowFocused();
	
protected TableViewSWTPanelCreatorgetMainPanelCreator()

return

		return mainPanelCreator;
	
private intgetOffset(int i)

		int ofs = (i % ListRow.ROW_HEIGHT);
		if (ofs == 0) {
			return 0;
		}
		return ofs;
	
public java.lang.StringgetPropertiesPrefix()

		return sTableID;
	
public TableRowCoregetRow(int x, int y)

param
x
param
y
return

		int pos = (y + iLastVBarPos) / ListRow.ROW_HEIGHT;
		if (pos < rows.size() && pos >= 0) {
			ListRow row = (ListRow) rows.get(pos);
			//System.out.println("getRow; y=" + y + ";sb=" + iLastVBarPos + ";pos="
			//		+ pos + ";" + row);
			return row;
		}

		return null;
	
public ListRowgetRow(int i)

		if (i < 0 || i >= rows.size()) {
			return null;
		}
		return (ListRow) rows.get(i);
	
public TableRowCoregetRow(java.lang.Object dataSource)
Get the row associated with a datasource

param
dataSource a reference to a core Datasource object (not a plugin datasource object)
return
The row, or null

		return (ListRow) mapDataSourceToRow.get(dataSource);
	
public TableRowCoregetRow(org.eclipse.swt.dnd.DropTargetEvent event)

		return null;
	
public ListRowgetRowFocused()

		return rowFocused;
	
public TableRowSWTgetRowSWT(java.lang.Object dataSource)

		return (TableRowSWT) mapDataSourceToRow.get(dataSource);
	
public TableRowCore[]getRows()

		return (TableRowCore[]) rows.toArray(new TableRowCore[0]);
	
public ListRow[]getRowsUnsorted()
Get all the rows for this table

return
a list of TableRowCore objects

		try {
			row_mon.enter();

			return (ListRow[]) rows.toArray(new ListRow[0]);

		} finally {
			row_mon.exit();
		}
	
public java.lang.Object[]getSelectedDataSources()
Returns an array of all selected Data Sources. Null data sources are ommitted.

return
an array containing the selected data sources

		return getSelectedDataSourcesList().toArray();
	
public java.lang.Object[]getSelectedDataSources(boolean bCoreDataSource)

		return getSelectedDataSourcesList(bCoreDataSource).toArray();
	
public java.util.ListgetSelectedDataSourcesList()

		return getSelectedDataSourcesList(true);
	
public java.util.ListgetSelectedDataSourcesList(boolean bCoreDataSource)
Returns an array of all selected Data Sources. Null data sources are ommitted.

return
an array containing the selected data sources
TODO
TuxPaper: Virtual row not created when usint getSelection? computePossibleActions isn't being calculated right because of non-created rows when select user selects all

		ArrayList l = new ArrayList();

		TableRowCore[] selectedRows = getSelectedRows();
		for (int i = 0; i < selectedRows.length; i++) {
			TableRowCore row = selectedRows[i];
			if (row != null) {
				l.add(row.getDataSource(bCoreDataSource));
			}
		}

		return l;
	
public TableRowCore[]getSelectedRows()

		selectedRows_mon.enter();
		try {
			ListRow[] rows = new ListRow[selectedRows.size()];
			rows = (ListRow[]) selectedRows.toArray(rows);
			return rows;
		} finally {
			selectedRows_mon.exit();
		}
	
public intgetSelectedRowsSize()

		return selectedRows.size();
	
public com.aelitis.azureus.ui.swt.skin.SWTSkinPropertiesgetSkinProperties()

		return skinProperties;
	
public TableColumnCoregetSortColumn()

		return sortColumn;
	
public TableCellSWTgetTableCell(int x, int y)

		ListRow row = (ListRow) getRow(x, y);
		if (row == null) {
			return null;
		}
		return row.getTableCellSWT(x, y);
	
public TableCellSWTgetTableCellWithCursor()

		Point pt = listCanvas.getDisplay().getCursorLocation();
		pt = listCanvas.toControl(pt);
		return getTableCell(pt.x, pt.y);
	
public CompositegetTableComposite()

		return listCanvas;
	
public java.lang.StringgetTableID()

		return sTableID;
	
private intgetTopIndex()

		if (listCanvas == null || listCanvas.isDisposed()) {
			return -1;
		}

		return iLastVBarPos / ListRow.ROW_HEIGHT;
	
public java.lang.StringgetUpdateUIName()

		return "ListView";
	
public TableColumnCore[]getVisibleColumns()


	   
		if (lastVisibleColumns == null) {
			return new TableColumnCore[0];
		}

		adjustingColumns = true;

		try {
			final int iClientWidth = listCanvas.getClientArea().width;

			if (iClientWidth <= 0) {
				return new TableColumnCore[0];
			}

			if (lastClientWidth == iClientWidth) {
				return lastVisibleColumns;
			}

			lastClientWidth = iClientWidth;

			if (DEBUG_COLUMNSIZE) {
				logCOLUMNSIZE(listCanvas.getSize() + "," + listCanvas.getBounds());
			}

			TableColumnManager tcManager = TableColumnManager.getInstance();
			List autoHideOrder = tcManager.getAutoHideOrder(sTableID);

			// calculate totals
			int totalWidthVis = 0;
			int totalMinWidth = 0;
			int totalMinWidthVis = 0;
			int totalPrefWidthVis = 0;
			for (int i = 0; i < allColumns.length; i++) {
				TableColumnCore column = allColumns[i];
				if (column.getPosition() < 0) {
					continue;
				}

				int minWidth = column.getMinWidth();
				if (DEBUG_COLUMNSIZE) {
					logCOLUMNSIZE("  " + column.getName() + ",w" + column.getWidth()
							+ ",mi" + minWidth + ",ma=" + column.getMaxWidth() + ",p="
							+ column.getPreferredWidth());
				}

				totalMinWidth += minWidth + COLUMN_PADDING_WIDTH;
				if (column.isVisible()) {
					totalMinWidthVis += minWidth + COLUMN_PADDING_WIDTH;
					totalWidthVis += column.getWidth() + COLUMN_PADDING_WIDTH;
					totalPrefWidthVis += column.getPreferredWidth()
							+ COLUMN_PADDING_WIDTH;
				}
			}

			if (DEBUG_COLUMNSIZE) {
				logCOLUMNSIZE("tot=" + totalWidthVis + ";minTot=" + totalMinWidth + "/"
						+ totalMinWidthVis + ";avail=" + iClientWidth);
			}

			ArrayList visibleColumnsList = new ArrayList(allColumns.length);
			for (int i = 0; i < allColumns.length; i++) {
				if (allColumns[i].getPosition() >= 0) {
					visibleColumnsList.add(allColumns[i]);
				}
			}

			if (totalMinWidthVis > iClientWidth) {
				// we gotta do column removals
				int pos = 0;
				while (totalMinWidthVis > iClientWidth && pos < autoHideOrder.size()) {
					TableColumn columnToHide = (TableColumn) autoHideOrder.get(pos);
					if (columnToHide.isVisible()) {
						totalMinWidth -= columnToHide.getMinWidth() + COLUMN_PADDING_WIDTH;
						totalWidthVis -= columnToHide.getWidth() + COLUMN_PADDING_WIDTH;
						totalMinWidthVis -= columnToHide.getMinWidth()
								+ COLUMN_PADDING_WIDTH;
						columnToHide.setVisible(false);
						visibleColumnsList.remove(columnToHide);
						if (DEBUG_COLUMNSIZE) {
							logCOLUMNSIZE("--- remove column " + columnToHide.getName()
									+ ". minTot=" + totalMinWidth + "/" + totalMinWidthVis);
						}
					}
					pos++;
				}
			} else if (totalMinWidth != totalMinWidthVis) {
				// add a column
				TableColumn columnToShow;
				for (int i = autoHideOrder.size() - 1; i >= 0; i--) {
					TableColumnCore column = (TableColumnCore) autoHideOrder.get(i);
					if (!column.isVisible()) {
						columnToShow = column;

						int iMinWidth = columnToShow.getMinWidth();
						if (totalMinWidthVis + iMinWidth + COLUMN_PADDING_WIDTH < iClientWidth) {
							columnToShow.setWidth(iMinWidth);
							columnToShow.setVisible(true);
							// reget width in case minwidth didn't apply 
							int width = columnToShow.getWidth();
							totalWidthVis += width + COLUMN_PADDING_WIDTH;
							totalMinWidthVis += width + COLUMN_PADDING_WIDTH;
							if (DEBUG_COLUMNSIZE) {
								logCOLUMNSIZE("+++ add column " + column.getName() + ";w="
										+ width + ";mw=" + iMinWidth + "; left: "
										+ (totalMinWidthVis + iMinWidth - iClientWidth));
							}
						} else {
							break;
						}
					}
				}
			}

			// clean up list.  remove anything with 0 width
			for (Iterator iter = visibleColumnsList.iterator(); iter.hasNext();) {
				TableColumnCore column = (TableColumnCore) iter.next();

				if (!column.isVisible()) {
					iter.remove();
				}
			}

			if (totalWidthVis > iClientWidth) {
				// we gotta do some shrinking
				int iNeededSpace = totalWidthVis - iClientWidth;
				if (DEBUG_COLUMNSIZE) {
					logCOLUMNSIZE("1] Shrink by " + iNeededSpace + " (tot="
							+ totalWidthVis + ";avail=" + iClientWidth + ")");
				}

				// Pass 1: Shrink to preferred width
				for (int i = 0; i < visibleColumnsList.size(); i++) {
					if (iNeededSpace <= 0) {
						break;
					}

					TableColumnCore column = (TableColumnCore) visibleColumnsList.get(i);

					int width = column.getWidth();
					int prefWidth = column.getPreferredWidth();
					if (prefWidth <= 0 || width < prefWidth) {
						continue;
					}
					int minWidth = column.getMinWidth();
					if (prefWidth < minWidth) {
						prefWidth = minWidth;
					}
					int diff = width - prefWidth;
					if (diff > iNeededSpace) {
						column.setWidth(width - iNeededSpace);
						iNeededSpace = 0;
					} else {
						column.setWidth(prefWidth);
						iNeededSpace -= diff;
					}
				}

				if (DEBUG_COLUMNSIZE) {
					logCOLUMNSIZE("2] Shrink by " + iNeededSpace + " (tot="
							+ totalWidthVis + ";avail=" + iClientWidth + ")");
				}
				// Pass 2: Shrink to min width
				for (int i = visibleColumnsList.size() - 1; i >= 0; i--) {
					if (iNeededSpace <= 0) {
						break;
					}

					TableColumnCore column = (TableColumnCore) visibleColumnsList.get(i);

					int width = column.getWidth();
					int minWidth = column.getMinWidth();
					int diff = width - minWidth;
					if (diff > iNeededSpace) {
						column.setWidth(width - iNeededSpace);
						iNeededSpace = 0;
					} else {
						column.setWidth(minWidth);
						iNeededSpace -= diff;
					}
				}
				if (DEBUG_COLUMNSIZE) {
					logCOLUMNSIZE("3] Remaining Needed Space" + iNeededSpace + " (tot="
							+ totalWidthVis + ";avail=" + iClientWidth + ")");
				}
			} else if (totalWidthVis < iClientWidth) {

				// Expand expandable columns
				int iExtraSpace = iClientWidth - totalWidthVis;

				ArrayList expandableColumns = new ArrayList();
				for (int i = 0; i < visibleColumnsList.size(); i++) {
					TableColumnCore column = (TableColumnCore) visibleColumnsList.get(i);

					int width = column.getWidth();
					int maxWidth = column.getMaxWidth();

					//					if (width == 0) {
					//						int minWidth = column.getMinWidth();
					//						if (minWidth == -1) {
					//							minWidth = 50;
					//						}
					//						width = minWidth;
					//						column.setWidth(width);
					//					}
					//
					if (width != maxWidth) {
						expandableColumns.add(column);
					}
				}

				// pass 1.. set to preferred width if smaller
				boolean bMoreSpace;
				do {
					int numExpandableColumns = expandableColumns.size();
					if (DEBUG_COLUMNSIZE) {
						logCOLUMNSIZE("1] Extra Space=" + iExtraSpace + ";# Expandable: "
								+ numExpandableColumns);
					}
					bMoreSpace = false;

					for (Iterator iter = expandableColumns.iterator(); iter.hasNext();) {
						TableColumnCore column = (TableColumnCore) iter.next();
						int width = column.getWidth();
						int prefWidth = column.getPreferredWidth();
						if (width >= prefWidth) {
							continue;
						}

						int expandBy = (int) ((double) iExtraSpace / numExpandableColumns);
						if (expandBy == 0) {
							expandBy = 1;
						}
						int newWidth = width + expandBy;
						if (newWidth > prefWidth) {
							expandBy -= newWidth - prefWidth;
							newWidth = prefWidth;
						} else {
							bMoreSpace = true;
						}
						column.setWidth(newWidth);
						numExpandableColumns--;
						iExtraSpace -= expandBy;
						if (iExtraSpace <= 0) {
							break;
						}
					}
				} while (bMoreSpace && iExtraSpace > 0);

				// pass 2: expand columns
				if (iExtraSpace > 0) {
					int numExpandableColumns = expandableColumns.size();
					if (DEBUG_COLUMNSIZE) {
						logCOLUMNSIZE("2] Extra Space=" + iExtraSpace + ";# Expandable: "
								+ numExpandableColumns);
					}

					for (Iterator iter = expandableColumns.iterator(); iter.hasNext();) {
						TableColumnCore column = (TableColumnCore) iter.next();
						int width = column.getWidth();
						int maxWidth = column.getMaxWidth();

						int expandBy = (int) ((double) iExtraSpace / numExpandableColumns);
						int newWidth = width + expandBy;
						if (maxWidth != -1 && newWidth > maxWidth) {
							newWidth = maxWidth;
							expandBy = maxWidth - width;
						}
						column.setWidth(newWidth);
						if (DEBUG_COLUMNSIZE) {
							logCOLUMNSIZE(column.getName() + "]" + numExpandableColumns
									+ ": expandBy:" + expandBy + ";newWidth=" + column.getWidth()
									+ ";wantedW=" + newWidth + ";mxw=" + column.getMaxWidth());
						}
						expandBy = column.getWidth() - width;
						numExpandableColumns--;
						iExtraSpace -= expandBy;
					}
					if (DEBUG_COLUMNSIZE) {
						logCOLUMNSIZE("3] Extra Space=" + iExtraSpace);
					}
				}

			} else {
				if (DEBUG_COLUMNSIZE) {
					logCOLUMNSIZE("perfect fit");
				}
			}

			// Do a pass to try to match preferred widths
			int iPrefWidthsOver = 0;
			int iPrefWidthsUnder = 0;
			int iPrefWidthsOverCount = 0;
			int iPrefWidthsUnderCount = 0;
			int iPrefWidthDiff = 0;
			for (int i = 0; i < visibleColumnsList.size(); i++) {
				TableColumnCore column = (TableColumnCore) visibleColumnsList.get(i);
				int iPrefWidth = column.getPreferredWidth();
				if (iPrefWidth <= 0) {
					continue;
				}
				int diff = column.getWidth() - iPrefWidth;
				if (diff > 0) {
					iPrefWidthsOverCount++;
					iPrefWidthsOver += diff;
				} else {
					iPrefWidthsUnderCount++;
					iPrefWidthsUnder -= diff;
				}
				iPrefWidthDiff += diff;
			}

			if (DEBUG_COLUMNSIZE) {
				logCOLUMNSIZE("PrefWO=" + iPrefWidthsOver + "(" + iPrefWidthsOverCount
						+ "),PrefWU=" + iPrefWidthsUnder + "(" + iPrefWidthsUnderCount
						+ "),d=" + iPrefWidthDiff);
			}
			if (iPrefWidthsOver > 0 && iPrefWidthsUnder > 0) {
				if (iPrefWidthDiff >= 0) {
					// we have iPrefWidthsUnder to shift to the under. All unders
					// will end up at pref
					int remaining = iPrefWidthsUnder;
					int adj = (int) (remaining / iPrefWidthsOverCount) + 1;
					if (DEBUG_COLUMNSIZE) {
						logCOLUMNSIZE("remaining " + remaining + ";adj=" + adj);
					}
					for (int i = 0; i < visibleColumnsList.size(); i++) {
						TableColumnCore column = (TableColumnCore) visibleColumnsList.get(i);
						int iPrefWidth = column.getPreferredWidth();
						if (iPrefWidth <= 0) {
							continue;
						}
						int iWidth = column.getWidth();
						int diff = iWidth - iPrefWidth;
						if (diff < 0) {
							diff *= -1;
							// we can always set it to pref, because we have more over than
							// under
							column.setWidth(iPrefWidth);
						} else if (diff > 0) {
							if (adj > remaining) {
								column.setWidth(iWidth - remaining);
							} else {
								column.setWidth(iWidth - adj);
							}
							remaining -= (column.getWidth() - iWidth);
						}
					}
				} else {
					// we have iPrefWidthOver to shift to under.  Some unders will
					// remain under. All overs will end up at pref
					int remaining = iPrefWidthsOver;
					double pctPerColumn = ((double) iPrefWidthsOver / iPrefWidthsUnder);

					if (DEBUG_COLUMNSIZE) {
						logCOLUMNSIZE("pct per column: " + pctPerColumn);
					}

					for (int i = 0; i < visibleColumnsList.size(); i++) {
						TableColumnCore column = (TableColumnCore) visibleColumnsList.get(i);

						int iPrefWidth = column.getPreferredWidth();
						if (iPrefWidth <= 0) {
							continue;
						}
						int iWidth = column.getWidth();
						int diff = iWidth - iPrefWidth;
						if (diff < 0 && remaining > 0) {
							if (iPrefWidthsUnderCount == 1) {
								diff = remaining;
							} else {
								diff = (int) ((diff * -1) * pctPerColumn);
								if (diff > remaining) {
									if (DEBUG_COLUMNSIZE) {
										logCOLUMNSIZE(column.getName() + " wants " + diff
												+ ", gets " + remaining);
									}
									diff = remaining;
								}
							}
							column.setWidth(iWidth + diff);

							remaining -= (column.getWidth() - iWidth);
							if (DEBUG_COLUMNSIZE) {
								logCOLUMNSIZE(column.getName() + "sw from " + iWidth + " to "
										+ (iWidth + diff) + "; End Size=" + column.getWidth()
										+ ";remaining=" + remaining);
							}
							iPrefWidthsUnderCount--;

						} else if (diff > 0) {
							column.setWidth(iPrefWidth);
						}
					}
				}
			}

			// fill in metrics map
			Map mapColumnMetricsNew = new HashMap();
			int iStartPos = COLUMN_MARGIN_WIDTH;
			for (int i = 0; i < visibleColumnsList.size(); i++) {
				TableColumnCore column = (TableColumnCore) visibleColumnsList.get(i);
				int width = column.getWidth();

				TableColumnMetrics metrics = new TableColumnMetrics(iStartPos, width);
				mapColumnMetricsNew.put(column, metrics);

				iStartPos += width + COLUMN_PADDING_WIDTH;
			}

			// make new values live
			mapColumnMetrics = mapColumnMetricsNew;

			lastVisibleColumns = new TableColumnCore[visibleColumnsList.size()];
			visibleColumnsList.toArray(lastVisibleColumns);

			// refresh
			changeColumnIndicator();

			refreshVisible(true, true, false);

		} finally {
			adjustingColumns = false;
		}

		return lastVisibleColumns;
	
public TableRowSWT[]getVisibleRows()

		if (listCanvas == null || listCanvas.isDisposed()) {
			return new TableRowSWT[0];
		}

		int y = iLastVBarPos;
		Rectangle clientArea = listCanvas.getClientArea();
		int iTopIndex = y / ListRow.ROW_HEIGHT;
		int iBottomIndex = (y + clientArea.height - 1) / ListRow.ROW_HEIGHT;

		int size = iBottomIndex - iTopIndex + 1;
		if (size <= 0) {
			return new TableRowSWT[0];
		}

		TableRowSWT[] visiblerows = new TableRowSWT[size];
		int pos = 0;

		for (int i = iTopIndex; i <= iBottomIndex; i++) {
			if (i >= 0 && i < rows.size()) {
				TableRowSWT row = (TableRowSWT) rows.get(i);
				if (row != null) {
					visiblerows[pos++] = row;
				}
			}
		}

		if (pos <= visiblerows.length) {
			// Some were null, shrink array
			TableRowSWT[] temp = new TableRowSWT[pos];
			System.arraycopy(visiblerows, 0, temp, 0, pos);
			return temp;
		}

		return visiblerows;
	
public voidhandleEvent(Event event)

		if (event.type == SWT.FocusIn || event.type == SWT.FocusOut) {
			selectedRows_mon.enter();
			try {
				for (Iterator iter = selectedRows.iterator(); iter.hasNext();) {
					ListRow row = (ListRow) iter.next();
					if (row != null) {
						row.redraw();
					}
				}
			} finally {
				selectedRows_mon.exit();
			}
		} else if (event.type == SWT.Traverse) {
			event.doit = true;

			switch (event.detail) {
				case SWT.TRAVERSE_ARROW_NEXT:
					if (event.stateMask == SWT.MOD2) { // shift only
						ListRow focusedRow = getRowFocused();
						if (focusedRow != null) {
							int index = focusedRow.getIndex();
							index++;
							ListRow nextRow = getRow(index);
							if (nextRow != null) {
								if (nextRow.isSelected()) {
									focusedRow.setSelected(false);
								}

								nextRow.setSelected(true);
								nextRow.setFocused(true);
							}
						}
					} else if (event.stateMask == SWT.MOD1) { // control only
						ListRow focusedRow = getRowFocused();
						if (focusedRow != null) {
							int index = focusedRow.getIndex();
							index++;
							ListRow nextRow = getRow(index);
							if (nextRow != null) {
								nextRow.setFocused(true);
							}
						}
					} else if (event.stateMask == 0) {
						moveFocus(1, false);
					}
					break;

				case SWT.TRAVERSE_ARROW_PREVIOUS:
					if (event.stateMask == SWT.MOD2) { // shift only
						// select up
						ListRow activeRow = getRowFocused();
						if (activeRow != null) {
							int index = activeRow.getIndex();
							index--;
							ListRow previousRow = getRow(index);
							if (previousRow != null) {
								if (previousRow.isSelected()) {
									activeRow.setSelected(false);
								}
								previousRow.setSelected(true);
								previousRow.setFocused(true);
							}
						}
					} else if (event.stateMask == SWT.MOD1) { // control only
						// focus up
						ListRow focusedRow = getRowFocused();
						if (focusedRow != null) {
							int index = focusedRow.getIndex();
							index--;
							ListRow nextRow = getRow(index);
							if (nextRow != null) {
								nextRow.setFocused(true);
							}
						}
					} else if (event.stateMask == 0) {
						// focus up, selection replace
						moveFocus(-1, false);
					}
					break;

				case SWT.TRAVERSE_RETURN:
					_runDefaultAction();
					break;

				default:
					System.out.println("TR" + event.detail);

			}
		} else if (event.type == SWT.DefaultSelection
				|| event.type == SWT.MouseDoubleClick) {
			_runDefaultAction();
		}

	
protected voidhandleResize(boolean bForce)

param
bForce
since
3.0.0.7

		boolean bNeedsRefresh = false;
		if (listCanvas == null || listCanvas.isDisposed()) {
			return;
		}

		Rectangle clientArea = listCanvas.getClientArea();

		if (clientArea.width == 0 || clientArea.height == 0) {
			return;
		}

		if (imgView == null || bForce) {
			if (DEBUGPAINT) {
				logPAINT("first resize (img null)");
			}
			imgView = new Image(listCanvas.getDisplay(), clientArea);
			bNeedsRefresh = true;
		} else {
			if (!lastBounds.equals(clientArea)) {
				bNeedsRefresh = lastBounds.height != clientArea.height;
				Image newImageView = new Image(listCanvas.getDisplay(), clientArea);
				GC gc = null;
				try {
					gc = new GC(newImageView);
					gc.drawImage(imgView, 0, 0);

					Region reg = new Region();
					reg.add(clientArea);
					reg.subtract(imgView.getBounds());
					gc.setClipping(reg);

					//gc.setBackground(Display.getDefault().getSystemColor((int)(Math.random() * 16)));
					gc.setBackground(listCanvas.getBackground());
					gc.fillRectangle(clientArea);

					gc.setClipping((Region) null);
					reg.dispose();
				} finally {
					if (gc != null) {
						gc.dispose();
					}
				}
				imgView.dispose();
				imgView = newImageView;
				//listCanvas.update();
			}
		}

		if (bNeedsRefresh) {
			if (DEBUGPAINT) {
				logPAINT("paint needs refresh");
			}

			boolean isOurGC = gcImgView == null;
			try {
				if (isOurGC) {
					gcImgView = new GC(imgView);
				}
				gcImgView.setForeground(listCanvas.getForeground());
				gcImgView.setBackground(listCanvas.getBackground());

				TableRowSWT[] visibleRows = getVisibleRows();
				if (visibleRows.length > 0) {
					//gc.setClipping(e.gc.getClipping());
					int ofs = getOffset(iLastVBarPos);

					int y0 = lastBounds.y + lastBounds.height;
					int y1 = clientArea.y + clientArea.height;

					int start = y0 / ListRow.ROW_HEIGHT;
					int end = y1 / ListRow.ROW_HEIGHT + 1;
					if (end > visibleRows.length) {
						end = visibleRows.length;
					}

					if (DEBUGPAINT) {
						logPAINT(visibleRows.length + " visible;" + "; " + start + " - "
								+ (end - 1));
					}
					long lStart = System.currentTimeMillis();
					for (int i = start; i < end; i++) {
						TableRowSWT row = visibleRows[i];

						row.doPaint(gcImgView, true);
					}

					// Blank out area below visible rows
					int endY = visibleRows.length * ListRow.ROW_HEIGHT + ofs;
					if (endY < clientArea.height) {
						if (DEBUGPAINT) {
							logPAINT("fill " + (clientArea.height - endY) + "@" + endY);
						}
						gcImgView.setBackground(listCanvas.getBackground());
						gcImgView.fillRectangle(0, endY, clientArea.width,
								clientArea.height - endY);
					}

					long diff = System.currentTimeMillis() - lStart;
					if (diff > 100) {
						log(diff + "ms to paint" + start + " - " + (end - 1));
					}
				} else {
					if (DEBUGPAINT) {
						logPAINT("fillall");
					}
					gcImgView.fillRectangle(clientArea);
				}
			} catch (Exception ex) {
				if (!(ex instanceof IllegalArgumentException)) {
					// IllegalArgumentException happens when we are already drawing 
					// to the image.  This is "normal" as we may be in a paint event,
					// and something forces a repaint
					Debug.out(ex);
				}
			} finally {
				if (isOurGC && gcImgView != null) {
					gcImgView.dispose();
					gcImgView = null;
				}
			}
		}
		lastBounds = clientArea;

		// SWT does resize, then paint 

		// Refreshing the scrollbar will trigger a bigger paint
		// Otherwise, we may have to trigger one ourselves
		if (vBar == null || !refreshScrollbar()) {
			getVisibleColumns();
		}
	
public intindexOf(TableRowCore row)

		return rows.indexOf(row);
	
public voidinitialize(Composite parent)

		FormData formData;

		COConfigurationManager.addAndFireParameterListener("Graphics Update",
				new ParameterListener() {
					public void parameterChanged(String parameterName) {
						graphicsUpdate = COConfigurationManager.getIntParameter("Graphics Update");
					}
				});

		TableViewSWTPanelCreator mainPanelCreator = getMainPanelCreator();
		if (mainPanelCreator != null) {
			listParent = mainPanelCreator.createTableViewPanel(parent);
		} else {
			listParent = parent;
		}

		listParent.setBackgroundMode(SWT.INHERIT_FORCE);

		listCanvas = new Canvas(listParent, SWT.NO_BACKGROUND
				| SWT.NO_REDRAW_RESIZE | style);
		listCanvas.setLayout(new FormLayout());

		Object layout = listParent.getLayout();
		if (layout instanceof FormLayout) {
			formData = new FormData();
			formData.left = new FormAttachment(0);
			formData.top = new FormAttachment(0);
			formData.right = new FormAttachment(100);
			formData.bottom = new FormAttachment(100);
			listCanvas.setLayoutData(formData);
		} else if (layout instanceof GridLayout) {
			GridData gd = new GridData(GridData.FILL_BOTH);
			listCanvas.setLayoutData(gd);
		}

		vBar = listCanvas.getVerticalBar();
		if (vBar != null) {
			vBar.addListener(SWT.Selection, new Listener() {
				private TimerEvent event;

				public void handleEvent(Event e) {
					if (DELAY_SCROLL) {
						if (event == null || event.hasRun() || event.isCancelled()) {
							event = SimpleTimer.addEvent("Scrolling",
									SystemTime.getOffsetTime(0), new TimerEventPerformer() {
										public void perform(TimerEvent event) {
											event.cancel();
											event = null;

											Utils.execSWTThread(new AERunnable() {
												public void runSupport() {
													scrollTo(vBar.getSelection());
												}
											});
										}
									});
						} else {
							log("save");
						}
					} else {
						scrollTo(vBar.getSelection());
					}
				}
			});
		}

		// Track whether the view is visible or not and adjust scrollbar when
		// visibility becomes true (Bug on SWT/Windows where setting scrollbar's
		// visibility doesn't set it in Windows, but SWT still returns that it
		// does)
		Composite c = listCanvas;
		Listener listenerShow = new Listener() {
			public void handleEvent(Event event) {
				if (event.type == SWT.Show) {
					viewVisible = true;
					// asyncExec so SWT finishes up it's show routine
					// Otherwise, the scrollbar visibility setting will fail
					listCanvas.getDisplay().asyncExec(new AERunnable() {
						public void runSupport() {
							refreshVisible(true, true, true);
							refreshScrollbar();
							handleResize(true);
						}
					});
				} else {
					viewVisible = false;
				}
			}
		};
		viewVisible = true;
		while (c != null) {
			viewVisible |= c.isVisible();
			c.addListener(SWT.Show, listenerShow);
			c.addListener(SWT.Hide, listenerShow);
			c = c.getParent();
		}

		listCanvas.addListener(SWT.Resize, new Listener() {

			public void handleEvent(Event event) {
				handleResize(false);
			}
		});

		listCanvas.addListener(SWT.Paint, new canvasPaintListener());

		selectionListener l = new selectionListener();
		listCanvas.addListener(SWT.MouseDown, l);
		listCanvas.addListener(SWT.MouseUp, l);
		listCanvas.addListener(SWT.MouseMove, l);

		listCanvas.addListener(SWT.MouseDoubleClick, this);
		listCanvas.addListener(SWT.FocusIn, this);
		listCanvas.addListener(SWT.FocusOut, this);
		listCanvas.addListener(SWT.Traverse, this);
		listCanvas.addListener(SWT.DefaultSelection, this);
		listCanvas.addKeyListener(this);

		listCanvas.setMenu(createMenu());

		Listener mouseListener = new Listener() {
			TableCellSWT lastCell = null;

			TableRowCore lastRow = null;

			int lastCursorID = -1;

			public void handleEvent(Event e) {
				try {
					boolean bExited = e.type == SWT.MouseExit;
					TableRowCore row = bExited ? null : getRow(e.x, e.y);
					TableCellSWT cell = bExited ? null : getTableCell(e.x, e.y);
					int iCursorID = -1;

					boolean changedCell = lastCell != cell;
					boolean changedRow = row != lastRow;

					// Exit previous
					if (changedCell && lastCell != null && !lastCell.isDisposed()) {
						TableCellMouseEvent event = createMouseEvent(lastCell, e,
								TableCellMouseEvent.EVENT_MOUSEEXIT);
						if (event != null) {
							TableColumnCore tc = ((TableColumnCore) lastCell.getTableColumn());
							tc.invokeCellMouseListeners(event);
							lastCell.invokeMouseListeners(event);
						}
					}

					if (changedRow && lastRow != null && !lastRow.isRowDisposed()) {
						TableCellMouseEvent event = createMouseEvent(lastCell, e,
								TableCellMouseEvent.EVENT_MOUSEEXIT);
						if (event != null) {
							event.row = lastRow;
							lastRow.invokeMouseListeners(event);
						}
					}

					// Enter new
					if (cell == null) {
						lastCell = null;
					} else {
						if (changedCell) {
							TableCellMouseEvent event = createMouseEvent(cell, e,
									TableCellMouseEvent.EVENT_MOUSEENTER);
							if (event != null) {
								TableColumnCore tc = ((TableColumnCore) cell.getTableColumn());
								tc.invokeCellMouseListeners(event);
								cell.invokeMouseListeners(event);
							}
							iCursorID = cell.getCursorID();
							lastCell = cell;
						}
					}

					if (row == null) {
						lastRow = null;
					} else {
						if (changedRow) {
							TableCellMouseEvent event = createMouseEvent(cell, e,
									TableCellMouseEvent.EVENT_MOUSEENTER);
							if (event != null) {
								event.row = row;
								row.invokeMouseListeners(event);
							}
							lastRow = row;
						}
					}

					// cursor
					if (iCursorID != lastCursorID) {
						lastCursorID = iCursorID;

						if (iCursorID >= 0) {
							listCanvas.setCursor(listCanvas.getDisplay().getSystemCursor(
									iCursorID));
						} else {
							listCanvas.setCursor(null);
						}
					}

					// mouse move for good gesture
					if (cell != null) {
						TableCellMouseEvent event = createMouseEvent(cell, e,
								TableCellMouseEvent.EVENT_MOUSEMOVE);
						if (event != null) {
							TableColumnCore tc = ((TableColumnCore) cell.getTableColumn());
							if (tc.hasCellMouseMoveListener()) {
								((TableColumnCore) cell.getTableColumn()).invokeCellMouseListeners(event);
							}
							cell.invokeMouseListeners(event);
						}
					}
					if (row != null) {
						TableCellMouseEvent event = createMouseEvent(cell, e,
								TableCellMouseEvent.EVENT_MOUSEMOVE);
						if (event != null) {
							event.row = row;
							row.invokeMouseListeners(event);
						}
					}
				} catch (Exception ex) {
					Debug.out(ex);
				}
			}
		};

		listCanvas.addListener(SWT.MouseMove, mouseListener);
		listCanvas.addListener(SWT.MouseExit, mouseListener);

		new TableTooltips(this, listCanvas);

		TableStructureEventDispatcher.getInstance(sTableID).addListener(this);

		triggerLifeCycleListener(TableLifeCycleListener.EVENT_INITIALIZED);

		if (headerArea != null) {
			setupHeader(headerArea);
		}
	
public booleanisDisposed()

		return listCanvas == null || listCanvas.isDisposed();
	
public booleanisFocused()

		return listCanvas.isFocusControl();
	
public booleanisRowVisible(ListRow row)


		return Utils.execSWTThreadWithBool("isRowVisible", new AERunnableBoolean() {
			public boolean runSupport() {
				return _isRowVisible(row);
			}
		}, 30000);
	
public booleanisTableFocus()

		return listCanvas.isFocusControl();
	
public voidkeyPressed(KeyEvent event)

		Object[] listeners = listenersKey.toArray();
		for (int i = 0; i < listeners.length; i++) {
			KeyListener l = (KeyListener) listeners[i];
			l.keyPressed(event);
			if (!event.doit) {
				return;
			}
		}

		int key = event.character;
		if (key <= 26 && key > 0) {
			key += 'a" - 1;
		}

		if (event.stateMask == SWT.MOD1) { // Control/Command
			switch (event.keyCode) {
				case 'a": // select all
					selectAll();
					break;

				case ' ":
					event.doit = false;
					ListRow focusedRow = getRowFocused();
					if (focusedRow != null) {
						focusedRow.setSelected(!focusedRow.isSelected());
					}
					break;

				case SWT.F5: {
					System.out.println("^F5");
					refreshVisible(true, true, true);
					break;
				}
			}

		} else {
			switch (event.keyCode) {
				case SWT.PAGE_UP:
					moveFocus(getClientArea().height / -ListRow.ROW_HEIGHT, false);
					break;

				case SWT.PAGE_DOWN:
					moveFocus(getClientArea().height / ListRow.ROW_HEIGHT, false);
					break;

				case SWT.HOME: {
					ListRow row = (ListRow) rows.get(0);
					if (row != null) {
						setSelectedRows(new ListRow[] {
							row
						});
					}
					break;
				}

				case SWT.END: {
					int i = rows.size();
					if (i > 0) {
						ListRow row = (ListRow) rows.get(i - 1);

						if (row != null) {
							setSelectedRows(new ListRow[] {
								row
							});
						}
					}
					break;
				}

				case SWT.F5: {
					System.out.println("F5");
					TableRowCore[] rows = getSelectedRows();
					for (int i = 0; i < rows.length; i++) {
						rows[i].invalidate();
						rows[i].refresh(true);
					}
					break;
				}
			}
		}
	
public voidkeyReleased(KeyEvent event)

		Object[] listeners = listenersKey.toArray();
		for (int i = 0; i < listeners.length; i++) {
			KeyListener l = (KeyListener) listeners[i];
			l.keyReleased(event);
			if (!event.doit) {
				return;
			}
		}
	
protected voidlog(java.lang.String string)

		System.err.println(System.currentTimeMillis() + ":" + sTableID + "] "
				+ string);
	
protected voidlogADDREMOVE(java.lang.String string)

param
string

		if (DEBUGADDREMOVE) {
			System.out.println(System.currentTimeMillis() + ":" + sTableID + "] "
					+ string);
		}
	
protected voidlogCOLUMNSIZE(java.lang.String string)

		if (!DEBUG_COLUMNSIZE) {
			return;
		}

		System.out.println(System.currentTimeMillis() + ":" + sTableID + "] "
				+ string);
	
protected voidlogPAINT(java.lang.String string)

		if (!DEBUGPAINT) {
			return;
		}

		System.out.println(System.currentTimeMillis() + ":" + sTableID + "] "
				+ string);
	
protected voidmoveFocus(int relative, boolean moveall)

param
i

		int index;

		if (moveall) {
			System.err.println("moveall not supported "
					+ Debug.getCompressedStackTrace());
		}

		selectedRows_mon.enter();
		try {
			if (selectedRows.size() == 0) {
				return;
			}

			ListRow firstRow = (ListRow) selectedRows.get(0);

			index = indexOf(firstRow) + relative;
			if (index < 0) {
				index = 0;
			}

			if (index >= rows.size()) {
				if (index == 0) {
					return;
				}
				index = rows.size() - 1;
			}

			ListRow newRow = (ListRow) rows.get(index);
			setSelectedRows(new ListRow[] {
				newRow
			});
		} finally {
			selectedRows_mon.exit();
		}
	
public ImageobfusticatedImage(Image image, Point shellOffset)

		return image;
	
public voidprocessDataSourceQueue()
Process the queue of datasources to be added and removed

		Object[] dataSourcesAdd = null;
		Object[] dataSourcesRemove = null;

		try {
			row_mon.enter();
			if (dataSourcesToAdd.size() > 0) {
				dataSourcesAdd = dataSourcesToAdd.toArray();
				dataSourcesToAdd.clear();

				// remove the ones we are going to add then delete
				if (dataSourcesToRemove != null && dataSourcesToRemove.size() > 0) {
					for (int i = 0; i < dataSourcesAdd.length; i++) {
						if (dataSourcesToRemove.contains(dataSourcesAdd[i])) {
							dataSourcesToRemove.remove(dataSourcesAdd[i]);
							dataSourcesAdd[i] = null;
							if (DEBUGADDREMOVE) {
								logADDREMOVE("Saved time by not adding a row that was removed");
							}
						}
					}
				}
			}

			if (dataSourcesToRemove != null && dataSourcesToRemove.size() > 0) {
				dataSourcesRemove = dataSourcesToRemove.toArray();
				if (DEBUGADDREMOVE && dataSourcesRemove.length > 1) {
					logADDREMOVE("Streamlining removing " + dataSourcesRemove.length
							+ " rows");
				}
				dataSourcesToRemove.clear();
			}
		} finally {
			row_mon.exit();
		}

		if (dataSourcesAdd != null) {
			addDataSources(dataSourcesAdd, true);
			if (DEBUGADDREMOVE && dataSourcesAdd.length > 1) {
				logADDREMOVE("Streamlined adding " + dataSourcesAdd.length + " rows");
			}
		}

		if (dataSourcesRemove != null) {
			// for now, remove one at a time
			// TODO: all at once
			for (int i = 0; i < dataSourcesRemove.length; i++) {
				Object ds = dataSourcesRemove[i];
				removeDataSource(ds, true);
			}
		}
	
protected booleanrefreshScrollbar()

		if (!viewVisible || vBar == null || vBar.isDisposed()) {
			return false;
		}
		boolean changed = false;

		Rectangle client = listCanvas.getClientArea();
		int h = (rows.size() * ListRow.ROW_HEIGHT) - client.height;

		if (h <= 0 || client.height == 0) {
			if (vBar.isVisible()) {
				vBar.setVisible(false);
				listCanvas.redraw();
				if (headerArea != null) {
					headerArea.redraw();
				}
			}
			iLastVBarPos = 0;
		} else {
			if (!vBar.isVisible()) {
				vBar.setVisible(true);
				listCanvas.redraw();
				if (headerArea != null) {
					headerArea.redraw();
				}
				changed = true;
			}
			vBar.setIncrement(ListRow.ROW_HEIGHT);
			int thumb = client.height;
			vBar.setMaximum(h + thumb);
			vBar.setThumb(thumb);
			vBar.setPageIncrement(client.height / 2);
			if (iLastVBarPos != vBar.getSelection()) {
				scrollTo(vBar.getSelection());
				changed = true;
			}
		}
		if (DEBUGPAINT) {
			logPAINT("refreshScrollbar. changed? " + changed);
		}
		if (changed) {
			listCanvas.update();
		}
		return changed;
	
public voidrefreshTable(boolean forceSort)

		if (listCanvas.isDisposed()) {
			return;
		}
		//log("updateUI via " + Debug.getCompressedStackTrace());
		processDataSourceQueue();

		if (!sortTable(forceSort)) {
			iGraphicRefresh++;
			boolean bDoGraphics = (iGraphicRefresh % graphicsUpdate) == 0;
			refreshVisible(bDoGraphics, false, true);
		}
	
public voidrefreshVisible(boolean doGraphics, boolean bForceRedraw, boolean bAsync)

		if (isDisposed() || !listCanvas.isVisible()) {
			return;
		}
		if (bInRefreshVisible) {
			if (DEBUGPAINT) {
				logPAINT("Set flag to restart visible because of "
						+ Debug.getCompressedStackTrace());
			}
			restartRefreshVisible = new Object[] {
				new Boolean(doGraphics),
				new Boolean(bForceRedraw),
				new Boolean(bAsync)
			};
			return;
		}
		bInRefreshVisible = true;
		if (DEBUGPAINT) {
			logPAINT("Start refreshVisible " + Debug.getCompressedStackTrace());
		}

		final Display display = listCanvas.getDisplay();

		AERunnable runnable = new AERunnable() {
			public void runSupport() {
				final TableRowCore[] visibleRows = getVisibleRows();
				try {
					for (int i = 0; i < visibleRows.length; i++) {
						if (restartRefreshVisible != null) {
							if (DEBUGPAINT) {
								logPAINT("STOPPED refresh at " + i);
							}
							return;
						}

						final ListRow row = (ListRow) visibleRows[i];

						AERunnable rowRunnable = new AERunnable() {
							public void runSupport() {

								if (restartRefreshVisible != null) {
									if (DEBUGPAINT) {
										logPAINT("stopped refresh at " + row.getIndex());
									}
									return;
								}

								if (!row.isVisible()) {
									// uh oh, order changed, refresh!
									restartRefreshVisible = new Object[] {
										new Boolean(doGraphics),
										new Boolean(bForceRedraw),
										new Boolean(bAsync)
									};
									return;
								}

								if (bForceRedraw) {
									row.invalidate();
								}

								if (row.isVisible()) {
									//if (row.isVisible() && !row.isValid()) {
									rowRefreshAsync(row, doGraphics, bForceRedraw);
								} else {
									//System.out.println("skipping.. not visible. valid? " + row.isValid());
								}
							}
						};

						if (bAsync) {
							display.asyncExec(rowRunnable);
						} else {
							display.syncExec(rowRunnable);
						}
					}
				} finally {
					bInRefreshVisible = false;

					if (restartRefreshVisible != null) {
						Object[] params = restartRefreshVisible;
						restartRefreshVisible = null;
						if (DEBUGPAINT) {
							logPAINT("Restarting refresh");
						}
						boolean newDoGraphics = ((Boolean) params[0]).booleanValue()
								|| doGraphics;
						boolean newForceRedraw = ((Boolean) params[1]).booleanValue()
								|| bForceRedraw;
						boolean newAsync = ((Boolean) params[2]).booleanValue();
						refreshVisible(newDoGraphics, newForceRedraw, newAsync);
					}
				}
			}
		};

		if (bAsync) {
			display.asyncExec(runnable);
		} else {
			display.syncExec(runnable);
		}
	
public voidremoveAllDataSources(boolean bImmediate)

		Utils.execSWTThread(new Runnable() {
			public void run() {

				row_mon.enter();
				logADDREMOVE("removeAll");

				try {
					for (Iterator iterator = mapDataSourceToRow.keySet().iterator(); iterator.hasNext();) {
						Object datasource = iterator.next();
						ListRow row = (ListRow) mapDataSourceToRow.get(datasource);

						if (row != null) {
							rows.remove(row);
							row.setSelected(false);
							row.setFocused(false);
							row.delete();
						}
					}

					mapDataSourceToRow.clear();
					rows.clear();
				} finally {
					row_mon.exit();
				}

				handleResize(true);
				listCanvas.redraw();
			}
		}, !bImmediate);
	
public voidremoveAllTableRows()

		removeAllDataSources(true);
	
public voidremoveDataSource(java.lang.Object dataSource)

		removeDataSources(new Object[] {
			dataSource
		});
	
public voidremoveDataSource(java.lang.Object datasource, boolean bImmediate)

		removeDataSources(new Object[] {
			datasource
		});
	
public voidremoveDataSources(java.lang.Object[] dataSources)

		Utils.execSWTThread(new Runnable() {
			public void run() {
				try {
					row_mon.enter();
					int firstIndex = dataSources.length;
					ListRow newFocusRow = null;

					for (int i = 0; i < dataSources.length; i++) {
						Object datasource = dataSources[i];
						ListRow row = (ListRow) mapDataSourceToRow.get(datasource);
						if (row != null) {

							int index = row.getIndex();
							if (index < firstIndex) {
								firstIndex = index;
							}

							if (newFocusRow == null && row.isFocused()) {
								int newIndex = index + 1;
								if (index >= 0) {
									if (newIndex >= mapDataSourceToRow.size()) {
										newIndex -= 2;
									}

									if (newIndex >= 0) {
										newFocusRow = getRow(newIndex);
									}
								}
							}

							row = (ListRow) mapDataSourceToRow.remove(datasource);
							if (row == null) {
								return;
							}

							// Delete row before removing in case delete(..) calls back a method
							// which needs rows.
							row.setSelected(false);
							row.setFocused(false);
							row.delete();
							rows.remove(row);

							logADDREMOVE("remDS pos " + index + ";" + rows.size());

							triggerListenerRowRemoved(row);
						} else {
							//System.out.println("not found " + datasource);
						}
					}

					if (newFocusRow != null) {
						//System.out.println("SR " + newFocusRow.getIndex());
						rowSetFocused(newFocusRow);
						newFocusRow.setSelected(true);
					}

					for (int i = firstIndex; i < rows.size(); i++) {
						ListRow fixRow = (ListRow) rows.get(i);
						fixRow.fixupPosition();
					}
				} finally {
					row_mon.exit();
					refreshScrollbar();
					// TODO: Redraw only if visible or above visible (bg change)

					if (imgView != null && !imgView.isDisposed()
							&& !listCanvas.isDisposed()) {
						TableRowCore[] visibleRows = getVisibleRows();
						Rectangle clientArea = listCanvas.getClientArea();
						if (visibleRows.length > 0) {
							int ofs = getOffset(iLastVBarPos);
							int endY = visibleRows.length * ListRow.ROW_HEIGHT + ofs;

							if (endY < clientArea.height) {
								boolean isOurGC = gcImgView == null;
								try {
									if (isOurGC) {
										gcImgView = new GC(imgView);
									}
									gcImgView.setBackground(listCanvas.getBackground());

									gcImgView.fillRectangle(0, endY, clientArea.width,
											clientArea.height - endY);
									listCanvas.redraw(0, endY, clientArea.width,
											clientArea.height - endY, false);
								} catch (Exception ex) {
									if (!(ex instanceof IllegalArgumentException)) {
										// IllegalArgumentException happens when we are already drawing 
										// to the image.  This is "normal" as we may be in a paint event,
										// and something forces a repaint
										Debug.out(ex);
									}
								} finally {
									if (isOurGC && gcImgView != null) {
										gcImgView.dispose();
										gcImgView = null;
									}
								}
							}
						} else {
							boolean isOurGC = gcImgView == null;
							try {
								if (isOurGC) {
									gcImgView = new GC(imgView);
								}
								gcImgView.setBackground(listCanvas.getBackground());
								gcImgView.fillRectangle(clientArea);
								listCanvas.redraw();
							} catch (Exception ex) {
								if (!(ex instanceof IllegalArgumentException)) {
									// IllegalArgumentException happens when we are already drawing 
									// to the image.  This is "normal" as we may be in a paint event,
									// and something forces a repaint
									Debug.out(ex);
								}
							} finally {
								if (isOurGC && gcImgView != null) {
									gcImgView.dispose();
									gcImgView = null;
								}
							}
						}

						listCanvas.redraw();
					}

					refreshVisible(true, true, true);
				}
			}
		});
	
public voidremoveKeyListener(KeyListener listener)

		listenersKey.remove(listener);
	
protected introwGetVisibleYOffset(TableRowCore row)

		int i = row.getIndex();
		int iTopIndex = iLastVBarPos / ListRow.ROW_HEIGHT;
		int ofs = getOffset(iLastVBarPos);
		return (i - iTopIndex) * ListRow.ROW_HEIGHT - ofs;
	
public booleanrowIsSelected(ListRow row)

		selectedRows_mon.enter();
		return selectedRows.indexOf(row) >= 0;
	
public java.util.ListrowRefresh(ListRow row, boolean bDoGraphics, boolean bForceRedraw)

param
row
param
bDoGraphics

		List list = (List) Utils.execSWTThreadWithObject("rowRefresh",
				new AERunnableObject() {
					public Object runSupport() {
						return _rowRefresh(row, bDoGraphics, bForceRedraw);
					}
				});

		return list == null ? new ArrayList() : list;
	
public voidrowRefreshAsync(ListRow row, boolean bDoGraphics, boolean bForceRedraw)

		if (DEBUGPAINT) {
			logPAINT("rowRefreshA " + row + " force? " + bForceRedraw + " via "
					+ Debug.getCompressedStackTrace(5));
		}

		try {
			rowsToRefresh_mon.enter();

			if (rowsToRefresh.contains(row)) {
				return;
			}
			rowsToRefresh.add(row);

			if (rowsToRefresh.size() > 1) {
				return;
			}
		} finally {
			rowsToRefresh_mon.exit();
		}

		if (!isDisposed()) {
			listCanvas.getDisplay().asyncExec(new AERunnable() {
				public void runSupport() {
					Object[] rows;
					try {
						rowsToRefresh_mon.enter();

						rows = rowsToRefresh.toArray();

						rowsToRefresh.clear();
					} finally {
						rowsToRefresh_mon.exit();
					}
					if (DEBUGPAINT) {
						logPAINT("rowRefreshA hit " + rows.length + " force? "
								+ bForceRedraw);
					}

					for (int i = 0; i < rows.length; i++) {
						ListRow row = (ListRow) rows[i];
						if (row.isVisible()) {
							// XXX May be using the wrong boolean params!!
							_rowRefresh(row, bDoGraphics, bForceRedraw);
						}
					}
				}
			});
		}
		;
	
protected voidrowSetFocused(ListRow row)

		if (row != null) {
			rowShow(row);
		}

		rowFocused = row;

		triggerFocusChangedListeners(row);
	
protected voidrowSetSelected(ListRow row, boolean bSelected)
XXX DO NOT CALL FROM LISTVIEW! Adds row to selection list

		if (row == null || (selectedRows.indexOf(row) >= 0) == bSelected) {
			return;
		}
		selectedRows_mon.enter();
		try {
			if (bSelected) {
				selectedRows.add(row);
			} else {
				selectedRows.remove(row);
			}
		} finally {
			selectedRows_mon.exit();
		}

		if (!bSkipSelectionTrigger) {
			if (bSelected) {
				triggerDeselectionListeners(new TableRowCore[] {
					row
				});
			} else {
				triggerSelectionListeners(new TableRowCore[] {
					row
				});
			}
		}
	
public voidrowShow(ListRow row)

		// move into view
		if (row == null) {
			return;
		}

		Rectangle clientArea = listCanvas.getClientArea();
		int iTopIndex = iLastVBarPos / ListRow.ROW_HEIGHT;
		int iBottomIndex = (iLastVBarPos + clientArea.height - 1)
				/ ListRow.ROW_HEIGHT;

		int size = iBottomIndex - iTopIndex + 1;
		if (size <= 0) {
			return;
		}

		int i = row.getIndex();
		if (i > iTopIndex && i < iBottomIndex) {
			return;
		}

		int myPos = (i - iTopIndex) * ListRow.ROW_HEIGHT;

		//System.out.println("rowShow:" + i + ";top=" + iTopIndex + ";b="
		//		+ iBottomIndex + ";" + iLastVBarPos);

		if (i == iTopIndex) {
			int ofs = getOffset(iLastVBarPos);
			if (ofs == 0) {
				return;
			}

			scrollTo(myPos);
			return;
		}

		if (i == iBottomIndex) {
			int ofs = getOffset(iLastVBarPos + clientArea.height);
			if (ofs == 0) {
				return;
			}

			scrollTo((iTopIndex + 1) * ListRow.ROW_HEIGHT);

			return;
		}

		// adjust bar so that new focused row is in the same spot as the old
		// one
		int ofs = 0;
		ListRow rowFocused = getRowFocused();
		if (rowFocused != null) {
			int iFocusedIdx = rowFocused.getIndex();
			if (iFocusedIdx >= iTopIndex && iFocusedIdx <= iBottomIndex) {
				ofs = (rowFocused.getIndex() * ListRow.ROW_HEIGHT) - iLastVBarPos;
				//System.out.println("moveF ofs=" + ofs + "; mp = " + myPos);
				if (ofs < 0) {
					ofs = 0;
				}
			}
		}
		scrollTo(i * ListRow.ROW_HEIGHT - ofs);
	
public voidrunForAllRows(TableGroupRowVisibilityRunner runner)

		TableRowCore[] rows = getRows();

		for (int i = 0; i < rows.length; i++) {
			runner.run(rows[i], rows[i].isVisible());
		}
	
public voidrunForSelectedRows(TableGroupRowRunner runner)

		TableRowCore[] rows = getSelectedRows();
		if (runner.run(rows)) {
			return;
		}

		for (int i = 0; i < rows.length; i++) {
			runner.run(rows[i]);
		}
	
private voidscrollTo(int pos)

		if (vBar == null || !vBar.getVisible()) {
			return;
		}

		if (pos == iLastVBarPos && pos == vBar.getSelection()) {
			return;
		}

		long lTimeStart = System.currentTimeMillis();

		if (pos < 0) {
			System.err.println("scrollto " + pos + " via "
					+ Debug.getCompressedStackTrace());
			pos = 0;
		} else {
			//System.out.println("scrollto " + pos + "of" + vBar.getMaximum() + " via "
			//		+ Debug.getCompressedStackTrace());
		}
		if (pos != vBar.getSelection()) {
			vBar.setSelection(pos);
		}
		int iThisVBarPos = vBar.getSelection();
		int diff = iLastVBarPos - iThisVBarPos;
		if (DEBUGPAINT) {
			logPAINT("scroll diff = " + diff + ";" + imgView);
		}

		if (diff != 0 && imgView != null && !imgView.isDisposed()) {
			// Shift image up or down, then fill in the gap with a newly displayed row
			boolean isOurGC = gcImgView == null;

			try {
				if (isOurGC) {
					gcImgView = new GC(imgView);
				}
				scrollToWithGC(gcImgView, diff, iThisVBarPos, false, true);
			} catch (Exception ex) {
				Debug.out(ex);
			} finally {
				if (isOurGC && gcImgView != null) {
					gcImgView.dispose();
					gcImgView = null;
				}
			}

			listCanvas.redraw();
			listCanvas.update();
		}
		iLastVBarPos = iThisVBarPos;

		if (DEBUGPAINT) {
			logPAINT("done in " + (System.currentTimeMillis() - lTimeStart));
		}
	
private voidscrollToWithGC(GC gc, int diff, int iThisVBarPos, boolean bMoveOnly, boolean bGCisImage)

		Rectangle bounds = imgView.getBounds();

		if (diff > 0) {
			int h = bounds.height - diff;
			if (h > 0) {
				if (Constants.isOSX) {
					// copyArea should work on OSX, but why risk it when drawImage works
					gc.drawImage(imgView, 0, 0, bounds.width, h, 0, diff, bounds.width, h);
				} else {
					// Windows can't use drawImage on same image
					gc.copyArea(0, 0, bounds.width, h, 0, diff);
				}
			}
		} else {
			int h = bounds.height + diff;
			if (h > 0) {
				if (Constants.isOSX) {
					// OSX can't copyArea upwards
					gc.drawImage(imgView, 0, -diff, bounds.width, h, 0, 0, bounds.width,
							h);
				} else {
					// Windows can't use drawImage on same image
					gc.copyArea(0, -diff, bounds.width, h, 0, 0);
				}
			}
		}

		if (bMoveOnly) {
			return;
		}

		iLastVBarPos = iThisVBarPos;
		TableRowSWT[] visibleRows = getVisibleRows();
		if (diff < 0) {
			int ofs = getBottomRowHeight();
			// image moved up.. gap at bottom
			int i = visibleRows.length - 1;
			while (diff <= 0 && i >= 0) {
				TableRowSWT row = visibleRows[i];
				if (DEBUGPAINT) {
					logPAINT("scrollTo repaint visRow#" + i + "(idx:" + row.getIndex()
							+ ") d=" + diff + ";ofs=" + ofs);
				}
				row.doPaint(gc, true);
				i--;
				diff += ofs;
				ofs = ListRow.ROW_HEIGHT;
			}
			if (i >= 0) {
				ListRow row = (ListRow) visibleRows[i];
				if (!row.isValid()) {
					if (DEBUGPAINT) {
						logPAINT("scrollTo repaint dirty Row#" + i + "(idx:"
								+ row.getIndex() + ") d=" + diff + ";ofs=" + ofs);
					}
					row.doPaint(gc, true);
				}
			}
		} else {
			// image moved down.. gap at top to draw
			int i = 0;
			int ofs = ListRow.ROW_HEIGHT - getOffset(iLastVBarPos);
			while (diff >= 0 && i < visibleRows.length) {
				TableRowSWT row = visibleRows[i];
				if (DEBUGPAINT) {
					logPAINT("repaint " + i + "(" + row.getIndex() + ") d=" + diff
							+ ";o=" + ofs);
				}
				row.doPaint(gc, true);
				i++;
				diff -= ofs;
				ofs = ListRow.ROW_HEIGHT;
			}
		}
	
public voidselectAll()

		setSelectedRows(getRowsUnsorted());
	
public voidsetColumnList(TableColumnCore[] columns, java.lang.String defaultSortColumnID, boolean defaultSortAscending, boolean titleIsMinWidth)

		this.bTitleIsMinWidth = titleIsMinWidth;
		// XXX Adding Columns only has to be done once per TableID.  
		// Doing it more than once won't harm anything, but it's a waste.
		TableColumnManager tcManager = TableColumnManager.getInstance();
		if (tcManager.getTableColumnCount(sTableID) != columns.length) {
			for (int i = 0; i < columns.length; i++) {
				columns[i].setTableID(sTableID);
				tcManager.addColumn(columns[i]);
			}
		}

		// fixup order
		tcManager.ensureIntegrety(sTableID);

		allColumns = tcManager.getAllTableColumnCoreAsArray(sTableID);

		Arrays.sort(allColumns, new Comparator() {
			public int compare(Object o1, Object o2) {
				TableColumn tc0 = (TableColumn) o1;
				TableColumn tc1 = (TableColumn) o2;
				return tc0.getPosition() - tc1.getPosition();
			}
		});

		ArrayList visibleColumnsList = new ArrayList();
		for (int i = 0; i < allColumns.length; i++) {
			if (allColumns[i].getPosition() >= 0) {
				visibleColumnsList.add(allColumns[i]);
			}
		}
		lastVisibleColumns = (TableColumnCore[]) visibleColumnsList.toArray(new TableColumnCore[0]);
		// TODO: Refresh all rows

		// Initialize the sorter after the columns have been added
		// TODO: Restore sort column and direction from config (list in TVSWTImpl)
		String sSortColumn = defaultSortColumnID;
		boolean bSortAscending = defaultSortAscending;

		TableColumnCore tc = tcManager.getTableColumnCore(sTableID, sSortColumn);
		if (tc == null) {
			tc = lastVisibleColumns[0];
		}
		sortColumn = tc;
		sortColumn.setSortAscending(bSortAscending);

		if (bTitleIsMinWidth) {
			setColumnMinWidthToHeaders();
		}

		changeColumnIndicator();
	
private voidsetColumnMinWidthToHeaders()

since
3.0.0.7

		int sortWidth = Math.max(imgSortAsc == null ? -2
				: imgSortAsc.getBounds().width, imgSortDesc == null ? -2
				: imgSortDesc.getBounds().width) + 2;

		// set min column width to width of header
		GC gc = new GC(headerArea);
		try {
			TableColumnCore[] columns = getAllColumns();
			if (columns == null) {
				return;
			}
			for (int i = 0; i < columns.length; i++) {
				TableColumnCore column = columns[i];
				String title = MessageText.getString(column.getTitleLanguageKey(), "");
				int oldWidth = column.getMinWidth();
				int minWidth = gc.textExtent(title).x + sortWidth;
				if (minWidth > oldWidth) {
					column.setMinWidth(minWidth);
				}
			}
		} finally {
			if (gc != null) {
				gc.dispose();
			}
		}
	
public voidsetCoreTabViews(org.gudy.azureus2.ui.swt.views.IView[] coreTabViews)

		// XXX TabViews not supported
	
public voidsetEnableTabViews(boolean enableTabViews)

		// XXX TabViews not supported
	
public voidsetFocus()

		listCanvas.setFocus();
	
public voidsetHeaderArea(Composite headerArea, Image imgSortAsc, Image imgSortDesc)

		this.headerArea = headerArea;
		this.imgSortAsc = imgSortAsc;
		this.imgSortDesc = imgSortDesc;
	
public voidsetMainPanelCreator(TableViewSWTPanelCreator mainPanelCreator)

		this.mainPanelCreator = mainPanelCreator;
	
public voidsetMouseClickIsDefaultSelection(boolean b)

		bMouseClickIsDefaultSelection = b;
	
public voidsetRowDefaultHeight(int height)

		ListRow.ROW_HEIGHT = height + (ListView.ROW_MARGIN_HEIGHT * 2);
	
public voidsetRowDefaultIconSize(Point size)

		ListRow.ROW_HEIGHT = size.y + (ListView.ROW_MARGIN_HEIGHT * 2);
	
public voidsetSelectedRows(TableRowCore[] rows)

		selectedRows_mon.enter();
		try {
			ArrayList rowsToSelect = new ArrayList();
			ArrayList rowsToDeselect = new ArrayList();
			for (int i = 0; i < rows.length; i++) {
				rowsToSelect.add(rows[i]);
			}
			TableRowCore[] selectedRows = getSelectedRows();

			// unselect already selected rows that aren't going to be selected anymore
			for (int i = 0; i < selectedRows.length; i++) {
				TableRowCore selectedRow = selectedRows[i];
				boolean bStillSelected = false;
				for (int j = 0; j < rows.length; j++) {
					TableRowCore row = rows[j];
					if (row.equals(selectedRow)) {
						bStillSelected = true;
						break;
					}
				}
				if (!bStillSelected) {
					rowsToDeselect.add(selectedRow);
				} else {
					rowsToSelect.remove(selectedRow);
				}
			}

			// trigger selection/deselection early, which will prevent each
			// row from firing one individually

			TableRowCore[] rowsToDeselectArray = new TableRowCore[rowsToDeselect.size()];
			rowsToDeselect.toArray(rowsToDeselectArray);
			triggerDeselectionListeners(rowsToDeselectArray);

			TableRowCore[] rowsToSelectArray = new TableRowCore[rowsToSelect.size()];
			rowsToSelect.toArray(rowsToSelectArray);
			triggerDeselectionListeners(rowsToSelectArray);

			bSkipSelectionTrigger = true;
			for (Iterator iter = rowsToDeselect.iterator(); iter.hasNext();) {
				ListRow row = (ListRow) iter.next();
				row.setSelected(false);
			}

			for (Iterator iter = rowsToSelect.iterator(); iter.hasNext();) {
				ListRow row = (ListRow) iter.next();
				row.setSelected(true);
			}

			if (rows.length > 0) {
				((ListRow) rows[0]).setFocused(true);
			}
		} finally {
			bSkipSelectionTrigger = false;
			selectedRows_mon.exit();
		}
	
public voidsetSortColumn(TableColumnCore sorter)

		boolean bSameColumn = sortColumn.equals(sorter);
		if (!bSameColumn) {
			sortColumn = sorter;
			int iSortDirection = configMan.getIntParameter(CFG_SORTDIRECTION);
			if (iSortDirection == 0) {
				sortColumn.setSortAscending(true);
			} else if (iSortDirection == 1) {
				sortColumn.setSortAscending(false);
			} else {
				sortColumn.setSortAscending(!sortColumn.isSortAscending());
			}

			configMan.setParameter(sTableID + ".sortAsc",
					sortColumn.isSortAscending());
			configMan.setParameter(sTableID + ".sortColumn", sortColumn.getName());
		} else {
			sortColumn.setSortAscending(!sortColumn.isSortAscending());
			configMan.setParameter(sTableID + ".sortAsc",
					sortColumn.isSortAscending());
		}

		sortColumn.setLastSortValueChange(SystemTime.getCurrentTime());

		changeColumnIndicator();
		sortTable(false);
	
private voidsetupHeader(Composite headerArea)

param
headerArea

		this.headerArea = headerArea;

		final Cursor cursor = new Cursor(headerArea.getDisplay(), SWT.CURSOR_HAND);
		headerArea.addDisposeListener(new DisposeListener() {
			public void widgetDisposed(DisposeEvent e) {
				Utils.disposeSWTObjects(new Object[] {
					cursor
				});
			}
		});

		if (bTitleIsMinWidth) {
			setColumnMinWidthToHeaders();
		}

		headerArea.addMouseListener(new MouseListener() {

			public void mouseDoubleClick(MouseEvent e) {
			}

			public void mouseDown(MouseEvent e) {
			}

			public void mouseUp(MouseEvent e) {
				TableColumnCore[] columns = getVisibleColumns();
				int inColumn = -1;
				for (int i = 0; i < columns.length; i++) {
					Rectangle bounds = (Rectangle) headerArea.getData("Column" + i
							+ "Bounds");
					if (bounds != null && bounds.contains(e.x, e.y)) {
						inColumn = i;
						break;
					}
				}
				if (inColumn != -1) {
					setSortColumn(columns[inColumn]);
					if (DEBUG_SORTER) {
						log("sorting on " + columns[inColumn].getName());
					}
				}
			}

		});

		headerArea.addMouseMoveListener(new MouseMoveListener() {
			Cursor cursor = null;

			public void mouseMove(MouseEvent e) {
				TableColumnCore[] columns = getVisibleColumns();
				int inColumn = -1;
				for (int i = 0; i < columns.length; i++) {
					Rectangle bounds = (Rectangle) headerArea.getData("Column" + i
							+ "Bounds");
					if (bounds != null && bounds.contains(e.x, e.y)) {
						inColumn = i;
						break;
					}
				}
				Cursor newCursor = (inColumn != -1) ? cursor : null;
				if (cursor != newCursor) {
					headerArea.setCursor(newCursor);
					cursor = newCursor;
				}
			}
		});

		headerArea.addPaintListener(new PaintListener() {
			public void paintControl(PaintEvent e) {
				TableColumnCore[] columns = lastVisibleColumns;
				if (columns == null) {
					return;
				}

				if (skinProperties != null) {
					e.gc.setForeground(skinProperties.getColor("color.list.header.fg"));
				}

				Rectangle clientArea = headerArea.getClientArea();
				int pos = clientArea.x + ListView.COLUMN_MARGIN_WIDTH;
				int lastExtraSpace = -1;
				for (int i = 0; i < columns.length; i++) {
					int width = columns[i].getWidth();
					String key = columns[i].getTitleLanguageKey();
					String text = MessageText.getString(key, "");
					int align = CoreTableColumn.getSWTAlign(columns[i].getAlignment());

					int drawWidth = width;

					Point size = e.gc.textExtent(text);
					Rectangle bounds;
					if (size.x > drawWidth && lastExtraSpace > 0) {
						int giveSpace = Math.min(lastExtraSpace, size.x - drawWidth);
						bounds = new Rectangle(pos - giveSpace, clientArea.y + 1, drawWidth
								+ giveSpace, clientArea.height);
					} else {
						bounds = new Rectangle(pos, clientArea.y + 1, drawWidth,
								clientArea.height);
					}

					headerArea.setData("Column" + i + "Bounds", bounds);

					if (text.length() > 0) {
						GCStringPrinter.printString(e.gc, text, bounds, false, false, align
								| SWT.TOP);
					}

					int middlePos = bounds.x;
					if (align == SWT.LEFT) {
						lastExtraSpace = bounds.width - size.x;
						middlePos += size.x / 2;
					} else if (align == SWT.CENTER) {
						lastExtraSpace = (bounds.width - size.x) / 2;
						middlePos += bounds.width / 2;
					} else {
						lastExtraSpace = 0;
						middlePos += bounds.width - (size.x / 2);
					}

					if (columns[i].equals(sortColumn)) {
						Image img = sortColumn.isSortAscending() ? imgSortAsc : imgSortDesc;
						if (img != null) {
							Rectangle imgBounds = img.getBounds();
							e.gc.drawImage(img, middlePos - (imgBounds.width / 2),
									bounds.height + bounds.y - imgBounds.height);
						}
					}

					//e.gc.drawLine(pos, bounds.y, pos, bounds.y + bounds.height);
					pos += width + (ListView.COLUMN_MARGIN_WIDTH * 2);
				}
			}
		});
	
public intsize(boolean bIncludeQueue)

		int size = rows.size();

		if (bIncludeQueue) {
			if (dataSourcesToAdd != null) {
				size += dataSourcesToAdd.size();
			}
			if (dataSourcesToRemove != null) {
				size += dataSourcesToRemove.size();
			}
		}
		return size;
	
private booleansortTable(boolean bForce)
Sorts the table

param
bForce
return
true: There were sort order changes (and the visible rows were refreshed)
false: No sort order changes
since
3.0.0.7

		long lTimeStart;
		if (DEBUG_SORTER) {
			log(">>> Sort.. " + (sortColumn.getLastSortValueChange() - lLastSortedOn));
			lTimeStart = System.currentTimeMillis();
		}

		int iFirstChange = -1;
		try {
			row_mon.enter();

			if (sortColumn != null
					&& (bForce || sortColumn.getLastSortValueChange() > lLastSortedOn)) {
				lLastSortedOn = SystemTime.getCurrentTime();

				// 1) Copy rows to array and sort
				// 2) check if any have changed position
				// 3) make row sort permanent (move sorted array back into rows field)
				// 4) tell each row affected to fix itself up

				if (sortColumn != null) {
					String sColumnID = sortColumn.getName();
					for (Iterator iter = rows.iterator(); iter.hasNext();) {
						TableRowCore row = (TableRowCore) iter.next();
						TableCellCore cell = row.getTableCellCore(sColumnID);
						if (cell != null) {
							cell.refresh(true, false, false);
						}
					}
				}

				// Since rows alternate in color, all rows including and after the
				// first changed row need to be notified to change.

				Object[] rowsArray = rows.toArray();
				Arrays.sort(rowsArray, sortColumn);

				//Collections.sort(rows, sortColumn);
				if (DEBUG_SORTER) {
					long lTimeDiff = (System.currentTimeMillis() - lTimeStart);
					if (lTimeDiff > 100) {
						System.out.println("--- Build & Sort took " + lTimeDiff + "ms");
					}
					lTimeStart = System.currentTimeMillis();
				}

				int iNumChanged = 0;
				for (int i = 0; i < rowsArray.length; i++) {
					ListRow row = (ListRow) rowsArray[i];
					if (row != rows.get(i)) {
						iNumChanged++;
						if (iFirstChange < 0) {
							iFirstChange = i;
						}
					}
				}

				List list = Arrays.asList(rowsArray);
				if (list instanceof ArrayList) {
					rows = (ArrayList) list;
				} else {
					rows = new ArrayList(list);
				}

				if (DEBUG_SORTER) {
					log("numChanged " + iNumChanged);
				}
				if (iFirstChange >= 0) {
					for (int i = iFirstChange; i < rows.size(); i++) {
						ListRow row = (ListRow) rows.get(i);
						row.fixupPosition();
					}

					if (DEBUG_SORTER) {
						long lTimeDiff = (System.currentTimeMillis() - lTimeStart);
						System.out.println("Sort made " + iNumChanged + " rows move in "
								+ lTimeDiff + "ms");
					}

					rowShow(getRowFocused());

					refreshVisible(true, true, true);
				}
			}

		} finally {
			row_mon.exit();
		}

		return iFirstChange >= 0;

		// Selection should be okay still.  May need to be moved into view
		// if we want that behaviour
	
public voidtableStructureChanged()

		// force an eventual recalc of visible row widths
		lastClientWidth = 0;
		triggerLifeCycleListener(TableLifeCycleListener.EVENT_DESTROYED);

		removeAllTableRows();

		triggerLifeCycleListener(TableLifeCycleListener.EVENT_INITIALIZED);
	
protected voidtriggerListenerRowAdded(ListRow row)

		for (Iterator iter = listenersCountChange.iterator(); iter.hasNext();) {
			TableCountChangeListener l = (TableCountChangeListener) iter.next();
			l.rowAdded(row);
		}
	
protected voidtriggerListenerRowRemoved(ListRow row)

		for (Iterator iter = listenersCountChange.iterator(); iter.hasNext();) {
			TableCountChangeListener l = (TableCountChangeListener) iter.next();
			l.rowRemoved(row);
		}
	
public voidupdateLanguage()

	
public voidupdateUI()

		refreshTable(false);